# Tasks: Customer Management — SP-04 Re-execution

**Input**: Design documents from `/specs/005-customer-management-reexec/`
**Prerequisites**: plan.md (required), spec.md (required), research.md, data-model.md, contracts/, quickstart.md
**Tests**: Feature tests + unit tests are explicitly required by the spec's acceptance scenarios and by the constitution's Section 2.V (≥80% coverage). All test tasks are included.

## Format: `[ID] [P?] [Story] Description`

- **[P]**: Can run in parallel (different files, no dependencies)
- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US5)
- Include exact file paths in descriptions

## Path Conventions

The Laravel application source is under `src/` at the repository root. All paths below are relative to the project root unless they start with `src/`.

---

## Phase 1: Setup (Project Cleanup)

**Purpose**: Remove forbidden patterns from the existing repository so the re-executed code lands in a clean tree.

- [X] T001 [P] Delete forbidden Customer domain subdirectories: `src/app/Domains/Customer/Repositories/`, `src/app/Domains/Customer/DTO/`, `src/app/Domains/Customer/Pipelines/`, `src/app/Domains/Customer/Models/Query/`, `src/app/Domains/Customer/Models/Views/`
- [X] T002 [P] Delete forbidden Customer domain files: `src/app/Domains/Customer/Services/ClientLookupService.php`, `src/app/Domains/Customer/Http/Resources/CustomerResource.php`, `src/app/Domains/Customer/Http/Requests/CreateCustomerRequest.php`, `src/app/Domains/Customer/Http/Requests/ExportCustomerRequest.php`
- [X] T003 [P] Delete cross-cutting forbidden code: `src/app/Exceptions/Custom/` (entire dir), `src/app/Exceptions/ApiResponseException.php`, `src/app/Exceptions/ApiRateLimitException.php`, `src/app/Exceptions/ApiConnectionException.php`, `src/app/Exceptions/ExceptionHandler.php`, `src/app/Helpers/CostumErrorResponse.php`, `src/app/Models/Client.php`, `src/app/Shared/Repositories/` (entire dir), `src/config/ExceptionClassToMethod.php`
- [X] T004 Verify environment prerequisites: PHP 8.3+, PostgreSQL 15+ reachable, `composer install` complete, current branch is `005-customer-management-reexec`, `App\Domains\Auth\Models\Admin::first()` returns one row (run `php artisan tinker` to confirm); if not, run the Auth domain seeder first (out of scope)
- [X] T005 Confirm `customer_listing_mv` materialized view exists with its three required indexes (`idx_customer_listing_mv_client_id`, `idx_customer_listing_mv_next_due`, `idx_customer_listing_mv_first_contract`); report any missing index to the user (new migration would be required, per constitution Section 3.4)

---

## Phase 2: Foundational (Blocking Prerequisites)

**Purpose**: Cross-cutting infrastructure that MUST be complete before ANY user story can be implemented. The seeder is included here because without permissions assigned to the Admin, every subsequent user story's permission-enforcement tests would fail.

**⚠️ CRITICAL**: No user story work can begin until this phase is complete

