# Leave Monetization (LM) — API Reference

**Base URL:** `/api/v1/leave-monetization`

---

## Table of Contents

1. [Period Configuration (HR Admin)](#1-period-configuration-hr-admin)
2. [Monetization Requests (Employee + HR)](#2-monetization-requests)
3. [Reports & Exports](#3-reports--exports)
4. [Flow Summary](#4-flow-summary)
5. [Error Codes](#5-error-codes)

---

## 1. Period Configuration (HR Admin)

HR Admin creates a monetization window per fiscal year. Employees can only file requests when a period is active (within the date range) and unlocked.

### 1.1 List Periods

```
GET /api/v1/leave-monetization/periods
```

**Query Parameters:**

| Param | Type | Description |
|---|---|---|
| `label` | string | Filter by label (partial match) |
| `is_locked` | boolean | Filter by lock status (`true` or `false`) |
| `page` | integer | Page number (default: 1) |
| `limit` | integer | Items per page (max: 1000) |
| `operator` | string | Global operator: `equal`, `less_than`, `greater_than` |

**Response `200`:**

```json
{
  "success": true,
  "data": [
    {
      "id": 1,
      "label": "CY 2026",
      "period_start": "2026-06-01",
      "period_end": "2026-07-31",
      "is_locked": false,
      "max_vl_credits": "5.000",
      "max_sl_credits": "5.000",
      "notes": null,
      "created_by": null,
      "created_at": "2026-06-03T09:00:00.000000Z",
      "updated_at": "2026-06-03T09:00:00.000000Z"
    }
  ],
  "message": "Monetization periods retrieved successfully."
}
```

### 1.2 Create Period

```
POST /api/v1/leave-monetization/periods
```

**Request Body:**

```json
{
  "label": "CY 2026",
  "period_start": "2026-06-01",
  "period_end": "2026-07-31",
  "max_vl_credits": 5,
  "max_sl_credits": 5,
  "notes": "First half fiscal year monetization"
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `label` | string | ✅ | Display name, e.g. "CY 2026" |
| `period_start` | date (Y-m-d) | ✅ | Filing window opens |
| `period_end` | date (Y-m-d) | ✅ | Filing window closes (must be >= start) |
| `max_vl_credits` | number | ❌ | Max VL credits monetizable per CSC policy |
| `max_sl_credits` | number | ❌ | Max SL credits monetizable per CSC policy |
| `notes` | string | ❌ | Internal notes |

**Response `201`:**

```json
{
  "success": true,
  "data": {
    "id": 1,
    "label": "CY 2026",
    "period_start": "2026-06-01",
    "period_end": "2026-07-31",
    "is_locked": false,
    "max_vl_credits": "5.000",
    "max_sl_credits": "5.000",
    "notes": "First half fiscal year monetization",
    "created_at": "2026-06-03T09:00:00.000000Z",
    "updated_at": "2026-06-03T09:00:00.000000Z"
  },
  "message": "Monetization period created successfully."
}
```

**Error `422` — End before start:**
```json
{
  "success": false,
  "message": "The period end field must be a date after or equal to period start.",
  "error_code": 422
}
```

### 1.3 Get Active Period

Returns the currently open (unlocked and within date range) period.

```
GET /api/v1/leave-monetization/periods/active
```

**Response `200` (active period exists):**
```json
{
  "success": true,
  "data": {
    "id": 1,
    "label": "CY 2026",
    "period_start": "2026-06-01",
    "period_end": "2026-07-31",
    "is_locked": false,
    "max_vl_credits": "5.000",
    "max_sl_credits": "5.000",
    "notes": null,
    "created_at": "2026-06-03T09:00:00.000000Z",
    "updated_at": "2026-06-03T09:00:00.000000Z"
  },
  "message": "Active monetization period retrieved successfully."
}
```

**Response `200` (no active period):**
```json
{
  "success": true,
  "data": null,
  "message": "No active monetization period found."
}
```

### 1.4 Update Period

```
PUT /api/v1/leave-monetization/periods/{id}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `label` | string | ❌ | Display name |
| `period_start` | date (Y-m-d) | ❌ | Filing window opens |
| `period_end` | date (Y-m-d) | ❌ | Filing window closes |
| `max_vl_credits` | number | ❌ | Max VL credits |
| `max_sl_credits` | number | ❌ | Max SL credits |
| `notes` | string | ❌ | Internal notes |

**Response `200`:**
```json
{
  "success": true,
  "data": {
    "id": 1,
    "label": "CY 2026 Updated",
    ...
  },
  "message": "Monetization period updated successfully."
}
```

### 1.5 Delete Period

```
DELETE /api/v1/leave-monetization/periods/{id}
```

**Response `200`:**
```json
{
  "success": true,
  "data": null,
  "message": "Monetization period deleted successfully."
}
```

**Error `422` — Has existing requests:**
```json
{
  "success": false,
  "message": "Cannot delete period with existing monetization requests.",
  "error_code": 422
}
```

### 1.6 Lock Period

Blocks all new filings. Existing requests remain unaffected.

```
POST /api/v1/leave-monetization/periods/{id}/lock
```

**Response `200`:**
```json
{
  "success": true,
  "data": {
    "id": 1,
    "is_locked": true,
    ...
  },
  "message": "Monetization period locked successfully."
}
```

### 1.7 Unlock Period

Re-opens the period for new filings.

```
POST /api/v1/leave-monetization/periods/{id}/unlock
```

**Response `200`:**
```json
{
  "success": true,
  "data": {
    "id": 1,
    "is_locked": false,
    ...
  },
  "message": "Monetization period unlocked successfully."
}
```

---

## 2. Monetization Requests

### 2.1 List Requests

```
GET /api/v1/leave-monetization/requests
```

**Query Parameters:**

| Param | Type | Description |
|---|---|---|
| `employee_id` | string | Filter by employee |
| `period_id` | integer | Filter by period |
| `status` | string | Filter: `pending`, `approved`, `disapproved`, `settled`, `voided` |
| `date_filed` | date (Y-m-d) | Filter by filing date |
| `page` | integer | Page number (default: 1) |
| `limit` | integer | Items per page (max: 1000) |
| `operator` | string | Global operator |

**Response `200`:**
```json
{
  "success": true,
  "data": [
    {
      "id": 1,
      "employee_id": "EMP001",
      "period_id": 1,
      "vl_credits": "3.000",
      "sl_credits": "2.000",
      "vl_credits_approved": null,
      "sl_credits_approved": null,
      "status": "pending",
      "date_filed": "2026-06-10",
      "employee_remarks": "Please process my LM request.",
      "approver_remarks": null,
      "created_at": "2026-06-10T08:00:00.000000Z",
      "updated_at": "2026-06-10T08:00:00.000000Z",
      "period": {
        "id": 1,
        "label": "CY 2026",
        "period_start": "2026-06-01",
        "period_end": "2026-07-31",
        "is_locked": false
      },
      "employee": {
        "employee_id": "EMP001",
        "employee_name": "Doe, John M.",
        "first_name": "John",
        "last_name": "Doe",
        "department": "Human Resources Department",
        "division": "HR Division"
      }
    }
  ],
  "message": "Monetization requests retrieved successfully."
}
```

### 2.2 Show Request

```
GET /api/v1/leave-monetization/requests/{id}
```

**Response `200`:**
Same structure as list item above.

**Error `404`:**
```json
{
  "success": false,
  "message": "Monetization request not found.",
  "error_code": 404
}
```

### 2.3 File Request (Employee)

```
POST /api/v1/leave-monetization/requests
```

**Request Body:**

```json
{
  "employee_id": "EMP001",
  "vl_credits": 3,
  "sl_credits": 2,
  "employee_remarks": "Please process my LM request."
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `employee_id` | string | ✅ | Employee ID |
| `vl_credits` | number | ✅ | VL credits to monetize (0 if none) |
| `sl_credits` | number | ✅ | SL credits to monetize (0 if none) |
| `employee_remarks` | string | ❌ | Optional notes from employee |

**Validation Rules:**
- At least one of `vl_credits` or `sl_credits` must be > 0
- Requested credits must not exceed `max_vl_credits` / `max_sl_credits` from period config
- Employee must have sufficient VL/SL balance
- A monetization period must be active (not locked, within date range)

**Response `201`:**
```json
{
  "success": true,
  "data": {
    "id": 1,
    "employee_id": "EMP001",
    "period_id": 1,
    "vl_credits": "3.000",
    "sl_credits": "2.000",
    "vl_credits_approved": null,
    "sl_credits_approved": null,
    "status": "pending",
    "date_filed": "2026-06-10",
    "employee_remarks": "Please process my LM request.",
    "approver_remarks": null,
    "created_at": "2026-06-10T08:00:00.000000Z",
    "updated_at": "2026-06-10T08:00:00.000000Z"
  },
  "message": "Monetization request filed successfully."
}
```

**Error `422` — No active period:**
```json
{
  "success": false,
  "message": "No active monetization period is currently open. Please check with HR.",
  "error_code": 422
}
```

**Error `422` — Period locked:**
```json
{
  "success": false,
  "message": "Monetization period is currently locked. Please check with HR.",
  "error_code": 422
}
```

**Error `422` — Exceeds max:**
```json
{
  "success": false,
  "message": "VL credits requested (8) exceed the maximum allowed (5).",
  "error_code": 422
}
```

**Error `422` — Insufficient balance:**
```json
{
  "success": false,
  "message": "Insufficient VL balance. You have 15 VL credits but requested 50.",
  "error_code": 422
}
```

### 2.4 Approve/Disapprove Request (Approver)

```
POST /api/v1/leave-monetization/requests/{id}/approval
```

**Request Body:**

```json
{
  "status": "approved",
  "approver_remarks": "Approved by HR."
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `status` | string | ✅ | `approved` or `disapproved` |
| `approver_remarks` | string | ❌ | Reason or notes |

**Validation:**
- Only `pending` requests can be approved/disapproved

**Response `200` (approved):**
```json
{
  "success": true,
  "data": {
    "id": 1,
    "status": "approved"
  },
  "message": "Monetization request approval status updated successfully."
}
```

**Response `200` (disapproved):**
```json
{
  "success": true,
  "data": {
    "id": 1,
    "status": "disapproved"
  },
  "message": "Monetization request approval status updated successfully."
}
```

**Error `422` — Not pending:**
```json
{
  "success": false,
  "message": "Only pending requests can be approved or disapproved.",
  "error_code": 422
}
```

### 2.5 HR Confirm (HR Admin)

Confirms the request, deducts VL/SL credits from employee's leave balance, and marks the request as settled.

```
POST /api/v1/leave-monetization/requests/{id}/hr-confirm
```

**Request Body:**

```json
{
  "vl_credits_approved": 3,
  "sl_credits_approved": 2,
  "remarks": "Confirmed for June payroll."
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `vl_credits_approved` | number | ❌ | Actual VL credits to deduct (defaults to `vl_credits` from request) |
| `sl_credits_approved` | number | ❌ | Actual SL credits to deduct (defaults to `sl_credits` from request) |
| `remarks` | string | ❌ | Internal notes |

**Validation:**
- Request must be in `approved` status
- At least one of `vl_credits_approved` or `sl_credits_approved` must be > 0
- Employee must have sufficient VL/SL balance at time of confirmation

**Response `200`:**
```json
{
  "success": true,
  "data": {
    "request_id": 1,
    "status": "settled",
    "deductions": [
      {
        "leave_type": "VL",
        "deducted": 3,
        "ledger_entry_id": 101
      },
      {
        "leave_type": "SL",
        "deducted": 2,
        "ledger_entry_id": 102
      }
    ],
    "updated_balances": {
      "VL": 12.0,
      "SL": 13.0
    }
  },
  "message": "Monetization request confirmed and ledger updated."
}
```

**Error `422` — Already settled:**
```json
{
  "success": false,
  "message": "Request is already settled.",
  "error_code": 422
}
```

**Error `422` — Not approved:**
```json
{
  "success": false,
  "message": "Only approved requests can be confirmed.",
  "error_code": 422
}
```

**Error `422` — Insufficient balance:**
```json
{
  "success": false,
  "message": "Insufficient VL balance. Employee has 0 VL credits but 3 need to be deducted.",
  "error_code": 422
}
```

### 2.6 HR Void (HR Admin)

Voids a settled request and restores the deducted credits back to the employee's leave balance.

```
POST /api/v1/leave-monetization/requests/{id}/hr-void
```

**Request Body:**

```json
{
  "void_reason": "Incorrect calculation, re-processing."
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `void_reason` | string | ✅ | Reason for voiding |

**Validation:**
- Request must be in `settled` status

**Response `200`:**
```json
{
  "success": true,
  "data": {
    "request_id": 1,
    "status": "voided",
    "restorations": [
      {
        "leave_type": "VL",
        "restored": 3,
        "ledger_entry_id": 201
      },
      {
        "leave_type": "SL",
        "restored": 2,
        "ledger_entry_id": 202
      }
    ],
    "updated_balances": {
      "VL": 15.0,
      "SL": 15.0
    }
  },
  "message": "Monetization request voided successfully."
}
```

**Error `422` — Not settled:**
```json
{
  "success": false,
  "message": "Only settled requests can be voided.",
  "error_code": 422
}
```

---

## 3. Reports & Exports

### 3.1 Report Data (JSON)

```
GET /api/v1/leave-monetization/report
```

**Query Parameters:**

| Param | Type | Description |
|---|---|---|
| `period_id` | integer | Filter by period |

**Response `200`:**
```json
{
  "success": true,
  "data": [
    {
      "id": 1,
      "employee_id": "EMP001",
      "period_id": 1,
      "vl_credits": "3.000",
      "sl_credits": "2.000",
      "vl_credits_approved": "3.000",
      "sl_credits_approved": "2.000",
      "status": "settled",
      "date_filed": "2026-06-10",
      "employee": {
        "employee_id": "EMP001",
        "employee_name": "Doe, John M.",
        "first_name": "John",
        "last_name": "Doe",
        "department": "Human Resources Department",
        "division": "HR Division"
      },
      "period": {
        "id": 1,
        "label": "CY 2026",
        "period_start": "2026-06-01",
        "period_end": "2026-07-31",
        "is_locked": false
      }
    }
  ],
  "message": "Monetization report data retrieved successfully."
}
```

### 3.2 Export Report (Excel / PDF)

```
GET /api/v1/leave-monetization/report/export?period_id=1&format=xlsx
```

**Query Parameters:**

| Param | Type | Required | Default | Description |
|---|---|---|---|---|
| `period_id` | integer | ❌ | all | Filter by period |
| `format` | string | ❌ | `xlsx` | `xlsx` or `pdf` |

**Response:** File download (Content-Type: `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` for XLSX, `application/pdf` for PDF).

**Columns in Export:**
| Column | Description |
|---|---|
| Employee Name | Full name from employee record |
| Employee ID | Employee identifier |
| Division | Employee's division |
| VL Credits Monetized | Approved/requested VL credits |
| SL Credits Monetized | Approved/requested SL credits |
| Total Credits | VL + SL combined |
| Period | Period label |
| Date Filed | When the request was filed |
| Date Confirmed | When HR confirmed (or last updated for voided) |
| Status | settled, approved, etc. |

---

## 4. Flow Summary

```
ESS (Employee)                 Approver                  HR Admin
     │                            │                         │
     │  POST /requests            │                         │
     │  {vl_credits, sl_credits}  │                         │
     │  → status: pending         │                         │
     │                            │                         │
     │         POST /requests/{id}/approval                 │
     │         {status: approved/disapproved}               │
     │                            │                         │
     │                            │  POST /requests/{id}/   │
     │                            │  hr-confirm             │
     │                            │  {vl_credits_approved,  │
     │                            │   sl_credits_approved}   │
     │                            │  → ledger deduction     │
     │                            │  → status: settled      │
     │                            │                         │
     │                            │  POST /requests/{id}/   │
     │                            │  hr-void (if needed)    │
     │                            │  → ledger restoration   │
     │                            │  → status: voided       │
```

### Status State Machine

```
                      ┌──────────────┐
                      │   pending    │
                      └──────┬───────┘
                             │
                     ┌───────┴───────┐
                     ▼               ▼
              ┌──────────┐   ┌──────────────┐
              │ approved │   │ disapproved  │
              └─────┬────┘   └──────────────┘
                    │
                    ▼
              ┌──────────┐
              │  settled │
              └─────┬────┘
                    │
                    ▼
              ┌──────────┐
              │  voided  │
              └──────────┘
```

### Frontend Flow Guidelines

1. **HR Admin Page — Period Configuration:**
   - Display list of periods (sorted by start date desc)
   - Create button → form with label, start/end dates, max VL/SL credits
   - Each period row has Lock/Unlock toggle button
   - Delete button (disabled if period has requests)

2. **ESS Page — File LM Request:**
   - Check `GET /periods/active` — if null, show "No active period" message, disable filing
   - If active, show form: employee enters VL credits, SL credits, optional remarks
   - Client-side: show employee's current VL and SL balances (fetch from `GET /balance_history/leave_management?employee_id=X`)
   - Submit to `POST /requests`

3. **HR Admin Page — Process Requests:**
   - List requests with filters by status
   - For pending items: Approve/Disapprove buttons
   - For approved items: Confirm button → opens dialog to enter/confirm VL/SL credits
   - For settled items: Void button → requires void reason

4. **Reporting Page:**
   - Select period from dropdown
   - Display table of settled/approved records
   - Export buttons: Excel (.xlsx) and PDF

---

## 5. Error Codes

| HTTP Status | Meaning |
|---|---|
| `200` | Success |
| `201` | Created successfully |
| `404` | Resource not found |
| `422` | Validation error (see `message` field for details) |
| `500` | Server error |

All error responses follow this format:
```json
{
  "success": false,
  "message": "Human-readable error description.",
  "error_code": 422
}
```

All success responses follow this format:
```json
{
  "success": true,
  "data": { ... },
  "message": "Human-readable success message."
}
```
