package refund import ( "context" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/internal/model/dto" "github.com/break/junhong_cmp_fiber/pkg/errors" ) // refundPackageUsage 是退款展示所需的当前套餐用量。 type refundPackageUsage struct { UsedMB int64 TotalMB int64 } // loadRefundPackageUsages 批量解析每个退款申请的「当前退款套餐」用量。 // // 定位口径与退款套餐失效保持一致,按优先级取唯一一条: // 1. 退款申请冻结的套餐使用记录(package_usage_id); // 2. 该退款关联订单下主套餐使用记录(master_usage_id 为空); // 3. 该退款关联订单下任一套餐使用记录。 // // 同一优先级内按使用记录标识升序取第一条,保证同一退款每次返回相同结果。 // 不按当前生效套餐或当前世代推断,也不跨订单取套餐:换货后权益仍保留原订单关系, // 按世代推断会指向错误的资产。 // // 用量的性质是「当前可取得」:不按套餐状态过滤。退款完成后套餐已转已失效, // 此时仍需展示其用量;若按状态过滤,退款完成后字段会集体归零,与用途矛盾。 // // 实现固定为两次查询(按标识、按订单),与退款条数无关;解析不到时返回 0 而非报错, // 使展示增强不阻断列表、详情或导出。 func (s *Service) loadRefundPackageUsages(ctx context.Context, refunds []*model.RefundRequest) (map[uint]refundPackageUsage, error) { result := make(map[uint]refundPackageUsage, len(refunds)) usageIDs := make([]uint, 0, len(refunds)) orderIDs := make([]uint, 0, len(refunds)) seenUsage := make(map[uint]struct{}, len(refunds)) seenOrder := make(map[uint]struct{}, len(refunds)) for _, refund := range refunds { if refund == nil || refund.ID == 0 { continue } result[refund.ID] = refundPackageUsage{} if refund.PackageUsageID != nil && *refund.PackageUsageID > 0 { if _, exists := seenUsage[*refund.PackageUsageID]; !exists { seenUsage[*refund.PackageUsageID] = struct{}{} usageIDs = append(usageIDs, *refund.PackageUsageID) } } if refund.OrderID > 0 { if _, exists := seenOrder[refund.OrderID]; !exists { seenOrder[refund.OrderID] = struct{}{} orderIDs = append(orderIDs, refund.OrderID) } } } if len(usageIDs) == 0 && len(orderIDs) == 0 { return result, nil } // 第一次查询:按套餐使用记录标识取冻结记录。 byUsageID := make(map[uint]*model.PackageUsage, len(usageIDs)) // 第二次查询:按订单取全部候选,再在内存中按优先级择一。 byOrderID := make(map[uint][]*model.PackageUsage, len(orderIDs)) if len(usageIDs) > 0 { var usages []model.PackageUsage if err := s.db.WithContext(ctx). Select("id", "order_id", "master_usage_id", "data_usage_mb", "data_limit_mb"). Where("id IN ?", usageIDs).Find(&usages).Error; err != nil { return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询退款套餐使用记录失败") } for index := range usages { byUsageID[usages[index].ID] = &usages[index] } } if len(orderIDs) > 0 { var usages []model.PackageUsage if err := s.db.WithContext(ctx). Select("id", "order_id", "master_usage_id", "data_usage_mb", "data_limit_mb"). Where("order_id IN ?", orderIDs). Order("id ASC").Find(&usages).Error; err != nil { return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询退款订单套餐使用记录失败") } for index := range usages { usage := &usages[index] byOrderID[usage.OrderID] = append(byOrderID[usage.OrderID], usage) } } for _, refund := range refunds { if refund == nil || refund.ID == 0 { continue } usage := resolveRefundPackageUsage(refund, byUsageID, byOrderID) if usage == nil { continue } result[refund.ID] = refundPackageUsage{UsedMB: usage.DataUsageMB, TotalMB: usage.DataLimitMB} } return result, nil } // resolveRefundPackageUsage 按优先级为单个退款申请择一当前套餐使用记录。 func resolveRefundPackageUsage( refund *model.RefundRequest, byUsageID map[uint]*model.PackageUsage, byOrderID map[uint][]*model.PackageUsage, ) *model.PackageUsage { // 优先级一:冻结的套餐使用记录,且必须属于该退款关联订单(防止跨订单取值)。 if refund.PackageUsageID != nil && *refund.PackageUsageID > 0 { if usage, exists := byUsageID[*refund.PackageUsageID]; exists && usage.OrderID == refund.OrderID { return usage } } // 优先级二:订单主套餐;优先级三:订单任一套餐。候选已按标识升序,取首个命中。 candidates := byOrderID[refund.OrderID] if len(candidates) == 0 { return nil } for _, usage := range candidates { if usage.MasterUsageID == nil { return usage } } return candidates[0] } // applyRefundPackageUsage 把解析结果写入响应字段。 func applyRefundPackageUsage(response *dto.RefundResponse, usage refundPackageUsage) { if response == nil { return } response.RefundPackageUsedMB = usage.UsedMB response.RefundPackageTotalMB = usage.TotalMB }