Files
junhong_cmp_fiber/internal/handler/admin/withdrawal_qualification.go
break 333ba4b647
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m23s
feat(H5弹窗): AUG26-007 风险换卡与运营弹窗投放通知
新增 000225 迁移:运营弹窗配置表 tb_h5_popup_configuration(页面/范围/优先级/频率/受控动作/启停/有效期/版本)
与 tb_notification 可空 JSONB 列 popup_snapshot。

新增通知直建窄接口 DirectWriter.CreateOrGetPersonal:与 Outbox 消费共用 prepareDelivery 的渲染、
展示期与 CreateIdempotent 规则,冲突时回查返回既有行;同步扩展个人通知查询与已读两处类型白名单,
并按个人客户入口补齐投递审计来源。

新增 H5 候选与风险换卡:GET /api/c/v1/popup-candidates 先判风险资格(广电卡 + 风险停机 +
无活动物流换货单),命中只返回风险候选;未命中再按时间/启停/页面/店铺/设备类型/卡类型范围/频率
匹配运营配置。POST /api/c/v1/risk-exchanges/:asset_id/address 锁资产行后幂等创建待发货物流换货单,
首次地址锁定,不沿用资产级群发通知。

新增后台运营弹窗配置 CRUD 与启停(仅超级管理员与平台账号),更新递增版本并刷新最近更新时间,
标题与正文统一拒绝 URL 与前端路由,全部写操作记录操作者、前后值、版本与时间。

同步 OpenAPI(cmd/gendocs、cmd/api/docs.go、pkg/openapi/handlers.go)与参数校验中文提示共用实现。
2026-09-15 15:23:52 +08:00

139 lines
5.9 KiB
Go
Raw Permalink 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 admin
import (
"strconv"
"github.com/go-playground/validator/v10"
"github.com/gofiber/fiber/v2"
distributionapp "github.com/break/junhong_cmp_fiber/internal/application/distributionwithdrawal"
distributiondomain "github.com/break/junhong_cmp_fiber/internal/domain/distribution"
"github.com/break/junhong_cmp_fiber/internal/handler/validation"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
distributionquery "github.com/break/junhong_cmp_fiber/internal/query/distributionwithdrawal"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/middleware"
"github.com/break/junhong_cmp_fiber/pkg/response"
)
// WithdrawalQualificationHandler 提现资料资格后台 Handler。
type WithdrawalQualificationHandler struct {
service *distributionapp.QualificationService
query *distributionquery.Query
validator *validator.Validate
}
// NewWithdrawalQualificationHandler 创建提现资料资格后台 Handler。
func NewWithdrawalQualificationHandler(
service *distributionapp.QualificationService,
query *distributionquery.Query,
validate *validator.Validate,
) *WithdrawalQualificationHandler {
return &WithdrawalQualificationHandler{service: service, query: query, validator: validate}
}
// SubmitWithdrawalQualification 提交或替换提现资料资格
// POST /api/admin/shops/:shop_id/withdrawal-qualifications
// 仅本人代理店铺;替换合同或法人身份证时同一事务新增版本并使旧有效版本失效。
func (h *WithdrawalQualificationHandler) SubmitWithdrawalQualification(c *fiber.Ctx) error {
if h.service == nil {
return errors.New(errors.CodeServiceUnavailable, "提现资料资格能力尚未配置")
}
var req dto.SubmitWithdrawalQualificationReq
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
shopID, err := strconv.ParseUint(c.Params("shop_id"), 10, 64)
if err != nil || shopID == 0 {
return errors.New(errors.CodeInvalidParam, "无效的店铺 ID")
}
// shop_id 只来自路径,必须在校验前回填,否则 ShopID 的 required 恒失败。
req.ShopID = uint(shopID)
if h.validator == nil {
return errors.New(errors.CodeInternalError, "提现资料资格校验器未配置")
}
if err := h.validator.Struct(&req); err != nil {
return errors.New(errors.CodeInvalidParam, validationMessage("提现资料资格参数不合法", &req, err))
}
result, err := h.service.Submit(c.UserContext(), uint(shopID), distributiondomain.QualificationInput{
SubjectType: req.SubjectType,
SubjectCode: req.SubjectCode,
LegalPersonIDCard: req.LegalPersonIDCard,
ContractFileKey: req.ContractFileKey,
IDCardFrontFileKey: req.IDCardFrontFileKey,
IDCardBackFileKey: req.IDCardBackFileKey,
BusinessLicenseFileKey: req.BusinessLicenseFileKey,
ShopFrontFileKey: req.ShopFrontFileKey,
InvoiceFileKey: req.InvoiceFileKey,
InvoiceTitle: req.InvoiceTitle,
InvoiceSubjectCode: req.InvoiceSubjectCode,
})
if err != nil {
return err
}
return response.Success(c, &dto.SubmitWithdrawalQualificationResp{
ID: result.QualificationID,
Status: result.Status,
StatusName: constants.GetWithdrawalQualificationStatusName(result.Status),
})
}
// VoidWithdrawalQualification 超级管理员作废有效提现资料资格
// POST /api/admin/withdrawal-qualifications/:id/void
func (h *WithdrawalQualificationHandler) VoidWithdrawalQualification(c *fiber.Ctx) error {
if h.service == nil {
return errors.New(errors.CodeServiceUnavailable, "提现资料资格能力尚未配置")
}
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
if err != nil || id == 0 {
return errors.New(errors.CodeInvalidParam, "无效的资格 ID")
}
var req dto.VoidWithdrawalQualificationReq
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
if h.validator == nil {
return errors.New(errors.CodeInternalError, "提现资料资格校验器未配置")
}
if err := h.validator.Struct(&req); err != nil {
return errors.New(errors.CodeInvalidParam, validationMessage("作废提现资料资格参数不合法", &req, err))
}
if err := h.service.Void(c.UserContext(), uint(id), req.Reason); err != nil {
return err
}
return response.Success(c, nil)
}
// validationMessage 把请求校验失败转换为可定位字段的中文提示。
// 规则实现收口在 internal/handler/validation管理端与 C 端共用同一套提示口径。
func validationMessage(prefix string, req any, err error) string {
return validation.Message(prefix, req, err)
}
// ListWithdrawalQualifications 查询提现资料资格版本
// GET /api/admin/shops/:shop_id/withdrawal-qualifications
// 仅返回当前账号数据范围内的资料版本;证件号脱敏,附件只返回对象存储 Key。
func (h *WithdrawalQualificationHandler) ListWithdrawalQualifications(c *fiber.Ctx) error {
if h.query == nil {
return errors.New(errors.CodeInternalError, "提现资料资格查询能力未配置")
}
var req dto.WithdrawalQualificationListReq
if err := c.QueryParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
// 路由路径必带 shop_id数据范围由 CanManageShop 在业务边界强制。
shopID, err := strconv.ParseUint(c.Params("shop_id"), 10, 64)
if err != nil || shopID == 0 {
return errors.New(errors.CodeInvalidParam, "无效的店铺 ID")
}
if err := middleware.CanManageShop(c.UserContext(), uint(shopID)); err != nil {
return errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
}
result, err := h.query.ListQualifications(c.UserContext(), []uint{uint(shopID)}, &req)
if err != nil {
return err
}
return response.SuccessWithPagination(c, result.Items, result.Total, result.Page, result.Size)
}