- [X] T006 Create `ClientImmutableException` in `src/app/Exceptions/Business/ClientImmutableException.php` extending `RuntimeException`, with Arabic default message `"لا يمكن حذف العميل"` and HTTP status code 403
- [X] T007 Create `ExceptionMappings` in `src/app/Exceptions/ExceptionMappings.php` with a `public static function map(): array` that includes the `ClientImmutableException` mapping (forbidden response) and all other framework/domain mappings per constitution Section 12
- [X] T008 Wire `ExceptionMappings::map()` into `src/bootstrap/app.php` via `->withExceptions(function (Exceptions $exceptions) { foreach (\App\Exceptions\ExceptionMappings::map() as $class => $handler) { $exceptions->map($class, $handler); } })`
- [X] T009 Rewrite `src/app/Helpers/ApiResponseHelper.php` to match the constitution's response format: replace `status` key with `success`, pass error `message` as a string (not array-wrapped), keep all helper function names and signatures
- [X] T010 [P] Create `Client` model in `src/app/Domains/Customer/Models/Client.php` extending `Illuminate\Database\Eloquent\Model` (NOT `Authenticatable`); `$fillable` includes `client_type_flags`; casts `client_type_flags` to `array`; overrides `boot()`, `delete()`, `destroy()` to throw `ClientImmutableException`; exposes `isCustomer()`, `isAgent()`, `isInvestor()`, `markAsCustomer()`, `markAsAgent()`, `markAsInvestor()` (each flag mutator calls `$this->save()`, never `saveQuietly()`); defines relationships `contractsAsCustomer()`, `contractsAsAgent()`, `sharesLogs()`
- [X] T011 [P] Create `CustomerListingMv` model in `src/app/Domains/Customer/Models/CustomerListingMv.php` with `$table = 'customer_listing_mv'`, `$primaryKey = 'client_id'`, `$incrementing = false`, `$timestamps = false`, the seven monetary/date/integer casts from `data-model.md`, and the four Scopes (`scopeSearch`, `scopeFirstContractFrom`, `scopeFirstContractTo`, `scopeOrderByNextDue` which orders by `next_due_date ASC NULLS LAST, client_id ASC`)
- [X] T012 Create `CustomerPermissionsSeeder` in `src/database/seeders/CustomerPermissionsSeeder.php` that idempotently creates permissions `customers.view`, `customers.create`, `customers.update` (guard `admin`) via `Permission::firstOrCreate(...)` and attaches them to `Admin::first()` via `syncPermissions([...])`
- [X] T013 Register the seeder in `src/database/seeders/DatabaseSeeder.php` by adding `$this->call(CustomerPermissionsSeeder::class);` to the `run()` method
- [X] T014 Run the seeder with `php artisan db:seed --class=CustomerPermissionsSeeder` and verify three `permissions` rows exist with the exact names plus the admin has them via `php artisan tinker` smoke checks
- [X] T015 Register the domain Service Provider by adding `App\Domains\Customer\CustomerServiceProvider::class` to the `providers` array in `src/bootstrap/providers.php`
- [X] T016 Rewrite `src/app/Domains/Customer/CustomerServiceProvider.php`: `boot()` calls `Route::middleware('api')->prefix('api/v1')->group(__DIR__ . '/Routes/v1/api.php')` (or `loadRoutesFrom` per Laravel 11 convention); no Repository binding in `register()` (Repository pattern is forbidden)
- [X] T017 Ensure `src/routes/api.php` contains only a guidance comment pointing to domain Service Providers and zero `Route::*` definitions

**Checkpoint**: Foundation ready — admin can authenticate, `customers.*` permissions exist and are assigned, `Client` model is hard-immutable, exception infrastructure is wired, response helper conforms to the constitution. User story implementation can now begin.

---

## Phase 3: User Story 1 — Customer Creation (Priority: P1) 🎯 MVP

**Goal**: Admin creates a new customer via `POST /api/v1/customers`. The new record is persisted in the `clients` table with the `"customer"` flag in `client_type_flags`, and an optional avatar is uploaded to the public disk.

**Independent Test**: `POST` with valid name + unique phone returns 201 with the customer data, and a follow-up `GET` on the new customer's `id` shows the same data with `client_type_flags` containing `"customer"`. A 422 is returned for a duplicate phone. A 403 is returned when the caller lacks `customers.create`.

### Tests for User Story 1

> **NOTE: Write these tests FIRST, ensure they FAIL before implementation**

- [X] T018 [P] [US1] Feature test for create-customer happy path, validation errors, permission enforcement, and `client_type_flags` post-condition in `src/tests/Feature/Customer/CreateCustomerTest.php` (10 scenarios from `contracts/create-customer.md`)

### Implementation for User Story 1

- [X] T019 [P] [US1] Create `StoreCustomerRequest` in `src/app/Domains/Customer/Http/Requests/StoreCustomerRequest.php` with rules from `data-model.md` (name required 1–150; phone required Syrian local format `^0?9\d{8}$` and unique; description nullable max 1000; avatar nullable image mimes jpg/jpeg/png/webp max 5 MB) and Arabic `messages()`
- [X] T020 [P] [US1] Create `CustomerListResource` in `src/app/Domains/Customer/Http/Resources/CustomerListResource.php` (shared with US2; creates the canonical response shape for list items)
- [X] T021 [P] [US1] Create `CustomerDetailResource` in `src/app/Domains/Customer/Http/Resources/CustomerDetailResource.php` (shared with US3; creates the canonical response shape for detail items)
- [X] T022 [US1] Implement `CustomerService::createCustomer(StoreCustomerRequest $request): array` in `src/app/Domains/Customer/Services/CustomerService.php` (depends on T019, T010): open `DB::transaction`; `Client::create([... 'client_type_flags' => ['customer']]) `; if avatar present, `Storage::disk('public')->putFile('avatars', $file)` then `$client->avatar = $path; $client->save();`; commit; return array
- [X] T023 [US1] Implement `CustomerController::store(StoreCustomerRequest $request)` in `src/app/Domains/Customer/Http/Controllers/CustomerController.php` (depends on T022): inject `CustomerService`; call `$this->customerService->createCustomer($request)`; return `resourceCreatedResponse('تم إنشاء العميل بنجاح.', $resource->toArray(...))`
- [X] T024 [US1] Add `POST /customers` route to `src/app/Domains/Customer/Routes/v1/api.php` inside the `auth:sanctum` group with middleware `permissions:customers.create` pointing to `CustomerController@store`

