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:
149
internal/model/dto/agent_distribution_dto.go
Normal file
149
internal/model/dto/agent_distribution_dto.go
Normal file
@@ -0,0 +1,149 @@
|
||||
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:"审批尝试记录,按提交次序倒序;历史尝试与审批结果不被覆盖"`
|
||||
}
|
||||
@@ -86,9 +86,12 @@ func (r *UpdateShopRequest) UnmarshalJSON(data []byte) error {
|
||||
|
||||
// ShopResponse 店铺响应
|
||||
type ShopResponse struct {
|
||||
ID uint `json:"id" description:"店铺ID"`
|
||||
ShopName string `json:"shop_name" description:"店铺名称"`
|
||||
ShopCode string `json:"shop_code" description:"店铺编号"`
|
||||
ID uint `json:"id" description:"店铺ID"`
|
||||
ShopName string `json:"shop_name" description:"店铺名称"`
|
||||
ShopCode string `json:"shop_code" description:"店铺编号"`
|
||||
// DistributionCode 是创建时生成的全局唯一随机分销码,创建后不可修改。
|
||||
// 该字段为只读;它是该店铺作为上级被扫码注册的唯一入口标识,二维码只编码该码。
|
||||
DistributionCode string `json:"distribution_code" description:"分销码(全局唯一、创建后不可修改的只读随机码;该店铺作为上级被扫码注册的唯一入口标识)"`
|
||||
ParentID *uint `json:"parent_id,omitempty" description:"上级店铺ID"`
|
||||
ParentShopName string `json:"parent_shop_name,omitempty" description:"上级店铺名称"`
|
||||
BusinessOwnerAccountID *uint `json:"business_owner_account_id" description:"平台业务员账号ID,null 表示未归属"`
|
||||
|
||||
@@ -23,7 +23,7 @@ type InspectWeComTemplateParams struct {
|
||||
|
||||
// WeComBusinessFieldListParams 查询企业微信审批场景可映射字段路径参数。
|
||||
type WeComBusinessFieldListParams struct {
|
||||
BusinessType string `path:"business_type" required:"true" enum:"refund_approval,offline_recharge_approval,employee_collection_approval" description:"业务类型 (refund_approval:退款审批, offline_recharge_approval:员工线下代充值审批, employee_collection_approval:员工代收款核销审批)"`
|
||||
BusinessType string `path:"business_type" required:"true" enum:"refund_approval,offline_recharge_approval,employee_collection_approval,agent_distribution_approval,withdrawal_qualification_approval,commission_withdrawal_approval" description:"业务类型 (refund_approval:退款审批, offline_recharge_approval:员工线下代充值审批, employee_collection_approval:员工代收款核销审批, agent_distribution_approval:代理扫码分销注册审批, withdrawal_qualification_approval:提现资料资格审批, commission_withdrawal_approval:佣金提现终审)"`
|
||||
}
|
||||
|
||||
// WeComBusinessFieldResponse 企业微信审批场景可映射业务字段响应。
|
||||
@@ -67,7 +67,7 @@ type SaveWeComApprovalSceneRequest struct {
|
||||
|
||||
// SaveWeComApprovalSceneParams 保存企业微信审批场景路径与请求参数。
|
||||
type SaveWeComApprovalSceneParams struct {
|
||||
BusinessType string `path:"business_type" required:"true" enum:"refund_approval,offline_recharge_approval,employee_collection_approval" description:"业务类型 (refund_approval:退款审批, offline_recharge_approval:员工线下代充值审批, employee_collection_approval:员工代收款核销审批);可先调用 GET /api/admin/wecom/scenes/{business_type}/fields 查询允许映射的业务字段"`
|
||||
BusinessType string `path:"business_type" required:"true" enum:"refund_approval,offline_recharge_approval,employee_collection_approval,agent_distribution_approval,withdrawal_qualification_approval,commission_withdrawal_approval" description:"业务类型 (refund_approval:退款审批, offline_recharge_approval:员工线下代充值审批, employee_collection_approval:员工代收款核销审批, agent_distribution_approval:代理扫码分销注册审批, withdrawal_qualification_approval:提现资料资格审批, commission_withdrawal_approval:佣金提现终审);可先调用 GET /api/admin/wecom/scenes/{business_type}/fields 查询允许映射的业务字段"`
|
||||
SaveWeComApprovalSceneRequest
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user