feat(代理分销提现): 落地扫码注册、提现资料资格与企微终审提现
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled
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
配置启用场景与模板控件映射;未配置时相应提交失败关闭。
This commit is contained in:
@@ -357,6 +357,36 @@ func NewRegistry() *Registry {
|
||||
withdrawalRequested := commissionWithdrawalAction(constants.AuditActionCommissionWithdrawalRequested, "提交佣金提现申请")
|
||||
withdrawalApproved := commissionWithdrawalAction(constants.AuditActionCommissionWithdrawalApproved, "通过佣金提现申请")
|
||||
withdrawalRejected := commissionWithdrawalAction(constants.AuditActionCommissionWithdrawalRejected, "驳回佣金提现申请")
|
||||
// 提现审批尝试的提交由代理后台入口触发(account/admin_api),
|
||||
// 终态由 Outbox 消费任务触发(system_task/worker),因此必须使用含两类入口的 distributionAction,
|
||||
// 而不是仅允许后台账号入口的 commissionWithdrawalAction。
|
||||
withdrawalAttemptSubmitted := distributionAction(
|
||||
constants.AuditActionCommissionWithdrawalAttemptSubmitted, "提交佣金提现审批尝试", constants.AuditResourceCommissionWithdrawal)
|
||||
withdrawalAttemptApproved := distributionAction(
|
||||
constants.AuditActionCommissionWithdrawalAttemptApproved, "企业微信通过佣金提现", constants.AuditResourceCommissionWithdrawal)
|
||||
withdrawalAttemptClosed := distributionAction(
|
||||
constants.AuditActionCommissionWithdrawalAttemptClosed, "企业微信驳回或撤销佣金提现", constants.AuditResourceCommissionWithdrawal)
|
||||
withdrawalAnomalyFlagged := distributionAction(
|
||||
constants.AuditActionCommissionWithdrawalAnomalyFlagged, "企微通过后撤销提现异常标记", constants.AuditResourceCommissionWithdrawal)
|
||||
distributionRegistrationApproved := distributionAction(
|
||||
constants.AuditActionAgentDistributionRegistrationApproved, "企业微信通过代理扫码注册", constants.AuditResourceAgentDistributionRegistration)
|
||||
distributionRegistrationRejected := distributionAction(
|
||||
constants.AuditActionAgentDistributionRegistrationRejected, "企业微信驳回代理扫码注册", constants.AuditResourceAgentDistributionRegistration)
|
||||
qualificationSubmitted := distributionAction(
|
||||
constants.AuditActionWithdrawalQualificationSubmitted, "提交提现资料资格", constants.AuditResourceWithdrawalQualification)
|
||||
qualificationApproved := distributionAction(
|
||||
constants.AuditActionWithdrawalQualificationApproved, "提现资料资格企业微信通过", constants.AuditResourceWithdrawalQualification)
|
||||
qualificationRejected := distributionAction(
|
||||
constants.AuditActionWithdrawalQualificationRejected, "提现资料资格企业微信驳回", constants.AuditResourceWithdrawalQualification)
|
||||
qualificationVoided := distributionAction(
|
||||
constants.AuditActionWithdrawalQualificationVoided, "作废提现资料资格", constants.AuditResourceWithdrawalQualification)
|
||||
qualificationInvalidated := distributionAction(
|
||||
constants.AuditActionWithdrawalQualificationInvalidated, "代理停用联动失效提现资料资格", constants.AuditResourceWithdrawalQualification)
|
||||
// 创建前拒绝:没有业务实体可作主要资源,以店铺为主要资源,保证拒绝事实可追溯且主资源唯一。
|
||||
withdrawalAttemptRejected := distributionAction(
|
||||
constants.AuditActionCommissionWithdrawalAttemptRejected, "提现提交被拒绝", constants.AuditResourceShop)
|
||||
qualificationSubmitRejected := distributionAction(
|
||||
constants.AuditActionWithdrawalQualificationSubmitRejected, "资格提交被拒绝", constants.AuditResourceShop)
|
||||
return &Registry{
|
||||
actionsByOperation: map[string]ActionDefinition{
|
||||
constants.AuditOperationSystemConfigUpdate: systemConfigUpdated,
|
||||
@@ -611,6 +641,19 @@ func NewRegistry() *Registry {
|
||||
constants.AuditActionCommissionWithdrawalRequested: withdrawalRequested,
|
||||
constants.AuditActionCommissionWithdrawalApproved: withdrawalApproved,
|
||||
constants.AuditActionCommissionWithdrawalRejected: withdrawalRejected,
|
||||
constants.AuditActionCommissionWithdrawalAttemptSubmitted: withdrawalAttemptSubmitted,
|
||||
constants.AuditActionCommissionWithdrawalAttemptApproved: withdrawalAttemptApproved,
|
||||
constants.AuditActionCommissionWithdrawalAttemptClosed: withdrawalAttemptClosed,
|
||||
constants.AuditActionCommissionWithdrawalAnomalyFlagged: withdrawalAnomalyFlagged,
|
||||
constants.AuditActionAgentDistributionRegistrationApproved: distributionRegistrationApproved,
|
||||
constants.AuditActionAgentDistributionRegistrationRejected: distributionRegistrationRejected,
|
||||
constants.AuditActionWithdrawalQualificationSubmitted: qualificationSubmitted,
|
||||
constants.AuditActionWithdrawalQualificationApproved: qualificationApproved,
|
||||
constants.AuditActionWithdrawalQualificationRejected: qualificationRejected,
|
||||
constants.AuditActionWithdrawalQualificationVoided: qualificationVoided,
|
||||
constants.AuditActionWithdrawalQualificationInvalidated: qualificationInvalidated,
|
||||
constants.AuditActionCommissionWithdrawalAttemptRejected: withdrawalAttemptRejected,
|
||||
constants.AuditActionWithdrawalQualificationSubmitRejected: qualificationSubmitRejected,
|
||||
},
|
||||
resources: map[string]ResourceDefinition{
|
||||
constants.AuditResourceAccount: {
|
||||
@@ -838,6 +881,18 @@ func NewRegistry() *Registry {
|
||||
Type: constants.AuditResourceCommissionWithdrawal, Name: "佣金提现单",
|
||||
IdentityFields: []string{"id", "withdrawal_no", "shop_id", "applicant_id", "amount", "fee", "fee_rate", "actual_amount", "withdrawal_method", "payment_type", "status", "processor_id", "processed_at", "paid_at"},
|
||||
},
|
||||
constants.AuditResourceCommissionWithdrawalAttempt: {
|
||||
Type: constants.AuditResourceCommissionWithdrawalAttempt, Name: "提现审批尝试记录",
|
||||
IdentityFields: []string{"id", "request_id", "attempt_no", "amount", "fee", "actual_amount", "approval_instance_id", "released_at"},
|
||||
},
|
||||
constants.AuditResourceAgentDistributionRegistration: {
|
||||
Type: constants.AuditResourceAgentDistributionRegistration, Name: "代理扫码注册记录",
|
||||
IdentityFields: []string{"id", "parent_shop_id", "distribution_code_masked", "phone_masked", "username", "shop_code", "status", "approval_instance_id", "reject_reason"},
|
||||
},
|
||||
constants.AuditResourceWithdrawalQualification: {
|
||||
Type: constants.AuditResourceWithdrawalQualification, Name: "提现资料资格版本",
|
||||
IdentityFields: []string{"id", "shop_id", "subject_type", "subject_code_masked", "status", "approval_instance_id", "invalid_reason"},
|
||||
},
|
||||
constants.AuditResourceWeComApplication: {
|
||||
Type: constants.AuditResourceWeComApplication, Name: "企业微信应用配置",
|
||||
IdentityFields: []string{"id", "corp_id", "agent_id", "name", "status", "credentials_configured"},
|
||||
@@ -1099,6 +1154,9 @@ func commissionWithdrawalAction(code, name string) ActionDefinition {
|
||||
RequireTransaction: true, DefaultVisibility: constants.AuditSubjectResult,
|
||||
AllowedVisibility: []string{constants.AuditSubjectInternalOnly, constants.AuditSubjectResult, constants.AuditSubjectDetail},
|
||||
SubjectFields: []string{"amount", "fee", "actual_amount", "withdrawal_method", "payment_type", "status"},
|
||||
// 同一动作还由退款佣金回扣的自动拒绝提现路径发出(Worker 消费任务,system_task/worker),
|
||||
// 因此必须同时允许该入口,否则该审计会以「操作者或入口不符合动作注册规则」被丢弃。
|
||||
AllowedOrigins: []ActionOrigin{{Actor: constants.AuditActorSystemTask, Source: constants.AuditSourceWorker}},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1286,6 +1344,23 @@ func employeeCollectionAction(code, name, primaryResource string) ActionDefiniti
|
||||
}
|
||||
}
|
||||
|
||||
// distributionAction 定义分销注册、提现资格与提现终审动作;提交由后台账号或公开个人接口触发,
|
||||
// 终态由 Outbox 消费、渠道回调或恢复任务触发,均要求与业务事实同事务。
|
||||
func distributionAction(code, name, primaryResource string) ActionDefinition {
|
||||
return ActionDefinition{
|
||||
Code: code, Name: name, Category: constants.AuditCategoryBusiness, Risk: constants.AuditRiskHigh,
|
||||
PrimaryResource: primaryResource, RequireTransaction: true,
|
||||
DefaultVisibility: constants.AuditSubjectResult,
|
||||
AllowedVisibility: []string{constants.AuditSubjectInternalOnly, constants.AuditSubjectResult},
|
||||
AllowedOrigins: []ActionOrigin{
|
||||
{Actor: constants.AuditActorAccount, Source: constants.AuditSourceAdminAPI},
|
||||
{Actor: constants.AuditActorSystemTask, Source: constants.AuditSourceWorker},
|
||||
{Actor: constants.AuditActorExternalSystem, Source: constants.AuditSourceCallback},
|
||||
{Actor: constants.AuditActorScheduledJob, Source: constants.AuditSourceScheduler},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Action 返回已注册动作定义。
|
||||
func (r *Registry) Action(code string) (ActionDefinition, bool) {
|
||||
if r == nil {
|
||||
|
||||
Reference in New Issue
Block a user