feat: 实名即生效套餐沉默期、延期办理与资产出库时间
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Failing after 8m39s
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Failing after 8m39s
- 迁移 000259~000265:套餐记录沉默期字段与扫描索引、套餐默认沉默期、延期政策/流水/订单/批次/明细 5 张表、默认政策与系统配置(总开关默认关闭)、企微审批白名单、卡与设备出库时间及出库记录 - 沉默期设置(订单发放、自动购包、钱包续费)、后台购买时调整(超默认值按政策免审或企微审批)、统一判定与激活闸门、排队与孤儿扫描跳过过期套餐、过期套餐禁止退款、每 10 分钟扫描 - 总开关专用接口与打开前 4 项检查;延期政策;延期资格与计费;H5 自助与后台二维码在线支付(商户池路由)、支付回调与查单恢复、线下登记、特殊/批量延期、异常重试与原路退款;沉默期审批场景与终态处理 - 换货沉默期流水与出库时间;卡/设备列表、详情、导出与出库记录接口;延期订单与即将过期套餐列表及导出;财务流水纳入延期支付 - 沉默期与出库时间回填工具 cmd/silence-backfill;前端接口变更说明 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@ import (
|
||||
"gorm.io/gorm/clause"
|
||||
|
||||
cardObservationApp "github.com/break/junhong_cmp_fiber/internal/application/cardobservation"
|
||||
packagesilenceapp "github.com/break/junhong_cmp_fiber/internal/application/packagesilence"
|
||||
priorityapp "github.com/break/junhong_cmp_fiber/internal/application/prioritypolling"
|
||||
packagedomain "github.com/break/junhong_cmp_fiber/internal/domain/package"
|
||||
"github.com/break/junhong_cmp_fiber/internal/infrastructure/audit"
|
||||
@@ -54,6 +55,18 @@ type AutoPurchaseHandler struct {
|
||||
observationSeriesEvents cardObservationApp.SeriesEventWriter
|
||||
priorityEvents priorityapp.PriorityEventWriter
|
||||
auditWriter *audit.Writer
|
||||
silenceSetter *packagesilenceapp.Setter
|
||||
outboundRecorder *packagesilenceapp.OutboundRecorder
|
||||
}
|
||||
|
||||
// SetSilenceSetter 注入沉默期设置用例;自动购包是无人参与路径,只用套餐默认沉默期。
|
||||
func (h *AutoPurchaseHandler) SetSilenceSetter(setter *packagesilenceapp.Setter) {
|
||||
h.silenceSetter = setter
|
||||
}
|
||||
|
||||
// SetOutboundRecorder 注入资产出库时间写入用例。
|
||||
func (h *AutoPurchaseHandler) SetOutboundRecorder(recorder *packagesilenceapp.OutboundRecorder) {
|
||||
h.outboundRecorder = recorder
|
||||
}
|
||||
|
||||
// NewAutoPurchaseHandler 创建充值后自动购包处理器
|
||||
@@ -589,6 +602,7 @@ func (h *AutoPurchaseHandler) activatePackages(
|
||||
return err
|
||||
}
|
||||
|
||||
createdUsage := false
|
||||
for _, pkg := range packages {
|
||||
var existingUsage model.PackageUsage
|
||||
err := tx.Where("order_id = ? AND package_id = ?", order.ID, pkg.ID).First(&existingUsage).Error
|
||||
@@ -598,6 +612,7 @@ func (h *AutoPurchaseHandler) activatePackages(
|
||||
if err != gorm.ErrRecordNotFound {
|
||||
return err
|
||||
}
|
||||
createdUsage = true
|
||||
|
||||
if pkg.PackageType == constants.PackageTypeFormal {
|
||||
if err = h.activateMainPackage(ctx, tx, order, pkg, carrierType, carrierID, now); err != nil {
|
||||
@@ -611,7 +626,10 @@ func (h *AutoPurchaseHandler) activatePackages(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if createdUsage && h.outboundRecorder != nil {
|
||||
// 出库时间:资产当前世代第 1 次购买套餐的支付时间
|
||||
return h.outboundRecorder.RecordForOrderInTx(ctx, tx, order)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -722,6 +740,16 @@ func (h *AutoPurchaseHandler) activateMainPackage(
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
// 沉默期:无人参与路径只用套餐默认值(上下文不携带调整值)
|
||||
if h.silenceSetter != nil {
|
||||
paidAt := now
|
||||
if order.PaidAt != nil {
|
||||
paidAt = *order.PaidAt
|
||||
}
|
||||
if err = h.silenceSetter.ApplyOnUsageCreatedInTx(ctx, tx, usage, pkg, paidAt, constants.SilenceTriggerAutoPurchase); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 仅在生效状态分支追加优先轮询请求:待生效(含等待实名激活)不属于触发场景。
|
||||
if status == constants.PackageUsageStatusActive {
|
||||
|
||||
75
internal/task/package_silence_scan.go
Normal file
75
internal/task/package_silence_scan.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hibiken/asynq"
|
||||
"go.uber.org/zap"
|
||||
|
||||
packagesilenceapp "github.com/break/junhong_cmp_fiber/internal/application/packagesilence"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/auditcontext"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
)
|
||||
|
||||
// PackageSilenceScanHandler 处理沉默期扫描任务 package:silence:scan(每 10 分钟)。
|
||||
// 总开关关闭时直接返回;判定使用条件更新,重复执行幂等。
|
||||
type PackageSilenceScanHandler struct {
|
||||
resolver *packagesilenceapp.Resolver
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewPackageSilenceScanHandler 创建沉默期扫描任务处理器。
|
||||
func NewPackageSilenceScanHandler(resolver *packagesilenceapp.Resolver, logger *zap.Logger) *PackageSilenceScanHandler {
|
||||
return &PackageSilenceScanHandler{resolver: resolver, logger: logger}
|
||||
}
|
||||
|
||||
// Handle 执行一轮沉默期扫描。
|
||||
func (h *PackageSilenceScanHandler) Handle(ctx context.Context, _ *asynq.Task) error {
|
||||
if h.resolver == nil {
|
||||
return nil
|
||||
}
|
||||
ctx = auditcontext.With(ctx, auditcontext.Context{
|
||||
ActorKind: constants.AuditActorScheduledJob, ActorID: constants.TaskTypePackageSilenceScan,
|
||||
ActorName: "沉默期扫描任务", Source: constants.AuditSourceScheduler,
|
||||
})
|
||||
result, err := h.resolver.Scan(ctx)
|
||||
if err != nil {
|
||||
h.logger.Error("沉默期扫描失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
if result.Scanned > 0 {
|
||||
h.logger.Info("沉默期扫描完成", zap.Int("scanned", result.Scanned), zap.Int("expired", result.Expired), zap.Int("ended", result.Ended))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PackageSilenceOrderRecoverHandler 处理延期订单查单恢复任务(每 5 分钟):查单、超时关闭、异常订单原路退款。
|
||||
type PackageSilenceOrderRecoverHandler struct {
|
||||
extensions *packagesilenceapp.ExtensionService
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewPackageSilenceOrderRecoverHandler 创建延期订单恢复任务处理器。
|
||||
func NewPackageSilenceOrderRecoverHandler(extensions *packagesilenceapp.ExtensionService, logger *zap.Logger) *PackageSilenceOrderRecoverHandler {
|
||||
return &PackageSilenceOrderRecoverHandler{extensions: extensions, logger: logger}
|
||||
}
|
||||
|
||||
// Handle 执行一轮延期订单恢复。
|
||||
func (h *PackageSilenceOrderRecoverHandler) Handle(ctx context.Context, _ *asynq.Task) error {
|
||||
if h.extensions == nil {
|
||||
return nil
|
||||
}
|
||||
ctx = auditcontext.With(ctx, auditcontext.Context{
|
||||
ActorKind: constants.AuditActorScheduledJob, ActorID: constants.TaskTypePackageSilenceOrderRecover,
|
||||
ActorName: "沉默期延期订单恢复任务", Source: constants.AuditSourceScheduler,
|
||||
})
|
||||
result, err := h.extensions.Recover(ctx)
|
||||
if err != nil {
|
||||
h.logger.Error("沉默期延期订单恢复失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
if result.Confirmed+result.Closed+result.Refunded > 0 {
|
||||
h.logger.Info("沉默期延期订单恢复完成", zap.Int("confirmed", result.Confirmed), zap.Int("closed", result.Closed), zap.Int("refunded", result.Refunded))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user