win 8d1eef2f7f fix(channel): 修复渠道统计GMV重复计数和商城直购误计入
1. 排除商城直购(source_type=1):GMV和成本过滤条件从IN(1,2,3,4)改为IN(2,3,4)
2. 排除次卡免费使用订单(actual_amount=0):避免购买次卡和使用次卡双重计入GMV
   - source_type=4 一番赏使用次卡:1578单 44032元重复
   - source_type=3 对对碰使用次卡:422单 7042元重复
   - 合计去除51074元虚增GMV(29.1%)
3. 成本过滤条件同步修正:source_type IN(2,3,4),total_amount>0

修正后:GMV从175600降至124527元,毛利率从37.4%回到真实的11.8%
2026-03-16 21:41:39 +08:00

148 lines
5.6 KiB
Go
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 main
import (
"fmt"
"strings"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
dsn := "root:bindbox2025kdy@tcp(150.158.78.154:3306)/dev_game?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
fmt.Println("连接失败:", err)
return
}
channelID := 3
// 1. 成本统计(复用 calcCostByInventory 的 SQL 逻辑)
type costRow struct {
UnitCost int64
Multiplier int64
}
var rows []costRow
db.Table("user_inventory").
Select(`
COALESCE(NULLIF(user_inventory.value_cents, 0), activity_reward_settings.price_snapshot_cents, products.price, 0) AS unit_cost,
GREATEST(COALESCE(system_item_cards.reward_multiplier_x1000, 1000), 1000) AS multiplier
`).
Joins("JOIN users ON users.id = user_inventory.user_id").
Joins("LEFT JOIN orders ON orders.id = user_inventory.order_id").
Joins("LEFT JOIN activity_reward_settings ON activity_reward_settings.id = user_inventory.reward_id").
Joins("LEFT JOIN products ON products.id = user_inventory.product_id").
Joins("LEFT JOIN user_item_cards ON user_item_cards.id = orders.item_card_id").
Joins("LEFT JOIN system_item_cards ON system_item_cards.id = user_item_cards.card_id").
Where("users.channel_id = ? AND users.deleted_at IS NULL", channelID).
Where("user_inventory.status IN ?", []int{1, 3}).
Where("COALESCE(user_inventory.remark, '') NOT LIKE ?", "%void%").
Where("(orders.status = 2 OR user_inventory.order_id = 0 OR user_inventory.order_id IS NULL)").
Where("(orders.source_type IN (1,2,3,4) OR user_inventory.order_id = 0 OR user_inventory.order_id IS NULL)").
Where("(orders.actual_amount > 0 OR user_inventory.order_id = 0 OR user_inventory.order_id IS NULL)").
Scan(&rows)
var totalCostBase, totalCostFinal int64
var withCard, withoutCard int
for _, r := range rows {
cost := r.UnitCost * r.Multiplier / 1000
totalCostFinal += cost
totalCostBase += r.UnitCost
if r.Multiplier > 1000 {
withCard++
} else {
withoutCard++
}
}
fmt.Println("========================================")
fmt.Printf("渠道 %d 盈亏分析\n", channelID)
fmt.Println("========================================")
fmt.Println()
fmt.Println("【成本统计】")
fmt.Printf(" 资产记录数: %d 条\n", len(rows))
fmt.Printf(" 无道具卡: %d 条\n", withoutCard)
fmt.Printf(" 有道具卡: %d 条(成本×倍数)\n", withCard)
fmt.Printf(" 基础成本: %d 分 = %.2f 元\n", totalCostBase, float64(totalCostBase)/100)
fmt.Printf(" 含卡成本: %d 分 = %.2f 元\n", totalCostFinal, float64(totalCostFinal)/100)
if totalCostBase > 0 {
fmt.Printf(" 道具卡加成: +%.2f 元 (%.1f%%)\n",
float64(totalCostFinal-totalCostBase)/100,
float64(totalCostFinal-totalCostBase)/float64(totalCostBase)*100)
}
fmt.Println()
// 2. 收入统计(已有的 price_draw × count
// 简化:直接用 SQL 统计 actual_amount 作为对比参考
type amountResult struct {
TotalCents int64
}
var ar amountResult
orderFilter := "users.channel_id = ? AND users.deleted_at IS NULL AND orders.status = 2 AND orders.actual_amount > 0 AND orders.source_type IN (1,2,3,4) AND (orders.ext_order_id = '' OR orders.ext_order_id IS NULL)"
db.Table("orders").
Joins("JOIN users ON users.id = orders.user_id").
Select("COALESCE(SUM(orders.actual_amount), 0) as total_cents").
Where(orderFilter, channelID).
Scan(&ar)
fmt.Println("【收入参考 (actual_amount)】")
fmt.Printf(" 实付金额: %d 分 = %.2f 元\n", ar.TotalCents, float64(ar.TotalCents)/100)
fmt.Println()
// 3. 盈亏
profit := ar.TotalCents - totalCostFinal
fmt.Println("【盈亏】")
fmt.Printf(" 收入(实付): %.2f 元\n", float64(ar.TotalCents)/100)
fmt.Printf(" 成本(含卡): %.2f 元\n", float64(totalCostFinal)/100)
fmt.Println(strings.Repeat("-", 40))
fmt.Printf(" 盈亏: %.2f 元\n", float64(profit)/100)
if profit > 0 {
fmt.Printf(" 状态: 盈利 ✅\n")
} else if profit < 0 {
fmt.Printf(" 状态: 亏损 ❌\n")
} else {
fmt.Printf(" 状态: 持平\n")
}
fmt.Println()
// 4. 道具卡详情
type cardDetail struct {
CardName string
Multiplier int64
Count int64
}
var cards []cardDetail
db.Table("user_inventory").
Select(`
system_item_cards.name as card_name,
system_item_cards.reward_multiplier_x1000 as multiplier,
COUNT(*) as count
`).
Joins("JOIN users ON users.id = user_inventory.user_id").
Joins("LEFT JOIN orders ON orders.id = user_inventory.order_id").
Joins("LEFT JOIN user_item_cards ON user_item_cards.id = orders.item_card_id").
Joins("LEFT JOIN system_item_cards ON system_item_cards.id = user_item_cards.card_id").
Where("users.channel_id = ? AND users.deleted_at IS NULL", channelID).
Where("user_inventory.status IN ?", []int{1, 3}).
Where("COALESCE(user_inventory.remark, '') NOT LIKE ?", "%void%").
Where("(orders.status = 2 OR user_inventory.order_id = 0 OR user_inventory.order_id IS NULL)").
Where("(orders.source_type IN (1,2,3,4) OR user_inventory.order_id = 0 OR user_inventory.order_id IS NULL)").
Where("(orders.actual_amount > 0 OR user_inventory.order_id = 0 OR user_inventory.order_id IS NULL)").
Where("system_item_cards.id IS NOT NULL").
Group("system_item_cards.id").
Scan(&cards)
if len(cards) > 0 {
fmt.Println("【道具卡使用详情】")
fmt.Printf("%-20s %-10s %-10s\n", "卡名", "倍数", "次数")
fmt.Println(strings.Repeat("-", 40))
for _, c := range cards {
fmt.Printf("%-20s ×%.1f %-10d\n", c.CardName, float64(c.Multiplier)/1000, c.Count)
}
} else {
fmt.Println("【道具卡使用详情】无道具卡使用记录")
}
}