# Design Document: USMS-DOJ Prison API

## Overview

Fitur ini membangun integrasi API antara sistem USMS (localhost:8003) dan DOJ (localhost:8001) untuk sinkronisasi data tahanan. DOJ menyediakan RESTful API endpoints untuk menerima, menyimpan, dan mengambil data Criminal Case. USMS menggunakan service class `DojCriminalCaseService` untuk berkomunikasi dengan DOJ API.

**Alur utama:**
1. Officer USMS menambahkan tahanan di halaman `/prison`
2. USMS memetakan data `UserPrison` ke format `Criminal_Case`
3. USMS mengirim HTTP POST ke DOJ `/api/criminal-cases`
4. DOJ memvalidasi, menyimpan, dan mengembalikan response

**Prinsip desain:**
- Mengikuti pola existing di DOJ (response format `{success, message, data}`)
- Menggunakan middleware `api.key` yang sudah ada untuk autentikasi
- Fire-and-forget dari sisi USMS: kegagalan API tidak menggagalkan penyimpanan lokal
- Service class di USMS mengenkapsulasi semua komunikasi HTTP ke DOJ

## Architecture

### System Context Diagram

```mermaid
graph LR
    subgraph USMS ["USMS App (localhost:8003)"]
        PC[PrisonController]
        SVC[DojCriminalCaseService]
    end

    subgraph DOJ ["DOJ App (localhost:8001)"]
        MW[ApiKeyMiddleware]
        CC[CriminalCaseController]
        DB[(criminal_cases table)]
    end

    PC -->|"addPrisoner()"| SVC
    SVC -->|"POST /api/criminal-cases"| MW
    SVC -->|"GET /api/criminal-cases"| MW
    SVC -->|"GET /api/criminal-cases/{id}"| MW
    MW -->|valid key| CC
    CC -->|CRUD| DB
    MW -->|invalid key| SVC
```

### Sequence Diagram: Add Prisoner Flow

```mermaid
sequenceDiagram
    participant Officer
    participant PrisonController
    participant DojCriminalCaseService
    participant DOJ API
    participant DB (USMS)
    participant DB (DOJ)

    Officer->>PrisonController: POST /prison/{id}/prisoners
    PrisonController->>DB (USMS): Save UserPrison locally
    PrisonController->>DojCriminalCaseService: store(mappedData)
    DojCriminalCaseService->>DOJ API: POST /api/criminal-cases (X-API-Key)
    DOJ API->>DOJ API: Validate request
    DOJ API->>DB (DOJ): Insert criminal_case
    DOJ API-->>DojCriminalCaseService: 201 {success, data}
    DojCriminalCaseService-->>PrisonController: response
    PrisonController-->>Officer: redirect with success

    Note over PrisonController,DojCriminalCaseService: If DOJ fails, local save is preserved
```

## Components and Interfaces

### DOJ Side

#### 1. CriminalCaseController (New)

**Location:** `DOJ/app/Http/Controllers/Api/CriminalCaseController.php`

```php
namespace App\Http\Controllers\Api;

class CriminalCaseController extends Controller
{
    /**
     * Store a new criminal case.
     * POST /api/criminal-cases
     */
    public function store(Request $request): JsonResponse;

    /**
     * List criminal cases with optional filters.
     * GET /api/criminal-cases
     * Query params: cid_no, accused_name, status
     */
    public function index(Request $request): JsonResponse;

    /**
     * Show a single criminal case by ID or cid_no.
     * GET /api/criminal-cases/{id}
     */
    public function show(string $id): JsonResponse;

    /**
     * Generate next application number.
     * Format: CT-CR-{YEAR}-{XXXX}
     */
    private function generateApplicationNo(): string;
}
```

#### 2. API Routes (Updated)

**Location:** `DOJ/routes/api.php`

```php
// Criminal Cases API (protected by API key)
Route::middleware('api.key')->group(function () {
    Route::get('criminal-cases', [CriminalCaseController::class, 'index']);
    Route::post('criminal-cases', [CriminalCaseController::class, 'store']);
    Route::get('criminal-cases/{id}', [CriminalCaseController::class, 'show']);
});
```

#### 3. ApiKeyMiddleware (Existing - No Changes)

