# Implementation Plan: SP-05 — Agent Management & Investor Logic

**Branch**: `006-agent-investor` | **Date**: 2026-06-15 | **Spec**: [spec.md](spec.md)

**Input**: Feature specification from `/specs/006-agent-investor/spec.md`

**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/templates/plan-template.md` for the execution workflow.

## Summary

Implement the **Agent Management & Investor Logic** domain for the Tamkeen system. This delivers eight REST endpoints under `/api/v1/agents*` for managing agents, their share movements, and their referred-customer financial footprint. The implementation lives in a new `app/Domains/Agent/` domain directory following the project's Clean Architecture (Constitution §I) and Domain Separation (Constitution §II) patterns. The two technical pillars are:

1. **Computed, not stored** — `computed_profit` and `total_shares` are SQL aggregations, never persisted; rendered through reusable Eloquent Scopes on the `Client` model (in an `AgentScopes` trait) that use `bcmath` for all financial math (BR-006-1/2). This follows the project's Scopes-default convention (ADR-022) and avoids introducing a separate service class for query reuse.
2. **Logical-only share ledger** — `agent_shares_logs` is an append-only table; PATCH/DELETE flip a `status` column under a `lockForUpdate()` pessimistic row lock (per Q1 clarification); the 30-day correction window and "most recent active" rule are enforced in the service layer (BR-005-5/6/7).

The implementation inherits the unified `clients` table and `Client` model from SP-03/SP-04 — no new tables, no migrations required. The eight endpoints expose the eight user stories in spec §3.1..3.8 plus the immutability assertion in §3.9. Performance targets (p95 ≤ 200ms) are met by index-backed queries (existing `idx_clients_type_flags` GIN, `idx_contracts_agent_status` composite, `idx_shares_logs_client_status_created` composite).

## Technical Context

**Language/Version**: PHP 8.3+ (Constitution, `03_01_SYSTEM_OVERVIEW.md`)

**Primary Dependencies**:
- Laravel 12+ (framework)
- Laravel Sanctum (auth — existing from SP-01)
- Spatie Laravel Permission (RBAC — existing from SP-01, `guard_name='admin'`)
- PostgreSQL 15+ with JSONB + GIN index support
- PHPUnit (testing — existing)
- PHP `bcmath` extension (Constitution §I ALWAYS; BR-006-1)

**Storage**: PostgreSQL 15+ — unified `clients` table (existing from SP-02), `agent_shares_logs` table (existing from SP-02), `contracts` table (existing from SP-02), no new migrations needed for SP-05.

**Testing**: PHPUnit (existing). Unit tests for computed fields (100% coverage per Constitution §III); Feature tests for every endpoint; `RefreshDatabase` trait.

**Target Platform**: Linux server (Laravel web service); timezone `Asia/Damascus`.

**Project Type**: Web service (REST API), internal admin tool. No frontend, no mobile-app code in this spec.

**Performance Goals**:
- p95 ≤ 200ms for `GET /api/v1/agents` over 10,000 agents (SP-05-NFR-P-001)
- p95 ≤ 200ms for `GET /api/v1/agents/{id}` for an agent with 50 active contracts and 500 installments (SP-05-NFR-P-003)
- p95 ≤ 200ms for all share-movement endpoints (SP-05-NFR-P-005..008)
- p95 ≤ 300ms for `POST /api/v1/agents` (single transaction; SP-05-NFR-P-002)
- General baseline: every read endpoint ≤ 500ms (Constitution, `03_03`)

**Constraints**:
- Strict Clean Architecture layers (Constitution §I): Controller → Service → Model/Scope → Resource → Response
- No business logic in Controllers or Models
- All financial math uses `bcmath` (no `float`/`double`)
- `client_type_flags` and `reference_number` MUST NEVER appear in API responses (Constitution NEVER, `07_06`)
- No physical DELETE on `agent_shares_logs` (BR-005-7, AC-011, SP-05-NFR-D-005)
- 30-day correction window is bounded (BR-005-5)
- No `DELETE /api/v1/agents/{id}` route (BR-001-1, AC-004)
- No `saveQuietly()` for flag updates (Constitution NEVER)
- All user-facing strings via `__()` translation keys (Constitution ALWAYS, `07_02`)
- `client_type_flags` updates must be inside the same DB transaction as the triggering write (BR-001-5/6, AC-010)
- PATCH/DELETE share log must use `lockForUpdate()` (Q1 clarification, SP-05-NFR-D-006)
- Withdrawal that would yield negative balance is rejected with HTTP 422 (Q2, BR-005-8)

**Scale/Scope**:
- 10,000 agents target (load test: AC-030)
- Single agent can hold 50+ contracts and 500+ installments
- Eight endpoints; three FormRequests; three Resources; one Service; one Service Provider
- 40 Functional Requirements, 22 Non-Functional Requirements, 39 Acceptance Criteria
- Test coverage target: 100% for computed-field unit tests; 80% overall for the agent domain (Constitution §III)

## Constitution Check

*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*

### §I Clean Architecture — Strict Layers

| Gate | Status | Evidence |
|------|--------|----------|
| Controllers are thin (≤ 5 lines of orchestration per method) | **PASS** | SP-05-FR-033..040 enforce; AC-033 verifies |
| No business logic in Controllers | **PASS** | All logic in `AgentService` / `AgentSharesService`; financial aggregation pushed to PostgreSQL via `AgentScopes` trait on `Client` (per ADR-022 Scopes-default) |
| No business logic in Models | **PASS** | Models are Eloquent with `$fillable`; scopes only; no logic in `booted()` other than immutability |
| Service expresses one Use Case | **PASS** | Two services, each one Use Case: `AgentService` (CRUD + detail orchestration), `AgentSharesService` (share movement). Computed-field aggregation is query reuse, expressed as Model Scopes (see §II ADR-022) — not a separate Use Case. |
| Model Scopes for reusable queries | **PASS** | `ScopeActive`, `ScopeForAgent`, `ScopeRecentFirst` traits per Model |
| Repository Pattern NOT default | **PASS** | Direct Eloquent + Scopes (Constitution §I rule) |
| DTOs only when justified | **PASS** | `AgentSummaryDTO` MAY be created if Service output is reused; defer to Plan-phase decision |
| `BaseResource` permitted for shared fields | **PASS** | `BaseAgentResource` will host list/detail common fields (SP-05-FR-038) |
| `$fillable` explicit, no `$guarded=['*']` | **PASS** | SP-05-NFR-S-009, AC-034 |

### §II Domain Separation

| Gate | Status | Evidence |
|------|--------|----------|
| Domain directory `app/Domains/Agent/` | **PASS** | SP-05-FR-035 |
| Service Provider loads domain routes | **PASS** | `AgentServiceProvider` registered in `bootstrap/providers.php` |
| `routes/api.php` remains empty | **PASS** | SP-05-FR-035, AC-032 |

### §III Test-First (NON-NEGOTIABLE)

| Gate | Status | Evidence |
|------|--------|----------|
| PHPUnit for Unit Tests; pure (no DB) for calculators | **PASS** | `ComputedFieldsTest` will be 100% unit, no DB |
| Feature Tests for every API Endpoint | **PASS** | AC-037 mandates happy + 2 error + 1 edge per endpoint |
| Coverage target 80% minimum | **PASS** | AC-036 (100% for computed fields), AC-038 (80% overall) |
| No deletion of failing test | **PASS** | Constitution §III ALWAYS |

### §IV Spec-Driven Development

| Gate | Status | Evidence |
|------|--------|----------|
| Spec exists in `.specify/specs/` | **PASS** | This is the spec |
| Conflicts resolved in favor of Constitution | **PASS** | Spec cross-references Constitution in §11 |
| Spec verifies compliance | **PASS** | §11 cross-reference summary |

### §V Immutability Principle

| Gate | Status | Evidence |
|------|--------|----------|
| Agents can never be deleted | **PASS** | SP-05-FR-011, AC-020, AC-024 |
| Immutability enforced at multiple levels | **PASS** | Code (`Client::booted()` + `delete()` override — existing from SP-03) + DB (`ON DELETE RESTRICT` — existing from SP-02) |
| Share log rows never physically deleted | **PASS** | SP-05-NFR-D-005, AC-025 |

### §VI Installment Due Date Calculation

**N/A** — SP-05 does not generate installment due dates. Future Spec (Contract Management) owns this.

### ALWAYS / ASK / NEVER Compliance

| Rule | Status | Evidence |
|------|--------|----------|
| ALWAYS: tests before commit | **PASS** | Project convention |
| ALWAYS: `bcmath` for financial math | **PASS** | SP-05-FR-014, AC enforced by code review |
| ALWAYS: Unified Response | **PASS** | SP-05-FR-037 |
| ALWAYS: Validate business rules in Service | **PASS** | All BR-005 rules enforced in `AgentSharesService` |
| ALWAYS: FormRequest for validation | **PASS** | SP-05-FR-036, 3 FormRequests per write endpoint |
| ALWAYS: Queues for heavy processing | **PASS** | No heavy processing in SP-05 (no exports) |
| ALWAYS: Model Scopes for reusable queries | **PASS** | `ScopeActive`, `ScopeForAgent`, `ScopeRecentFirst` |
| ALWAYS: `installment_id` for payment linking | **N/A** | No payment operations in SP-05 |
| ALWAYS: `FileService` facade for files | **PASS** | No file operations in SP-05 (no avatar on agents per OQ-8) |
| ALWAYS: Arabic `fake('ar_SA')` in factories | **PASS** | Inherited from SP-03/SP-04 |
| ALWAYS: Translated exception defaults via `__()` | **PASS** | SP-05-FR-039, AC-028 |
| ALWAYS: Handle varying month lengths | **N/A** | No date math in SP-05 |
| ASK FIRST: schema modification | **N/A** | No new tables or migrations |
| ASK FIRST: new library/package | **N/A** | No new dependencies |
| NEVER: business logic in Controller/Model | **PASS** | See §I |
| NEVER: float/double in financial | **PASS** | SP-05-FR-014 |
| NEVER: ignoring LIFO | **N/A** | No payment deletion in SP-05 |
| NEVER: contract modification after active | **N/A** | No contract operations in SP-05 |
| NEVER: raw SQL without comment | **PASS** | No raw SQL expected |
| NEVER: large reports sync | **PASS** | No reports in SP-05 |
| NEVER: commit credentials | **PASS** | No credentials in SP-05 |
| NEVER: delete failing test | **PASS** | Constitution §III |
| NEVER: Repository Pattern as default | **PASS** | Using Scopes only |
| NEVER: DTOs without justification | **PASS** | Will justify any DTO use |
| NEVER: modify payment amount | **N/A** | No payments in SP-05 |
| NEVER: guess installment for payment | **N/A** | No payments in SP-05 |
| NEVER: `saveQuietly()` for flag updates | **PASS** | SP-05-NFR-D-002 |
| NEVER: hardcode language strings | **PASS** | SP-05-FR-039 |
| NEVER: delete any client entity | **PASS** | SP-05-FR-011 |
| NEVER: return `client_type_flags` in any API | **PASS** | SP-05-FR-030, AC-026 |

**Constitution Check Result: ALL GATES PASS** — no violations, no complexity tracking needed.

## Project Structure

### Documentation (this feature)

```text
specs/006-agent-investor/
├── plan.md              # This file (/speckit.plan command output)
├── research.md          # Phase 0 output (/speckit.plan command)
├── data-model.md        # Phase 1 output (/speckit.plan command)
├── quickstart.md        # Phase 1 output (/speckit.plan command)
├── contracts/           # Phase 1 output (/speckit.plan command)
│   └── agents.openapi.yaml
└── tasks.md             # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan)
```

### Source Code (repository root)

The implementation creates a new domain directory `app/Domains/Agent/` and registers a service provider. All other directories (existing from SP-01..SP-04) are untouched.

```text
app/
├── Domains/
│   ├── Agent/                                       ← NEW
│   │   ├── Http/
│   │   │   ├── Controllers/
│   │   │   │   ├── AgentController.php              ← index, store, show, update
│   │   │   │   └── AgentSharesController.php        ← shares POST/PATCH/DELETE
│   │   │   ├── Requests/
│   │   │   │   ├── StoreAgentRequest.php
│   │   │   │   ├── UpdateAgentRequest.php
│   │   │   │   ├── StoreAgentShareRequest.php
│   │   │   │   ├── UpdateAgentShareRequest.php
│   │   │   │   └── ListAgentsRequest.php
│   │   │   └── Resources/
│   │   │       ├── BaseAgentResource.php            ← common fields (per 07_06 inheritance)
│   │   │       ├── AgentListResource.php            ← for GET /agents
│   │   │       ├── AgentDetailResource.php          ← for GET /agents/{id}
│   │   │       ├── AgentShareLogResource.php        ← for GET /agents/{id}/shares-log
│   │   │       └── AgentShareCreatedResource.php    ← for POST/PATCH/DELETE /shares
│   │   ├── Services/
│   │   │   ├── AgentService.php                     ← UC: agent CRUD + detail orchestration
│   │   │   └── AgentSharesService.php               ← UC: share movement (add/withdraw/modify/delete)
│   │   ├── Models/Scopes/                           ← (scope traits)
│   │   │   ├── AgentSharesLogScopes.php              ← scopes for share log rows
│   │   │   └── AgentScopes.php                      ← scopes for Client aggregates (withFinancialSummary, withSharesBalance, withInvestorProfit)
│   │   ├── AgentServiceProvider.php                 ← loads Routes/v1/api.php
│   │   └── Routes/
│   │       └── v1/
│   │           └── api.php
│   ├── Customer/                                    ← (existing from SP-04, used for Client model + RefreshCustomerListingJob)
│   ├── Contract/                                    ← (existing from SP-02, used for contracts/installments)
│   ├── Payment/                                     ← (existing from SP-02)
│   ├── Analytics/                                   ← (existing from SP-02)
│   ├── Notification/                                ← (existing from SP-02)
│   └── Auth/                                        ← (existing from SP-01)
├── Exceptions/Business/                              ← (existing)
│   ├── ClientImmutableException.php                 ← (existing from SP-03)
│   └── (new) InsufficientShareBalanceException.php ← 422 insufficient_balance
├── Helpers/                                         ← (existing from SP-03)
├── Facades/                                         ← (existing)
└── Shared/                                          ← (existing from SP-03)
    └── Traits/
        └── HasReferenceNumber.php                   ← (existing — used by Client)

