# AGENTS.md

ERP project: modular accounting + inventory + billing. Design docs are authoritative:
[docs/PLAN.md](docs/PLAN.md), [docs/SCHEMA.md](docs/SCHEMA.md), [docs/ACCOUNTING.md](docs/ACCOUNTING.md).

**User-facing docs**: [SETUP.md](SETUP.md) (installation, config, deployment), [USER_GUIDE.md](USER_GUIDE.md) (end-user usage manual).

Status: **all 9 phases complete** (Phase 0–9). 171 tests passing, Pint clean, PHPStan level 1 (with Larastan) clean. Read PLAN.md for design context.

## Environment (verified 2026-08-19)

- Windows + XAMPP at `C:\xampp`, project at `C:\xampp\htdocs\erp`
- PHP 8.2.12 (`C:\xampp\php\php.exe`, ZTS) — Laravel 12 needs 8.2–8.5; upgrade to 8.3/8.4 before production
- MariaDB 10.4.32 (`C:\xampp\mysql\bin\mysql.exe`) — no `SKIP LOCKED` (needs 10.6+), so run **one** queue worker on the `database` driver
- Composer 2.8.12, Node 24.11.0, npm 11.6.1
- Shell is PowerShell; chain with `;` or `; if ($?) { ... }`, not `&&`

## Non-negotiable engineering rules

1. Only `App\Domain\Accounting\Posting\PostingService` may write `journal_entries`. Only `App\Domain\Inventory\Ledger\StockLedger` may write `stock_movements`.
2. Money/quantity are `DECIMAL`, never float. Compare in minor units, never with an epsilon.
3. No `MAX(id)+1` numbering — use `document_sequences` with `SELECT ... FOR UPDATE`, allocated at POST only.
4. Lock `stock_balances` rows `FOR UPDATE` ordered by `product_id, warehouse_id` before any stock write.
5. Posted financial documents are immutable. Amend = reverse + clone. Never delete.
6. Every unique index on a business table is composite with `company_id`.
7. No hardcoded account IDs — resolve through `account_mappings` keys.
8. Post inventory before the GL (inventory produces the COGS figures).
9. Event listeners must be non-financial and queued.
10. Every invariant in PLAN.md §4 has a passing Pest test before its phase is called done.

## Commands

- verify: `php vendor/bin/pint` + `php artisan test`
- test: `php artisan test` / `./vendor/bin/pest`
- dev: `php artisan serve` + `npm run dev`
- fresh seed: `php artisan migrate:fresh --seed`
- test DB reset: `C:\xampp\mysql\bin\mysql.exe -u root -e "DROP DATABASE erp_test; CREATE DATABASE erp_test;"`

## Phase completion summary

| Phase | Description | Status |
|-------|-------------|--------|
| 0 | Skeleton: Laravel 12, Pest, Pint, Money/Quantity VO, BelongsToCompany, audit log | Done |
| 1 | Foundation: Auth, Company, Branch, Users, Roles/Permissions, Settings, Fiscal years | Done |
| 2 | Accounting core: Chart of accounts, PostingService, reversal, Trial Balance, GL | Done |
| 3 | Inventory core: Products, Warehouses, StockLedger, WAC costing, adjustments, transfers | Done |
| 4 | Purchases: Suppliers, PostPurchase, landed cost allocation, purchase returns, supplier payments (PaySupplier), purchase cancellation, PO → GRN → Invoice flow | Done |
| 5 | Sales/Estimates: Customers, PostSale, estimates→invoice, sales returns (UI), receipts + allocation, split payments, price lists + PricingService + management UI, sale cancellation, A4 + 80mm print | Done |
| 6 | POS: POS billing, sessions, cash drawer, held bills | Done |
| 7 | Banking: Bank accounts, deposits/withdrawals, expenses, bank reconciliation UI, bank statement CSV import, undeposited funds (DepositCheques) | Done |
| 8 | Reports: P&L, Balance Sheet, Cash Flow, VAT registers, AR/AP aging, Gross Profit, Dead/Slow stock, Year-end close, Dashboard, Profit Analytics (by product/customer/salesperson) | Done |
| 9 | Commercial: Plans/Subscriptions, REST API (Sanctum), FIFO recosting, notifications, backup, import/export, offline queue, Nepali/BS date localization | Done |

## UI pages (Livewire)

All backend actions have corresponding Livewire UI pages accessible from the sidebar nav:

