# Backend conventions

Read this fully before writing any module. Consistency across modules matters more
than any single module being clever.

## Stack facts (verified for this install — don't assume)

- Laravel 13.29, PHP 8.3, Passport 13.x, spatie/laravel-query-builder 7.x,
  barryvdh/laravel-dompdf 3.x, intervention/image 4.x.
- DB is SQLite locally (`database/database.sqlite`), `:memory:` for tests
  (already configured in `phpunit.xml`). Don't write MySQL-only SQL.
- Auth guard `api` uses Passport (`config/auth.php`). Middleware alias `role:admin`
  / `role:salesman` (comma-separate for either) is registered in `bootstrap/app.php`
  and enforces both role and `is_active`.
- Every `api/*` request already gets `Accept: application/json` forced
  (`ForceJsonResponse` middleware) and unhandled exceptions render through
  `App\Exceptions\ApiExceptionHandler` — you don't need your own try/catch for
  validation/model-not-found/auth exceptions, just let them throw.

## Layered architecture — do not skip a layer

`Route` → `Controller` (thin: validate via FormRequest, call one Service method,
wrap the result in `ApiResponse`) → `Service` (business rules, transactions,
orchestration) → `Repository` (interface + Eloquent impl, the only place querying
happens) → `Model`. Controllers never touch Eloquent directly. Services never build
Spatie `QueryBuilder` queries directly — that's the repository's job.

## Directory / naming per module (example: `Foo`)

- `app/Http/Controllers/Api/V1/FooController.php`
- `app/Http/Requests/Foo/StoreFooRequest.php`, `UpdateFooRequest.php`
- `app/Http/Resources/FooResource.php`
- `app/Services/FooService.php`
- `app/Repositories/Contracts/FooRepositoryInterface.php`
- `app/Repositories/Eloquent/FooRepository.php`
- `routes/api/v1/foo.php` — **only this file**. Never touch `routes/api/v1.php`,
  it already `require`s every module's file.
- `database/factories/FooFactory.php` (only for models you own)
- `tests/Feature/FooTest.php`

Then add ONE line to `App\Providers\AppServiceProvider::$repositoryBindings`
(the array already exists — add your interface => concrete pair, don't touch
anything else in that file, and don't remove other modules' entries).

## Response envelope — use `App\Support\ApiResponse`, never `response()->json()` directly

```php
return ApiResponse::success($data, 'Message.');                 // 200
return ApiResponse::success($data, 'Created.', 201);
return ApiResponse::paginated($paginator, FooResource::class);  // list endpoints
return ApiResponse::error('Message.', code: 422);
```
`ApiResponse::paginated` expects a `LengthAwarePaginator` (i.e. return
`$this->repository->paginate(...)` straight from the service, don't `->get()` list
endpoints).

## FormRequests

Extend `App\Http\Requests\BaseFormRequest` (already returns the error envelope on
422 automatically — don't override `failedValidation`). `authorize()` is already
`true` by default; override it only if a request needs field-level authorization
beyond the route's `role:` middleware.

## Repositories

Extend `App\Repositories\BaseRepository` (constructor takes the Eloquent model via
DI — bind the concrete with `fn ($app) => new FooRepository($app->make(Foo::class))`
if you need non-default construction, otherwise Laravel autowires the model fine
since `BaseRepository::__construct(Model $model)` — bind
`FooRepository::class` itself in the container isn't necessary, only the interface).
Set `$allowedFilters`, `$allowedSorts`, `$defaultWith` as protected properties; see
`Spatie\QueryBuilder\AllowedFilter` for exact/partial/scope filters. Add custom
finder methods (e.g. `findBySlug`) directly on the repository, declared on its
interface too.

## Services

Plain classes, constructor-injected with the repository interface (not the
concrete). Wrap multi-step writes in `DB::transaction()`. Throw
`Illuminate\Validation\ValidationException::withMessages([...])` for business-rule
violations (e.g. "phone already used at this location") — it renders through the
same 422 envelope automatically.

## Resources

One `FooResource` per model, explicit `toArray`, no `parent::toArray($request)`.
Nest related resources with `new BarResource($this->whenLoaded('bar'))` — never
lazy-load a relation inside a resource.

## Image uploads

Store on the `public` disk under `storage/app/public/{module}/...}` via
`Illuminate\Support\Facades\Storage::disk('public')->putFile(...)`. Use
`Intervention\Image\ImageManager` (driver already required) to resize to a sane max
(e.g. 1600px longest side) before storing, and generate a thumbnail conversion
where the module lists many images (products). Resources return
`asset('storage/'.$path)`. Never store outside `storage/app/public`.

## Routes

```php
// routes/api/v1/foo.php
use App\Http\Controllers\Api\V1\FooController;
use Illuminate\Support\Facades\Route;

Route::middleware(['auth:api'])->prefix('foo')->group(function () {
    Route::get('/', [FooController::class, 'index']);
    Route::middleware('role:admin')->group(function () {
        Route::post('/', [FooController::class, 'store']);
        Route::put('/{foo}', [FooController::class, 'update']);
        Route::delete('/{foo}', [FooController::class, 'destroy']);
    });
    Route::get('/{foo}', [FooController::class, 'show']);
});
```
Use route-model binding (`{foo}` typed `Foo $foo` in the controller method) rather
than manually finding by ID in the controller.

## Tests

`tests/Feature/FooTest.php` extends `Tests\TestCase`, uses
`Illuminate\Foundation\Testing\RefreshDatabase`. Authenticate with
`Laravel\Passport\Passport::actingAs($user)` — do NOT hit `/oauth/token` in tests,
it's slow and unnecessary. Cover: happy path list/create/update/delete, a validation
failure (422 shape), and a role-guard rejection (salesman hitting an admin-only
route gets 403). Run with `php artisan test --filter=FooTest` before considering
the module done — don't report a module finished without a green test run pasted
into your summary.

## What already exists — don't recreate

Models (all in `app/Models`, already migrated): `User`, `Location`, `Unit`,
`Category`, `Product`, `ProductImage`, `Retailer`, `InventoryMovement`,
`InventoryAlert`, `Order`, `OrderItem`, `Setting`. Relations are already defined on
each. `App\Support\ApiResponse`, `App\Http\Requests\BaseFormRequest`,
`App\Repositories\BaseRepository` + `Contracts\BaseRepositoryInterface`,
`App\Http\Resources\{UserResource,LocationResource}`, `App\Services\AuthService`
(reference for style).

## Style

Match `App\Http\Controllers\Api\V1\Auth\AuthController` and `App\Services\AuthService`
for exact style: constructor property promotion, explicit return types on every
method, PHPDoc over inline comments (skip comments entirely unless something is
genuinely non-obvious), curly braces always even for one-line bodies.
