# Charges API

Manage service charges (charges locatives) for leases — heating, water, elevator maintenance, common area upkeep, and other recurring costs billed to tenants. The charge system enforces French regulatory compliance (Décret n°87-713) with billing mode validation, calculation basis rules, lease type compatibility, freeze periods, and annual regularization workflow.

## Quick Example

```bash
# Create a lease charge linking a service, category, and distribution key
curl -X POST https://api.faireplace.com/api/leases/<lease_id>/charges \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "service_place_id": "<service_uuid>",
    "category_id": "<category_uuid>",
    "distribution_key_id": "<distribution_key_uuid>",
    "calculation_method": "PROVISION",
    "calculation_basis": "FORFAIT",
    "amount": 75.00,
    "billing_period_start": "2024-01-01",
    "billing_period_end": "2024-12-31"
  }'
```

## Common Workflows

### Set up charges for a new lease
1. Create [place services](/properties/services) for the building
2. Choose or create [distribution keys](/leases/distribution-keys)
3. `POST /api/leases/{id}/charges` — attach charges to the lease
4. `POST /api/charges/readings` — record meter readings over time
5. `POST /api/leases/{id}/charges/{id}/regularize` — perform annual settlement

### Annual charge settlement (régularisation)
1. `GET /api/charges/readings` — get all readings for the period
2. `POST /api/leases/{id}/charges/reviews` — create an annual review
3. `POST /api/leases/{id}/charges/reviews/{id}/details` — add detail lines per charge
4. `POST /api/leases/{id}/charges/reviews/{id}/transition` — advance through status workflow
5. `POST /api/leases/{id}/charges/{id}/regularize` — record the settlement per charge
6. Or use `POST /api/leases/{id}/charges/finalize` — bulk settlement at lease end

### Generate rent receipts (quittances)
Once charges are set up, generate monthly rent receipts that separate rent and charges as required by Art. 21 Loi 89-462:
1. `POST /api/leases/{id}/receipts/generate` — generate receipts for all active tenants
2. `GET /api/leases/{id}/receipts/{receipt_id}/pdf` — download the PDF

See the full [Rent Receipts guide](/leases/receipts) for details.

---

## Authentication

All endpoints require authentication:
- **LeasesRead**: GET operations
- **LeasesWrite**: POST and PUT operations
- **LeasesDelete**: DELETE operations

---

## Charge Categories

Categories classify charges according to Décret n°87-713, which defines recoverable charges for residential leases.

### Category Types

| Value | Description | Recovery Rate | Legal Reference |
|-------|-------------|---------------|-----------------|
| `EMPLOYEE` | Building employee costs (concierge, gardien) | 75%, 40%, or 100% (see below) | Décret 87-713, Article 2 |
| `ELEVATOR` | Elevator operation and maintenance | 100% | Décret 87-713, Annexe, I |
| `HEATING` | Collective heating and hot water production | 100% | Décret 87-713, Annexe, II |
| `WATER` | Cold water and sanitation | 100% | Décret 87-713, Annexe, II |
| `INDIVIDUAL_EQUIPMENT` | Equipment specific to the tenant's unit | 100% | Décret 87-713, Annexe, III |
| `COMMON_AREAS` | Common area maintenance (interior lighting, green spaces) | 100% | Décret 87-713, Annexe, IV & V |
| `CLEANING` | Hygiene and common area cleaning | 100% | Décret 87-713, Annexe, VI |
| `MAINTENANCE` | General maintenance and small repairs | 100% | Décret 87-713, Annexe, VII |
| `TAXES` | Taxes and fees (TEOM, sweeping tax) | 100% | Décret 87-713, Annexe, VIII |

