feat(fragment): 商品成本价 + 活动奖品单次产出数量
- products 表新增 cost_price 字段(成本价/分) - activity_reward_settings 新增 drop_quantity(单次产出数量,默认1) 和 cost_snapshot_cents(成本价快照) - 奖品创建/修改时自动快照成本价,drop_quantity 限制 1-100 - 抽奖发放逻辑按 drop_quantity 循环创建多个库存项 - 抽奖结果接口按 drop_quantity 返回多条 item,前端自动合并显示 - 抽奖记录接口返回 drop_quantity 字段 - 商品管理 API 全链路支持 cost_price
This commit is contained in:
parent
1f8f3f7fad
commit
46b9555823
@ -32,6 +32,7 @@ type drawLogItem struct {
|
|||||||
IsWinner int32 `json:"is_winner"`
|
IsWinner int32 `json:"is_winner"`
|
||||||
Level int32 `json:"level"`
|
Level int32 `json:"level"`
|
||||||
CurrentLevel int32 `json:"current_level"`
|
CurrentLevel int32 `json:"current_level"`
|
||||||
|
DropQuantity int32 `json:"drop_quantity"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,6 +143,7 @@ func (h *handler) ListDrawLogs() core.HandlerFunc {
|
|||||||
// 批量查询奖品与商品信息
|
// 批量查询奖品与商品信息
|
||||||
rewardNameMap := make(map[int64]string)
|
rewardNameMap := make(map[int64]string)
|
||||||
rewardImageMap := make(map[int64]string)
|
rewardImageMap := make(map[int64]string)
|
||||||
|
rewardDropQtyMap := make(map[int64]int32)
|
||||||
if len(rewardIDs) > 0 {
|
if len(rewardIDs) > 0 {
|
||||||
rewards, err := h.readDB.ActivityRewardSettings.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.ActivityRewardSettings.ID.In(rewardIDs...)).Find()
|
rewards, err := h.readDB.ActivityRewardSettings.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.ActivityRewardSettings.ID.In(rewardIDs...)).Find()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -150,6 +152,7 @@ func (h *handler) ListDrawLogs() core.HandlerFunc {
|
|||||||
rewardProductMap := make(map[int64]int64)
|
rewardProductMap := make(map[int64]int64)
|
||||||
|
|
||||||
for _, r := range rewards {
|
for _, r := range rewards {
|
||||||
|
rewardDropQtyMap[r.ID] = r.DropQuantity
|
||||||
// 不再使用 r.Name,只通过 ProductID 关联查询商品名称
|
// 不再使用 r.Name,只通过 ProductID 关联查询商品名称
|
||||||
if r.ProductID > 0 {
|
if r.ProductID > 0 {
|
||||||
if _, ok := productSet[r.ProductID]; !ok {
|
if _, ok := productSet[r.ProductID]; !ok {
|
||||||
@ -208,6 +211,7 @@ func (h *handler) ListDrawLogs() core.HandlerFunc {
|
|||||||
IsWinner: v.IsWinner,
|
IsWinner: v.IsWinner,
|
||||||
Level: v.Level,
|
Level: v.Level,
|
||||||
CurrentLevel: v.CurrentLevel,
|
CurrentLevel: v.CurrentLevel,
|
||||||
|
DropQuantity: rewardDropQtyMap[v.RewardID],
|
||||||
CreatedAt: v.CreatedAt,
|
CreatedAt: v.CreatedAt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -257,7 +261,9 @@ func (h *handler) ListDrawLogsByLevel() core.HandlerFunc {
|
|||||||
// 收集所有 ProductID 用于批量查询商品名称
|
// 收集所有 ProductID 用于批量查询商品名称
|
||||||
productIDs := make([]int64, 0, len(rewards))
|
productIDs := make([]int64, 0, len(rewards))
|
||||||
rewardProductMap := make(map[int64]int64) // rewardID -> productID
|
rewardProductMap := make(map[int64]int64) // rewardID -> productID
|
||||||
|
rewardDropQtyMap2 := make(map[int64]int32)
|
||||||
for _, r := range rewards {
|
for _, r := range rewards {
|
||||||
|
rewardDropQtyMap2[r.ID] = r.DropQuantity
|
||||||
if r.ProductID > 0 {
|
if r.ProductID > 0 {
|
||||||
productIDs = append(productIDs, r.ProductID)
|
productIDs = append(productIDs, r.ProductID)
|
||||||
rewardProductMap[r.ID] = r.ProductID
|
rewardProductMap[r.ID] = r.ProductID
|
||||||
@ -304,6 +310,7 @@ func (h *handler) ListDrawLogsByLevel() core.HandlerFunc {
|
|||||||
IsWinner: v.IsWinner,
|
IsWinner: v.IsWinner,
|
||||||
Level: v.Level,
|
Level: v.Level,
|
||||||
CurrentLevel: v.CurrentLevel,
|
CurrentLevel: v.CurrentLevel,
|
||||||
|
DropQuantity: rewardDropQtyMap2[v.RewardID],
|
||||||
}
|
}
|
||||||
groupsMap[v.Level] = append(groupsMap[v.Level], item)
|
groupsMap[v.Level] = append(groupsMap[v.Level], item)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -159,7 +159,11 @@ func (h *handler) LotteryResultByOrder() core.HandlerFunc {
|
|||||||
drawLogIDs = append(drawLogIDs, lg.ID)
|
drawLogIDs = append(drawLogIDs, lg.ID)
|
||||||
image := ""
|
image := ""
|
||||||
name := ""
|
name := ""
|
||||||
|
dropQty := int32(1)
|
||||||
if rw, ok := rewardMap[lg.RewardID]; ok {
|
if rw, ok := rewardMap[lg.RewardID]; ok {
|
||||||
|
if rw.DropQuantity > 1 {
|
||||||
|
dropQty = rw.DropQuantity
|
||||||
|
}
|
||||||
if p, ok := productMap[rw.ProductID]; ok {
|
if p, ok := productMap[rw.ProductID]; ok {
|
||||||
name = p.Name
|
name = p.Name
|
||||||
if p.ImagesJSON != "" {
|
if p.ImagesJSON != "" {
|
||||||
@ -171,13 +175,15 @@ func (h *handler) LotteryResultByOrder() core.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
items = append(items, orderResultItem{
|
for q := int32(0); q < dropQty; q++ {
|
||||||
RewardID: lg.RewardID,
|
items = append(items, orderResultItem{
|
||||||
RewardName: name,
|
RewardID: lg.RewardID,
|
||||||
Level: lg.Level,
|
RewardName: name,
|
||||||
DrawIndex: int32(lg.DrawIndex + 1),
|
Level: lg.Level,
|
||||||
Image: image,
|
DrawIndex: int32(lg.DrawIndex + 1),
|
||||||
})
|
Image: image,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理翻倍奖励
|
// 处理翻倍奖励
|
||||||
|
|||||||
@ -24,6 +24,7 @@ type rewardItem struct {
|
|||||||
IsBoss int32 `json:"is_boss"`
|
IsBoss int32 `json:"is_boss"`
|
||||||
ProductImage string `json:"product_image"`
|
ProductImage string `json:"product_image"`
|
||||||
MinScore int64 `json:"min_score"`
|
MinScore int64 `json:"min_score"`
|
||||||
|
DropQuantity int32 `json:"drop_quantity"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type listRewardsResponse struct {
|
type listRewardsResponse struct {
|
||||||
@ -130,6 +131,7 @@ func (h *handler) ListIssueRewards() core.HandlerFunc {
|
|||||||
IsBoss: v.IsBoss,
|
IsBoss: v.IsBoss,
|
||||||
ProductImage: imageMap[v.ProductID],
|
ProductImage: imageMap[v.ProductID],
|
||||||
MinScore: v.MinScore,
|
MinScore: v.MinScore,
|
||||||
|
DropQuantity: v.DropQuantity,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx.Payload(res)
|
ctx.Payload(res)
|
||||||
|
|||||||
@ -15,6 +15,7 @@ type createProductRequest struct {
|
|||||||
CategoryID int64 `json:"category_id" binding:"required"`
|
CategoryID int64 `json:"category_id" binding:"required"`
|
||||||
ImagesJSON string `json:"images_json"`
|
ImagesJSON string `json:"images_json"`
|
||||||
Price int64 `json:"price" binding:"required"`
|
Price int64 `json:"price" binding:"required"`
|
||||||
|
CostPrice int64 `json:"cost_price"`
|
||||||
Stock int64 `json:"stock" binding:"required"`
|
Stock int64 `json:"stock" binding:"required"`
|
||||||
Status int32 `json:"status"`
|
Status int32 `json:"status"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
@ -48,7 +49,7 @@ func (h *handler) CreateProduct() core.HandlerFunc {
|
|||||||
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.CreateAdminError, "禁止操作"))
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.CreateAdminError, "禁止操作"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
item, err := h.product.CreateProduct(ctx.RequestContext(), prodsvc.CreateProductInput{Name: req.Name, CategoryID: req.CategoryID, ImagesJSON: req.ImagesJSON, Price: req.Price, Stock: req.Stock, Status: req.Status, Description: req.Description, ShowInMiniapp: req.ShowInMiniapp})
|
item, err := h.product.CreateProduct(ctx.RequestContext(), prodsvc.CreateProductInput{Name: req.Name, CategoryID: req.CategoryID, ImagesJSON: req.ImagesJSON, Price: req.Price, CostPrice: req.CostPrice, Stock: req.Stock, Status: req.Status, Description: req.Description, ShowInMiniapp: req.ShowInMiniapp})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.CreateAdminError, err.Error()))
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.CreateAdminError, err.Error()))
|
||||||
return
|
return
|
||||||
@ -64,6 +65,7 @@ type modifyProductRequest struct {
|
|||||||
CategoryID *int64 `json:"category_id"`
|
CategoryID *int64 `json:"category_id"`
|
||||||
ImagesJSON *string `json:"images_json"`
|
ImagesJSON *string `json:"images_json"`
|
||||||
Price *int64 `json:"price"`
|
Price *int64 `json:"price"`
|
||||||
|
CostPrice *int64 `json:"cost_price"`
|
||||||
Stock *int64 `json:"stock"`
|
Stock *int64 `json:"stock"`
|
||||||
Status *int32 `json:"status"`
|
Status *int32 `json:"status"`
|
||||||
Description *string `json:"description"`
|
Description *string `json:"description"`
|
||||||
@ -94,7 +96,7 @@ func (h *handler) ModifyProduct() core.HandlerFunc {
|
|||||||
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.CreateAdminError, "禁止操作"))
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.CreateAdminError, "禁止操作"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := h.product.ModifyProduct(ctx.RequestContext(), id, prodsvc.ModifyProductInput{Name: req.Name, CategoryID: req.CategoryID, ImagesJSON: req.ImagesJSON, Price: req.Price, Stock: req.Stock, Status: req.Status, Description: req.Description, ShowInMiniapp: req.ShowInMiniapp}); err != nil {
|
if err := h.product.ModifyProduct(ctx.RequestContext(), id, prodsvc.ModifyProductInput{Name: req.Name, CategoryID: req.CategoryID, ImagesJSON: req.ImagesJSON, Price: req.Price, CostPrice: req.CostPrice, Stock: req.Stock, Status: req.Status, Description: req.Description, ShowInMiniapp: req.ShowInMiniapp}); err != nil {
|
||||||
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.CreateAdminError, err.Error()))
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.CreateAdminError, err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -143,6 +145,7 @@ type productItem struct {
|
|||||||
CategoryID int64 `json:"category_id"`
|
CategoryID int64 `json:"category_id"`
|
||||||
ImagesJSON string `json:"images_json"`
|
ImagesJSON string `json:"images_json"`
|
||||||
Price int64 `json:"price"`
|
Price int64 `json:"price"`
|
||||||
|
CostPrice int64 `json:"cost_price"`
|
||||||
Stock int64 `json:"stock"`
|
Stock int64 `json:"stock"`
|
||||||
Sales int64 `json:"sales"`
|
Sales int64 `json:"sales"`
|
||||||
Status int32 `json:"status"`
|
Status int32 `json:"status"`
|
||||||
@ -188,7 +191,7 @@ func (h *handler) ListProducts() core.HandlerFunc {
|
|||||||
res.Total = total
|
res.Total = total
|
||||||
res.List = make([]productItem, len(items))
|
res.List = make([]productItem, len(items))
|
||||||
for i, it := range items {
|
for i, it := range items {
|
||||||
res.List[i] = productItem{ID: it.ID, Name: it.Name, CategoryID: it.CategoryID, ImagesJSON: it.ImagesJSON, Price: it.Price, Stock: it.Stock, Sales: it.Sales, Status: it.Status, Description: it.Description, ShowInMiniapp: it.ShowInMiniapp}
|
res.List[i] = productItem{ID: it.ID, Name: it.Name, CategoryID: it.CategoryID, ImagesJSON: it.ImagesJSON, Price: it.Price, CostPrice: it.CostPrice, Stock: it.Stock, Sales: it.Sales, Status: it.Status, Description: it.Description, ShowInMiniapp: it.ShowInMiniapp}
|
||||||
}
|
}
|
||||||
ctx.Payload(res)
|
ctx.Payload(res)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,11 +21,13 @@ type rewardItem struct {
|
|||||||
Sort int32 `json:"sort"`
|
Sort int32 `json:"sort"`
|
||||||
IsBoss int32 `json:"is_boss"`
|
IsBoss int32 `json:"is_boss"`
|
||||||
MinScore int64 `json:"min_score"`
|
MinScore int64 `json:"min_score"`
|
||||||
|
DropQuantity int32 `json:"drop_quantity"`
|
||||||
ProductName string `json:"product_name"`
|
ProductName string `json:"product_name"`
|
||||||
ProductImageUrl string `json:"product_image_url"`
|
ProductImageUrl string `json:"product_image_url"`
|
||||||
ProductPrice float64 `json:"product_price"` // 兼容:返回配置快照价
|
ProductPrice float64 `json:"product_price"` // 兼容:返回配置快照价
|
||||||
ProductPriceSnapshot float64 `json:"product_price_snapshot"`
|
ProductPriceSnapshot float64 `json:"product_price_snapshot"`
|
||||||
ProductPriceCurrent float64 `json:"product_price_current"`
|
ProductPriceCurrent float64 `json:"product_price_current"`
|
||||||
|
CostSnapshotCents int64 `json:"cost_snapshot_cents"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type createRewardsRequest struct {
|
type createRewardsRequest struct {
|
||||||
@ -73,15 +75,16 @@ func (h *handler) CreateIssueRewards() core.HandlerFunc {
|
|||||||
var rewards []activitysvc.CreateRewardInput
|
var rewards []activitysvc.CreateRewardInput
|
||||||
for _, r := range req.Rewards {
|
for _, r := range req.Rewards {
|
||||||
rewards = append(rewards, activitysvc.CreateRewardInput{
|
rewards = append(rewards, activitysvc.CreateRewardInput{
|
||||||
ProductID: r.ProductID,
|
ProductID: r.ProductID,
|
||||||
Name: "", // Name 不再从前端传递,后续可从商品表获取
|
Name: "", // Name 不再从前端传递,后续可从商品表获取
|
||||||
Weight: int32(r.Weight),
|
Weight: int32(r.Weight),
|
||||||
Quantity: r.Quantity,
|
Quantity: r.Quantity,
|
||||||
OriginalQty: r.OriginalQty,
|
OriginalQty: r.OriginalQty,
|
||||||
Level: r.Level,
|
Level: r.Level,
|
||||||
Sort: r.Sort,
|
Sort: r.Sort,
|
||||||
IsBoss: r.IsBoss,
|
IsBoss: r.IsBoss,
|
||||||
MinScore: r.MinScore,
|
MinScore: r.MinScore,
|
||||||
|
DropQuantity: r.DropQuantity,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,15 +141,17 @@ func (h *handler) ListIssueRewards() core.HandlerFunc {
|
|||||||
res.List = make([]rewardItem, len(items))
|
res.List = make([]rewardItem, len(items))
|
||||||
for i, v := range items {
|
for i, v := range items {
|
||||||
it := rewardItem{
|
it := rewardItem{
|
||||||
ID: v.ID,
|
ID: v.ID,
|
||||||
ProductID: v.ProductID,
|
ProductID: v.ProductID,
|
||||||
Weight: float64(v.Weight),
|
Weight: float64(v.Weight),
|
||||||
Quantity: v.Quantity,
|
Quantity: v.Quantity,
|
||||||
OriginalQty: v.OriginalQty,
|
OriginalQty: v.OriginalQty,
|
||||||
Level: v.Level,
|
Level: v.Level,
|
||||||
Sort: v.Sort,
|
Sort: v.Sort,
|
||||||
IsBoss: v.IsBoss,
|
IsBoss: v.IsBoss,
|
||||||
MinScore: v.MinScore,
|
MinScore: v.MinScore,
|
||||||
|
DropQuantity: v.DropQuantity,
|
||||||
|
CostSnapshotCents: v.CostSnapshotCents,
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.ProductID > 0 {
|
if v.ProductID > 0 {
|
||||||
@ -165,14 +170,15 @@ func (h *handler) ListIssueRewards() core.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type modifyRewardRequest struct {
|
type modifyRewardRequest struct {
|
||||||
ProductID *int64 `json:"product_id"`
|
ProductID *int64 `json:"product_id"`
|
||||||
Weight *float64 `json:"weight"`
|
Weight *float64 `json:"weight"`
|
||||||
Quantity *int64 `json:"quantity"`
|
Quantity *int64 `json:"quantity"`
|
||||||
OriginalQty *int64 `json:"original_qty"`
|
OriginalQty *int64 `json:"original_qty"`
|
||||||
Level *int32 `json:"level"`
|
Level *int32 `json:"level"`
|
||||||
Sort *int32 `json:"sort"`
|
Sort *int32 `json:"sort"`
|
||||||
IsBoss *int32 `json:"is_boss"`
|
IsBoss *int32 `json:"is_boss"`
|
||||||
MinScore *int64 `json:"min_score"`
|
MinScore *int64 `json:"min_score"`
|
||||||
|
DropQuantity *int32 `json:"drop_quantity"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ModifyIssueReward 更新期数奖励
|
// ModifyIssueReward 更新期数奖励
|
||||||
@ -206,15 +212,16 @@ func (h *handler) ModifyIssueReward() core.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
in := activitysvc.ModifyRewardInput{
|
in := activitysvc.ModifyRewardInput{
|
||||||
ProductID: req.ProductID,
|
ProductID: req.ProductID,
|
||||||
Name: "", // Name 不再使用
|
Name: "", // Name 不再使用
|
||||||
Weight: req.Weight,
|
Weight: req.Weight,
|
||||||
Quantity: req.Quantity,
|
Quantity: req.Quantity,
|
||||||
OriginalQty: req.OriginalQty,
|
OriginalQty: req.OriginalQty,
|
||||||
Level: req.Level,
|
Level: req.Level,
|
||||||
Sort: req.Sort,
|
Sort: req.Sort,
|
||||||
IsBoss: req.IsBoss,
|
IsBoss: req.IsBoss,
|
||||||
MinScore: req.MinScore,
|
MinScore: req.MinScore,
|
||||||
|
DropQuantity: req.DropQuantity,
|
||||||
}
|
}
|
||||||
if err := h.activity.ModifyIssueReward(ctx.RequestContext(), rewardID, in); err != nil {
|
if err := h.activity.ModifyIssueReward(ctx.RequestContext(), rewardID, in); err != nil {
|
||||||
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.CreateIssueRewardsError, err.Error()))
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.CreateIssueRewardsError, err.Error()))
|
||||||
|
|||||||
@ -28,7 +28,9 @@ type ActivityRewardSettings struct {
|
|||||||
Sort int32 `gorm:"column:sort;comment:排序" json:"sort"` // 排序
|
Sort int32 `gorm:"column:sort;comment:排序" json:"sort"` // 排序
|
||||||
IsBoss int32 `gorm:"column:is_boss;comment:Boss 1 是 0 不是" json:"is_boss"` // Boss 1 是 0 不是
|
IsBoss int32 `gorm:"column:is_boss;comment:Boss 1 是 0 不是" json:"is_boss"` // Boss 1 是 0 不是
|
||||||
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at" json:"deleted_at"`
|
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at" json:"deleted_at"`
|
||||||
MinScore int64 `gorm:"column:min_score;not null;comment:最低得分/碰数要求" json:"min_score"` // 最低得分/碰数要求
|
MinScore int64 `gorm:"column:min_score;not null;comment:最低得分/碰数要求" json:"min_score"` // 最低得分/碰数要求
|
||||||
|
DropQuantity int32 `gorm:"column:drop_quantity;not null;default:1;comment:单次抽中产出数量" json:"drop_quantity"` // 单次抽中产出数量
|
||||||
|
CostSnapshotCents int64 `gorm:"column:cost_snapshot_cents;not null;default:0;comment:奖品配置时成本价快照(分)" json:"cost_snapshot_cents"` // 奖品配置时成本价快照(分)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TableName ActivityRewardSettings's table name
|
// TableName ActivityRewardSettings's table name
|
||||||
|
|||||||
@ -21,6 +21,7 @@ type Products struct {
|
|||||||
CategoryID int64 `gorm:"column:category_id;comment:单一主分类ID(product_categories.id)" json:"category_id"` // 单一主分类ID(product_categories.id)
|
CategoryID int64 `gorm:"column:category_id;comment:单一主分类ID(product_categories.id)" json:"category_id"` // 单一主分类ID(product_categories.id)
|
||||||
ImagesJSON string `gorm:"column:images_json;comment:商品图片JSON(数组)" json:"images_json"` // 商品图片JSON(数组)
|
ImagesJSON string `gorm:"column:images_json;comment:商品图片JSON(数组)" json:"images_json"` // 商品图片JSON(数组)
|
||||||
Price int64 `gorm:"column:price;not null;comment:商品售价(分)" json:"price"` // 商品售价(分)
|
Price int64 `gorm:"column:price;not null;comment:商品售价(分)" json:"price"` // 商品售价(分)
|
||||||
|
CostPrice int64 `gorm:"column:cost_price;not null;default:0;comment:成本价(分)" json:"cost_price"` // 成本价(分)
|
||||||
Stock int64 `gorm:"column:stock;not null;comment:可售库存" json:"stock"` // 可售库存
|
Stock int64 `gorm:"column:stock;not null;comment:可售库存" json:"stock"` // 可售库存
|
||||||
Sales int64 `gorm:"column:sales;not null;comment:已售数量" json:"sales"` // 已售数量
|
Sales int64 `gorm:"column:sales;not null;comment:已售数量" json:"sales"` // 已售数量
|
||||||
Status int32 `gorm:"column:status;not null;default:1;comment:上下架状态:1上架 2下架" json:"status"` // 上下架状态:1上架 2下架
|
Status int32 `gorm:"column:status;not null;default:1;comment:上下架状态:1上架 2下架" json:"status"` // 上下架状态:1上架 2下架
|
||||||
|
|||||||
@ -232,6 +232,8 @@ type CreateRewardInput struct {
|
|||||||
// IsBoss 是否Boss奖励
|
// IsBoss 是否Boss奖励
|
||||||
IsBoss int32
|
IsBoss int32
|
||||||
MinScore int64
|
MinScore int64
|
||||||
|
// DropQuantity 单次抽中产出数量(默认1)
|
||||||
|
DropQuantity int32
|
||||||
}
|
}
|
||||||
|
|
||||||
type ModifyRewardInput struct {
|
type ModifyRewardInput struct {
|
||||||
@ -252,4 +254,6 @@ type ModifyRewardInput struct {
|
|||||||
// IsBoss 是否Boss奖励
|
// IsBoss 是否Boss奖励
|
||||||
IsBoss *int32
|
IsBoss *int32
|
||||||
MinScore *int64
|
MinScore *int64
|
||||||
|
// DropQuantity 单次抽中产出数量
|
||||||
|
DropQuantity *int32
|
||||||
}
|
}
|
||||||
|
|||||||
@ -168,8 +168,20 @@ func (s *service) ProcessOrderLottery(ctx context.Context, orderID int64) error
|
|||||||
var effectLogs []*model.ActivityDrawLogs // 需要处理道具卡效果的日志
|
var effectLogs []*model.ActivityDrawLogs // 需要处理道具卡效果的日志
|
||||||
|
|
||||||
// 无限赏模式下使用总数检测(因为inventory.RewardID=0)
|
// 无限赏模式下使用总数检测(因为inventory.RewardID=0)
|
||||||
// 如果已发放总数已达到开奖数量,说明已完成发放,跳过后续逻辑
|
// 计算期望发放总数(考虑 drop_quantity)
|
||||||
if invTotalCount >= dc {
|
expectedTotal := int64(0)
|
||||||
|
for _, lg := range existingLogs {
|
||||||
|
if rw, ok := rewardMap[lg.RewardID]; ok {
|
||||||
|
dq := int64(rw.DropQuantity)
|
||||||
|
if dq < 1 {
|
||||||
|
dq = 1
|
||||||
|
}
|
||||||
|
expectedTotal += dq
|
||||||
|
} else {
|
||||||
|
expectedTotal++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if invTotalCount >= expectedTotal && expectedTotal > 0 {
|
||||||
// s.logger.Info("奖励已全部发放,跳过重复发放", zap.Int64("order_id", orderID), zap.Int64("dc", dc), zap.Int64("invTotalCount", invTotalCount))
|
// s.logger.Info("奖励已全部发放,跳过重复发放", zap.Int64("order_id", orderID), zap.Int64("dc", dc), zap.Int64("invTotalCount", invTotalCount))
|
||||||
} else {
|
} else {
|
||||||
for i := int64(0); i < dc; i++ {
|
for i := int64(0); i < dc; i++ {
|
||||||
@ -178,19 +190,29 @@ func (s *service) ProcessOrderLottery(ctx context.Context, orderID int64) error
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// 统计该 RewardID 应得数量
|
rw := rewardMap[log.RewardID]
|
||||||
needed := int64(0)
|
if rw == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
dropQty := int64(rw.DropQuantity)
|
||||||
|
if dropQty < 1 {
|
||||||
|
dropQty = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// 统计该 RewardID 应得库存数量(命中次数 × 单次产出数量)
|
||||||
|
hitCount := int64(0)
|
||||||
for j := int64(0); j <= i; j++ {
|
for j := int64(0); j <= i; j++ {
|
||||||
if l, ok := logMap[j]; ok && l.RewardID == log.RewardID {
|
if l, ok := logMap[j]; ok && l.RewardID == log.RewardID {
|
||||||
needed++
|
hitCount++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
needed := hitCount * dropQty
|
||||||
|
|
||||||
// 检查是否需要发放
|
// 检查是否需要发放
|
||||||
if invCountMap[log.RewardID] < needed {
|
if invCountMap[log.RewardID] < needed {
|
||||||
rw := rewardMap[log.RewardID]
|
rewardIDRef := &log.RewardID
|
||||||
if rw != nil {
|
for q := int64(0); q < dropQty; q++ {
|
||||||
rewardIDRef := &log.RewardID
|
|
||||||
batchItems = append(batchItems, usersvc.BatchRewardItem{
|
batchItems = append(batchItems, usersvc.BatchRewardItem{
|
||||||
ProductID: rw.ProductID,
|
ProductID: rw.ProductID,
|
||||||
RewardID: rewardIDRef,
|
RewardID: rewardIDRef,
|
||||||
@ -198,12 +220,12 @@ func (s *service) ProcessOrderLottery(ctx context.Context, orderID int64) error
|
|||||||
ActivityID: aid,
|
ActivityID: aid,
|
||||||
Remark: productNameMap[rw.ProductID],
|
Remark: productNameMap[rw.ProductID],
|
||||||
})
|
})
|
||||||
invCountMap[log.RewardID]++ // 内存计数同步
|
}
|
||||||
|
invCountMap[log.RewardID] += dropQty // 内存计数同步
|
||||||
|
|
||||||
// 记录需要处理道具卡效果的日志
|
// 记录需要处理道具卡效果的日志
|
||||||
if act != nil && act.AllowItemCards && icID > 0 {
|
if act != nil && act.AllowItemCards && icID > 0 {
|
||||||
effectLogs = append(effectLogs, log)
|
effectLogs = append(effectLogs, log)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,6 +20,7 @@ func (s *service) CreateIssueRewards(ctx context.Context, issueID int64, rewards
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
productPriceMap := make(map[int64]int64)
|
productPriceMap := make(map[int64]int64)
|
||||||
|
productCostMap := make(map[int64]int64)
|
||||||
if len(productIDs) > 0 {
|
if len(productIDs) > 0 {
|
||||||
ids := make([]int64, 0, len(productIDs))
|
ids := make([]int64, 0, len(productIDs))
|
||||||
for id := range productIDs {
|
for id := range productIDs {
|
||||||
@ -31,15 +32,25 @@ func (s *service) CreateIssueRewards(ctx context.Context, issueID int64, rewards
|
|||||||
}
|
}
|
||||||
for _, p := range products {
|
for _, p := range products {
|
||||||
productPriceMap[p.ID] = p.Price
|
productPriceMap[p.ID] = p.Price
|
||||||
|
productCostMap[p.ID] = p.CostPrice
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, r := range rewards {
|
for _, r := range rewards {
|
||||||
|
dropQty := r.DropQuantity
|
||||||
|
if dropQty < 1 {
|
||||||
|
dropQty = 1
|
||||||
|
}
|
||||||
|
if dropQty > 100 {
|
||||||
|
dropQty = 100
|
||||||
|
}
|
||||||
item := &model.ActivityRewardSettings{
|
item := &model.ActivityRewardSettings{
|
||||||
IssueID: issueID,
|
IssueID: issueID,
|
||||||
ProductID: r.ProductID,
|
ProductID: r.ProductID,
|
||||||
PriceSnapshotCents: productPriceMap[r.ProductID],
|
PriceSnapshotCents: productPriceMap[r.ProductID],
|
||||||
PriceSnapshotAt: time.Now(),
|
PriceSnapshotAt: time.Now(),
|
||||||
|
CostSnapshotCents: productCostMap[r.ProductID],
|
||||||
|
DropQuantity: dropQty,
|
||||||
Weight: r.Weight,
|
Weight: r.Weight,
|
||||||
Quantity: r.Quantity,
|
Quantity: r.Quantity,
|
||||||
OriginalQty: r.OriginalQty,
|
OriginalQty: r.OriginalQty,
|
||||||
|
|||||||
@ -18,14 +18,17 @@ func (s *service) ModifyIssueReward(ctx context.Context, rewardID int64, in Modi
|
|||||||
if in.ProductID != nil {
|
if in.ProductID != nil {
|
||||||
item.ProductID = *in.ProductID
|
item.ProductID = *in.ProductID
|
||||||
priceSnapshot := int64(0)
|
priceSnapshot := int64(0)
|
||||||
|
costSnapshot := int64(0)
|
||||||
if *in.ProductID > 0 {
|
if *in.ProductID > 0 {
|
||||||
product, err := s.readDB.Products.WithContext(ctx).Where(s.readDB.Products.ID.Eq(*in.ProductID)).First()
|
product, err := s.readDB.Products.WithContext(ctx).Where(s.readDB.Products.ID.Eq(*in.ProductID)).First()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
priceSnapshot = product.Price
|
priceSnapshot = product.Price
|
||||||
|
costSnapshot = product.CostPrice
|
||||||
}
|
}
|
||||||
item.PriceSnapshotCents = priceSnapshot
|
item.PriceSnapshotCents = priceSnapshot
|
||||||
|
item.CostSnapshotCents = costSnapshot
|
||||||
item.PriceSnapshotAt = time.Now()
|
item.PriceSnapshotAt = time.Now()
|
||||||
}
|
}
|
||||||
if in.Weight != nil {
|
if in.Weight != nil {
|
||||||
@ -49,6 +52,16 @@ func (s *service) ModifyIssueReward(ctx context.Context, rewardID int64, in Modi
|
|||||||
if in.MinScore != nil {
|
if in.MinScore != nil {
|
||||||
item.MinScore = *in.MinScore
|
item.MinScore = *in.MinScore
|
||||||
}
|
}
|
||||||
|
if in.DropQuantity != nil {
|
||||||
|
dq := *in.DropQuantity
|
||||||
|
if dq < 1 {
|
||||||
|
dq = 1
|
||||||
|
}
|
||||||
|
if dq > 100 {
|
||||||
|
dq = 100
|
||||||
|
}
|
||||||
|
item.DropQuantity = dq
|
||||||
|
}
|
||||||
item.UpdatedAt = time.Now()
|
item.UpdatedAt = time.Now()
|
||||||
return s.writeDB.ActivityRewardSettings.WithContext(ctx).Save(item)
|
return s.writeDB.ActivityRewardSettings.WithContext(ctx).Save(item)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -128,6 +128,7 @@ type CreateProductInput struct {
|
|||||||
CategoryID int64
|
CategoryID int64
|
||||||
ImagesJSON string
|
ImagesJSON string
|
||||||
Price int64
|
Price int64
|
||||||
|
CostPrice int64
|
||||||
Stock int64
|
Stock int64
|
||||||
Status int32
|
Status int32
|
||||||
Description string
|
Description string
|
||||||
@ -139,6 +140,7 @@ type ModifyProductInput struct {
|
|||||||
CategoryID *int64
|
CategoryID *int64
|
||||||
ImagesJSON *string
|
ImagesJSON *string
|
||||||
Price *int64
|
Price *int64
|
||||||
|
CostPrice *int64
|
||||||
Stock *int64
|
Stock *int64
|
||||||
Status *int32
|
Status *int32
|
||||||
Description *string
|
Description *string
|
||||||
@ -192,7 +194,7 @@ func (s *service) CreateProduct(ctx context.Context, in CreateProductInput) (*mo
|
|||||||
if in.ShowInMiniapp != nil {
|
if in.ShowInMiniapp != nil {
|
||||||
showInMiniapp = *in.ShowInMiniapp
|
showInMiniapp = *in.ShowInMiniapp
|
||||||
}
|
}
|
||||||
m := &model.Products{Name: in.Name, CategoryID: in.CategoryID, ImagesJSON: normalizeJSON(in.ImagesJSON), Price: in.Price, Stock: in.Stock, Status: in.Status, Description: in.Description, ShowInMiniapp: showInMiniapp}
|
m := &model.Products{Name: in.Name, CategoryID: in.CategoryID, ImagesJSON: normalizeJSON(in.ImagesJSON), Price: in.Price, CostPrice: in.CostPrice, Stock: in.Stock, Status: in.Status, Description: in.Description, ShowInMiniapp: showInMiniapp}
|
||||||
if err := s.writeDB.Products.WithContext(ctx).Create(m); err != nil {
|
if err := s.writeDB.Products.WithContext(ctx).Create(m); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -214,6 +216,9 @@ func (s *service) ModifyProduct(ctx context.Context, id int64, in ModifyProductI
|
|||||||
if in.Price != nil {
|
if in.Price != nil {
|
||||||
set["price"] = *in.Price
|
set["price"] = *in.Price
|
||||||
}
|
}
|
||||||
|
if in.CostPrice != nil {
|
||||||
|
set["cost_price"] = *in.CostPrice
|
||||||
|
}
|
||||||
if in.Stock != nil {
|
if in.Stock != nil {
|
||||||
set["stock"] = *in.Stock
|
set["stock"] = *in.Stock
|
||||||
}
|
}
|
||||||
|
|||||||
8
migrations/20260323_fragment_cost_and_drop_qty.sql
Normal file
8
migrations/20260323_fragment_cost_and_drop_qty.sql
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
-- 1. products 表新增成本价
|
||||||
|
ALTER TABLE products
|
||||||
|
ADD COLUMN cost_price BIGINT NOT NULL DEFAULT 0 COMMENT '成本价(分)';
|
||||||
|
|
||||||
|
-- 2. activity_reward_settings 新增单次产出数量 + 成本价快照
|
||||||
|
ALTER TABLE activity_reward_settings
|
||||||
|
ADD COLUMN drop_quantity INT NOT NULL DEFAULT 1 COMMENT '单次抽中产出数量',
|
||||||
|
ADD COLUMN cost_snapshot_cents BIGINT NOT NULL DEFAULT 0 COMMENT '奖品配置时成本价快照(分)';
|
||||||
Loading…
x
Reference in New Issue
Block a user