This commit is contained in:
@@ -56,12 +56,13 @@ func (h *AssetHandler) SetLifecycleService(svc AssetLifecycleService) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// resolveByIdentifier 通过标识符解析资产,用于各处理器的公共逻辑
|
// resolveByIdentifier 通过标识符解析资产,用于各处理器的公共逻辑
|
||||||
|
// 不传递 include_usage_summary,始终为 false,不影响其他 Handler 性能
|
||||||
func (h *AssetHandler) resolveByIdentifier(c *fiber.Ctx) (*dto.AssetResolveResponse, error) {
|
func (h *AssetHandler) resolveByIdentifier(c *fiber.Ctx) (*dto.AssetResolveResponse, error) {
|
||||||
identifier := c.Params("identifier")
|
identifier := c.Params("identifier")
|
||||||
if identifier == "" {
|
if identifier == "" {
|
||||||
return nil, errors.New(errors.CodeInvalidParam, "标识符不能为空")
|
return nil, errors.New(errors.CodeInvalidParam, "标识符不能为空")
|
||||||
}
|
}
|
||||||
return h.assetService.Resolve(c.UserContext(), identifier)
|
return h.assetService.Resolve(c.UserContext(), identifier, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve 通过任意标识符解析资产(设备或卡)
|
// Resolve 通过任意标识符解析资产(设备或卡)
|
||||||
@@ -72,7 +73,8 @@ func (h *AssetHandler) Resolve(c *fiber.Ctx) error {
|
|||||||
return errors.New(errors.CodeInvalidParam, "标识符不能为空")
|
return errors.New(errors.CodeInvalidParam, "标识符不能为空")
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := h.assetService.Resolve(c.UserContext(), identifier)
|
includeUsageSummary := c.QueryBool("include_usage_summary", false)
|
||||||
|
result, err := h.assetService.Resolve(c.UserContext(), identifier, includeUsageSummary)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -378,7 +380,7 @@ func (h *AssetHandler) UpdatePollingStatus(c *fiber.Ctx) error {
|
|||||||
return errors.New(errors.CodeInvalidParam)
|
return errors.New(errors.CodeInvalidParam)
|
||||||
}
|
}
|
||||||
|
|
||||||
asset, err := h.assetService.Resolve(c.UserContext(), identifier)
|
asset, err := h.assetService.Resolve(c.UserContext(), identifier, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -424,7 +426,7 @@ func (h *AssetHandler) UpdateRealnamePolicy(c *fiber.Ctx) error {
|
|||||||
return errors.New(errors.CodeInvalidParam, "无效的实名认证策略")
|
return errors.New(errors.CodeInvalidParam, "无效的实名认证策略")
|
||||||
}
|
}
|
||||||
|
|
||||||
asset, err := h.assetService.Resolve(c.UserContext(), identifier)
|
asset, err := h.assetService.Resolve(c.UserContext(), identifier, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -467,7 +469,7 @@ func (h *AssetHandler) UpdateRealnameStatus(c *fiber.Ctx) error {
|
|||||||
return errors.New(errors.CodeInvalidParam, "无效的实名状态")
|
return errors.New(errors.CodeInvalidParam, "无效的实名状态")
|
||||||
}
|
}
|
||||||
|
|
||||||
asset, err := h.assetService.Resolve(c.UserContext(), identifier)
|
asset, err := h.assetService.Resolve(c.UserContext(), identifier, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ func (h *AssetWalletHandler) GetWallet(c *fiber.Ctx) error {
|
|||||||
return errors.New(errors.CodeInternalError, "资产服务未配置")
|
return errors.New(errors.CodeInternalError, "资产服务未配置")
|
||||||
}
|
}
|
||||||
|
|
||||||
asset, err := h.assetService.Resolve(c.UserContext(), identifier)
|
asset, err := h.assetService.Resolve(c.UserContext(), identifier, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -76,7 +76,7 @@ func (h *AssetWalletHandler) ListTransactions(c *fiber.Ctx) error {
|
|||||||
return errors.New(errors.CodeInternalError, "资产服务未配置")
|
return errors.New(errors.CodeInternalError, "资产服务未配置")
|
||||||
}
|
}
|
||||||
|
|
||||||
asset, err := h.assetService.Resolve(c.UserContext(), identifier)
|
asset, err := h.assetService.Resolve(c.UserContext(), identifier, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ func (h *ClientAssetHandler) resolveAssetFromIdentifier(c *fiber.Ctx, identifier
|
|||||||
}
|
}
|
||||||
|
|
||||||
skipPermissionCtx := context.WithValue(c.UserContext(), constants.ContextKeySubordinateShopIDs, []uint{})
|
skipPermissionCtx := context.WithValue(c.UserContext(), constants.ContextKeySubordinateShopIDs, []uint{})
|
||||||
assetInfo, err := h.assetService.Resolve(skipPermissionCtx, identifier)
|
assetInfo, err := h.assetService.Resolve(skipPermissionCtx, identifier, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ func (h *ClientDeviceHandler) validateDeviceAsset(c *fiber.Ctx, identifier strin
|
|||||||
ctx := c.UserContext()
|
ctx := c.UserContext()
|
||||||
|
|
||||||
// 通过标识符解析资产
|
// 通过标识符解析资产
|
||||||
asset, err := h.assetService.Resolve(ctx, identifier)
|
asset, err := h.assetService.Resolve(ctx, identifier, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ func (h *ClientRealnameHandler) GetRealnameLink(c *fiber.Ctx) error {
|
|||||||
ctx := c.UserContext()
|
ctx := c.UserContext()
|
||||||
|
|
||||||
// 3. 通过标识符解析资产
|
// 3. 通过标识符解析资产
|
||||||
asset, err := h.assetService.Resolve(ctx, req.Identifier)
|
asset, err := h.assetService.Resolve(ctx, req.Identifier, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -540,7 +540,7 @@ func (h *ClientWalletHandler) resolveAssetFromIdentifier(c *fiber.Ctx, identifie
|
|||||||
}
|
}
|
||||||
|
|
||||||
skipPermissionCtx := context.WithValue(c.UserContext(), constants.ContextKeySubordinateShopIDs, []uint{})
|
skipPermissionCtx := context.WithValue(c.UserContext(), constants.ContextKeySubordinateShopIDs, []uint{})
|
||||||
assetInfo, err := h.assetService.Resolve(skipPermissionCtx, identifier)
|
assetInfo, err := h.assetService.Resolve(skipPermissionCtx, identifier, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,6 +68,9 @@ type AssetResolveResponse struct {
|
|||||||
NetworkStatus int `json:"network_status" description:"网络状态:0停机 1开机(asset_type=card时有效)"`
|
NetworkStatus int `json:"network_status" description:"网络状态:0停机 1开机(asset_type=card时有效)"`
|
||||||
GatewayExtend string `json:"gateway_extend" description:"Gateway 卡状态扩展字段,原样返回上游 extend,用于展示运营商侧实际停机原因"`
|
GatewayExtend string `json:"gateway_extend" description:"Gateway 卡状态扩展字段,原样返回上游 extend,用于展示运营商侧实际停机原因"`
|
||||||
GatewayCardIMEI string `json:"gateway_card_imei" description:"插拔卡业务 IMEI(由 Gateway 卡状态接口同步,非设备自身 IMEI,无业务含义,仅供查看)"`
|
GatewayCardIMEI string `json:"gateway_card_imei" description:"插拔卡业务 IMEI(由 Gateway 卡状态接口同步,非设备自身 IMEI,无业务含义,仅供查看)"`
|
||||||
|
// 流量汇总字段(仅在 ?include_usage_summary=true 时返回非 null 值)
|
||||||
|
TotalVirtualUsedMB *float64 `json:"total_virtual_used_mb" description:"当前世代所有套餐虚已用之和(MB),未请求时为 null"`
|
||||||
|
TotalVirtualRemainingMB *float64 `json:"total_virtual_remaining_mb" description:"当前世代所有套餐虚剩余之和(MB),未请求时为 null"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// BoundCardInfo 设备绑定的卡信息
|
// BoundCardInfo 设备绑定的卡信息
|
||||||
|
|||||||
@@ -484,7 +484,7 @@ func (s *Service) resolveOpenAPICard(ctx context.Context, cardNo string) (*model
|
|||||||
return nil, errors.New(errors.CodeInternalError, "开放接口资产解析依赖未配置")
|
return nil, errors.New(errors.CodeInternalError, "开放接口资产解析依赖未配置")
|
||||||
}
|
}
|
||||||
|
|
||||||
resolved, err := s.assetService.Resolve(ctx, identifier)
|
resolved, err := s.assetService.Resolve(ctx, identifier, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if appErr, ok := err.(*errors.AppError); ok && appErr.Code == errors.CodeNotFound {
|
if appErr, ok := err.(*errors.AppError); ok && appErr.Code == errors.CodeNotFound {
|
||||||
return s.resolveOpenAPICardByIdentifier(ctx, identifier)
|
return s.resolveOpenAPICardByIdentifier(ctx, identifier)
|
||||||
|
|||||||
@@ -91,7 +91,8 @@ func New(
|
|||||||
// Resolve 通过任意标识符解析资产
|
// Resolve 通过任意标识符解析资产
|
||||||
// 主路径:查注册表(精确匹配 ICCID 或 VirtualNo)
|
// 主路径:查注册表(精确匹配 ICCID 或 VirtualNo)
|
||||||
// Fallback:原有跨表 OR 查询(处理 IMEI/SN/MSISDN 等非注册标识符)
|
// Fallback:原有跨表 OR 查询(处理 IMEI/SN/MSISDN 等非注册标识符)
|
||||||
func (s *Service) Resolve(ctx context.Context, identifier string) (*dto.AssetResolveResponse, error) {
|
// includeUsageSummary 为 true 时额外填充当前世代流量汇总字段
|
||||||
|
func (s *Service) Resolve(ctx context.Context, identifier string, includeUsageSummary bool) (*dto.AssetResolveResponse, error) {
|
||||||
if s.assetIdentifierStore != nil {
|
if s.assetIdentifierStore != nil {
|
||||||
regRecord, regErr := s.assetIdentifierStore.FindByIdentifier(ctx, identifier)
|
regRecord, regErr := s.assetIdentifierStore.FindByIdentifier(ctx, identifier)
|
||||||
if regErr == nil && regRecord != nil {
|
if regErr == nil && regRecord != nil {
|
||||||
@@ -102,6 +103,9 @@ func (s *Service) Resolve(ctx context.Context, identifier string) (*dto.AssetRes
|
|||||||
resp, buildErr := s.buildDeviceResolveResponse(ctx, device)
|
resp, buildErr := s.buildDeviceResolveResponse(ctx, device)
|
||||||
if buildErr == nil {
|
if buildErr == nil {
|
||||||
resp.Identifier = identifier
|
resp.Identifier = identifier
|
||||||
|
if includeUsageSummary {
|
||||||
|
s.fillUsageSummary(ctx, resp, "device", device.ID)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return resp, buildErr
|
return resp, buildErr
|
||||||
}
|
}
|
||||||
@@ -111,6 +115,9 @@ func (s *Service) Resolve(ctx context.Context, identifier string) (*dto.AssetRes
|
|||||||
resp, buildErr := s.buildCardResolveResponse(ctx, card)
|
resp, buildErr := s.buildCardResolveResponse(ctx, card)
|
||||||
if buildErr == nil {
|
if buildErr == nil {
|
||||||
resp.Identifier = identifier
|
resp.Identifier = identifier
|
||||||
|
if includeUsageSummary {
|
||||||
|
s.fillUsageSummary(ctx, resp, "iot_card", card.ID)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return resp, buildErr
|
return resp, buildErr
|
||||||
}
|
}
|
||||||
@@ -123,6 +130,9 @@ func (s *Service) Resolve(ctx context.Context, identifier string) (*dto.AssetRes
|
|||||||
resp, buildErr := s.buildDeviceResolveResponse(ctx, device)
|
resp, buildErr := s.buildDeviceResolveResponse(ctx, device)
|
||||||
if buildErr == nil {
|
if buildErr == nil {
|
||||||
resp.Identifier = identifier
|
resp.Identifier = identifier
|
||||||
|
if includeUsageSummary {
|
||||||
|
s.fillUsageSummary(ctx, resp, "device", device.ID)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return resp, buildErr
|
return resp, buildErr
|
||||||
}
|
}
|
||||||
@@ -135,6 +145,9 @@ func (s *Service) Resolve(ctx context.Context, identifier string) (*dto.AssetRes
|
|||||||
resp, buildErr := s.buildCardResolveResponse(ctx, &card)
|
resp, buildErr := s.buildCardResolveResponse(ctx, &card)
|
||||||
if buildErr == nil {
|
if buildErr == nil {
|
||||||
resp.Identifier = identifier
|
resp.Identifier = identifier
|
||||||
|
if includeUsageSummary {
|
||||||
|
s.fillUsageSummary(ctx, resp, "iot_card", card.ID)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return resp, buildErr
|
return resp, buildErr
|
||||||
}
|
}
|
||||||
@@ -336,6 +349,19 @@ func (s *Service) fillPackageInfo(ctx context.Context, resp *dto.AssetResolveRes
|
|||||||
resp.EnableVirtualData = usage.EnableVirtualDataSnapshot
|
resp.EnableVirtualData = usage.EnableVirtualDataSnapshot
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fillUsageSummary 填充当前世代流量汇总字段。
|
||||||
|
// 仅在 includeUsageSummary=true 时被调用,无套餐时返回 0.0(非 null)。
|
||||||
|
func (s *Service) fillUsageSummary(ctx context.Context, resp *dto.AssetResolveResponse, carrierType string, carrierID uint) {
|
||||||
|
usages, err := s.packageUsageStore.GetCurrentGenerationUsagesForSummary(ctx, carrierType, carrierID)
|
||||||
|
if err != nil {
|
||||||
|
logger.GetAppLogger().Error("查询流量汇总失败", zap.String("carrierType", carrierType), zap.Uint("carrierID", carrierID), zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
totalUsed, totalRemaining := computeUsageSummary(usages)
|
||||||
|
resp.TotalVirtualUsedMB = &totalUsed
|
||||||
|
resp.TotalVirtualRemainingMB = &totalRemaining
|
||||||
|
}
|
||||||
|
|
||||||
// fillShopName 填充店铺名称
|
// fillShopName 填充店铺名称
|
||||||
func (s *Service) fillShopName(ctx context.Context, resp *dto.AssetResolveResponse) {
|
func (s *Service) fillShopName(ctx context.Context, resp *dto.AssetResolveResponse) {
|
||||||
if resp.ShopID == nil || *resp.ShopID == 0 {
|
if resp.ShopID == nil || *resp.ShopID == 0 {
|
||||||
@@ -971,7 +997,7 @@ func (s *Service) GetOrders(ctx context.Context, identifier string, page, pageSi
|
|||||||
pageSize = 100
|
pageSize = 100
|
||||||
}
|
}
|
||||||
|
|
||||||
asset, err := s.Resolve(ctx, identifier)
|
asset, err := s.Resolve(ctx, identifier, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
16
internal/service/asset/usage_summary.go
Normal file
16
internal/service/asset/usage_summary.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package asset
|
||||||
|
|
||||||
|
import "github.com/break/junhong_cmp_fiber/internal/model"
|
||||||
|
|
||||||
|
// computeUsageSummary 汇总当前世代套餐的虚已用量和虚剩余量。
|
||||||
|
// 对每条记录调用 BuildTrafficMetrics() 获取虚流量字段,累加后计算剩余。
|
||||||
|
func computeUsageSummary(usages []*model.PackageUsage) (totalUsedMB float64, totalRemainingMB float64) {
|
||||||
|
var totalVirtualTotal float64
|
||||||
|
for _, u := range usages {
|
||||||
|
m := u.BuildTrafficMetrics()
|
||||||
|
totalUsedMB += m.VirtualUsedMB
|
||||||
|
totalVirtualTotal += float64(m.VirtualTotalMB)
|
||||||
|
}
|
||||||
|
totalRemainingMB = totalVirtualTotal - totalUsedMB
|
||||||
|
return
|
||||||
|
}
|
||||||
81
internal/service/asset/usage_summary_test.go
Normal file
81
internal/service/asset/usage_summary_test.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package asset
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestComputeUsageSummary_EmptyList(t *testing.T) {
|
||||||
|
totalUsed, totalRemaining := computeUsageSummary(nil)
|
||||||
|
if totalUsed != 0.0 {
|
||||||
|
t.Errorf("期望 totalUsed=0.0,实际=%v", totalUsed)
|
||||||
|
}
|
||||||
|
if totalRemaining != 0.0 {
|
||||||
|
t.Errorf("期望 totalRemaining=0.0,实际=%v", totalRemaining)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComputeUsageSummary_SingleVirtualPackage(t *testing.T) {
|
||||||
|
// 启用虚流量:真实总量=100MB,虚拟总量=200MB,倍率=0.5
|
||||||
|
// 真实已用=40MB → 虚拟已用=min(40*0.5, 100)=20MB
|
||||||
|
// 虚拟剩余=200-20=180MB
|
||||||
|
usage := &model.PackageUsage{
|
||||||
|
DataLimitMB: 100,
|
||||||
|
DataUsageMB: 40,
|
||||||
|
VirtualTotalMBSnapshot: 200,
|
||||||
|
DisplayGainRatioSnapshot: 0.5,
|
||||||
|
EnableVirtualDataSnapshot: true,
|
||||||
|
}
|
||||||
|
totalUsed, totalRemaining := computeUsageSummary([]*model.PackageUsage{usage})
|
||||||
|
if totalUsed != 20.0 {
|
||||||
|
t.Errorf("期望 totalUsed=20.0,实际=%v", totalUsed)
|
||||||
|
}
|
||||||
|
if totalRemaining != 180.0 {
|
||||||
|
t.Errorf("期望 totalRemaining=180.0,实际=%v", totalRemaining)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComputeUsageSummary_SingleNonVirtualPackage(t *testing.T) {
|
||||||
|
// 未启用虚流量:退化为真实值
|
||||||
|
// 真实总量=100MB,真实已用=40MB → 虚拟已用=40MB,虚拟剩余=60MB
|
||||||
|
usage := &model.PackageUsage{
|
||||||
|
DataLimitMB: 100,
|
||||||
|
DataUsageMB: 40,
|
||||||
|
EnableVirtualDataSnapshot: false,
|
||||||
|
}
|
||||||
|
totalUsed, totalRemaining := computeUsageSummary([]*model.PackageUsage{usage})
|
||||||
|
if totalUsed != 40.0 {
|
||||||
|
t.Errorf("期望 totalUsed=40.0,实际=%v", totalUsed)
|
||||||
|
}
|
||||||
|
if totalRemaining != 60.0 {
|
||||||
|
t.Errorf("期望 totalRemaining=60.0,实际=%v", totalRemaining)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComputeUsageSummary_MultiplePackages(t *testing.T) {
|
||||||
|
// 套餐1(启用虚流量):VirtualUsed=20, VirtualTotal=200
|
||||||
|
// 套餐2(未启用虚流量):VirtualUsed=10, VirtualTotal=50
|
||||||
|
// 汇总:totalUsed=30, totalRemaining=(200+50)-30=220
|
||||||
|
usages := []*model.PackageUsage{
|
||||||
|
{
|
||||||
|
DataLimitMB: 100,
|
||||||
|
DataUsageMB: 40,
|
||||||
|
VirtualTotalMBSnapshot: 200,
|
||||||
|
DisplayGainRatioSnapshot: 0.5,
|
||||||
|
EnableVirtualDataSnapshot: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
DataLimitMB: 50,
|
||||||
|
DataUsageMB: 10,
|
||||||
|
EnableVirtualDataSnapshot: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
totalUsed, totalRemaining := computeUsageSummary(usages)
|
||||||
|
if totalUsed != 30.0 {
|
||||||
|
t.Errorf("期望 totalUsed=30.0,实际=%v", totalUsed)
|
||||||
|
}
|
||||||
|
if totalRemaining != 220.0 {
|
||||||
|
t.Errorf("期望 totalRemaining=220.0,实际=%v", totalRemaining)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -128,7 +128,7 @@ func (s *Service) CreateOrder(ctx context.Context, customerID uint, req *dto.Cli
|
|||||||
}
|
}
|
||||||
|
|
||||||
skipCtx := context.WithValue(ctx, constants.ContextKeySubordinateShopIDs, []uint{})
|
skipCtx := context.WithValue(ctx, constants.ContextKeySubordinateShopIDs, []uint{})
|
||||||
assetInfo, err := s.assetService.Resolve(skipCtx, strings.TrimSpace(req.Identifier))
|
assetInfo, err := s.assetService.Resolve(skipCtx, strings.TrimSpace(req.Identifier), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -1060,7 +1060,7 @@ func (s *Service) createAlipayForceRechargeOrder(
|
|||||||
func (s *Service) ListOrders(ctx context.Context, customerID uint, req *dto.ClientOrderListRequest) ([]dto.ClientOrderListItem, int64, error) {
|
func (s *Service) ListOrders(ctx context.Context, customerID uint, req *dto.ClientOrderListRequest) ([]dto.ClientOrderListItem, int64, error) {
|
||||||
skipCtx := context.WithValue(ctx, constants.ContextKeySubordinateShopIDs, []uint{})
|
skipCtx := context.WithValue(ctx, constants.ContextKeySubordinateShopIDs, []uint{})
|
||||||
|
|
||||||
assetInfo, err := s.assetService.Resolve(skipCtx, strings.TrimSpace(req.Identifier))
|
assetInfo, err := s.assetService.Resolve(skipCtx, strings.TrimSpace(req.Identifier), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
@@ -1231,7 +1231,7 @@ func (s *Service) PayOrder(ctx context.Context, customerID uint, orderID uint, r
|
|||||||
if err == nil && device != nil {
|
if err == nil && device != nil {
|
||||||
assetRealnamePolicy = device.RealnamePolicy
|
assetRealnamePolicy = device.RealnamePolicy
|
||||||
// 设备的 RealNameStatus 需要通过 Resolve 获取(从当前卡派生)
|
// 设备的 RealNameStatus 需要通过 Resolve 获取(从当前卡派生)
|
||||||
resolved, resolveErr := s.assetService.Resolve(skipCtx, device.VirtualNo)
|
resolved, resolveErr := s.assetService.Resolve(skipCtx, device.VirtualNo, false)
|
||||||
if resolveErr == nil && resolved != nil {
|
if resolveErr == nil && resolved != nil {
|
||||||
assetRealnameStatus = resolved.RealNameStatus
|
assetRealnameStatus = resolved.RealNameStatus
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -301,6 +301,40 @@ func (s *PackageUsageStore) ListByCarrier(ctx context.Context, carrierType strin
|
|||||||
return usages, nil
|
return usages, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCurrentGenerationUsagesForSummary 查询当前世代内所有已激活过的套餐记录(含加油包),用于流量汇总。
|
||||||
|
// 当前世代定义:status∈{1,2,3,4} 中 generation 最大值所在的那一批记录。
|
||||||
|
// 返回计算所需字段:DataUsageMB、DataLimitMB、VirtualTotalMBSnapshot、DisplayGainRatioSnapshot、EnableVirtualDataSnapshot。
|
||||||
|
func (s *PackageUsageStore) GetCurrentGenerationUsagesForSummary(ctx context.Context, carrierType string, carrierID uint) ([]*model.PackageUsage, error) {
|
||||||
|
var usages []*model.PackageUsage
|
||||||
|
|
||||||
|
// 子查询取当前世代编号
|
||||||
|
subQuery := s.db.WithContext(ctx).Model(&model.PackageUsage{}).
|
||||||
|
Select("MAX(generation)").
|
||||||
|
Where("status IN ?", []int{
|
||||||
|
constants.PackageUsageStatusActive,
|
||||||
|
constants.PackageUsageStatusDepleted,
|
||||||
|
constants.PackageUsageStatusExpired,
|
||||||
|
constants.PackageUsageStatusInvalidated,
|
||||||
|
})
|
||||||
|
subQuery = applyPackageUsageCarrierFilter(subQuery, carrierType, carrierID)
|
||||||
|
|
||||||
|
query := s.db.WithContext(ctx).Model(&model.PackageUsage{}).
|
||||||
|
Select("data_usage_mb, data_limit_mb, virtual_total_mb_snapshot, display_gain_ratio_snapshot, enable_virtual_data_snapshot").
|
||||||
|
Where("status IN ?", []int{
|
||||||
|
constants.PackageUsageStatusActive,
|
||||||
|
constants.PackageUsageStatusDepleted,
|
||||||
|
constants.PackageUsageStatusExpired,
|
||||||
|
constants.PackageUsageStatusInvalidated,
|
||||||
|
}).
|
||||||
|
Where("generation = (?)", subQuery)
|
||||||
|
query = applyPackageUsageCarrierFilter(query, carrierType, carrierID)
|
||||||
|
|
||||||
|
if err := query.Find(&usages).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return usages, nil
|
||||||
|
}
|
||||||
|
|
||||||
func applyPackageUsageCarrierFilter(query *gorm.DB, carrierType string, carrierID uint) *gorm.DB {
|
func applyPackageUsageCarrierFilter(query *gorm.DB, carrierType string, carrierID uint) *gorm.DB {
|
||||||
if carrierType == "iot_card" {
|
if carrierType == "iot_card" {
|
||||||
return query.Where("iot_card_id = ?", carrierID)
|
return query.Where("iot_card_id = ?", carrierID)
|
||||||
|
|||||||
Reference in New Issue
Block a user