**Checkpoint**: User Story 1 fully functional and testable independently — admin can create a customer, the customer appears in subsequent reads, duplicate phone is rejected, missing permission returns 403.

---

## Phase 4: User Story 2 — Customer Listing (Priority: P1)

**Goal**: Admin views the paginated customer list via `GET /api/v1/customers`, served from the `customer_listing_mv` materialized view, ordered by `next_due_date ASC NULLS LAST`, filterable by search, date range, and cursor-paginated.

**Independent Test**: `GET` returns a paginated list whose first page is ordered by `next_due_date ASC NULLS LAST`, the SQL query log shows only a `SELECT` on `customer_listing_mv` (no JOIN of `clients` + `contracts` + `installments`), the `next_cursor` works for pagination, and a 403 is returned for users without `customers.view`.

### Tests for User Story 2

> **NOTE: Write these tests FIRST, ensure they FAIL before implementation**

- [X] T025 [P] [US2] Feature test for list-customer scenarios in `src/tests/Feature/Customer/ListCustomersTest.php` (14 scenarios from `contracts/list-customers.md`), including a query-log assertion that the SQL targets `customer_listing_mv` only; tests call `DB::statement('REFRESH MATERIALIZED VIEW customer_listing_mv')` after seeding

### Implementation for User Story 2

- [X] T026 [P] [US2] Create `ListCustomersRequest` in `src/app/Domains/Customer/Http/Requests/ListCustomersRequest.php` with rules from `data-model.md` (search nullable string max 150; from_date/to_date nullable `date_format:Y-m-d` with `to_date >= from_date`; per_page nullable integer 1–100; cursor nullable string) and Arabic `messages()`
- [X] T027 [US2] Implement `CustomerService::getCustomerList(ListCustomersRequest $request): array` in `src/app/Domains/Customer/Services/CustomerService.php` (depends on T011, T026, T020): build query on `CustomerListingMv` with `search`, `firstContractFrom`, `firstContractTo`, `orderByNextDue` Scopes; `->cursorPaginate($perPage ?? 20)`; return `['items' => CustomerListResource::collection(...)->resolve(), 'meta' => ['path' => ..., 'per_page' => ..., 'next_cursor' => ..., 'prev_cursor' => ...]]`
- [X] T028 [US2] Implement `CustomerController::index(ListCustomersRequest $request)` in `src/app/Domains/Customer/Http/Controllers/CustomerController.php` (depends on T027): call service; return `success(data: $result['items'], msg: 'قائمة العملاء', meta: $result['meta'])`
- [X] T029 [US2] Add `GET /customers` route to `src/app/Domains/Customer/Routes/v1/api.php` with middleware `permissions:customers.view` pointing to `CustomerController@index`

**Checkpoint**: User Story 2 fully functional — admin sees the customer list, search and date filters work, ordering is correct, cursor pagination works, 403 for unauthorized users.

---

## Phase 5: User Story 3 — Customer Details with Live Financial Summary (Priority: P1)

**Goal**: Admin views a single customer's basic data and a live-computed financial summary via `GET /api/v1/customers/{id}`. Basic data is from the `clients` table; the financial summary is a live aggregate from `installments` JOIN `contracts WHERE contracts.status IN ('active', 'completed')`.

**Independent Test**: `GET` on a customer with mixed-status installments returns the correct aggregates matching a manual SQL computation; a customer with no active/completed contracts returns zeros; a non-existent id returns 404; query log shows the live `installments` + `contracts` JOIN (not the MV); 403 for unauthorized users.

### Tests for User Story 3

> **NOTE: Write these tests FIRST, ensure they FAIL before implementation**

