update post api status dan API data pasien ruang
This commit is contained in:
@@ -117,6 +117,9 @@ export default defineNuxtConfig({
|
||||
'/stats-api/**': {
|
||||
proxy: 'http://10.10.150.100:8084/api/v1/**'
|
||||
},
|
||||
'/visit-api/**': {
|
||||
proxy: 'http://10.10.150.100:8084/api/v1/**'
|
||||
},
|
||||
},
|
||||
|
||||
vite: {
|
||||
|
||||
@@ -890,6 +890,25 @@ const showFilterDialog = ref({});
|
||||
const filterOptions = ref({});
|
||||
const klinikRuangSearch = ref('');
|
||||
|
||||
// API Patient Data
|
||||
const apiPatients = ref([]);
|
||||
const isLoadingPatients = ref(false);
|
||||
const apiError = ref(null);
|
||||
|
||||
// Helper: Check if a patient is from today
|
||||
const isTodayPatient = (patient) => {
|
||||
if (!patient.createdAt && !patient.jamPanggil) return false;
|
||||
|
||||
const patientDate = new Date(patient.createdAt || patient.jamPanggil);
|
||||
const today = new Date();
|
||||
|
||||
return (
|
||||
patientDate.getDate() === today.getDate() &&
|
||||
patientDate.getMonth() === today.getMonth() &&
|
||||
patientDate.getFullYear() === today.getFullYear()
|
||||
);
|
||||
};
|
||||
|
||||
// Initialize filter options for each room
|
||||
const initializeFilterOptions = (ruang) => {
|
||||
if (!filterOptions.value[ruang.nomorRuang]) {
|
||||
@@ -933,17 +952,454 @@ const showSnackbar = (message, color = 'success') => {
|
||||
snackbar.value = true;
|
||||
};
|
||||
|
||||
// Get all patients for room (hanya pasien dari processStage 'klinik-ruang')
|
||||
const getAllPatientsForRoom = (ruang) => {
|
||||
return queueStore.allPatients
|
||||
.filter(p =>
|
||||
p.kodeKlinik === klinikData.value?.kodeKlinik &&
|
||||
p.nomorRuang === ruang.nomorRuang &&
|
||||
p.ruang === ruang.namaRuang &&
|
||||
p.processStage === 'klinik-ruang' &&
|
||||
// Include semua pasien dengan status yang relevan
|
||||
(p.status === 'anjungan' || p.status === 'di-loket' || p.status === 'terlambat' || p.status === 'pending')
|
||||
/**
|
||||
* Fetch patients from API for the clinic
|
||||
* Uses filters: klinik_id, klinik_ruang_id, active, limit
|
||||
*/
|
||||
const fetchPatientsFromAPI = async () => {
|
||||
if (!klinikData.value) {
|
||||
console.warn('⚠️ Klinik data not available');
|
||||
return;
|
||||
}
|
||||
|
||||
isLoadingPatients.value = true;
|
||||
apiError.value = null;
|
||||
|
||||
try {
|
||||
// Get klinik_id from clinicStore - find clinic by kodeKlinik
|
||||
const clinic = clinicStore.clinics.find(c =>
|
||||
c.kode === klinikData.value.kodeKlinik &&
|
||||
(!jenisLayanan.value || c.jenisLayanan === jenisLayanan.value)
|
||||
);
|
||||
|
||||
if (!clinic || !clinic.id) {
|
||||
console.warn('⚠️ Klinik ID not found for', klinikData.value.kodeKlinik);
|
||||
console.log('Available clinics:', clinicStore.clinics.map(c => ({ kode: c.kode, id: c.id, jenisLayanan: c.jenisLayanan })));
|
||||
isLoadingPatients.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const klinikId = clinic.id;
|
||||
|
||||
// Build API URL with filters - using Nuxt proxy to avoid CORS
|
||||
const baseUrl = '/visit-api/visit';
|
||||
const params = new URLSearchParams({
|
||||
active: '1',
|
||||
klinik_id: klinikId.toString(),
|
||||
limit: '500'
|
||||
});
|
||||
|
||||
// If there are rooms, fetch data for each room
|
||||
// For now, we'll fetch all patients for the clinic and filter by room on the client side
|
||||
const url = `${baseUrl}?${params.toString()}`;
|
||||
|
||||
console.log('🔄 Fetching patients from:', url);
|
||||
console.log('📋 Using clinic ID:', klinikId, 'for clinic code:', klinikData.value.kodeKlinik);
|
||||
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const rawResponse = await response.json();
|
||||
console.log('📦 Raw API Response:', rawResponse);
|
||||
|
||||
// Extract data array from response wrapper
|
||||
const data = rawResponse?.data || [];
|
||||
const message = rawResponse?.message || '';
|
||||
|
||||
console.log('📋 API Message:', message);
|
||||
console.log('📊 Data array length:', data.length);
|
||||
|
||||
// Process the response data
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
// Map API data to our internal format
|
||||
apiPatients.value = [];
|
||||
|
||||
data.forEach((visit, index) => {
|
||||
// Each visit can have multiple healthcare_services (sub-services/rooms)
|
||||
const healthcareServices = visit.healthcare_services || [];
|
||||
|
||||
if (healthcareServices.length > 0) {
|
||||
healthcareServices.forEach(service => {
|
||||
// Convert room ID to string for nomorRuang matching
|
||||
const roomId = service.fk_ms_sub_healthcare_service_id;
|
||||
const roomIdString = roomId ? String(roomId) : null;
|
||||
|
||||
// Extract status from visit_statuses array (use latest status)
|
||||
const visitStatuses = visit.visit_statuses || [];
|
||||
const latestStatus = visitStatuses.length > 0
|
||||
? visitStatuses[visitStatuses.length - 1]
|
||||
: null;
|
||||
|
||||
// Map status from API or default to 'di-loket' (patients just arrived from loket)
|
||||
// Status 'pemeriksaan' will be set explicitly when patient is transferred from loket
|
||||
let patientStatus = 'di-loket'; // Default for klinik-ruang patients
|
||||
if (latestStatus && latestStatus.desc) {
|
||||
const desc = latestStatus.desc.toLowerCase();
|
||||
if (desc.includes('pemeriksaan') || desc.includes('sedang diproses')) {
|
||||
patientStatus = 'pemeriksaan';
|
||||
} else if (desc.includes('check-in') || desc.includes('loket')) {
|
||||
patientStatus = 'di-loket';
|
||||
}
|
||||
}
|
||||
|
||||
const mappedPatient = {
|
||||
no: visit.id || (10000 + index),
|
||||
barcode: visit.visit_code || service.ticket || '', // Prioritize visit_code
|
||||
noAntrian: service.ticket || visit.visit_code || '',
|
||||
jamPanggil: service.check_in_datetime || visit.registration_datetime || '',
|
||||
klinik: service.healthcare_service_name || klinikData.value.namaKlinik,
|
||||
kodeKlinik: klinikData.value.kodeKlinik,
|
||||
klinikId: service.fk_ms_healthcare_service_id || klinikId,
|
||||
ruang: service.sub_healthcare_service_name || '',
|
||||
nomorRuang: roomIdString, // Use string version of room ID
|
||||
pembayaran: service.payment_type_name || visit.payment_type_name || '', // Payment type from service level
|
||||
status: patientStatus, // Status from API or default 'di-loket'
|
||||
processStage: 'klinik-ruang',
|
||||
createdAt: visit.registration_datetime || new Date().toISOString(),
|
||||
visitType: visit.visit_type_name || 'ONSITE',
|
||||
noRM: visit.norm || '',
|
||||
fastTrack: 'TIDAK',
|
||||
registrationType: 'api',
|
||||
visitId: visit.id,
|
||||
visitCode: visit.visit_code,
|
||||
// Store original API data for reference
|
||||
_apiData: { visit, service }
|
||||
};
|
||||
|
||||
// Only add if patient is from today
|
||||
if (isTodayPatient(mappedPatient)) {
|
||||
apiPatients.value.push(mappedPatient);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// If no healthcare_services, create one entry for the visit
|
||||
// Extract status from visit_statuses
|
||||
const visitStatuses = visit.visit_statuses || [];
|
||||
const latestStatus = visitStatuses.length > 0
|
||||
? visitStatuses[visitStatuses.length - 1]
|
||||
: null;
|
||||
|
||||
let patientStatus = 'di-loket';
|
||||
if (latestStatus && latestStatus.desc) {
|
||||
const desc = latestStatus.desc.toLowerCase();
|
||||
if (desc.includes('pemeriksaan') || desc.includes('sedang diproses')) {
|
||||
patientStatus = 'pemeriksaan';
|
||||
} else if (desc.includes('check-in') || desc.includes('loket')) {
|
||||
patientStatus = 'di-loket';
|
||||
}
|
||||
}
|
||||
|
||||
const mappedPatient = {
|
||||
no: visit.id || (10000 + index),
|
||||
barcode: visit.visit_code || '', // Use visit_code for barcode
|
||||
noAntrian: visit.visit_code || '',
|
||||
jamPanggil: visit.registration_datetime || '',
|
||||
klinik: klinikData.value.namaKlinik,
|
||||
kodeKlinik: klinikData.value.kodeKlinik,
|
||||
klinikId: klinikId,
|
||||
ruang: '',
|
||||
nomorRuang: null,
|
||||
pembayaran: visit.payment_type_name || '', // Payment type from visit level
|
||||
status: patientStatus, // Status from API or default 'di-loket'
|
||||
processStage: 'klinik-ruang',
|
||||
createdAt: visit.registration_datetime || new Date().toISOString(),
|
||||
visitType: visit.visit_type_name || 'ONSITE',
|
||||
noRM: visit.norm || '',
|
||||
fastTrack: 'TIDAK',
|
||||
registrationType: 'api',
|
||||
visitId: visit.id,
|
||||
visitCode: visit.visit_code,
|
||||
_apiData: { visit }
|
||||
};
|
||||
|
||||
// Only add if patient is from today
|
||||
if (isTodayPatient(mappedPatient)) {
|
||||
apiPatients.value.push(mappedPatient);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const totalVisits = data.length;
|
||||
const totalPatientEntries = apiPatients.value.length;
|
||||
|
||||
console.log(`✅ Loaded ${totalPatientEntries} patient entries from ${totalVisits} visits (today only)`);
|
||||
console.log('📋 Sample patient data:', apiPatients.value.slice(0, 2).map(p => ({
|
||||
ticket: p.noAntrian,
|
||||
ruang: p.ruang,
|
||||
nomorRuang: p.nomorRuang,
|
||||
pembayaran: p.pembayaran,
|
||||
kodeKlinik: p.kodeKlinik,
|
||||
createdAt: p.createdAt
|
||||
})));
|
||||
|
||||
// Merge with queueStore.allPatients to maintain consistency
|
||||
mergeApiPatientsToStore();
|
||||
} else {
|
||||
console.warn('⚠️ Unexpected API response format:', data);
|
||||
apiPatients.value = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Error fetching patients:', error);
|
||||
apiError.value = error.message;
|
||||
showSnackbar(`Gagal memuat data pasien: ${error.message}`, 'error');
|
||||
} finally {
|
||||
isLoadingPatients.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch patients for a specific room
|
||||
*/
|
||||
const fetchPatientsForRoom = async (ruang) => {
|
||||
if (!klinikData.value || !ruang) {
|
||||
console.warn('⚠️ Klinik data or room not available');
|
||||
return;
|
||||
}
|
||||
|
||||
isLoadingPatients.value = true;
|
||||
apiError.value = null;
|
||||
|
||||
try {
|
||||
// Get klinik_id from masterStore ruangData
|
||||
const ruangData = masterStore.ruangData || [];
|
||||
const currentKlinik = ruangData.find(r =>
|
||||
r.kodeKlinik === klinikData.value.kodeKlinik &&
|
||||
(!jenisLayanan.value || r.jenisLayanan === jenisLayanan.value)
|
||||
);
|
||||
|
||||
if (!currentKlinik || !currentKlinik.idKlinik) {
|
||||
console.warn('⚠️ Klinik ID not found');
|
||||
isLoadingPatients.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const klinikId = currentKlinik.idKlinik;
|
||||
|
||||
// Build API URL with filters including room ID - using Nuxt proxy to avoid CORS
|
||||
const baseUrl = '/visit-api/visit';
|
||||
const params = new URLSearchParams({
|
||||
active: '1',
|
||||
klinik_id: klinikId.toString(),
|
||||
limit: '500'
|
||||
});
|
||||
|
||||
// Add room filter if room has an ID
|
||||
if (ruang.idRuang) {
|
||||
params.append('klinik_ruang_id', ruang.idRuang.toString());
|
||||
}
|
||||
|
||||
const url = `${baseUrl}?${params.toString()}`;
|
||||
|
||||
console.log('🔄 Fetching patients for room from:', url);
|
||||
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const rawResponse = await response.json();
|
||||
|
||||
// Extract data array from response wrapper
|
||||
const data = rawResponse?.data || [];
|
||||
|
||||
// Process and merge the response data
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
const roomPatients = [];
|
||||
|
||||
data.forEach((visit, index) => {
|
||||
const healthcareServices = visit.healthcare_services || [];
|
||||
|
||||
if (healthcareServices.length > 0) {
|
||||
healthcareServices.forEach(service => {
|
||||
// Convert room ID to string for nomorRuang matching
|
||||
const roomId = service.fk_ms_sub_healthcare_service_id;
|
||||
const roomIdString = roomId ? String(roomId) : null;
|
||||
|
||||
// Extract status from visit_statuses
|
||||
const visitStatuses = visit.visit_statuses || [];
|
||||
const latestStatus = visitStatuses.length > 0
|
||||
? visitStatuses[visitStatuses.length - 1]
|
||||
: null;
|
||||
|
||||
let patientStatus = 'di-loket';
|
||||
if (latestStatus && latestStatus.desc) {
|
||||
const desc = latestStatus.desc.toLowerCase();
|
||||
if (desc.includes('pemeriksaan') || desc.includes('sedang diproses')) {
|
||||
patientStatus = 'pemeriksaan';
|
||||
} else if (desc.includes('check-in') || desc.includes('loket')) {
|
||||
patientStatus = 'di-loket';
|
||||
}
|
||||
}
|
||||
|
||||
const mappedPatient = {
|
||||
no: visit.id || (10000 + index),
|
||||
barcode: visit.visit_code || service.ticket || '', // Prioritize visit_code
|
||||
noAntrian: service.ticket || visit.visit_code || '',
|
||||
jamPanggil: service.check_in_datetime || visit.registration_datetime || '',
|
||||
klinik: service.healthcare_service_name || klinikData.value.namaKlinik,
|
||||
kodeKlinik: klinikData.value.kodeKlinik,
|
||||
klinikId: service.fk_ms_healthcare_service_id || klinikId,
|
||||
ruang: ruang.namaRuang,
|
||||
nomorRuang: roomIdString, // Use string version of room ID
|
||||
nomorScreen: ruang.nomorScreen,
|
||||
pembayaran: service.payment_type_name || visit.payment_type_name || '', // Payment type from service level
|
||||
status: patientStatus, // Status from API or default 'di-loket'
|
||||
processStage: 'klinik-ruang',
|
||||
createdAt: visit.registration_datetime || new Date().toISOString(),
|
||||
visitType: visit.visit_type_name || 'ONSITE',
|
||||
noRM: visit.norm || '',
|
||||
fastTrack: 'TIDAK',
|
||||
registrationType: 'api',
|
||||
visitId: visit.id,
|
||||
visitCode: visit.visit_code,
|
||||
_apiData: { visit, service }
|
||||
};
|
||||
|
||||
// Only add if patient is from today
|
||||
if (isTodayPatient(mappedPatient)) {
|
||||
roomPatients.push(mappedPatient);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Extract status from visit_statuses
|
||||
const visitStatuses = visit.visit_statuses || [];
|
||||
const latestStatus = visitStatuses.length > 0
|
||||
? visitStatuses[visitStatuses.length - 1]
|
||||
: null;
|
||||
|
||||
let patientStatus = 'di-loket';
|
||||
if (latestStatus && latestStatus.desc) {
|
||||
const desc = latestStatus.desc.toLowerCase();
|
||||
if (desc.includes('pemeriksaan') || desc.includes('sedang diproses')) {
|
||||
patientStatus = 'pemeriksaan';
|
||||
} else if (desc.includes('check-in') || desc.includes('loket')) {
|
||||
patientStatus = 'di-loket';
|
||||
}
|
||||
}
|
||||
|
||||
const mappedPatient = {
|
||||
no: visit.id || (10000 + index),
|
||||
barcode: visit.visit_code || '', // Use visit_code for barcode
|
||||
noAntrian: visit.visit_code || '',
|
||||
jamPanggil: visit.registration_datetime || '',
|
||||
klinik: klinikData.value.namaKlinik,
|
||||
kodeKlinik: klinikData.value.kodeKlinik,
|
||||
klinikId: klinikId,
|
||||
ruang: ruang.namaRuang,
|
||||
nomorRuang: ruang.nomorRuang,
|
||||
nomorScreen: ruang.nomorScreen,
|
||||
pembayaran: visit.payment_type_name || '', // Payment type from visit level
|
||||
status: patientStatus, // Status from API or default 'di-loket'
|
||||
processStage: 'klinik-ruang',
|
||||
createdAt: visit.registration_datetime || new Date().toISOString(),
|
||||
visitType: visit.visit_type_name || 'ONSITE',
|
||||
noRM: visit.norm || '',
|
||||
fastTrack: 'TIDAK',
|
||||
registrationType: 'api',
|
||||
visitId: visit.id,
|
||||
visitCode: visit.visit_code,
|
||||
_apiData: { visit }
|
||||
};
|
||||
|
||||
// Only add if patient is from today
|
||||
if (isTodayPatient(mappedPatient)) {
|
||||
roomPatients.push(mappedPatient);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Update apiPatients for this specific room
|
||||
// Remove old patients for this room and add new ones
|
||||
apiPatients.value = [
|
||||
...apiPatients.value.filter(p => p.nomorRuang !== ruang.nomorRuang),
|
||||
...roomPatients
|
||||
];
|
||||
|
||||
console.log(`✅ Loaded ${roomPatients.length} patients for room ${ruang.namaRuang}`);
|
||||
|
||||
mergeApiPatientsToStore();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Error fetching patients for room:', error);
|
||||
apiError.value = error.message;
|
||||
} finally {
|
||||
isLoadingPatients.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Merge API patients into queueStore.allPatients
|
||||
*/
|
||||
const mergeApiPatientsToStore = () => {
|
||||
if (apiPatients.value.length === 0) return;
|
||||
|
||||
// Build a Set of unique identifiers from API patients for fast lookup
|
||||
const apiPatientIds = new Set(
|
||||
apiPatients.value.map(p => p.visitId || p.visitCode || p.barcode || p.noAntrian).filter(Boolean)
|
||||
);
|
||||
|
||||
// Remove duplicates: remove any patient (API or not) that matches visitId, visitCode, barcode, or noAntrian
|
||||
queueStore.allPatients = queueStore.allPatients.filter(p => {
|
||||
const patientId = p.visitId || p.visitCode || p.barcode || p.noAntrian;
|
||||
const isDuplicate = apiPatientIds.has(patientId);
|
||||
|
||||
if (isDuplicate) {
|
||||
console.log('🗑️ Removing duplicate patient:', {
|
||||
ticket: p.noAntrian,
|
||||
barcode: p.barcode,
|
||||
visitId: p.visitId,
|
||||
registrationType: p.registrationType
|
||||
});
|
||||
}
|
||||
|
||||
return !isDuplicate;
|
||||
});
|
||||
|
||||
// Add new API patients
|
||||
queueStore.allPatients.push(...apiPatients.value);
|
||||
|
||||
console.log(`📊 Total patients in store: ${queueStore.allPatients.length}`);
|
||||
};
|
||||
|
||||
// Get all patients for room (menggunakan data dari API)
|
||||
const getAllPatientsForRoom = (ruang) => {
|
||||
// Debug logging
|
||||
console.log('🔍 Filtering patients for room:', {
|
||||
ruangName: ruang.namaRuang,
|
||||
ruangNomor: ruang.nomorRuang,
|
||||
klinikCode: klinikData.value?.kodeKlinik
|
||||
});
|
||||
|
||||
// Prioritize API patients, fallback to queueStore patients
|
||||
const patients = queueStore.allPatients
|
||||
.filter(p => {
|
||||
const matches =
|
||||
p.kodeKlinik === klinikData.value?.kodeKlinik &&
|
||||
p.nomorRuang === ruang.nomorRuang &&
|
||||
p.processStage === 'klinik-ruang' &&
|
||||
// Include semua pasien dengan status yang relevan (including pemeriksaan for API patients)
|
||||
(p.status === 'anjungan' || p.status === 'pemeriksaan' || p.status === 'di-loket' || p.status === 'terlambat' || p.status === 'pending');
|
||||
|
||||
if (!matches && p.kodeKlinik === klinikData.value?.kodeKlinik) {
|
||||
console.log('❌ Patient did not match room:', {
|
||||
ticket: p.noAntrian,
|
||||
patientRoom: p.nomorRuang,
|
||||
expectedRoom: ruang.nomorRuang,
|
||||
stage: p.processStage,
|
||||
status: p.status
|
||||
});
|
||||
}
|
||||
|
||||
return matches;
|
||||
});
|
||||
|
||||
console.log(` ✅ Found ${patients.length} patients for room ${ruang.namaRuang}`);
|
||||
|
||||
return patients;
|
||||
};
|
||||
|
||||
// Get filtered and sorted patients for room
|
||||
@@ -1794,6 +2250,12 @@ onMounted(async () => {
|
||||
await clinicStore.fetchRegulerClinics();
|
||||
// Then sync rooms
|
||||
await ruangStore.fetchRuangFromAPI();
|
||||
|
||||
// 2. Fetch patient data from API
|
||||
if (klinikData.value) {
|
||||
console.log('📋 Fetching patient data for clinic:', klinikData.value.kodeKlinik);
|
||||
await fetchPatientsFromAPI();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Error syncing data in AdminKlinikRuang:', error);
|
||||
}
|
||||
|
||||
+134
-2
@@ -617,7 +617,17 @@ const anjunganCount = computed(() => {
|
||||
|
||||
const nextQueueInfo = computed(() => {
|
||||
const currentPatientNo = currentProcessingPatient.value?.no;
|
||||
const nextPatient = (diLoketPatients.value || []).find(p => p.no !== currentPatientNo) || (diLoketPatients.value || [])[0];
|
||||
const targetLoketId = parseInt(loketId.value);
|
||||
|
||||
// Filter diLoketPatients to only show patients for THIS loket
|
||||
const loketFilteredPatients = (diLoketPatients.value || []).filter(p => {
|
||||
// Only show patients assigned to this specific loket
|
||||
return p.loketId && String(p.loketId) === String(targetLoketId);
|
||||
});
|
||||
|
||||
// Find next patient (excluding current processing patient)
|
||||
const nextPatient = loketFilteredPatients.find(p => p.no !== currentPatientNo) || loketFilteredPatients[0];
|
||||
|
||||
if (nextPatient) {
|
||||
return `Antrian berikutnya: ${nextPatient.noAntrian.split(" |")[0]}`;
|
||||
}
|
||||
@@ -750,11 +760,132 @@ const buatAntreanKlinikRuang = async (klinikRuang, ruang) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// POST to external visit ticket API FIRST to get ticket number
|
||||
let apiTicketNumber = null;
|
||||
let apiCallSuccess = false;
|
||||
|
||||
try {
|
||||
// Map payment type: BPJS/JKN -> 2, UMUM/others -> 1
|
||||
const paymentTypeId = (patient.pembayaran || '').toUpperCase().includes('BPJS') ||
|
||||
(patient.pembayaran || '').toUpperCase().includes('JKN')
|
||||
? 2 : 1;
|
||||
|
||||
// Map service type: Reguler -> 1, Eksekutif/Grand Pav -> 2
|
||||
const serviceTypeId = (patient.pembayaran || '').toUpperCase().includes('EKSEKUTIF') ||
|
||||
(patient.pembayaran || '').toUpperCase().includes('VIP') ||
|
||||
(patient.pembayaran || '').toUpperCase().includes('GRAND')
|
||||
? 2 : 1;
|
||||
|
||||
// Get idruangan from ruang object, ensure it's a number or null
|
||||
let subHealthcareServiceId = null;
|
||||
if (ruang.kodeRuang) {
|
||||
const parsed = Number(ruang.kodeRuang);
|
||||
subHealthcareServiceId = isNaN(parsed) ? null : parsed;
|
||||
} else if (ruang.idruangan) {
|
||||
const parsed = Number(ruang.idruangan);
|
||||
subHealthcareServiceId = isNaN(parsed) ? null : parsed;
|
||||
}
|
||||
|
||||
// Ensure healthcare_service_id is a number - MUST use actual clinic ID from clinicStore
|
||||
const actualClinic = clinicStore.clinics.find(c => c.kode === klinikRuang.kodeKlinik);
|
||||
const healthcareServiceId = actualClinic ? Number(actualClinic.id) : null;
|
||||
|
||||
if (!healthcareServiceId) {
|
||||
console.error('❌ Could not find clinic ID for kode:', klinikRuang.kodeKlinik);
|
||||
snackbarText.value = "Gagal membuat antrean ruang: ID klinik tidak ditemukan";
|
||||
snackbarColor.value = "error";
|
||||
snackbar.value = true;
|
||||
closeKlinikRuangDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
const visitTicketBody = {
|
||||
visit_code: Number(patient.barcode),
|
||||
healthcare_service_id: healthcareServiceId,
|
||||
sub_healthcare_service_id: subHealthcareServiceId,
|
||||
payment_type_id: paymentTypeId,
|
||||
visit_status_id: [14, 15],
|
||||
visit_type_id: 1,
|
||||
service_type_id: serviceTypeId,
|
||||
healthcare_type_id: 2
|
||||
};
|
||||
|
||||
console.log('📤 Sending visit ticket to API:', visitTicketBody);
|
||||
|
||||
const visitResponse = await fetch('http://10.10.150.100:8084/api/v1/visit/ticket/klinik', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(visitTicketBody)
|
||||
});
|
||||
|
||||
if (visitResponse.ok) {
|
||||
const visitResult = await visitResponse.json();
|
||||
console.log('✅ Visit ticket created successfully:', visitResult);
|
||||
|
||||
// Extract ticket number from API response
|
||||
if (visitResult.healthcare_service && visitResult.healthcare_service.ticket) {
|
||||
apiTicketNumber = visitResult.healthcare_service.ticket;
|
||||
apiCallSuccess = true;
|
||||
console.log('🎫 Using API ticket number:', apiTicketNumber);
|
||||
} else {
|
||||
console.error('❌ API response missing ticket number');
|
||||
snackbarText.value = "Gagal membuat antrean ruang: Nomor tiket tidak ditemukan di response API";
|
||||
snackbarColor.value = "error";
|
||||
snackbar.value = true;
|
||||
closeKlinikRuangDialog();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Parse error response
|
||||
let errorMessage = "Gagal membuat antrean ruang";
|
||||
try {
|
||||
const errorData = await visitResponse.json();
|
||||
console.error('⚠️ Visit ticket API error:', errorData);
|
||||
|
||||
// Check if it's a duplicate ticket error
|
||||
if (errorData.message && errorData.message.toLowerCase().includes('already exists')) {
|
||||
errorMessage = `Tiket klinik ruang sudah ada untuk pasien ini. ${errorData.message}`;
|
||||
} else if (errorData.message) {
|
||||
errorMessage = `Gagal membuat antrean ruang: ${errorData.message}`;
|
||||
} else if (errorData.error) {
|
||||
errorMessage = `Gagal membuat antrean ruang: ${errorData.error}`;
|
||||
}
|
||||
} catch (parseError) {
|
||||
const errorText = await visitResponse.text();
|
||||
console.error('⚠️ Visit ticket API returned error:', visitResponse.status, errorText);
|
||||
errorMessage = `Gagal membuat antrean ruang (Status: ${visitResponse.status})`;
|
||||
}
|
||||
|
||||
snackbarText.value = errorMessage;
|
||||
snackbarColor.value = "error";
|
||||
snackbar.value = true;
|
||||
closeKlinikRuangDialog();
|
||||
return;
|
||||
}
|
||||
} catch (apiError) {
|
||||
console.error('❌ Error sending visit ticket to API:', apiError);
|
||||
snackbarText.value = "Gagal membuat antrean ruang: Kesalahan koneksi ke API";
|
||||
snackbarColor.value = "error";
|
||||
snackbar.value = true;
|
||||
closeKlinikRuangDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
// Only create room queue if API call was successful
|
||||
if (!apiCallSuccess || !apiTicketNumber) {
|
||||
console.error('❌ Cannot create room queue without API ticket number');
|
||||
return;
|
||||
}
|
||||
|
||||
// Create room queue with API ticket number
|
||||
const result = queueStore.createAntreanKlinikRuang(
|
||||
klinikRuang,
|
||||
ruang,
|
||||
patient,
|
||||
"loket"
|
||||
"loket",
|
||||
apiTicketNumber // Pass API ticket number
|
||||
);
|
||||
|
||||
if (result.success && result.patient) {
|
||||
@@ -769,6 +900,7 @@ const buatAntreanKlinikRuang = async (klinikRuang, ruang) => {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
snackbarText.value = result.message;
|
||||
snackbarColor.value = result.success ? "success" : "error";
|
||||
|
||||
+63
-22
@@ -169,7 +169,7 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
processStage: 'loket',
|
||||
createdAt: apiPatient.tanggal || new Date().toISOString(),
|
||||
registrationType: 'api',
|
||||
visitType: 'SEKARANG',
|
||||
visitType: 'Onsite',
|
||||
visitDate: apiPatient.tanggal ? apiPatient.tanggal.split('T')[0] : new Date().toISOString().substring(0, 10),
|
||||
namaDokter: null,
|
||||
noRM: null,
|
||||
@@ -325,13 +325,21 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
});
|
||||
|
||||
// 4. Remove existing patients that collide with new ones (to be replaced)
|
||||
// AND remove stale 'api' patients for this loket
|
||||
// This ensures API data ALWAYS takes precedence over local/seed data
|
||||
// Remove ANY patient (local, seed, or api) with matching barcode/idtiket
|
||||
allPatients.value = allPatients.value.filter(p => {
|
||||
const key = p.idtiket ? `id-${p.idtiket}` : `bc-${p.barcode}`;
|
||||
|
||||
// Remove if it's being replaced by new batch
|
||||
// Remove if it's being replaced by new API batch (matches by barcode or idtiket)
|
||||
if (newPatientMap.has(key)) return false;
|
||||
|
||||
// Also check if barcode matches any new API patient (for cases where local has no idtiket)
|
||||
// This prevents duplicates like barcode "2602050017" appearing as both BPJS (local) and JKN (API)
|
||||
if (p.barcode) {
|
||||
const barcodeMatches = patientsWithLoketId.some(newP => newP.barcode === p.barcode);
|
||||
if (barcodeMatches) return false;
|
||||
}
|
||||
|
||||
// Remove if it's a stale 'api' patient for this loket (that wasn't in the new batch)
|
||||
if (p.registrationType === 'api' && String(p.loketId) === String(loketId)) return false;
|
||||
|
||||
@@ -1164,6 +1172,32 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
};
|
||||
syncApiPatientStatus(allPatients.value[patientIndex], "di-loket");
|
||||
message = `Pasien ${patientCode} berhasil check in dan masuk ke Tabel Loket Klinik`;
|
||||
|
||||
// POST to external API when patient finishes at loket
|
||||
try {
|
||||
fetch('http://10.10.150.131:8089/api/v1/tiket/selesai', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
idloket: String(patient.loketId || specificId || ""),
|
||||
barcode: patient.barcode || "",
|
||||
statuspasien: "9",
|
||||
idklinikstatus: "2"
|
||||
})
|
||||
}).then(response => {
|
||||
if (response.ok) {
|
||||
console.log(`✅ [queueStore] Successfully posted selesai status for patient ${patient.barcode}`);
|
||||
} else {
|
||||
console.error(`⚠️ [queueStore] Failed to post selesai status for patient ${patient.barcode}:`, response.status);
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error(`❌ [queueStore] Error posting selesai status for patient ${patient.barcode}:`, error);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`❌ [queueStore] Error initiating selesai status update for patient ${patient.barcode}:`, error);
|
||||
}
|
||||
}
|
||||
// Jika check-in di klinik, selesai
|
||||
else if (adminType === 'klinik') {
|
||||
@@ -1402,7 +1436,7 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
};
|
||||
};
|
||||
|
||||
const createAntreanKlinikRuang = (klinikRuang, ruang, patient = null, adminType = 'klinik') => {
|
||||
const createAntreanKlinikRuang = (klinikRuang, ruang, patient = null, adminType = 'klinik', apiTicketNumber = null) => {
|
||||
const newNo = allPatients.value.length + 1;
|
||||
const timestamp = new Date();
|
||||
const barcode = patient ? patient.barcode : generateBarcode([], allPatients);
|
||||
@@ -1410,24 +1444,32 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
// Generate nomor antrian baru dengan format: [huruf pertama poli + urutan abjad ruang + nomor antrian ruang]
|
||||
// Contoh: "Anak" ruang 1 = "AA001" (A dari Anak, A dari ruang 1, 001 nomor antrian)
|
||||
|
||||
// 1. Ambil huruf pertama dari nama klinik/poli
|
||||
const firstLetter = klinikRuang.namaKlinik.charAt(0).toUpperCase();
|
||||
let newNoAntrian;
|
||||
|
||||
// 2. Konversi nomor ruang ke abjad (1 = A, 2 = B, 3 = C, dst)
|
||||
const ruangNumber = parseInt(ruang.nomorRuang) || 1;
|
||||
const ruangLetter = String.fromCharCode(64 + ruangNumber); // 64 = '@', 65 = 'A', 66 = 'B', dst
|
||||
|
||||
// 3. Hitung nomor antrian ruang (dimulai dari 1, maksimal 3 digit)
|
||||
const roomQueues = allPatients.value.filter(p =>
|
||||
p.kodeKlinik === klinikRuang.kodeKlinik &&
|
||||
p.nomorRuang === ruang.nomorRuang &&
|
||||
p.processStage === 'klinik-ruang'
|
||||
);
|
||||
const queueNumber = roomQueues.length + 1;
|
||||
const queueNumberStr = String(queueNumber).padStart(3, "0");
|
||||
|
||||
// 4. Format nomor antrian: AA001, AB002, dst
|
||||
const newNoAntrian = `${firstLetter}${ruangLetter}${queueNumberStr}`;
|
||||
// Use API ticket number if available, otherwise generate locally
|
||||
if (apiTicketNumber) {
|
||||
newNoAntrian = apiTicketNumber; // Use ticket from API (e.g., "PK001")
|
||||
console.log('🎫 Using API ticket number:', newNoAntrian);
|
||||
} else {
|
||||
// 1. Ambil huruf pertama dari nama klinik/poli
|
||||
const firstLetter = klinikRuang.namaKlinik.charAt(0).toUpperCase();
|
||||
|
||||
// 2. Konversi nomor ruang ke abjad (1 = A, 2 = B, 3 = C, dst)
|
||||
const ruangNumber = parseInt(ruang.nomorRuang) || 1;
|
||||
const ruangLetter = String.fromCharCode(64 + ruangNumber); // 64 = '@', 65 = 'A', 66 = 'B', dst
|
||||
|
||||
// 3. Hitung nomor antrian ruang (dimulai dari 1, maksimal 3 digit)
|
||||
const roomQueues = allPatients.value.filter(p =>
|
||||
p.kodeKlinik === klinikRuang.kodeKlinik &&
|
||||
p.nomorRuang === ruang.nomorRuang &&
|
||||
p.processStage === 'klinik-ruang'
|
||||
);
|
||||
const queueNumber = roomQueues.length + 1;
|
||||
const queueNumberStr = String(queueNumber).padStart(3, "0");
|
||||
|
||||
// 4. Format nomor antrian: AA001, AB002, dst
|
||||
newNoAntrian = `${firstLetter}${ruangLetter}${queueNumberStr}`;
|
||||
}
|
||||
|
||||
const newPatient = {
|
||||
no: newNo,
|
||||
@@ -1475,7 +1517,6 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
|
||||
// Pindah pasien ke klinik ruang lain dengan nomor antrian tetap
|
||||
const pindahKlinikRuang = (patient, targetKlinikRuang, targetRuang) => {
|
||||
const patientIndex = allPatients.value.findIndex(p => p.no === patient.no);
|
||||
|
||||
if (patientIndex === -1) {
|
||||
return { success: false, message: "Pasien tidak ditemukan" };
|
||||
|
||||
Reference in New Issue
Block a user