Files
junhong_cmp_fiber/internal/model/dto/agent_distribution_dto.go
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

150 lines
13 KiB
Go
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.
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:"关联的通用审批实例ID0 表示尚未关联"`
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:"本次尝试关联的通用审批实例ID0 表示尚未关联"`
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:"最新审批尝试记录ID0 表示尚未接入企业微信审批"`
LatestApprovalInstanceID uint `json:"latest_approval_instance_id" description:"最新通用审批实例ID0 表示尚未接入"`
ApprovalInstanceID uint `json:"approval_instance_id" description:"首次接入企业微信审批的审批实例ID0 表示存量申请仍走本地人工终审"`
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:"审批尝试记录,按提交次序倒序;历史尝试与审批结果不被覆盖"`
}