后端: - StatsOverview/StatsDailyItem 新增 cost/profit 字段 - 新增 calcPaidByPriceDraw 三路收入分类(抽奖/对对碰/一番赏) - 新增 calcCostByInventory 成本计算(含道具卡倍数) - 修复成本统计未过滤 source_type 导致直播间免费发奖资产被错误计入 - remark.go 新增 PkgID 解析支持一番赏订单 前端: - 渠道统计弹窗新增"总成本"和"盈亏"卡片 - 趋势图新增"盈亏分析"Tab Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
146 lines
5.4 KiB
Go
146 lines
5.4 KiB
Go
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)").
|
||
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("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("【道具卡使用详情】无道具卡使用记录")
|
||
}
|
||
}
|