docs: gather phase 1 context
This commit is contained in:
parent
e3b0ab7cca
commit
e0097f50c8
113
.planning/phases/01-core-pnl-functions/1-CONTEXT.md
Normal file
113
.planning/phases/01-core-pnl-functions/1-CONTEXT.md
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
# Phase 1: Core P&L Functions - Context
|
||||||
|
|
||||||
|
**Gathered:** 2026-03-21
|
||||||
|
**Status:** Ready for planning
|
||||||
|
|
||||||
|
<domain>
|
||||||
|
## Phase Boundary
|
||||||
|
|
||||||
|
在 `internal/service/finance/` 包下新增两个函数 `QueryUserProfitLoss` 和 `QueryActivityProfitLoss`,接收可选参数(资产类型、维度 ID、时间范围),返回含汇总 + 明细的 ProfitLossResult。正确处理 game-pass 互斥、退款排除、已作废库存排除、历史数据兼容等边界情况。
|
||||||
|
|
||||||
|
</domain>
|
||||||
|
|
||||||
|
<decisions>
|
||||||
|
## Implementation Decisions
|
||||||
|
|
||||||
|
### 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 查询的拆分粒度和合并策略
|
||||||
|
- 单元测试的具体用例选择
|
||||||
|
|
||||||
|
</decisions>
|
||||||
|
|
||||||
|
<specifics>
|
||||||
|
## Specific Ideas
|
||||||
|
|
||||||
|
- 订单与活动是 1:1 关系,简化了活动维度的收入归属查询(不需要 dashboard 中复杂的双层子查询)
|
||||||
|
- 复用 `finance.ClassifyOrderSpending` 做 game-pass / 现金收入分类,保持与 dashboard 计算一致
|
||||||
|
- 积分汇率存在 system_configs KV 表中,需要在查询时动态读取
|
||||||
|
|
||||||
|
</specifics>
|
||||||
|
|
||||||
|
<canonical_refs>
|
||||||
|
## Canonical References
|
||||||
|
|
||||||
|
### Finance primitives (MUST reuse)
|
||||||
|
- `internal/service/finance/profit_metrics.go` — ClassifyOrderSpending, IsGamePassOrder, ComputeGamePassValue, ComputePrizeCostWithMultiplier, ComputeProfit
|
||||||
|
- `internal/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 calculation
|
||||||
|
- `internal/api/admin/dashboard_user_spending.go` — User-dimension spending aggregation pattern
|
||||||
|
- `internal/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, activities
|
||||||
|
- `internal/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 detection
|
||||||
|
- `finance.ComputeGamePassValue()` — draw_count × activity_price
|
||||||
|
- `finance.ComputePrizeCostWithMultiplier()` — Base cost × item-card multiplier
|
||||||
|
- `finance.ComputeProfit()` — profit + profit_rate calculation
|
||||||
|
- `finance.NormalizeMultiplierX1000()` — Multiplier normalization
|
||||||
|
|
||||||
|
### Established Patterns
|
||||||
|
- **Service constructor:** `New(logger, db) → Service` with injected logger and repo
|
||||||
|
- **Fan-out queries:** Multiple independent `db.Table().Scan()` calls, merge in Go via `map[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)` AND `remark NOT LIKE '%void%'`
|
||||||
|
- **Refund exclusion:** `orders.status = 2` with legacy escape: `OR order_id = 0 OR order_id IS NULL`
|
||||||
|
|
||||||
|
### Integration Points
|
||||||
|
- Constructor receives `mysql.Repo` — uses `GetDbR()` 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>
|
||||||
|
## 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)
|
||||||
|
|
||||||
|
</deferred>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Phase: 01-core-pnl-functions*
|
||||||
|
*Context gathered: 2026-03-21*
|
||||||
Loading…
x
Reference in New Issue
Block a user