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
配置启用场景与模板控件映射;未配置时相应提交失败关闭。
150 lines
13 KiB
Go
150 lines
13 KiB
Go
package dto
|
||
|
||
// CreateAgentDistributionRegistrationReq 公开扫码注册请求(无需认证)。
|
||
type CreateAgentDistributionRegistrationReq struct {
|
||
DistributionCode string `json:"distribution_code" validate:"required,min=1,max=32" required:"true" minLength:"1" maxLength:"32" description:"上级代理店铺的分销码"`
|
||
Phone string `json:"phone" validate:"required,len=11" required:"true" minLength:"11" maxLength:"11" description:"注册手机号,必须已完成短信验证码校验"`
|
||
Code string `json:"code" validate:"required,len=6" required:"true" minLength:"6" maxLength:"6" description:"短信验证码,校验成功即消费"`
|
||
Password string `json:"password" validate:"required,min=6,max=64" required:"true" minLength:"6" maximum:"64" description:"代理主账号登录密码"`
|
||
Username string `json:"username" validate:"required,min=3,max=50" required:"true" minLength:"3" maxLength:"50" description:"代理主账号用户名"`
|
||
ShopName string `json:"shop_name" validate:"required,min=1,max=100" required:"true" minLength:"1" maxLength:"100" description:"申请店铺名称"`
|
||
ShopCode string `json:"shop_code" validate:"required,min=1,max=50" required:"true" minLength:"1" maxLength:"50" description:"申请店铺编号"`
|
||
ContactName string `json:"contact_name" validate:"omitempty,max=50" maxLength:"50" description:"联系人姓名"`
|
||
Province string `json:"province" validate:"omitempty,max=50" maxLength:"50" description:"省份"`
|
||
City string `json:"city" validate:"omitempty,max=50" maxLength:"50" description:"城市"`
|
||
District string `json:"district" validate:"omitempty,max=50" maxLength:"50" description:"区县"`
|
||
Address string `json:"address" validate:"omitempty,max=255" maxLength:"255" description:"详细地址"`
|
||
}
|
||
|
||
// CreateAgentDistributionRegistrationResp 公开扫码注册响应。
|
||
// 仅返回注册记录标识与待审批状态,不返回任何账号凭证或审批实例明细。
|
||
type CreateAgentDistributionRegistrationResp struct {
|
||
ID uint `json:"id" description:"待审批注册记录ID"`
|
||
Status int `json:"status" description:"注册记录状态 (0:待审批, 1:已通过, 2:已驳回)"`
|
||
StatusName string `json:"status_name" description:"状态名称(中文)"`
|
||
}
|
||
|
||
// SubmitWithdrawalQualificationReq 提交或替换提现资料资格请求。
|
||
type SubmitWithdrawalQualificationReq struct {
|
||
ShopID uint `json:"-" params:"shop_id" path:"shop_id" validate:"required" description:"店铺ID"`
|
||
SubjectType string `json:"subject_type" validate:"required,oneof=enterprise personal" required:"true" enum:"enterprise,personal" description:"签约主体类型 (enterprise:企业, personal:个人)"`
|
||
SubjectCode string `json:"subject_code" validate:"omitempty,max=64" maxLength:"64" description:"签约主体代码:企业填统一社会信用代码;个人留空并按法人身份证号取值"`
|
||
LegalPersonIDCard string `json:"legal_person_id_card" validate:"required,min=1,max=64" required:"true" minLength:"1" maxLength:"64" description:"法人身份证号"`
|
||
ContractFileKey string `json:"contract_file_key" validate:"required,min=1,max=255" required:"true" minLength:"1" maxLength:"255" description:"合同附件对象存储 Key(单个对象)"`
|
||
IDCardFrontFileKey string `json:"id_card_front_file_key" validate:"required,min=1,max=255" required:"true" minLength:"1" maxLength:"255" description:"法人身份证正面附件对象存储 Key(单个对象)"`
|
||
IDCardBackFileKey string `json:"id_card_back_file_key" validate:"required,min=1,max=255" required:"true" minLength:"1" maxLength:"255" description:"法人身份证反面附件对象存储 Key(单个对象)"`
|
||
BusinessLicenseFileKey string `json:"business_license_file_key" validate:"omitempty,max=255" maxLength:"255" description:"营业执照附件对象存储 Key(单个对象,可选)"`
|
||
ShopFrontFileKey string `json:"shop_front_file_key" validate:"omitempty,max=255" maxLength:"255" description:"门头照附件对象存储 Key(单个对象,可选)"`
|
||
InvoiceFileKey string `json:"invoice_file_key" validate:"omitempty,max=255" maxLength:"255" description:"发票附件对象存储 Key(单个对象,仅企业可选)"`
|
||
InvoiceTitle string `json:"invoice_title" validate:"omitempty,max=200" maxLength:"200" description:"发票抬头,仅企业填写,必须与合同主体一致"`
|
||
InvoiceSubjectCode string `json:"invoice_subject_code" validate:"omitempty,max=64" maxLength:"64" description:"发票统一社会信用代码,必须与签约主体代码一致"`
|
||
}
|
||
|
||
// SubmitWithdrawalQualificationResp 提现资料资格提交响应。
|
||
type SubmitWithdrawalQualificationResp struct {
|
||
ID uint `json:"id" description:"资料版本ID"`
|
||
Status int `json:"status" description:"资料版本状态 (0:待审批, 1:已通过有效, 2:已驳回, 3:已失效)"`
|
||
StatusName string `json:"status_name" description:"状态名称(中文)"`
|
||
}
|
||
|
||
// VoidWithdrawalQualificationReq 超级管理员作废提现资料资格请求。
|
||
type VoidWithdrawalQualificationReq struct {
|
||
Reason string `json:"reason" validate:"required,min=1,max=500" required:"true" minLength:"1" maxLength:"500" description:"作废原因,必填且可在资格详情查询"`
|
||
}
|
||
|
||
// VoidWithdrawalQualificationParams 作废提现资料资格路径与请求参数。
|
||
type VoidWithdrawalQualificationParams struct {
|
||
IDReq
|
||
VoidWithdrawalQualificationReq
|
||
}
|
||
|
||
// WithdrawalQualificationListReq 提现资料资格列表查询请求。
|
||
type WithdrawalQualificationListReq struct {
|
||
ShopID uint `json:"-" params:"shop_id" path:"shop_id" validate:"required" required:"true" description:"店铺ID(路径参数,必填;查询前校验当前账号对该店铺的数据范围)"`
|
||
Page int `query:"page" validate:"omitempty,min=1" minimum:"1" description:"页码(默认1)"`
|
||
PageSize int `query:"page_size" validate:"omitempty,min=1,max=100" minimum:"1" maximum:"100" description:"每页数量(默认20,最大100)"`
|
||
Status *int `query:"status" validate:"omitempty,oneof=0 1 2 3" enum:"0,1,2,3" description:"资料版本状态 (0:待审批, 1:已通过有效, 2:已驳回, 3:已失效)"`
|
||
}
|
||
|
||
// WithdrawalQualificationItem 提现资料资格版本项。
|
||
// 证件号按脱敏值返回,附件仅返回对象存储 Key 引用。
|
||
type WithdrawalQualificationItem struct {
|
||
ID uint `json:"id" description:"资料版本ID"`
|
||
ShopID uint `json:"shop_id" description:"所属店铺ID"`
|
||
ShopName string `json:"shop_name" description:"所属店铺名称"`
|
||
SubjectType string `json:"subject_type" description:"签约主体类型 (enterprise:企业, personal:个人)"`
|
||
SubjectTypeName string `json:"subject_type_name" description:"签约主体类型名称(中文)"`
|
||
SubjectCodeMasked string `json:"subject_code_masked" description:"脱敏后的签约主体代码"`
|
||
LegalPersonIDCardMask string `json:"legal_person_id_card_masked" description:"脱敏后的法人身份证号"`
|
||
ContractFileKey string `json:"contract_file_key" description:"合同附件对象存储 Key"`
|
||
IDCardFrontFileKey string `json:"id_card_front_file_key" description:"法人身份证正面附件对象存储 Key"`
|
||
IDCardBackFileKey string `json:"id_card_back_file_key" description:"法人身份证反面附件对象存储 Key"`
|
||
BusinessLicenseFileKey string `json:"business_license_file_key,omitempty" description:"营业执照附件对象存储 Key"`
|
||
ShopFrontFileKey string `json:"shop_front_file_key,omitempty" description:"门头照附件对象存储 Key"`
|
||
InvoiceFileKey string `json:"invoice_file_key,omitempty" description:"发票附件对象存储 Key"`
|
||
InvoiceTitle string `json:"invoice_title,omitempty" description:"发票抬头"`
|
||
Status int `json:"status" description:"状态 (0:待审批, 1:已通过有效, 2:已驳回, 3:已失效)"`
|
||
StatusName string `json:"status_name" description:"状态名称(中文)"`
|
||
InvalidReason string `json:"invalid_reason,omitempty" description:"失效或作废原因"`
|
||
InvalidatedAt string `json:"invalidated_at,omitempty" description:"失效时间"`
|
||
ApprovalInstanceID uint `json:"approval_instance_id" description:"关联的通用审批实例ID,0 表示尚未关联"`
|
||
ApprovalStatus int `json:"approval_status" description:"通用审批实例状态 (0:提交中, 1:审批中, 2:已通过, 3:已拒绝, 4:已撤销, 5:通过后撤销, 6:已删除, 7:提交失败, 8:提交结果未知)"`
|
||
ApprovalStatusName string `json:"approval_status_name" description:"通用审批状态名称(中文)"`
|
||
CreatedAt string `json:"created_at" description:"创建时间"`
|
||
UpdatedAt string `json:"updated_at" description:"最近更新时间"`
|
||
}
|
||
|
||
// WithdrawalQualificationPageResult 提现资料资格分页响应。
|
||
type WithdrawalQualificationPageResult struct {
|
||
Items []WithdrawalQualificationItem `json:"items" description:"资料版本列表(按版本从新到旧)"`
|
||
Total int64 `json:"total" description:"总记录数"`
|
||
Page int `json:"page" description:"当前页码"`
|
||
Size int `json:"size" description:"每页数量"`
|
||
}
|
||
|
||
// ResubmitWithdrawalReq 驳回后重提提现申请请求。
|
||
type ResubmitWithdrawalReq struct {
|
||
ShopID uint `json:"-" params:"shop_id" path:"shop_id" validate:"required" description:"店铺ID"`
|
||
ID uint `json:"-" params:"id" path:"id" validate:"required" description:"提现申请ID"`
|
||
Amount int64 `json:"amount" validate:"required,min=1" required:"true" minimum:"1" description:"提现金额(分)"`
|
||
WithdrawalMethod string `json:"withdrawal_method" validate:"required,oneof=alipay" required:"true" enum:"alipay" description:"收款类型"`
|
||
AccountName string `json:"account_name" validate:"required,max=50" required:"true" maximum:"50" description:"收款人姓名"`
|
||
AccountNumber string `json:"account_number" validate:"required,max=100" required:"true" maximum:"100" description:"收款账号"`
|
||
InvoiceKeys []string `json:"invoice_keys" description:"本次申请级发票对象存储 Key 列表;仅企业主体有效资格可提交"`
|
||
}
|
||
|
||
// ShopWithdrawalRequestDetailReq 提现申请详情路径参数。
|
||
type ShopWithdrawalRequestDetailReq struct {
|
||
ShopID uint `json:"-" params:"shop_id" path:"shop_id" validate:"required" description:"店铺ID"`
|
||
ID uint `json:"-" params:"id" path:"id" validate:"required" description:"提现申请ID"`
|
||
}
|
||
|
||
// WithdrawalRequestAttemptItem 提现审批尝试记录项。
|
||
type WithdrawalRequestAttemptItem struct {
|
||
ID uint `json:"id" description:"审批尝试记录ID"`
|
||
AttemptNo int `json:"attempt_no" description:"第几次提交,从 1 递增"`
|
||
Amount int64 `json:"amount" description:"本次提现金额(分)"`
|
||
Fee int64 `json:"fee" description:"本次手续费(分)"`
|
||
FeeRate int64 `json:"fee_rate" description:"本次手续费率(基点,100=1%)"`
|
||
ActualAmount int64 `json:"actual_amount" description:"本次实际到账金额(分)"`
|
||
WithdrawalMethod string `json:"withdrawal_method" description:"本次收款方式"`
|
||
SubmittedByID uint `json:"submitted_by_account_id" description:"本次提交账号ID"`
|
||
ApprovalInstanceID uint `json:"approval_instance_id" description:"本次尝试关联的通用审批实例ID,0 表示尚未关联"`
|
||
ApprovalStatus int `json:"approval_status" description:"通用审批实例状态 (0:提交中, 1:审批中, 2:已通过, 3:已拒绝, 4:已撤销, 5:通过后撤销, 6:已删除, 7:提交失败, 8:提交结果未知)"`
|
||
ApprovalStatusName string `json:"approval_status_name" description:"通用审批状态名称(中文)"`
|
||
ReleasedAt string `json:"released_at,omitempty" description:"本次冻结释放时间,空表示冻结仍未结算"`
|
||
CreatedAt string `json:"created_at" description:"创建时间"`
|
||
}
|
||
|
||
// ShopWithdrawalRequestDetailResp 提现申请详情响应。
|
||
type ShopWithdrawalRequestDetailResp struct {
|
||
ShopWithdrawalRequestItem
|
||
LatestAttemptID uint `json:"latest_attempt_id" description:"最新审批尝试记录ID,0 表示尚未接入企业微信审批"`
|
||
LatestApprovalInstanceID uint `json:"latest_approval_instance_id" description:"最新通用审批实例ID,0 表示尚未接入"`
|
||
ApprovalInstanceID uint `json:"approval_instance_id" description:"首次接入企业微信审批的审批实例ID,0 表示存量申请仍走本地人工终审"`
|
||
AnomalyFlag int `json:"anomaly_flag" description:"正交异常标记 (0:无异常, 1:通过后撤销)"`
|
||
AnomalyName string `json:"anomaly_name" description:"异常名称(中文)"`
|
||
AnomalyReason string `json:"anomaly_reason,omitempty" description:"异常原因,供人工处理"`
|
||
Attempts []WithdrawalRequestAttemptItem `json:"attempts" description:"审批尝试记录,按提交次序倒序;历史尝试与审批结果不被覆盖"`
|
||
}
|