Files
break 575d056f54
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled
feat(代理分销提现): 落地扫码注册、提现资料资格与企微终审提现
AUG26-008。

- 迁移 000214–000217:tb_shop 全局唯一且不可修改的随机分销码(含存量回填)、
  tb_agent_distribution_registration 待审批注册记录、tb_withdrawal_qualification 资料版本、
  tb_commission_withdrawal_request_attempt 审批尝试记录,以及提现申请的 latest_*/异常标记列;
  不修改既有迁移,down 在存在本 Change 业务事实或新类型场景行时拒绝破坏性回滚。
- 公开接口 POST /api/c/v1/agent-distribution-registrations:无认证,复用既有短信验证码校验、
  消费与限流;无效分销码、停用上级、验证码无效或已消费统一返回「分销码不可用」且不落库,
  审批通过前不创建店铺、账号或钱包。
- 审批通过才在同一事务内建启用店铺、代理主账号、钱包、上级层级与业务员快照,驳回不建实体,
  重复回调不重复建实体,提交后清理上级下级缓存。
- 提现资料资格按不可变版本保存,替换合同或法人身份证即新增版本并同事务失效旧有效版本;
  超管作废原因必填;代理停用与店铺删除联动失效。
- 提现每次提交或重提新增不可变审批尝试记录并冻结金额;企业微信通过仅一次从冻结扣减、
  保持状态 2 并写 paid_at(不使用状态 4),驳回/cancelled/deleted 仅一次释放,
  通过后撤销不回滚、不重新冻结、只写正交异常标记;加锁顺序统一为申请→尝试→钱包。
- 本地人工终审对已关联审批实例的申请返回状态冲突,approval_instance_id 为空的存量申请保持既有行为,
  不新增任何配置开关。
- 补齐审批业务类型注册点全集:业务类型与场景字段常量、场景 DTO 两处枚举与中文描述、
  场景字段白名单/合法类型/中文名、数据库 CHECK、Worker 决策消费者与装配、审批审计资源映射,
  以及三个新审计资源与 13 个审计动作;失败/拒绝审计改为必达。
- 新增后台路由与 OpenAPI:资格提交/查询/作废、提现申请/重提/详情、店铺详情返回只读分销码。
- 归档本 Change:主 Spec 新增 agent-distribution-withdrawal 能力(5 个 Requirement)。

验证(junhong_cmp_test + Redis DB 6,显式 DB_*,未重置整库):
- 迁移 up → version 217 且 dirty=false → down 3 → up 回 217,fixture 复核残留为 0。
- 受控状态机脚手架 227 项通过 / 0 项失败,覆盖 18 组场景(幂等与乱序回调、资金冻结/释放/重提、
  退款回扣 × 在途提现并发、负向场景拒绝审计与 14 个动作码审计真实落库)。
- gofmt 空、go build/go vet 通过、gendocs 与工作区逐字节一致、context-health 通过、
  openspec validate --strict 通过、doctor healthy;自动化测试按项目决策为 N/A。

运行期前置(未完成,非代码交付物):由超管经 PUT /api/admin/wecom/scenes/{business_type} 为
agent_distribution_approval、withdrawal_qualification_approval、commission_withdrawal_approval
配置启用场景与模板控件映射;未配置时相应提交失败关闭。
2026-09-14 09:45:13 +08:00

