# Implementation Plan: Customer Management (SP-04)

**Branch**: `development` | **Date**: 2026-06-04 | **Spec**: [spec.md](./spec.md)

**Input**: Feature specification from `/specs/004-customer-management/spec.md`

## Summary

Build complete Customer Domain with CRUD, Search, Financial Summary, and Async Export. Customer is a User who has at least one contract. This domain provides:
- Customer CRUD operations (no delete - Constitution prohibits user deletion)
- Complex listing with cursor pagination, search, time filtering, and sorting by nearest pending/overdue installment due date
- Customer details with computed financial summary using SQL aggregation
- Async export via queue job for Excel/PDF generation

## Technical Context

**Language/Version**: PHP 8.3+ (Laravel 11+)

**Primary Dependencies**:
- `spatie/laravel-permission` (RBAC)
- `maatwebsite/excel` (Excel export)
- `barryvdh/laravel-dompdf` (PDF export)
- `laravel/sanctum` (API authentication)

**Storage**: PostgreSQL 15+ (database queue driver configured)

**Testing**: PHPUnit + Feature Tests per endpoint

**Target Platform**: Linux server (API-first backend)

**Performance Goals**: Cursor pagination for 10k+ customers, <1s response for list endpoints, <1s for export dispatch

**Constraints**: 
- Cursor-based pagination (not offset)
- SQL aggregation for financial calculations (not PHP loops)
- Async export via queue (not synchronous)
- No delete endpoint for customers (Constitution)

**Scale/Scope**: 10,000+ customers, 100,000+ contracts

## Constitution Check

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

### Gates Evaluated

| Gate | Status | Evidence |
|------|--------|----------|
| Clean Architecture Layers | ✅ PASS | Controller → Service → Repository → Model, no business logic in controller |
| Domain Separation | ✅ PASS | Customer domain under `app/Domains/Customer/` following SP-03 pattern |
| Domain Routing | ✅ PASS | Routes in `app/Domains/Customer/Routes/v1/api.php`, loaded via ServiceProvider |
| Response Helpers | ✅ PASS | All responses use `ApiResponseHelper` functions (`success()`, `resourceCreatedResponse()`, etc.) |
| Cursor Pagination | ✅ PASS | Using `cursorPaginate()` from BaseRepository |
| Financial Calculations (bcmath) | ✅ PASS | DECIMAL(10,2) in DB, SQL SUM/COUNT aggregation |
| No Delete for Users | ✅ PASS | User model has `deleting` override throwing `ImmutabilityViolationException` |
| Async Export via Queues | ✅ PASS | Export job dispatched to `database` queue driver |
| Exception Handling Pattern | ✅ PASS | Using `config/ExceptionClassToMethod.php` mapping |

### Boundary System Check

| Rule | Compliance |
|------|------------|
| Use Queues for heavy processing (Export) | ✅ Complied |
| No business logic in Controller/Model | ✅ Complied |
| Use FormRequest for validation | ✅ Will comply |
| Use bcmath for financial calculations | ✅ SQL aggregation |
| Return Unified Response from every endpoint | ✅ Via ApiResponseHelper |

## Project Structure

### Documentation (this feature)

```text
specs/004-customer-management/
├── plan.md              # This file (/speckit.plan command output)
├── research.md          # Phase 0 output - NEEDS CLARIFICATION: Sorting query details, export file location
├── data-model.md        # Phase 1 output
├── quickstart.md        # Phase 1 output
└── tasks.md             # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan)
```

### Source Code (repository root)

```text
src/
├── app/
│   ├── Domains/
│   │   └── Customer/
│   │       ├── CustomerServiceProvider.php      # Registers routes, bindings
│   │       ├── Models/
│   │       │   └── Customer.php                  # User model extension if needed
│   │       ├── Repositories/
│   │       │   ├── Contracts/
│   │       │   │   └── CustomerRepositoryInterface.php
│   │       │   └── Eloquent/
│   │       │       └── EloquentCustomerRepository.php
│   │       ├── Services/
│   │       │   ├── CustomerService.php           # Business logic per use case
│   │       │   └── CustomerExportService.php     # Export file generation
│   │       ├── Http/
│   │       │   ├── Controllers/
│   │       │   │   └── CustomerController.php
│   │       │   ├── Requests/
│   │       │   │   ├── CreateCustomerRequest.php
│   │       │   │   ├── UpdateCustomerRequest.php
│   │       │   │   └── ExportCustomerRequest.php
│   │       │   └── Resources/
│   │       │       ├── CustomerResource.php
│   │       │       ├── CustomerListResource.php    # For list endpoint with nearest_due_date
│   │       │       ├── CustomerDetailResource.php
│   │       │       └── CustomerExportResource.php
│   │       ├── Jobs/
│   │       │   └── ExportCustomersJob.php        # Async export job
│   │       └── Routes/
│   │           └── v1/
│   │               └── api.php                   # Customer routes
│   ├── Helpers/
│   │   └── ApiResponseHelper.php                # Already exists (SP-03)
│   ├── Models/
│   │   └── User.php                             # Already exists with immutability
│   └── Shared/
│       └── Repositories/
│           ├── Contracts/
│           │   └── BaseRepositoryInterface.php   # Already exists with cursorPaginate
│           └── Eloquent/
│               └── BaseEloquentRepository.php    # Already exists
├── config/
│   └── queue.php                                # Already configured with database driver
├── database/
│   └── migrations/
│       └── (No migrations needed - uses existing users, contracts, installments tables)
├── routes/
│   └── api.php                                  # Empty (domain routing pattern)
└── tests/
    ├── Feature/
    │   └── Customer/
    │       ├── CustomerCrudTest.php
    │       ├── CustomerListTest.php
    │       ├── CustomerDetailTest.php
    │       └── CustomerExportTest.php
    └── Unit/
        └── Customer/
            └── CustomerServiceTest.php
```

**Structure Decision**: Laravel Domain-Driven Design pattern. Customer is a "User with contracts" - no separate customer table. Uses existing `users`, `contracts`, and `installments` tables. Export files stored in `storage/app/exports/`.

## Complexity Tracking

> No violations requiring justification. All requirements align with Constitution.

## Clarifications

### Q1: Export file storage location

**Context**: The spec mentions storing generated export files but doesn't specify location.

| Option | Description |
|--------|-------------|
| A | `storage/app/exports/` (Laravel standard) |
| B | `storage/app/public/exports/` (web accessible) |
| C | Custom path via config |

**Recommended:** Option A - `storage/app/exports/` - follows Laravel conventions, files not directly web-accessible.

### Q2: Export file naming convention

**Context**: Need consistent naming for generated files.

| Option | Description |
|--------|-------------|
| A | `customers_export_{YYYYMMDD}_{timestamp}.{format}` |
| B | `customers_{from_date}_to_{to_date}.{format}` |
| C | `customer_report_{uuid}.{format}` |

**Recommended:** Option A - includes date for identification, timestamp for uniqueness.

### Q3: Time filter basis for customer list

**Context**: The spec mentions `from_date`/`to_date` filtering but doesn't specify what date field to filter on.

| Option | Description |
|--------|-------------|
| A | Filter by contract `created_at` |
| B | Filter by first installment `due_date` |
| C | Filter by customer `created_at` (users table) |

**Recommended:** Option A - Contract creation date is most relevant for business reporting.

---

*Plan created by `/speckit.plan` - Phase 2 (task generation) requires `/speckit.tasks`*