# SPECIFICATIONS/06_API/06_04_CONTRACTS_ENDPOINTS.md

**PURPOSE:** Specifies the contract lifecycle API surface.

> **Business rules enforced by these endpoints:** `../01_BUSINESS_CONTEXT/03_BUSINESS_RULES.md` → BR-002-*, BR-003-*
> **Math validation algorithm:** `../05_ALGORITHMS/02_CONTRACT_MATH_VALIDATION.md`
> **Payment engine algorithms:** `../05_ALGORITHMS/03_PAYMENT_ENGINE.md`
> **State machine:** `../03_ARCHITECTURE/02_STATE_MACHINES.md`
> **Response builder pattern for detail endpoint:** `../../CONSTITUTIONS/04_DATABASE_COMMUNICATION.md` → Read Path - Complex Reads

---

## POST /api/v1/contracts

**Permission:** `contracts.create`

**Inputs:**

| Field | Type | Required | Validation |
|:---|---|:---|:---|
| `customer_id` | integer | Yes | exists in clients |
| `agent_id` | integer | No | exists in clients, nullable |
| `product_name` | string | Yes | — |
| `product_description` | text | No | nullable |
| `purchase_amount` | decimal | Yes | min 0.01 |
| `initial_payment` | decimal | Yes | min 0 |
| `profit_percentage` | decimal | Yes | min 0 |
| `total_after_profit` | decimal | Yes | — |
| `months` | integer | Yes | min 1, max 60 |
| `monthly_installment_amount` | decimal | Yes | — |
| `start_date` | date | Yes | — |

**Outputs:**
```json
{
    "success": true,
    "message": "string",
    "data": {
        "id": 3,
        "status": "draft",
        "signed_at": null,
        "customer": {
            "id": 1,
            "name": "string",
            "phone": "string"
        },
        "agent": null,
        "product_name": "string",
        "product_description": "string|null",
        "purchase_amount": "5000.00",
        "initial_payment": "1500.00",
        "profit_percentage": "10.00",
        "total_after_profit": "5500.00",
        "months": 10,
        "monthly_installment_amount": "400.00",
        "start_date": "2026-06-18",
        "installments": [
            {
                "id": 21,
                "installment_number": 1,
                "due_date": "2026-07-18",
                "amount": "400.00",
                "status": "pending",
                "paid_amount": "0.00",
                "paid_at": null
            }
        ],
        "narrative_timeline": [
            "string (company_payout narrative)",
            "string (initial_payment narrative, if applicable)"
        ],
        "created_at": "timestamp"
    }
}
```

**Behavior:**
1. Validates all financial figures via Contract Math Validator — rejects on mismatch or fractions
2. Creates contract with `draft` status and auto-generated `reference_number`
3. Generates installments automatically based on the provided figures and start date (handles varying month lengths and leap years)
4. Creates `company_payout` payment automatically (amount equals `purchase_amount`, not linked to any installment)
5. If `initial_payment > 0`: creates `initial_payment` record + audit log entry
6. Assigns `customer` flag to the customer and `agent` flag to the agent (if provided) atomically

**Errors:**

| Condition | Key | HTTP |
|:---|:---|:---|
| Math validation failed | `errors/contract.math_validation_failed` | 422 |
| Fractional payment detected | `errors/contract.fractional_payment` | 422 |
| Total mismatch | `errors/contract.total_mismatch` | 422 |

---

## GET /api/v1/contracts/{id}

**Permission:** `contracts.view`

**Inputs:** None (route model binding: `Contract`)

**Outputs:**
```json
{
    "success": true,
    "message": "string",
    "data": {
        "id": 3,
        "status": "active",
        "signed_at": "timestamp|null",
        "customer": {
            "id": 1,
            "name": "string",
            "phone": "string",
            "avatar_url": "string|null",
            "total_remaining_amount": "3600.00"
        },
        "agent": null,
        "purchase_amount": "5000.00",
        "initial_payment": "1500.00",
        "start_date": "2026-06-18",
        "transactions": [
            {
                "id": 5,
                "date": "2026-06-18",
                "amount": "5000.00",
                "is_send": true,
                "is_deleted": false,
                "status": "completed"
            },
            {
                "id": null,
                "customer_id": null,
                "date": "2026-08-20",
                "amount": "400.00",
                "is_send": false,
                "is_deleted": true,
                "status": "deleted"
            }
        ]
    }
}
```

**Behavior:**
- Uses a Response Builder (not a simple Resource) to assemble data from multiple sources
- `customer.total_remaining_amount`: computed from ALL active/completed contracts of this customer, not just the viewed contract
- `transactions[]`: unified timeline combining:
  - Actual payments from `payments` table
  - Deleted payments inferred from audit logs where `action_type = 'delete'` (amount extracted from narrative text)
- Pending/overdue installments are NOT shown — only actual transaction records

**Transaction field semantics:**

| Source | `is_send` | `is_deleted` | `status` |
|:---|:---|:---|:---|
| `company_payout` payment | `true` | `false` | `completed` |
| `initial_payment` payment | `false` | `false` | `completed` |
| `installment_payment` payment | `false` | `false` | `paid` |
| Deleted payment (from audit log) | `false` | `true` | `deleted` |

For deleted transactions, `id` is `null` because `payment_id` becomes NULL via ON DELETE SET NULL.

---

## GET /api/v1/contracts/customer/{customer}

**Permission:** `contracts.view`

**Inputs:** None (route model binding: `Client` as `customer`)

**Outputs:**
```json
{
    "success": true,
    "message": "string",
    "data": [
        {
            "id": 3,
            "product_name": "string",
            "next_due_date": "2026-07-18|null",
            "status": "active|completed"
        }
    ]
}
```

**Behavior:**
- Returns only `active` or `completed` contracts for the given customer
- `next_due_date`: due date of the first pending or overdue installment by number order, or `null` if all paid
- Sort by urgency: overdue (earliest first) → upcoming (nearest first) → completed
- No pagination — returns all matching contracts

---

## PATCH /api/v1/contracts/{id}/sign

**Permission:** `contracts.sign`

**Inputs:** None (route model binding: `Contract`)

**Outputs:** Same shape as POST create response (with installments and narrative timeline)

**Behavior:**
- Guard: status must be `draft` — reject if not
- Sets status to `active` and `signed_at` to current timestamp
- The contract becomes immutable after this action

**Errors:**

| Condition | Key | HTTP |
|:---|:---|:---|
| Not a draft | `errors/contract.cannot_sign` | 403 |

---

## DELETE /api/v1/contracts/{id}

**Permission:** `contracts.delete`

**Inputs:** None (route model binding: `Contract`)

**Outputs:**
```json
{
    "success": true,
    "message": "string",
    "data": null
}
```

**Behavior:**
- Guard: status must be `draft` — reject if not
- Hard deletes the contract with CASCADE: contract, all installments, all payments (including company_payout), all audit logs
- No trace remains in the database
- Triggers async refresh of `customer_listing_mv`

**Errors:**

| Condition | Key | HTTP |
|:---|:---|:---|
| Not a draft | `errors/contract.cannot_delete` | 403 |