> **Note on EMPLOYEE recovery rates:** The rate depends on the employee's situation:
> - **75%** — Gardien/concierge performing **both** common area maintenance and waste disposal (Article 2, Décret 87-713)
> - **40%** — Gardien/concierge performing **only one** of those two tasks
> - **100%** — Non-resident building employee (employé d'immeuble not housed on-site)
>
> The API applies the correct rate based on the `PERSONNEL_*` calculation basis selected.

### Category-Basis Constraint

The `EMPLOYEE` category has a special coupling with personnel calculation bases:
- **`EMPLOYEE` charges MUST use** a `PERSONNEL_*` calculation basis (`PERSONNEL_75_POURCENT`, `PERSONNEL_40_POURCENT`, or `PERSONNEL_100_POURCENT`). Using `FORFAIT` or `DEPENSE_REELLE` with `EMPLOYEE` returns `400 Bad Request`.
- **`PERSONNEL_*` bases can ONLY be used with** the `EMPLOYEE` category. Using them with any other category returns `400 Bad Request`.

### Endpoints

#### List Categories
**GET** `/api/charges/categories`

**Query Parameters:**
- `category_type` (optional): Filter by category type

**Response:** `200 OK`
```json
[
  {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Eau et assainissement",
    "description": "Charges eau froide, eau chaude et assainissement",
    "legal_reference": "Décret 87-713, Annexe, II",
    "category_type": "WATER",
    "recovery_rate": 100.00,
    "maintenance_details": {
      "frequency": "monthly",
      "contractor": "Eau de Paris"
    },
    "notification_threshold": 100.00,
    "notification_frequency": "monthly",
    "created_at": "2024-01-15T10:30:00Z",
    "updated_at": "2024-01-15T10:30:00Z"
  }
]
```

#### Create Category
**POST** `/api/charges/categories`

**Request Body:**
```json
{
  "name": "Entretien chauffage collectif",
  "description": "Entretien annuel chaudière et circuits",
  "legal_reference": "Décret 87-713, Annexe, II",
  "category_type": "HEATING",
  "maintenance_details": {
    "frequency": "annual",
    "contractor": "Thermi-Services Paris"
  },
  "notification_threshold": 500.00,
  "notification_frequency": "quarterly"
}
```

**Response:** `201 Created`

#### Get Category
**GET** `/api/charges/categories/{id}`

**Response:** `200 OK`

#### Update Category
**PUT** `/api/charges/categories/{id}`

**Response:** `200 OK`

#### Delete Category
**DELETE** `/api/charges/categories/{id}`

**Response:** `204 No Content`

---

## Lease Charges

A lease charge links a lease to a [place service](/properties/services), a charge category, and a [distribution key](/leases/distribution-keys). It defines the billing mode, calculation basis, and amount for one specific charge line on the lease.

### LeaseCharge Model

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | UUID | auto | Unique identifier |
| `lease_id` | UUID | yes | The lease this charge belongs to |
| `service_place_id` | UUID | no | The place service being charged |
| `category_id` | UUID | no | The charge category |
| `distribution_key_id` | UUID | no | How costs are split — resolved from service default if not provided |
| `calculation_method` | BillingMode | yes | How the tenant is billed |
| `calculation_basis` | CalculationBasis | yes | How the amount is determined |
| `amount` | decimal | yes | Charge amount in euros |
| `billing_period_start` | date | no | Billing period start |
| `billing_period_end` | date | no | Billing period end |
| `last_regularization_date` | date | no | Date of last annual settlement |
| `notes` | string | no | Additional notes (required for increases >20%) |
| `created_at` | datetime | auto | Creation timestamp |
| `updated_at` | datetime | auto | Last update timestamp |

### Distribution Key Resolution

When creating a charge, the distribution key is resolved in order:
1. Explicit `distribution_key_id` in the request body
2. `default_distribution_key_id` from the linked service place
3. If neither is available, the request fails with `400 Bad Request`

### Endpoints

#### List Charges for a Lease
**GET** `/api/leases/{lease_id}/charges`

**Response:** `200 OK`
```json
[
  {
    "id": "880e8400-e29b-41d4-a716-446655440010",
    "lease_id": "550e8400-e29b-41d4-a716-446655440020",
    "service_place_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "category_id": "550e8400-e29b-41d4-a716-446655440000",
    "distribution_key_id": "770e8400-e29b-41d4-a716-446655440001",
    "calculation_method": "PROVISION",
    "calculation_basis": "FORFAIT",
    "amount": 75.00,
    "billing_period_start": "2024-01-01",
    "billing_period_end": "2024-12-31",
    "last_regularization_date": null,
    "notes": null,
    "created_at": "2024-01-15T10:30:00Z",
    "updated_at": "2024-01-15T10:30:00Z"
  }
]
```

#### Create Charge
**POST** `/api/leases/{lease_id}/charges`

The API runs a full validation pipeline before creating the charge (see [Validation Rules](#validation-rules)).

**Request Body:**
```json
{
  "service_place_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "category_id": "550e8400-e29b-41d4-a716-446655440000",
  "distribution_key_id": "770e8400-e29b-41d4-a716-446655440001",
  "calculation_method": "PROVISION",
  "calculation_basis": "FORFAIT",
  "amount": 75.00,
  "billing_period_start": "2024-01-01",
  "billing_period_end": "2024-12-31"
}
```

You can also embed specialized charge data inline:

```json
{
  "calculation_method": "FORFAIT",
  "calculation_basis": "FORFAIT",
  "amount": 25.00,
  "energy_efficiency": {
    "baseline_consumption": 15000.00,
    "monthly_contribution_amount": 25.00,
    "duration_months": 120,
    "work_description": "Isolation thermique par l'extérieur",
    "work_cost": 45000.00,
    "target_savings_percentage": 35,
    "start_date": "2024-01-01"
  }
}
```

**Response:** `201 Created`

#### Get Charge
**GET** `/api/leases/{lease_id}/charges/{charge_id}`

**Response:** `200 OK`

#### Update Charge
**PUT** `/api/leases/{lease_id}/charges/{charge_id}`

All fields are optional for partial updates. If the charge amount increases by more than 20%, the `notes` field becomes mandatory (min 10 characters).

**Response:** `200 OK`

#### Delete Charge
**DELETE** `/api/leases/{lease_id}/charges/{charge_id}`

**Response:** `204 No Content`

#### Regularize Charge
**POST** `/api/leases/{lease_id}/charges/{charge_id}/regularize`

Perform the annual settlement (régularisation) for a provisioned charge. Records the actual expense and calculates the difference.

**Request Body:**
```json
{
  "regularization_amount": 850.00,
  "regularization_date": "2025-01-15",
  "notes": "Régularisation annuelle eau 2024"
}
```

**Response:** `200 OK`

---

## Billing Modes

The billing mode (`calculation_method`) defines *how* the tenant is invoiced for a charge.

| Value | Description | Settlement |
|-------|-------------|------------|
| `FORFAIT` | Fixed amount — tenant pays a flat fee regardless of actual cost | No annual settlement. Amount is final. |
| `PROVISION` | Provisional monthly payments, adjusted at year-end | Annual settlement (régularisation) required. Tenant pays or is credited the difference. |
| `DEPENSE_REELLE` | Actual expense — tenant pays exact costs incurred | Billed after the expense occurs. |
| `RELEVE_DIRECT` | Direct meter reading — tenant pays based on individual consumption | Billed per reading period. |

### When to use each mode

- **FORFAIT**: Ideal for furnished leases where the law allows a fixed charge amount. No obligation to justify actual costs.
- **PROVISION**: Standard for unfurnished residential leases. Monthly provisions with annual regularization.
- **DEPENSE_REELLE**: Used when costs are invoiced to the building and redistributed. Common for co-ownership charges.
- **RELEVE_DIRECT**: When tenants have individual meters (electricity, gas). The charge tracks what each tenant actually consumes.

---

## Calculation Basis

The calculation basis defines *how* the charge amount is determined.

| Value | Description | Category Restriction |
|-------|-------------|---------------------|
| `FORFAIT` | Fixed amount agreed in the lease | Any category except `EMPLOYEE` |
| `DEPENSE_REELLE` | Based on actual documented expenses | Any category except `EMPLOYEE` |
| `PERSONNEL_75_POURCENT` | 75% of personnel costs (concierge: cleaning + trash) | `EMPLOYEE` only |
| `PERSONNEL_40_POURCENT` | 40% of personnel costs (single task, e.g. waste disposal only) | `EMPLOYEE` only |
| `PERSONNEL_100_POURCENT` | 100% of personnel costs (non-resident building employee) | `EMPLOYEE` only |

---

## Billing Mode × Calculation Basis Compatibility

Not all combinations of billing mode and calculation basis are valid. The API enforces the following matrix — invalid combinations return `400 Bad Request`.

| Billing Mode | Allowed Calculation Bases |
|---|---|
| **FORFAIT** | `FORFAIT` |
| **PROVISION** | All bases (`FORFAIT`, `DEPENSE_REELLE`, `PERSONNEL_75_POURCENT`, `PERSONNEL_40_POURCENT`, `PERSONNEL_100_POURCENT`) |
| **DEPENSE_REELLE** | `DEPENSE_REELLE`, `PERSONNEL_75_POURCENT`, `PERSONNEL_40_POURCENT`, `PERSONNEL_100_POURCENT` |
| **RELEVE_DIRECT** | `FORFAIT` |

### Distribution Type × Billing Mode Constraint

An additional rule applies when using `ConsommationMesuree` distribution keys:
- `ConsommationMesuree` distribution requires `RELEVE_DIRECT` or `PROVISION` billing mode
- Other distribution types allow `PROVISION` or `FORFAIT`
- `DEPENSE_REELLE` billing mode accepts all distribution types

### Examples

| Scenario | Billing Mode | Calculation Basis |
|----------|-------------|-------------------|
| Furnished lease, fixed monthly charges | `FORFAIT` | `FORFAIT` |
| Unfurnished lease, monthly water provision | `PROVISION` | `FORFAIT` |
| Concierge costs, provisioned monthly | `PROVISION` | `PERSONNEL_75_POURCENT` |
| Co-ownership charges billed at actuals | `DEPENSE_REELLE` | `DEPENSE_REELLE` |
| Individual electricity meter | `RELEVE_DIRECT` | `FORFAIT` |

---

## Lease Type × Charge Compatibility

Different lease types have different rules for which charge categories and billing modes are allowed. This is configured per lease type via `LeaseTypeChargeConfig`.

| Lease Type | Allowed Categories | Forfait Allowed | Provision Allowed | Annual Review Required | Max Provisional Months |
|---|---|---|---|---|---|
| **Bail habitation nue** | All residential | No | Yes | Yes | 12 |
| **Bail meublé** | All residential | Yes | Yes | Yes | 12 |
| **Bail mobilité** | All residential | Yes (mandatory) | No | No | — |
| **Bail commercial** | All + commercial | Yes | Yes | Per contract | Per contract |
| **Bail professionnel** | All + professional | Yes | Yes | Per contract | Per contract |
| **Colocation** | All residential | Yes | Yes | Yes | 12 |

### Key Rules

- **Bail habitation nue** (unfurnished residential): Forfait billing is **not allowed** — charges must be provisioned with annual regularization. This is a legal requirement.
- **Bail meublé** (furnished): Both forfait and provision are allowed. Forfait is common for simplicity.
- **Bail mobilité** (mobility lease): Only forfait is allowed — no provisions, no regularization. The lease is short-term (1–10 months).
- **Bail commercial / professionnel**: Rules are more flexible and governed by the lease contract rather than statute.

---

## Charge Settlement Modes

At the lease level, a `ChargeSettlementMode` governs how all charges on that lease are billed together.

| Value | Description | Required Billing Mode |
|-------|-------------|----------------------|
| `PROVISION` | Provisional with annual regularization | All charges must be `PROVISION` |
| `PERIODIC` | Periodic payment of actual costs | All charges must be `DEPENSE_REELLE` or `RELEVE_DIRECT` |
| `FLAT_RATE` | Fixed charge amount | All charges must be `FORFAIT` |

The API validates that every individual charge billing mode matches the lease-level settlement mode. Mismatches return `400 Bad Request`.

**Settlement mode availability by lease type:**
- Lease types configured with `LeaseChargeMode::Provision` allow `PROVISION` and `PERIODIC` settlement (but **not** `FLAT_RATE`)
- Lease types configured with `LeaseChargeMode::Forfait` allow **all three** settlement modes

---

## Validation Rules

When creating or updating a lease charge, the API runs the following validation pipeline in order:

### 1. Lease Status Check
Charges can only be created/modified on leases with status **Active**, **Draft**, or **Notice**. Terminated or expired leases return `422`.

### 2. Freeze Period Check
If an active charge freeze period exists for the charge's category and country, modifications are blocked. See [Charge Freeze Periods](#charge-freeze-periods).

### 3. Distribution Key Resolution
See [Distribution Key Resolution](#distribution-key-resolution) above.

### 4. Distribution Type × Billing Mode Compatibility
`ConsommationMesuree` distribution keys require `RELEVE_DIRECT` or `PROVISION`.

### 5. Billing Mode × Calculation Basis Compatibility
See the [compatibility matrix](#billing-mode--calculation-basis-compatibility).

### 6. Category × Basis Coupling
`EMPLOYEE` ↔ `PERSONNEL_*` exclusive coupling.

### 7. Lease Type Compatibility
The charge category must be allowed for the lease type. The billing mode must be permitted.

### 8. Billing Period Validation
- `billing_period_start` must be ≤ `billing_period_end`
- Periods must fall within the lease's start/end dates
- For `PROVISION` mode, period cannot exceed `max_provisional_months`

### 9. No Period Overlap
Two charges for the same service/category cannot have overlapping billing periods.

### 10. Regularization Requirements
If the lease type requires annual review and the billing mode is `PROVISION`:
- `last_regularization_date` must be within the last 12 months

### 11. Charge-to-Rent Ratio Warnings
The API issues non-blocking warnings when charges appear disproportionate relative to rent (these are business safeguards, not legal limits):
- **Residential leases** (habitation nue/meublé): warning if total charges > **40% of rent**
- **Commercial leases**: warning if total charges > **50% of rent**

### 12. Documentation Requirement
When **updating** a charge with an increase > **20%**, the `notes` field is required (minimum 10 characters explaining the reason).

### 13. Service-Lease Compatibility
When a `service_place_id` is provided:
- The service's `place_id` must match the lease's estate's `place_id`
- The service must have `is_chargeable = true`
- The service's category must be allowed for the lease type

---

## Charge Freeze Periods

Regulatory freeze periods temporarily block charge modifications for specific categories and countries.

### FreezePeriod Model

| Field | Type | Description |
|-------|------|-------------|
| `id` | UUID | Unique identifier |
| `freeze_type` | FreezeType | Type of freeze |
| `category_id` | UUID | Affected charge category (or all) |
| `country_id` | UUID | Affected country/legislative zone |
| `start_date` | date | Freeze start |
| `end_date` | date | Freeze end |
| `max_increase_percentage` | decimal | Maximum allowed increase (for `INCREASE_LIMIT` type) |
| `legal_reference` | string | Legal basis for the freeze |
| `conditions` | JSONB | Additional conditions |
| `exceptions` | JSONB | Cases exempt from the freeze |

### Freeze Types

| Value | Description |
|-------|-------------|
| `TOTAL_FREEZE` | All charge modifications blocked |
| `PARTIAL_FREEZE` | Some modifications blocked (conditions apply) |
| `INCREASE_LIMIT` | Increases limited to a percentage |
| `SEASONAL_FREEZE` | Freeze active during specific seasons (e.g., winter truce) |

---

## Charge Regulations (Adjustments)

When charges are adjusted due to freeze periods, legal caps, or exceptions, the system records a `ChargeRegulation`.

| Field | Type | Description |
|-------|------|-------------|
| `id` | UUID | Unique identifier |
| `lease_charge_id` | UUID | The affected charge |
| `regulation_type` | RegulationType | Type of regulation |
| `original_amount` | decimal | Amount before adjustment |
| `adjusted_amount` | decimal | Amount after adjustment |
| `adjustment_details` | JSONB | Justification and details |

### Regulation Types

| Value | Description |
|-------|-------------|
| `FREEZE` | Adjustment due to an active freeze period |
| `LEGAL_EXCEPTION` | Exception to normal rules (e.g., court order) |

> **Required fields by regulation type:**
> - When `regulation_type = FREEZE`, the `freeze_period_id` field is **required**.
> - When `regulation_type = LEGAL_EXCEPTION`, the `legal_exception_id` field is **required**.

---

## Charge Reviews (Régularisation Annuelle)

The annual review process compares provisional charges against actual expenses.

### ChargeReview Model

| Field | Type | Description |
|-------|------|-------------|
| `id` | UUID | Unique identifier |
| `estate_id` | UUID | The estate being reviewed |
| `year` | integer | Year under review |
| `status` | ReviewStatus | Current workflow status |
| `total_provisional_amount` | decimal | Total provisions collected |
| `total_actual_amount` | decimal | Total actual expenses |
| `total_difference_amount` | decimal | Difference (positive = tenant owes, negative = credit) |

### Review Status Workflow

```
DRAFT → IN_PROGRESS → PENDING_VALIDATION → VALIDATED → SENT → CLOSED
                                                     ↘ DISPUTED → CLOSED
```

| Status | Description |
|--------|-------------|
| `DRAFT` | Review created, not yet started |
| `IN_PROGRESS` | Actual expenses being collected |
| `PENDING_VALIDATION` | Expenses entered, awaiting validation |
| `VALIDATED` | Amounts confirmed by the manager |
| `SENT` | Settlement notice sent to tenant |
| `DISPUTED` | Tenant has disputed the settlement |
| `CLOSED` | Review complete, adjustments applied |

### Legal Requirements (Art. 23 Loi 89-462)

- Annual regularization is **mandatory** for provisioned charges
- The landlord must communicate the itemized breakdown to the tenant **1 month before** the regularization
- Supporting documents must be available to the tenant for **6 months** after the regularization notice
- Unpaid charges can be claimed for up to **3 years** (Art. 7-1 Loi 89-462)

---

## Specialized Charges

### Energy Efficiency Contribution (Contribution au partage des économies d'énergie)

When a landlord performs energy-efficient renovations, they may bill tenants a monthly contribution under certain conditions (Art. 23-1 Loi 89-462, Décret 2009-1439):

- The works must consist of a **bouquet de travaux** (combination of at least 2 energy improvement actions) **or** achieve a global energy performance threshold
- The contribution cannot exceed **50%** of the estimated monthly energy savings
- The contribution is limited to **15 years** after the renovation
- The contribution is **fixed and non-revisable** (forfaitaire) for private-sector landlords
- A concertation procedure with the tenant is required before charging
- **Building restrictions**: pre-1948 buildings can only use the work package option; post-1990 buildings are excluded entirely

**Model fields:**

| Field | Type | Description |
|-------|------|-------------|
| `baseline_consumption` | decimal | Energy consumption before works |
| `target_savings_percentage` | decimal | Expected savings percentage (0–100) |
| `monthly_contribution_amount` | decimal | Monthly amount charged to tenant |
| `remaining_months` | integer | Months remaining for the contribution |
| `work_description` | string | Description of the energy improvement works (min 10, max 1000 chars) |
| `work_cost` | decimal | Total cost of the works |
| `energy_audit_reference` | string | Reference to the energy audit/DPE report |
| `start_date` | date | Contribution start date |
| `end_date` | date | Contribution end date (computed: start + duration × 30 days) |

Create inline with a lease charge:
```json
{
  "calculation_method": "FORFAIT",
  "calculation_basis": "FORFAIT",
  "amount": 25.00,
  "energy_efficiency": {
    "baseline_consumption": 15000.00,
    "monthly_contribution_amount": 25.00,
    "duration_months": 120,
    "work_description": "Isolation thermique par l'extérieur",
    "work_cost": 45000.00,
    "target_savings_percentage": 35,
    "start_date": "2024-01-01"
  }
}
```

### Colocation Insurance (Assurance colocation)

For shared-tenancy leases (colocation), the landlord may subscribe to a group insurance policy and recover the cost from tenants.

**Model fields:**

| Field | Type | Description |
|-------|------|-------------|
| `contract_number` | string | Insurance policy number |
| `insurance_company` | string | Insurance provider name |
| `annual_premium` | decimal | Total annual premium |
| `recoverable_amount` | decimal | Amount recoverable from tenants |
| `coverage_details` | string | Coverage description |
| `policy_start_date` | date | Policy start |
| `policy_end_date` | date | Policy end |
| `deductible_amount` | decimal | Policy deductible |
| `coverage_limits` | JSONB | Coverage limits by type |
| `exclusions` | string | Policy exclusions |

**Validation rules:**
- `annual_premium` must be > 0
- `recoverable_amount` must be > 0 and ≤ `annual_premium`
- `policy_end_date` must be after `policy_start_date`
- `coverage_details` must be 10–500 characters

The response includes computed fields:
- `monthly_recoverable`: `annual_premium / 12`
- `is_policy_active`: `true` if today falls between `policy_start_date` and `policy_end_date`

Create inline with a lease charge:
```json
{
  "calculation_method": "FORFAIT",
  "calculation_basis": "FORFAIT",
  "amount": 15.00,
  "insurance": {
    "contract_number": "HAB-2024-12345",
    "insurance_company": "MAIF",
    "annual_premium": 540.00,
    "recoverable_amount": 540.00,
    "coverage_details": "Multirisque habitation colocation",
    "policy_start_date": "2024-01-01",
    "policy_end_date": "2024-12-31"
  }
}
```

---

## Charge Readings

Readings track consumption and usage data for charges billed by provision or actual expense.

### Reading Types

| Value | Description | Used With |
|-------|-------------|-----------|
| `MeterReading` | Raw meter value at a point in time | Water, gas, electricity meters |
| `Consumption` | Calculated consumption between two dates | Heating fuel, aggregate usage |
| `OccupantCount` | Number of occupants at a point in time | Water, waste charges split by occupant |
| `EquipmentCount` | Number of relevant equipment items | Heating charges split by radiator count |

### Reading Subtypes

| Value | Description |
|-------|-------------|
| `CommonAreas` | Reading for common area consumption |
| `Individual` | Reading for individual tenant consumption |
| `Maintenance` | Reading taken during maintenance |
| `LeakFix` | Reading taken after a leak repair |

### Anomaly Detection

The system automatically flags suspicious readings via the `anomaly` field, which is a nested `AnomalyDetails` object (or `null` when no anomaly is detected):
- `is_anomaly` (boolean): whether the reading is flagged as anomalous
- `deviation_percentage` (number, nullable): percentage deviation from average
- `average_value` (number, nullable): historical average value for comparison
- `threshold_high` (number, nullable): upper threshold (150% of average)
- `threshold_low` (number, nullable): lower threshold (50% of average)

### Endpoints

#### List Readings
**GET** `/api/charges/readings`

**Query Parameters:**
- `lease_id` (optional): Filter by lease
- `category_id` (optional): Filter by category
- `start_date` / `end_date` (optional): Date range

**Response:** `200 OK`
```json
[
  {
    "id": "550e8400-e29b-41d4-a716-446655440010",
    "charge_id": "880e8400-e29b-41d4-a716-446655440010",
    "reading_type": "MeterReading",
    "reading_subtype": "Individual",
    "value": 1234.56,
    "reading_date": "2024-01-31T00:00:00Z",
    "anomaly": null,
    "created_at": "2024-02-01T08:00:00Z",
    "updated_at": "2024-02-01T08:00:00Z"
  }
]
```

#### Create Reading
**POST** `/api/charges/readings`

**Request Body:**
```json
{
  "reading_type": "MeterReading",
  "reading_subtype": "Individual",
  "value": 1234.56,
  "period_start": "2024-01-01",
  "period_end": "2024-01-31",
  "reading_date": "2024-01-31T00:00:00Z"
}
```

**Response:** `201 Created`

#### Get Reading
**GET** `/api/charges/readings/{id}`

**Response:** `200 OK`

#### Update Reading
**PUT** `/api/charges/readings/{id}`

**Response:** `200 OK`

#### Delete Reading
**DELETE** `/api/charges/readings/{id}`

**Response:** `204 No Content`

#### Validate Reading
**GET** `/api/charges/readings/{id}/validate`

Validates a reading against business rules (value within expected range, no duplicates for the same period, anomaly detection).

**Response:** `200 OK`
```json
{
  "is_valid": true,
  "validation_errors": [],
  "warnings": [
    "Reading value is 25% higher than the previous period average"
  ]
}
```

---

## Lease Meters

Associate physical meters with leases to track consumption over time. Meters are the foundation for metered billing (`RELEVE_DIRECT`).

### Endpoints

#### List Lease Meters
**GET** `/api/leases/{lease_id}/meters`

**Response:** `200 OK` — Array of `LeaseMeter`

#### Create Lease Meter
**POST** `/api/leases/{lease_id}/meters`

**Request Body:**
```json
{
  "lease_id": "<lease_uuid>",
  "meter_id": "<meter_uuid>",
  "start_date": "2024-01-01",
  "initial_reading": 1234.56,
  "reading_frequency": "MONTHLY",
  "is_billing_meter": true,
  "billing_ratio": 100.00
}
```

**Validation rules:**
- `start_date` must be <= `end_date` (if provided)
- `initial_reading` must be <= `final_reading` (if both provided)
- `billing_ratio`: 0 < x <= 100

**Response:** `201 Created`

#### Get / Update / Delete Lease Meter
- **GET** `/api/leases/{lease_id}/meters/{id}` — `200 OK`
- **PUT** `/api/leases/{lease_id}/meters/{id}` — `200 OK`
- **DELETE** `/api/leases/{lease_id}/meters/{id}` — `204 No Content`

### LeaseMeter Model

| Field | Type | Description |
|-------|------|-------------|
| `id` | UUID | Unique identifier |
| `lease_id` | UUID | Parent lease |
| `meter_id` | UUID | Physical meter reference |
| `start_date` | date | When the meter became active for this lease |
| `end_date` | date? | When the meter was deactivated (null = active) |
| `initial_reading` | decimal? | Meter reading at start |
| `final_reading` | decimal? | Meter reading at end |
| `reading_frequency` | enum? | DAILY, WEEKLY, MONTHLY, QUARTERLY, ANNUALLY |
| `is_billing_meter` | boolean | Whether used for billing (default: true) |
| `billing_ratio` | decimal? | Billing ratio % (0 < x <= 100) |

---

## Charge Meters

Link a meter to a specific charge to define how consumption is calculated and allocated for billing.

### Endpoints

#### List Charge Meters
**GET** `/api/leases/{lease_id}/charges/{charge_id}/meters`

**Response:** `200 OK` — Array of `LeaseChargeMeter`

#### Create Charge Meter
**POST** `/api/leases/{lease_id}/charges/{charge_id}/meters`

**Request Body:**
```json
{
  "lease_charge_id": "<charge_uuid>",
  "meter_id": "<meter_uuid>",
  "calculation_method": "DIRECT",
  "allocation_ratio": 100.00,
  "start_date": "2024-01-01"
}
```

**Response:** `201 Created`

#### Get / Update / Delete Charge Meter
- **GET** `/api/leases/{lease_id}/charges/{charge_id}/meters/{id}` — `200 OK`
- **PUT** `/api/leases/{lease_id}/charges/{charge_id}/meters/{id}` — `200 OK`
- **DELETE** `/api/leases/{lease_id}/charges/{charge_id}/meters/{id}` — `204 No Content`

### Calculation Methods

| Method | Description |
|--------|-------------|
| `DIRECT` | Direct consumption billing — the full metered value is billed |
| `WEIGHTED` | Weighted by a coefficient/ratio |
| `SHARED` | Shared proportionally among tenants |
| `FIXED_ALLOCATION` | Fixed allocation regardless of actual consumption |

---

## Charge Notifications

Send and track charge-related notifications to tenants and managers.

### Notification Types

| Type | Description |
|------|-------------|
| `CONSUMPTION_ALERT` | Alert about unusual consumption patterns |
| `ANNUAL_REVIEW` | Annual charge review notification |
| `REGULARIZATION_DUE` | Regularization is due for the period |
| `DOCUMENTATION_REQUEST` | Request for supporting documents |

### Notification Status Flow

```
DRAFT → SENT → READ → ACTIONED
                   ↘ EXPIRED
```

### Endpoints

- **GET** `/api/leases/{lease_id}/charges/notifications` — List notifications
- **POST** `/api/leases/{lease_id}/charges/notifications` — Create notification
- **GET** `/api/leases/{lease_id}/charges/notifications/{id}` — Get notification
- **PUT** `/api/leases/{lease_id}/charges/notifications/{id}` — Update notification (status, sent_date)
- **DELETE** `/api/leases/{lease_id}/charges/notifications/{id}` — Delete notification

### Create Notification

```json
{
  "lease_id": "<lease_uuid>",
  "notification_type": "CONSUMPTION_ALERT",
  "content": {
    "message": "Water consumption 40% above average",
    "charge_id": "<charge_uuid>",
    "deviation_percentage": 40
  }
}
```

---

## Charge Regulations

Apply regulatory adjustments to charges when freeze periods or legal exceptions are active.

### Regulation Types

| Type | Description | Required Field |
|------|-------------|----------------|
| `FREEZE` | Adjustment due to an active freeze period | `freeze_period_id` |
| `LEGAL_EXCEPTION` | Adjustment due to a legal exception | `legal_exception_id` |

### Business Rules

- `adjusted_amount` **cannot exceed** `original_amount`
- If `regulation_type = FREEZE`, `freeze_period_id` is **required**
- If `regulation_type = LEGAL_EXCEPTION`, `legal_exception_id` is **required**

### Endpoints

- **GET** `/api/leases/{lease_id}/charges/{charge_id}/regulations` — List regulations
- **POST** `/api/leases/{lease_id}/charges/{charge_id}/regulations` — Create regulation
- **GET** `/api/leases/{lease_id}/charges/{charge_id}/regulations/{id}` — Get regulation
- **PUT** `/api/leases/{lease_id}/charges/{charge_id}/regulations/{id}` — Update (adjusted_amount, adjustment_details)
- **DELETE** `/api/leases/{lease_id}/charges/{charge_id}/regulations/{id}` — Delete regulation

### Create Regulation

```json
{
  "lease_charge_id": "<charge_uuid>",
  "regulation_type": "FREEZE",
  "freeze_period_id": "<freeze_period_uuid>",
  "application_date": "2024-06-01",
  "original_amount": 150.00,
  "adjusted_amount": 120.00,
  "adjustment_details": {
    "reason": "Zone tendue - gel partiel des charges",
    "max_increase_percentage": 3.5
  }
}
```

---

## Charge Review Details

Each review contains detail lines comparing provisional vs. actual amounts per charge.

### Review Detail Status

| Status | Description |
|--------|-------------|
| `DRAFT` | Detail created, not yet calculated |
| `CALCULATED` | Amounts computed |
| `VALIDATED` | Amounts confirmed |
| `DISPUTED` | Tenant disputes this line |
| `ADJUSTED` | Amount adjusted after dispute |
| `CLOSED` | Detail finalized |

### Endpoints

- **GET** `/api/leases/{lease_id}/charges/reviews/{review_id}/details` — List details
- **POST** `/api/leases/{lease_id}/charges/reviews/{review_id}/details` — Create detail
- **GET** `/api/leases/{lease_id}/charges/reviews/{review_id}/details/{id}` — Get detail
- **PUT** `/api/leases/{lease_id}/charges/reviews/{review_id}/details/{id}` — Update detail
- **DELETE** `/api/leases/{lease_id}/charges/reviews/{review_id}/details/{id}` — Delete detail

### Create Review Detail

```json
{
  "annual_charge_review_id": "<review_uuid>",
  "lease_charge_id": "<charge_uuid>",
  "provisional_amount": 900.00,
  "actual_amount": 1050.00,
  "difference_amount": 150.00,
  "calculation_details": {
    "method": "prorata_temporis",
    "days_occupied": 365
  }
}
```

---

## Charge Review Endpoints

### Endpoints

- **GET** `/api/leases/{lease_id}/charges/reviews` — List reviews
- **POST** `/api/leases/{lease_id}/charges/reviews` — Create review
- **GET** `/api/leases/{lease_id}/charges/reviews/{id}` — Get review
- **PUT** `/api/leases/{lease_id}/charges/reviews/{id}` — Update review
- **POST** `/api/leases/{lease_id}/charges/reviews/{id}/transition` — Transition review status
- **DELETE** `/api/leases/{lease_id}/charges/reviews/{id}` — Delete review

### Transition Status

```bash
curl -X POST https://api.faireplace.com/api/leases/<lease_id>/charges/reviews/<review_id>/transition \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status": "VALIDATED"}'
```

---

## Bulk Finalization

Perform bulk regularization for all charges at lease end in a single atomic transaction.

**POST** `/api/leases/{lease_id}/charges/finalize`

```json
{
  "regularization_date": "2024-12-31",
  "notes": "End of lease regularization",
  "charges": [
    {
      "charge_id": "<charge_uuid_1>",
      "actual_amount": 1200.00,
      "notes": "Water - actual consumption"
    },
    {
      "charge_id": "<charge_uuid_2>",
      "actual_amount": 800.00,
      "notes": "Heating - actual consumption"
    }
  ]
}
```

This operation:
1. Creates a single `ChargeReview` for the lease
2. Creates a `ChargeReviewDetail` for each charge
3. Updates each charge's amount to the actual value
4. All within a single database transaction (atomicity guaranteed)

**Response:** `200 OK` — Array of updated `LeaseCharge`

---

## Legal Documents (Art. 23 al. 4)

Supporting documents (justificatifs de charges) that back up charge amounts during annual regularization. Per Art. 23 al. 4 of Loi 89-462, these must be available to the tenant for **6 months** after the regularization notice.

### Endpoints

- **GET** `/api/leases/{lease_id}/charges/documents` — List documents
- **POST** `/api/leases/{lease_id}/charges/documents` — Upload document
- **GET** `/api/leases/{lease_id}/charges/documents/{id}` — Get document
- **PUT** `/api/leases/{lease_id}/charges/documents/{id}` — Update document
- **DELETE** `/api/leases/{lease_id}/charges/documents/{id}` — Delete document

---

## Audit Log

Full traceability of all modifications to lease charges. Each change records the previous values, new values, who made the change, and why.

### Endpoint

**GET** `/api/leases/{lease_id}/charges/{charge_id}/audit-log`

**Response:** `200 OK`
```json
[
  {
    "id": "550e8400-e29b-41d4-a716-446655440099",
    "lease_charge_id": "880e8400-e29b-41d4-a716-446655440010",
    "action": "UPDATE",
    "changed_by": "user@example.com",
    "previous_values": { "amount": 75.00 },
    "new_values": { "amount": 90.00 },
    "change_reason": "Annual indexation",
    "created_at": "2024-03-15T10:30:00Z"
  }
]
```

### Audit Actions

| Action | Description |
|--------|-------------|
| `CREATE` | Charge was created |
| `UPDATE` | Charge was modified |
| `DELETE` | Charge was deleted |

---

## Distribution References

Historical reference values for distribution keys, used to compute charge allocations for a specific estate.

### Global Endpoints

- **GET** `/api/charges/distribution-references?distribution_key_id=<uuid>` — List by distribution key
- **GET** `/api/charges/distribution-references?estate_id=<uuid>` — List by estate
- **POST** `/api/charges/distribution-references` — Create reference
- **GET** `/api/charges/distribution-references/{id}` — Get reference
- **PUT** `/api/charges/distribution-references/{id}` — Update reference
- **DELETE** `/api/charges/distribution-references/{id}` — Delete reference

### Create Distribution Reference

```json
{
  "distribution_key_id": "<key_uuid>",
  "estate_id": "<estate_uuid>",
  "reference_date": "2024-01-01T00:00:00Z",
  "reference_value": 45.50,
  "notes": "Surface habitable mesurée par géomètre"
}
```

---

## Business Validation Rules

The charge system enforces multiple layers of validation:

### 1. Lease Status Check
- Charges **cannot be modified** if the lease is `TERMINATED` or `EXPIRED`
- Allowed statuses: `DRAFT`, `ACTIVE`, `NOTICE`

### 2. Period Overlap Detection
- New charges cannot overlap with existing charges for the same service/category
- 4 overlap types detected: new start in existing, new end in existing, new encompasses existing, existing encompasses new

### 3. Freeze Period Enforcement
- Active freeze periods block charge modifications for the affected category + legislative zone
- A freeze is active when: `start_date <= today` AND (`end_date IS NULL` OR `end_date >= today`)

### 4. Amount Change Validation
- Delegates to `LeaseCharge::update_amount()` for internal invariants
- Skipped for creation and zero-amount charges

### 5. Charge-to-Rent Ratio Warning
Non-blocking warning when charges exceed:
- **40%** of rent for residential leases (HABITATION_VIDE, MEUBLE)
- **50%** of rent for commercial leases

---

## Error Responses

| Status | Condition |
|--------|-----------|
| `400 Bad Request` | Invalid billing mode × calculation basis combination; `EMPLOYEE` ↔ `PERSONNEL_*` coupling violation; missing required fields; period overlap |
| `403 Forbidden` | Insufficient permissions |
| `404 Not Found` | Charge, category, or reading not found |
| `409 Conflict` | Cannot delete category — referenced by active charges; active freeze period blocks modification |
| `422 Unprocessable Entity` | Lease type does not allow the requested billing mode; lease is terminated/expired; service place not compatible with lease |

---

## Notes

- All monetary values are stored as BigDecimal for precision
- Notification thresholds help identify unusual consumption patterns
- The system enforces Décret n°87-713 recovery rates automatically during settlement
- Employee charges (`EMPLOYEE` category) are automatically capped at 75%, 40%, or 100% during régularisation depending on the `PERSONNEL_*` basis
- Charge-to-rent ratio warnings (40% / 50%) are non-blocking business safeguards, not legal requirements

---

## Related

- **[Rent Receipts](/leases/receipts)** — Generate rent receipts (quittances) with itemized charges
- **[Place Services](/properties/services)** — Link chargeable services to places
- **[Distribution Keys](/leases/distribution-keys)** — Define how costs are split among tenants
- **[Lease Types](/leases/lease-types)** — Lease type rules and constraints
- **[Rent Regulation](/compliance/rent-regulation)** — Rent control and compliance
