diff --git a/internal/bootstrap/services.go b/internal/bootstrap/services.go index 4b78614..af624a1 100644 --- a/internal/bootstrap/services.go +++ b/internal/bootstrap/services.go @@ -177,7 +177,7 @@ func initServices(s *stores, deps *Dependencies) *services { PollingAlert: pollingSvc.NewAlertService(s.PollingAlertRule, s.PollingAlertHistory, deps.Redis, deps.Logger), PollingCleanup: pollingSvc.NewCleanupService(s.DataCleanupConfig, s.DataCleanupLog, deps.Logger), PollingManualTrigger: pollingSvc.NewManualTriggerService(s.PollingManualTriggerLog, s.IotCard, deps.Redis, deps.Logger), - Asset: assetSvc.New(deps.DB, s.Device, s.IotCard, s.PackageUsage, s.Package, s.PackageSeries, s.DeviceSimBinding, s.Shop, deps.Redis, iotCard), + Asset: assetSvc.New(deps.DB, s.Device, s.IotCard, s.PackageUsage, s.Package, s.PackageSeries, s.DeviceSimBinding, s.Shop, deps.Redis, iotCard, deps.GatewayClient), AssetLifecycle: assetSvc.NewLifecycleService(deps.DB, s.IotCard, s.Device), AssetWallet: assetWalletSvc.New(s.AssetWallet, s.AssetWalletTransaction), StopResumeService: iotCardSvc.NewStopResumeService(deps.DB, deps.Redis, s.IotCard, s.DeviceSimBinding, deps.GatewayClient, deps.Logger), diff --git a/internal/model/financial.go b/internal/model/financial.go index e387b36..e1da628 100644 --- a/internal/model/financial.go +++ b/internal/model/financial.go @@ -24,8 +24,6 @@ type CommissionWithdrawalRequest struct { PaymentType string `gorm:"column:payment_type;type:varchar(20);default:'manual';comment:放款类型(manual=人工打款)" json:"payment_type"` AccountInfo datatypes.JSON `gorm:"column:account_info;type:jsonb;comment:收款账户信息(姓名、账号等)" json:"account_info"` Status int `gorm:"column:status;type:int;default:1;comment:状态 1-待审核 2-已通过 3-已拒绝 4-已到账" json:"status"` - ApprovedBy uint `gorm:"column:approved_by;index;comment:审批人用户ID" json:"approved_by"` - ApprovedAt *time.Time `gorm:"column:approved_at;comment:审批时间" json:"approved_at"` ProcessorID uint `gorm:"column:processor_id;index;comment:处理人ID" json:"processor_id"` ProcessedAt *time.Time `gorm:"column:processed_at;comment:处理时间" json:"processed_at"` PaidAt *time.Time `gorm:"column:paid_at;comment:到账时间" json:"paid_at"` diff --git a/internal/routes/asset.go b/internal/routes/asset.go index e30979c..065f539 100644 --- a/internal/routes/asset.go +++ b/internal/routes/asset.go @@ -41,10 +41,10 @@ func registerAssetRoutes(router fiber.Router, handler *admin.AssetHandler, walle Register(assets, doc, groupPath, "GET", "/:asset_type/:id/packages", handler.Packages, RouteSpec{ Summary: "资产套餐列表", - Description: "查询该资产所有套餐记录,含虚流量换算结果。", + Description: "查询该资产所有套餐记录,含虚流量换算结果。支持分页(默认 page=1, page_size=50, 最大100)。", Tags: []string{"资产管理"}, - Input: new(dto.AssetTypeIDRequest), - Output: new([]dto.AssetPackageResponse), + Input: new(dto.AssetPackagesRequest), + Output: new(dto.AssetPackagesResult), Auth: true, }) diff --git a/internal/service/asset/service.go b/internal/service/asset/service.go index 2d21a98..b7c5e46 100644 --- a/internal/service/asset/service.go +++ b/internal/service/asset/service.go @@ -8,6 +8,7 @@ import ( "sort" "time" + "github.com/break/junhong_cmp_fiber/internal/gateway" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/internal/model/dto" "github.com/break/junhong_cmp_fiber/internal/store/postgres" @@ -37,6 +38,7 @@ type Service struct { shopStore *postgres.ShopStore redis *redis.Client iotCardService IotCardRefresher + gatewayClient *gateway.Client // 用于调用 sync-info 同步设备信息(可为 nil,失败时仅记录日志) } // New 创建资产服务实例 @@ -51,6 +53,7 @@ func New( shopStore *postgres.ShopStore, redisClient *redis.Client, iotCardService IotCardRefresher, + gatewayClient *gateway.Client, ) *Service { return &Service{ db: db, @@ -63,6 +66,7 @@ func New( shopStore: shopStore, redis: redisClient, iotCardService: iotCardService, + gatewayClient: gatewayClient, } } @@ -336,6 +340,29 @@ func (s *Service) Refresh(ctx context.Context, assetType string, id uint) (*dto. // 设置冷却 Key s.redis.Set(ctx, cooldownKey, 1, constants.DeviceRefreshCooldownDuration) + // 刷新设备自身信息(在线状态、当前卡、固件版本等),失败不阻断刷新流程(per D-15) + if s.gatewayClient != nil { + device, devErr := s.deviceStore.GetByID(ctx, id) + if devErr == nil { + // IMEI 优先,无则用 SN(per D-3 注释说明) + cardNo := device.IMEI + if cardNo == "" { + cardNo = device.SN + } + if cardNo != "" { + if syncResp, syncErr := s.gatewayClient.SyncDeviceInfo(ctx, &gateway.SyncDeviceInfoReq{ + CardNo: cardNo, + }); syncErr == nil { + s.updateDeviceFromSyncInfo(ctx, id, syncResp) + } else { + logger.GetAppLogger().Warn("sync-info 调用失败", + zap.Uint("device_id", id), + zap.Error(syncErr)) + } + } + } + } + return s.GetRealtimeStatus(ctx, "device", id) default: @@ -343,6 +370,48 @@ func (s *Service) Refresh(ctx context.Context, assetType string, id uint) (*dto. } } +// updateDeviceFromSyncInfo 根据 Gateway sync-info 响应更新设备状态字段和当前卡标识 +// 失败时只记录日志,不返回错误(非关键步骤) +func (s *Service) updateDeviceFromSyncInfo(ctx context.Context, deviceID uint, resp *gateway.SyncDeviceInfoResp) { + now := time.Now() + + // 解析 LastOnlineTime(ISO 8601 字符串 → *time.Time) + var lastOnlineTime *time.Time + if resp.LastOnlineTime != "" { + if t, err := time.Parse(time.RFC3339, resp.LastOnlineTime); err == nil { + lastOnlineTime = &t + } + } + + // 更新设备 5 个存储字段 + updates := map[string]any{ + "online_status": resp.OnlineStatus, + "software_version": resp.SoftwareVersion, + "switch_mode": resp.SwitchMode, + "last_gateway_sync_at": now, + } + if lastOnlineTime != nil { + updates["last_online_time"] = lastOnlineTime + } + + if err := s.db.WithContext(ctx). + Model(&model.Device{}). + Where("id = ?", deviceID). + Updates(updates).Error; err != nil { + logger.GetAppLogger().Warn("sync-info 更新设备字段失败", + zap.Uint("device_id", deviceID), + zap.Error(err)) + return + } + + // 更新 is_current 标识(事务原子操作,per D-14 决策) + if err := s.deviceSimBindingStore.UpdateIsCurrentByDeviceID(ctx, deviceID, resp.CurrentIccid); err != nil { + logger.GetAppLogger().Warn("sync-info 更新 is_current 失败", + zap.Uint("device_id", deviceID), + zap.Error(err)) + } +} + // GetPackages 获取资产的所有套餐列表(支持分页) // page 默认 1,pageSize 默认 50,pageSize 最大 100 func (s *Service) GetPackages(ctx context.Context, assetType string, id uint, page, pageSize int) (*dto.AssetPackagesResult, error) { diff --git a/internal/service/commission_withdrawal/service.go b/internal/service/commission_withdrawal/service.go index f3f186c..c76e493 100644 --- a/internal/service/commission_withdrawal/service.go +++ b/internal/service/commission_withdrawal/service.go @@ -206,8 +206,6 @@ func (s *Service) Approve(ctx context.Context, id uint, req *dto.ApproveWithdraw "processed_at": now, "payment_type": req.PaymentType, "remark": req.Remark, - "approved_by": currentUserID, - "approved_at": now, } if req.Amount != nil { diff --git a/migrations/000092_drop_commission_withdrawal_approved_fields.down.sql b/migrations/000092_drop_commission_withdrawal_approved_fields.down.sql new file mode 100644 index 0000000..37b9362 --- /dev/null +++ b/migrations/000092_drop_commission_withdrawal_approved_fields.down.sql @@ -0,0 +1,3 @@ +ALTER TABLE tb_commission_withdrawal_request + ADD COLUMN IF NOT EXISTS approved_by BIGINT NOT NULL DEFAULT 0, + ADD COLUMN IF NOT EXISTS approved_at TIMESTAMPTZ; diff --git a/migrations/000092_drop_commission_withdrawal_approved_fields.up.sql b/migrations/000092_drop_commission_withdrawal_approved_fields.up.sql new file mode 100644 index 0000000..67f9bc9 --- /dev/null +++ b/migrations/000092_drop_commission_withdrawal_approved_fields.up.sql @@ -0,0 +1,7 @@ +-- 删除 tb_commission_withdrawal_request 表中的冗余审批人字段 +-- approved_by / approved_at 与 processor_id / processed_at 完全等价: +-- Approve 时两组字段写同一个值,Reject 时只写 processor_id。 +-- processor_id / processed_at 已覆盖全部场景,approved_* 为历史遗留字段。 +ALTER TABLE tb_commission_withdrawal_request + DROP COLUMN IF EXISTS approved_by, + DROP COLUMN IF EXISTS approved_at;