Audit pass (33 new tests in tests/Feature/Audit/):
- CrmFunnelE2ETest: full Lead→Deal→Appointment→WO→Payment journey,
covering 5 previously-untested models. Verifies WO.balanceDue updates
correctly after payments, including refunds (negative amount → balance
increases).
- WorkOrderTotalsTest: works+parts+subcontract+discount sum correctly,
cancelled subcontract excluded, deleting lines triggers recalc, status=done
consumes part reservations into issues, cancelled releases reservations.
- ShopJourneyE2ETest: register→cart→checkout→email confirmation→tracking
page reachable→admin fulfills→stock drops→warehouse event recorded.
Also guest checkout still works without account.
- CsvImportExportTest: round-trip, dedup-by-phone, **caught real bug** —
Vehicle export wrote $row->brand (no such property) and import set
'brand' => row['brand'] in Vehicle::create (column is `make`). Fix
applied to both paths.
- TenantBackupServiceTest: zip contains valid manifest with counts +
data/*.json per model + works embedded with WorkOrder.
- WorkOrderPdfServiceTest: generated PDF starts with %PDF, includes WO data,
non-trivial size, handles empty WO.
- PayrollCalculatorTest: base + works_pct + parts_pct + bonus - fine -
advance, scoped to user + period.
- NotificationFallbackTest: Telegram wins when chat_id present, falls back
to email when not, returns false when neither, tenant disable flag stops
both.
- AiProvidersCrossCheckTest: OpenAI request shape, Gemini URL with model,
no-key friendly message, tenant model override propagates into HTTP body.
- SettingsPersistenceTest: 25-key settings JSON round-trips, partial update
via array_replace_recursive preserves other keys.
- CompanyProvisionerTest: suspend / reactivate / archive behavior.
Bug fixed: CsvImportExport used `brand` on Vehicle which has column `make`.
The export silently emitted empty values, the import silently dropped the
brand. Now both paths use `make`.
Full suite: 173 passed (468 assertions). 0 failed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>