完善退款审批材料与恢复流程
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m35s

This commit is contained in:
2026-09-20 17:59:52 +08:00
parent 4a6cec2730
commit b063617153
29 changed files with 1119 additions and 156 deletions

View File

@@ -132,6 +132,10 @@ func (s *SceneService) Save(ctx context.Context, businessType string, request dt
s.recordFailure(ctx, businessType, request, constants.AuditResultDenied, errors.CodeInvalidParam, "拒绝保存非法企业微信审批场景映射")
return nil, err
}
if err := validateRefundConditionalMappings(businessType, request.ControlMapping, definition.Controls); err != nil {
s.recordFailure(ctx, businessType, request, constants.AuditResultDenied, errors.CodeInvalidParam, "拒绝保存非法退款条件材料控件映射")
return nil, err
}
request.ControlMapping = normalizeSceneMapping(request.ControlMapping)
mappingJSON, err := sonic.Marshal(request.ControlMapping)
if err != nil {
@@ -305,19 +309,14 @@ func validateSceneMapping(businessType string, mapping []dto.WeComControlMapping
return nil
}
// RequiredSceneField 描述本变更新增、必须配置控件映射的业务字段
// RequiredSceneField 描述审批字段映射的固定或条件必需规则
type RequiredSceneField struct {
// Code 是 control_mapping.business_field 应填写的稳定字段编码。
Code string
// Name 是字段中文名称,用于在缺失时指出具体缺哪个字段/控件。
Name string
Code string
Name string
RequiredMapping bool
}
// RequiredSceneFields 返回指定业务类型下必须配置控件映射的业务字段,顺序与场景白名单一致
//
// 这是「必须映射字段」的唯一判定来源:场景映射校验(保存时)与表单构建(提交时)都从
// 这里取集合MUST NOT 再维护第二份清单。既有未标注必须映射的可选字段不在返回结果中,
// 因此其缺失映射仍保持既有静默跳过行为。
// RequiredSceneFields 返回场景级固定必需字段
func RequiredSceneFields(businessType string) []RequiredSceneField {
fields, ok := sceneBusinessFields(businessType)
if !ok {
@@ -326,16 +325,31 @@ func RequiredSceneFields(businessType string) []RequiredSceneField {
result := make([]RequiredSceneField, 0, len(fields))
for _, field := range fields {
if field.RequiredMapping {
result = append(result, RequiredSceneField{Code: field.Code, Name: field.Name})
result = append(result, RequiredSceneField{Code: field.Code, Name: field.Name, RequiredMapping: true})
}
}
return result
}
// RequiredRefundFields 返回退款提交固定与条件必需字段;快照方式编码缺失或未知时由表单构建拒绝。
func RequiredRefundFields(snapshot map[string]any) []RequiredSceneField {
methodCode, ok := snapshot[constants.ApprovalFieldRefundMethodCode].(string)
methodCode = strings.TrimSpace(methodCode)
if !ok || methodCode == "" || (methodCode != constants.RefundMethodOriginalRoute && methodCode != constants.RefundMethodCustomerAccount && methodCode != constants.RefundMethodAssetWallet && methodCode != constants.RefundMethodAgentWallet) {
return []RequiredSceneField{{Code: constants.ApprovalFieldRefundMethodCode, Name: "退款方式编码"}}
}
result := []RequiredSceneField{{Code: constants.ApprovalFieldRefundMethod, Name: "退款方式", RequiredMapping: true}}
if methodCode == constants.RefundMethodCustomerAccount {
result = append(result, RequiredSceneField{Code: constants.ApprovalFieldCustomerAccountInfo, Name: "客户收款信息"}, RequiredSceneField{Code: constants.ApprovalFieldCustomerVoucherKey, Name: "客户收款凭证"})
}
return result
}
// approvalBusinessTypes 返回全部已登记审批业务场景,供只读字段汇总复用。
func approvalBusinessTypes() []string {
return []string{
constants.ApprovalBusinessTypeRefund,
constants.ApprovalBusinessTypeOfflineRecharge,
constants.ApprovalBusinessTypeEmployeeCollection,
constants.ApprovalBusinessTypeAgentDistribution,
@@ -344,6 +358,30 @@ func approvalBusinessTypes() []string {
}
}
// validateRefundConditionalMappings 校验退款条件字段使用正确的企微控件类型。
func validateRefundConditionalMappings(businessType string, mapping []dto.WeComControlMappingItem, controls []TemplateControl) error {
if businessType != constants.ApprovalBusinessTypeRefund {
return nil
}
types := make(map[string]string, len(controls))
for _, control := range controls {
types[control.ID] = strings.ToLower(strings.TrimSpace(control.Type))
}
for _, item := range mapping {
switch item.BusinessField {
case constants.ApprovalFieldCustomerAccountInfo:
if types[item.ControlID] != "textarea" {
return errors.New(errors.CodeInvalidParam, "客户收款信息必须映射到多行文本控件")
}
case constants.ApprovalFieldCustomerVoucherKey:
if types[item.ControlID] != "file" {
return errors.New(errors.CodeInvalidParam, "客户收款凭证必须映射到附件控件")
}
}
}
return nil
}
func allowedSceneBusinessField(businessType, businessField string) bool {
fields, ok := sceneBusinessFields(businessType)
if !ok {
@@ -394,6 +432,10 @@ func sceneBusinessFields(businessType string) ([]dto.WeComBusinessFieldResponse,
{Code: constants.ApprovalFieldRefundPackageTotalMB, Name: "当前退款套餐总量MB", ValueType: constants.ApprovalFieldValueTypeInteger, Description: "当前退款套餐总量MB真实流量解析不到套餐时为 0", RequiredMapping: true},
{Code: constants.ApprovalFieldRefundChannelTradeNo, Name: "原支付渠道交易流水号", ValueType: constants.ApprovalFieldValueTypeString, Description: "创建申请时冻结的原成功支付记录渠道交易流水号;线下订单为空字符串", RequiredMapping: true},
{Code: constants.ApprovalFieldRefundApplicantRemark, Name: "申请人备注", ValueType: constants.ApprovalFieldValueTypeString, Description: "本次提交填写的申请人备注快照;未填写时为空字符串。与审批备注不是同一字段", RequiredMapping: true},
{Code: constants.ApprovalFieldRefundMethodCode, Name: "退款方式编码", ValueType: constants.ApprovalFieldValueTypeString, Description: "仅用于按本次审批快照计算条件材料,不要求配置模板控件"},
{Code: constants.ApprovalFieldRefundMethod, Name: "退款方式", ValueType: constants.ApprovalFieldValueTypeString, Description: "本次审批尝试冻结的退款方式中文名称", RequiredMapping: true},
{Code: constants.ApprovalFieldCustomerAccountInfo, Name: "客户收款信息", ValueType: constants.ApprovalFieldValueTypeString, Description: "客户收款信息退款的多行收款文本;其它退款方式为空"},
{Code: constants.ApprovalFieldCustomerVoucherKey, Name: "客户收款凭证", ValueType: constants.ApprovalFieldValueTypeFileList, Description: "客户收款信息退款的凭证列表;其它退款方式为空"},
{Code: constants.ApprovalFieldSubmitterID, Name: "提交人账号 ID", ValueType: constants.ApprovalFieldValueTypeInteger, Description: "本系统真实业务提交人账号 ID"},
{Code: constants.ApprovalFieldSubmitterName, Name: "提交人名称", ValueType: constants.ApprovalFieldValueTypeString, Description: "本系统真实业务提交人名称快照"},
}, true