update bug ws checkin loket

This commit is contained in:
Fanrouver
2026-02-10 15:38:23 +07:00
parent 6af3f58e89
commit b6dc252bc9
2 changed files with 346 additions and 311 deletions
@@ -85,6 +85,24 @@
</div>
<div class="patient-klinik-pembayaran">{{ patient.klinik }} | {{ patient.pembayaran }}</div>
</div>
<!-- NEW: Option to process next even if current is active -->
<div class="next-action-shortcut mt-2" v-if="hasNextQueue">
<v-btn
block
variant="tonal"
color="primary-600"
size="small"
class="text-none font-weight-bold"
@click="$emit('process-next')"
>
<v-icon start size="16">mdi-skip-next</v-icon>
Panggil Berikutnya
</v-btn>
<div class="info-text text-center mt-1" style="font-size: 10px; color: var(--color-neutral-600);">
(Gunakan jika lupa klik Selesai pada pasien ini)
</div>
</div>
<div class="create-queue-buttons mt-4">
<div class="create-buttons-container">
+328 -311
View File
@@ -3289,331 +3289,348 @@ const checkInManual = async () => {
console.log("🔍 Original input:", originalInput);
console.log("📊 Total patients in store:", queueStore.allPatients.length);
// Cari pasien dengan dua metode:
// 1. EXACT barcode match (prioritas tertinggi) - format: YYMMDD + 5 digit (contoh: 26011500001)
// 2. EXACT nomor antrean match - format: RA020, F-RA001, EA013, dll
// Menggunakan matchNoAntrian helper untuk memastikan kode + angka match dengan benar
const foundPatient = queueStore.allPatients.find((p) => {
if (!p) return false;
let foundPatient: any = null;
let freshPatient: any = null;
// Normalize barcode untuk comparison (remove whitespace, case-insensitive)
const patientBarcode = String(p.barcode || "").trim();
try {
// Cari pasien dengan dua metode:
// ...
foundPatient = queueStore.allPatients.find((p) => {
try {
if (!p) return false;
// 1. EXACT barcode match (case-insensitive, whitespace-insensitive) - PRIORITAS TERTINGGI
if (
patientBarcode === cleanInput ||
patientBarcode.toLowerCase() === cleanInput.toLowerCase()
) {
console.log(
"✅ Found by exact barcode match:",
patientBarcode,
"===",
cleanInput,
// Normalize barcode untuk comparison (remove whitespace, case-insensitive)
const patientBarcode = String(p.barcode || "").trim();
// 1. EXACT barcode match (case-insensitive, whitespace-insensitive) - PRIORITAS TERTINGGI
if (
patientBarcode === cleanInput ||
patientBarcode.toLowerCase() === cleanInput.toLowerCase()
) {
console.log(
"✅ Found by exact barcode match:",
patientBarcode,
"===",
cleanInput,
);
return true;
}
// 2. EXACT nomor antrean match (untuk manual input)
// Menggunakan matchNoAntrian helper untuk memastikan kode + angka match dengan benar
if (p.noAntrian && typeof matchNoAntrian === "function") {
if (matchNoAntrian(originalInput, p.noAntrian)) {
console.log(
"✅ Found by exact nomor antrean match:",
p.noAntrian,
"===",
originalInput,
);
return true;
}
}
} catch (innerErr) {
console.warn("⚠️ Error in patient find loop for patient:", p, innerErr);
}
return false;
});
if (!foundPatient) {
// Tiket belum di-generate (tidak ada di queueStore)
console.error("❌ Patient not found. Input:", inputValue);
console.error("🔍 Extracted barcode/patientId:", searchBarcode);
console.error(
"📋 Available patients (first 10):",
queueStore.allPatients.slice(0, 10).map((p) => ({
no: p.no,
barcode: p.barcode,
noAntrian: p.noAntrian?.split(" |")[0],
})),
);
return true;
}
// 2. EXACT nomor antrean match (untuk manual input)
// Menggunakan matchNoAntrian helper untuk memastikan kode + angka match dengan benar
// Ini mencegah false positive seperti RA020 terdeteksi sebagai RA002
if (p.noAntrian && matchNoAntrian(originalInput, p.noAntrian)) {
console.log(
"✅ Found by exact nomor antrean match:",
p.noAntrian,
"===",
originalInput,
saveToHistory({
patientId: searchBarcode || inputValue,
queueNumber: null,
klinikQueueNumber: null,
pembayaran: "N/A",
status: "failed",
checkInTime: new Date().toISOString(),
checkInDate: new Date().toISOString(),
method: "Manual",
klinik: "N/A",
kodeKlinik: null,
});
// RESET lastCheckInResult for failure to avoid success color bleed
lastCheckInResult.value = {
success: false,
patientId: searchBarcode || inputValue,
status: "NOT_FOUND",
};
const errorMsg = `❌ Tiket Tidak Ditemukan!\n\nInput: ${inputValue}\n\nTiket dengan nomor antrean atau barcode tersebut tidak ditemukan di sistem.\n\nPastikan:\n- Nomor antrean atau barcode sudah benar\n- Tiket sudah di-generate terlebih dahulu\n- Format input: RA020, F-RA001, atau 26011500001`;
infoMessage.value = errorMsg;
infoAction.value = "checkin";
infoDialog.value = true;
showSnackbar(
"Error",
"Tiket tidak ditemukan. Pastikan nomor antrean atau barcode sudah benar.",
"error",
"mdi-alert-circle",
);
return true;
return;
}
return false;
});
// CATATAN: Validasi processStage dihapus karena terlalu ketat
// Yang penting adalah status pasien (waiting/pending), bukan processStage
// Pasien yang sudah dipanggil (status: waiting) harus bisa check-in
// meskipun processStage-nya bukan 'loket' (bisa 'klinik' atau 'penunjang')
// Validasi processStage akan dilakukan di checkInPatient() di queueStore
// yang akan mengecek status pasien secara real-time
if (!foundPatient) {
// Tiket belum di-generate (tidak ada di queueStore)
console.error("❌ Patient not found. Input:", inputValue);
console.error("🔍 Extracted barcode/patientId:", searchBarcode);
console.error(
"📋 Available patients (first 10):",
queueStore.allPatients.slice(0, 10).map((p) => ({
no: p.no,
barcode: p.barcode,
noAntrian: p.noAntrian?.split(" |")[0],
})),
);
// IMPORTANT: Refresh data pasien dari queueStore untuk mendapatkan status REAL-TIME
// Jangan gunakan foundPatient.status karena mungkin sudah stale
// Cari ulang pasien dari queueStore dengan EXACT match (barcode atau nomor antrean)
freshPatient = queueStore.allPatients.find((p) => {
if (!p) return false;
saveToHistory({
patientId: searchBarcode || inputValue,
queueNumber: null,
klinikQueueNumber: null,
pembayaran: "N/A",
status: "failed",
checkInTime: new Date().toISOString(),
checkInDate: new Date().toISOString(),
method: "Manual",
klinik: "N/A",
kodeKlinik: null,
// Match by barcode
const patientBarcode = String(p.barcode || "").trim();
if (
foundPatient && (
patientBarcode === foundPatient.barcode ||
patientBarcode === searchBarcode ||
patientBarcode === cleanInput
)
) {
return true;
}
// Match by nomor antrean (jika input adalah nomor antrean)
if (foundPatient && p.noAntrian && p.no === foundPatient.no) {
return true;
}
return false;
});
// RESET lastCheckInResult for failure to avoid success color bleed
lastCheckInResult.value = {
success: false,
patientId: searchBarcode || inputValue,
status: "NOT_FOUND",
};
if (!freshPatient) {
console.error("❌ Patient not found in queueStore after refresh");
saveToHistory({
patientId: foundPatient.barcode,
queueNumber: foundPatient.noAntrian,
klinikQueueNumber:
foundPatient.noAntrian?.split(" |")[0] || foundPatient.noAntrian,
pembayaran: foundPatient.pembayaran || "N/A",
status: "failed",
checkInTime: new Date().toISOString(),
checkInDate: new Date().toISOString(),
method: "Manual",
klinik: foundPatient.klinik || "N/A",
kodeKlinik: foundPatient.kodeKlinik || null,
});
const errorMsg = `❌ Tiket Tidak Ditemukan!\n\nInput: ${inputValue}\n\nTiket dengan nomor antrean atau barcode tersebut tidak ditemukan di sistem.\n\nPastikan:\n- Nomor antrean atau barcode sudah benar\n- Tiket sudah di-generate terlebih dahulu\n- Format input: RA020, F-RA001, atau 26011500001`;
infoMessage.value = errorMsg;
infoAction.value = "checkin";
infoDialog.value = true;
// RESET lastCheckInResult for failure
lastCheckInResult.value = {
success: false,
patientId: foundPatient.barcode,
status: "REFRESH_NOT_FOUND",
};
const errorMsg = `❌ Pasien tidak ditemukan di sistem setelah refresh data.`;
infoMessage.value = errorMsg;
infoAction.value = "checkin";
infoDialog.value = true;
showSnackbar(
"Error",
"Pasien tidak ditemukan di sistem",
"error",
"mdi-alert-circle",
);
return;
}
// Gunakan data pasien yang fresh untuk validasi
const patientStatus = freshPatient.status;
const klinikQueueNumber =
freshPatient.noAntrian?.split(" |")[0] || freshPatient.noAntrian || "N/A";
console.log("🔍 Fresh patient status:", {
barcode: freshPatient.barcode,
noAntrian: freshPatient.noAntrian,
status: patientStatus,
processStage: freshPatient.processStage,
});
// Cek apakah sudah check-in (status === 'di-loket') - gunakan data fresh
if (patientStatus === "di-loket") {
// Sudah check-in sebelumnya
saveToHistory({
patientId: freshPatient.barcode,
queueNumber: freshPatient.noAntrian,
klinikQueueNumber: klinikQueueNumber,
pembayaran: freshPatient.pembayaran || "N/A",
status: "failed",
checkInTime: new Date().toISOString(),
checkInDate: new Date().toISOString(),
method: "Manual",
klinik: freshPatient.klinik || "N/A",
kodeKlinik: freshPatient.kodeKlinik || null,
});
lastCheckInResult.value = {
success: false,
patientId: freshPatient.barcode,
status: "ALREADY_CHECKED_IN",
};
const errorMsg = `⚠️ Sudah Check-in!\n\nNomor Antrean ${klinikQueueNumber} sudah melakukan check-in sebelumnya.`;
infoMessage.value = errorMsg;
infoAction.value = "checkin";
infoDialog.value = true;
showSnackbar(
"Warning",
`Tiket ${klinikQueueNumber} sudah check-in sebelumnya`,
"warning",
"mdi-check-circle",
);
return;
}
if (patientStatus === "menunggu") {
// Status "menunggu" = belum dipanggil oleh admin loket, belum muncul di AntrianLoket
// TIDAK BOLEH check-in
saveToHistory({
patientId: freshPatient.barcode,
queueNumber: freshPatient.noAntrian,
klinikQueueNumber: klinikQueueNumber,
pembayaran: freshPatient.pembayaran || "N/A",
status: "NOT_ALLOWED",
checkInTime: new Date().toISOString(),
checkInDate: new Date().toISOString(),
method: "Manual",
klinik: freshPatient.klinik || "N/A",
kodeKlinik: freshPatient.kodeKlinik || null,
});
lastCheckInResult.value = {
success: false,
patientId: freshPatient.barcode,
status: "NOT_ALLOWED",
};
const errorMsg = `⏳ Belum Diizinkan Check-in\n\nNomor Antrean ${klinikQueueNumber} belum dipanggil oleh admin loket. Mohon menunggu hingga nomor antrean Anda dipanggil dan muncul di layar Antrian Loket.`;
infoMessage.value = errorMsg;
infoAction.value = "kembali";
infoDialog.value = true;
showSnackbar(
"Info",
`Tiket ${klinikQueueNumber} belum dipanggil. Mohon menunggu.`,
"info",
"mdi-clock-alert",
);
return;
}
// Status "waiting" atau "pending" BOLEH check-in
// Panggil checkInPatient untuk validasi dan update status ke "di-loket"
// Gunakan barcode pasien yang fresh, bukan inputValue (untuk memastikan format benar)
const patientBarcodeForCheckIn =
freshPatient.barcode || searchBarcode || inputValue;
console.log(
"🔍 Calling checkInPatient with barcode (fresh):",
patientBarcodeForCheckIn,
);
console.log(
"🔍 Original input:",
inputValue,
"| Extracted barcode:",
searchBarcode,
);
const checkInResult = await queueStore.checkInPatient(
patientBarcodeForCheckIn,
);
if (checkInResult.success && checkInResult.patient) {
// Check-in berhasil
const successKlinikQueueNumber =
checkInResult.patient.noAntrian?.split(" |")[0] ||
checkInResult.patient.noAntrian ||
"N/A";
lastCheckInResult.value = {
success: true,
patientId: checkInResult.patient.barcode,
status: "ALLOWED",
};
saveToHistory({
patientId: checkInResult.patient.barcode,
queueNumber: checkInResult.patient.noAntrian,
klinikQueueNumber: successKlinikQueueNumber,
pembayaran: checkInResult.patient.pembayaran || "N/A",
status: "success",
checkInTime: new Date().toISOString(),
checkInDate: new Date().toISOString(),
method: "Manual",
klinik: checkInResult.patient.klinik || "N/A",
kodeKlinik: checkInResult.patient.kodeKlinik || null,
});
const successMsg = `✅ Check-in Berhasil!\n\nPasien ${successKlinikQueueNumber} berhasil melakukan check-in dan status berubah menjadi di loket.`;
infoMessage.value = successMsg;
infoAction.value = "checkin";
infoDialog.value = true;
showSnackbar(
"Berhasil",
`Check-in manual berhasil. Tiket ${successKlinikQueueNumber} siap diproses.`,
"success",
"mdi-check-circle",
);
manualInput.value = "";
if (manualForm.value) {
(manualForm.value as any).reset();
}
} else {
// Check-in gagal (misalnya validasi di checkInPatient gagal)
saveToHistory({
patientId: foundPatient.barcode,
queueNumber: foundPatient.noAntrian,
klinikQueueNumber: klinikQueueNumber,
pembayaran: foundPatient.pembayaran || "N/A",
status: "failed",
checkInTime: new Date().toISOString(),
checkInDate: new Date().toISOString(),
method: "Manual",
klinik: foundPatient.klinik || "N/A",
kodeKlinik: foundPatient.kodeKlinik || null,
});
lastCheckInResult.value = {
success: false,
patientId: foundPatient.barcode,
status: "FAILED",
};
const errorMsg = `❌ Check-in Gagal!\n\n${checkInResult?.message || "Status pasien tidak memungkinkan untuk check-in."}`;
infoMessage.value = errorMsg;
infoAction.value = "checkin";
infoDialog.value = true;
showSnackbar(
"Gagal",
checkInResult?.message || "Check-in manual gagal dilakukan",
"error",
"mdi-close-circle",
);
}
} catch (error) {
console.error("❌ Fatal Error in checkInManual:", error);
showSnackbar(
"Error",
"Tiket tidak ditemukan. Pastikan nomor antrean atau barcode sudah benar.",
"Fatal Error",
"Terjadi kesalahan sistem saat check-in manual",
"error",
"mdi-alert-circle",
);
return;
}
// CATATAN: Validasi processStage dihapus karena terlalu ketat
// Yang penting adalah status pasien (waiting/pending), bukan processStage
// Pasien yang sudah dipanggil (status: waiting) harus bisa check-in
// meskipun processStage-nya bukan 'loket' (bisa 'klinik' atau 'penunjang')
// Validasi processStage akan dilakukan di checkInPatient() di queueStore
// yang akan mengecek status pasien secara real-time
// IMPORTANT: Refresh data pasien dari queueStore untuk mendapatkan status REAL-TIME
// Jangan gunakan foundPatient.status karena mungkin sudah stale
// Cari ulang pasien dari queueStore dengan EXACT match (barcode atau nomor antrean)
const freshPatient = queueStore.allPatients.find((p) => {
if (!p) return false;
// Match by barcode
const patientBarcode = String(p.barcode || "").trim();
if (
patientBarcode === foundPatient.barcode ||
patientBarcode === searchBarcode ||
patientBarcode === cleanInput
) {
return true;
}
// Match by nomor antrean (jika input adalah nomor antrean)
if (p.noAntrian && p.no === foundPatient.no) {
return true;
}
return false;
});
if (!freshPatient) {
console.error("❌ Patient not found in queueStore after refresh");
saveToHistory({
patientId: foundPatient.barcode,
queueNumber: foundPatient.noAntrian,
klinikQueueNumber:
foundPatient.noAntrian?.split(" |")[0] || foundPatient.noAntrian,
pembayaran: foundPatient.pembayaran || "N/A",
status: "failed",
checkInTime: new Date().toISOString(),
checkInDate: new Date().toISOString(),
method: "Manual",
klinik: foundPatient.klinik || "N/A",
kodeKlinik: foundPatient.kodeKlinik || null,
});
// RESET lastCheckInResult for failure
lastCheckInResult.value = {
success: false,
patientId: foundPatient.barcode,
status: "REFRESH_NOT_FOUND",
};
const errorMsg = `❌ Pasien tidak ditemukan di sistem setelah refresh data.`;
infoMessage.value = errorMsg;
infoAction.value = "checkin";
infoDialog.value = true;
showSnackbar(
"Error",
"Pasien tidak ditemukan di sistem",
"error",
"mdi-alert-circle",
);
return;
}
// Gunakan data pasien yang fresh untuk validasi
const patientStatus = freshPatient.status;
const klinikQueueNumber =
freshPatient.noAntrian?.split(" |")[0] || freshPatient.noAntrian || "N/A";
console.log("🔍 Fresh patient status:", {
barcode: freshPatient.barcode,
noAntrian: freshPatient.noAntrian,
status: patientStatus,
processStage: freshPatient.processStage,
});
// Cek apakah sudah check-in (status === 'di-loket') - gunakan data fresh
if (patientStatus === "di-loket") {
// Sudah check-in sebelumnya
saveToHistory({
patientId: freshPatient.barcode,
queueNumber: freshPatient.noAntrian,
klinikQueueNumber: klinikQueueNumber,
pembayaran: freshPatient.pembayaran || "N/A",
status: "failed",
checkInTime: new Date().toISOString(),
checkInDate: new Date().toISOString(),
method: "Manual",
klinik: freshPatient.klinik || "N/A",
kodeKlinik: freshPatient.kodeKlinik || null,
});
lastCheckInResult.value = {
success: false,
patientId: freshPatient.barcode,
status: "ALREADY_CHECKED_IN",
};
const errorMsg = `⚠️ Sudah Check-in!\n\nNomor Antrean ${klinikQueueNumber} sudah melakukan check-in sebelumnya.`;
infoMessage.value = errorMsg;
infoAction.value = "checkin";
infoDialog.value = true;
showSnackbar(
"Warning",
`Tiket ${klinikQueueNumber} sudah check-in sebelumnya`,
"warning",
"mdi-check-circle",
);
return;
}
if (patientStatus === "menunggu") {
// Status "menunggu" = belum dipanggil oleh admin loket, belum muncul di AntrianLoket
// TIDAK BOLEH check-in
saveToHistory({
patientId: freshPatient.barcode,
queueNumber: freshPatient.noAntrian,
klinikQueueNumber: klinikQueueNumber,
pembayaran: freshPatient.pembayaran || "N/A",
status: "NOT_ALLOWED",
checkInTime: new Date().toISOString(),
checkInDate: new Date().toISOString(),
method: "Manual",
klinik: freshPatient.klinik || "N/A",
kodeKlinik: freshPatient.kodeKlinik || null,
});
lastCheckInResult.value = {
success: false,
patientId: freshPatient.barcode,
status: "NOT_ALLOWED",
};
const errorMsg = `⏳ Belum Diizinkan Check-in\n\nNomor Antrean ${klinikQueueNumber} belum dipanggil oleh admin loket. Mohon menunggu hingga nomor antrean Anda dipanggil dan muncul di layar Antrian Loket.`;
infoMessage.value = errorMsg;
infoAction.value = "kembali";
infoDialog.value = true;
showSnackbar(
"Info",
`Tiket ${klinikQueueNumber} belum dipanggil. Mohon menunggu.`,
"info",
"mdi-clock-alert",
);
return;
}
// Status "waiting" atau "pending" BOLEH check-in
// Panggil checkInPatient untuk validasi dan update status ke "di-loket"
// Gunakan barcode pasien yang fresh, bukan inputValue (untuk memastikan format benar)
const patientBarcodeForCheckIn =
freshPatient.barcode || searchBarcode || inputValue;
console.log(
"🔍 Calling checkInPatient with barcode (fresh):",
patientBarcodeForCheckIn,
);
console.log(
"🔍 Original input:",
inputValue,
"| Extracted barcode:",
searchBarcode,
);
const checkInResult = await queueStore.checkInPatient(
patientBarcodeForCheckIn,
);
if (checkInResult.success && checkInResult.patient) {
// Check-in berhasil
const successKlinikQueueNumber =
checkInResult.patient.noAntrian?.split(" |")[0] ||
checkInResult.patient.noAntrian ||
"N/A";
lastCheckInResult.value = {
success: true,
patientId: checkInResult.patient.barcode,
status: "ALLOWED",
};
saveToHistory({
patientId: checkInResult.patient.barcode,
queueNumber: checkInResult.patient.noAntrian,
klinikQueueNumber: successKlinikQueueNumber,
pembayaran: checkInResult.patient.pembayaran || "N/A",
status: "success",
checkInTime: new Date().toISOString(),
checkInDate: new Date().toISOString(),
method: "Manual",
klinik: checkInResult.patient.klinik || "N/A",
kodeKlinik: checkInResult.patient.kodeKlinik || null,
});
const successMsg = `✅ Check-in Berhasil!\n\nPasien ${successKlinikQueueNumber} berhasil melakukan check-in dan status berubah menjadi di loket.`;
infoMessage.value = successMsg;
infoAction.value = "checkin";
infoDialog.value = true;
showSnackbar(
"Berhasil",
`Check-in manual berhasil. Tiket ${successKlinikQueueNumber} siap diproses.`,
"success",
"mdi-check-circle",
);
manualInput.value = "";
if (manualForm.value) {
(manualForm.value as any).reset();
}
} else {
// Check-in gagal (misalnya validasi di checkInPatient gagal)
saveToHistory({
patientId: foundPatient.barcode,
queueNumber: foundPatient.noAntrian,
klinikQueueNumber: klinikQueueNumber,
pembayaran: foundPatient.pembayaran || "N/A",
status: "failed",
checkInTime: new Date().toISOString(),
checkInDate: new Date().toISOString(),
method: "Manual",
klinik: foundPatient.klinik || "N/A",
kodeKlinik: foundPatient.kodeKlinik || null,
});
lastCheckInResult.value = {
success: false,
patientId: foundPatient.barcode,
status: "FAILED",
};
const errorMsg = `❌ Check-in Gagal!\n\n${checkInResult.message || "Status pasien tidak memungkinkan untuk check-in."}`;
infoMessage.value = errorMsg;
infoAction.value = "checkin";
infoDialog.value = true;
showSnackbar(
"Gagal",
checkInResult.message || "Check-in manual gagal dilakukan",
"error",
"mdi-close-circle",
"mdi-alert-octagon",
);
}
};