补全一些接口
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m9s

This commit is contained in:
2026-07-27 17:17:43 +08:00
parent 072ec1db7e
commit d46ccbd319
8 changed files with 221 additions and 43 deletions

View File

@@ -257,7 +257,7 @@ default:
- **套餐系统升级**:完整的套餐生命周期管理,支持主套餐排队激活、加油包绑定主套餐、囤货待实名激活、流量按优先级扣减、自然月/按天有效期计算、日/月/年流量重置、客户端流量查询和套餐流量详单;详见 [套餐系统升级文档](docs/package-system-upgrade/)
- **套餐生效条件覆盖与购买快照**:支持代理分配覆盖套餐生效条件,并将生效条件、周期类型和购买时长固化到套餐使用记录;激活与排队接续只消费购买快照。详见 [功能总结](docs/ur55-package-expiry-base/功能总结.md)
- **UR#33 套餐临期列表与 C 端节点提醒**:平台和代理可按统一最终到期口径分页查看 015 天临期卡/设备及分类数量,返回高亮和优先标识;每日 15/7/3 天节点通过公共 Outbox 向关联个人客户幂等发送站内通知。详见 [功能总结](docs/ur33-package-expiry-reminder/功能总结.md)
- **企业微信最小审批 Adapter、退款与员工线下代充值**:超级管理员可维护自建应用连接参数、默认发起人和后台模板映射,可按已知模板 ID 实时读取控件后配置映射通讯录按应用可见部门同步不依赖根部门权限。管理接口和数据库均使用明文连接凭据。Adapter 支持成员显式绑定、异步提交、加密回调、权威详情同步、未终态轮询和结果未知时间窗恢复退款和员工线下代充值均原子创建业务单与审批事实approved 复用既有资金用例幂等执行,其他终态不产生资金副作用。未知结果只有唯一审批单号候选才关联,绝不盲目重提。详见 [功能总结](docs/wecom-application-connection/功能总结.md)
- **企业微信最小审批 Adapter、退款与员工线下代充值**:超级管理员可维护自建应用连接参数、默认发起人和后台模板映射,可按已知模板 ID 实时读取控件,并按业务类型查询系统可映射字段通讯录按应用可见部门同步不依赖根部门权限。管理接口和数据库均使用明文连接凭据。Adapter 支持成员显式绑定、异步提交、加密回调、权威详情同步、未终态轮询和结果未知时间窗恢复退款和员工线下代充值均原子创建业务单与审批事实approved 复用既有资金用例幂等执行,其他终态不产生资金副作用。未知结果只有唯一审批单号候选才关联,绝不盲目重提。详见 [功能总结](docs/wecom-application-connection/功能总结.md)
- **代理钱包桌面扫码充值**:代理可使用微信 Native 或支付宝当面付为当前店铺主钱包在线自充;充值接口通过 `recharge_source` 区分平台线下代充与代理在线自充,支付确认后由 Outbox/Worker 幂等入账,并保留创建支付商户身份供未来对账。详见 [功能总结](docs/feature-034-agent-wallet-qr-recharge/功能总结.md)
- **套餐价格回退与平台赠送策略**:新增价格配置状态、普通套餐成本价回退、赠送套餐独立语义、平台后台赠送订单发放和历史 0 价复核清单;详见 [功能总结](docs/package-price-fallback-and-platform-gift-policy/功能总结.md) 与 [最终验收清单](docs/package-price-fallback-and-platform-gift-policy/最终验收清单.md)
- **分佣验证指引**:对代理分佣的冻结、解冻、提现校验流程进行了结构化说明与流程图,详见 [分佣逻辑正确与否验证](docs/优化说明/分佣逻辑正确与否验证.md)

View File