**Location:** `DOJ/app/Http/Middleware/ApiKeyMiddleware.php`

Middleware sudah ada dan sudah mengimplementasikan:
- Membaca key dari header `X-API-Key` atau query param `api_key`
- Prioritas header over query param
- Timing-safe comparison via `hash_equals()`
- Response 401 jika key tidak valid

### USMS Side

#### 4. DojCriminalCaseService (New)

**Location:** `usms/app/Services/DojCriminalCaseService.php`

```php
namespace App\Services;

class DojCriminalCaseService
{
    private string $baseUrl;
    private string $apiKey;
    private int $timeout = 10;

    public function __construct();

    /**
     * Send criminal case data to DOJ.
     * @throws \RuntimeException if DOJ_API_URL or DOJ_API_KEY not configured
     * @throws \Illuminate\Http\Client\RequestException on non-2xx response
     */
    public function store(array $data): array;

    /**
     * Get list of criminal cases from DOJ with optional filters.
     * @param array $filters ['cid_no' => ..., 'accused_name' => ..., 'status' => ...]
     */
    public function index(array $filters = []): array;

    /**
     * Get single criminal case by ID or cid_no.
     */
    public function show(string $id): array;

    /**
     * Map UserPrison data to DOJ Criminal Case format.
     */
    public static function mapPrisonerToCase(UserPrison $prisoner): array;
}
```

#### 5. PrisonController (Modified)

**Location:** `usms/app/Http/Controllers/Member/PrisonController.php`

Modifikasi method `addPrisoner()` untuk memanggil `DojCriminalCaseService::store()` setelah data lokal tersimpan. Kegagalan API di-catch dan di-log, tidak menggagalkan flow utama.

## Data Models

### Criminal Case (DOJ - Existing Table)

| Field | Type | Constraints | Notes |
|-------|------|-------------|-------|
| id | bigint | PK, auto-increment | |
| application_no | string | unique, auto-generated | Format: CT-CR-{YEAR}-{XXXX} |
| cid_no | string(100) | required | CID dari USMS |
| accused_name | string(255) | required | Nama tersangka |
| judge_name | string(255) | nullable | |
| prosecutor_name | string(255) | nullable | |
| public_defender_name | string(255) | nullable | |
| date_of_hearing | date | nullable | Format: YYYY-MM-DD |
| accused_plea | string(100) | required | |
| bail_amount | decimal(10,2) | nullable, min:0 | |
| bail_status | enum | required | paid, not paid, refund |
| decision | string(1000) | nullable | Gabungan penal_code names |
| status | enum | required | active, closed, arraignment, trial, sentence, search warrant, arrest warrant, withdrawn |
| user_id | bigint | nullable, FK | |
| created_at | timestamp | | |
| updated_at | timestamp | | |
| deleted_at | timestamp | nullable | SoftDeletes |

### Data Mapping: UserPrison → Criminal Case

| USMS Source | DOJ Target | Transformation |
|-------------|------------|----------------|
| `suspect.nama` | `accused_name` | Direct copy |
| `prison.cid` | `cid_no` | Direct copy |
| `bail_out` | `bail_amount` | Direct copy (numeric) |
| `penal_code[].name` | `decision` | Join with comma separator |
| `bail_out` | `bail_status` | `> 0` → "paid", else → "not paid" |
| (constant) | `accused_plea` | "not guilty" |
| (constant) | `status` | "active" |
| (constant) | `judge_name` | "" |
| (constant) | `prosecutor_name` | "" |

### Application Number Generation Algorithm

```
function generateApplicationNo():
    year = current year (4 digits)
    prefix = "CT-CR-{year}-"
    lastRecord = CriminalCase where application_no LIKE "CT-CR-{year}-%"
                 order by application_no DESC
                 first()
    if lastRecord exists:
        lastNumber = extract last 4 digits from lastRecord.application_no
        nextNumber = lastNumber + 1
    else:
        nextNumber = 1
    return prefix + pad(nextNumber, 4, '0')
```

## Correctness Properties

*A property is a characteristic or behavior that should hold true across all valid executions of a system — essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.*

### Property 1: Store Round-Trip

