# Disciplinary Actions — API Documentation

> V2 endpoints under the `with_api_gateway.php` route group.
> Base URL: `<service-base>/api/v2` (all routes are prefixed accordingly).

---

## Table of Contents

1. [List per Employee](#1-list-per-employee-get)
2. [Get Single Record](#2-get-single-record-get)
3. [Create Record](#3-create-record-post)
4. [Complete Record (Status Update)](#4-complete-record-patch)
5. [Admin — List All (search/paginate)](#5-admin--list-all-get)

---

## Data Model

### DisciplinaryAction Record

| Field | Type | Description |
|-------|------|-------------|
| `id` | integer | Primary key |
| `employee_id` | integer | FK to employees table |
| `penalty_type_id` | integer | Nullable. FK to penalty_types |
| `case_description` | string | Description of the case |
| `date_from` | date (Y-m-d) | Start date |
| `date_to` | date (Y-m-d) | Nullable. End date |
| `status` | enum (`Pending`, `Ongoing`, `Completed`) | Default: `Pending` |
| `attachment` | string (nullable) | File path to uploaded attachment |
| `created_at` | datetime | Timestamp |
| `updated_at` | datetime | Timestamp |

### Relationship — penaltyType

When eager-loaded via `with('penaltyType')`, each record includes a nested `penalty_type` object:

```json
"penalty_type": {
    "id": 1,
    "name": "Written Reprimand",
    "created_at": "2026-06-04T00:00:00.000000Z",
    "updated_at": "2026-06-04T00:00:00.000000Z"
}
```

**Available penalty types** (seeded reference data):

| id | name |
|----|------|
| 1 | Written Reprimand |
| 2 | Verbal Reprimand |
| 3 | Suspension |
| 4 | Demotion |
| 5 | Fine |
| 6 | Termination |
| 7 | Forced Resignation |
| 8 | Warning |

---

## Endpoints

---

### 1. List per Employee (GET)

Returns all disciplinary action records for a specific employee.

```
GET /api/v2/employee/{employee_no}/disciplinary-actions
```

#### Path Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `employee_no` | string | The employee's employee number |

#### Success Response — `200 OK`

```json
{
    "data": [
        {
            "id": 1,
            "employee_id": 5,
            "penalty_type_id": 1,
            "case_description": "Violation of company code of conduct Section 4.2",
            "date_from": "2026-05-01",
            "date_to": null,
            "status": "Pending",
            "attachment": "disciplinary-actions/abc123.pdf",
            "created_at": "2026-06-04T06:30:00.000000Z",
            "updated_at": "2026-06-04T06:30:00.000000Z",
            "penalty_type": {
                "id": 1,
                "name": "Written Reprimand",
                "created_at": "2026-06-04T00:00:00.000000Z",
                "updated_at": "2026-06-04T00:00:00.000000Z"
            }
        }
    ]
}
```

#### Error Response — `404 Not Found`

```json
{
    "message": "Disciplinary actions not found"
}
```

*Note: This error fires when the `employee_no` does not match any employee.*

---

### 2. Get Single Record (GET)

Returns one disciplinary action record by ID for a specific employee.

```
GET /api/v2/employee/{employee_no}/disciplinary-actions/{id}
```

#### Path Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `employee_no` | string | The employee's employee number |
| `id` | integer | The disciplinary action record ID |

#### Success Response — `200 OK`

```json
{
    "data": {
        "id": 1,
        "employee_id": 5,
        "penalty_type_id": 1,
        "case_description": "Violation of company code of conduct Section 4.2",
        "date_from": "2026-05-01",
        "date_to": null,
        "status": "Pending",
        "attachment": "disciplinary-actions/abc123.pdf",
        "created_at": "2026-06-04T06:30:00.000000Z",
        "updated_at": "2026-06-04T06:30:00.000000Z",
        "penalty_type": {
            "id": 1,
            "name": "Written Reprimand",
            "created_at": "2026-06-04T00:00:00.000000Z",
            "updated_at": "2026-06-04T00:00:00.000000Z"
        }
    }
}
```

#### Error Response — `404 Not Found`

```json
{
    "message": "Disciplinary action not found"
}
```

---

### 3. Create Record (POST)

Creates a new disciplinary action record for a specific employee.

```
POST /api/v2/employee/{employee_no}/disciplinary-actions
```

#### Path Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `employee_no` | string | The employee's employee number |

#### Request Body (multipart/form-data)

| Field | Type | Required | Rules |
|-------|------|----------|-------|
| `penalty_type_id` | integer | No | Must exist in `penalty_types` table |
| `case_description` | string | **Yes** | — |
| `date_from` | date (Y-m-d) | **Yes** | Must be a valid date |
| `date_to` | date (Y-m-d) | No | Must be after or equal to `date_from` |
| `status` | string | No | One of: `Pending`, `Ongoing`, `Completed`. Defaults to `Pending` |
| `attachment` | file | No | File upload. Accepted: `pdf`, `jpg`, `jpeg`, `png`. Max: 5MB |

#### Example Request Body

```json
{
    "penalty_type_id": 1,
    "case_description": "Violation of company code of conduct Section 4.2",
    "date_from": "2026-05-01",
    "date_to": "2026-05-15",
    "status": "Pending"
}
```

**With file attachment:** use `multipart/form-data` and include the `attachment` field as a file.

#### Success Response — `201 Created`

```json
{
    "message": "Disciplinary action added successfully",
    "data": {
        "id": 1,
        "employee_id": 5,
        "penalty_type_id": 1,
        "case_description": "Violation of company code of conduct Section 4.2",
        "date_from": "2026-05-01",
        "date_to": "2026-05-15",
        "status": "Pending",
        "attachment": "disciplinary-actions/abc123.pdf",
        "created_at": "2026-06-04T06:30:00.000000Z",
        "updated_at": "2026-06-04T06:30:00.000000Z",
        "penalty_type": {
            "id": 1,
            "name": "Written Reprimand",
            "created_at": "2026-06-04T00:00:00.000000Z",
            "updated_at": "2026-06-04T00:00:00.000000Z"
        }
    }
}
```

#### Error Response — `500 Internal Server Error`

```json
{
    "message": "Failed to add disciplinary action",
    "error": "SQLSTATE[23000]: ..."
}
```

#### Validation Error Response — `422 Unprocessable Entity`

```json
{
    "message": "The case_description field is required. (and 2 more errors)",
    "errors": {
        "case_description": ["The case_description field is required."],
        "date_from": ["The date_from field is required."],
        "attachment": ["The attachment field must be a file of type: pdf, jpg, jpeg, png."]
    }
}
```

---

### 4. Complete Record (PATCH)

Updates a disciplinary action record's status to `Completed`.  
**Business rule:** Only records with `Pending` status can be completed.

```
PATCH /api/v2/employee/{employee_no}/disciplinary-actions/{id}
```

#### Path Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `employee_no` | string | The employee's employee number |
| `id` | integer | The disciplinary action record ID |

#### Request Body

```json
{
    "status": "Completed"
}
```

| Field | Type | Required | Rules |
|-------|------|----------|-------|
| `status` | string | **Yes** | Must be exactly `"Completed"` |

#### Success Response — `200 OK`

```json
{
    "message": "Disciplinary action completed successfully",
    "data": {
        "id": 1,
        "employee_id": 5,
        "penalty_type_id": 1,
        "case_description": "Violation of company code of conduct Section 4.2",
        "date_from": "2026-05-01",
        "date_to": null,
        "status": "Completed",
        "attachment": null,
        "created_at": "2026-06-04T06:30:00.000000Z",
        "updated_at": "2026-06-04T06:35:00.000000Z",
        "penalty_type": {
            "id": 1,
            "name": "Written Reprimand",
            "created_at": "2026-06-04T00:00:00.000000Z",
            "updated_at": "2026-06-04T00:00:00.000000Z"
        }
    }
}
```

#### Error — `422 Unprocessable Entity` (status not Pending)

```json
{
    "message": "Only records with Pending status can be completed"
}
```

#### Error Response — `500 Internal Server Error`

```json
{
    "message": "Failed to update disciplinary action",
    "error": "..."
}
```

---

### 5. Admin — List All (GET)

Returns all disciplinary action records across all employees with search, filter, and pagination.  
This is the **admin/search** endpoint.

```
GET /api/v2/employee/all/disciplinary-actions
```

#### Query Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `employee_no` | string | No | Filter by exact employee number |
| `employee_name` | string | No | Search by employee name (partial match on first, middle, or last name) |
| `division` | string | No | Filter by division name (partial match) |
| `status` | string | No | Filter by status: `Pending`, `Ongoing`, or `Completed` |
| `show_all` | boolean | No | Set to `true` to return all records without pagination |
| `per_page` | integer | No | Items per page (default: `10`). Only applies when `show_all` is not true |

#### Success Response — `200 OK` (paginated)

```json
{
    "data": [
        {
            "id": 1,
            "case_description": "Violation of company code of conduct Section 4.2",
            "date_from": "2026-05-01",
            "date_to": null,
            "status": "Pending",
            "attachment": null,
            "created_at": "2026-06-04T06:30:00.000000Z",
            "employee_id": 5,
            "employee_no": "EMP-2026-001",
            "employee_name": "Juan A Dela Cruz",
            "penalty_type": "Written Reprimand",
            "division": "Human Resources"
        }
    ],
    "links": {
        "first": "http://...",
        "last": "http://...",
        "prev": null,
        "next": "http://..."
    },
    "meta": {
        "current_page": 1,
        "from": 1,
        "last_page": 3,
        "links": [...],
        "path": "http://...",
        "per_page": 10,
        "to": 10,
        "total": 25
    }
}
```

#### Success Response — `200 OK` (show_all=true)

```json
{
    "data": [
        {
            "id": 1,
            "case_description": "Violation of company code of conduct Section 4.2",
            "date_from": "2026-05-01",
            "date_to": null,
            "status": "Pending",
            "attachment": null,
            "created_at": "2026-06-04T06:30:00.000000Z",
            "employee_id": 5,
            "employee_no": "EMP-2026-001",
            "employee_name": "Juan A Dela Cruz",
            "penalty_type": "Written Reprimand",
            "division": "Human Resources"
        }
    ]
}
```

**Important:** The admin endpoint returns **flat** objects (not nested). The `penalty_type` field is a **string** (the penalty type name), **not** an object. This differs from the per-employee endpoints.

#### Response Fields (admin endpoint)

| Field | Type | Description |
|-------|------|-------------|
| `id` | integer | Record ID |
| `case_description` | string | Case description |
| `date_from` | date | Start date |
| `date_to` | date (nullable) | End date |
| `status` | string | `Pending`, `Ongoing`, or `Completed` |
| `attachment` | string (nullable) | File path |
| `created_at` | datetime | Timestamp |
| `employee_id` | integer | Employee record ID |
| `employee_no` | string | Employee number |
| `employee_name` | string | Full name: `"First Middle Last"` |
| `penalty_type` | string | Penalty type **name** (flat string) |
| `division` | string (nullable) | Division name |

#### Error Response — `500 Internal Server Error`

```json
{
    "message": "Failed to retrieve disciplinary actions"
}
```

---

## Status Lifecycle

```mermaid
graph LR
    A[Pending] -->|PATCH| B[Completed]
    A --> C[Ongoing]
    C --> B
```

- **Create** → defaults to `Pending` (unless explicitly set otherwise)
- **PATCH** → moves a `Pending` record to `Completed` (only valid transition via API)
- `Ongoing` → currently only settable at creation; no API endpoint transitions to/from it yet
- Once `Completed`, no further status changes are allowed

---

## Error Codes Summary

| HTTP Code | Meaning |
|-----------|---------|
| `200 OK` | Success (GET, PATCH) |
| `201 Created` | Success (POST) |
| `404 Not Found` | Employee or record not found |
| `422 Unprocessable Entity` | Validation failure or business rule violation |
| `500 Internal Server Error` | Server/database error |

---

## Notes for Frontend

1. **File upload**: The `POST` endpoint expects `multipart/form-data` when sending an attachment. Use `FormData` in JavaScript.
2. **Date format**: All dates are in `Y-m-d` (e.g., `2026-05-01`).
3. **Penalty type list**: The penalty types are reference data. The frontend should either:
   - Fetch them from ms-general once migrated, or
   - Hardcode the known list (see [Data Model](#data-model) above) until ms-general exposes an endpoint.
4. **Admin endpoint difference**: Note that the admin list all endpoint (`employee/all/disciplinary-actions`) returns flat response objects with `penalty_type` as a **string**, while the per-employee endpoints return nested `penalty_type` objects. Plan your UI data mapping accordingly.
5. **Pagination**: The admin endpoint uses Laravel's default pagination structure (`links` and `meta` objects). Use `show_all=true` to bypass pagination if needed.
6. **Authorization**: These routes are behind JWT middleware with role checks. Ensure the API gateway attaches a valid service token.
