# Architecture reference (frozen at Sprint 0.5)

This is the permanent contract. Every future sprint MUST follow it. Deviations
require an explicit ADR (architecture decision record) added under `docs/adr/`.

## 1. Layers

```
Route ─▶ Middleware ─▶ Controller (thin) ─▶ FormRequest ─▶ Action / Service ─▶ Model ─▶ DB
                                                    │
                                                    └─▶ Event ─▶ Listener ─▶ Notification / Log
```

- **Controllers**: 5–30 lines. Return view or JSON envelope only. No business logic.
- **FormRequest**: all validation + `authorize()` + `attemptAuthentication()` style helpers.
- **Action** (`app/Actions/`): invokable single-purpose class doing one atomic operation inside a DB transaction. Returns the created/updated model.
- **Service** (`app/Services/`): stateful application service (composition of actions, cache, external I/O). Registered as singleton in `AppServiceProvider`.
- **Model**: pure data + relations + cast rules + tiny attribute accessors. No queries beyond scopes.
- **Policy**: authorization only. `before()` bypass for admin role via `Gate::before`.
- **Observer**: cache invalidation, log fan-out. Never business logic.

## 2. Multi-tenancy

- One MySQL schema, `tenant_id` (aliased `company_id` in Sprint 1) on every business row.
- `active_company()` global helper resolves the tenant from session/URL.
- All queries scoped via global scope + a `BelongsToTenant` trait added at Sprint 2.

## 3. AJAX envelope

Every AJAX endpoint (identified by `X-Requested-With: XMLHttpRequest`) must respond with:

```json
{
    "ok": true,
    "message": "Human-readable status",
    "data": { "..." },
    "redirect": "/optional",
    "errors": { "field": ["..."] }
}
```

Controllers use `$this->ok(...)` / `$this->fail(...)` helpers on the base Controller.

## 4. Settings

- `settings` table = tenant-scoped key/value store, JSON `value`.
- Access via `SettingsService::forCompany($company)->get('group.key', default)`.
- Cached per-tenant; observer flushes on write.
- Seeded groups: `company`, `financial`, `invoice`, `printer`, `security`, `theme`, `notification`, `backup`.

## 5. Permissions

- Backed by Spatie Permission with an extended `App\Models\Role`.
- Permissions are DYNAMIC — sprints register them via `UserPermissionService::registerBatch()`.
- Never check hard-coded role names in production code. Check permissions.
- Admin bypasses all gates via `AuthServiceProvider::boot() -> Gate::before`.

## 6. Feature flags

`config/decent.php.feature_flags` gates modules that are not yet shipped. Sidebar
renders them disabled with a "Coming in Sprint N" badge.

## 7. Audit trail

- `spatie/laravel-activitylog` with `LogsActivity` on User, Company, Role, Setting.
- `activity_log.company_id` column is required and indexed.
- Never delete audit rows; soft-delete only on the source entity.

## 8. Stock ledger (starting Sprint 2)

- Append-only `stock_ledger` table. No UPDATE, no DELETE.
- Every business event (Purchase, Sale, Return, Adjustment) writes rows via one Action.
- Costing = weighted average, recomputed idempotently per SKU.

## 9. UI

- Theme = CSS custom properties resolved from DB at request-time.
- Two modes (light/dark) × two densities (comfortable/compact) = 4 render permutations.
- Bootstrap 5 base + design-system SCSS layered on top; never edit Bootstrap source.
- All interactive components use Blade `<x-…>`; no ad-hoc HTML for buttons, tables, etc.

## 10. Frontend

- Vite + ES modules.
- `resources/js/core/` = framework-agnostic building blocks (http, toast, modal, palette, theme, keyboard).
- `resources/js/modules/` = per-feature bindings.
- Bootstrap JS bundle imported once in `app.js`.
- ApexCharts imported once and exposed on `window.ApexCharts`.

## 11. Security

- CSRF token on every form + `X-CSRF-TOKEN` header on every AJAX request.
- Rate limiters: `login`, `password-reset`, `ajax`.
- All user input escaped through Blade `{{ }}`; unescaped `{!! !!}` only used for controlled icons.
- Password policy sourced from settings (min length, char classes, expiry).

## 12. Testing

- Feature tests per sprint, run with `RefreshDatabase`.
- Every controller endpoint must have at least one happy-path + one denied test.
- Actions get unit tests covering their invariants.

## 13. Coding standards

- PSR-12 + Laravel Pint (`composer format`).
- Larastan level 6 (`composer analyse`).
- No `else` after early return; guard-clause style.
- No `->raw` SQL unless documented in a comment.
- All new columns require a migration + downgrade in the same change.

## 14. Naming

- Tables: snake_case plural. Models: singular StudlyCase.
- Routes: dot.namespaced (`settings.company.update`).
- Blade views: `pages/<module>/<action>.blade.php`.
- SCSS files: `_kebab-case.scss` in the appropriate folder.
- JS modules: `<module>.js` in `resources/js/modules/`.

## 15. Folder freeze

The `app/`, `resources/js/`, `resources/sass/`, `resources/views/`, `database/`
folder trees defined in Sprint 1 are frozen. New sprints ADD; they do not
reorganize.