35 lines
9.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 1. 分销与注册
- [x] 1.1 为 `tb_shop` 新增全局唯一、不可修改的随机 `distribution_code`(新成对迁移 + 条件唯一索引,编号按实施时最大编号顺延);在 `internal/application/shop/create.go` 建店事务内生成并处理唯一冲突重试;同步 `scripts/migration/lib/shop_sql.py` 使批量导入店铺同样生成分销码;不提供人工指定或编辑入口。
- [x] 1.2 新增 `tb_agent_distribution_registration`(分销码、上级店铺、手机号、密码哈希、状态、审批实例与时间戳)及模型、状态常量、审计资源常量与 registry 条目;不创建 `status=2` 的店铺或账号;同一手机号驳回后再次扫码为新记录。
- [x] 1.3 实现公开接口 `POST /api/c/v1/agent-distribution-registrations`:经 `internal/routes.Register` 注册,且在 `internal/routes/personal.go``Use()` 之前;复用既有验证码校验、消费与限流;锁定上级店铺并校验启用,无效码、停用上级、验证码无效或已消费统一返回“分销码不可用”且不落库;新 Handler 同步 `pkg/openapi/handlers.go``cmd/api/docs.go``cmd/gendocs/main.go` 与 bootstrap 装配。
- [x] 1.4 注册 `agent_distribution_approval` 决策消费者:通过时同一事务建启用店铺、代理主账号、所需钱包、上级层级与上级业务员快照,标记注册记录并写审计,提交后清理上级店铺下级缓存;驳回仅标记不建实体;条件更新保证重复回调不重复建账号、层级或钱包。
- [x] 1.5 新增 `tb_withdrawal_qualification`(主体类型、签约主体代码、法人身份证号、合同与身份证正反面附件、可选营业执照/门头照/发票及其主体代码、状态与失效原因、审批实例)与模型、常量;附件每项单对象键,总数不超过企微 6 附件上限。
- [x] 1.6 实现资格提交、替换、作废与停用失效:`POST /api/admin/shops/:shop_id/withdrawal-qualifications`(仅本人代理店铺)、超管作废 `POST /api/admin/withdrawal-qualifications/:id/void`(原因必填)、代理停用联动失效;替换合同或身份证时同一事务新增版本并使旧有效版本失效;注册 `withdrawal_qualification_approval` 消费者,驳回不影响既有有效版本。
- [x] 1.7 为分销码生成、注册通过/驳回、资格提交/替换/作废/停用失效补齐事务内审计;日志与审计不记录密码、完整证件号或附件内容。
## 2. 提现
- [x] 2.1 新增 `tb_commission_withdrawal_request_attempt``request_id``attempt_no`、金额/手续费/费率/实际到账快照、收款信息、申请级发票快照、提交人、审批实例、释放时间)及唯一约束与索引;为 `tb_commission_withdrawal_request` 追加 `latest_attempt_id``latest_approval_instance_id`、通过时间与异常标记/原因列。
- [x] 2.2 改造 `CreateWithdrawalRequest`:增加有效资格校验;事务内锁定佣金钱包、冻结金额、写尝试记录、创建审批实例(`business_id` = 尝试记录主键)、回填 `latest_*`、写钱包流水与审计;余额不足、资格无效或非本人代理均不创建申请或冻结。
- [x] 2.3 实现驳回后重提:仅已驳回申请可由本人代理修改金额、收款信息与本次发票;事务内先释放旧未结算尝试的冻结,再按新金额冻结并新增尝试记录与新审批实例,历史快照与审批结果不被覆盖,不产生第二笔冻结。
- [x] 2.4 注册 `commission_withdrawal_approval` 终态消费者:通过仅一次从冻结余额扣减、保持 `WithdrawalStatusApproved`=2 并写 `paid_at`;驳回与 `cancelled`/`deleted` 仅一次释放本次尝试冻结并写释放时间;`revoked_after_approved` 不回滚、不重新冻结、不自动重提,写正交异常标记与原因供详情展示;重复、乱序与未知结果由既有交付租约与查询恢复收敛。
- [x] 2.5 本地人工终审边界:`internal/service/commission_withdrawal` 的通过/驳回在申请已关联审批实例时拒绝并返回状态冲突,不新增任何配置开关;`approval_instance_id` 为空的存量申请保持既有兼容处理;错误交全局 ErrorHandler。
- [x] 2.6 注册资格、提现申请与提现详情的后台路由及 OpenAPI保障自身店铺与数据范围校验公开接口不得承载资格、提现或后台查询操作。
## 3. 审批业务类型注册点
- [x] 3.1 `pkg/constants/approval.go`:新增 `agent_distribution_approval``withdrawal_qualification_approval``commission_withdrawal_approval` 三个业务类型常量与各场景业务字段常量,注释写明 `business_id` 取值语义。
- [x] 3.2 `internal/model/dto/wecom_scene_dto.go`:两处 `business_type` enum 与中文 description 同步新增三个类型ENG-DTO-001
- [x] 3.3 `internal/application/wecom/scene.go``sceneBusinessFields` 白名单、`validApprovalBusinessType`、业务类型中文名三处新增。
- [x] 3.4 新成对迁移扩展 `chk_wecom_approval_scene_business` 纳入三个新类型;`down` 在存在新类型场景行时拒绝破坏性回滚并报明确异常;不修改既有迁移,编号按实施时最大编号顺延。
- [x] 3.5 `cmd/worker/main.go`:新增三个决策 handler 的依赖装配并注册到 `BusinessDecisionHandler` dispatcher缺注册时按既有规则返回服务不可用
- [x] 3.6 `internal/infrastructure/audit/approval.go``approvalBusinessResource` 新增三个业务类型的资源映射(缺映射会 fail-closed`pkg/constants/audit.go``internal/infrastructure/audit/registry.go` 新增分销注册记录、资格资料版本、提现审批尝试记录三个审计资源(提现单资源已存在,无需新增)。
- [ ] 3.7 运行期前置:上线前由超级管理员经 `PUT /api/admin/wecom/scenes/{business_type}` 为三个业务类型配置启用场景与模板控件映射;迁移不写入模板,未配置时相应提交失败关闭。**待维护者执行**:配置 `agent_distribution_approval``withdrawal_qualification_approval``commission_withdrawal_approval` 的启用场景与控件映射(资格 6 个附件控件对应 `file_list`,必填合同/法人身份证正反面、可选营业执照/门头照/发票);验收证据为 `GET /api/admin/wecom/scenes` 返回三类型启用记录及 `control_mapping`,字段清单经 `GET /api/admin/wecom/scenes/{business_type}/fields` 核对。本项为运行期配置数据,不是代码交付物,故未勾选。
## 4. 验证
- [x] 4.1 按 ENG-TEST-001 在 `junhong_cmp_test` PostgreSQL + Redis DB 6 验证:迁移 `up/down/up`;从本地工作区以显式 `DB_*` 参数执行 `scripts/migrate.sh`;仅创建、删除本 Change 自己的 fixture禁止重置整库。验证以显式 `DB_*``junhong_cmp_test`)执行 `./scripts/migrate.sh up`214/215/216/217 全部成功)→ `version` 为 217 且 `dirty=false``down 3` → 再 `up` 回 217`down` 守卫按设计拒绝破坏性回滚(存在 `tb_agent_distribution_registration` 行时 000214 拒绝、存在新类型场景行时 000216 拒绝),因此整库回退只能到 `000214`,发布说明写明该边界。验证期间只创建、删除本 Change 自己的 fixture`shop_code LIKE 'AUG26008%'`、三张新表、13 个新增动作码审计与资源行),复核残留:新表与审计残留均为 0`tb_shop` 无空分销码且全局唯一;未重置整库。
- [x] 4.2 受控验证本地状态机:以 Redis 预置验证码DB 6`approval.Port.Prepare` 与受控 `TerminalDecisionEvent` 驱动业务消费者,验证分销码唯一与停用门禁、注册通过/驳回与重复回调、资格替换与作废、提现冻结/重提/通过/驳回/取消/删除/通过后撤销、本地终审拒绝与存量兼容、待审提现释放接缝不调用企业微信、短信、对象存储或支付网络。验证临时受控脚手架Redis DB 6 显式覆盖,`.env.local` 的 DB 7 不适用)驱动真实业务消费者,结果 **227 项通过 / 0 项失败**,分 18 组:分销码唯一/随机/不可修改/冲突重试与非事务句柄守卫;公开注册验证码无效与已消费、无效码、停用上级统一拒绝且不落库;注册通过建店+账号+钱包+层级+业务员快照且新码≠上级码并可续开下级,重复回调不重复建实体,驳回不建实体且同手机号可再注册;资格提交/替换失效/驳回不改写历史/超管作废/停用与删店联动失效;提现冻结/通过(状态保持 2 且写 `paid_at`,不使用 4/驳回/cancelled/deleted/重提单笔冻结/通过后撤销不回滚;释放接缝先释放后扣减、重复释放不重复入账;本地终审拒绝新申请与存量兼容;退款佣金回扣 × 在途企微提现(修复前复现 8 项失败);统一加锁序下 6 轮并发无死锁;负向场景拒绝审计与 14 个动作码审计真实落库、恰一个主要资源、失败/拒绝审计不再被吞(`auditfailure` 日志 0 行);数据范围与脱敏投影。脚手架为一次性产物,按清理要求已删除,未在仓库留下测试入口(自动化测试按项目决策为 N/A
- [x] 4.3 运行 `gofmt -w``go build ./cmd/api ./cmd/worker``go run cmd/gendocs/main.go``openspec validate add-agent-distribution-withdrawal-qualification --strict``openspec doctor --json``./scripts/context-health.sh`;自动化测试按项目决策为 N/A。验证`gofmt -l` 对全部变更 Go 文件输出为空;`go build ./cmd/api ./cmd/worker` 无错误;`go vet ./internal/... ./pkg/...` 无输出;`go run cmd/gendocs/main.go` 重新生成 `docs/admin-openapi.yaml`,与工作区文件逐字节一致,新增路由与 6 值业务类型枚举齐全,既有路由未被改写或删除;`./scripts/context-health.sh` 输出「Context 健康检查通过」;`openspec validate add-agent-distribution-withdrawal-qualification --strict` 输出 `Change 'add-agent-distribution-withdrawal-qualification' is valid``openspec doctor --json``"healthy": true`