@@ -11006,6 +11006,36 @@ components:
format: date-time
type: string
type: object
DtoWeComBusinessFieldListResponse:
properties:
business_type:
description: 业务类型
type: string
business_type_name:
description: 业务类型名称(中文)
type: string
items:
description: 当前业务类型允许写入 control_mapping.business_field 的字段
items:
$ref: '#/components/schemas/DtoWeComBusinessFieldResponse'
nullable: true
type: array
type: object
DtoWeComBusinessFieldResponse:
properties:
code:
description: control_mapping.business_field 应填写的稳定字段编码
type: string
description:
description: 字段取值说明
type: string
name:
description: 字段中文名称
type: string
value_type:
description: 字段值类型 (string:字符串, integer:整数, money:两位小数的元金额字符串, file_list:对象存储文件引用列表)
type: string
type: object
DtoWeComConnectionTestResponse:
properties:
success:
@@ -11015,7 +11045,7 @@ components:
DtoWeComControlMappingItem:
properties:
business_field:
description: 稳定业务字段编码
description: 系统业务字段编码;按 business_type 调用 GET /api/admin/wecom/scenes/{business_type}/fields 获取可选值
type: string
control_id:
description: 企微模板控件 ID
@@ -28890,12 +28920,12 @@ paths:
/api/admin/wecom/scenes/{business_type}:
put:
parameters:
- description: 业务类型 (refund_approval:退款审批, offline_recharge_approval:员工线下代充值审批)
- description: 业务类型 (refund_approval:退款审批, offline_recharge_approval:员工线下代充值审批);可先调用 GET /api/admin/wecom/scenes/{business_type}/fields 查询允许映射的业务字段
in: path
name: business_type
required: true
schema:
description: 业务类型 (refund_approval:退款审批, offline_recharge_approval:员工线下代充值审批)
description: 业务类型 (refund_approval:退款审批, offline_recharge_approval:员工线下代充值审批);可先调用 GET /api/admin/wecom/scenes/{business_type}/fields 查询允许映射的业务字段
type: string
requestBody:
content:
@@ -28958,6 +28988,72 @@ paths:
summary: 保存并校验企业微信审批场景模板映射
tags:
- 企业微信审批
/api/admin/wecom/scenes/{business_type}/fields:
get:
parameters:
- description: 业务类型 (refund_approval:退款审批, offline_recharge_approval:员工线下代充值审批)
in: path
name: business_type
required: true
schema:
description: 业务类型 (refund_approval:退款审批, offline_recharge_approval:员工线下代充值审批)
type: string
responses:
"200":
content:
application/json:
schema:
properties:
code:
description: 响应码
example: 0
type: integer
data:
$ref: '#/components/schemas/DtoWeComBusinessFieldListResponse'
msg:
description: 响应消息
example: success
type: string
timestamp:
description: 时间戳
format: date-time
type: string
required:
- code
- msg
- data
- timestamp
type: object
description: 成功
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 请求参数错误
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 未认证或认证已过期
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 无权访问
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 服务器内部错误
security:
- BearerAuth: []
summary: 查询企业微信审批场景可映射字段
tags:
- 企业微信审批
/api/auth/login:
post:
requestBody:

View File

@@ -11,6 +11,7 @@
- `POST /api/admin/wecom/applications/:id/members/sync`:拉取应用可见范围内的成员并原子刷新本地选择快照。
- `GET /api/admin/wecom/applications/:id/members`:按姓名或 userid 搜索并分页选择最近同步的可见成员。
- `POST /api/admin/wecom/applications/:id/templates/inspect`:输入企微后台已知模板 ID实时读取模板名称、控件 ID、类型、必填标识和选项 key供保存映射前选择控件。
- `GET /api/admin/wecom/scenes/:business_type/fields`:返回指定业务类型允许映射的系统字段编码、中文名、值类型和取值说明。
- `PUT /api/admin/accounts/:id/wecom-binding`:把系统账号绑定到管理员明确选择的 `(corp_id, userid)`,同时保存姓名快照。
- `PUT /api/admin/wecom/scenes/:business_type`:配置已知 `template_id` 和业务字段控件映射,保存前实时读取模板详情校验。
- `GET /api/admin/wecom/scenes`:分页查询退款、员工线下代充值两个稳定业务场景的当前模板映射。
@@ -50,6 +51,7 @@
## 审批场景与模板映射
- 企微没有按应用枚举全部审批模板的接口;管理员需先从企微后台取得模板 ID再调用模板检查接口读取可映射控件。
- 前端保存映射前先查询场景字段接口,以返回的 `code` 填写 `control_mapping.business_field`;字段接口与保存校验复用同一份定义。
- 稳定业务类型固定为 `refund_approval``offline_recharge_approval`,业务代码不直接写死企微模板 ID。
- 模板必须先在企业微信后台创建;本系统不创建模板,也不保存审批节点或审批人规则。
- 保存映射时调用模板详情,逐项校验控件 ID、控件类型和选择项 key并要求模板所有必填控件都有映射模板结构已变化时拒绝覆盖当前有效配置。

