package exporter import ( "context" "time" "gorm.io/gorm" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/pkg/constants" ) // RefundDataSource 退款记录导出数据源。 type RefundDataSource struct { db *gorm.DB } // NewRefundDataSource 创建退款记录导出数据源。 func NewRefundDataSource(db *gorm.DB) *RefundDataSource { return &RefundDataSource{db: db} } // Scene 返回导出场景编码。 func (s *RefundDataSource) Scene() string { return constants.ExportTaskSceneRefund } // Count 统计退款记录导出行数。 func (s *RefundDataSource) Count(ctx context.Context, params ExportParams) (int, error) { var total int64 query := s.applyFilters(s.baseQuery(ctx), params) if err := query.Count(&total).Error; err != nil { return 0, err } return int(total), nil } // Headers 返回退款记录导出表头。 func (s *RefundDataSource) Headers(context.Context, ExportParams) ([]string, error) { return []string{ "退款单号", "代理店铺名称", "关联的支付订单号", "资产类型", "资产标识", "套餐名称", "原订单金额(元)", "实收金额(元)", "可退金额(元)", "申请退款金额(元)", "实际退款金额(元)", "状态", "退款原因", "审批备注", "审批来源", "审批状态", "退款处理状态", "退款申请时间", "退款审批时间", "提交人", "退款凭证", }, nil } // Fetch 按 offset/limit 查询退款记录导出数据。 func (s *RefundDataSource) Fetch(ctx context.Context, params ExportParams, offset, limit int) ([][]string, error) { if limit <= 0 { return [][]string{}, nil } var items []refundExportRow query := s.applyFilters(s.baseQuery(ctx), params). Select(` r.refund_no, r.order_no, r.order_type, r.asset_identifier, r.actual_received_amount, r.requested_refund_amount, r.approved_refund_amount, r.status, r.refund_reason, r.remark, r.commission_deducted, r.asset_reset, r.created_at, r.processed_at, COALESCE(sh.shop_name, '') AS shop_name, o.total_amount AS original_amount, o.actual_paid_amount AS refundable_amount, COALESCE(pu.package_name, items.package_names, '') AS package_name, COALESCE(ac.username, '') AS submitter_name, ai.provider AS approval_provider, ai.status AS approval_status, COALESCE((SELECT string_agg(voucher.value, ',' ORDER BY voucher.ordinality) FROM jsonb_array_elements_text(COALESCE(r.refund_voucher_key, '[]'::jsonb)) WITH ORDINALITY AS voucher(value, ordinality)), '') AS voucher_keys `). Joins("LEFT JOIN tb_shop AS sh ON sh.id = r.shop_id"). Joins("LEFT JOIN tb_order AS o ON o.id = r.order_id AND o.deleted_at IS NULL"). Joins("LEFT JOIN tb_package_usage AS pu ON pu.id = r.package_usage_id AND pu.deleted_at IS NULL"). Joins("LEFT JOIN tb_account AS ac ON ac.id = r.creator"). Joins("LEFT JOIN tb_approval_instance AS ai ON ai.id = r.approval_instance_id"). Joins(`LEFT JOIN LATERAL ( SELECT string_agg(oi.package_name, ',' ORDER BY oi.id) AS package_names FROM tb_order_item AS oi WHERE oi.order_id = r.order_id AND oi.deleted_at IS NULL ) AS items ON TRUE`). Order("r.id ASC"). Limit(limit). Offset(offset) if err := query.Scan(&items).Error; err != nil { return nil, err } rows := make([][]string, 0, len(items)) for _, item := range items { rows = append(rows, []string{ item.RefundNo, item.ShopName, item.OrderNo, formatRefundAssetType(item.OrderType), item.AssetIdentifier, item.PackageName, formatOptionalMoneyYuan(item.OriginalAmount), formatMoneyYuan(item.ActualReceivedAmount), formatOptionalMoneyYuan(item.RefundableAmount), formatMoneyYuan(item.RequestedRefundAmount), formatOptionalMoneyYuan(item.ApprovedRefundAmount), constants.GetRefundStatusName(item.Status), item.RefundReason, item.Remark, formatRefundApprovalSource(item.ApprovalProvider), formatOptionalApprovalStatus(item.ApprovalStatus), formatRefundProcessingStatus(item.Status, item.CommissionDeducted, item.AssetReset), item.CreatedAt.Format(exportTimeLayout), formatOptionalTime(item.ProcessedAt), item.SubmitterName, item.VoucherKeys, }) } return rows, nil } func (s *RefundDataSource) baseQuery(ctx context.Context) *gorm.DB { return s.db.WithContext(ctx).Table("tb_refund_request AS r").Where("r.deleted_at IS NULL") } func (s *RefundDataSource) applyFilters(query *gorm.DB, params ExportParams) *gorm.DB { query = applyExportShopScope(query, params, "r.shop_id") if status, ok := filterInt(params.Filters, "status"); ok { query = query.Where("r.status = ?", status) } if orderID, ok := filterUint(params.Filters, "order_id"); ok { query = query.Where("r.order_id = ?", orderID) } if shopID, ok := filterUint(params.Filters, "shop_id"); ok { query = query.Where("r.shop_id = ?", shopID) } if identifier, ok := filterString(params.Filters, "asset_identifier"); ok { query = query.Where("r.asset_identifier = ?", identifier) } return query } type refundExportRow struct { RefundNo string `gorm:"column:refund_no"` ShopName string `gorm:"column:shop_name"` OrderNo string `gorm:"column:order_no"` OrderType string `gorm:"column:order_type"` AssetIdentifier string `gorm:"column:asset_identifier"` PackageName string `gorm:"column:package_name"` OriginalAmount *int64 `gorm:"column:original_amount"` ActualReceivedAmount int64 `gorm:"column:actual_received_amount"` RefundableAmount *int64 `gorm:"column:refundable_amount"` RequestedRefundAmount int64 `gorm:"column:requested_refund_amount"` ApprovedRefundAmount *int64 `gorm:"column:approved_refund_amount"` Status int `gorm:"column:status"` RefundReason string `gorm:"column:refund_reason"` Remark string `gorm:"column:remark"` ApprovalProvider *string `gorm:"column:approval_provider"` ApprovalStatus *int `gorm:"column:approval_status"` CommissionDeducted bool `gorm:"column:commission_deducted"` AssetReset bool `gorm:"column:asset_reset"` CreatedAt time.Time `gorm:"column:created_at"` ProcessedAt *time.Time `gorm:"column:processed_at"` SubmitterName string `gorm:"column:submitter_name"` VoucherKeys string `gorm:"column:voucher_keys"` } func formatRefundAssetType(orderType string) string { switch orderType { case model.OrderTypeSingleCard: return "物联网卡" case model.OrderTypeDevice: return "设备" case "": return "" default: return "未知" } } func formatRefundApprovalSource(provider *string) string { if provider == nil || *provider == "" { return "历史审批" } return formatApprovalProvider(provider) } func formatRefundProcessingStatus(status int, commissionDeducted, assetReset bool) string { switch status { case model.RefundStatusPending, model.RefundStatusReturned: return "待审批" case model.RefundStatusRejected: return "无需处理" case model.RefundStatusApproved: if commissionDeducted && assetReset { return "已完成" } return "处理中" default: return "未知" } }