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>
219 lines
8.5 KiB
Go
219 lines
8.5 KiB
Go
package exporter
|
||
|
||
import (
|
||
"context"
|
||
"strconv"
|
||
|
||
"gorm.io/gorm"
|
||
|
||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||
"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/constants"
|
||
)
|
||
|
||
// 沉默期延期订单与即将过期 / 已过期套餐导出(add-package-silence-period)。
|
||
// 导出在 Worker 中执行,数据范围按任务创建时冻结的账号类型与店铺范围重建上下文。
|
||
|
||
var silenceOrderHeaders = []string{
|
||
"订单号", "批次号", "资产类型", "资产标识", "套餐名称", "来源", "状态", "输入天数", "计费月数", "延长天数",
|
||
"单价(元/月)", "金额(元)", "政策", "下单时截止", "预计新截止", "支付方式", "收款商户", "交易流水号",
|
||
"线下收款方式", "线下交易单号", "实付金额(元)", "支付时间", "延期生效时间", "退款单号", "退款金额(元)", "退款时间", "异常说明", "创建时间",
|
||
}
|
||
|
||
var silenceUsageHeaders = []string{
|
||
"套餐记录ID", "订单号", "资产类型", "资产标识", "店铺", "套餐名称", "购买时间", "沉默期状态", "沉默期天数",
|
||
"截止时间", "过期时间", "累计延期天数", "付费延期天数", "延期次数", "免费延期次数", "调整审批中",
|
||
}
|
||
|
||
// silenceExportContext 用任务冻结的账号类型与店铺范围构造查询上下文。
|
||
func silenceExportContext(ctx context.Context, params ExportParams) context.Context {
|
||
ctx = context.WithValue(ctx, constants.ContextKeyUserType, params.UserType)
|
||
if params.CreatorShopID != nil {
|
||
ctx = context.WithValue(ctx, constants.ContextKeyShopID, *params.CreatorShopID)
|
||
}
|
||
if params.UserType == constants.UserTypeAgent {
|
||
ctx = context.WithValue(ctx, constants.ContextKeySubordinateShopIDs, append([]uint{}, params.ScopeShopIDs...))
|
||
}
|
||
return ctx
|
||
}
|
||
|
||
// SilenceOrderDataSource 沉默期延期订单导出数据源(场景 package_silence_order)。
|
||
type SilenceOrderDataSource struct {
|
||
query *packagesilencequery.Query
|
||
}
|
||
|
||
// NewSilenceOrderDataSource 创建延期订单导出数据源。
|
||
func NewSilenceOrderDataSource(db *gorm.DB) *SilenceOrderDataSource {
|
||
return &SilenceOrderDataSource{query: packagesilencequery.NewQuery(db)}
|
||
}
|
||
|
||
// Scene 返回导出场景编码。
|
||
func (s *SilenceOrderDataSource) Scene() string { return constants.ExportTaskSceneSilenceOrder }
|
||
|
||
func (s *SilenceOrderDataSource) filter(params ExportParams) packagesilencequery.OrderFilter {
|
||
req := dto.SilenceOrderListRequest{}
|
||
if id, ok := filterUint(params.Filters, "package_usage_id"); ok && id > 0 {
|
||
req.PackageUsageID = &id
|
||
}
|
||
if id, ok := filterUint(params.Filters, "package_id"); ok && id > 0 {
|
||
req.PackageID = &id
|
||
}
|
||
req.AssetIdentifier, _ = filterString(params.Filters, "asset_identifier")
|
||
req.OrderNo, _ = filterString(params.Filters, "order_no")
|
||
req.Status, _ = filterString(params.Filters, "status")
|
||
req.Source, _ = filterString(params.Filters, "source")
|
||
req.PaymentMethod, _ = filterString(params.Filters, "payment_method")
|
||
req.MerchantName, _ = filterString(params.Filters, "merchant_name")
|
||
req.ThirdPartyTradeNo, _ = filterString(params.Filters, "third_party_trade_no")
|
||
req.PaidStart, _ = filterString(params.Filters, "paid_start")
|
||
req.PaidEnd, _ = filterString(params.Filters, "paid_end")
|
||
return packagesilencequery.OrderFilter{Request: req}
|
||
}
|
||
|
||
// Count 统计导出行数。
|
||
func (s *SilenceOrderDataSource) Count(ctx context.Context, params ExportParams) (int, error) {
|
||
query, err := s.query.OrderQuery(silenceExportContext(ctx, params), s.filter(params))
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
var total int64
|
||
if err := query.Count(&total).Error; err != nil {
|
||
return 0, err
|
||
}
|
||
return int(total), nil
|
||
}
|
||
|
||
// Headers 返回导出表头。
|
||
func (s *SilenceOrderDataSource) Headers(context.Context, ExportParams) ([]string, error) {
|
||
return silenceOrderHeaders, nil
|
||
}
|
||
|
||
// Fetch 按 offset/limit 返回导出行。
|
||
func (s *SilenceOrderDataSource) Fetch(ctx context.Context, params ExportParams, offset, limit int) ([][]string, error) {
|
||
if limit <= 0 {
|
||
return [][]string{}, nil
|
||
}
|
||
ctx = silenceExportContext(ctx, params)
|
||
query, err := s.query.OrderQuery(ctx, s.filter(params))
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
var orders []model.PackageSilenceExtensionOrder
|
||
if err := query.Select("o.*").Order("o.id ASC").Offset(offset).Limit(limit).Find(&orders).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
items, err := s.query.OrderItems(ctx, orders)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
rows := make([][]string, 0, len(items))
|
||
for _, item := range items {
|
||
rows = append(rows, []string{
|
||
item.OrderNo, item.BatchNo, item.AssetType, item.AssetIdentifier, item.PackageName, item.SourceName, item.StatusName,
|
||
strconv.Itoa(item.InputDays), strconv.Itoa(item.Months), strconv.Itoa(item.ExtensionDays),
|
||
formatMoneyYuan(item.UnitPrice), formatMoneyYuan(item.Amount), item.PolicyName,
|
||
item.BeforeDeadlineAt.Format(exportTimeLayout), item.ExpectedDeadlineAt.Format(exportTimeLayout),
|
||
item.PaymentMethod, item.PaymentMerchantName, item.ThirdPartyTradeNo, item.OfflinePaymentMethodName, item.OfflineTradeNo,
|
||
formatOptionalMoney(item.PaidAmount), formatOptionalTime(item.PaidAt), formatOptionalTime(item.CompletedAt),
|
||
item.RefundNo, formatOptionalMoney(item.RefundAmount), formatOptionalTime(item.RefundedAt), item.ExceptionSummary,
|
||
item.CreatedAt.Format(exportTimeLayout),
|
||
})
|
||
}
|
||
return rows, nil
|
||
}
|
||
|
||
// SilenceUsageDataSource 即将过期 / 已过期套餐导出数据源(场景 package_silence_usage)。
|
||
type SilenceUsageDataSource struct {
|
||
query *packagesilencequery.Query
|
||
}
|
||
|
||
// NewSilenceUsageDataSource 创建沉默期套餐导出数据源。
|
||
func NewSilenceUsageDataSource(db *gorm.DB) *SilenceUsageDataSource {
|
||
return &SilenceUsageDataSource{query: packagesilencequery.NewQuery(db)}
|
||
}
|
||
|
||
// Scene 返回导出场景编码。
|
||
func (s *SilenceUsageDataSource) Scene() string { return constants.ExportTaskSceneSilenceUsage }
|
||
|
||
func (s *SilenceUsageDataSource) request(params ExportParams) dto.SilenceUsageListRequest {
|
||
req := dto.SilenceUsageListRequest{}
|
||
req.View, _ = filterString(params.Filters, "view")
|
||
if days, ok := filterInt(params.Filters, "within_days"); ok {
|
||
req.WithinDays = days
|
||
}
|
||
req.AssetIdentifier, _ = filterString(params.Filters, "asset_identifier")
|
||
if id, ok := filterUint(params.Filters, "package_id"); ok && id > 0 {
|
||
req.PackageID = &id
|
||
}
|
||
if id, ok := filterUint(params.Filters, "shop_id"); ok && id > 0 {
|
||
req.ShopID = &id
|
||
}
|
||
req.DeadlineStart, _ = filterString(params.Filters, "deadline_start")
|
||
req.DeadlineEnd, _ = filterString(params.Filters, "deadline_end")
|
||
return req
|
||
}
|
||
|
||
// Count 统计导出行数。
|
||
func (s *SilenceUsageDataSource) Count(ctx context.Context, params ExportParams) (int, error) {
|
||
query, err := s.query.UsageQuery(silenceExportContext(ctx, params), s.request(params))
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
var total int64
|
||
if err := query.Count(&total).Error; err != nil {
|
||
return 0, err
|
||
}
|
||
return int(total), nil
|
||
}
|
||
|
||
// Headers 返回导出表头。
|
||
func (s *SilenceUsageDataSource) Headers(context.Context, ExportParams) ([]string, error) {
|
||
return silenceUsageHeaders, nil
|
||
}
|
||
|
||
// Fetch 按 offset/limit 返回导出行。
|
||
func (s *SilenceUsageDataSource) Fetch(ctx context.Context, params ExportParams, offset, limit int) ([][]string, error) {
|
||
if limit <= 0 {
|
||
return [][]string{}, nil
|
||
}
|
||
ctx = silenceExportContext(ctx, params)
|
||
query, err := s.query.UsageQuery(ctx, s.request(params))
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
items, err := s.query.FetchUsages(ctx, query, offset, limit)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
rows := make([][]string, 0, len(items))
|
||
for _, item := range items {
|
||
view := item.Silence
|
||
if view == nil {
|
||
view = &dto.PackageSilenceView{}
|
||
}
|
||
rows = append(rows, []string{
|
||
strconv.FormatUint(uint64(item.PackageUsageID), 10), item.OrderNo, item.AssetType, item.AssetIdentifier, item.ShopName,
|
||
item.PackageName, item.CreatedAt.Format(exportTimeLayout), view.SilenceStatusName, formatOptionalInt(view.SilencePeriodDays),
|
||
formatOptionalTime(view.SilenceDeadlineAt), formatOptionalTime(view.SilenceExpiredAt), strconv.Itoa(view.SilenceExtendedDays),
|
||
strconv.Itoa(view.SilencePaidExtendedDays), strconv.Itoa(view.SilenceExtensionCount), strconv.Itoa(view.SilenceFreeExtensionUsed),
|
||
formatYesNo(view.SilenceAdjustPending),
|
||
})
|
||
}
|
||
return rows, nil
|
||
}
|
||
|
||
func formatOptionalMoney(value *int64) string {
|
||
if value == nil {
|
||
return ""
|
||
}
|
||
return formatMoneyYuan(*value)
|
||
}
|
||
|
||
func formatOptionalInt(value *int) string {
|
||
if value == nil {
|
||
return ""
|
||
}
|
||
return strconv.Itoa(*value)
|
||
}
|