*For any* valid criminal case payload (with all required fields meeting validation constraints), POSTing it to `/api/criminal-cases` SHALL return HTTP 201 with a response containing `success: true` and a `data` object that includes all submitted fields plus a generated `application_no`.

**Validates: Requirements 1.1, 1.3**

### Property 2: Application Number Format and Sequencing

*For any* sequence of N successfully created criminal cases within the same year, each generated `application_no` SHALL match the pattern `CT-CR-{YYYY}-{XXXX}` where YYYY is the current year and XXXX is a zero-padded number that increments by 1 for each subsequent record.

**Validates: Requirements 1.2**

### Property 3: Validation Rejection

*For any* request payload that violates at least one validation rule (missing required field, string exceeding max length, negative bail_amount, invalid enum value, or malformed date), the API SHALL return HTTP 422 with `success: false` and an `errors` object containing at least one key-value pair identifying the invalid field.

**Validates: Requirements 1.4, 1.6**

### Property 4: Data Mapping Transformation

*For any* UserPrison record with a non-null suspect name, the mapping function SHALL produce a Criminal Case payload where `accused_name` equals `suspect.nama`, `cid_no` equals `prison.cid`, `bail_amount` equals `bail_out`, and `decision` equals the comma-joined `name` fields from the `penal_code` array.

**Validates: Requirements 3.2**

### Property 5: Bail Status Derivation

*For any* numeric `bail_out` value, the derived `bail_status` SHALL be "paid" if `bail_out > 0`, and "not paid" if `bail_out` is null or 0.

**Validates: Requirements 3.3**

### Property 6: Index Ordering

*For any* set of criminal cases in the database, a GET request to `/api/criminal-cases` SHALL return them ordered by `created_at` descending (newest first).

**Validates: Requirements 4.1**

### Property 7: Filter by cid_no (Partial, Case-Insensitive)

*For any* filter value provided as `cid_no` query parameter, all returned criminal cases SHALL have a `cid_no` field that contains the filter value as a substring (case-insensitive comparison).

**Validates: Requirements 4.2**

### Property 8: Filter by accused_name (Partial, Case-Insensitive)

*For any* filter value provided as `accused_name` query parameter, all returned criminal cases SHALL have an `accused_name` field that contains the filter value as a substring (case-insensitive comparison).

**Validates: Requirements 4.3**

### Property 9: Filter by status (Exact Match)

*For any* valid status value provided as `status` query parameter, all returned criminal cases SHALL have a `status` field that exactly equals the filter value.

**Validates: Requirements 4.4**

### Property 10: Show Lookup by ID or cid_no

*For any* existing criminal case, a GET request to `/api/criminal-cases/{id}` using either its numeric ID or its `cid_no` SHALL return the same complete criminal case data with all required fields.

**Validates: Requirements 6.1, 6.2**

### Property 11: Non-2xx Response Throws Exception

*For any* HTTP response from DOJ API with status code outside the 2xx range, the `DojCriminalCaseService` SHALL throw an exception containing the HTTP status code and response body.

**Validates: Requirements 5.5**

## Error Handling

### DOJ API Error Responses

| Scenario | HTTP Status | Response Format |
|----------|-------------|-----------------|
| Valid request, success | 201 (store) / 200 (index/show) | `{"success": true, "message": "...", "data": ...}` |
| Validation failure | 422 | `{"success": false, "message": "...", "errors": {...}}` |
| Unauthorized (no/invalid key) | 401 | `{"success": false, "message": "Unauthorized..."}` |
| Not found | 404 | `{"success": false, "message": "Data tidak ditemukan.", "data": null}` |
| Server error | 500 | `{"success": false, "message": "Internal server error."}` |

### USMS Error Handling Strategy

```mermaid
graph TD
    A[addPrisoner called] --> B[Save UserPrison locally]
    B --> C{suspect.nama valid?}
    C -->|No| D[Log validation error, skip API call]
    C -->|Yes| E[Map data & call DojCriminalCaseService::store]
    E --> F{API Response}
    F -->|201 Success| G[Continue normally]
    F -->|4xx/5xx Error| H[Log error with status + timestamp]
    F -->|Timeout 10s| I[Log timeout error with timestamp]
    H --> G
    I --> G
    D --> G
```