| Module | Pages | Routes |
|--------|-------|--------|
| Sales | Invoices, New Invoice, Estimates, Price Lists, Price Approvals, POS Billing, POS Session, Offline Queue, Sales Returns, Receipts, Customers | `invoices.*`, `estimates.*`, `price-lists.*`, `price-approvals.*`, `pos.*`, `sales-returns.*`, `receipts.*`, `customers.*` |
| Inventory | Products, Stock Valuation, Warehouses, Transfers, In-Transit Transfers, Dispatch Transfer, Receive Transfer, Adjustments | `products.*`, `stock.*`, `warehouses.*`, `transfers.*`, `adjustments.*` |
| Purchases | Suppliers, Purchases, New Purchase, Purchase Orders, New PO, Goods Receipts, New GRN, Landed Cost, Supplier Payments, New Supplier Payment, Purchase Returns | `suppliers.*`, `purchases.*`, `purchase-orders.*`, `goods-receipts.*`, `landed-cost.*`, `supplier-payments.*`, `purchase-returns.*` |
| Accounting | Chart of Accounts, Journal Entry, General Ledger, Trial Balance, P&L, Balance Sheet, Cash Flow, Year-End Close, Fiscal Years | `accounts.*`, `journals.*`, `ledger.*`, `trial-balance.*`, `reports.*`, `fiscal-years.*` |
| Reports | VAT Registers, AR/AP Aging, Gross Profit, Dead & Slow Stock, Customer Statement, Profit Analytics | `reports.*` |
| Banking | Bank Accounts, Transactions, Statement Import, Reconciliation, Expenses, New Expense, Cheque Deposits | `bank-accounts.*`, `bank-transactions.*`, `bank-statements.*`, `expenses.*`, `cheque-deposits.*` |
| Setup | Company, Branches, Users, Roles, Settings, Subscription, Import/Export, API Tokens, Audit Trail | `company.*`, `branches.*`, `users.*`, `roles.*`, `settings.*`, `audit.*` |

Cancel Sale/Purchase: Cancel buttons with reason modal are on the Invoices and Purchases list pages.

## Invariants (PLAN.md §4) — all tested

| # | Invariant | Test file |
|---|-----------|-----------|
| I1 | SUM(debit) = SUM(credit) per journal | PostingEngineTest |
| I2 | Trial balance: total debits = total credits | PostingEngineTest |
| I3 | stock_balances.qty = SUM(movements) | StockEngineTest |
| I4 | stock_balances.value = FIFO/WAC valuation | StockEngineTest |
| I5 | Inventory GL balance = total stock valuation | InvariantTest |
| I6 | AR/AP control = subledger balances | SalesEngineTest, PurchaseEngineTest |
| I7 | Bank GL balance = bank book balance | BankingEngineTest |
| I8 | No DRAFT/CANCELLED posts to GL or stock | PostingEngineTest |
| I9 | Cancel-by-reversal = net zero | PostingEngineTest |
| I10 | Document numbers gapless + unique | InvariantTest |
| I11 | No line below min price without approval | SalesEngineTest |
| I12 | Estimate→Invoice preserves price/tax/discount | SalesEngineTest |
| I13 | Every tenant query has company_id; no data bleed | InvariantTest |

## Key architectural decisions

- **Money/Quantity**: `App\Support\Decimals\Money` and `Quantity` value objects, DECIMAL columns, minor-unit comparisons.
- **Posting**: Only `PostingService` writes `journal_entries`; only `StockLedger` writes `stock_movements`.
- **Costing**: `CostingMethod` interface (D2) with `WacCosting` strategy, registered in AppServiceProvider. StockLedger delegates cost calculation to it. FIFO recosting via `FifoRecosting` service (Phase 9, `fifo_layers` table).
- **Multi-tenancy**: `BelongsToCompany` trait + global `company_id` scope + composite unique indexes.
- **Document numbering**: `document_sequences` table with `SELECT ... FOR UPDATE`, allocated at POST only.
- **Pricing**: `PricingService::resolve()` returns a `ResolvedPrice` snapshot (unit_price, min_price, tax_rate, source). Resolution: customer-specific → price list → qty break → product default. PostSale stores the snapshot on `sale_items`.
- **API**: Laravel Sanctum, `/api/v1` routes, `SetApiCompanyContext` middleware, Eloquent resources.
- **Localization**: `NepaliDate` helper (AD↔BS, years 2000-2099), `DateFormatter` service, `@date` Blade directive. AD storage, BS display.
- **Offline queue**: `offline_bills` table (not `held_bills` — that name is used by POS held carts), `SyncHeldBills` action, UUID dedup.

## PLAN.md audit results (D1–D14 + phase gates)

