# DTR Correction Enhancement — API Reference

> Frontend-facing documentation for the DTR Correction Enhancement.
> All endpoints are prefixed with `/api/v1` (or `/api/v2` via API gateway).

---

## 1. Create DTR Correction (Updated)

Creates a new DTR correction. Now supports both the legacy 4-datetime field format and the new simplified `entry_type` + `corrected_time` format.

### `POST /api/v1/filing/dtr`

### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `is_filing` | boolean | **yes** | `true` = goes through approval flow; `false` = auto-approved (legacy) |
| `employee_id` | string | **yes** | Employee identifier |
| `date_correction` | date (Y-m-d) | **yes** | Date of correction request |
| `schedule_date` | date (Y-m-d) | **yes** | Schedule date being corrected |
| `reason` | string | **yes** | Reason for the correction |
| `entry_type` | string | no | New simplified field: `"time_in"` or `"time_out"`. Mutually exclusive with the 4 datetime fields. |
| `corrected_time` | time (H:i:s) | no | New simplified field: the corrected clock time (e.g. `"08:30:00"`). Required if `entry_type` is set. |
| `justification` | text | **conditional** | **Required** when `is_filing: true`. Explanation for the correction. |
| `time_in` | datetime | no | Legacy field: `"Y-m-d H:i:s"` |
| `time_out` | datetime | no | Legacy field: `"Y-m-d H:i:s"` |
| `break_in` | datetime | no | Legacy field: `"Y-m-d H:i:s"` |
| `break_out` | datetime | no | Legacy field: `"Y-m-d H:i:s"` |
| `remarks` | string | no | Additional remarks |
| `attachments` | file\|array | no | Supporting documents (processed via file upload trait) |
| `status` | string | no | Default `"pending"` |
| `created_by` | string | **yes** | Who created this |

### Example: Simplified Form (Filing)

```json
{
  "is_filing": true,
  "employee_id": "EMP001",
  "date_correction": "2026-05-22",
  "schedule_date": "2026-05-22",
  "entry_type": "time_in",
  "corrected_time": "08:30:00",
  "justification": "Arrived late due to heavy traffic along EDSA.",
  "reason": "Late clock in",
  "created_by": "EMP001"
}
```

### Example: Legacy Form (Admin/Manual)

```json
{
  "is_filing": false,
  "employee_id": "EMP001",
  "date_correction": "2026-05-22",
  "schedule_date": "2026-05-22",
  "time_in": "2026-05-22 08:00:00",
  "time_out": "2026-05-22 17:00:00",
  "break_in": "2026-05-22 12:00:00",
  "break_out": "2026-05-22 13:00:00",
  "reason": "Manual entry",
  "created_by": "admin"
}
```

### Response (201 Created)

```json
{
  "success": true,
  "data": {
    "id": 1,
    "employee_id": "EMP001",
    "schedule_date": "2026-05-22",
    "entry_type": "time_in",
    "corrected_time": "08:30:00",
    "justification": "Arrived late due to heavy traffic along EDSA.",
    "status": "pending",
    "approval_request_id": "req-abc-123",
    "created_by": "EMP001",
    "created_at": "2026-05-22T10:00:00.000000Z",
    "updated_at": "2026-05-22T10:00:00.000000Z"
  },
  "message": "DTR created successfully"
}
```

### Errors

| Status | Condition |
|--------|-----------|
| `422` | Payroll period is locked (`"DTR corrections cannot be filed for a locked payroll period."`) |
| `422` | Missing `justification` when `is_filing: true` |
| `422` | Invalid `entry_type` (must be `"time_in"` or `"time_out"`) |
| `422` | Validation failures on other fields |

> **Note:** When `is_filing: true`, the system sends an approval request to ms-general (`POST /api/v1/approval-requests`). The DTR correction stays in `pending` status until ms-general calls back via the approval callback endpoint. The `approval_request_id` field stores the reference from ms-general for tracking.

---

## 2. Approval Callback (New — Internal)

Called by **ms-general** when the approval workflow completes. This is an **internal endpoint** — the frontend does NOT call this directly.

### `POST /api/v1/dtr/{id}/approval-callback`

### Headers

| Header | Value | Description |
|--------|-------|-------------|
| `Authorization` | `Bearer <app.api_key>` | Shared API key between ms-general and ms-timekeeping |

### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `status` | string | **yes** | `"approved"` or `"rejected"` |

### Example

```json
{
  "status": "approved"
}
```

### Response (200 OK)

```json
{
  "success": true,
  "data": {
    "id": 1,
    "status": "approved",
    "approval_request_id": "req-abc-123",
    ...
  },
  "message": "Approval callback processed successfully"
}
```

### Behavior

| Status | Action |
|--------|--------|
| `approved` | Updates `dtr_corrections.status` to `approved`, syncs the `corrected_time` to the `time_entries` table (preserving existing break_in/break_out and the uncorrected field), logs an audit trail with original vs. corrected values |
| `rejected` | Updates `dtr_corrections.status` to `rejected`, no other changes |

### Errors

| Status | Condition |
|--------|-----------|
| `401` | Missing or invalid Bearer token |
| `422` | Invalid `status` value (must be `"approved"` or `"rejected"`) |
| `404` | DTR correction ID not found |

---

## 3. Update DTR Correction (Updated Behavior)

### `PUT /api/v1/filing/dtr/{id}`
### `PUT /api/v1/filing_approval/dtr/{id}`

**Behavior change:** When a DTR correction has an `approval_request_id` (i.e., it went through the ms-general approval flow), setting `status` to `"approved"` via this endpoint will:

1. Update the `dtr_corrections.status` to `"approved"` on the record itself
2. **NOT** sync directly to `time_entries` — the sync already happened via the approval callback

This enables manual/admin overrides of the status without duplicating the time entry sync. The legacy behavior (no `approval_request_id`) remains unchanged — direct sync still works as before.

---

## 4. Payroll Period Locks (New)

Manage payroll periods and prevent DTR corrections on locked periods.

### 4.1 List All Locks

### `GET /api/v1/payroll-period-locks`

### Query Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `is_locked` | boolean | Filter by lock status |

### Response (200 OK)

```json
{
  "success": true,
  "data": [
    {
      "id": 1,
      "period_start": "2026-05-01",
      "period_end": "2026-05-31",
      "period_label": "May 2026",
      "is_locked": true,
      "locked_by": "HR Manager",
      "locked_at": "2026-05-22T10:00:00.000000Z",
      "unlocked_by": null,
      "unlocked_at": null,
      "notes": "End of month processing",
      "created_at": "2026-05-22T10:00:00.000000Z",
      "updated_at": "2026-05-22T10:00:00.000000Z"
    }
  ],
  "message": "Payroll period locks retrieved"
}
```

---

### 4.2 Create a Payroll Period

### `POST /api/v1/payroll-period-locks`

### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `period_start` | date (Y-m-d) | **yes** | Start of payroll period |
| `period_end` | date (Y-m-d) | **yes** | End of payroll period (must be >= start) |
| `period_label` | string | **yes** | Human-readable label (e.g. "May 2026") |
| `notes` | text | no | Optional notes |

### Example

```json
{
  "period_start": "2026-06-01",
  "period_end": "2026-06-30",
  "period_label": "June 2026",
  "notes": "End of fiscal year"
}
```

### Response (201 Created)

```json
{
  "success": true,
  "data": {
    "id": 2,
    "period_start": "2026-06-01",
    "period_end": "2026-06-30",
    "period_label": "June 2026",
    "is_locked": false,
    "notes": "End of fiscal year",
    "created_at": "2026-05-22T10:00:00.000000Z",
    "updated_at": "2026-05-22T10:00:00.000000Z"
  },
  "message": "Payroll period created"
}
```

---

### 4.3 Lock a Payroll Period

### `POST /api/v1/payroll-period-locks/{id}/lock`

### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `locked_by` | string | **yes** | Name/ID of who is locking |

### Response (200 OK)

```json
{
  "success": true,
  "data": {
    "id": 1,
    "is_locked": true,
    "locked_by": "HR Manager",
    "locked_at": "2026-05-22T10:00:00.000000Z",
    ...
  },
  "message": "Payroll period locked"
}
```

---

### 4.4 Unlock a Payroll Period

### `POST /api/v1/payroll-period-locks/{id}/unlock`

### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `unlocked_by` | string | **yes** | Name/ID of who is unlocking |

