All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m23s
新增 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)与参数校验中文提示共用实现。
139 lines
5.9 KiB
Go
139 lines
5.9 KiB
Go
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)
|
||
}
|