- Add internal/service/finance/types.go: AssetType enum, param/result structs - Add internal/service/finance/service.go: Service interface, read-only ctor - Add internal/service/finance/query_user.go: QueryUserProfitLoss (4 fan-out scans) - Add internal/service/finance/query_activity.go: QueryActivityProfitLoss (4 fan-out scans) - Add internal/service/finance/service_test.go: 22 integration tests (all pass) - Wire finance.Service into admin handler (admin.go) - Replace dashboard_activity cost scan with finance.Service call (D-09: value_cents single source of truth) - Revenue/gamepass/draw-count scans unchanged; response schema fully compatible Co-Authored-By: claude-flow <ruv@ruv.net>
57 lines
4.3 KiB
Markdown
57 lines
4.3 KiB
Markdown
# Roadmap: Bindbox Game 盈亏统计函数
|
||
|
||
## Overview
|
||
|
||
Two reusable service-layer functions — `QueryUserProfitLoss` and `QueryActivityProfitLoss` — are built in a new `internal/service/finance/` package. Phase 1 delivers correct, working functions covering the full P&L computation path. Phase 2 populates the per-asset-type breakdown slice, including Fragment synthesis cost. The result is a clean, testable service that callers can invoke without touching HTTP handler logic.
|
||
|
||
## Phases
|
||
|
||
**Phase Numbering:**
|
||
- Integer phases (1, 2, 3): Planned milestone work
|
||
- Decimal phases (2.1, 2.2): Urgent insertions (marked with INSERTED)
|
||
|
||
Decimal phases appear between their surrounding integers in numeric order.
|
||
|
||
- [x] **Phase 1: Core P&L Functions** - Scaffold the finance package and deliver working QueryUserProfitLoss / QueryActivityProfitLoss with correct revenue, cost, and profit (Completed: 2026-03-21)
|
||
- [ ] **Phase 2: Per-Asset-Type Breakdown** - Populate the ProfitLossBreakdown slice for all 5 asset types, including Fragment synthesis cost from its own table
|
||
|
||
## Phase Details
|
||
|
||
### Phase 1: Core P&L Functions
|
||
**Goal**: Callers can invoke QueryUserProfitLoss and QueryActivityProfitLoss and receive a correct ProfitLossResult with total revenue, cost, profit, and profit rate — with all edge cases handled
|
||
**Depends on**: Nothing (first phase)
|
||
**Requirements**: PNL-01, PNL-02, PNL-03, PNL-04, PNL-05, PNL-06, PNL-07, PNL-08, DIM-01, DIM-02, DIM-03, DIM-04, RET-01, RET-03, AST-01, QUA-01, QUA-02, QUA-03, QUA-04, QUA-05
|
||
**Success Criteria** (what must be TRUE):
|
||
1. Calling QueryUserProfitLoss with a list of user IDs returns a ProfitLossResult where Revenue equals actual_amount + discount_amount for non-refunded orders only, and game-pass orders contribute draw_count × activity_price instead of cash revenue
|
||
2. Calling QueryActivityProfitLoss with an activity ID returns a ProfitLossResult where Revenue is attributed directly to the order's activity (1:1 per D-01 — no proration subquery)
|
||
3. Both functions return an error (not silent zero) when any db.Scan() call fails
|
||
4. Passing nil for StartTime/EndTime applies no time filter; passing nil/0 for AssetType returns aggregated totals across all asset types
|
||
5. The package contains no call to GetDbW() — all queries route through the injected DbR handle; voided inventory (remark LIKE '%void%' or status=2) and refunded orders (status=3,4) are excluded from cost and revenue respectively
|
||
**Plans**: 4 plans
|
||
|
||
Plans:
|
||
- [ ] 01-01-PLAN.md — Package scaffold: types.go (AssetType enum + param/result structs) + service.go (interface + read-only constructor) + service_test.go (SQLite test infrastructure)
|
||
- [ ] 01-02-PLAN.md — QueryUserProfitLoss: query_user.go with 4 fan-out scans (revenue, inventory cost, points cost, coupon cost) + integration tests
|
||
- [ ] 01-03-PLAN.md — QueryActivityProfitLoss: query_activity.go with 4 fan-out scans attributed to activity dimension + integration tests
|
||
- [ ] 01-04-PLAN.md — Phase 1 verification: full test suite + static checks (no GetDbW, fan-out count, finance functions reused, int64 monetary types)
|
||
|
||
### Phase 2: Per-Asset-Type Breakdown
|
||
**Goal**: The ProfitLossBreakdown slice in every ProfitLossResult contains one entry per relevant asset type (Points, Coupon, ItemCard, Product, Fragment), with correct per-type cost including Fragment synthesis cost sourced from fragment_synthesis_logs
|
||
**Depends on**: Phase 1
|
||
**Requirements**: AST-02, AST-03, RET-02
|
||
**Success Criteria** (what must be TRUE):
|
||
1. A ProfitLossResult for an activity that awarded Points, Coupons, and Items contains exactly one ProfitLossBreakdown entry per asset type that had non-zero cost, with the correct cost value for each
|
||
2. Fragment cost in the breakdown is sourced from fragment_synthesis_logs using the verified join path (not from user_inventory), and matches manual spot-checks against the database
|
||
3. Summing all breakdown entries' Cost fields equals the total Cost field in the parent ProfitLossResult (no gaps or double-counting between breakdown and totals)
|
||
**Plans**: TBD
|
||
|
||
## Progress
|
||
|
||
**Execution Order:**
|
||
Phases execute in numeric order: 1 → 2
|
||
|
||
| Phase | Plans Complete | Status | Completed |
|
||
|-------|----------------|--------|-----------|
|
||
| 1. Core P&L Functions | 4/4 | Complete | 2026-03-21 |
|
||
| 2. Per-Asset-Type Breakdown | 0/TBD | Not started | - |
|