Compare commits
2 Commits
5dc9f034c8
...
0e202fabd8
| Author | SHA1 | Date | |
|---|---|---|---|
| 0e202fabd8 | |||
| 1cf57657ca |
@ -19,7 +19,7 @@ type userProfitLossRequest struct {
|
|||||||
type userProfitLossPoint struct {
|
type userProfitLossPoint struct {
|
||||||
Date string `json:"date"`
|
Date string `json:"date"`
|
||||||
Cost int64 `json:"cost"` // 累计投入(已支付-已退款)
|
Cost int64 `json:"cost"` // 累计投入(已支付-已退款)
|
||||||
Value int64 `json:"value"` // 累计产出(当前资产快照)
|
Value int64 `json:"value"` // 累计产出(订单链路商品成本)
|
||||||
Profit int64 `json:"profit"` // 净盈亏
|
Profit int64 `json:"profit"` // 净盈亏
|
||||||
Ratio float64 `json:"ratio"` // 盈亏比
|
Ratio float64 `json:"ratio"` // 盈亏比
|
||||||
Breakdown struct {
|
Breakdown struct {
|
||||||
@ -79,7 +79,7 @@ func (h *handler) GetUserProfitLossTrend() core.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 1. 获取当前资产快照(实时余额)---
|
// --- 1. 获取当前资产快照(用于积分/道具卡/优惠券分项展示)---
|
||||||
var curAssets struct {
|
var curAssets struct {
|
||||||
Points int64
|
Points int64
|
||||||
Products int64
|
Products int64
|
||||||
@ -87,24 +87,46 @@ func (h *handler) GetUserProfitLossTrend() core.HandlerFunc {
|
|||||||
Coupons int64
|
Coupons int64
|
||||||
}
|
}
|
||||||
_ = h.repo.GetDbR().Raw("SELECT COALESCE(SUM(points), 0) FROM user_points WHERE user_id = ? AND (valid_end IS NULL OR valid_end > NOW())", userID).Scan(&curAssets.Points).Error
|
_ = h.repo.GetDbR().Raw("SELECT COALESCE(SUM(points), 0) FROM user_points WHERE user_id = ? AND (valid_end IS NULL OR valid_end > NOW())", userID).Scan(&curAssets.Points).Error
|
||||||
_ = h.repo.GetDbR().Raw(`
|
|
||||||
SELECT CAST(COALESCE(SUM(
|
|
||||||
COALESCE(NULLIF(ui.value_cents, 0), ars.price_snapshot_cents, p.price, 0)
|
|
||||||
* GREATEST(COALESCE(sic.reward_multiplier_x1000, 1000), 1000) / 1000
|
|
||||||
), 0) AS SIGNED)
|
|
||||||
FROM user_inventory ui
|
|
||||||
LEFT JOIN products p ON p.id = ui.product_id
|
|
||||||
LEFT JOIN activity_reward_settings ars ON ars.id = ui.reward_id
|
|
||||||
LEFT JOIN orders o ON o.id = ui.order_id
|
|
||||||
LEFT JOIN user_item_cards uic ON uic.id = o.item_card_id
|
|
||||||
LEFT JOIN system_item_cards sic ON sic.id = uic.card_id
|
|
||||||
WHERE ui.user_id = ? AND ui.status IN (1, 3)
|
|
||||||
AND COALESCE(ui.remark, '') NOT LIKE '%%void%%'
|
|
||||||
AND NOT (ui.status = 3 AND (COALESCE(ui.remark, '') LIKE '%%redeemed_points%%' OR COALESCE(ui.remark, '') LIKE '%%batch_redeemed%%'))
|
|
||||||
`, userID).Scan(&curAssets.Products).Error
|
|
||||||
_ = h.repo.GetDbR().Raw("SELECT COALESCE(SUM(sc.price), 0) FROM user_item_cards uic LEFT JOIN system_item_cards sc ON sc.id = uic.card_id WHERE uic.user_id = ? AND uic.status = 1", userID).Scan(&curAssets.Cards).Error
|
_ = h.repo.GetDbR().Raw("SELECT COALESCE(SUM(sc.price), 0) FROM user_item_cards uic LEFT JOIN system_item_cards sc ON sc.id = uic.card_id WHERE uic.user_id = ? AND uic.status = 1", userID).Scan(&curAssets.Cards).Error
|
||||||
_ = h.repo.GetDbR().Raw("SELECT COALESCE(SUM(balance_amount), 0) FROM user_coupons WHERE user_id = ? AND status = 1", userID).Scan(&curAssets.Coupons).Error
|
_ = h.repo.GetDbR().Raw("SELECT COALESCE(SUM(balance_amount), 0) FROM user_coupons WHERE user_id = ? AND status = 1", userID).Scan(&curAssets.Coupons).Error
|
||||||
totalAssetValue := curAssets.Points + curAssets.Products + curAssets.Cards + curAssets.Coupons
|
|
||||||
|
// --- 2. 获取订单链路商品产出(与 spending 保持一致,仅统计普通活动)---
|
||||||
|
type prizeRow struct {
|
||||||
|
CreatedAt time.Time
|
||||||
|
PrizeValue int64
|
||||||
|
}
|
||||||
|
var basePrizeValue int64 = 0
|
||||||
|
_ = h.repo.GetDbR().Raw(`
|
||||||
|
SELECT CAST(COALESCE(SUM(COALESCE(products.cost_price, 0) * (
|
||||||
|
COALESCE(NULLIF(activity_reward_settings.drop_quantity, 0), 1) +
|
||||||
|
CASE WHEN user_item_cards.used_draw_log_id = activity_draw_logs.id AND system_item_cards.effect_type = 1 AND system_item_cards.reward_multiplier_x1000 >= 2000 THEN 1 ELSE 0 END
|
||||||
|
)), 0) AS SIGNED) as prize_value
|
||||||
|
FROM activity_draw_logs
|
||||||
|
JOIN activity_issues ON activity_issues.id = activity_draw_logs.issue_id
|
||||||
|
LEFT JOIN activity_reward_settings ON activity_reward_settings.id = activity_draw_logs.reward_id
|
||||||
|
LEFT JOIN products ON products.id = activity_reward_settings.product_id
|
||||||
|
LEFT JOIN orders ON orders.id = activity_draw_logs.order_id
|
||||||
|
LEFT JOIN user_item_cards ON user_item_cards.id = orders.item_card_id
|
||||||
|
LEFT JOIN system_item_cards ON system_item_cards.id = user_item_cards.card_id
|
||||||
|
WHERE orders.user_id = ? AND orders.status = 2 AND orders.created_at < ?
|
||||||
|
`, userID, start).Scan(&basePrizeValue).Error
|
||||||
|
|
||||||
|
var prizeRows []prizeRow
|
||||||
|
_ = h.repo.GetDbR().Raw(`
|
||||||
|
SELECT orders.created_at,
|
||||||
|
CAST(COALESCE(products.cost_price, 0) * (
|
||||||
|
COALESCE(NULLIF(activity_reward_settings.drop_quantity, 0), 1) +
|
||||||
|
CASE WHEN user_item_cards.used_draw_log_id = activity_draw_logs.id AND system_item_cards.effect_type = 1 AND system_item_cards.reward_multiplier_x1000 >= 2000 THEN 1 ELSE 0 END
|
||||||
|
) AS SIGNED) as prize_value
|
||||||
|
FROM activity_draw_logs
|
||||||
|
JOIN activity_issues ON activity_issues.id = activity_draw_logs.issue_id
|
||||||
|
LEFT JOIN activity_reward_settings ON activity_reward_settings.id = activity_draw_logs.reward_id
|
||||||
|
LEFT JOIN products ON products.id = activity_reward_settings.product_id
|
||||||
|
LEFT JOIN orders ON orders.id = activity_draw_logs.order_id
|
||||||
|
LEFT JOIN user_item_cards ON user_item_cards.id = orders.item_card_id
|
||||||
|
LEFT JOIN system_item_cards ON system_item_cards.id = user_item_cards.card_id
|
||||||
|
WHERE orders.user_id = ? AND orders.status = 2 AND orders.created_at BETWEEN ? AND ?
|
||||||
|
`, userID, start, end).Scan(&prizeRows).Error
|
||||||
|
|
||||||
// --- 2. 获取订单数据(仅 status=2 已支付) ---
|
// --- 2. 获取订单数据(仅 status=2 已支付) ---
|
||||||
// 注意:为了计算累计趋势,我们需要获取 start 之前的所有已支付订单总额作为基数
|
// 注意:为了计算累计趋势,我们需要获取 start 之前的所有已支付订单总额作为基数
|
||||||
@ -183,6 +205,7 @@ func (h *handler) GetUserProfitLossTrend() core.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cumulativeCost := baseCost
|
cumulativeCost := baseCost
|
||||||
|
cumulativeValue := basePrizeValue
|
||||||
|
|
||||||
for i, b := range buckets {
|
for i, b := range buckets {
|
||||||
p := &list[i]
|
p := &list[i]
|
||||||
@ -207,13 +230,19 @@ func (h *handler) GetUserProfitLossTrend() core.HandlerFunc {
|
|||||||
}
|
}
|
||||||
p.Cost = cumulativeCost
|
p.Cost = cumulativeCost
|
||||||
|
|
||||||
// 产出值:当前资产是一个存量值。
|
var periodValueDelta int64 = 0
|
||||||
// 理想逻辑是回溯各时间点的余额,简化逻辑下:
|
for _, prize := range prizeRows {
|
||||||
// 如果该点还没有在该范围内发生过任何投入(且没有基数),则显示0;否则显示当前快照值。
|
if inBucket(prize.CreatedAt, b) {
|
||||||
// 这里我们统一显示当前快照,但在前端图表上它会是一条水平线或阶梯线。
|
periodValueDelta += prize.PrizeValue
|
||||||
p.Value = totalAssetValue
|
}
|
||||||
|
}
|
||||||
|
cumulativeValue += periodValueDelta
|
||||||
|
if cumulativeValue < 0 {
|
||||||
|
cumulativeValue = 0
|
||||||
|
}
|
||||||
|
p.Value = cumulativeValue
|
||||||
|
p.Breakdown.Products = cumulativeValue
|
||||||
p.Breakdown.Points = curAssets.Points
|
p.Breakdown.Points = curAssets.Points
|
||||||
p.Breakdown.Products = curAssets.Products
|
|
||||||
p.Breakdown.Cards = curAssets.Cards
|
p.Breakdown.Cards = curAssets.Cards
|
||||||
p.Breakdown.Coupons = curAssets.Coupons
|
p.Breakdown.Coupons = curAssets.Coupons
|
||||||
|
|
||||||
@ -257,24 +286,39 @@ func (h *handler) GetUserProfitLossTrend() core.HandlerFunc {
|
|||||||
finalNetCost = 0
|
finalNetCost = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var totalValue int64 = 0
|
||||||
|
_ = h.repo.GetDbR().Raw(`
|
||||||
|
SELECT CAST(COALESCE(SUM(COALESCE(products.cost_price, 0) * (
|
||||||
|
COALESCE(NULLIF(activity_reward_settings.drop_quantity, 0), 1) +
|
||||||
|
CASE WHEN user_item_cards.used_draw_log_id = activity_draw_logs.id AND system_item_cards.effect_type = 1 AND system_item_cards.reward_multiplier_x1000 >= 2000 THEN 1 ELSE 0 END
|
||||||
|
)), 0) AS SIGNED) as prize_value
|
||||||
|
FROM activity_draw_logs
|
||||||
|
JOIN activity_issues ON activity_issues.id = activity_draw_logs.issue_id
|
||||||
|
LEFT JOIN activity_reward_settings ON activity_reward_settings.id = activity_draw_logs.reward_id
|
||||||
|
LEFT JOIN products ON products.id = activity_reward_settings.product_id
|
||||||
|
LEFT JOIN orders ON orders.id = activity_draw_logs.order_id
|
||||||
|
LEFT JOIN user_item_cards ON user_item_cards.id = orders.item_card_id
|
||||||
|
LEFT JOIN system_item_cards ON system_item_cards.id = user_item_cards.card_id
|
||||||
|
WHERE orders.user_id = ? AND orders.status = 2
|
||||||
|
`, userID).Scan(&totalValue).Error
|
||||||
|
|
||||||
resp := userProfitLossResponse{
|
resp := userProfitLossResponse{
|
||||||
Granularity: gran,
|
Granularity: gran,
|
||||||
List: list,
|
List: list,
|
||||||
}
|
}
|
||||||
resp.Summary.TotalCost = finalNetCost
|
resp.Summary.TotalCost = finalNetCost
|
||||||
resp.Summary.TotalValue = totalAssetValue
|
resp.Summary.TotalValue = totalValue
|
||||||
resp.Summary.TotalProfit = finalNetCost - totalAssetValue
|
resp.Summary.TotalProfit = finalNetCost - totalValue
|
||||||
if totalAssetValue > 0 {
|
if totalValue > 0 {
|
||||||
resp.Summary.AvgRatio = float64(finalNetCost) / float64(totalAssetValue)
|
resp.Summary.AvgRatio = float64(finalNetCost) / float64(totalValue)
|
||||||
} else if finalNetCost > 0 {
|
} else if finalNetCost > 0 {
|
||||||
resp.Summary.AvgRatio = 99.9
|
resp.Summary.AvgRatio = 99.9
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.CurrentAssets.Points = curAssets.Points
|
resp.CurrentAssets.Points = curAssets.Points
|
||||||
resp.CurrentAssets.Products = curAssets.Products
|
resp.CurrentAssets.Products = totalValue
|
||||||
resp.CurrentAssets.Cards = curAssets.Cards
|
resp.CurrentAssets.Cards = curAssets.Cards
|
||||||
resp.CurrentAssets.Coupons = curAssets.Coupons
|
resp.CurrentAssets.Coupons = curAssets.Coupons
|
||||||
resp.CurrentAssets.Total = totalAssetValue
|
resp.CurrentAssets.Total = totalValue + curAssets.Points + curAssets.Cards + curAssets.Coupons
|
||||||
|
|
||||||
ctx.Payload(resp)
|
ctx.Payload(resp)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package user
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -14,18 +15,30 @@ type ProductInfo struct {
|
|||||||
Price int64 `json:"price"`
|
Price int64 `json:"price"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ShipmentAddressInfo struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Phone string `json:"phone"`
|
||||||
|
Province string `json:"province"`
|
||||||
|
City string `json:"city"`
|
||||||
|
District string `json:"district"`
|
||||||
|
Detail string `json:"detail"`
|
||||||
|
IsDefault bool `json:"is_default"`
|
||||||
|
}
|
||||||
|
|
||||||
type ShipmentGroup struct {
|
type ShipmentGroup struct {
|
||||||
ExpressCode string `json:"express_code"`
|
ExpressCode string `json:"express_code"`
|
||||||
ExpressNo string `json:"express_no"`
|
ExpressNo string `json:"express_no"`
|
||||||
BatchNo string `json:"batch_no"`
|
BatchNo string `json:"batch_no"`
|
||||||
Status int32 `json:"status"`
|
Status int32 `json:"status"`
|
||||||
Count int64 `json:"count"`
|
Count int64 `json:"count"`
|
||||||
ShippedAt *time.Time `json:"shipped_at,omitempty"`
|
ShippedAt *time.Time `json:"shipped_at,omitempty"`
|
||||||
ReceivedAt *time.Time `json:"received_at,omitempty"`
|
ReceivedAt *time.Time `json:"received_at,omitempty"`
|
||||||
CreatedAt *time.Time `json:"created_at,omitempty"` // 发货申请创建时间,用于前端判断48小时撤销限制
|
CreatedAt *time.Time `json:"created_at,omitempty"` // 发货申请创建时间,用于前端判断48小时撤销限制
|
||||||
InventoryIDs []int64 `json:"inventory_ids"`
|
InventoryIDs []int64 `json:"inventory_ids"`
|
||||||
ProductIDs []int64 `json:"product_ids"`
|
ProductIDs []int64 `json:"product_ids"`
|
||||||
Products []ProductInfo `json:"products"`
|
Products []ProductInfo `json:"products"`
|
||||||
|
Address *ShipmentAddressInfo `json:"address,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) ListUserShipmentGroups(ctx context.Context, userID int64, page, pageSize int) (items []*ShipmentGroup, total int64, err error) {
|
func (s *service) ListUserShipmentGroups(ctx context.Context, userID int64, page, pageSize int) (items []*ShipmentGroup, total int64, err error) {
|
||||||
@ -55,6 +68,7 @@ func (s *service) ListUserShipmentGroups(ctx context.Context, userID int64, page
|
|||||||
shippedAt *time.Time
|
shippedAt *time.Time
|
||||||
receivedAt *time.Time
|
receivedAt *time.Time
|
||||||
createdAt *time.Time // 最早的创建时间
|
createdAt *time.Time // 最早的创建时间
|
||||||
|
addressID int64
|
||||||
inv []int64
|
inv []int64
|
||||||
pid []int64
|
pid []int64
|
||||||
}
|
}
|
||||||
@ -99,11 +113,47 @@ func (s *service) ListUserShipmentGroups(ctx context.Context, userID int64, page
|
|||||||
a.createdAt = &t
|
a.createdAt = &t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if a.addressID == 0 && r.AddressID > 0 {
|
||||||
|
a.addressID = r.AddressID
|
||||||
|
}
|
||||||
a.inv = append(a.inv, r.InventoryID)
|
a.inv = append(a.inv, r.InventoryID)
|
||||||
if r.ProductID > 0 {
|
if r.ProductID > 0 {
|
||||||
a.pid = append(a.pid, r.ProductID)
|
a.pid = append(a.pid, r.ProductID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addressIDs := make([]int64, 0, len(m))
|
||||||
|
addressIDSet := make(map[int64]struct{}, len(m))
|
||||||
|
for _, a := range m {
|
||||||
|
if a.addressID > 0 {
|
||||||
|
if _, ok := addressIDSet[a.addressID]; !ok {
|
||||||
|
addressIDSet[a.addressID] = struct{}{}
|
||||||
|
addressIDs = append(addressIDs, a.addressID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Slice(addressIDs, func(i, j int) bool { return addressIDs[i] < addressIDs[j] })
|
||||||
|
|
||||||
|
addressMap := make(map[int64]*ShipmentAddressInfo, len(addressIDs))
|
||||||
|
if len(addressIDs) > 0 {
|
||||||
|
addresses, addrErr := s.readDB.UserAddresses.WithContext(ctx).ReadDB().Where(s.readDB.UserAddresses.ID.In(addressIDs...)).Find()
|
||||||
|
if addrErr != nil {
|
||||||
|
return nil, 0, addrErr
|
||||||
|
}
|
||||||
|
for _, addr := range addresses {
|
||||||
|
addressMap[addr.ID] = &ShipmentAddressInfo{
|
||||||
|
ID: addr.ID,
|
||||||
|
Name: addr.Name,
|
||||||
|
Phone: addr.Mobile,
|
||||||
|
Province: addr.Province,
|
||||||
|
City: addr.City,
|
||||||
|
District: addr.District,
|
||||||
|
Detail: addr.Address,
|
||||||
|
IsDefault: addr.IsDefault == 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
items = make([]*ShipmentGroup, 0, len(m))
|
items = make([]*ShipmentGroup, 0, len(m))
|
||||||
for _, k := range order {
|
for _, k := range order {
|
||||||
a := m[k]
|
a := m[k]
|
||||||
@ -139,6 +189,7 @@ func (s *service) ListUserShipmentGroups(ctx context.Context, userID int64, page
|
|||||||
InventoryIDs: a.inv,
|
InventoryIDs: a.inv,
|
||||||
ProductIDs: a.pid,
|
ProductIDs: a.pid,
|
||||||
Products: products,
|
Products: products,
|
||||||
|
Address: addressMap[a.addressID],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return items, total, nil
|
return items, total, nil
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user