- [X] T030 [P] [US3] Feature test for show-customer scenarios in `src/tests/Feature/Customer/ShowCustomerTest.php` (10 scenarios from `contracts/get-customer.md`), including a query-log assertion that the financial summary reads live from `installments JOIN contracts` (not the MV)

### Implementation for User Story 3

- [X] T031 [US3] Implement `CustomerService::getCustomerDetail(int $id): array` in `src/app/Domains/Customer/Services/CustomerService.php` (depends on T010, T021): `Client::findOrFail($id)` (throws `ModelNotFoundException` → 404 via ExceptionMappings); run a single live `DB::table('installments')->join('contracts', ...)` aggregate query with the six SELECTs from `contracts/get-customer.md`; merge client data + summary into the array returned via `CustomerDetailResource`
- [X] T032 [US3] Implement `CustomerController::show(int $id)` in `src/app/Domains/Customer/Http/Controllers/CustomerController.php` (depends on T031): call service; return `success(data: $result, msg: 'تم جلب بيانات العميل بنجاح.')`
- [X] T033 [US3] Add `GET /customers/{id}` route to `src/app/Domains/Customer/Routes/v1/api.php` with middleware `permissions:customers.view` pointing to `CustomerController@show`; ensure route model binding is NOT used (id is passed explicitly and resolved by the service via `findOrFail`)

**Checkpoint**: User Story 3 fully functional — admin can inspect a customer's live financial picture.

---

## Phase 6: User Story 4 — Customer Update (Priority: P1)

**Goal**: Admin updates a customer's name, phone, description, or avatar via `PUT /api/v1/customers/{id}`. Updates are allowed even with active or completed contracts; `client_type_flags` is NEVER modified; a new avatar replaces the old one (old file deleted from storage).

**Independent Test**: `PUT` with valid fields returns 200 with the updated record; `client_type_flags` is unchanged after update; updating a customer with active contracts succeeds; a new avatar replaces the old (old file removed from storage); a 422 is returned for bad phone format or duplicate phone; 404 for missing id; 403 for unauthorized users.

### Tests for User Story 4

> **NOTE: Write these tests FIRST, ensure they FAIL before implementation**

- [X] T034 [P] [US4] Feature test for update-customer scenarios in `src/tests/Feature/Customer/UpdateCustomerTest.php` (19 scenarios from `contracts/update-customer.md`), including an explicit assertion that `client_type_flags` does not change even when the request body attempts to send it

### Implementation for User Story 4

- [X] T035 [P] [US4] Create `UpdateCustomerRequest` in `src/app/Domains/Customer/Http/Requests/UpdateCustomerRequest.php` with rules from `data-model.md` (name sometimes 1–150; phone sometimes Syrian format + `Rule::unique('clients', 'phone')->ignore($this->route('id'))`; description sometimes nullable max 1000; avatar sometimes nullable image mimes jpg/jpeg/png/webp max 5 MB); include a `withValidator()` rule that requires at least one field to be present; Arabic `messages()`
- [X] T036 [US4] Implement `CustomerService::updateCustomer(int $id, UpdateCustomerRequest $request): array` in `src/app/Domains/Customer/Services/CustomerService.php` (depends on T010, T035): `Client::findOrFail($id)`; `DB::transaction`; `$oldAvatar = $client->avatar;`; `$client->fill($request->only(['name', 'phone', 'description']))` (deliberately excludes `client_type_flags`); `$client->save();`; if avatar present, `Storage::disk('public')->putFile('avatars', $file)`, set `$client->avatar`, `save()`, and `Storage::disk('public')->delete($oldAvatar)` on best-effort; commit; return array
- [X] T037 [US4] Implement `CustomerController::update(UpdateCustomerRequest $request, int $id)` in `src/app/Domains/Customer/Http/Controllers/CustomerController.php` (depends on T036): call service; return `success(data: $result, msg: 'تم تحديث بيانات العميل بنجاح.')`
- [X] T038 [US4] Add `PUT /customers/{id}` route to `src/app/Domains/Customer/Routes/v1/api.php` with middleware `permissions:customers.update` pointing to `CustomerController@update`

**Checkpoint**: User Story 4 fully functional — admin can update a customer without disturbing the `customer` flag and without leaving orphan avatar files.

---

## Phase 7: User Story 5 — Permission Seeding Verification (Priority: P1)

**Goal**: Verify the `CustomerPermissionsSeeder` produces exactly the three required permissions and assigns them to the existing Admin. Also verify the Client model enforces hard immutability at the Eloquent level (no `saveQuietly` for flag changes).

