5.4 KiB
5.4 KiB
Phase 1: Core P&L Functions - Context
Gathered: 2026-03-21 Status: Ready for planning
## Phase Boundary在 internal/service/finance/ 包下新增两个函数 QueryUserProfitLoss 和 QueryActivityProfitLoss,接收可选参数(资产类型、维度 ID、时间范围),返回含汇总 + 明细的 ProfitLossResult。正确处理 game-pass 互斥、退款排除、已作废库存排除、历史数据兼容等边界情况。
Revenue Attribution Rules
- D-01: 一个订单只对应一个活动(1:1 关系),不需要比例分摊逻辑(跳过 dashboard 中的 two-level subquery 方案)
- D-02: Game-pass 收入按 draw_count × activity_unit_price 计算,每个活动独立计算
- D-03: 用户维度直接汇总用户所有订单,不做跨活动分摊
Function Signature Design
- D-04: 两个独立的参数结构体:
UserProfitLossParams和ActivityProfitLossParams(不共享) - D-05: 返回
(*ProfitLossResult, error)— Go 标准模式,error 时 result 为 nil - D-06: ProfitLossResult 包含汇总(TotalResult)+ 明细切片(
[]ProfitLossDetail,每个元素含 UserID/ActivityID 字段) - D-07: 参数全部可选:空 []int64 = 统计全部,nil time = 不限时间,AssetType=0 = 全部类型
Cost Source Mapping
- D-08: 成本数据分布在多张表:user_inventory(实物/道具卡)、user_points_ledger(积分)、user_coupon_ledger(优惠券)、fragment_synthesis_logs(碎片,Phase 2)
- D-09: 实物商品/道具卡成本以
user_inventory.value_cents为准(单一真相源),不需要 fallback chain - D-10: 积分通过 system_configs 表中的固定汇率换算为金额(如 100积分 = 1元)
- D-11: 优惠券成本 = 优惠券面值(discount_amount)
Claude's Discretion
- 具体 SQL 查询结构和 GORM 调用方式
- ProfitLossDetail 内部字段的精确命名
- fan-out 查询的拆分粒度和合并策略
- 单元测试的具体用例选择
- 订单与活动是 1:1 关系,简化了活动维度的收入归属查询(不需要 dashboard 中复杂的双层子查询)
- 复用
finance.ClassifyOrderSpending做 game-pass / 现金收入分类,保持与 dashboard 计算一致 - 积分汇率存在 system_configs KV 表中,需要在查询时动态读取
<canonical_refs>
Canonical References
Finance primitives (MUST reuse)
internal/service/finance/profit_metrics.go— ClassifyOrderSpending, IsGamePassOrder, ComputeGamePassValue, ComputePrizeCostWithMultiplier, ComputeProfitinternal/service/finance/profit_metrics_test.go— Existing test patterns for finance functions
Existing dashboard implementations (reference patterns, don't copy)
internal/api/admin/dashboard_activity.go— Activity-level P&L query pattern, cost query with item-card multiplier, game-pass value calculationinternal/api/admin/dashboard_user_spending.go— User-dimension spending aggregation patterninternal/api/admin/users_profit_loss.go— User P&L trend, per-user details
Data models
internal/repository/mysql/model/*.gen.go— GORM models for orders, user_inventory, user_points_ledger, user_coupon_ledger, system_configs, activitiesinternal/repository/mysql/mysql.go— Repo interface, GetDbR()/GetDbW() split
Research findings
.planning/research/PITFALLS.md— 6 critical pitfalls with prevention strategies.planning/research/STACK.md— Query patterns and SQLite test compatibility notes.planning/research/FEATURES.md— Feature prioritization and dependency map
</canonical_refs>
<code_context>
Existing Code Insights
Reusable Assets
finance.ClassifyOrderSpending()— Unified spending classification (game-pass vs cash)finance.IsGamePassOrder()— Three-condition game-pass detectionfinance.ComputeGamePassValue()— draw_count × activity_pricefinance.ComputePrizeCostWithMultiplier()— Base cost × item-card multiplierfinance.ComputeProfit()— profit + profit_rate calculationfinance.NormalizeMultiplierX1000()— Multiplier normalization
Established Patterns
- Service constructor:
New(logger, db) → Servicewith injected logger and repo - Fan-out queries: Multiple independent
db.Table().Scan()calls, merge in Go viamap[int64]*Result - Read-only routing:
repo.GetDbR()for all read operations - CAST(AS SIGNED): Required for any SUM containing division (MySQL returns Decimal)
- Void exclusion:
user_inventory.status IN (1, 3)ANDremark NOT LIKE '%void%' - Refund exclusion:
orders.status = 2with legacy escape:OR order_id = 0 OR order_id IS NULL
Integration Points
- Constructor receives
mysql.Repo— usesGetDbR()only - Constructor receives
logger.CustomLogger— project-standard logger - Result types defined in same package — callers import
finance.ProfitLossResult - system_configs table for points exchange rate lookup
</code_context>
## Deferred Ideas- Per-asset-type breakdown (Phase 2) — struct field defined here as empty slice, populated in Phase 2
- Fragment synthesis cost integration (Phase 2) — AST-03
- Redis caching wrapper (v2)
- Admin API endpoints for frontend (v2)
Phase: 01-core-pnl-functions Context gathered: 2026-03-21