View File

@@ -90,6 +90,22 @@ func (s *SceneService) InspectTemplate(ctx context.Context, applicationID uint,
}, nil
}
// ListBusinessFields 返回指定审批场景允许映射的业务快照字段。
func (s *SceneService) ListBusinessFields(ctx context.Context, businessType string) (*dto.WeComBusinessFieldListResponse, error) {
userType := middleware.GetUserTypeFromContext(ctx)
if userType != constants.UserTypeSuperAdmin && userType != constants.UserTypePlatform {
return nil, errors.New(errors.CodeForbidden)
}
businessType = strings.TrimSpace(businessType)
items, ok := sceneBusinessFields(businessType)
if !ok {
return nil, errors.New(errors.CodeInvalidParam, "不支持的审批业务类型")
}
return &dto.WeComBusinessFieldListResponse{
BusinessType: businessType, BusinessTypeName: approvalBusinessTypeName(businessType), Items: items,
}, nil
}
// Save 校验模板控件后创建或替换指定稳定业务场景映射。
func (s *SceneService) Save(ctx context.Context, businessType string, request dto.SaveWeComApprovalSceneRequest) (*dto.WeComApprovalSceneResponse, error) {
if s == nil || s.db == nil || s.provider == nil || s.repo == nil {
@@ -269,40 +285,50 @@ func validateSceneMapping(businessType string, mapping []dto.WeComControlMapping
}
func allowedSceneBusinessField(businessType, businessField string) bool {
allowed := map[string]struct{}{}
switch businessType {
case constants.ApprovalBusinessTypeOfflineRecharge:
allowed = map[string]struct{}{
constants.ApprovalFieldRechargeNo: {},
constants.ApprovalFieldShopID: {},
constants.ApprovalFieldShopName: {},
constants.ApprovalFieldAmount: {},
constants.ApprovalFieldAmountCent: {},
constants.ApprovalFieldPaymentVoucherKey: {},
constants.ApprovalFieldRemark: {},
constants.ApprovalFieldSubmitterID: {},
constants.ApprovalFieldSubmitterName: {},
}
case constants.ApprovalBusinessTypeRefund:
allowed = map[string]struct{}{
constants.ApprovalFieldRefundNo: {},
constants.ApprovalFieldOrderID: {},
constants.ApprovalFieldOrderNo: {},
constants.ApprovalFieldAssetIdentifier: {},
constants.ApprovalFieldAssetType: {},
constants.ApprovalFieldActualReceivedAmount: {},
constants.ApprovalFieldRequestedRefundAmount: {},
constants.ApprovalFieldRefundVoucherKey: {},
constants.ApprovalFieldRefundReason: {},
constants.ApprovalFieldPackageUsageID: {},
constants.ApprovalFieldSubmitterID: {},
constants.ApprovalFieldSubmitterName: {},
}
default:
fields, ok := sceneBusinessFields(businessType)
if !ok {
return false
}
_, exists := allowed[businessField]
return exists
for _, field := range fields {
if field.Code == businessField {
return true
}
}
return false
}
func sceneBusinessFields(businessType string) ([]dto.WeComBusinessFieldResponse, bool) {
switch businessType {
case constants.ApprovalBusinessTypeOfflineRecharge:
return []dto.WeComBusinessFieldResponse{
{Code: constants.ApprovalFieldRechargeNo, Name: "充值单号", ValueType: constants.ApprovalFieldValueTypeString, Description: "员工线下代充值单号"},
{Code: constants.ApprovalFieldShopID, Name: "目标店铺 ID", ValueType: constants.ApprovalFieldValueTypeInteger, Description: "本次充值目标店铺的系统 ID"},
{Code: constants.ApprovalFieldShopName, Name: "目标店铺名称", ValueType: constants.ApprovalFieldValueTypeString, Description: "本次充值目标店铺名称快照"},
{Code: constants.ApprovalFieldAmount, Name: "充值金额", ValueType: constants.ApprovalFieldValueTypeMoney, Description: "以元为单位且保留两位小数的充值金额"},
{Code: constants.ApprovalFieldAmountCent, Name: "充值金额(分)", ValueType: constants.ApprovalFieldValueTypeInteger, Description: "以分为单位的充值金额整数"},
{Code: constants.ApprovalFieldPaymentVoucherKey, Name: "付款凭证", ValueType: constants.ApprovalFieldValueTypeFileList, Description: "提交时上传到企微文件控件的付款凭证列表"},
{Code: constants.ApprovalFieldRemark, Name: "备注", ValueType: constants.ApprovalFieldValueTypeString, Description: "员工提交线下代充值时填写的备注"},
{Code: constants.ApprovalFieldSubmitterID, Name: "提交人账号 ID", ValueType: constants.ApprovalFieldValueTypeInteger, Description: "本系统真实业务提交人账号 ID"},
{Code: constants.ApprovalFieldSubmitterName, Name: "提交人名称", ValueType: constants.ApprovalFieldValueTypeString, Description: "本系统真实业务提交人名称快照"},
}, true
case constants.ApprovalBusinessTypeRefund:
return []dto.WeComBusinessFieldResponse{
{Code: constants.ApprovalFieldRefundNo, Name: "退款单号", ValueType: constants.ApprovalFieldValueTypeString, Description: "本次退款申请单号"},
{Code: constants.ApprovalFieldOrderID, Name: "订单 ID", ValueType: constants.ApprovalFieldValueTypeInteger, Description: "退款关联订单的系统 ID"},
{Code: constants.ApprovalFieldOrderNo, Name: "订单号", ValueType: constants.ApprovalFieldValueTypeString, Description: "退款关联订单号"},
{Code: constants.ApprovalFieldAssetIdentifier, Name: "资产标识", ValueType: constants.ApprovalFieldValueTypeString, Description: "退款关联卡或设备的业务标识"},
{Code: constants.ApprovalFieldAssetType, Name: "资产类型", ValueType: constants.ApprovalFieldValueTypeString, Description: "退款关联资产类型编码"},
{Code: constants.ApprovalFieldActualReceivedAmount, Name: "订单实收金额", ValueType: constants.ApprovalFieldValueTypeMoney, Description: "以元为单位且保留两位小数的订单实收金额"},
{Code: constants.ApprovalFieldRequestedRefundAmount, Name: "申请退款金额", ValueType: constants.ApprovalFieldValueTypeMoney, Description: "以元为单位且保留两位小数的本次申请退款金额"},
{Code: constants.ApprovalFieldRefundVoucherKey, Name: "退款凭证", ValueType: constants.ApprovalFieldValueTypeFileList, Description: "提交时上传到企微文件控件的退款凭证列表"},
{Code: constants.ApprovalFieldRefundReason, Name: "退款原因", ValueType: constants.ApprovalFieldValueTypeString, Description: "业务提交人填写的退款原因"},
{Code: constants.ApprovalFieldPackageUsageID, Name: "套餐使用记录 ID", ValueType: constants.ApprovalFieldValueTypeInteger, Description: "退款关联套餐使用记录 ID无关联记录时可能为空"},
{Code: constants.ApprovalFieldSubmitterID, Name: "提交人账号 ID", ValueType: constants.ApprovalFieldValueTypeInteger, Description: "本系统真实业务提交人账号 ID"},
{Code: constants.ApprovalFieldSubmitterName, Name: "提交人名称", ValueType: constants.ApprovalFieldValueTypeString, Description: "本系统真实业务提交人名称快照"},
}, true
default:
return nil, false
}
}
func normalizeSceneMapping(mapping []dto.WeComControlMappingItem) []dto.WeComControlMappingItem {
@@ -323,6 +349,13 @@ func validApprovalBusinessType(businessType string) bool {
return businessType == constants.ApprovalBusinessTypeRefund || businessType == constants.ApprovalBusinessTypeOfflineRecharge
}
func approvalBusinessTypeName(businessType string) string {
if businessType == constants.ApprovalBusinessTypeRefund {
return "退款审批"
}
return "员工线下代充值审批"
}
func sceneAuditSnapshot(scene *model.WeComApprovalScene) map[string]any {
return map[string]any{
"business_type": scene.BusinessType, "application_id": scene.ApplicationID,
@@ -336,12 +369,8 @@ func sceneResponse(scene model.WeComApprovalScene) (*dto.WeComApprovalSceneRespo
if err := sonic.Unmarshal(scene.ControlMapping, &mapping); err != nil {
return nil, errors.Wrap(errors.CodeInternalError, err, "解析企业微信控件映射失败")
}
name := "员工线下代充值审批"
if scene.BusinessType == constants.ApprovalBusinessTypeRefund {
name = "退款审批"
}
return &dto.WeComApprovalSceneResponse{
ID: scene.ID, BusinessType: scene.BusinessType, BusinessTypeName: name,
ID: scene.ID, BusinessType: scene.BusinessType, BusinessTypeName: approvalBusinessTypeName(scene.BusinessType),
ApplicationID: scene.ApplicationID, TemplateID: scene.TemplateID, TemplateName: scene.TemplateName,
TemplateFingerprint: scene.TemplateFingerprint,
ControlMapping: mapping, Status: scene.Status, StatusName: constants.GetStatusName(scene.Status),

View File

@@ -180,6 +180,19 @@ func (h *WeComHandler) InspectTemplate(c *fiber.Ctx) error {
return response.Success(c, result)
}
// ListSceneFields 查询审批场景允许映射的业务字段。
// GET /api/admin/wecom/scenes/:business_type/fields
func (h *WeComHandler) ListSceneFields(c *fiber.Ctx) error {
if h == nil || h.scenes == nil {
return errors.New(errors.CodeServiceUnavailable, "企业微信审批场景服务未配置")
}
result, err := h.scenes.ListBusinessFields(c.UserContext(), c.Params("business_type"))
if err != nil {
return err
}
return response.Success(c, result)
}
// SaveScene 保存并校验稳定业务类型的企微模板控件映射。
// PUT /api/admin/wecom/scenes/:business_type
func (h *WeComHandler) SaveScene(c *fiber.Ctx) error {

View File

@@ -4,7 +4,7 @@ import "time"
// WeComControlMappingItem 描述一个业务字段到企微模板控件的显式映射。
type WeComControlMappingItem struct {
BusinessField string `json:"business_field" validate:"required,min=1,max=64" description:"稳定业务字段编码"`
BusinessField string `json:"business_field" validate:"required,min=1,max=64" description:"系统业务字段编码;按 business_type 调用 GET /api/admin/wecom/scenes/{business_type}/fields 获取可选值"`
ControlID string `json:"control_id" validate:"required,min=1,max=128" description:"企微模板控件 ID"`
ControlType string `json:"control_type" validate:"required,min=1,max=64" description:"企微模板控件类型,必须与模板详情一致"`
OptionMapping map[string]string `json:"option_mapping" description:"业务枚举值到企微选择项 key 的映射;非选择控件传空对象"`
@@ -21,6 +21,26 @@ type InspectWeComTemplateParams struct {
InspectWeComTemplateRequest
}
// WeComBusinessFieldListParams 查询企业微信审批场景可映射字段路径参数。
type WeComBusinessFieldListParams struct {
BusinessType string `path:"business_type" required:"true" description:"业务类型 (refund_approval:退款审批, offline_recharge_approval:员工线下代充值审批)"`
}
// WeComBusinessFieldResponse 企业微信审批场景可映射业务字段响应。
type WeComBusinessFieldResponse struct {
Code string `json:"code" description:"control_mapping.business_field 应填写的稳定字段编码"`
Name string `json:"name" description:"字段中文名称"`
ValueType string `json:"value_type" description:"字段值类型 (string:字符串, integer:整数, money:两位小数的元金额字符串, file_list:对象存储文件引用列表)"`
Description string `json:"description" description:"字段取值说明"`
}
// WeComBusinessFieldListResponse 企业微信审批场景可映射业务字段列表响应。
type WeComBusinessFieldListResponse struct {
BusinessType string `json:"business_type" description:"业务类型"`
BusinessTypeName string `json:"business_type_name" description:"业务类型名称(中文)"`
Items []WeComBusinessFieldResponse `json:"items" description:"当前业务类型允许写入 control_mapping.business_field 的字段"`
}
// WeComTemplateControlResponse 企业微信审批模板控件响应。
type WeComTemplateControlResponse struct {
ID string `json:"id" description:"企微模板控件 ID"`
@@ -47,7 +67,7 @@ type SaveWeComApprovalSceneRequest struct {
// SaveWeComApprovalSceneParams 保存企业微信审批场景路径与请求参数。
type SaveWeComApprovalSceneParams struct {
BusinessType string `path:"business_type" required:"true" description:"业务类型 (refund_approval:退款审批, offline_recharge_approval:员工线下代充值审批)"`
BusinessType string `path:"business_type" required:"true" description:"业务类型 (refund_approval:退款审批, offline_recharge_approval:员工线下代充值审批);可先调用 GET /api/admin/wecom/scenes/{business_type}/fields 查询允许映射的业务字段"`
SaveWeComApprovalSceneRequest
}

View File

@@ -70,6 +70,13 @@ func registerWeComRoutes(router fiber.Router, handler *admin.WeComHandler, doc *
Output: new(dto.WeComTemplateDetailResponse),
Auth: true,
})
Register(group, doc, groupPath, "GET", "/scenes/:business_type/fields", handler.ListSceneFields, RouteSpec{
Summary: "查询企业微信审批场景可映射字段",
Tags: []string{"企业微信审批"},
Input: new(dto.WeComBusinessFieldListParams),
Output: new(dto.WeComBusinessFieldListResponse),
Auth: true,
})
Register(group, doc, groupPath, "PUT", "/scenes/:business_type", handler.SaveScene, RouteSpec{
Summary: "保存并校验企业微信审批场景模板映射",
Tags: []string{"企业微信审批"},

View File

@@ -50,6 +50,17 @@ const (
ApprovalFieldPackageUsageID = "package_usage_id"
)
const (
// ApprovalFieldValueTypeString 表示业务字段值为普通字符串。
ApprovalFieldValueTypeString = "string"
// ApprovalFieldValueTypeInteger 表示业务字段值为整数。
ApprovalFieldValueTypeInteger = "integer"
// ApprovalFieldValueTypeMoney 表示业务字段值为两位小数的元金额字符串。
ApprovalFieldValueTypeMoney = "money"
// ApprovalFieldValueTypeFileList 表示业务字段值为对象存储文件引用列表。
ApprovalFieldValueTypeFileList = "file_list"
)
const (
// ApprovalStatusSubmitting 表示审批申请等待渠道提交。
ApprovalStatusSubmitting = 0