**Independent Test**: Running the seeder twice is idempotent and the permission set is exactly `{customers.view, customers.create, customers.update}`. The Admin user has all three via `hasPermissionTo`. Calling `$client->delete()` (or `Client::destroy($id)` or attempting to soft-delete) throws `ClientImmutableException`. Calling `markAsCustomer()` fires the `saving` model event (i.e., does NOT use `saveQuietly()`).

### Tests for User Story 5

> **NOTE: Write these tests FIRST, ensure they FAIL before implementation**

- [X] T039 [P] [US5] Unit test for Client immutability in `src/tests/Unit/Domains/Customer/ClientImmutabilityTest.php` covering `$client->delete()`, `Client::destroy($id)`, and a forced `deleting` event (verifying all three throw `ClientImmutableException` with Arabic message)
- [X] T040 [P] [US5] Unit test for `client_type_flags` save behavior in `src/tests/Unit/Domains/Customer/ClientTypeFlagsTest.php` covering `markAsCustomer()`, `markAsAgent()`, `markAsInvestor()` (verifying each fires the `saving` Eloquent event, i.e., `save()` is used and `saveQuietly()` is not)
- [X] T041 [US5] Feature test for the seeder in `src/tests/Feature/Auth/CustomerPermissionsSeederTest.php` covering: (a) first run creates exactly 3 permission records with the right names and guard; (b) the Admin has all 3 via `hasPermissionTo`; (c) second run is idempotent (no duplicate records); (d) a user with only `customers.view` gets 403 on `POST /customers` and 200 on `GET /customers`

**Checkpoint**: User Story 5 verified — seeder works, client is hard-immutable, flag updates use `save()` and fire model events. Combined with US1–US4, the entire customer domain is operational and constitutionally compliant.

---

## Phase 8: Polish & Cross-Cutting Concerns

**Purpose**: Improvements and verifications that affect all user stories. Independent of the user story phases.