bootstrap/
├── app.php                                          ← (existing — ExceptionMappings registered here)
└── providers.php                                    ← AgentServiceProvider added here

routes/
└── api.php                                          ← REMAIN EMPTY

database/
├── factories/
│   └── (no new factory — extend `ClientFactory` from SP-03/SP-04 with `asAgent()` / `asInvestor()` / `asAgentAndInvestor()` state methods)
├── migrations/                                      ← (NO new migrations — all tables exist)
└── seeders/
    └── RolePermissionSeeder.php                     ← (existing — must add agent permissions)

lang/ar/errors/
├── agent.php                                        ← (new) agent.not_found, agent.phone_unique
└── shares.php                                       ← (new) shares.not_latest, shares.lock_period_expired, shares.insufficient_balance
lang/ar/success/
├── agent.php                                        ← (new) agent.created, agent.updated, shares.added, etc.

tests/
├── Unit/Services/Agent/
│   └── AgentSharesServiceTest.php                   ← calculation tests
├── Unit/Models/
│   ├── AgentScopesTest.php                          ← 100% coverage (scope SQL generation, pure Unit, no DB)
│   └── AgentSharesLogScopesTest.php
└── Feature/Agent/
    ├── CreateAgentTest.php
    ├── ListAgentsTest.php
    ├── ViewAgentTest.php
    ├── UpdateAgentTest.php
    ├── AddSharesTest.php
    ├── WithdrawSharesTest.php                       ← includes BR-005-8 negative-balance rejection
    ├── ModifyShareLogTest.php                       ← includes 30-day lock + concurrency
    ├── DeleteShareLogTest.php                       ← includes 30-day lock
    ├── ViewSharesLogTest.php
    └── AgentImmutabilityTest.php                    ← AC-024