| Item | Status | Notes |
|------|--------|-------|
| D1 Money/qty | ✓ | DECIMAL columns, Money/Quantity VOs, exact equality |
| D2 Costing interface | ✓ | `CostingMethod` interface + `WacCosting` strategy, registered in AppServiceProvider. StockLedger delegates to it. Swappable to FIFO or custom. |
| D3 No running balance | ✓ | stock:verify command added, FOR UPDATE locking, no balance_qty column |
| D4 Subledgers as GL dims | ✓ | party_type/party_id on journal_entries, customer statement is a query |
| D5 Doc numbers at POST | ✓ | DocumentNumberAllocator with lockForUpdate |
| D6 Posted immutability | ✓ | GuardsPostedDocuments trait on Sale/Purchase/Expense/SalesReturn/PurchaseReturn + architecture test |
| D7 Period control | ✓ | PeriodGuard, fiscal years/periods, year-end close, period-blocking test |
| D8 Multi-branch | ✓ | branch_id everywhere; in-transit warehouse pattern with DispatchTransfer + ReceiveTransfer for multi-day inter-branch transfers; same-day TransferStock also available |
| D9 Tenancy | ✓ | BelongsToCompany trait, architecture test, ExistsInCompany rule, composite unique indexes |
| D10 POS | ✓ | Alpine cart, ETag product index, server recalculates + rejects tampered totals |
| D11 Pricing engine | ✓ | `PricingService::resolve()` returns `ResolvedPrice` VO with `PriceSource` enum. Resolution chain: customer-specific → price list → qty break → product default. Price snapshot stored on sale_items. Below-min guard in PosPostController. `price_approvals` table for audit. `price_lists`/`price_list_items` tables with models. Price list management UI is V2. |
| D12 Payments as documents | ✓ | `receipts` + `receipt_allocations` tables, `PostReceipt` action. `payment_lines` table for split tenders (cash/bank/card/cheque/mobile/credit_note/tds). `DepositCheques` action for undeposited funds GL automation. `PaySupplier` action for supplier payments. TDS line supported. |
| D13 Localization | ✓ | NepaliDate helper, date_system setting, configurable FY, IRD fields (is_printed, print_count, printed_by, sync_status, synced_at, sync_error) |
| D14 Feature gating | ✓ | Plans/features/subscriptions, Features::enabled(), @feature directive, accounting always runs |

### Deliberate deferrals (V1 scope decisions)

1. **Postable interface not wired to existing actions** (PLAN.md §3): The `Postable` interface, `PostingContext`, and `PostDocumentAction` exist and are tested. However, the existing `PostSale`/`PostPurchase` actions remain the primary posting path — they predate the abstraction and have domain-specific logic (price guards, COGS writeback, payment status) that doesn't fit the generic flow cleanly. New document types can use `PostDocumentAction` directly.
2. **Concurrent posting test** (Phase 3): The 500-movement soak test validates consistency. A true parallel test (multiple processes hitting the same balance) requires Redis/10.6+ and is deferred.
3. **PHPStan level 8** (Phase 0): Running at level 1 with Larastan. Level 2 produces 82 errors (real type issues, not just property access). Bumping to level 8 requires fixing type annotations across the codebase — deferred.
4. **TaxService wired into PostSale/PostPurchase, DocumentCalculator not** (architecture): TaxService is now injected and used in both actions. DocumentCalculator exists but is not wired in — the actions have interleaved logic (price resolution, price guards) that doesn't fit a pure calculator pattern.

### New tests added in audit pass

- `tests/Architecture/ArchitectureTest.php` — D1 (no float casts), D6 (sole writers), D9 (tenant trait compliance)
- `tests/Feature/Accounting/PeriodControlTest.php` — D7 (closed/locked period blocks posting)
- `tests/Feature/Architecture/PostedImmutabilityTest.php` — D6 (posted doc edit/delete guards)
- `tests/Feature/Inventory/VerifyStockTest.php` — D3 (stock:verify command)
- `tests/Feature/Inventory/StockSoakTest.php` — Phase 3 (500 randomized movements, GL balance = 0)
- `tests/Feature/Sales/PriceApprovalTest.php` — D11 (below-min blocking + override + audit log)
- `tests/Feature/Inventory/CostingMethodTest.php` — D2 (CostingMethod interface + WAC strategy + container swapping)
- `tests/Feature/Sales/PricingServiceTest.php` — D11 (PricingService resolution chain: default, price list, qty break, customer-specific, tax snapshot)
- `tests/Feature/Sales/SplitPaymentTest.php` — D12 (split tender lines: cash+bank, TDS+cash)
- `tests/Feature/Purchases/SupplierPaymentTest.php` — Phase 4 (PaySupplier: full, partial, advance, balanced GL)
- `tests/Feature/Purchases/LandedCostTest.php` — Phase 4 (landed cost allocation: by value, by qty, manual, balanced GL)
- `tests/Feature/CancelDocumentTest.php` — D6 (CancelSale + CancelPurchase: stock reversal, GL net zero)
- `tests/Feature/Reporting/ProfitAnalyticsTest.php` — Phase 8 (profit by product, customer, salesperson)
- `tests/Feature/Inventory/FifoCostingTest.php` — D2 (FifoCosting strategy + container swapping)
- `tests/Feature/Reporting/ComparativeStatementsTest.php` — Phase 8 (comparative P&L + BS with prior-period variance)
- `tests/Feature/PostableInterfaceTest.php` — PLAN.md §3 (Postable interface + PostDocumentAction + PostingContext)
- `tests/Feature/Purchases/PoGrnInvoiceFlowTest.php` — Phase 4 (PO → GRN → Invoice: create PO, post GRN, convert to purchase, partial GRN)
- `tests/Feature/Inventory/InTransitTransferTest.php` — D8 (DispatchTransfer + ReceiveTransfer with in-transit warehouse)
