From 52bdbbae25dca03fff7f75a11451d476b1ae0e4b Mon Sep 17 00:00:00 2001 From: break Date: Wed, 1 Jul 2026 12:03:39 +0900 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=EF=BC=9A=E5=A5=97=E9=A4=90?= =?UTF-8?q?=E8=80=97=E5=B0=BD=E5=90=8E=E6=B5=81=E9=87=8F=E8=AF=A6=E5=8D=95?= =?UTF-8?q?=E6=96=AD=E6=A1=A3=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题:套餐 status=Depleted 后,queryActivePackages 查不到套餐, DeductDataUsage 直接 return CodeNoAvailablePackage,导致上游 仍有真实流量时,tb_package_usage_daily_record 不再写入,详单断档。 修复:无 Active 套餐时,找最近一条 Depleted 套餐,将溢出流量 累加到 data_usage_mb 并写入当日 daily_record,保持详单连续性。 status 不变,不重复触发停机。 --- internal/service/package/usage_service.go | 48 ++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/internal/service/package/usage_service.go b/internal/service/package/usage_service.go index a00d1db..323d7a2 100644 --- a/internal/service/package/usage_service.go +++ b/internal/service/package/usage_service.go @@ -88,13 +88,22 @@ func (s *UsageService) DeductDataUsage(ctx context.Context, carrierType string, return err } + today := time.Now().Format("2006-01-02") + if len(packages) == 0 { + // 无生效套餐:尝试找 Depleted 套餐记录溢出流量详单,避免详单断档 + if recordErr := s.recordOverflowToDepletedPackage(ctx, tx, targetCarrierType, targetCarrierID, deductUsageMB, today); recordErr != nil { + s.logger.Warn("记录溢出流量到已耗尽套餐失败", + zap.String("carrier_type", targetCarrierType), + zap.Uint("carrier_id", targetCarrierID), + zap.Int64("usage_mb", deductUsageMB), + zap.Error(recordErr)) + } return errors.New(errors.CodeNoAvailablePackage, "没有可用套餐") } // 按优先级扣减流量 remainingUsage := deductUsageMB - today := time.Now().Format("2006-01-02") for index, pkg := range packages { if remainingUsage <= 0 { @@ -446,3 +455,40 @@ func (s *UsageService) triggerSuspensionAfterCommit(carrierType string, carrierI }(cardID) } } + +// recordOverflowToDepletedPackage 在所有套餐已耗尽时,将溢出流量写入最近的 Depleted 套餐日记录。 +// 目的:保持流量详单连续性,即便套餐用完期间上游仍有流量使用,详单不会断档。 +// 只写日记录和累加 data_usage_mb,不改变套餐 status,不触发停机。 +func (s *UsageService) recordOverflowToDepletedPackage(ctx context.Context, tx *gorm.DB, carrierType string, carrierID uint, overflowMB int64, today string) error { + query := tx.Where("status = ?", constants.PackageUsageStatusDepleted) + switch carrierType { + case constants.AssetTypeIotCard: + query = query.Where("iot_card_id = ?", carrierID) + case constants.AssetTypeDevice: + query = query.Where("device_id = ?", carrierID) + default: + return nil + } + + var pkg model.PackageUsage + if err := query.Order("id DESC").First(&pkg).Error; err != nil { + // 找不到 Depleted 套餐,静默忽略 + return nil + } + + newUsage := pkg.DataUsageMB + overflowMB + if err := tx.Model(&pkg).Update("data_usage_mb", newUsage).Error; err != nil { + return errors.Wrap(errors.CodeDatabaseError, err, "更新溢出流量失败") + } + + if err := s.updateDailyRecord(ctx, tx, pkg.ID, today, overflowMB, newUsage); err != nil { + return err + } + + s.logger.Info("记录溢出流量到已耗尽套餐", + zap.Uint("package_usage_id", pkg.ID), + zap.Int64("overflow_mb", overflowMB), + zap.Int64("new_usage_mb", newUsage)) + + return nil +}