```

**Structure Decision**: Single Laravel project (existing layout from SP-01). The Agent domain is a peer of Customer/Contract/Payment/Analytics, with its own ServiceProvider and routes file. No Option 2/3 needed.

## Complexity Tracking

> **Fill ONLY if Constitution Check has violations that must be justified**

No violations — table empty.

## Phase 0: Research Output

See [research.md](research.md) for the 5 Phase 0 research items (best practices for Laravel pessimistic locks, JSONB flag updates, PostgreSQL `FOR UPDATE` semantics, computed-field patterns, MV refresh coordination).

## Phase 1: Design Output

- **Data Model**: see [data-model.md](data-model.md) — entity-by-entity field list, relationships, validation rules, state transitions.
- **API Contracts**: see [contracts/agents.openapi.yaml](contracts/agents.openapi.yaml) — OpenAPI 3.0 spec for the 8 endpoints.
- **Quickstart**: see [quickstart.md](quickstart.md) — developer onboarding for implementing SP-05.
- **Agent Context**: updated (see Post-Phase 1 section below).

## Post-Phase 1: Constitution Re-evaluation

Re-running the Constitution Check after Phase 1 design: all gates still PASS. No new complexity introduced. The design:

- Creates 5 new Resources: `BaseAgentResource` (shared base), `AgentListResource`, `AgentDetailResource`, `AgentShareLogResource`, `AgentShareCreatedResource` — no separate `AgentUpdatedResource` (PUT reuses `BaseAgentResource` directly)
- Creates 3 new Services (one per Use Case)
- Creates 5 new FormRequests (one per write endpoint, plus the list request)
- Creates 1 new domain exception (`InsufficientShareBalanceException`)
- Creates 3 new translation files (`agent.php`, `shares.php` under `errors/`; `agent.php` under `success/`)
- Creates 1 new ServiceProvider (`AgentServiceProvider`)
- Creates ~9 new test files
- Adds 1 entry to `bootstrap/providers.php` (register AgentServiceProvider)
- Adds 1 entry to `ExceptionMappings.php` (the new exception)
- Adds 5 new permissions to `RolePermissionSeeder` (`agents.view`, `agents.create`, `agents.update`, `agents.manage_shares`, `agents.view_shares_log` — though some are likely already added in SP-04)

No new tables, no new migrations, no new third-party packages. No new raw SQL (all queries via Eloquent + Scopes). No float/double in financial math (all via `bcmath`).

**Final Constitution Check: ALL GATES PASS.**

## Notes for /speckit.tasks (Phase 2)

The task list should decompose by:
1. Domain scaffolding (directory + ServiceProvider + routes file + empty Controller stubs)
2. Model setup (if any new Model needs to be created — likely none; existing `Client` and a new `AgentSharesLog` model)
3. Migration check (none required)
4. FormRequests (5)
5. Resources (4 — base + 3 children)
6. Services (3)
7. Controllers (2)
8. Exception + mapping (1)
9. Translations (3 files)
10. Permissions seeder update
11. Tests: Unit first (TDD per Constitution §III)
12. Tests: Feature (happy + error + edge per endpoint)
13. Performance tests (AC-030, AC-031)
14. Architecture compliance tests (AC-032..035)
15. Integration check (full request → response roundtrip)
