Zuncle 471e21a68b fix(activity): 修复复制活动时奖励快照时间写入异常
本次提交修复了管理端复制活动接口在复制奖励配置时写入零时间导致的 MySQL datetime 报错,并补齐了活动奖励复制过程遗漏的快照与业务字段,避免新活动复制后出现配置丢失或插入失败。

- 复制逻辑:在 CopyActivity 中完整复制 price_snapshot_cents、cost_snapshot_cents、price_snapshot_at、min_score、drop_quantity 等奖励字段
- 异常兜底:当历史奖励快照时间为空或为零值时,复制时自动回填为当前时间;当价格/成本快照缺失且存在商品 ID 时,回退读取当前商品价格与成本价
- 兼容历史数据:确保旧奖励配置即使未完整回填快照字段,也能被安全复制,不再因 0000-00-00 时间值触发插入失败
- 测试覆盖:补充活动复制场景测试,验证快照字段原样复制、缺失快照自动补齐,以及 min_score/drop_quantity 等字段不会在复制时丢失
2026-04-21 03:08:21 +08:00

124 lines
3.4 KiB
Go
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package activity
import (
"context"
"time"
"bindbox-game/internal/repository/mysql/dao"
"bindbox-game/internal/repository/mysql/model"
)
// CopyActivity 复制活动及其期次与奖励
// 参数: activityID 源活动ID
// 返回: 新活动ID与错误
func (s *service) CopyActivity(ctx context.Context, activityID int64) (int64, error) {
var newActivityID int64
err := s.writeDB.Transaction(func(tx *dao.Query) error {
src, err := s.readDB.Activities.WithContext(ctx).Where(s.readDB.Activities.ID.Eq(activityID)).First()
if err != nil {
return err
}
newAct := &model.Activities{
Name: src.Name,
Banner: src.Banner,
ActivityCategoryID: src.ActivityCategoryID,
Status: 1,
PriceDraw: src.PriceDraw,
IsBoss: src.IsBoss,
EndTime: time.Now().AddDate(1, 0, 0), // 默认1年后结束确保活动列表可见
}
if err := tx.Activities.WithContext(ctx).Omit(
tx.Activities.StartTime,
tx.Activities.ScheduledTime,
tx.Activities.LastSettledAt,
).Create(newAct); err != nil {
return err
}
newActivityID = newAct.ID
issues, err := s.readDB.ActivityIssues.WithContext(ctx).Where(s.readDB.ActivityIssues.ActivityID.Eq(activityID)).Order(s.readDB.ActivityIssues.Sort.Desc()).Find()
if err != nil {
return err
}
idMap := make(map[int64]int64, len(issues))
for _, srcIssue := range issues {
ni := &model.ActivityIssues{
ActivityID: newActivityID,
IssueNumber: srcIssue.IssueNumber,
Status: 3,
Sort: srcIssue.Sort,
}
if err := tx.ActivityIssues.WithContext(ctx).Create(ni); err != nil {
return err
}
idMap[srcIssue.ID] = ni.ID
}
if len(idMap) == 0 {
return nil
}
oldIDs := make([]int64, 0, len(idMap))
for k := range idMap {
oldIDs = append(oldIDs, k)
}
rewards, err := s.readDB.ActivityRewardSettings.WithContext(ctx).Where(s.readDB.ActivityRewardSettings.IssueID.In(oldIDs...)).Order(s.readDB.ActivityRewardSettings.Sort.Desc()).Find()
if err != nil {
return err
}
for _, r := range rewards {
priceSnapshot := r.PriceSnapshotCents
costSnapshot := r.CostSnapshotCents
snapshotAt := r.PriceSnapshotAt
if r.ProductID > 0 && (priceSnapshot == 0 || costSnapshot == 0) {
product, err := s.readDB.Products.WithContext(ctx).Where(s.readDB.Products.ID.Eq(r.ProductID)).First()
if err != nil {
return err
}
if priceSnapshot == 0 {
priceSnapshot = product.Price
}
if costSnapshot == 0 {
costSnapshot = product.CostPrice
}
}
if snapshotAt.IsZero() {
snapshotAt = time.Now()
}
dropQuantity := r.DropQuantity
if dropQuantity < 1 {
dropQuantity = 1
}
nr := &model.ActivityRewardSettings{
IssueID: idMap[r.IssueID],
ProductID: r.ProductID,
PriceSnapshotCents: priceSnapshot,
PriceSnapshotAt: snapshotAt,
Weight: r.Weight,
Quantity: r.Quantity,
OriginalQty: r.OriginalQty,
Level: r.Level,
Sort: r.Sort,
IsBoss: r.IsBoss,
MinScore: r.MinScore,
DropQuantity: dropQuantity,
CostSnapshotCents: costSnapshot,
}
if err := tx.ActivityRewardSettings.WithContext(ctx).Create(nr); err != nil {
return err
}
}
return nil
})
if err != nil {
return 0, err
}
return newActivityID, nil
}