perbaikan ambil tiket

This commit is contained in:
Fanrouver
2026-02-24 09:12:48 +07:00
parent 4342cdcc67
commit 94ff9f55d9
6 changed files with 148 additions and 38 deletions
+69 -6
View File
@@ -1,15 +1,18 @@
# API Endpoint Documentation - Hak Akses
## Deskripsi
Dokumentasi ini menjelaskan semua API endpoint yang diperlukan untuk fitur **Hak Akses** di halaman `/pages/Setting/HakAkses.vue`. Endpoint-endpoint ini akan dihubungkan ke backend API yang disediakan oleh tim backend.
---
## Base URL
```
{API_BASE_URL}/api/v1
```
**Catatan:** Ganti `{API_BASE_URL}` dengan URL backend yang sebenarnya (contoh: `http://10.10.150.131:8089`)
**Catatan:** Ganti `{API_BASE_URL}` dengan URL backend yang sebenarnya (contoh: `http://10.10.123.140:8089/`)
---
@@ -18,12 +21,15 @@ Dokumentasi ini menjelaskan semua API endpoint yang diperlukan untuk fitur **Hak
Mengambil daftar semua users beserta roles, groups, dan informasi lainnya yang diperlukan untuk form hak akses.
### Endpoint
```
GET /api/users/list
```
### Request
**Headers:**
```
Content-Type: application/json
Accept: application/json
@@ -32,7 +38,9 @@ Accept: application/json
**Query Parameters:** Tidak ad
### Response
**Success (200 OK):**
```json
[
{
@@ -46,10 +54,7 @@ Accept: application/json
"realmRoles": ["default-roles-sandbox"],
"accountRoles": [],
"resourceRoles": [],
"groups": [
"/Instalasi STIM/Devops/Superadmin",
"/Instalasi STIM/Admin"
],
"groups": ["/Instalasi STIM/Devops/Superadmin", "/Instalasi STIM/Admin"],
"given_name": "John",
"family_name": "Doe",
"createdAt": 1704067200,
@@ -59,6 +64,7 @@ Accept: application/json
```
**Error (500 Internal Server Error):**
```json
{
"statusCode": 500,
@@ -67,6 +73,7 @@ Accept: application/json
```
### Field Description
- `id`: Unique identifier user
- `namaLengkap`: Nama lengkap user
- `namaUser`: Username untuk login
@@ -90,12 +97,15 @@ Accept: application/json
Mengambil daftar permissions dari backend berdasarkan role dan group yang dipilih.
### Endpoint
```
GET /api/v1/permission
```
### Request
**Headers:**
```
Content-Type: application/json
Accept: application/json
@@ -108,12 +118,15 @@ Accept: application/json
| `groups` | string | Yes | Group name atau group path (contoh: "STIM", "/Instalasi STIM/Devops/Superadmin") |
**Example:**
```
GET /api/v1/permission?roles=superadmin&groups=STIM
```
### Response
**Success (200 OK):**
```json
{
"message": "Data permission berhasil diambil",
@@ -169,6 +182,7 @@ GET /api/v1/permission?roles=superadmin&groups=STIM
```
**Error (400 Bad Request):**
```json
{
"statusCode": 400,
@@ -177,6 +191,7 @@ GET /api/v1/permission?roles=superadmin&groups=STIM
```
**Error (500 Internal Server Error):**
```json
{
"message": "Failed to fetch permissions",
@@ -189,6 +204,7 @@ GET /api/v1/permission?roles=superadmin&groups=STIM
```
### Field Description
- `id`: Unique identifier permission
- `create`: Boolean, permission untuk create/tambah data
- `read`: Boolean, permission untuk read/lihat data
@@ -209,12 +225,15 @@ GET /api/v1/permission?roles=superadmin&groups=STIM
Mengambil daftar semua hak akses yang sudah tersimpan.
### Endpoint
```
GET /api/v1/hak-akses
```
### Request
**Headers:**
```
Content-Type: application/json
Accept: application/json
@@ -223,7 +242,9 @@ Accept: application/json
**Query Parameters:** Tidak ada
### Response
**Success (200 OK):**
```json
[
{
@@ -262,6 +283,7 @@ Accept: application/json
```
**Error (500 Internal Server Error):**
```json
{
"statusCode": 500,
@@ -270,6 +292,7 @@ Accept: application/json
```
### Field Description
- `id`: Unique identifier hak akses
- `userId`: ID user (optional, untuk backward compatibility)
- `namaLengkap`: Nama lengkap user (optional)
@@ -297,18 +320,22 @@ Accept: application/json
Membuat hak akses baru (group-based atau individual).
### Endpoint
```
POST /api/v1/hak-akses
```
### Request
**Headers:**
```
Content-Type: application/json
Accept: application/json
```
**Body:**
```json
{
"role": "superadmin",
@@ -338,7 +365,9 @@ Accept: application/json
```
### Response
**Success (201 Created):**
```json
{
"success": true,
@@ -367,6 +396,7 @@ Accept: application/json
```
**Error (400 Bad Request):**
```json
{
"statusCode": 400,
@@ -375,6 +405,7 @@ Accept: application/json
```
**Error (409 Conflict):**
```json
{
"statusCode": 409,
@@ -383,12 +414,15 @@ Accept: application/json
```
### Field Description
**Required Fields:**
- `role`: Role name (string, required)
- `group`: Group name (string, required)
- `hakAksesMenu`: Array of menu permissions (array, required)
**Optional Fields:**
- `userId`: ID user (string, optional)
- `namaLengkap`: Nama lengkap (string, optional)
- `namaUser`: Username (string, optional)
@@ -404,12 +438,15 @@ Accept: application/json
Mengupdate hak akses yang sudah ada.
### Endpoint
```
PATCH /api/v1/hak-akses/{id}
```
### Request
**Headers:**
```
Content-Type: application/json
Accept: application/json
@@ -421,6 +458,7 @@ Accept: application/json
| `id` | integer | Yes | ID hak akses yang akan diupdate |
**Body (semua field optional, hanya kirim field yang ingin diupdate):**
```json
{
"role": "admin",
@@ -440,7 +478,9 @@ Accept: application/json
```
### Response
**Success (200 OK):**
```json
{
"success": true,
@@ -469,6 +509,7 @@ Accept: application/json
```
**Error (400 Bad Request):**
```json
{
"statusCode": 400,
@@ -477,6 +518,7 @@ Accept: application/json
```
**Error (404 Not Found):**
```json
{
"statusCode": 404,
@@ -485,6 +527,7 @@ Accept: application/json
```
**Error (500 Internal Server Error):**
```json
{
"statusCode": 500,
@@ -499,12 +542,15 @@ Accept: application/json
Menghapus hak akses.
### Endpoint
```
DELETE /api/v1/hak-akses/{id}
```
### Request
**Headers:**
```
Content-Type: application/json
Accept: application/json
@@ -516,7 +562,9 @@ Accept: application/json
| `id` | integer | Yes | ID hak akses yang akan dihapus |
### Response
**Success (200 OK):**
```json
{
"success": true,
@@ -525,6 +573,7 @@ Accept: application/json
```
**Error (400 Bad Request):**
```json
{
"statusCode": 400,
@@ -533,6 +582,7 @@ Accept: application/json
```
**Error (404 Not Found):**
```json
{
"statusCode": 404,
@@ -541,6 +591,7 @@ Accept: application/json
```
**Error (500 Internal Server Error):**
```json
{
"statusCode": 500,
@@ -558,11 +609,13 @@ Sistem akan otomatis memetakan `pagename` dari API permission ke nama menu di si
2. **Partial match**: `pagename.toLowerCase().includes(menu.name.toLowerCase())` atau sebaliknya
### Contoh Mapping:
- `pagename: "Halaman Utama"` → Menu: "Dashboard"
- `pagename: "Pengaturan"` → Menu: "Master Data"
- `pagename: "Hak Akses"` → Menu: "Hak Akses"
### Permission Mapping:
- `read: true``canView: true`
- `create: true``canAdd: true`
- `update: true``canEdit: true`
@@ -574,17 +627,21 @@ Sistem akan otomatis memetakan `pagename` dari API permission ke nama menu di si
## Group-Based Access
Sistem mendukung **Group-Based Access** dimana:
- Satu hak akses dapat diberikan ke semua user yang memiliki group dan role yang sama
- Setiap user dapat memiliki multiple hak akses dari berbagai group
- Hak akses disimpan dengan flag `isGroupBased: true` dan `groupPath` untuk identifikasi
### Contoh:
Jika hak akses dibuat dengan:
- `role: "superadmin"`
- `groupPath: "/Instalasi STIM/Devops/Superadmin"`
- `isGroupBased: true`
Maka semua user yang memiliki:
- Role: "superadmin" **DAN**
- Group: "/Instalasi STIM/Devops/Superadmin"
@@ -604,6 +661,7 @@ Semua endpoint harus mengembalikan error dengan format konsisten:
```
**HTTP Status Codes:**
- `200 OK`: Request berhasil
- `201 Created`: Resource berhasil dibuat
- `400 Bad Request`: Request tidak valid (missing required fields, invalid format)
@@ -618,6 +676,7 @@ Semua endpoint harus mengembalikan error dengan format konsisten:
**Catatan:** Tim backend perlu menentukan apakah endpoint-endpoint ini memerlukan authentication token atau tidak. Jika diperlukan, tambahkan:
**Headers:**
```
Authorization: Bearer {access_token}
```
@@ -629,18 +688,21 @@ Authorization: Bearer {access_token}
### Contoh Request dengan cURL:
**1. Get Users List:**
```bash
curl -X GET "http://{API_BASE_URL}/api/users/list" \
-H "Content-Type: application/json"
```
**2. Get Permissions:**
```bash
curl -X GET "http://{API_BASE_URL}/api/v1/permission?roles=superadmin&groups=STIM" \
-H "Content-Type: application/json"
```
**3. Create Hak Akses:**
```bash
curl -X POST "http://{API_BASE_URL}/api/v1/hak-akses" \
-H "Content-Type: application/json" \
@@ -664,6 +726,7 @@ curl -X POST "http://{API_BASE_URL}/api/v1/hak-akses" \
```
**4. Update Hak Akses:**
```bash
curl -X PATCH "http://{API_BASE_URL}/api/v1/hak-akses/1" \
-H "Content-Type: application/json" \
@@ -674,6 +737,7 @@ curl -X PATCH "http://{API_BASE_URL}/api/v1/hak-akses/1" \
```
**5. Delete Hak Akses:**
```bash
curl -X DELETE "http://{API_BASE_URL}/api/v1/hak-akses/1" \
-H "Content-Type: application/json"
@@ -697,4 +761,3 @@ curl -X DELETE "http://{API_BASE_URL}/api/v1/hak-akses/1" \
## Kontak
Jika ada pertanyaan atau perubahan requirement, silakan hubungi tim frontend.
+15 -2
View File
@@ -1,21 +1,27 @@
# Placeholder API Permission
## Deskripsi
Placeholder API untuk permission digunakan untuk testing dan development ketika backend API tidak tersedia.
## Cara Menggunakan
### 1. Menggunakan Placeholder API secara Default
Placeholder API akan otomatis digunakan sebagai fallback jika backend API (`http://10.10.150.131:8089/api/v1/permission`) tidak dapat diakses.
Placeholder API akan otomatis digunakan sebagai fallback jika backend API (`http://10.10.123.140:8089/api/v1/permission`) tidak dapat diakses.
### 2. Memaksa Menggunakan Placeholder API
Tambahkan query parameter `usePlaceholder=true` pada request:
```
GET /api/permission?roles=superadmin&groups=STIM&usePlaceholder=true
```
### 3. Menonaktifkan Placeholder API
Tambahkan query parameter `usePlaceholder=false` pada request:
```
GET /api/permission?roles=superadmin&groups=STIM&usePlaceholder=false
```
@@ -23,7 +29,9 @@ GET /api/permission?roles=superadmin&groups=STIM&usePlaceholder=false
## Data Placeholder yang Tersedia
### Role: superadmin, Group: STIM
Data placeholder mengembalikan 5 permission items:
- Halaman Utama (read: true, active: true)
- Pengaturan (read: true, active: true)
- Halaman (read: true, active: true, disable: true)
@@ -32,6 +40,7 @@ Data placeholder mengembalikan 5 permission items:
## Mapping Pagename ke Menu Sidebar
Sistem akan otomatis memetakan pagename dari API ke nama menu di sidebar:
- "Halaman Utama" → "Dashboard"
- "Pengaturan" → "Master Data"
- "Halaman" → "Master Data"
@@ -40,6 +49,7 @@ Sistem akan otomatis memetakan pagename dari API ke nama menu di sidebar:
## Testing dengan User bayurssa
Untuk testing dengan user email "bayurssa":
1. Pastikan user memiliki role "superadmin" dan group "STIM" di Keycloak
2. Login dengan email "bayurssa" dan password "12345"
3. Sistem akan otomatis menggunakan placeholder API jika backend tidak tersedia
@@ -50,21 +60,24 @@ Untuk testing dengan user email "bayurssa":
Sistem secara otomatis melakukan normalisasi untuk role dan group:
### Normalisasi Role
- `default-roles-sandbox``superadmin`
- Role lain akan digunakan apa adanya (lowercase)
### Normalisasi Group
- `Instalasi STIM``STIM`
- Group yang mengandung "Instalasi" akan diekstrak untuk mengambil bagian "STIM"
- Group lain akan digunakan apa adanya (uppercase)
### Contoh Mapping
- Role: `default-roles-sandbox` + Group: `Instalasi STIM` → API akan menggunakan `roles=superadmin&groups=STIM`
- Role: `default-roles-sandbox` + Group: `STIM` → API akan menggunakan `roles=superadmin&groups=STIM`
## Catatan
- Placeholder API hanya tersedia untuk kombinasi role dan group yang sudah didefinisikan
- Sistem akan otomatis melakukan normalisasi role dan group sebelum memanggil API
- Jika role/group tidak ditemukan di placeholder, sistem akan mencoba menggunakan backend API
- Jika backend API juga gagal, sistem akan mengembalikan data kosong
+3 -3
View File
@@ -3,9 +3,9 @@ import vuetify, { transformAssetUrls } from "vite-plugin-vuetify";
export default defineNuxtConfig({
compatibilityDate: "2025-05-15",
devtools: {
enabled: true,
enabled: process.env.ENABLE_DEVTOOLS === 'true',
timeline: {
enabled: true,
enabled: false,
},
},
@@ -81,8 +81,8 @@ export default defineNuxtConfig({
// authUrl: process.env.AUTH_ORIGIN || "http://10.10.150.175:3001",
// authUrl: process.env.AUTH_ORIGIN || "http://localhost:3001",
wsBaseUrl: process.env.WS_BASE_URL || 'ws://10.10.123.135:8084/api/v1/ws',
ekstrakExpertiseUrl: process.env.EKSTRAK_EXPERTISE_URL || 'http://10.10.123.218/ekstrakexpertise',
verificationApiBaseUrl: process.env.VERIFICATION_API_BASE_URL || 'http://10.10.123.140:8089/api/v1',
},
},
+38 -10
View File
@@ -229,12 +229,14 @@
size="large"
block
@click="registerPatientDirectly('Eksekutif')"
:disabled="isShift1Full(selectedClinic) || !selectedDoctor"
:loading="isRegistering"
:disabled="isRegistering || isShift1Full(selectedClinic) || !selectedDoctor"
:class="{
'btn-disabled':
isShift1Full(selectedClinic) || !selectedDoctor,
isRegistering || isShift1Full(selectedClinic) || !selectedDoctor,
}"
>
DAFTAR SEKARANG
</v-btn>
<p v-if="isShift1Full(selectedClinic)" class="disabled-hint">
@@ -258,9 +260,11 @@
size="large"
block
@click="registerPatientDirectly('BPJS')"
:disabled="isShift1Full(selectedClinic)"
:class="{ 'btn-disabled': isShift1Full(selectedClinic) }"
:loading="isRegistering"
:disabled="isRegistering || isShift1Full(selectedClinic)"
:class="{ 'btn-disabled': isRegistering || isShift1Full(selectedClinic) }"
>
BPJS
</v-btn>
</v-col>
@@ -271,9 +275,11 @@
size="large"
block
@click="registerPatientDirectly('UMUM')"
:disabled="isShift1Full(selectedClinic)"
:class="{ 'btn-disabled': isShift1Full(selectedClinic) }"
:loading="isRegistering"
:disabled="isRegistering || isShift1Full(selectedClinic)"
:class="{ 'btn-disabled': isRegistering || isShift1Full(selectedClinic) }"
>
UMUM / JKMM / SPM / DLL
</v-btn>
</v-col>
@@ -292,7 +298,9 @@
size="large"
block
@click="selectVisitType('JADWAL_LAIN')"
:disabled="isRegistering"
>
JADWAL LAIN
</v-btn>
</v-col>
@@ -303,9 +311,11 @@
size="large"
block
@click="registerPatientDirectly('FAST_TRACK')"
:disabled="isShift1Full(selectedClinic)"
:class="{ 'btn-disabled': isShift1Full(selectedClinic) }"
:loading="isRegistering"
:disabled="isRegistering || isShift1Full(selectedClinic)"
:class="{ 'btn-disabled': isRegistering || isShift1Full(selectedClinic) }"
>
FAST TRACK
</v-btn>
</v-col>
@@ -321,9 +331,10 @@
size="large"
block
@click="selectVisitType('JADWAL_LAIN')"
:disabled="!selectedDoctor"
:class="{ 'btn-disabled': !selectedDoctor }"
:disabled="isRegistering || !selectedDoctor"
:class="{ 'btn-disabled': isRegistering || !selectedDoctor }"
>
JADWAL LAIN
</v-btn>
<p v-if="!selectedDoctor" class="disabled-hint">
@@ -472,7 +483,10 @@
class="text-white"
variant="flat"
@click="submitBooking"
:loading="isRegistering"
:disabled="isRegistering"
>
Simpan
</v-btn>
</v-card-actions>
@@ -543,7 +557,10 @@
class="text-white"
variant="flat"
@click="submitFastTrackForm"
:loading="isRegistering"
:disabled="isRegistering"
>
Lanjutkan
</v-btn>
</v-card-actions>
@@ -746,7 +763,10 @@ const fetchAllData = async () => {
if (anjunganData.value?.jenisPasien === "Reguler") {
await clinicStore.fetchRegulerClinics();
}
// Ensure loket configuration is loaded for API registration matching
await loketStore.fetchLoketFromAPI();
queueStore.ensureInitialData();
console.log('✅ Anjungan refresh: Success');
} catch (err) {
console.error('❌ Anjungan refresh error:', err);
@@ -906,6 +926,8 @@ const selectedDoctor = ref(null);
const snackbar = ref(false);
const snackbarText = ref("");
const snackbarColor = ref("success");
const isRegistering = ref(false);
// Fast Track form data
const fastTrackForm = ref({
@@ -1190,7 +1212,9 @@ const registerPatient = async (
isFastTrack = false,
fastTrackData = null,
) => {
isRegistering.value = true;
try {
// Validasi bahwa fungsi registerPatientFromAnjungan ada
if (!queueStore) {
console.error("❌ queueStore is not initialized");
@@ -1276,10 +1300,14 @@ const registerPatient = async (
} finally {
selectedClinic.value = null;
selectedDoctor.value = null;
isRegistering.value = false;
}
};
const submitBooking = async () => {
if (isRegistering.value) return;
if (
!bookingForm.value.date ||
!bookingForm.value.shift ||
+7 -6
View File
@@ -1,4 +1,4 @@
// stores/loketStore.js
// stores/loketStore.js
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
import { useClinicStore } from './clinicStore';
@@ -25,7 +25,7 @@ const getPelayananContohDariAnjungan = (anjunganItems) => {
// Jika tidak ada data dari anjungan, gunakan data default dari master anjungan
if (klinikAnjunganReguler.length === 0) {
klinikAnjunganReguler = ['AN', 'AS', 'BD', 'GR', 'HO', 'GI', 'GZ', 'IP', 'JT', 'JW', 'KK', 'MT', 'OB', 'PR', 'RT', 'RM', 'SR'];
klinikAnjunganReguler = ['AK', 'AN', 'AS', 'BD', 'GR', 'HO', 'GI', 'GZ', 'IP', 'JT', 'JW', 'KK', 'MT', 'OB', 'PR', 'RT', 'RM', 'SR'];
}
// Distribusi pelayanan ke beberapa loket sebagai contoh
@@ -296,7 +296,8 @@ export const useLoketStore = defineStore('loket', () => {
try {
console.log(`🔄 [loketStore] Fetching loket configuration (Try ${retryCount + 1})...`);
const response = await fetch('http://10.10.123.140:8089/api/v1/klinik/loket');
const response = await fetch('http://10.10.123.140:8089/api/v1/loket');
if (!response.ok) {
if (response.status === 429 && retryCount < 3) {
@@ -393,15 +394,15 @@ export const useLoketStore = defineStore('loket', () => {
}
// FALLBACK 2: If no cache, populate with dummy Reguler data for Dev/Offline mode
console.warn('⚠️ [loketStore] No cache available, using FALLBACK dummy data...');
console.warn('⚠️ [loketStore] No cache available, using FALLBACK dummy data (v2 - with AK and JKN)...');
const dummyLokets = Array.from({length: 6}, (_, i) => ({
id: i + 1,
namaLoket: `LOKET ${i + 1} REG (Mock)`,
kodeLoket: `L${i+1}`,
kuota: 100,
pelayanan: ['UM', 'BP', 'OB', 'AN', 'IP', 'SR', 'TH', 'MT', 'KK', 'PR'], // Mock all services
pelayanan: ['AK', 'AN', 'AS', 'BD', 'GR', 'HO', 'GI', 'GZ', 'IP', 'JT', 'JW', 'KK', 'MT', 'OB', 'PR', 'RT', 'RM', 'SR', 'TH', 'TD', 'KP', 'MC', 'ON', 'JT', 'GG', 'GE'], // Mock all services
_spesialisDetail: [], // Empty detail
pembayaran: ['BPJS', 'UMUM'], // Changed to array for consistency
pembayaran: ['JKN', 'UMUM'], // Using JKN to match backend terminology
tipeLoket: 'REGULER', // Add tipeLoket for fallback data
source: 'api', // Mimic API
loketAktif: true,
+16 -11
View File
@@ -1600,18 +1600,15 @@ const fetchPatientsForLoket = async (loketId, force = false) => {
return loketPayments.some(lp => {
const normalized = String(lp).toUpperCase().trim();
// Handle exact match
if (normalized === patientCategory) return true;
const result = (normalized === patientCategory) ||
(patientCategory === 'UMUM' && (normalized.includes('UMUM') || normalized.includes('MANDIRI'))) ||
(patientCategory === 'JKN' && (normalized.includes('JKN') || normalized.includes('BPJS'))) ||
(patientCategory === 'EKSEKUTIF' && (normalized.includes('EKSEKUTIF') || normalized.includes('VIP'))) ||
(normalized === 'BPJS' && patientCategory === 'JKN') ||
(normalized === 'JKN' && patientCategory === 'JKN');
// Handle partial matches for complex payment types like "UMUM / JKMM / SPM / DLL"
if (patientCategory === 'UMUM' && normalized.includes('UMUM')) return true;
if (patientCategory === 'JKN' && (normalized.includes('JKN') || normalized.includes('BPJS'))) return true;
if (patientCategory === 'EKSEKUTIF' && (normalized.includes('EKSEKUTIF') || normalized.includes('VIP'))) return true;
// Handle alias: BPJS = JKN (only this alias is allowed)
if (normalized === 'BPJS' && patientCategory === 'JKN') return true;
return false;
console.log(` - Checking loket accepts "${lp}" (normalized: "${normalized}") vs patient category "${patientCategory}": ${result}`);
return result;
});
};
@@ -3008,7 +3005,15 @@ const fetchPatientsForLoket = async (loketId, force = false) => {
console.log('📋 [queueStore] Payment Type:', paymentType, '| Clinic:', clinic.name, '| Clinic Code:', clinic.kode);
// 1. Find appropriate idloket based on BOTH clinic code AND payment type
// IMPROVED: Check specifically for API-source lokets (id < 1000)
const apiLoketsExist = loketStore.lokets?.some(l => l.source === 'api' || l.id < 1000);
if (!apiLoketsExist) {
console.log('🔄 [queueStore] API loket data empty, fetching from API...');
await loketStore.fetchLoketFromAPI();
}
const allLokets = loketStore.lokets || [];
// Determine payment type for matching (normalize BPJS to JKN for API compatibility)
const paymentTypeForMatching = paymentType === "BPJS" ? "JKN" : paymentType;