This commit is contained in:
@@ -64,10 +64,36 @@ func NewRegistry() *Registry {
|
||||
TitleTemplate: "套餐即将到期",
|
||||
BodyTemplate: "您的套餐即将到期,请及时查看并处理。",
|
||||
TemplateFields: map[string]struct{}{},
|
||||
RecipientKinds: map[string]struct{}{constants.NotificationRecipientKindPersonalCustomer: {}},
|
||||
RecipientKinds: map[string]struct{}{
|
||||
constants.NotificationRecipientKindAccount: {},
|
||||
constants.NotificationRecipientKindPersonalCustomer: {},
|
||||
},
|
||||
AllowedRefTypes: map[string]struct{}{
|
||||
constants.NotificationRefTypePackage: {},
|
||||
constants.NotificationRefTypeAsset: {},
|
||||
constants.NotificationRefTypePackage: {},
|
||||
constants.NotificationRefTypeAsset: {},
|
||||
constants.NotificationRefTypeExpiringAsset: {},
|
||||
},
|
||||
},
|
||||
constants.NotificationTypeAgentRechargeCompleted: {
|
||||
Type: constants.NotificationTypeAgentRechargeCompleted, Category: constants.NotificationCategorySystem,
|
||||
Severity: constants.NotificationSeverityInfo,
|
||||
TitleTemplate: "店铺充值已入账",
|
||||
BodyTemplate: "店铺充值已成功入账,请进入充值详情查看。",
|
||||
TemplateFields: map[string]struct{}{},
|
||||
RecipientKinds: map[string]struct{}{constants.NotificationRecipientKindAccount: {}},
|
||||
AllowedRefTypes: map[string]struct{}{
|
||||
constants.NotificationRefTypeAgentRecharge: {},
|
||||
},
|
||||
},
|
||||
constants.NotificationTypeRefundCompleted: {
|
||||
Type: constants.NotificationTypeRefundCompleted, Category: constants.NotificationCategorySystem,
|
||||
Severity: constants.NotificationSeverityInfo,
|
||||
TitleTemplate: "店铺退款已完成",
|
||||
BodyTemplate: "店铺退款已完成,请进入退款详情查看。",
|
||||
TemplateFields: map[string]struct{}{},
|
||||
RecipientKinds: map[string]struct{}{constants.NotificationRecipientKindAccount: {}},
|
||||
AllowedRefTypes: map[string]struct{}{
|
||||
constants.NotificationRefTypeRefund: {},
|
||||
},
|
||||
},
|
||||
constants.NotificationTypeExchangeShippingCreated: {
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
|
||||
var shanghaiLocation = time.FixedZone("Asia/Shanghai", 8*60*60)
|
||||
|
||||
// ReminderPublisher 将临期节点事实转换为个人客户通知 Outbox。
|
||||
// ReminderPublisher 将临期节点事实转换为店铺和个人客户通知 Outbox。
|
||||
type ReminderPublisher struct {
|
||||
db *gorm.DB
|
||||
outbox *outbox.Repository
|
||||
@@ -34,7 +34,7 @@ type recipientRow struct {
|
||||
CustomerID uint
|
||||
}
|
||||
|
||||
// Publish 批量解析资产关联个人客户,并在同一事务内幂等追加通知事件。
|
||||
// Publish 批量解析资产关联个人客户,并在同一事务内幂等追加店铺和个人通知事件。
|
||||
func (p *ReminderPublisher) Publish(ctx context.Context, candidates []dto.ExpiringAssetItem) error {
|
||||
if p == nil || p.db == nil || p.outbox == nil {
|
||||
return errors.New(errors.CodeInternalError, "套餐临期通知 Publisher 未配置")
|
||||
@@ -49,6 +49,11 @@ func (p *ReminderPublisher) Publish(ctx context.Context, candidates []dto.Expiri
|
||||
continue
|
||||
}
|
||||
key := recipientKey(candidate.AssetType, candidate.AssetID)
|
||||
if candidate.ShopID != nil {
|
||||
if err := p.appendShopNotification(ctx, tx, candidate, *candidate.ShopID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for customerID := range recipients[key] {
|
||||
if err := p.appendNotification(ctx, tx, candidate, customerID); err != nil {
|
||||
return err
|
||||
@@ -59,6 +64,35 @@ func (p *ReminderPublisher) Publish(ctx context.Context, candidates []dto.Expiri
|
||||
})
|
||||
}
|
||||
|
||||
// appendShopNotification 按资产所属店铺写入一条动态接收人通知事件。
|
||||
func (p *ReminderPublisher) appendShopNotification(ctx context.Context, tx *gorm.DB, candidate dto.ExpiringAssetItem, shopID uint) error {
|
||||
node := *candidate.DaysUntilFinalExpiry
|
||||
expiryDate := candidate.EstimatedFinalExpiresAt.In(shanghaiLocation).Format("20060102")
|
||||
assetCode := "c"
|
||||
if candidate.AssetType == constants.AssetTypeDevice {
|
||||
assetCode = "d"
|
||||
}
|
||||
eventID := fmt.Sprintf("pex:%s:%d:%s:%d:shop:%d", assetCode, candidate.AssetID, expiryDate, node, shopID)
|
||||
assetID := strconv.FormatUint(uint64(candidate.AssetID), 10)
|
||||
shopIDText := strconv.FormatUint(uint64(shopID), 10)
|
||||
_, err := p.outbox.AppendIdempotent(ctx, tx, outbox.Envelope{
|
||||
EventID: eventID, EventType: constants.OutboxEventTypeAdminDynamicNotification,
|
||||
PayloadVersion: constants.NotificationPayloadVersionV1,
|
||||
AggregateType: "package_expiry", AggregateID: strconv.FormatUint(uint64(candidate.PackageUsageID), 10),
|
||||
ResourceType: candidate.AssetType, ResourceID: assetID, BusinessKey: eventID,
|
||||
Payload: notificationapp.AdminDynamicPayload{
|
||||
TargetKind: constants.NotificationTargetKindShop, TargetID: shopID,
|
||||
NotificationType: constants.NotificationTypePackageExpiring, TemplateData: map[string]string{},
|
||||
RefType: constants.NotificationRefTypeExpiringAsset, RefID: shopIDText,
|
||||
ExpiresAt: candidate.EstimatedFinalExpiresAt,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "写入店铺套餐临期通知事件失败")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *ReminderPublisher) loadRecipients(ctx context.Context, candidates []dto.ExpiringAssetItem) (map[string]map[uint]struct{}, error) {
|
||||
cardIDs := make([]uint, 0)
|
||||
deviceIDs := make([]uint, 0)
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
)
|
||||
|
||||
// RecipientResolver 按店铺当前独立归属解析主账号和可用平台业务员。
|
||||
// RecipientResolver 按店铺当前独立归属解析店铺账号和可用平台业务员。
|
||||
type RecipientResolver struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
@@ -25,7 +25,7 @@ func NewRecipientResolver(db *gorm.DB) *RecipientResolver {
|
||||
return &RecipientResolver{db: db}
|
||||
}
|
||||
|
||||
// ResolveNotificationRecipients 返回启用的店铺主账号和当前可用业务员账号 ID。
|
||||
// ResolveNotificationRecipients 返回全部启用的店铺账号和当前可用业务员账号 ID。
|
||||
//
|
||||
// 解析只读取目标店铺当前保存的业务员 ID,不读取父店铺、祖先店铺或创建人。
|
||||
func (r *RecipientResolver) ResolveNotificationRecipients(ctx context.Context, shopID uint) ([]uint, error) {
|
||||
@@ -46,8 +46,8 @@ func (r *RecipientResolver) ResolveNotificationRecipients(ctx context.Context, s
|
||||
|
||||
query := r.db.WithContext(ctx).Model(&model.Account{}).
|
||||
Select("id").
|
||||
Where("status = ? AND shop_id = ? AND is_primary = ? AND user_type = ?",
|
||||
constants.StatusEnabled, shopID, true, constants.UserTypeAgent)
|
||||
Where("status = ? AND shop_id = ? AND user_type = ?",
|
||||
constants.StatusEnabled, shopID, constants.UserTypeAgent)
|
||||
if target.BusinessOwnerAccountID != nil {
|
||||
query = query.Or("id = ? AND status = ? AND user_type = ?",
|
||||
*target.BusinessOwnerAccountID, constants.StatusEnabled, constants.UserTypePlatform)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
notificationapp "github.com/break/junhong_cmp_fiber/internal/application/notification"
|
||||
walletapp "github.com/break/junhong_cmp_fiber/internal/application/wallet"
|
||||
"github.com/break/junhong_cmp_fiber/internal/infrastructure/messaging/outbox"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
@@ -33,5 +34,22 @@ func (w *CreditEventWriter) Append(ctx context.Context, tx *gorm.DB, event walle
|
||||
ResourceType: event.ReferenceType, ResourceID: strconv.FormatUint(uint64(event.ReferenceID), 10),
|
||||
BusinessKey: event.EventID, RequestID: event.RequestID, CorrelationID: event.CorrelationID, Payload: event,
|
||||
})
|
||||
if err != nil || event.ReferenceType != constants.ReferenceTypeTopup || event.TransactionType != constants.AgentTransactionTypeRecharge {
|
||||
return err
|
||||
}
|
||||
rechargeID := strconv.FormatUint(uint64(event.ReferenceID), 10)
|
||||
notificationEventID := "agent-recharge:" + rechargeID + ":completed"
|
||||
_, err = w.outbox.AppendIdempotent(ctx, tx, outbox.Envelope{
|
||||
EventID: notificationEventID, EventType: constants.OutboxEventTypeAdminDynamicNotification,
|
||||
PayloadVersion: constants.NotificationPayloadVersionV1,
|
||||
AggregateType: "agent_recharge", AggregateID: rechargeID,
|
||||
ResourceType: constants.NotificationRefTypeAgentRecharge, ResourceID: rechargeID,
|
||||
BusinessKey: notificationEventID, RequestID: event.RequestID, CorrelationID: event.CorrelationID,
|
||||
Payload: notificationapp.AdminDynamicPayload{
|
||||
TargetKind: constants.NotificationTargetKindShop, TargetID: event.ShopID,
|
||||
NotificationType: constants.NotificationTypeAgentRechargeCompleted, TemplateData: map[string]string{},
|
||||
RefType: constants.NotificationRefTypeAgentRecharge, RefID: rechargeID,
|
||||
},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user