# Finance — Earnings + Wallet/Withdrawals + VAT — PARITY TABLE

Demo (canonical) = `/private/tmp/Navagoo_MI_dev/navagoo-app/src`.
Ours = repo cwd. Status: done / partial / missing.

| # | Demo behavior | Demo ref | Our ref | Status | Note |
|---|---|---|---|---|---|
| 1 | VAT-inclusive → base/vat split, gated on VAT registration | finance.ts:31-71; vat.test.ts:9-31 | Earnings.php:547-573 | done | Same formula; gated on `is_taxable` |
| 2 | VAT added ON TOP of each Navagoo fee | finance.ts:106,137 | Earnings.php:610-618 | done | `(navagooFees+serviceFee)×rate` |
| 3 | Single global VAT rate | finance.test.ts:38 | Earnings.php:561 (Settings.taxes) | done | Global, not per-shop |
| 4 | Marketing fee ONLY for navagoo-sourced customers | finance.ts:248; finance.test.ts:237-256 | — | missing | Ours charges every earning |
| 5 | Marketing basis = booking value excl-VAT | finance.ts:133 | Earnings.php:595-601 | partial | Ours uses collected-excl-VAT |
| 6 | Minimum marketing fee floor (default 5) | finance.ts:135; finance.test.ts:195 | — | missing | No floor |
| 7 | Grace-window waiver → fee 0 | finance.ts:136,293-295; finance.test.ts:258-277 | — | missing | No grace window |
| 8 | Per-shop marketing-fee rate | finance.ts:133 | Shop.php:62,215 (platform_commission) | done | |
| 9 | Payment-processing (gateway) fee: collected×rate%+fixed, non-refundable | finance.ts:95-118; finance.test.ts:158 | — | missing | No processing-fee concept |
| 10 | Refund zone by cancel timing (full/partial/none) | finance.ts:318-323 | — | missing | `refund_value` is a stored number |
| 11 | Customer-refund rules (shop-cancel full; deposit 0; zone-based) | finance.ts:326-338; finance.test.ts:234 | — | missing | No branches |
| 12 | Marketing-fee reversal row on customer cancel | finance.ts:205-222,269-282; finance.test.ts:221-231 | — | missing | No reversal/ledger |
| 13 | No-show CEO rule (uncollected no-show → no fee; completed pay-on-visit → fee) | finance.ts:256-260; finance.test.ts:164-198 | — | missing | Not modelled |
| 14 | Net payout = (collected−refund)+tips−mkt−proc−feeVat | finance.ts:358-383 | ShopEarning.php:268-293 | partial | Missing processing-fee term |
| 15 | Booking-method payout branches | — | ShopEarning.php:280-293; EarningsController.php:72-78 | done | Ours-specific; no demo equiv |
| 16 | Eligibility: terminal AND hold-days since transaction date | finance.ts:472-478 | EarningsController.php:167,174-182 | partial | Hold from earning created_at; not re-deriving terminality |
| 17 | Min-withdrawal gate (default 5000) | finance.ts:518 | EarningsController.php:166,197; AgentsWalletController.php:351-360 | done | |
| 18 | Carry-forward / negative balance (shop owes Navagoo) | finance.ts:588-631 | — | missing | No carry-forward model |
| 19 | No double-settle; paid vs incurred fee distinction | finance.ts:424-430,541-559 | AgentsWalletController.php:256,529-549 | partial | withdrawal_id guard works; no paid/incurred split |
| 20 | Stamped/frozen rates (ledger) | finance.ts:1-7,234-289 | — | missing | Ours recomputes fees on save |
| 21 | Withdrawal creation → stamp earnings/payments | (selectors only) | AgentsWalletController.php:210-570 | done | Ours has full pipeline demo lacks |
| 22 | Charge-type metadata (5 types) + badges | shared.tsx:10-24 | — | missing | Ours collapses to marketing fee only |
| 23 | FormulaStrip settlement visual | shared.tsx:54-92 | — | missing | No per-term breakdown in views |
| 24 | StatTile KPI header | shared.tsx:27-48 | earnings/index.php:112-185 | done | Richer KPI row on our side |
| 25 | Service discount helper (fixed/percent) | finance.ts:43-52; vat.test.ts:33-46 | — (out of area) | n/a | Catalogue display, not earnings |
| 26 | Subscription pricing by period | finance.ts:648-656 | — (out of area) | n/a | Subscription, not earnings/wallet |
| 27 | Shop-scoping / IDOR guards | (single-tenant) | AgentsWalletController.php:631-675; EarningsController.php:243-247 | done | Ours-only strength |
| 28 | RTL / bilingual i18n | (LTR only) | Yii::t across views; messages/{ar,en} | done | Ours-only strength |

