feat(收口): 补齐 8 月迭代缺口并同步 Spec 与证据链
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled

- 新增六对成对迁移 000232–000237:H5 弹窗类型、退款结算标识与申请人备注、优先轮询事实字段与两个新终态、通道阈值命中留痕、手机号最近解绑人、提现资格校验留痕
- 退款:原因必填与申请人备注、来源支付与渠道流水冻结、线下处理流水号补录审计、按订单查询可选退款方式、企微审批材料补齐且新增字段缺失映射即明确失败
- 优先轮询:人工关闭、有效期到期独立周期任务、失败与过期人工重触发、事实字段与异常重试查询、资产解析端点只读投影
- 通道阈值:命中事实同事务留痕与命中记录查询;员工账单:列表筛选与详情投影;商户池:列表投影与统计周期语义;H5:弹窗类型与类别排序
- 手机号:有效关联数量与最近解绑人、短信验证码失败次数限制;导出:佣金明细十五列与报表序号列
- 时间筛选:三处新增筛选纳入统一严格解析契约,员工账单产生时间参数改名
- 同步 12 份主 Spec 需求、两端点与异步任务证据链,门禁 context-health 与 OpenSpec 校验通过
This commit is contained in:
2026-09-18 15:34:29 +08:00
parent 5e78809b93
commit 5ed6b39deb
142 changed files with 7878 additions and 964 deletions

View File

@@ -101,7 +101,7 @@ func (s *RegistrationService) Register(
Status: constants.AgentDistributionRegistrationStatusPending,
}
submitterSnapshot, requestSnapshot, err := approvalSnapshots(submitter.ID, submitter.Username,
registrationApprovalForm(normalized, parent))
registrationApprovalForm(normalized, parent, s.parentBusinessOwnerName(ctx, parent)))
if err != nil {
return nil, err
}
@@ -252,9 +252,14 @@ func (s *RegistrationService) findDistributionParent(ctx context.Context, distri
}
// verificationFailure 把验证码校验失败转换为对外错误,保留验证码服务给出的可定位提示。
// 限流错误码必须原样保留:失败次数达到上限后的锁定必须让调用方看到「稍后再试」,
// 其余失败仍按验证码无效统一提示。
func verificationFailure(err error) error {
var appErr *errors.AppError
if stderrors.As(err, &appErr) {
if appErr.Code == errors.CodeTooManyRequests {
return appErr
}
return errors.Wrap(errors.CodeVerificationCodeInvalid, err, appErr.Message)
}
return errors.Wrap(errors.CodeVerificationCodeInvalid, err)
@@ -278,7 +283,16 @@ func resolveRegistrationSubmitter(ctx context.Context, db *gorm.DB, parentShopID
// registrationApprovalForm 生成企业微信审批表单业务快照。
// 手机号按脱敏值写入,禁止把完整手机号或密码写入审批表单与审计。
func registrationApprovalForm(input distributiondomain.RegistrationInput, parent *model.Shop) map[string]any {
//
// 业务员取上级店铺当前业务员的名称快照:上级无业务员时以空值提交并显式标记为「无」,
// MUST NOT 以提交人、上级店铺主账号或其它账号填充。该字段是只读快照,审批通过时写入新店铺的
// 初始业务员仍按既有规则取上级业务员当时值,不因本快照与通过时值不同而改写通过结果。
func registrationApprovalForm(input distributiondomain.RegistrationInput, parent *model.Shop, businessOwnerName string) map[string]any {
owner := strings.TrimSpace(businessOwnerName)
absenceMark := ""
if owner == "" {
absenceMark = agentDistributionBusinessOwnerAbsenceMark
}
return map[string]any{
constants.ApprovalFieldDistributionCode: distributiondomain.MaskDistributionCode(input.DistributionCode),
constants.ApprovalFieldDistributionParentShopID: parent.ID,
@@ -290,9 +304,34 @@ func registrationApprovalForm(input distributiondomain.RegistrationInput, parent
constants.ApprovalFieldDistributionContactName: input.ContactName,
constants.ApprovalFieldDistributionRegion: strings.TrimSpace(
input.Province + input.City + input.District + input.Address),
constants.ApprovalFieldDistributionBusinessOwner: owner,
constants.ApprovalFieldDistributionBusinessOwnerAbsence: absenceMark,
}
}
// agentDistributionBusinessOwnerAbsenceMark 是注册审批材料中「上级店铺无业务员」的显式标记。
// 与「字段未填」区分开,使审批人看到的是确认无业务员而非漏填。
const agentDistributionBusinessOwnerAbsenceMark = "无"
// parentBusinessOwnerName 读取上级店铺当前业务员的名称快照。
// 判定与店铺业务员投影的可用口径一致:账号存在、为平台账号、启用且未删除;
// 任一条件不满足时返回空串由审批材料显式标记为无MUST NOT 用其它账号填充。
func (s *RegistrationService) parentBusinessOwnerName(ctx context.Context, parent *model.Shop) string {
if parent == nil || parent.BusinessOwnerAccountID == nil || *parent.BusinessOwnerAccountID == 0 {
return ""
}
var account model.Account
if err := s.db.WithContext(ctx).
Select("id", "username", "user_type", "status").
First(&account, *parent.BusinessOwnerAccountID).Error; err != nil {
return ""
}
if account.UserType != constants.UserTypePlatform || account.Status != constants.StatusEnabled || account.DeletedAt.Valid {
return ""
}
return strings.TrimSpace(account.Username)
}
// attachRegistrationInstance 回写注册记录关联的审批实例,写入一次后不可修改。
func attachRegistrationInstance(
ctx context.Context,