### Response (200 OK)

```json
{
  "success": true,
  "data": {
    "id": 1,
    "is_locked": false,
    "unlocked_by": "HR Manager",
    "unlocked_at": "2026-05-22T10:00:00.000000Z",
    ...
  },
  "message": "Payroll period unlocked"
}
```

---

### 4.5 Delete a Payroll Period Lock

### `DELETE /api/v1/payroll-period-locks/{id}`

### Response (200 OK)

```json
{
  "success": true,
  "data": null,
  "message": "Payroll period lock deleted"
}
```

---

## 5. Integration Sequence (Frontend ↔ Backend)

```
[Employee submits DTR correction form]
       │
       ▼
POST /api/v1/filing/dtr
  { is_filing: true, entry_type, corrected_time, justification,... }
       │
       ▼
Response: { data: { id, status: "pending", approval_request_id } }
       │
       ▼
[Employee sees "pending" status in their DTR list]
       │
       ▼
[HR/Approver reviews in ms-general UI]
       │
       ▼
[ms-general approves/rejects]
       │
       ▼
POST /api/v1/dtr/{id}/approval-callback (internal - from ms-general)
       │
       ▼
Response: { data: { status: "approved"/"rejected" } }
       │
       ▼
[Frontend polls GET /api/v1/dtr/{id} or refetches list]
  → sees updated status
```

### Frontend Responsibilities

1. **New simplified form**: Send `entry_type`, `corrected_time`, and `justification` instead of the 4 datetime fields
2. **Justification**: Always require it from the user when `is_filing: true`
3. **Attachments**: Can still be attached (at least 1 recommended for filings)
4. **Status polling**: DTR corrections filed through the new flow start as `pending` — poll or refresh to see the final status after ms-general processes the approval
5. **Payroll period locks**: Check or display lock status; locked periods block new filings with a clear error message
6. **Legacy admin flow**: The old 4-field format still works when `is_filing: false` (auto-approved, no ms-general)

---

## 6. Pre-filing: Get Employee Section Code (New)

Returns the employee's section code, used to look up the approval chain before filing a DTR correction.

### `GET /api/v1/dtr/employee-section`

### Query Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `employee_id` | string | **yes** | Employee identifier |

### Response (200 OK)

```json
{
  "success": true,
  "data": {
    "employee_id": "EMP001",
    "section_code": "HR"
  },
  "message": "Employee section code retrieved successfully"
}
```

If no section is assigned to the employee, `section_code` will be `"DEFAULT"`. This matches the fallback used by ms-timekeeping when creating approval requests.

### Frontend Usage

Call this before displaying the approval chain to the user:

```typescript
const { data } = await api.get('/api/v1/dtr/employee-section', {
  params: { employee_id }
});
// Then use section_code to fetch the approval chain from ms-general:
const chain = await api.get('/general/api/v1/module-settings/approval-setting', {
  params: { module_code: 'dtr', section_code: data.section_code }
});
```

---

## 7. Database Schema Changes

### `dtr_corrections` — New Columns

| Column | Type | Description |
|--------|------|-------------|
| `entry_type` | string (nullable) | `"time_in"` or `"time_out"` |
| `corrected_time` | time (nullable) | The corrected clock time |
| `justification` | text (nullable) | Required for filings |
| `approval_request_id` | string (nullable, indexed) | Reference from ms-general approval |

### `payroll_period_locks` — New Table

| Column | Type | Description |
|--------|------|-------------|
| `id` | bigint PK | Auto-increment |
| `period_start` | date | Start of period |
| `period_end` | date | End of period |
| `period_label` | string | Label (e.g. "May 2026") |
| `is_locked` | boolean, default false | Lock status |
| `locked_by` | string (nullable) | Who locked it |
| `locked_at` | timestamp (nullable) | When it was locked |
| `unlocked_by` | string (nullable) | Who unlocked it |
| `unlocked_at` | timestamp (nullable) | When it was unlocked |
| `notes` | text (nullable) | Optional notes |
| `created_at` | timestamp | |
| `updated_at` | timestamp | |
| `deleted_at` | timestamp | Soft delete |
| *Unique* | `(period_start, period_end)` | Prevents duplicate periods |
| *Index* | `(period_start, period_end)` | Supports `containsDate` lookups |
