# Admin · Finance — Business rules (implementable)

Rules the dev demo encodes for the two admin finance surfaces. Numbered for implementation. Ours = state of our codebase.

## Shop Balances (running balance / carry-forward) — NEW

1. **Running balance** per shop = `collectableEarnings − outstandingFees`. Positive = Navagoo owes the shop; negative = shop owes Navagoo. (`finance.ts:617`) — OURS: not computed; **MISSING**.
2. **Collectable earnings** = Σ over the shop's eligible, terminal, *unconsumed* bookings of `(amountCollected − refundValue + tips)`. A booking is "consumed" once it is in a transfer or applied to an invoice. (`finance.ts:596-602`) — OURS: per-booking earnings exist (`ShopEarning`) but no aggregate "unconsumed collectable" selector.
3. **Outstanding fees** = Σ `(chargeAmount + vatAmount)` over charges that are `net_from_settlement`, `status != paid`, not `pending`, and not yet attached to a transfer. (`finance.ts:603-616`) — OURS: no `charges` ledger; **MISSING**.
4. **Credit limit (carry threshold)** = per-shop `carryForwardThreshold` if set, else global `config.carryForwardThreshold`. (`finance.ts:569`) — OURS: no such field/config; **MISSING**.
5. **Auto-bill at threshold**: when a shop's carried balance `>= credit limit` and no open `threshold` invoice exists, automatically issue a `threshold` invoice. (`store.ts reconcileBilling`) — OURS: **MISSING**.
6. **Held earnings netting**: a threshold/manual invoice nets held earnings into amount due: `earningsApplied = min(grossFees, heldEarnings)`, `amountDue = grossFees − earningsApplied`. (`finance.ts earningsApplied`, `buildShopInvoice`) — OURS: **MISSING**.
7. **Manual "Issue invoice now"** is only offered when `shopIssuableNow > 0` = there are outstanding settlement fees not yet invoiced and not in a transfer. (`selectors.ts:183`, `ShopBalances.tsx:99`) — OURS: **MISSING**.
8. **Default filter** = owing shops only (`runningBalance < -0.005`), sorted most-negative first. (`ShopBalances.tsx:46-48`) — OURS: **MISSING**.
9. **Age in negative** = age (days) of the oldest unpaid settlement fee; shown only while the shop is negative. (`finance.ts:634`, `ShopBalances.tsx:88-93`) — OURS: **MISSING**.

## Transfer Requests / Settlement

10. **Net payout formula** = `totalEarned + totalTips − totalMarketingFees − totalPaymentProcessingFees − totalFeeVat`. (`TransferRequests.tsx:188-205`) — OURS: `net_transferable_amount` summed from `ShopEarning.net_collectible_amount` at request creation (`AgentsWalletController.php:450`); equivalent net but **fee components are not individually stamped/shown** (marketing only; processing fee + VAT not broken out). **PARTIAL**.
11. **Rates/amounts are stamped at creation, never recomputed** — settlement uses the stored totals. (demo `TransferRequest` is immutable totals) — OURS: same — totals stamped at request build; settlement does not recompute. **DONE**.
12. **Settlement requires two documents**: an invoice document AND a bank-transfer (payout) confirmation; "Settle & mark paid" stays disabled until both present. (`TransferRequests.tsx:166`) — OURS: uploads `navagoo_invoice` + `transfer_receipt` but settle is **not hard-gated** on both being present (server validates files if provided). **PARTIAL**.
13. **Amount-paid reconciliation**: warn when `|amountPaid − netPayout| > 0.01`; admin must confirm before settling. (`TransferRequests.tsx:159, 247-256`) — OURS: no mismatch check on `amount_transferred`. **MISSING**.
14. **Settling flips charges to paid** and generates a `transfer_settlement` invoice for the *uninvoiced* fee rows only (no double-invoicing of fees already on a threshold invoice); also clears all invoices linked to the transfer via `net_from_payout`. (`store.ts:745-838`) — OURS: no charge ledger, no generated/linked invoice entity; admin uploads an invoice file manually. **MISSING (invoice generation/linking)**.
15. **Status transitions**: demo `requested → settled` (one step). OURS adds `NEW(-1) → REQUESTED(0) → IN_PROGRESS(1) → SETTLED(2)`; only REQUESTED→IN_PROGRESS allowed via `actionUpdateSettlementStatus`, settlement-form drives →SETTLED. **DONE (superset)**.
16. **On settle, propagate to agents/earnings**: ours flips related agent/earning settlement statuses and pushes agent `Transaction` rows (`pushSpecialistSettlementTransactions`, `synchronizeRelatedSettlementStatus`). Demo has no agent layer here. **OURS-EXTRA** (no demo counterpart).
17. **VAT on fees** is part of the netted amount. Demo: `totalFeeVat` shown and subtracted. OURS: `totalNavagooVat` stamped in `beforeSave` from `Earnings.sum(vat_navagoo)` but **not displayed** in admin list/settle. **PARTIAL**.
18. **Permissions / shop-scoping**: settlement is an admin action. OURS gates `WithdrawalController` per-permission for `manager` role (`beforeAction`, `checkPermissions(controller_action)`); admins bypass. Demo has no auth layer (single-role store). **OURS-EXTRA / DONE**.
19. **Settlement date** is set automatically to now when settling if empty (`WithdrawalController.php:165-170`); demo stamps `settledAt = simNow` in `settleTransfer`. **DONE**.
