All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m34s
- 新增 GET /orders/platform、/orders/agent、/orders/online,类别由路径固定,参数、响应、权限同原订单列表 - 新增导出场景 order_platform、order_agent、order_online,筛选与表头同 order - 类别按钱从哪来划分:客户端下单为在线支付;后台代理钱包扣款(含平台代扣)为代理商;其余后台订单为平台 - 列表与导出共用 internal/query/ordercategory 的类别条件 - 企业账号访问订单列表、详情、购买预检改为无权限,修复其可看到全部订单的问题 - 原订单列表与 order 导出场景保留不变 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
643 lines
18 KiB
Go
643 lines
18 KiB
Go
package exporter
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"strings"
|
||
"time"
|
||
|
||
"gorm.io/gorm"
|
||
|
||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||
"github.com/break/junhong_cmp_fiber/internal/query/ordercategory"
|
||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||
)
|
||
|
||
const (
|
||
orderExportBaseHeaderCount = 11
|
||
orderExportCardGroupSize = 2
|
||
orderExportMinCardGroups = 1
|
||
// orderExportOperatorHeader 操作人列固定追加在最后,历史任务的冻结表头没有该列。
|
||
orderExportOperatorHeader = "操作人"
|
||
)
|
||
|
||
// OrderDataSource 订单导出数据源。category 为空时导出全部订单(场景 order),
|
||
// 非空时只导出该类别的订单(场景 order_platform / order_agent / order_online),其余逻辑完全相同。
|
||
type OrderDataSource struct {
|
||
db *gorm.DB
|
||
category string
|
||
}
|
||
|
||
// NewOrderDataSource 创建订单导出数据源;category 为空表示不限类别。
|
||
func NewOrderDataSource(db *gorm.DB, category string) *OrderDataSource {
|
||
return &OrderDataSource{db: db, category: category}
|
||
}
|
||
|
||
// Scene 返回导出场景编码。
|
||
func (s *OrderDataSource) Scene() string {
|
||
switch s.category {
|
||
case constants.OrderCategoryPlatform:
|
||
return constants.ExportTaskSceneOrderPlatform
|
||
case constants.OrderCategoryAgent:
|
||
return constants.ExportTaskSceneOrderAgent
|
||
case constants.OrderCategoryOnline:
|
||
return constants.ExportTaskSceneOrderOnline
|
||
default:
|
||
return constants.ExportTaskSceneOrder
|
||
}
|
||
}
|
||
|
||
// Count 统计订单导出行数。
|
||
func (s *OrderDataSource) Count(ctx context.Context, params ExportParams) (int, error) {
|
||
query, err := s.applyFilters(ctx, s.baseQuery(ctx), 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 *OrderDataSource) Headers(ctx context.Context, params ExportParams) ([]string, error) {
|
||
cardGroups, err := s.resolveCardGroupCount(ctx, params)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return buildOrderExportHeaders(cardGroups), nil
|
||
}
|
||
|
||
// Fetch 按 offset/limit 查询订单导出数据。
|
||
func (s *OrderDataSource) Fetch(ctx context.Context, params ExportParams, offset, limit int) ([][]string, error) {
|
||
if limit <= 0 {
|
||
return [][]string{}, nil
|
||
}
|
||
|
||
cardGroups := orderCardGroupCountFromHeaders(params.ResolvedHeaders)
|
||
includeOperator := len(params.ResolvedHeaders) == 0 || orderHeadersIncludeOperator(params.ResolvedHeaders)
|
||
if cardGroups <= 0 {
|
||
resolved, err := s.resolveCardGroupCount(ctx, params)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
cardGroups = resolved
|
||
}
|
||
|
||
orders, err := s.fetchOrders(ctx, params, offset, limit)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if len(orders) == 0 {
|
||
return [][]string{}, nil
|
||
}
|
||
|
||
orderIDs := make([]uint, 0, len(orders))
|
||
deviceIDs := make([]uint, 0, len(orders))
|
||
for _, item := range orders {
|
||
orderIDs = append(orderIDs, item.ID)
|
||
if item.DeviceID != nil {
|
||
deviceIDs = append(deviceIDs, *item.DeviceID)
|
||
}
|
||
}
|
||
|
||
itemMap, err := s.fetchOrderItems(ctx, orderIDs)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
cardMap, err := s.fetchBoundCards(ctx, deviceIDs)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
paymentMap, err := s.fetchPreferredPayments(ctx, orderIDs)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
rows := make([][]string, 0, len(orders))
|
||
for _, item := range orders {
|
||
var cards []orderExportCardRow
|
||
if item.DeviceID != nil {
|
||
cards = cardMap[*item.DeviceID]
|
||
}
|
||
row := buildOrderExportRow(item, itemMap[item.ID], paymentMap[item.ID], cards, cardGroups)
|
||
if includeOperator {
|
||
row = append(row, item.OperatorAccountName)
|
||
}
|
||
rows = append(rows, row)
|
||
}
|
||
return rows, nil
|
||
}
|
||
|
||
func (s *OrderDataSource) baseQuery(ctx context.Context) *gorm.DB {
|
||
return s.db.WithContext(ctx).
|
||
Table("tb_order AS o").
|
||
Where("o.deleted_at IS NULL")
|
||
}
|
||
|
||
// applyFilters 应用与 /api/admin/orders 当前实际行为一致的筛选条件。
|
||
// is_expired 在接口 Service 中未进入 Store 筛选,这里按当前接口行为忽略。
|
||
func (s *OrderDataSource) applyFilters(ctx context.Context, query *gorm.DB, params ExportParams) (*gorm.DB, error) {
|
||
query = s.applyOrderScope(query, params)
|
||
// 订单类别与列表共用同一份条件,导出条数与对应列表一致。
|
||
if s.category != "" {
|
||
condition, args, ok := ordercategory.Condition("o", s.category)
|
||
if !ok {
|
||
return nil, fmt.Errorf("不支持的订单类别: %s", s.category)
|
||
}
|
||
query = query.Where(condition, args...)
|
||
}
|
||
|
||
if params.UserType == constants.UserTypeAgent {
|
||
if params.CreatorShopID != nil {
|
||
query = query.Where("o.buyer_type = ? AND o.buyer_id = ?", model.BuyerTypeAgent, *params.CreatorShopID)
|
||
} else {
|
||
query = query.Where("o.buyer_type = ?", model.BuyerTypeAgent)
|
||
}
|
||
}
|
||
if paymentStatus, ok := filterInt(params.Filters, "payment_status"); ok {
|
||
query = query.Where("o.payment_status = ?", paymentStatus)
|
||
}
|
||
if paymentMethod, ok := filterString(params.Filters, "payment_method"); ok {
|
||
query = query.Where("o.payment_method = ?", paymentMethod)
|
||
}
|
||
if orderType, ok := filterString(params.Filters, "order_type"); ok {
|
||
query = query.Where("o.order_type = ?", orderType)
|
||
}
|
||
if orderNo, ok := filterString(params.Filters, "order_no"); ok {
|
||
query = query.Where("o.order_no = ?", orderNo)
|
||
}
|
||
if purchaseRole, ok := filterString(params.Filters, "purchase_role"); ok {
|
||
query = query.Where("o.purchase_role = ?", purchaseRole)
|
||
}
|
||
if sellerShopID, ok := filterUint(params.Filters, "seller_shop_id"); ok {
|
||
query = query.Where("o.seller_shop_id = ?", sellerShopID)
|
||
}
|
||
start, end, err := strictTimeRange(params.Filters)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if start != nil {
|
||
query = query.Where("o.created_at >= ?", *start)
|
||
}
|
||
if end != nil {
|
||
query = query.Where("o.created_at <= ?", *end)
|
||
}
|
||
if buyerPhone, ok := filterString(params.Filters, "buyer_phone"); ok {
|
||
query = query.Where("o.buyer_phone = ?", buyerPhone)
|
||
}
|
||
if identifier, ok := filterString(params.Filters, "identifier"); ok {
|
||
iotCardID, deviceID, err := s.resolveOrderListAssetIdentifier(ctx, params, identifier)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
assetQuery := s.db.Where("o.asset_identifier = ?", identifier)
|
||
if iotCardID != nil {
|
||
assetQuery = assetQuery.Or("o.iot_card_id = ?", *iotCardID)
|
||
}
|
||
if deviceID != nil {
|
||
assetQuery = assetQuery.Or("o.device_id = ?", *deviceID)
|
||
}
|
||
query = query.Where(assetQuery)
|
||
}
|
||
return query, nil
|
||
}
|
||
|
||
func (s *OrderDataSource) applyOrderScope(query *gorm.DB, params ExportParams) *gorm.DB {
|
||
switch params.UserType {
|
||
case constants.UserTypeSuperAdmin, constants.UserTypePlatform:
|
||
return query
|
||
case constants.UserTypeAgent:
|
||
if len(params.ScopeShopIDs) == 0 {
|
||
return query.Where("1 = 0")
|
||
}
|
||
return query.Where(
|
||
s.db.Where("o.buyer_type = ? AND o.buyer_id IN ?", model.BuyerTypeAgent, params.ScopeShopIDs).
|
||
Or("o.seller_shop_id IN ?", params.ScopeShopIDs).
|
||
Or(s.db.Where("o.seller_shop_id IS NULL AND o.operator_type = ? AND o.operator_id IN ?", model.OperatorAccountTypeAgent, params.ScopeShopIDs)),
|
||
)
|
||
default:
|
||
return query.Where("1 = 0")
|
||
}
|
||
}
|
||
|
||
func (s *OrderDataSource) resolveCardGroupCount(ctx context.Context, params ExportParams) (int, error) {
|
||
filtered, err := s.applyFilters(ctx, s.baseQuery(ctx), params)
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
|
||
var count int64
|
||
query := filtered.
|
||
Select("COALESCE(MAX(bound_cards.card_count), 0)").
|
||
Joins(`
|
||
LEFT JOIN LATERAL (
|
||
SELECT COUNT(*) AS card_count
|
||
FROM tb_device_sim_binding AS b
|
||
WHERE b.device_id = o.device_id
|
||
AND b.bind_status = ?
|
||
AND b.deleted_at IS NULL
|
||
) AS bound_cards ON TRUE
|
||
`, constants.BindStatusBound)
|
||
if err := query.Scan(&count).Error; err != nil {
|
||
return 0, err
|
||
}
|
||
if count < orderExportMinCardGroups {
|
||
return orderExportMinCardGroups, nil
|
||
}
|
||
return int(count), nil
|
||
}
|
||
|
||
func (s *OrderDataSource) fetchOrders(ctx context.Context, params ExportParams, offset, limit int) ([]orderExportRow, error) {
|
||
var orders []orderExportRow
|
||
filtered, err := s.applyFilters(ctx, s.baseQuery(ctx), params)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
query := filtered.
|
||
Joins("LEFT JOIN tb_shop AS sh ON sh.id = o.seller_shop_id").
|
||
Select(`
|
||
o.id,
|
||
o.order_no,
|
||
o.asset_identifier,
|
||
o.seller_cost_price,
|
||
o.total_amount,
|
||
o.actual_paid_amount,
|
||
o.payment_status,
|
||
o.payment_method,
|
||
o.created_at,
|
||
o.device_id,
|
||
o.operator_account_name,
|
||
COALESCE(sh.shop_name, '') AS shop_name
|
||
`).
|
||
Order("o.id DESC").
|
||
Limit(limit).
|
||
Offset(offset)
|
||
if err := query.Scan(&orders).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
return orders, nil
|
||
}
|
||
|
||
func (s *OrderDataSource) fetchOrderItems(ctx context.Context, orderIDs []uint) (map[uint][]orderExportItemRow, error) {
|
||
result := make(map[uint][]orderExportItemRow, len(orderIDs))
|
||
if len(orderIDs) == 0 {
|
||
return result, nil
|
||
}
|
||
|
||
var items []orderExportItemRow
|
||
if err := s.db.WithContext(ctx).
|
||
Table("tb_order_item").
|
||
Select("order_id, package_name, quantity").
|
||
Where("order_id IN ?", orderIDs).
|
||
Where("deleted_at IS NULL").
|
||
Order("order_id ASC, id ASC").
|
||
Scan(&items).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
for _, item := range items {
|
||
result[item.OrderID] = append(result[item.OrderID], item)
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
func (s *OrderDataSource) fetchBoundCards(ctx context.Context, deviceIDs []uint) (map[uint][]orderExportCardRow, error) {
|
||
result := make(map[uint][]orderExportCardRow, len(deviceIDs))
|
||
deviceIDs = normalizeUintSlice(deviceIDs)
|
||
if len(deviceIDs) == 0 {
|
||
return result, nil
|
||
}
|
||
|
||
var cards []orderExportCardRow
|
||
if err := s.db.WithContext(ctx).
|
||
Table("tb_device_sim_binding AS b").
|
||
Select(`
|
||
b.device_id,
|
||
b.slot_position,
|
||
c.msisdn,
|
||
c.iccid
|
||
`).
|
||
Joins("JOIN tb_iot_card AS c ON c.id = b.iot_card_id AND c.deleted_at IS NULL").
|
||
Where("b.device_id IN ?", deviceIDs).
|
||
Where("b.bind_status = ?", constants.BindStatusBound).
|
||
Where("b.deleted_at IS NULL").
|
||
Order("b.device_id ASC, b.slot_position ASC, b.id ASC").
|
||
Scan(&cards).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
for _, card := range cards {
|
||
result[card.DeviceID] = append(result[card.DeviceID], card)
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
func (s *OrderDataSource) fetchPreferredPayments(ctx context.Context, orderIDs []uint) (map[uint]orderExportPaymentRow, error) {
|
||
result := make(map[uint]orderExportPaymentRow, len(orderIDs))
|
||
if len(orderIDs) == 0 {
|
||
return result, nil
|
||
}
|
||
|
||
var payments []orderExportPaymentRow
|
||
if err := s.db.WithContext(ctx).
|
||
Table("tb_payment").
|
||
Select("order_id, third_party_trade_no, status").
|
||
Where("order_id IN ?", orderIDs).
|
||
Where("order_type = ?", model.PaymentOrderTypePackage).
|
||
Where("deleted_at IS NULL").
|
||
Order("order_id ASC, CASE WHEN status = 1 THEN 0 ELSE 1 END ASC, created_at DESC, id DESC").
|
||
Scan(&payments).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
for _, payment := range payments {
|
||
if _, exists := result[payment.OrderID]; exists {
|
||
continue
|
||
}
|
||
result[payment.OrderID] = payment
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
func (s *OrderDataSource) resolveOrderListAssetIdentifier(ctx context.Context, params ExportParams, identifier string) (*uint, *uint, error) {
|
||
if identifier == "" {
|
||
return nil, nil, nil
|
||
}
|
||
|
||
var registry orderExportAssetIdentifierRow
|
||
registryErr := s.db.WithContext(ctx).
|
||
Table("tb_asset_identifier").
|
||
Select("asset_type, asset_id").
|
||
Where("identifier = ?", identifier).
|
||
First(®istry).Error
|
||
if registryErr != nil && registryErr != gorm.ErrRecordNotFound {
|
||
return nil, nil, registryErr
|
||
}
|
||
if registryErr == nil {
|
||
switch registry.AssetType {
|
||
case model.AssetTypeIotCard:
|
||
if s.assetVisible(ctx, params, "tb_iot_card", registry.AssetID) {
|
||
cardID := registry.AssetID
|
||
return &cardID, nil, nil
|
||
}
|
||
case model.AssetTypeDevice:
|
||
if s.assetVisible(ctx, params, "tb_device", registry.AssetID) {
|
||
deviceID := registry.AssetID
|
||
return nil, &deviceID, nil
|
||
}
|
||
}
|
||
}
|
||
|
||
if deviceID, err := s.resolveDeviceIDByIdentifier(ctx, params, identifier); err != nil {
|
||
return nil, nil, err
|
||
} else if deviceID != nil {
|
||
return nil, deviceID, nil
|
||
}
|
||
|
||
cardID, err := s.resolveCardIDByIdentifier(ctx, params, identifier)
|
||
if err != nil {
|
||
return nil, nil, err
|
||
}
|
||
return cardID, nil, nil
|
||
}
|
||
|
||
func (s *OrderDataSource) assetVisible(ctx context.Context, params ExportParams, table string, id uint) bool {
|
||
var count int64
|
||
query := s.db.WithContext(ctx).Table(table).Where("id = ? AND deleted_at IS NULL", id)
|
||
if params.UserType == constants.UserTypeAgent {
|
||
if len(params.ScopeShopIDs) == 0 {
|
||
return false
|
||
}
|
||
query = query.Where("shop_id IN ?", params.ScopeShopIDs)
|
||
}
|
||
return query.Count(&count).Error == nil && count > 0
|
||
}
|
||
|
||
func (s *OrderDataSource) resolveDeviceIDByIdentifier(ctx context.Context, params ExportParams, identifier string) (*uint, error) {
|
||
var row struct {
|
||
ID uint `gorm:"column:id"`
|
||
}
|
||
query := s.db.WithContext(ctx).
|
||
Table("tb_device").
|
||
Select("id").
|
||
Where("deleted_at IS NULL").
|
||
Where("virtual_no = ? OR imei = ? OR sn = ?", identifier, identifier, identifier)
|
||
if params.UserType == constants.UserTypeAgent {
|
||
if len(params.ScopeShopIDs) == 0 {
|
||
return nil, nil
|
||
}
|
||
query = query.Where("shop_id IN ?", params.ScopeShopIDs)
|
||
}
|
||
err := query.First(&row).Error
|
||
if err == gorm.ErrRecordNotFound {
|
||
return nil, nil
|
||
}
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return &row.ID, nil
|
||
}
|
||
|
||
func (s *OrderDataSource) resolveCardIDByIdentifier(ctx context.Context, params ExportParams, identifier string) (*uint, error) {
|
||
conditions := []string{"virtual_no = ?", "msisdn = ?", "iccid = ?"}
|
||
args := []any{identifier, identifier, identifier}
|
||
if len(identifier) == 19 {
|
||
conditions = append(conditions, "iccid_19 = ?")
|
||
args = append(args, identifier)
|
||
} else if len(identifier) == 20 {
|
||
conditions = append(conditions, "iccid_20 = ?")
|
||
args = append(args, identifier)
|
||
}
|
||
|
||
var row struct {
|
||
ID uint `gorm:"column:id"`
|
||
}
|
||
query := s.db.WithContext(ctx).
|
||
Table("tb_iot_card").
|
||
Select("id").
|
||
Where("deleted_at IS NULL").
|
||
Where(strings.Join(conditions, " OR "), args...)
|
||
if params.UserType == constants.UserTypeAgent {
|
||
if len(params.ScopeShopIDs) == 0 {
|
||
return nil, nil
|
||
}
|
||
query = query.Where("shop_id IN ?", params.ScopeShopIDs)
|
||
}
|
||
err := query.First(&row).Error
|
||
if err == gorm.ErrRecordNotFound {
|
||
return nil, nil
|
||
}
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return &row.ID, nil
|
||
}
|
||
|
||
type orderExportRow struct {
|
||
ID uint `gorm:"column:id"`
|
||
OrderNo string `gorm:"column:order_no"`
|
||
ShopName string `gorm:"column:shop_name"`
|
||
AssetIdentifier string `gorm:"column:asset_identifier"`
|
||
SellerCostPrice int64 `gorm:"column:seller_cost_price"`
|
||
TotalAmount int64 `gorm:"column:total_amount"`
|
||
ActualPaidAmount *int64 `gorm:"column:actual_paid_amount"`
|
||
PaymentStatus int `gorm:"column:payment_status"`
|
||
PaymentMethod string `gorm:"column:payment_method"`
|
||
CreatedAt time.Time `gorm:"column:created_at"`
|
||
DeviceID *uint `gorm:"column:device_id"`
|
||
// OperatorAccountName 订单创建时冻结的操作者名称,缺失时导出为空,不回查当前账号。
|
||
OperatorAccountName string `gorm:"column:operator_account_name"`
|
||
}
|
||
|
||
type orderExportItemRow struct {
|
||
OrderID uint `gorm:"column:order_id"`
|
||
PackageName string `gorm:"column:package_name"`
|
||
Quantity int `gorm:"column:quantity"`
|
||
}
|
||
|
||
type orderExportPaymentRow struct {
|
||
OrderID uint `gorm:"column:order_id"`
|
||
ThirdPartyTradeNo string `gorm:"column:third_party_trade_no"`
|
||
Status int `gorm:"column:status"`
|
||
}
|
||
|
||
type orderExportCardRow struct {
|
||
DeviceID uint `gorm:"column:device_id"`
|
||
SlotPosition int `gorm:"column:slot_position"`
|
||
MSISDN string `gorm:"column:msisdn"`
|
||
ICCID string `gorm:"column:iccid"`
|
||
}
|
||
|
||
type orderExportAssetIdentifierRow struct {
|
||
AssetType string `gorm:"column:asset_type"`
|
||
AssetID uint `gorm:"column:asset_id"`
|
||
}
|
||
|
||
func buildOrderExportHeaders(cardGroups int) []string {
|
||
if cardGroups < orderExportMinCardGroups {
|
||
cardGroups = orderExportMinCardGroups
|
||
}
|
||
|
||
headers := []string{
|
||
"订单号",
|
||
"店铺",
|
||
"虚拟号",
|
||
"套餐名称",
|
||
"成本价",
|
||
"下单金额",
|
||
"支付状态",
|
||
"支付方式",
|
||
"数量",
|
||
"下单时间",
|
||
"第三方单号",
|
||
}
|
||
for i := 1; i <= cardGroups; i++ {
|
||
headers = append(headers,
|
||
fmt.Sprintf("接入号%d", i),
|
||
fmt.Sprintf("ICCID%d", i),
|
||
)
|
||
}
|
||
return append(headers, orderExportOperatorHeader)
|
||
}
|
||
|
||
func buildOrderExportRow(item orderExportRow, items []orderExportItemRow, payment orderExportPaymentRow, cards []orderExportCardRow, cardGroups int) []string {
|
||
if cardGroups < orderExportMinCardGroups {
|
||
cardGroups = orderExportMinCardGroups
|
||
}
|
||
|
||
row := []string{
|
||
item.OrderNo,
|
||
item.ShopName,
|
||
item.AssetIdentifier,
|
||
joinOrderPackageNames(items),
|
||
formatMoneyYuan(item.SellerCostPrice),
|
||
formatMoneyYuan(orderActualPaidAmount(item)),
|
||
constants.GetOrderPaymentStatusName(item.PaymentStatus),
|
||
formatOrderPaymentMethod(item.PaymentMethod),
|
||
formatOrderQuantity(items),
|
||
item.CreatedAt.Format(exportTimeLayout),
|
||
payment.ThirdPartyTradeNo,
|
||
}
|
||
for i := 0; i < cardGroups; i++ {
|
||
if i >= len(cards) {
|
||
row = append(row, "", "")
|
||
continue
|
||
}
|
||
row = append(row, cards[i].MSISDN, cards[i].ICCID)
|
||
}
|
||
return row
|
||
}
|
||
|
||
func orderCardGroupCountFromHeaders(headers []string) int {
|
||
fixedCount := orderExportBaseHeaderCount
|
||
if orderHeadersIncludeOperator(headers) {
|
||
fixedCount++
|
||
}
|
||
if len(headers) < fixedCount {
|
||
return 0
|
||
}
|
||
cardColumnCount := len(headers) - fixedCount
|
||
if cardColumnCount <= 0 || cardColumnCount%orderExportCardGroupSize != 0 {
|
||
return 0
|
||
}
|
||
return cardColumnCount / orderExportCardGroupSize
|
||
}
|
||
|
||
// orderHeadersIncludeOperator 判断冻结表头是否为带“操作人”末列的新表头。
|
||
func orderHeadersIncludeOperator(headers []string) bool {
|
||
return len(headers) > 0 && headers[len(headers)-1] == orderExportOperatorHeader
|
||
}
|
||
|
||
func joinOrderPackageNames(items []orderExportItemRow) string {
|
||
if len(items) == 0 {
|
||
return ""
|
||
}
|
||
names := make([]string, 0, len(items))
|
||
for _, item := range items {
|
||
if item.PackageName != "" {
|
||
names = append(names, item.PackageName)
|
||
}
|
||
}
|
||
return strings.Join(names, ",")
|
||
}
|
||
|
||
func formatOrderQuantity(items []orderExportItemRow) string {
|
||
if len(items) == 0 {
|
||
return "0"
|
||
}
|
||
total := 0
|
||
for _, item := range items {
|
||
total += item.Quantity
|
||
}
|
||
return fmt.Sprintf("%d", total)
|
||
}
|
||
|
||
func orderActualPaidAmount(item orderExportRow) int64 {
|
||
if item.ActualPaidAmount != nil {
|
||
return *item.ActualPaidAmount
|
||
}
|
||
if item.PaymentStatus == model.PaymentStatusPaid {
|
||
return item.TotalAmount
|
||
}
|
||
return 0
|
||
}
|
||
|
||
func formatOrderPaymentMethod(method string) string {
|
||
switch method {
|
||
case model.PaymentMethodWallet:
|
||
return "钱包支付"
|
||
case model.PaymentMethodWechat:
|
||
return "微信支付"
|
||
case model.PaymentMethodAlipay:
|
||
return "支付宝支付"
|
||
case model.PaymentMethodOffline:
|
||
return "线下支付"
|
||
default:
|
||
return method
|
||
}
|
||
}
|