diff --git a/composables/useThermalPrint.ts b/composables/useThermalPrint.ts index 2181fbc..18981bf 100644 --- a/composables/useThermalPrint.ts +++ b/composables/useThermalPrint.ts @@ -18,82 +18,9 @@ export interface ThermalPrintData { export const useThermalPrint = () => { const isPrinting = ref(false); - /** - * Generate barcode dengan format: YYMMDD + 5 digit sequential - * Format: YY (tahun 2 digit terakhir) + MM (bulan 2 digit) + DD (tanggal 2 digit) + XXXXX (5 digit sequential) - * Contoh: 26011400001, 26011400002, dst - * Counter akan reset setiap ganti tanggal (mulai dari 00001 lagi) - * - * @param existingBarcodes - Array barcode yang sudah ada (optional, untuk validasi uniqueness) - * @returns Barcode string dengan format YYMMDDXXXXX - */ - const generateBarcode = (existingBarcodes: string[] = []): string => { - if (typeof window === 'undefined') { - // Fallback untuk SSR: gunakan counter berdasarkan existingBarcodes - const now = new Date(); - const year = String(now.getFullYear()).slice(-2); // 2 digit tahun terakhir - const month = String(now.getMonth() + 1).padStart(2, '0'); - const day = String(now.getDate()).padStart(2, '0'); - const datePrefix = `${year}${month}${day}`; // YYMMDD - - // Gunakan counter berdasarkan existingBarcodes untuk sequential - const existingCount = existingBarcodes.filter(b => b && b.startsWith(datePrefix)).length; - const counter = existingCount + 1; - const counterCode = String(counter).padStart(5, '0'); - return `${datePrefix}${counterCode}`; - } - - const now = new Date(); - const year = String(now.getFullYear()).slice(-2); // 2 digit tahun terakhir - const month = String(now.getMonth() + 1).padStart(2, '0'); // 2 digit bulan - const day = String(now.getDate()).padStart(2, '0'); // 2 digit tanggal - const datePrefix = `${year}${month}${day}`; // YYMMDD - - // Key untuk localStorage berdasarkan tanggal - const STORAGE_KEY = `barcode_counter_${datePrefix}`; - const LAST_DATE_KEY = 'barcode_last_date'; - - // Cek apakah tanggal sudah berubah (reset counter) - const lastDate = localStorage.getItem(LAST_DATE_KEY); - const currentDate = datePrefix; - - let counter = 1; // Default mulai dari 1 - - if (lastDate === currentDate) { - // Tanggal sama, lanjutkan counter dari localStorage - const storedCounter = localStorage.getItem(STORAGE_KEY); - if (storedCounter) { - counter = parseInt(storedCounter, 10) || 1; - } - } else { - // Tanggal berbeda, reset counter ke 1 - counter = 1; - // Hapus counter lama untuk tanggal sebelumnya (cleanup) - if (lastDate) { - localStorage.removeItem(`barcode_counter_${lastDate}`); - } - } - - // Generate barcode dengan counter saat ini - let barcode = `${datePrefix}${String(counter).padStart(5, '0')}`; - - // Cek apakah barcode sudah ada di existingBarcodes - let attempts = 0; - const maxAttempts = 1000; // Maksimal 1000 pasien per hari - - while (existingBarcodes.includes(barcode) && attempts < maxAttempts) { - counter++; - barcode = `${datePrefix}${String(counter).padStart(5, '0')}`; - attempts++; - } - - // Increment counter untuk next call dan simpan - counter++; - localStorage.setItem(STORAGE_KEY, String(counter)); - localStorage.setItem(LAST_DATE_KEY, currentDate); - - return barcode; - }; + // NOTE: generateBarcode telah dihapus dari useThermalPrint + // Gunakan generateBarcode dari queueStore.js untuk konsistensi + // Semua barcode generation harus melalui queueStore.generateBarcode() /** * Generate QR Code data URL @@ -699,7 +626,6 @@ export const useThermalPrint = () => { isPrinting, printTicket, printTicketFromPatient, - generateQRCode, - generateBarcode + generateQRCode }; }; diff --git a/pages/Anjungan/Anjungan/[id].vue b/pages/Anjungan/Anjungan/[id].vue index 1dcd1bd..f4a9153 100644 --- a/pages/Anjungan/Anjungan/[id].vue +++ b/pages/Anjungan/Anjungan/[id].vue @@ -1517,14 +1517,14 @@ watch(lastRegisteredPatient, (newVal) => { .clinic-available { border-color: var(--color-success-600); - background: var(--color-success-200); + background: var(--color-success-100); } .clinic-closed { opacity: 0.65; cursor: not-allowed; border-color: var(--color-danger-600); - background: var(--color-danger-200); + background: var(--color-danger-100); } .clinic-content { diff --git a/pages/CheckInPasien/checkIn.vue b/pages/CheckInPasien/checkIn.vue index 128739a..f3fddc5 100644 --- a/pages/CheckInPasien/checkIn.vue +++ b/pages/CheckInPasien/checkIn.vue @@ -2295,48 +2295,24 @@ const onDetect = async (decodedText: string) => { console.log('🔍 Cleaned input:', cleanInput); console.log('📊 Total patients in store:', queueStore.allPatients.length); - // Cari pasien di queueStore berdasarkan berbagai kriteria - // Prioritas: exact barcode match (case-insensitive, whitespace-insensitive) - // Kemudian: noAntrian match dengan kode + angka (tidak hanya angka saja) + // IMPORTANT: Hanya cari dengan EXACT barcode match untuk menghindari false positive + // Format barcode: YYMMDD + 5 digit (contoh: 26011500001) + // Jangan gunakan fallback ke noAntrian atau no karena bisa menyebabkan false positive + // Contoh masalah: RA020 dengan barcode 26011500001 terdeteksi sebagai RA002 dengan barcode 26011500198 const foundPatient = queueStore.allPatients.find(p => { if (!p) return false; // Normalize barcode untuk comparison (remove whitespace, case-insensitive) const patientBarcode = String(p.barcode || '').trim(); - const patientBarcodeUpper = patientBarcode.toUpperCase(); - const patientBarcodeNormalized = patientBarcodeUpper.replace(/\s+/g, ''); - const cleanInputNormalized = cleanInputUpper.replace(/\s+/g, ''); - // 1. Exact barcode match (case-insensitive, whitespace-insensitive) - PRIORITAS TERTINGGI + // EXACT barcode match (case-insensitive, whitespace-insensitive) + // Ini adalah satu-satunya cara yang aman untuk match pasien if (patientBarcode === cleanInput || - patientBarcodeNormalized === cleanInputNormalized || - patientBarcodeUpper === cleanInputUpper) { + patientBarcode.toLowerCase() === cleanInput.toLowerCase()) { console.log('✅ Found by exact barcode match:', patientBarcode, '===', cleanInput); return true; } - // 2. Match noAntrian dengan kode + angka (tidak hanya angka saja) - // Ini mencegah false positive ketika angka sama tapi kode beda (misalnya UM0014 vs AN0014) - if (p.noAntrian && matchNoAntrian(cleanInput, p.noAntrian)) { - console.log('✅ Found by noAntrian match (kode + angka):', p.noAntrian); - return true; - } - - // 3. Try parsing as number and match with no (fallback) - // IMPORTANT: Hanya gunakan fallback jika input TIDAK punya kode (hanya angka) - // Ini mencegah false positive: "RA004" (kode: RA) tidak boleh match dengan pasien no: 4 yang punya kode berbeda - const inputHasCode = /^[A-Z]+/.test(cleanInput); - if (!inputHasCode) { - // Input hanya angka, boleh match dengan no (tapi tetap prioritas ke barcode dan noAntrian) - const parsedNo = parseInt(cleanInput.replace(/[^0-9]/g, '')) || parseInt(searchBarcode); - if (!isNaN(parsedNo) && p.no === parsedNo) { - console.log('✅ Found by no (input tanpa kode):', p.no); - return true; - } - } - // Jika input punya kode (seperti "RA004"), jangan gunakan fallback ke no - // karena bisa match dengan pasien yang punya kode berbeda - return false; }); @@ -2378,13 +2354,13 @@ const onDetect = async (decodedText: string) => { // IMPORTANT: Refresh data pasien dari queueStore untuk mendapatkan status REAL-TIME // Jangan gunakan foundPatient.status karena mungkin sudah stale - // Cari ulang pasien dari queueStore untuk mendapatkan data terbaru + // Cari ulang pasien dari queueStore dengan EXACT barcode match const freshPatient = queueStore.allPatients.find(p => { const patientBarcode = String(p.barcode || '').trim(); + // Hanya exact barcode match untuk menghindari false positive return patientBarcode === foundPatient.barcode || patientBarcode === searchBarcode || - patientBarcode === decodedText || - p.no === foundPatient.no; + patientBarcode === cleanInput; }); if (!freshPatient) { @@ -2606,49 +2582,24 @@ const checkInManual = async () => { console.log('🔍 Cleaned input:', cleanInput); console.log('📊 Total patients in store:', queueStore.allPatients.length); - // Cari pasien di queueStore menggunakan logika yang sama dengan onDetect - // Prioritas: exact barcode match (case-insensitive, whitespace-insensitive) - // Kemudian: noAntrian match dengan kode + angka (tidak hanya angka saja) - // Fallback: hanya jika input tidak punya kode (hanya angka) + // IMPORTANT: Hanya cari dengan EXACT barcode match untuk menghindari false positive + // Format barcode: YYMMDD + 5 digit (contoh: 26011500001) + // Jangan gunakan fallback ke noAntrian atau no karena bisa menyebabkan false positive + // Contoh masalah: RA020 dengan barcode 26011500001 terdeteksi sebagai RA002 dengan barcode 26011500198 const foundPatient = queueStore.allPatients.find(p => { if (!p) return false; // Normalize barcode untuk comparison (remove whitespace, case-insensitive) const patientBarcode = String(p.barcode || '').trim(); - const patientBarcodeUpper = patientBarcode.toUpperCase(); - const patientBarcodeNormalized = patientBarcodeUpper.replace(/\s+/g, ''); - const cleanInputNormalized = cleanInputUpper.replace(/\s+/g, ''); - // 1. Exact barcode match (case-insensitive, whitespace-insensitive) - PRIORITAS TERTINGGI + // EXACT barcode match (case-insensitive, whitespace-insensitive) + // Ini adalah satu-satunya cara yang aman untuk match pasien if (patientBarcode === cleanInput || - patientBarcodeNormalized === cleanInputNormalized || - patientBarcodeUpper === cleanInputUpper) { + patientBarcode.toLowerCase() === cleanInput.toLowerCase()) { console.log('✅ Found by exact barcode match:', patientBarcode, '===', cleanInput); return true; } - // 2. Match noAntrian dengan kode + angka (tidak hanya angka saja) - // Ini mencegah false positive ketika angka sama tapi kode beda (misalnya UM0014 vs AN0014) - if (p.noAntrian && matchNoAntrian(cleanInput, p.noAntrian)) { - console.log('✅ Found by noAntrian match (kode + angka):', p.noAntrian); - return true; - } - - // 3. Try parsing as number and match with no (fallback) - // IMPORTANT: Hanya gunakan fallback jika input TIDAK punya kode (hanya angka) - // Ini mencegah false positive: "RA004" (kode: RA) tidak boleh match dengan pasien no: 4 yang punya kode berbeda - const inputHasCode = /^[A-Z]+/.test(cleanInput); - if (!inputHasCode) { - // Input hanya angka, boleh match dengan no (tapi tetap prioritas ke barcode dan noAntrian) - const parsedNo = parseInt(cleanInput.replace(/[^0-9]/g, '')) || parseInt(searchBarcode); - if (!isNaN(parsedNo) && p.no === parsedNo) { - console.log('✅ Found by no (input tanpa kode):', p.no); - return true; - } - } - // Jika input punya kode (seperti "RA004"), jangan gunakan fallback ke no - // karena bisa match dengan pasien yang punya kode berbeda - return false; }); @@ -2692,13 +2643,13 @@ const checkInManual = async () => { // IMPORTANT: Refresh data pasien dari queueStore untuk mendapatkan status REAL-TIME // Jangan gunakan foundPatient.status karena mungkin sudah stale - // Cari ulang pasien dari queueStore untuk mendapatkan data terbaru + // Cari ulang pasien dari queueStore dengan EXACT barcode match const freshPatient = queueStore.allPatients.find(p => { const patientBarcode = String(p.barcode || '').trim(); + // Hanya exact barcode match untuk menghindari false positive return patientBarcode === foundPatient.barcode || patientBarcode === searchBarcode || - patientBarcode === inputValue || - p.no === foundPatient.no; + patientBarcode === cleanInput; }); if (!freshPatient) { diff --git a/stores/queueStore.js b/stores/queueStore.js index df0f222..1b830b6 100644 --- a/stores/queueStore.js +++ b/stores/queueStore.js @@ -17,6 +17,26 @@ export const useQueueStore = defineStore('queue', () => { return loket1 ? loket1.namaLoket : 'Loket A'; }; + // Helper function untuk increment barcode counter setelah barcode digunakan + const incrementBarcodeCounter = () => { + if (typeof window === 'undefined' || typeof localStorage === 'undefined') return; + + const now = new Date(); + const year = String(now.getFullYear()).slice(-2); + const month = String(now.getMonth() + 1).padStart(2, '0'); + const day = String(now.getDate()).padStart(2, '0'); + const datePrefix = `${year}${month}${day}`; + const STORAGE_KEY = `barcode_counter_${datePrefix}`; + const LAST_DATE_KEY = 'barcode_last_date'; + + const storedCounter = localStorage.getItem(STORAGE_KEY); + if (storedCounter) { + const currentCounter = parseInt(storedCounter, 10) || 1; + localStorage.setItem(STORAGE_KEY, String(currentCounter + 1)); + localStorage.setItem(LAST_DATE_KEY, datePrefix); + } + }; + // Helper function untuk generate barcode dengan format: YYMMDD + 5 digit sequential // Format: YY (tahun 2 digit terakhir) + MM (bulan 2 digit) + DD (tanggal 2 digit) + XXXXX (5 digit sequential) // Contoh: 26011400001, 26011400002, dst @@ -78,9 +98,11 @@ export const useQueueStore = defineStore('queue', () => { attempts++; } - // Increment counter untuk next call dan simpan - counter++; - localStorage.setItem(STORAGE_KEY, String(counter)); + // IMPORTANT: JANGAN tulis counter ke localStorage di sini! + // Counter hanya di-increment dan di-save setelah barcode benar-benar digunakan + // Ini mencegah counter naik meskipun barcode tidak digunakan (misalnya saat refresh) + // Counter akan di-increment oleh incrementBarcodeCounter() setelah pasien dibuat + // Hanya update LAST_DATE_KEY untuk tracking tanggal localStorage.setItem(LAST_DATE_KEY, currentDate); return barcode; @@ -100,24 +122,73 @@ export const useQueueStore = defineStore('queue', () => { // IMPORTANT: Setiap pasien HARUS memiliki barcode UNIK untuk menghindari konflik // Format barcode menggunakan generateBarcode() untuk konsistensi dengan format baru // Generate barcode dengan memastikan uniqueness menggunakan existingBarcodes array - const seedBarcodes = []; - const seedBarcode1 = generateBarcode(seedBarcodes); seedBarcodes.push(seedBarcode1); - const seedBarcode2 = generateBarcode(seedBarcodes); seedBarcodes.push(seedBarcode2); - const seedBarcode3 = generateBarcode(seedBarcodes); seedBarcodes.push(seedBarcode3); - const seedBarcode4 = generateBarcode(seedBarcodes); seedBarcodes.push(seedBarcode4); - const seedBarcode5 = generateBarcode(seedBarcodes); seedBarcodes.push(seedBarcode5); - const seedBarcode6 = generateBarcode(seedBarcodes); seedBarcodes.push(seedBarcode6); - const seedBarcode7 = generateBarcode(seedBarcodes); seedBarcodes.push(seedBarcode7); - const seedBarcode8 = generateBarcode(seedBarcodes); seedBarcodes.push(seedBarcode8); - const seedBarcode9 = generateBarcode(seedBarcodes); seedBarcodes.push(seedBarcode9); - const seedBarcode10 = generateBarcode(seedBarcodes); seedBarcodes.push(seedBarcode10); - const seedBarcode11 = generateBarcode(seedBarcodes); seedBarcodes.push(seedBarcode11); - const seedBarcode12 = generateBarcode(seedBarcodes); seedBarcodes.push(seedBarcode12); + // NOTE: Seed data menggunakan barcode statis untuk menghindari increment counter saat refresh + // Counter hanya di-increment ketika pasien baru benar-benar dibuat dari Anjungan + + // Gunakan barcode statis untuk seed data (tidak memanggil generateBarcode) + // Format: YYMMDD + 5 digit sequential dimulai dari 00001 + // Ini mencegah counter naik setiap kali halaman di-refresh + const getSeedBarcode = (index) => { + if (typeof window !== 'undefined' && typeof localStorage !== 'undefined') { + const now = new Date(); + const year = String(now.getFullYear()).slice(-2); + const month = String(now.getMonth() + 1).padStart(2, '0'); + const day = String(now.getDate()).padStart(2, '0'); + const datePrefix = `${year}${month}${day}`; + + // Gunakan barcode statis berdasarkan index (1-16) + // Format: YYMMDD + 5 digit (contoh: 26011500001, 26011500002, dst) + return `${datePrefix}${String(index).padStart(5, '0')}`; + } + // Fallback untuk SSR + const now = new Date(); + const year = String(now.getFullYear()).slice(-2); + const month = String(now.getMonth() + 1).padStart(2, '0'); + const day = String(now.getDate()).padStart(2, '0'); + const datePrefix = `${year}${month}${day}`; + return `${datePrefix}${String(index).padStart(5, '0')}`; + }; + + const seedBarcode1 = getSeedBarcode(1); + const seedBarcode2 = getSeedBarcode(2); + const seedBarcode3 = getSeedBarcode(3); + const seedBarcode4 = getSeedBarcode(4); + const seedBarcode5 = getSeedBarcode(5); + const seedBarcode6 = getSeedBarcode(6); + const seedBarcode7 = getSeedBarcode(7); + const seedBarcode8 = getSeedBarcode(8); + const seedBarcode9 = getSeedBarcode(9); + const seedBarcode10 = getSeedBarcode(10); + const seedBarcode11 = getSeedBarcode(11); + const seedBarcode12 = getSeedBarcode(12); // Barcode untuk pasien Eksekutif - const seedBarcodeE1 = generateBarcode(seedBarcodes); seedBarcodes.push(seedBarcodeE1); - const seedBarcodeE2 = generateBarcode(seedBarcodes); seedBarcodes.push(seedBarcodeE2); - const seedBarcodeE3 = generateBarcode(seedBarcodes); seedBarcodes.push(seedBarcodeE3); - const seedBarcodeE4 = generateBarcode(seedBarcodes); seedBarcodes.push(seedBarcodeE4); + const seedBarcodeE1 = getSeedBarcode(13); + const seedBarcodeE2 = getSeedBarcode(14); + const seedBarcodeE3 = getSeedBarcode(15); + const seedBarcodeE4 = getSeedBarcode(16); + + // IMPORTANT: Set counter ke nilai yang sesuai dengan jumlah seed data + // Ini mencegah counter naik tidak terkendali saat seed data di-generate + // Counter akan di-set ke jumlah seed data (16) + 1 untuk next barcode + // Hanya set jika counter belum ada atau lebih kecil dari jumlah seed data + if (typeof window !== 'undefined' && typeof localStorage !== 'undefined') { + const now = new Date(); + const year = String(now.getFullYear()).slice(-2); + const month = String(now.getMonth() + 1).padStart(2, '0'); + const day = String(now.getDate()).padStart(2, '0'); + const datePrefix = `${year}${month}${day}`; + const STORAGE_KEY = `barcode_counter_${datePrefix}`; + const LAST_DATE_KEY = 'barcode_last_date'; + const storedCounter = localStorage.getItem(STORAGE_KEY); + const seedDataCount = 16; // Jumlah seed data + + // Hanya set counter jika belum ada atau lebih kecil dari jumlah seed data + // Jangan overwrite counter yang sudah lebih besar (berarti sudah ada pasien baru) + if (!storedCounter || parseInt(storedCounter, 10) < seedDataCount) { + localStorage.setItem(STORAGE_KEY, String(seedDataCount + 1)); + localStorage.setItem(LAST_DATE_KEY, datePrefix); + } + } const seedPatients = [ { @@ -843,6 +914,11 @@ export const useQueueStore = defineStore('queue', () => { }; allPatients.value.push(newPatient); + + // Increment counter setelah barcode digunakan + if (!patient) { + incrementBarcodeCounter(); + } return { success: true, @@ -874,6 +950,11 @@ export const useQueueStore = defineStore('queue', () => { }; allPatients.value.push(newPatient); + + // Increment counter setelah barcode digunakan + if (!patient) { + incrementBarcodeCounter(); + } return { success: true, @@ -938,6 +1019,11 @@ export const useQueueStore = defineStore('queue', () => { }; allPatients.value.push(newPatient); + + // Increment counter setelah barcode digunakan + if (!patient) { + incrementBarcodeCounter(); + } return { success: true, @@ -1455,6 +1541,11 @@ export const useQueueStore = defineStore('queue', () => { }; allPatients.value.push(newPatient); + + // IMPORTANT: Increment counter SETELAH barcode benar-benar digunakan untuk membuat pasien + // Ini mencegah counter naik meskipun barcode tidak digunakan + // registerPatientFromAnjungan selalu membuat pasien baru, jadi selalu increment counter + incrementBarcodeCounter(); return { success: true, @@ -1464,78 +1555,42 @@ export const useQueueStore = defineStore('queue', () => { }; // Check-in patient (update status from waiting to di-loket) + // IMPORTANT: Hanya menggunakan EXACT barcode match untuk menghindari false positive + // Format barcode: YYMMDD + 5 digit (contoh: 26011500001) + // Jangan gunakan fallback ke noAntrian atau no karena bisa menyebabkan false positive const checkInPatient = (patientIdOrBarcode) => { console.log('🔍 checkInPatient called with:', patientIdOrBarcode); console.log('📊 Total patients in store:', allPatients.value.length); - console.log('📋 First few patients:', allPatients.value.slice(0, 3).map(p => ({ - no: p.no, - noAntrian: p.noAntrian, - barcode: p.barcode, - status: p.status - }))); // Clean input - remove whitespace and normalize const cleanInput = String(patientIdOrBarcode).trim(); - const cleanInputUpper = cleanInput.toUpperCase(); - // Try to find by multiple criteria: - // 1. Exact barcode match (case-insensitive, whitespace-insensitive) - // 2. Parse as number and match with no - // 3. Extract number from input (e.g., "UM0014" -> "0014" or "14") and match with noAntrian - // 4. Check if noAntrian includes the input + // PRIORITAS: Hanya cari dengan EXACT barcode match (case-insensitive, whitespace-insensitive) + // Format barcode: YYMMDD + 5 digit (contoh: 26011500001) + // Jangan gunakan fallback ke noAntrian atau no karena bisa menyebabkan false positive const patientIndex = allPatients.value.findIndex(p => { // Normalize barcode untuk comparison const patientBarcode = String(p.barcode || '').trim(); - const patientBarcodeUpper = patientBarcode.toUpperCase(); - // Exact barcode match (case-insensitive, whitespace-insensitive) + // EXACT barcode match (case-insensitive, whitespace-insensitive) + // Ini adalah satu-satunya cara yang aman untuk match pasien if (patientBarcode === cleanInput || - patientBarcodeUpper === cleanInputUpper || - patientBarcode === patientIdOrBarcode) { - console.log('✅ Found by barcode:', patientBarcode, '===', cleanInput); + patientBarcode.toLowerCase() === cleanInput.toLowerCase()) { + console.log('✅ Found by exact barcode match:', patientBarcode, '===', cleanInput); return true; } - // Try parsing as number - const parsedNo = parseInt(cleanInput.replace(/[^0-9]/g, '')) || parseInt(patientIdOrBarcode); - if (!isNaN(parsedNo) && p.no === parsedNo) { - console.log('✅ Found by no:', p.no); - return true; - } - - // Check if noAntrian includes the input (case insensitive) - // Handle format "F-RA001" by removing "F-" prefix for comparison - const noAntrianUpper = (p.noAntrian || '').toUpperCase(); - const noAntrianWithoutPrefix = noAntrianUpper.replace(/^F-/, ''); // Remove "F-" prefix if exists - const cleanInputWithoutPrefix = cleanInputUpper.replace(/^F-/, ''); // Remove "F-" prefix from input if exists - - if (noAntrianUpper.includes(cleanInput) || - noAntrianUpper.includes(patientIdOrBarcode) || - noAntrianWithoutPrefix.includes(cleanInputWithoutPrefix) || - noAntrianWithoutPrefix === cleanInputWithoutPrefix) { - console.log('✅ Found by noAntrian:', p.noAntrian); - return true; - } - - // Try to extract number from noAntrian (e.g., "F-RA001 | Onsite - ..." -> "RA001" -> "001") - // Handle format "F-RA001" by extracting after "F-" prefix - const noAntrianMatch = noAntrianUpper.match(/^(?:F-)?([A-Z]+)(\d+)/); - if (noAntrianMatch) { - const extractedNumber = noAntrianMatch[2]; - const inputNumber = cleanInputWithoutPrefix.replace(/[^0-9]/g, ''); - if (inputNumber && (extractedNumber.includes(inputNumber) || inputNumber.includes(extractedNumber))) { - console.log('✅ Found by extracted number from noAntrian'); - return true; - } - } - return false; }); if (patientIndex === -1) { - console.log('❌ Patient not found. Searched for:', cleanInput); - console.log('📋 Available noAntrian values:', allPatients.value.map(p => p.noAntrian)); - return { success: false, message: "Pasien tidak ditemukan" }; + console.log('❌ Patient not found. Searched for barcode:', cleanInput); + console.log('📋 Available barcodes (first 10):', allPatients.value.slice(0, 10).map(p => ({ + no: p.no, + barcode: p.barcode, + noAntrian: p.noAntrian?.split(' |')[0] + }))); + return { success: false, message: `Pasien dengan barcode ${cleanInput} tidak ditemukan. Pastikan barcode benar (format: YYMMDD + 5 digit, contoh: 26011500001).` }; } // IMPORTANT: Get fresh patient data from array to avoid stale data