Files
junhong_cmp_fiber/internal/handler/admin/package_silence.go
邹进 62ec94adec
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Failing after 38s
refactor: 出库时间更名为首次购买套餐时间,换货新资产不再按换货完成时间记录
- 字段 outbound_at → first_purchase_at,记录表 tb_asset_outbound_record → tb_asset_first_purchase_record,接口 /outbound-records → /first-purchase-records,筛选参数与导出列同步更名
- 换货后的新资产不因换货写入,在当前世代真正支付套餐订单时才记录;记录来源只保留 order / backfill,去掉换货单字段
- 迁移 000266 重命名列、表、索引与约束;同步 Spec、设计、任务与前端接口变更说明

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 16:47:02 +09:00

360 lines
11 KiB
Go
Raw 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 (
"github.com/go-playground/validator/v10"
"github.com/gofiber/fiber/v2"
packagesilenceapp "github.com/break/junhong_cmp_fiber/internal/application/packagesilence"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
packagesilencequery "github.com/break/junhong_cmp_fiber/internal/query/packagesilence"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/response"
)
// PackageSilenceHandler 后台沉默期:总开关、延期政策、延期资格、延期订单、线下登记、特殊延期、批次、异常、流水与首次购买套餐记录。
// Handler 只做绑定与参数校验,权限点、数据范围、金额与状态由用例判断。
type PackageSilenceHandler struct {
switches *packagesilenceapp.SwitchService
policies *packagesilenceapp.PolicyService
extensions *packagesilenceapp.ExtensionService
query *packagesilencequery.Query
validator *validator.Validate
}
// NewPackageSilenceHandler 创建后台沉默期 Handler。
func NewPackageSilenceHandler(
switches *packagesilenceapp.SwitchService,
policies *packagesilenceapp.PolicyService,
extensions *packagesilenceapp.ExtensionService,
query *packagesilencequery.Query,
validator *validator.Validate,
) *PackageSilenceHandler {
return &PackageSilenceHandler{switches: switches, policies: policies, extensions: extensions, query: query, validator: validator}
}
func (h *PackageSilenceHandler) bindQuery(c *fiber.Ctx, prefix string, req any) error {
if err := c.QueryParser(req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
return h.validate(prefix, req)
}
func (h *PackageSilenceHandler) bindBody(c *fiber.Ctx, prefix string, req any) error {
if err := c.BodyParser(req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
return h.validate(prefix, req)
}
func (h *PackageSilenceHandler) validate(prefix string, req any) error {
if h.validator == nil {
return nil
}
if err := h.validator.Struct(req); err != nil {
return errors.New(errors.CodeInvalidParam, validationMessage(prefix, req, err))
}
return nil
}
func silencePathID(c *fiber.Ctx) (uint, error) {
id, err := c.ParamsInt("id")
if err != nil || id <= 0 {
return 0, errors.New(errors.CodeInvalidParam, "ID 无效")
}
return uint(id), nil
}
// GetSwitch 查询沉默期总开关与打开前检查。GET /api/admin/package-silence/switch
func (h *PackageSilenceHandler) GetSwitch(c *fiber.Ctx) error {
result, err := h.switches.Status(c.UserContext())
if err != nil {
return err
}
return response.Success(c, result)
}
// UpdateSwitch 切换沉默期总开关(仅超级管理员)。PUT /api/admin/package-silence/switch
func (h *PackageSilenceHandler) UpdateSwitch(c *fiber.Ctx) error {
var req dto.SilenceSwitchRequest
if err := h.bindBody(c, "沉默期总开关参数不合法", &req); err != nil {
return err
}
result, err := h.switches.Toggle(c.UserContext(), *req.Enabled)
if err != nil {
return err
}
return response.Success(c, result)
}
// ListPolicies 延期政策列表。GET /api/admin/package-silence/policies
func (h *PackageSilenceHandler) ListPolicies(c *fiber.Ctx) error {
var req dto.SilencePolicyListRequest
if err := h.bindQuery(c, "延期政策查询参数不合法", &req); err != nil {
return err
}
items, total, err := h.policies.List(c.UserContext(), &req)
if err != nil {
return err
}
page, pageSize := req.Page, req.PageSize
if page < 1 {
page = 1
}
if pageSize < 1 {
pageSize = 20
}
return response.SuccessWithPagination(c, items, total, page, pageSize)
}
// GetPolicy 延期政策详情。GET /api/admin/package-silence/policies/:id
func (h *PackageSilenceHandler) GetPolicy(c *fiber.Ctx) error {
id, err := silencePathID(c)
if err != nil {
return err
}
result, err := h.policies.Get(c.UserContext(), id)
if err != nil {
return err
}
return response.Success(c, result)
}
// CreatePolicy 新增延期政策。POST /api/admin/package-silence/policies
func (h *PackageSilenceHandler) CreatePolicy(c *fiber.Ctx) error {
var req dto.SilencePolicyRequest
if err := h.bindBody(c, "延期政策参数不合法", &req); err != nil {
return err
}
result, err := h.policies.Create(c.UserContext(), &req)
if err != nil {
return err
}
return response.Success(c, result)
}
// UpdatePolicy 编辑延期政策。PUT /api/admin/package-silence/policies/:id
func (h *PackageSilenceHandler) UpdatePolicy(c *fiber.Ctx) error {
id, err := silencePathID(c)
if err != nil {
return err
}
var req dto.SilencePolicyRequest
if err := h.bindBody(c, "延期政策参数不合法", &req); err != nil {
return err
}
result, err := h.policies.Update(c.UserContext(), id, &req)
if err != nil {
return err
}
return response.Success(c, result)
}
// UpdatePolicyStatus 启停延期政策。PUT /api/admin/package-silence/policies/:id/status
func (h *PackageSilenceHandler) UpdatePolicyStatus(c *fiber.Ctx) error {
id, err := silencePathID(c)
if err != nil {
return err
}
var req dto.SilencePolicyStatusRequest
if err := h.bindBody(c, "延期政策状态参数不合法", &req); err != nil {
return err
}
result, err := h.policies.SetStatus(c.UserContext(), id, *req.Status)
if err != nil {
return err
}
return response.Success(c, result)
}
// MatchPolicy 查询套餐当前匹配的延期政策。GET /api/admin/package-silence/policies/match
func (h *PackageSilenceHandler) MatchPolicy(c *fiber.Ctx) error {
var req dto.SilencePolicyMatchRequest
if err := h.bindQuery(c, "政策匹配查询参数不合法", &req); err != nil {
return err
}
result, err := h.policies.MatchForPackage(c.UserContext(), req.PackageID)
if err != nil {
return err
}
return response.Success(c, result)
}
// Eligibility 延期资格。GET /api/admin/package-silence/eligibility
func (h *PackageSilenceHandler) Eligibility(c *fiber.Ctx) error {
var req dto.SilenceEligibilityRequest
if err := h.bindQuery(c, "延期资格查询参数不合法", &req); err != nil {
return err
}
result, err := h.extensions.Eligibility(c.UserContext(), req.PackageUsageID, packagesilenceapp.Scope{})
if err != nil {
return err
}
return response.Success(c, result)
}
// Quote 按天数试算付费延期。GET /api/admin/package-silence/quote
func (h *PackageSilenceHandler) Quote(c *fiber.Ctx) error {
var req dto.SilenceQuoteRequest
if err := h.bindQuery(c, "延期试算参数不合法", &req); err != nil {
return err
}
result, err := h.extensions.Quote(c.UserContext(), req.PackageUsageID, req.Days, packagesilenceapp.Scope{})
if err != nil {
return err
}
return response.Success(c, result)
}
// CreateQRCodeOrder 运营创建在线延期订单并生成支付二维码。POST /api/admin/package-silence/orders/qrcode
func (h *PackageSilenceHandler) CreateQRCodeOrder(c *fiber.Ctx) error {
var req dto.SilenceOnlineOrderRequest
if err := h.bindBody(c, "延期下单参数不合法", &req); err != nil {
return err
}
result, err := h.extensions.CreateOnlineOrder(c.UserContext(), packagesilenceapp.OnlineOrderCommand{
Request: req, Scope: packagesilenceapp.Scope{}, PayerClientIP: c.IP(),
})
if err != nil {
return err
}
return response.Success(c, result)
}
// RegisterOffline 登记线下付费延期。POST /api/admin/package-silence/orders/offline
func (h *PackageSilenceHandler) RegisterOffline(c *fiber.Ctx) error {
var req dto.SilenceOfflineOrderRequest
if err := h.bindBody(c, "线下付费延期参数不合法", &req); err != nil {
return err
}
result, err := h.extensions.RegisterOffline(c.UserContext(), req)
if err != nil {
return err
}
return response.Success(c, result)
}
// ListOrders 延期订单列表。GET /api/admin/package-silence/orders
func (h *PackageSilenceHandler) ListOrders(c *fiber.Ctx) error {
var req dto.SilenceOrderListRequest
if err := h.bindQuery(c, "延期订单查询参数不合法", &req); err != nil {
return err
}
result, err := h.query.ListOrders(c.UserContext(), packagesilencequery.OrderFilter{Request: req})
if err != nil {
return err
}
return response.Success(c, result)
}
// GetOrder 延期订单详情。GET /api/admin/package-silence/orders/:id
func (h *PackageSilenceHandler) GetOrder(c *fiber.Ctx) error {
id, err := silencePathID(c)
if err != nil {
return err
}
result, err := h.query.GetOrder(c.UserContext(), id, 0)
if err != nil {
return err
}
return response.Success(c, result)
}
// SubmitSpecial 提交特殊延期(单个 / 批量)。POST /api/admin/package-silence/special
func (h *PackageSilenceHandler) SubmitSpecial(c *fiber.Ctx) error {
var req dto.SilenceSpecialRequest
if err := h.bindBody(c, "特殊延期参数不合法", &req); err != nil {
return err
}
result, err := h.extensions.SubmitSpecial(c.UserContext(), req)
if err != nil {
return err
}
return response.Success(c, result)
}
// ListBatches 延期批次列表。GET /api/admin/package-silence/batches
func (h *PackageSilenceHandler) ListBatches(c *fiber.Ctx) error {
var req dto.SilenceBatchListRequest
if err := h.bindQuery(c, "延期批次查询参数不合法", &req); err != nil {
return err
}
result, err := h.query.ListBatches(c.UserContext(), req)
if err != nil {
return err
}
return response.Success(c, result)
}
// GetBatch 延期批次详情(含明细)。GET /api/admin/package-silence/batches/:id
func (h *PackageSilenceHandler) GetBatch(c *fiber.Ctx) error {
id, err := silencePathID(c)
if err != nil {
return err
}
result, err := h.extensions.BatchDetail(c.UserContext(), id)
if err != nil {
return err
}
return response.Success(c, result)
}
// HandleException 异常明细重试 / 关闭。POST /api/admin/package-silence/extensions/:id/handle
func (h *PackageSilenceHandler) HandleException(c *fiber.Ctx) error {
id, err := silencePathID(c)
if err != nil {
return err
}
var req dto.SilenceExceptionHandleRequest
if err := h.bindBody(c, "异常处理参数不合法", &req); err != nil {
return err
}
result, err := h.extensions.HandleException(c.UserContext(), id, req)
if err != nil {
return err
}
return response.Success(c, result)
}
// ListRecords 套餐沉默期流水。GET /api/admin/package-silence/records
func (h *PackageSilenceHandler) ListRecords(c *fiber.Ctx) error {
var req dto.SilenceRecordListRequest
if err := h.bindQuery(c, "沉默期流水查询参数不合法", &req); err != nil {
return err
}
result, err := h.extensions.Records(c.UserContext(), req.PackageUsageID, packagesilenceapp.Scope{})
if err != nil {
return err
}
return response.Success(c, result)
}
// ListUsages 即将过期 / 已过期套餐。GET /api/admin/package-silence/usages
func (h *PackageSilenceHandler) ListUsages(c *fiber.Ctx) error {
var req dto.SilenceUsageListRequest
if err := h.bindQuery(c, "沉默期套餐查询参数不合法", &req); err != nil {
return err
}
result, err := h.query.ListUsages(c.UserContext(), req)
if err != nil {
return err
}
return response.Success(c, result)
}
// ListFirstPurchaseRecords 资产各世代首次购买套餐记录。GET /api/admin/assets/:asset_type/:id/first-purchase-records
func (h *PackageSilenceHandler) ListFirstPurchaseRecords(c *fiber.Ctx) error {
id, err := silencePathID(c)
if err != nil {
return err
}
assetType := c.Params("asset_type")
if assetType != "card" && assetType != "iot_card" && assetType != "device" {
return errors.New(errors.CodeInvalidParam, "资产类型仅支持 card 或 device")
}
result, err := h.query.ListFirstPurchaseRecords(c.UserContext(), assetType, id)
if err != nil {
return err
}
return response.Success(c, result)
}