fix: 第1批 M9 套餐临期提醒按资产、扫码进入时触发
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 10m17s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 10m17s
- 每日扫描不再给个人客户写临期通知,店铺通知不变 - H5 资产信息在当前资产剩余 0~15 天且当天首次进入时返回 expiry_reminder,并为该客户、该资产写一条临期通知(Redis 去重,失败不占键) - 个人通知列表、未读数、全部已读中临期提醒只处理当前资产(按 ref_key) - OpenSpec Change scope-expiry-reminder-to-current-asset:6/9,临期 fixture 端到端验证与每日扫描验证待测试部署后完成
This commit is contained in:
@@ -46,6 +46,17 @@ type ClientAssetHandler struct {
|
||||
observationSeries cardObservationApp.BestEffortSeriesDispatcher
|
||||
paymentMethodPolicy ClientPaymentMethodPolicy
|
||||
forceRechargeChecker ClientForceRechargeChecker
|
||||
expiryReminder ClientExpiryReminder
|
||||
}
|
||||
|
||||
// ClientExpiryReminder 在 H5 进入资产时判定并发出套餐临期提醒。
|
||||
type ClientExpiryReminder interface {
|
||||
Remind(ctx context.Context, customerID uint, candidate dto.ExpiringAssetItem) *dto.AssetExpiryReminder
|
||||
}
|
||||
|
||||
// SetExpiryReminder 注入进入资产时的套餐临期提醒用例。
|
||||
func (h *ClientAssetHandler) SetExpiryReminder(reminder ClientExpiryReminder) {
|
||||
h.expiryReminder = reminder
|
||||
}
|
||||
|
||||
// ClientPaymentMethodPolicy 提供 C 端支付方式读取和校验能力。
|
||||
@@ -279,6 +290,10 @@ func (h *ClientAssetHandler) GetAssetInfo(c *fiber.Ctx) error {
|
||||
BoundDeviceName: resolved.Asset.BoundDeviceName,
|
||||
}
|
||||
|
||||
if h.expiryReminder != nil {
|
||||
resp.ExpiryReminder = h.expiryReminder.Remind(c.UserContext(), resolved.CustomerID, expiringCandidateOf(resolved.Asset))
|
||||
}
|
||||
|
||||
// 调用服务层获取设备实时 Gateway 数据(per D-11)
|
||||
// Gateway 失败时 DeviceRealtime 仅含 gateway_msg,不阻断主流程(per D-06)
|
||||
if resp.AssetType == "device" {
|
||||
@@ -851,3 +866,24 @@ func mapDeviceGatewayInfoToClientInfo(g *dto.DeviceGatewayInfo) *dto.DeviceRealt
|
||||
info.GatewayMsg = g.GatewayMsg
|
||||
return info
|
||||
}
|
||||
|
||||
// expiringCandidateOf 把资产解析结果转换为临期提醒候选;资产标识与通知中心过滤口径一致(卡 ICCID;设备虚拟号,空则 IMEI)。
|
||||
func expiringCandidateOf(asset *dto.AssetResolveResponse) dto.ExpiringAssetItem {
|
||||
candidate := dto.ExpiringAssetItem{
|
||||
AssetID: asset.AssetID, ShopID: asset.ShopID, PackageExpiryEstimate: asset.PackageExpiryEstimate,
|
||||
}
|
||||
if asset.CurrentPackageUsageID != nil {
|
||||
candidate.PackageUsageID = *asset.CurrentPackageUsageID
|
||||
}
|
||||
if asset.AssetType == "device" {
|
||||
candidate.AssetType = constants.AssetTypeDevice
|
||||
candidate.Identifier = asset.VirtualNo
|
||||
if candidate.Identifier == "" {
|
||||
candidate.Identifier = asset.IMEI
|
||||
}
|
||||
return candidate
|
||||
}
|
||||
candidate.AssetType = constants.AssetTypeIotCard
|
||||
candidate.Identifier = asset.ICCID
|
||||
return candidate
|
||||
}
|
||||
|
||||
@@ -36,7 +36,11 @@ func (h *ClientNotificationHandler) UnreadCount(c *fiber.Ctx) error {
|
||||
if !ok || customerID == 0 {
|
||||
return errors.New(errors.CodeUnauthorized)
|
||||
}
|
||||
result, err := h.query.PersonalUnreadCount(c.UserContext(), customerID)
|
||||
assetKey, err := h.currentAssetKey(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result, err := h.query.PersonalUnreadCount(c.UserContext(), customerID, assetKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -61,7 +65,11 @@ func (h *ClientNotificationHandler) List(c *fiber.Ctx) error {
|
||||
if !ok || customerID == 0 {
|
||||
return errors.New(errors.CodeUnauthorized)
|
||||
}
|
||||
result, err := h.query.PersonalList(c.UserContext(), customerID, request)
|
||||
assetKey, err := h.currentAssetKey(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result, err := h.query.PersonalList(c.UserContext(), customerID, assetKey, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -75,7 +83,11 @@ func (h *ClientNotificationHandler) MarkAllRead(c *fiber.Ctx) error {
|
||||
if !ok || customerID == 0 {
|
||||
return errors.New(errors.CodeUnauthorized)
|
||||
}
|
||||
result, err := h.readService.MarkAllPersonalRead(c.UserContext(), customerID)
|
||||
assetKey, err := h.currentAssetKey(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result, err := h.readService.MarkAllPersonalRead(c.UserContext(), customerID, assetKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -99,6 +111,15 @@ func (h *ClientNotificationHandler) MarkRead(c *fiber.Ctx) error {
|
||||
return response.Success(c, dto.NotificationReadResponse{Success: true})
|
||||
}
|
||||
|
||||
// currentAssetKey 读取令牌当前资产的通知引用标识,用于只展示当前资产的套餐临期提醒。
|
||||
func (h *ClientNotificationHandler) currentAssetKey(c *fiber.Ctx) (string, error) {
|
||||
assetType, assetID, ok := middleware.GetCurrentAsset(c)
|
||||
if !ok {
|
||||
return "", nil
|
||||
}
|
||||
return h.query.CurrentAssetRefKey(c.UserContext(), assetType, assetID)
|
||||
}
|
||||
|
||||
func logPersonalNotificationListValidationFailure(c *fiber.Ctx, request dto.PersonalNotificationListRequest, err error) {
|
||||
logger.GetAppLogger().Warn("个人客户站内通知列表参数验证失败",
|
||||
zap.String("method", c.Method()),
|
||||
|
||||
Reference in New Issue
Block a user