**Key principles:**
- Local data save ALWAYS succeeds regardless of DOJ API status
- All API errors are caught and logged, never thrown to the user
- Log entries include: timestamp, HTTP status code (if available), response body, and the prisoner data that failed
- Use Laravel's `Log::error()` for structured logging

### Exception Handling in DojCriminalCaseService

```php
// In store() method:
try {
    $response = Http::timeout($this->timeout)
        ->withHeaders([...])
        ->post($url, $data);

    if (!$response->successful()) {
        throw new \RuntimeException(
            "DOJ API error [{$response->status()}]: {$response->body()}"
        );
    }

    return $response->json();
} catch (ConnectionException $e) {
    throw new \RuntimeException("DOJ API timeout: {$e->getMessage()}");
}
```

### In PrisonController (caller):

```php
try {
    $service = app(DojCriminalCaseService::class);
    $mapped = DojCriminalCaseService::mapPrisonerToCase($userPrison);
    $service->store($mapped);
} catch (\Throwable $e) {
    Log::error('Failed to send prisoner data to DOJ', [
        'prisoner_id' => $userPrison->id,
        'error' => $e->getMessage(),
        'timestamp' => now()->toIso8601String(),
    ]);
}
```

## Testing Strategy

### Approach

Testing menggunakan dual approach:
- **Unit tests (PHPUnit)**: Untuk specific examples, edge cases, dan integration points
- **Property-based tests**: Untuk universal properties yang harus berlaku di semua valid inputs

### Property-Based Testing Library

**Library:** `phpunit/phpunit` with custom data providers generating random inputs (PHP tidak memiliki mature PBT library seperti QuickCheck, jadi kita gunakan data providers dengan randomized inputs via `Faker`).

Alternatif: Gunakan `eris/eris` (PHP property-based testing library) jika tersedia, atau implementasi sederhana dengan `Faker` + loop 100 iterasi.

**Configuration:** Minimum 100 iterations per property test.

**Tag format:** `Feature: usms-doj-prison-api, Property {number}: {property_text}`

### Test Plan

#### DOJ Side Tests

| Test | Type | Property # | Description |
|------|------|-----------|-------------|
| Store valid data returns 201 | Property | 1 | Random valid payloads → 201 + correct response |
| Application number format | Property | 2 | Sequential creates → correct CT-CR-YYYY-XXXX pattern |
| Validation rejects invalid data | Property | 3 | Random invalid payloads → 422 + errors |
| Index returns ordered results | Property | 6 | Random cases → ordered by created_at desc |
| Filter cid_no partial match | Property | 7 | Random filter → all results contain substring |
| Filter accused_name partial match | Property | 8 | Random filter → all results contain substring |
| Filter status exact match | Property | 9 | Valid status → all results match exactly |
| Show by ID or cid_no | Property | 10 | Random case → lookup by ID and cid_no both work |
| Auth middleware rejects no key | Example | - | No key → 401 |
| Auth middleware rejects wrong key | Example | - | Wrong key → 401 |
| Auth accepts valid key | Example | - | Valid key → passes through |
| Show returns 404 for missing | Edge case | - | Non-existent ID → 404 |
| Invalid status filter ignored | Example | - | Invalid status → returns all |

#### USMS Side Tests

| Test | Type | Property # | Description |
|------|------|-----------|-------------|
| Data mapping transformation | Property | 4 | Random UserPrison → correct mapped fields |
| Bail status derivation | Property | 5 | Random bail_out → correct bail_status |
| Non-2xx throws exception | Property | 11 | Random error codes → exception thrown |
| Service throws on missing config | Example | - | No env vars → RuntimeException |
| API failure doesn't break local save | Example | - | Mock 500 → local data preserved |
| Timeout doesn't break local save | Example | - | Mock timeout → local data preserved |
| Null suspect name skips API | Edge case | - | Null nama → no HTTP request |
| Default values set correctly | Example | - | accused_plea, status, judge_name constants |

### Test File Locations

- DOJ: `DOJ/tests/Feature/Api/CriminalCaseApiTest.php`
- USMS: `usms/tests/Unit/Services/DojCriminalCaseServiceTest.php`
- USMS: `usms/tests/Feature/PrisonControllerDojIntegrationTest.php`