- [X] T042 [P] Run `composer pint` (or the project's configured linter) across the touched files and resolve any style violations
- [X] T043 [P] Run the full test suite via `php artisan test` and confirm all tests in `tests/Feature/Customer/` and `tests/Unit/Domains/Customer/` pass; verify coverage ≥ 80% on the Customer domain files (per constitution Section 2.V)
- [X] T044 Run a final Constitution Check grep to ensure no constitutional regression snuck in: `grep -r "saveQuietly" src/app/Domains/Customer` returns empty; `find src/app/Domains/Customer -type d -name "Repositories" -o -name "DTO" -o -name "Pipelines"` returns empty; `grep -rn "response()->json\|DB::raw\|DB::select" src/app/Domains/Customer/Http/Controllers` returns empty; `cat src/routes/api.php` contains only the guidance comment
- [X] T045 [P] Manual API smoke test following `quickstart.md` Phase 3: create a customer, list, show, update; attempt a `DELETE` and confirm the route does not exist; log in as a non-Admin user and confirm 403 on every customer endpoint
- [X] T046 Update the plan reference in `AGENTS.md` between the `<!-- SPECKIT START -->` and `<!-- SPECKIT END -->` markers to point to `specs/005-customer-management-reexec/plan.md` (only if `AGENTS.md` exists; otherwise skip and document in the commit message)
- [X] T047 Commit all changes following the project's commit convention (`feat(SP-04): <short summary>` per constitution Section 8) and run `php artisan test` one final time to ensure a green baseline

---

## Dependencies & Execution Order

### Phase Dependencies

- **Setup (Phase 1)**: No dependencies — can start immediately
- **Foundational (Phase 2)**: Depends on Setup completion (T001–T005) — **BLOCKS** all user stories
- **User Stories (Phases 3–7)**: All depend on Foundational phase completion (T006–T017)
  - US1 (Create) → US2 (List), US3 (Details), US4 (Update) can be developed independently after Foundational, but each US2/US3/US4 test needs an existing customer, so US1 implementation is the most natural MVP target
  - US5 verification depends on Foundational (T010) being done
- **Polish (Phase 8)**: Depends on all user stories being complete

### User Story Dependencies

- **US1 (Create)**: Can start after Foundational (Phase 2) — No dependencies on other stories
- **US2 (List)**: Can start after Foundational (Phase 2) — Tests need seeded data, but the endpoint code has no runtime dependency on US1
- **US3 (Details)**: Can start after Foundational (Phase 2) — Tests need seeded data
- **US4 (Update)**: Can start after Foundational (Phase 2) — Tests need seeded data
- **US5 (Permissions)**: Depends on the Client model (T010) and the seeder (T012) being complete; verification tests in T041 reference customer endpoints, so the routes from US1–US4 should exist for full coverage of the 403 scenarios

### Within Each User Story

- Tests (T018, T025, T030, T034, T039, T040, T041) MUST be written FIRST and MUST FAIL before implementation
- Models (where applicable) before services
- Services before controllers
- Controllers before routes
- Story complete before moving to the next priority

### Parallel Opportunities

- T001, T002, T003 can run in parallel (different directories or files)
- T010 and T011 can run in parallel (different model files)
- T018, T020, T021 can run in parallel (test file, two resources, no dependency)
- T019, T020, T021 can run in parallel (request, list resource, detail resource)
- T025, T026 can run in parallel (test file, request file)
- T030, T031 (test + service method) can be drafted in parallel; test must fail first
- T034, T035 can run in parallel (test file, request file)
- T039, T040 can run in parallel (two unit test files)
- T042, T043, T044, T046 can run in parallel (different verification commands)
- US1, US2, US3, US4 (in different phases) can be worked on in parallel by different team members **after** Foundational is complete

---

## Parallel Example: User Story 1

```bash
# Launch the test + the two resources for User Story 1 together:
Task: "Feature test for create-customer in src/tests/Feature/Customer/CreateCustomerTest.php"
Task: "Create CustomerListResource in src/app/Domains/Customer/Http/Resources/CustomerListResource.php"
Task: "Create CustomerDetailResource in src/app/Domains/Customer/Http/Resources/CustomerDetailResource.php"
```

```bash
# After the above completes, launch the remaining US1 tasks sequentially (they have hard dependencies):
Task: "Create StoreCustomerRequest in src/app/Domains/Customer/Http/Requests/StoreCustomerRequest.php"
Task: "Implement CustomerService::createCustomer in src/app/Domains/Customer/Services/CustomerService.php"
Task: "Implement CustomerController::store in src/app/Domains/Customer/Http/Controllers/CustomerController.php"
Task: "Add POST route to src/app/Domains/Customer/Routes/v1/api.php"
```

---

## Implementation Strategy

### MVP First (US1 + US5 only)

1. Complete Phase 1: Setup (T001–T005)
2. Complete Phase 2: Foundational (T006–T017) — CRITICAL, blocks all stories
3. Complete Phase 3: User Story 1 (T018–T024)
4. Complete Phase 7: User Story 5 verification (T039–T041)
5. **STOP and VALIDATE**: run `php artisan test` and the smoke test; deploy/demo if the create endpoint + permission enforcement are working

This MVP delivers: an admin can create a customer, the customer record exists in `clients` with the correct flag, and the permission system is in place. Everything else (list, show, update) is incremental.

### Incremental Delivery

1. Complete Phase 1 + Phase 2 → Foundation ready
2. Add US1 (Create) → Test independently → **MVP deploy/demo**
3. Add US2 (List) → Test independently → Deploy/Demo
4. Add US3 (Details) → Test independently → Deploy/Demo
5. Add US4 (Update) → Test independently → Deploy/Demo
6. Each story adds value without breaking previous stories

### Parallel Team Strategy

With multiple developers (after Foundational is complete):

1. Team completes Setup + Foundational together
2. Once Foundational is done:
   - Developer A: User Story 1 (Create) — start here; everything else can be tested against records A creates
   - Developer B: User Story 2 (List) — can work in parallel; the list endpoint doesn't depend on US1 code
   - Developer C: User Story 3 (Details) — can work in parallel
   - Developer D: User Story 4 (Update) — can work in parallel
3. All four stories integrate independently and tests do not conflict

---

## Notes

- [P] tasks = different files, no dependencies
- [Story] label maps task to specific user story for traceability
- Each user story is independently completable and testable
- Verify tests fail before implementing
- Commit after each task or logical group (Setup commits, Foundational commit, then one commit per US)
- Stop at any checkpoint to validate a story independently
- Avoid: vague tasks, same-file conflicts, cross-story dependencies that break independence
- The export feature (User Story 6 in the previous spec) was removed during clarification Q3 and is intentionally absent from this task list — do NOT add it back
- The previous SP-04 spec's "User" model is not used; this re-execution operates exclusively on the unified `clients` table per constitution Section 3.2