Area score: 45%

## Verified verdict (adversarial)

Re-read demo (`finance.ts`, `vat.test.ts`) against our code line-by-line. Most
"done" rows hold; one money-math "done" is downgraded.

| # | Feature | Analyst | Verified | Evidence |
|---|---|---|---|---|
| 1 | VAT-inclusive base/vat split | done | **done** | Demo `noVat` divides by `(1+vatPct/100)` (finance.ts:31-34); ours identical `finalAmount/(1+vatRate)*vatRate` (Earnings.php:569-570). Gating differs in name only (`vatRegistered` vs `is_taxable`), same behavior. |
| 2 | VAT added on top of each Navagoo fee | done | **partial** | Demo stamps a `vatAmount = amount*(vatPct/100)` on EVERY charge type incl. `payment_processing_fee` (finance.ts:106) and `marketing_fee` (finance.ts:137). Ours computes VAT only over `(navagooFees+serviceFee)` (Earnings.php:610-618) — the processing-fee VAT term is absent because the processing fee itself is absent (see #9). Per-fee VAT across 5 charge types is NOT reproduced. |
| 15 | Booking-method payout branches | done | **done** (ours-only) | No demo equivalent; ShopEarning.php:280-293 is an ours-specific Mobile/Walk-in split. Nothing to refute. |
| 16 | Eligibility: terminal + hold days | partial | **partial** | Confirmed. Demo measures hold from `transactionDate` = completedAt/cancelledAt/noShowMarkedAt (finance.ts:462-477) and re-derives terminality every call; ours measures from `earning.created_at` and trusts stored `settlement_status` (EarningsController.php:175-179). Diverges when created_at != terminal-event date. |
| 17 | Min-withdrawal gate (default 5000) | done | **done** (gate); note | Gate operator matches: demo `eligibleNet >= shop.minWithdrawalAmount` (finance.ts:518); ours `>=` (EarningsController.php:197) and `< → error` (AgentsWalletController.php:351). Caveat: the literal `5000` default is ours-only — demo has no 5000 default in finance.ts, it reads `shop.minWithdrawalAmount`. Behavior equivalent when configured. |
| 19 | No double-settle; paid vs incurred | partial | **partial** | Confirmed. `withdrawal_id IS NULL` guard prevents re-settle (EarningsController.php:158); but demo's paid-vs-incurred split (`bookingSettlement` drops `status:'paid'` rows while `bookingFeesIncurred` keeps them, finance.ts:282-289 vs 424-430) has no analogue. |
| 21 | Withdrawal creation pipeline | done | **done** (ours-only) | Ours has a real pipeline (AgentsWalletController.php:210-570); demo is selector-only. Cannot refute a present-in-ours item. |
| 24 | StatTile KPI header | done | **done** | Confirmed richer KPI row, earnings/index.php:112-185. |
| 27 | Shop-scoping / IDOR guards | done | **done** (ours-only) | Confirmed IDOR guard EarningsController.php:243-247 throws NotFound on shop mismatch. Demo single-tenant. |
| 28 | RTL / bilingual i18n | done | **done** (ours-only) | Confirmed `Yii::t` throughout the view; ar/en message catalogs. Demo LTR-only. |

All "missing" rows (4,6,7,9,10,11,12,13,18,20,22,23) and the other "partial" rows
(5,14) were spot-checked and confirmed correct — no false "missing" inflation found.

### Overclaim found
- **#2 VAT-on-top: done → partial.** Ours reproduces the on-top formula only for
  the marketing-fee (+ legacy service-fee) it models. The demo applies per-fee VAT
  across all 5 charge types, including the payment-processing fee that ours lacks
  entirely. The VAT engine is therefore narrower than the demo, not at parity.

### Adjusted area score
One "done" (#2) drops to partial. Counting full credit for done, half for partial:
done {1,15,21,24,27,28} = 6; partial {2,5,14,16,19} = 5 (×0.5 = 2.5); missing/n-a
rest. Across the 23 in-area parity rows this nets ≈ **42%** (was 45%).

Adjusted area score: 42%
