feat: implement encounter edit mode with GET/PATCH endpoints and comprehensive testing documentation
This commit is contained in:
@@ -0,0 +1,347 @@
|
||||
# ENCOUNTER API REFERENCE
|
||||
|
||||
## Endpoints
|
||||
|
||||
### GET Encounter Detail
|
||||
```
|
||||
GET /api/v1/encounter/{id}
|
||||
Query Parameters: includes=patient,patient-person,specialist,subspecialist
|
||||
```
|
||||
|
||||
**Response Structure:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "OK",
|
||||
"data": {
|
||||
"id": 123,
|
||||
"patient_id": 456,
|
||||
"patient": {
|
||||
"id": 456,
|
||||
"number": "RM-2025-001",
|
||||
"person": {
|
||||
"id": 789,
|
||||
"name": "John Doe",
|
||||
"residentIdentityNumber": "1234567890123456"
|
||||
}
|
||||
},
|
||||
"appointment_doctor_id": 5,
|
||||
"responsible_doctor_id": 5,
|
||||
"specialist_id": 10,
|
||||
"subspecialist_id": 15,
|
||||
"specialist": {
|
||||
"id": 10,
|
||||
"code": "CARDIO",
|
||||
"name": "Cardiology"
|
||||
},
|
||||
"subspecialist": {
|
||||
"id": 15,
|
||||
"code": "CARDIO_ADULT",
|
||||
"name": "Cardiology Adult"
|
||||
},
|
||||
"registeredAt": "2025-12-02T10:30:00Z",
|
||||
"visitDate": "2025-12-02T10:30:00Z",
|
||||
"member_number": "0000123456789",
|
||||
"ref_number": "0301P123456789",
|
||||
"sep_type": "1",
|
||||
"participant_group_code": "1",
|
||||
"paymentMethod_code": "insurance",
|
||||
"vclaimReference": {
|
||||
"noSep": "0301P123456789",
|
||||
"tglRujukan": "2025-12-02T00:00:00Z",
|
||||
"ppkDirujuk": "rssa",
|
||||
"jnsPelayanan": "2",
|
||||
"catatan": "Rujukan BPJS"
|
||||
},
|
||||
"class_code": "ambulatory",
|
||||
"subClass_code": "reg",
|
||||
"unit_code": "UNIT001",
|
||||
"created_at": "2025-12-01T08:00:00Z",
|
||||
"updated_at": "2025-12-02T10:30:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### PATCH Encounter
|
||||
```
|
||||
PATCH /api/v1/encounter/{id}
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
**Request Payload:**
|
||||
```json
|
||||
{
|
||||
"patient_id": 456,
|
||||
"appointment_doctor_code": "5",
|
||||
"class_code": "ambulatory",
|
||||
"subClass_code": "reg",
|
||||
"unit_code": "UNIT001",
|
||||
"refSource_name": "RSSA",
|
||||
"refTypeCode": "bpjs",
|
||||
"vclaimReference": {
|
||||
"noSep": "0301P123456789",
|
||||
"tglRujukan": "2025-12-02T00:00:00Z",
|
||||
"ppkDirujuk": "rssa",
|
||||
"jnsPelayanan": "2"
|
||||
},
|
||||
"paymentType": "jkn",
|
||||
"paymentMethod_code": "insurance",
|
||||
"specialist_id": 10,
|
||||
"subspecialist_id": 15,
|
||||
"member_number": "0000123456789",
|
||||
"registeredAt": "2025-12-02T10:30:00.000Z",
|
||||
"visitDate": "2025-12-02T10:30:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Encounter updated successfully",
|
||||
"data": {
|
||||
"id": 123,
|
||||
"patient_id": 456,
|
||||
"updated_at": "2025-12-02T15:45:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Handler Methods
|
||||
|
||||
### getFetchEncounterDetail()
|
||||
**Purpose:** Load encounter data and map to form
|
||||
**Trigger:** Automatically on page mount if `props.id > 0`
|
||||
**Logic:**
|
||||
1. GET `/api/v1/encounter/{id}?includes=patient,patient-person,specialist,subspecialist`
|
||||
2. Call `mapEncounterToForm()` to transform data
|
||||
3. Update `formObjects.value` with transformed data
|
||||
4. Fetch doctors for selected specialist
|
||||
|
||||
**Logs:**
|
||||
```
|
||||
📥 [EDIT MODE] Loading encounter detail: {id: 123}
|
||||
📥 [EDIT MODE] API Response: {success: true, data: {...}}
|
||||
📋 [EDIT MODE] Mapped encounter to form: {...}
|
||||
✅ [EDIT MODE] Encounter detail loaded and form mapped successfully
|
||||
```
|
||||
|
||||
### mapEncounterToForm(encounter)
|
||||
**Purpose:** Transform API response to form values
|
||||
**Input:** Encounter object from GET response
|
||||
**Output:** `formObjects.value` with mapped fields
|
||||
|
||||
**Mapping:**
|
||||
```typescript
|
||||
formData = {
|
||||
patientName: encounter.patient.person.name,
|
||||
nationalIdentity: encounter.patient.person.residentIdentityNumber,
|
||||
medicalRecordNumber: encounter.patient.number,
|
||||
doctorId: String(encounter.appointment_doctor_id),
|
||||
subSpecialistId: specialist.code (resolved from ID),
|
||||
registerDate: "YYYY-MM-DD" (from registeredAt or visitDate),
|
||||
paymentType: "jkn|spm|pks|jkmm" (mapped from paymentMethod_code),
|
||||
cardNumber: encounter.member_number,
|
||||
sepNumber: encounter.ref_number,
|
||||
sepType: encounter.sep_type,
|
||||
patientCategory: encounter.participant_group_code,
|
||||
sepReference: encounter.vclaimReference?.noSep
|
||||
}
|
||||
```
|
||||
|
||||
### handleSaveEncounter(formValues)
|
||||
**Purpose:** Save encounter (create or update)
|
||||
**Input:** Form values from validation schema
|
||||
**Output:** POST/PATCH API call and navigation
|
||||
|
||||
**Logic:**
|
||||
1. Validate patient selected
|
||||
2. Build payload with transformations:
|
||||
- Convert doctorId to string
|
||||
- Convert specialist code to ID
|
||||
- Map paymentType to paymentMethod_code
|
||||
- Format dates to ISO
|
||||
3. If `isEditMode`: PATCH `/api/v1/encounter/{id}`
|
||||
4. Else: POST `/api/v1/encounter`
|
||||
5. Success: Toast + redirect to list
|
||||
6. Error: Toast with error message
|
||||
|
||||
**Logs:**
|
||||
```
|
||||
💾 [EDIT MODE] Sending PATCH request: {id: 123, payload: {...}}
|
||||
💾 [ADD MODE] Sending POST request: {payload: {...}}
|
||||
📤 [SAVE] API Response: {success: true, message: "OK"}
|
||||
✅ [SAVE] Success - Redirecting to list page
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Type Mapping
|
||||
|
||||
### Payment Type Mapping
|
||||
| Form Value | API Value | Description |
|
||||
|-----------|-----------|-------------|
|
||||
| jkn | insurance | BPJS (Insurance) |
|
||||
| jkmm | insurance | BPJS Mandiri |
|
||||
| spm | cash | Out of pocket |
|
||||
| pks | membership | Partnership/Membership |
|
||||
|
||||
### Specialist Type Mapping
|
||||
| Field | Type | Example |
|
||||
|-------|------|---------|
|
||||
| specialist_id | number | 10 |
|
||||
| specialist.code | string | "CARDIO" |
|
||||
| subspecialist_id | number | 15 |
|
||||
| subspecialist.code | string | "CARDIO_ADULT" |
|
||||
|
||||
### Date Format Mapping
|
||||
| Format | Usage | Example |
|
||||
|--------|-------|---------|
|
||||
| YYYY-MM-DD | Form display | "2025-12-02" |
|
||||
| ISO 8601 with Z | API send | "2025-12-02T10:30:00.000Z" |
|
||||
| ISO 8601 | API response | "2025-12-02T10:30:00Z" |
|
||||
|
||||
---
|
||||
|
||||
## Error Codes & Handling
|
||||
|
||||
| HTTP Status | Error | Handler Response |
|
||||
|-------------|-------|-----------------|
|
||||
| 200 | Success | Navigate to list, show success toast |
|
||||
| 400 | Validation error | Show error message from API |
|
||||
| 401 | Unauthorized | Redirect to login (middleware) |
|
||||
| 403 | Forbidden | RBAC middleware blocks, show error page |
|
||||
| 404 | Not found | Show error toast, redirect to list |
|
||||
| 422 | Unprocessable | Show field-specific validation errors |
|
||||
| 500 | Server error | Show generic error toast, allow retry |
|
||||
|
||||
---
|
||||
|
||||
## Testing Curl Commands
|
||||
|
||||
### Get Encounter Detail
|
||||
```bash
|
||||
curl -X GET "http://localhost:3000/api/v1/encounter/123?includes=patient,patient-person,specialist,subspecialist" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json"
|
||||
```
|
||||
|
||||
### Update Encounter
|
||||
```bash
|
||||
curl -X PATCH "http://localhost:3000/api/v1/encounter/123" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"patient_id": 456,
|
||||
"appointment_doctor_code": "5",
|
||||
"class_code": "ambulatory",
|
||||
"subClass_code": "reg",
|
||||
"unit_code": "UNIT001",
|
||||
"paymentType": "jkn",
|
||||
"paymentMethod_code": "insurance",
|
||||
"specialist_id": 10,
|
||||
"subspecialist_id": 15,
|
||||
"member_number": "0000123456789",
|
||||
"registeredAt": "2025-12-02T10:30:00.000Z",
|
||||
"visitDate": "2025-12-02T10:30:00.000Z"
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Handler Flow Diagram
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ ENCOUNTER EDIT MODE │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
|
||||
1. PAGE MOUNT (props.id > 0 = Edit Mode)
|
||||
│
|
||||
├─→ handleInit()
|
||||
│ ├─ Load specialists
|
||||
│ ├─ Load doctors
|
||||
│ └─ Load payment types
|
||||
│
|
||||
├─→ getFetchEncounterDetail()
|
||||
│ ├─ GET /api/v1/encounter/{id}
|
||||
│ ├─ await mapEncounterToForm()
|
||||
│ │ └─ formObjects.value = { ...mapped data }
|
||||
│ └─ await handleFetchDoctors()
|
||||
│
|
||||
└─→ Entry form watches props.objects
|
||||
└─ Updates form UI with formObjects data
|
||||
|
||||
2. USER EDITS FORM
|
||||
│
|
||||
└─→ Form state updates reactively
|
||||
└─ Validation runs on change
|
||||
|
||||
3. USER CLICKS SAVE
|
||||
│
|
||||
└─→ handleSaveEncounter(formValues)
|
||||
├─ Build payload
|
||||
│ └─ Convert types, dates, payment method
|
||||
├─ if isEditMode:
|
||||
│ └─ PATCH /api/v1/encounter/{id}
|
||||
├─ else:
|
||||
│ └─ POST /api/v1/encounter
|
||||
├─ Success:
|
||||
│ ├─ Show success toast
|
||||
│ └─ Navigate to list page
|
||||
└─ Error:
|
||||
├─ Show error toast
|
||||
└─ Stay on page (allow retry)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Issues & Solutions
|
||||
|
||||
### Issue: Form not populated after page load
|
||||
**Check:**
|
||||
1. Console shows `✅ [EDIT MODE] Encounter detail loaded` ✓
|
||||
2. formObjects.value has data (check in Vue DevTools)
|
||||
3. Form watches props.objects properly
|
||||
|
||||
**Solution:**
|
||||
```typescript
|
||||
// In entry.vue, ensure watch is set up:
|
||||
watch(
|
||||
() => props.objects,
|
||||
(objects) => {
|
||||
// Auto-populate form fields from props.objects
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
)
|
||||
```
|
||||
|
||||
### Issue: PATCH request failing with 422
|
||||
**Check:**
|
||||
1. Console shows full payload being sent
|
||||
2. Required fields are present
|
||||
3. Data types match API expectations (string IDs, ISO dates)
|
||||
|
||||
**Solution:**
|
||||
Look for payload fields and compare with expected structure in ENCOUNTER_API_REFERENCE.md
|
||||
|
||||
### Issue: Specialist not loading after edit
|
||||
**Check:**
|
||||
1. `subSpecialistId` is correctly set in form
|
||||
2. `handleFetchDoctors(subSpecialistId)` called after mapEncounterToForm
|
||||
|
||||
**Solution:**
|
||||
Verify specialist code to ID resolution in `getSpecialistIdsFromCode()`
|
||||
|
||||
---
|
||||
|
||||
## Performance Notes
|
||||
|
||||
- GET includes relationships: `patient,patient-person,specialist,subspecialist`
|
||||
- No N+1 queries expected (relationships included)
|
||||
- Form mapping happens once on page load
|
||||
- Doctor list fetched only if specialist selected
|
||||
- Debounced search if SEP number changed (500ms)
|
||||
|
||||
Reference in New Issue
Block a user