package exporter import ( "context" "strconv" "time" "gorm.io/gorm" "github.com/break/junhong_cmp_fiber/pkg/constants" ) // PackageDataSource 套餐导出数据源。 type PackageDataSource struct { db *gorm.DB } // NewPackageDataSource 创建套餐导出数据源。 func NewPackageDataSource(db *gorm.DB) *PackageDataSource { return &PackageDataSource{db: db} } // Scene 返回导出场景编码。 func (s *PackageDataSource) Scene() string { return constants.ExportTaskScenePackage } // Count 统计套餐导出行数。 func (s *PackageDataSource) Count(ctx context.Context, params ExportParams) (int, error) { var total int64 query := s.applyFilters(s.baseQuery(ctx, params), params) if err := query.Count(&total).Error; err != nil { return 0, err } return int(total), nil } // Headers 返回套餐导出固定表头。 func (s *PackageDataSource) Headers(context.Context, ExportParams) ([]string, error) { return []string{ "套餐编码", "套餐名称", "套餐系列名称", "套餐类型", "套餐时长(月)", "套餐时长说明", "套餐周期类型", "套餐天数", "真流量额度(MB)", "虚流量额度(MB)", "是否启用虚流量", "虚流量比例", "流量重置周期", "到期时间基准", "成本价(元)", "建议售价(元)", "价格配置状态", "状态", "上架状态", "是否赠送套餐", "创建人ID", "更新人ID", "创建时间", "更新时间", "删除时间", }, nil } // Fetch 按 offset/limit 查询套餐导出数据。 func (s *PackageDataSource) Fetch(ctx context.Context, params ExportParams, offset, limit int) ([][]string, error) { if limit <= 0 { return [][]string{}, nil } var items []packageExportRow query := s.applyFilters(s.baseQuery(ctx, params), params). Joins("LEFT JOIN tb_package_series AS ps ON ps.id = p.series_id AND ps.deleted_at IS NULL"). Select(s.selectColumns(params)). Order("p.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, buildPackageExportRow(item)) } return rows, nil } func (s *PackageDataSource) baseQuery(ctx context.Context, params ExportParams) *gorm.DB { query := s.db.WithContext(ctx).Table("tb_package AS p").Where("p.deleted_at IS NULL") switch params.UserType { case constants.UserTypeSuperAdmin, constants.UserTypePlatform: return query case constants.UserTypeAgent: if params.CreatorShopID == nil || *params.CreatorShopID == 0 { return query.Where("1 = 0") } return query. Joins(`INNER JOIN tb_shop_package_allocation AS a ON a.package_id = p.id AND a.shop_id = ? AND a.status = ? AND a.deleted_at IS NULL`, *params.CreatorShopID, constants.StatusEnabled). Where("p.is_gift = ?", false) default: return query.Where("1 = 0") } } func (s *PackageDataSource) applyFilters(query *gorm.DB, params ExportParams) *gorm.DB { if packageName, ok := filterString(params.Filters, "package_name"); ok { query = query.Where("p.package_name LIKE ?", "%"+packageName+"%") } if seriesID, ok := filterUint(params.Filters, "series_id"); ok { query = query.Where("p.series_id = ?", seriesID) } if status, ok := filterInt(params.Filters, "status"); ok { query = query.Where("p.status = ?", status) } if shelfStatus, ok := filterInt(params.Filters, "shelf_status"); ok { if params.UserType == constants.UserTypeAgent { query = query.Where("a.shelf_status = ?", shelfStatus) } else { query = query.Where("p.shelf_status = ?", shelfStatus) } } if packageType, ok := filterString(params.Filters, "package_type"); ok { query = query.Where("p.package_type = ?", packageType) } return query } func (s *PackageDataSource) selectColumns(params ExportParams) string { costPriceColumn := "p.cost_price" shelfStatusColumn := "p.shelf_status" if params.UserType == constants.UserTypeAgent { costPriceColumn = "a.cost_price" shelfStatusColumn = "a.shelf_status" } return ` p.package_code, p.package_name, COALESCE(ps.series_name, '') AS series_name, p.package_type, p.duration_months, p.calendar_type, p.duration_days, p.real_data_mb, p.virtual_data_mb, p.enable_virtual_data, p.virtual_ratio, p.data_reset_cycle, p.expiry_base, ` + costPriceColumn + ` AS cost_price, p.suggested_retail_price, p.price_config_status, p.status, ` + shelfStatusColumn + ` AS shelf_status, p.is_gift, p.creator, p.updater, p.created_at, p.updated_at` } type packageExportRow struct { PackageCode string `gorm:"column:package_code"` PackageName string `gorm:"column:package_name"` SeriesName string `gorm:"column:series_name"` PackageType string `gorm:"column:package_type"` DurationMonths int `gorm:"column:duration_months"` CalendarType string `gorm:"column:calendar_type"` DurationDays int `gorm:"column:duration_days"` RealDataMB int64 `gorm:"column:real_data_mb"` VirtualDataMB int64 `gorm:"column:virtual_data_mb"` EnableVirtualData bool `gorm:"column:enable_virtual_data"` VirtualRatio float64 `gorm:"column:virtual_ratio"` DataResetCycle string `gorm:"column:data_reset_cycle"` ExpiryBase string `gorm:"column:expiry_base"` CostPrice int64 `gorm:"column:cost_price"` SuggestedRetailPrice int64 `gorm:"column:suggested_retail_price"` PriceConfigStatus int `gorm:"column:price_config_status"` Status int `gorm:"column:status"` ShelfStatus int `gorm:"column:shelf_status"` IsGift bool `gorm:"column:is_gift"` Creator uint `gorm:"column:creator"` Updater uint `gorm:"column:updater"` CreatedAt time.Time `gorm:"column:created_at"` UpdatedAt time.Time `gorm:"column:updated_at"` } func buildPackageExportRow(item packageExportRow) []string { return []string{ item.PackageCode, item.PackageName, item.SeriesName, constants.GetPackageTypeName(item.PackageType), strconv.Itoa(item.DurationMonths), formatPackageDurationDescription(item), constants.GetPackageCalendarTypeName(item.CalendarType), strconv.Itoa(item.DurationDays), strconv.FormatInt(item.RealDataMB, 10), strconv.FormatInt(item.VirtualDataMB, 10), formatYesNo(item.EnableVirtualData), strconv.FormatFloat(item.VirtualRatio, 'f', 6, 64), constants.GetPackageDataResetCycleName(item.DataResetCycle), constants.GetPackageExpiryBaseName(item.ExpiryBase), formatMoneyYuan(item.CostPrice), formatPackageSuggestedRetailPrice(item), constants.GetPackagePriceConfigStatusName(item.PriceConfigStatus), constants.GetStatusName(item.Status), constants.GetShelfStatusName(item.ShelfStatus), formatYesNo(item.IsGift), strconv.FormatUint(uint64(item.Creator), 10), strconv.FormatUint(uint64(item.Updater), 10), item.CreatedAt.Format(exportTimeLayout), item.UpdatedAt.Format(exportTimeLayout), "", } } func formatPackageDurationDescription(item packageExportRow) string { if item.CalendarType == constants.PackageCalendarTypeByDay && item.DurationDays > 0 { return strconv.Itoa(item.DurationDays) + "天" } if item.DurationMonths > 0 { return strconv.Itoa(item.DurationMonths) + "个月" } return "" } func formatPackageSuggestedRetailPrice(item packageExportRow) string { if item.PriceConfigStatus == constants.PackagePriceConfigStatusUnconfigured { return "" } return formatMoneyYuan(item.SuggestedRetailPrice) } func formatYesNo(value bool) string { if value { return "是" } return "否" }