Workflow การจ่ายยาผู้ป่วยนอก (OPD Dispensing Workflow)
📋 สารบัญ
ภาพรวม
ไฟล์: modules/pharmacy/opd-dispensing.html (2353 lines)
การจ่ายยาผู้ป่วยนอก (OPD Dispensing) เป็นกระบวนการหลักของระบบเภสัชกรรมที่จัดการตั้งแต่การรับใบสั่งยาจากแพทย์ การตรวจสอบความปลอดภัย การจัดเตรียมยา การจ่ายยา และการให้คำแนะนำการใช้ยาแก่ผู้ป่วย
วัตถุประสงค์
- ✅ จัดการคิวผู้ป่วยที่รอรับยาอย่างเป็นระบบ
- ✅ ตรวจสอบความปลอดภัยของยา (Drug Interaction, Allergy)
- ✅ จัดเตรียมและจ่ายยาอย่างถูกต้องตามใบสั่ง
- ✅ พิมพ์ฉลากยาและเอกสารประกอบ
- ✅ ให้คำแนะนำการใช้ยาที่ถูกต้อง
- ✅ บันทึกข้อมูลการจ่ายยาและคืนยา
ขั้นตอนการทำงาน
1. Overview Workflow
graph TB
A[แพทย์สั่งยาที่ OPD Exam] --> B[ใบสั่งยาเข้าคิว Pharmacy]
B --> C[เภสัชกรเห็นคิวใน Patient List]
C --> D{เลือกผู้ป่วย}
D --> E[ดูรายการยาที่สั่ง]
E --> F[ตรวจสอบ Drug Interaction]
F --> G{มีปัญหา?}
G -->|Yes| H[แจ้งเตือน + แนะนำ]
H --> I{แพทย์เปลี่ยนคำสั่ง?}
I -->|Yes| E
I -->|No| J[Override + บันทึกเหตุผล]
G -->|No| K[เลือกรายการที่จะจ่าย]
J --> K
K --> L[พิมพ์ฉลากยา]
L --> M[จัดเตรียมยา]
M --> N{ผู้ป่วยจ่ายเงิน?}
N -->|ยัง| O[แจ้งให้ไปจ่ายเงิน]
O --> P[เปลี่ยนสถานะ: waiting_payment]
N -->|แล้ว| Q[เรียกผู้ป่วยมารับยา]
Q --> R[ให้คำแนะนำการใช้ยา]
R --> S[มอบยาให้ผู้ป่วย]
S --> T[บันทึกการจ่ายยา]
T --> U[เปลี่ยนสถานะ: dispensed]
U --> V[จบกระบวนการ]
2. ขั้นตอนแบบละเอียด
Step 1: เข้าสู่หน้าจ่ายยา OPD
// URL: modules/pharmacy/opd-dispensing.html
// เมื่อโหลดหน้า:
1. โหลดข้อมูลคิวผู้ป่วยจาก pharmacy-opd-queue.json
2. แสดงสถิติด้านบน (รอจัดยา, รอจ่ายเงิน, รอรับยา, จ่ายแล้ว)
3. แสดงรายชื่อผู้ป่วยในคิวทางซ้าย
JSON Source: data/pharmacy-opd-queue.json
Step 2: เลือกผู้ป่วย
// เภสัชกรคลิกที่รายชื่อผู้ป่วยในคิว
function selectPatient(prescriptionId) {
// 1. ดึงข้อมูลใบสั่งยา
const prescription = opdQueue.find(p => p.prescriptionId === prescriptionId);
// 2. แสดงข้อมูลผู้ป่วย (Header)
displayPatientInfo(prescription);
// 3. โหลดรายการยา
displayMedicationList(prescription.items);
// 4. ตรวจสอบ Drug Interactions
checkDrugInteractions(prescription.items);
// 5. โหลดประวัติการใช้ยา
loadMedicationHistory(prescription.hn);
// 6. แสดงปุ่มตามสถานะ
showActionButtons(prescription.status);
}
Step 3: ตรวจสอบความปลอดภัยของยา
3.1 Drug Interaction Check
async function checkDrugInteractions(medications) {
// ใช้ MedicationService
const interactions = await medicationService.checkDrugInteractions(medications);
if (interactions.length > 0) {
// แสดง Alert ตามความรุนแรง
interactions.forEach(interaction => {
showInteractionAlert(interaction);
});
}
}
ระดับความรุนแรง: - 🔴 Major (รุนแรง): ห้ามใช้ร่วมกัน - 🟡 Moderate (ปานกลาง): ระวังใช้ ปรับขนาดยา - 🟢 Minor (เล็กน้อย): ใช้ได้ แนะนำให้ความรู้
ตัวอย่าง Alert:
<div class="alert alert-danger">
<i class="bi bi-exclamation-triangle-fill"></i>
<strong>Drug Interaction: Major</strong>
<p>Warfarin ⚠️ Aspirin</p>
<p><small>เพิ่มความเสี่ยงเลือดออก ควรหลีกเลี่ยงการใช้ร่วมกัน</small></p>
<button class="btn btn-sm btn-warning">แจ้งแพทย์</button>
</div>
3.2 Allergy Check
function checkAllergies(medications, patientAllergies) {
const allergyWarnings = medicationService.checkAllergies(
medications,
patientAllergies
);
if (allergyWarnings.length > 0) {
showAllergyAlert(allergyWarnings);
}
}
Step 4: จัดการรายการยา
4.1 แสดงรายการยา
<div class="medication-item" data-item-id="PRESC-ITEM-001">
<div class="medication-header">
<input type="checkbox" class="medication-checkbox" checked>
<div class="medication-info">
<h6>Paracetamol 500mg</h6>
<div class="text-muted small">
<span>จำนวน: 20 เม็ด</span> |
<span>วิธีใช้: 1-2 เม็ด ทุก 4-6 ชั่วโมง เมื่อมีอาการ</span>
</div>
<div class="text-muted small">
<i class="bi bi-info-circle"></i> รับประทานเมื่อมีไข้หรือปวด
</div>
</div>
<div class="medication-actions">
<button class="btn btn-sm btn-outline-primary" onclick="editMedication('PRESC-ITEM-001')">
<i class="bi bi-pencil"></i>
</button>
<span class="text-success fw-bold">฿40.00</span>
</div>
</div>
</div>
4.2 เลือก/ยกเลิกรายการยา
// เลือกทั้งหมด
function selectAllMedications() {
document.querySelectorAll('.medication-checkbox').forEach(cb => {
cb.checked = true;
});
calculateTotal();
}
// ยกเลิกทั้งหมด
function deselectAllMedications() {
document.querySelectorAll('.medication-checkbox').forEach(cb => {
cb.checked = false;
});
calculateTotal();
}
// คำนวณยอดรวม
function calculateTotal() {
let total = 0;
document.querySelectorAll('.medication-checkbox:checked').forEach(cb => {
const item = cb.closest('.medication-item');
const price = parseFloat(item.dataset.price);
total += price;
});
document.getElementById('totalCost').textContent = `฿${total.toFixed(2)}`;
}
4.3 เพิ่มยา
function addMedication() {
// เปิด Modal ค้นหายา
showMedicationSearchModal();
}
async function searchMedication(query) {
// ใช้ MedicationService
const results = await medicationService.searchMedications(query, {
limit: 20,
includeOutOfStock: false
});
displaySearchResults(results);
}
function addMedicationToList(medication) {
// เพิ่มยาเข้ารายการ
const newItem = createMedicationItem(medication);
document.getElementById('medicationsList').appendChild(newItem);
calculateTotal();
}
Step 5: พิมพ์ฉลากยา
function printLabels() {
const selectedItems = getSelectedMedications();
if (selectedItems.length === 0) {
alert('กรุณาเลือกรายการยาที่ต้องการพิมพ์ฉลาก');
return;
}
// สร้าง HTML สำหรับพิมพ์
const printContent = generateLabelHTML(selectedItems);
// เปิดหน้าต่างใหม่สำหรับพิมพ์
const printWindow = window.open('', '_blank');
printWindow.document.write(printContent);
printWindow.document.close();
printWindow.print();
// อัปเดตสถานะว่าพิมพ์แล้ว
selectedItems.forEach(item => {
item.isPrinted = true;
});
}
ตัวอย่างฉลากยา:
╔════════════════════════════════════╗
║ โรงพยาบาลต้นแบบ ║
║ ฉลากยา OPD ║
╠════════════════════════════════════╣
║ ชื่อผู้ป่วย: สมชาย ใจดี ║
║ HN: HN000123 ║
║ วันที่: 11 ธ.ค. 2567 ║
╠════════════════════════════════════╣
║ ยา: Paracetamol 500mg ║
║ จำนวน: 20 เม็ด ║
║ ║
║ วิธีใช้: ║
║ รับประทาน 1-2 เม็ด ║
║ ทุก 4-6 ชั่วโมง ║
║ เมื่อมีไข้หรือปวด ║
║ ║
║ คำเตือน: ║
║ - ไม่เกิน 8 เม็ดต่อวัน ║
║ - ห่างจากแอลกอฮอล์ ║
╚════════════════════════════════════╝
Step 6: จ่ายยา
6.1 ตรวจสอบการชำระเงิน
async function handleDispensing() {
const prescription = getCurrentPrescription();
// ตรวจสอบว่าจ่ายเงินแล้วหรือยัง
if (prescription.status === 'waiting_payment') {
alert('ผู้ป่วยยังไม่ได้จ่ายเงิน กรุณาแจ้งให้ไปจ่ายเงินที่เคาน์เตอร์การเงิน');
return;
}
// เปลี่ยนสถานะเป็น ready_to_dispense
await updatePrescriptionStatus(prescription.prescriptionId, 'ready_to_dispense');
alert('พร้อมจ่ายยา กรุณาเรียกผู้ป่วยมารับยา');
}
6.2 จ่ายยาให้ผู้ป่วย
async function confirmDispense() {
const prescription = getCurrentPrescription();
const selectedItems = getSelectedMedications();
// ยืนยันการจ่ายยา
const confirmed = confirm(
`จ่ายยาให้ ${prescription.patientName} จำนวน ${selectedItems.length} รายการ?`
);
if (!confirmed) return;
// บันทึกการจ่ายยา
const result = await medicationService.dispenseMedication({
prescriptionId: prescription.prescriptionId,
hn: prescription.hn,
items: selectedItems,
dispensedBy: getCurrentUser().userId, // เภสัชกรที่จ่าย
dispensedAt: new Date().toISOString(),
instructions: getDispensingInstructions()
});
if (result.success) {
// เปลี่ยนสถานะเป็น dispensed
await updatePrescriptionStatus(prescription.prescriptionId, 'dispensed');
// แสดงข้อความสำเร็จ
showSuccessAlert('จ่ายยาเรียบร้อยแล้ว');
// รีเฟรชคิว
refreshQueue();
// ล้างข้อมูลผู้ป่วยปัจจุบัน
clearCurrentPatient();
} else {
showErrorAlert(result.message);
}
}
Step 7: คืนยา (Return Medication)
function handleMedicationReturn() {
const prescription = getCurrentPrescription();
// ตรวจสอบว่าจ่ายยาแล้วหรือยัง
if (prescription.status !== 'dispensed') {
alert('ใบสั่งยานี้ยังไม่ได้จ่ายยา ไม่สามารถทำการคืนยาได้');
return;
}
// แสดงฟอร์มคืนยา
showReturnForm(prescription);
}
async function submitReturn(returnData) {
// บันทึกการคืนยา
const result = await saveReturnTransaction({
prescriptionId: returnData.prescriptionId,
returnedItems: returnData.items, // รายการยาที่คืน
reason: returnData.reason,
reasonDetail: returnData.reasonDetail,
returnedBy: getCurrentUser().userId,
returnedAt: new Date().toISOString()
});
if (result.success) {
// อัปเดตสต็อคยา
await updateMedicationStock(returnData.items);
// บันทึกประวัติคืนยา
await saveReturnHistory(returnData);
showSuccessAlert('บันทึกการคืนยาเรียบร้อยแล้ว');
}
}
เหตุผลการคืนยา: - ยาหมดอายุ (expired) - จ่ายยาผิด (wrong_drug) - จ่ายจำนวนเกิน (wrong_quantity) - ผู้ป่วยไม่รับยา (patient_refuse) - แพทย์เปลี่ยนคำสั่ง (doctor_change) - ผู้ป่วยแพ้ยา (adverse_reaction) - จ่ายซ้ำ (duplicate) - ยาชำรุด (damaged) - อื่นๆ (other)
สถานะใบสั่งยา
Status Flow
stateDiagram-v2
[*] --> waiting_prepare: ใบสั่งยาเข้าระบบ
waiting_prepare --> waiting_payment: เภสัชกรจัดยาเสร็จ
waiting_payment --> ready_to_dispense: ผู้ป่วยจ่ายเงินแล้ว
ready_to_dispense --> dispensed: เภสัชกรจ่ายยาให้ผู้ป่วย
dispensed --> [*]: เสร็จสิ้น
waiting_prepare --> cancelled: ยกเลิก
waiting_payment --> cancelled: ยกเลิก
ready_to_dispense --> cancelled: ยกเลิก
dispensed --> returned: คืนยา (บางส่วน/ทั้งหมด)
Status Descriptions
| Status | ชื่อภาษาไทย | คำอธิบาย | สี | Action ที่ทำได้ |
|---|---|---|---|---|
waiting_prepare |
รอจัดยา | รอเภสัชกรจัดเตรียมยา | 🔵 Blue | เลือกรายการ, พิมพ์ฉลาก, ส่งจ่ายเงิน |
waiting_payment |
รอจ่ายเงิน | รอผู้ป่วยชำระเงิน | 🟡 Warning | รอการยืนยันจากการเงิน |
ready_to_dispense |
รอรับยา | พร้อมจ่ายยาให้ผู้ป่วย | 🟢 Success | เรียกผู้ป่วย, จ่ายยา |
dispensed |
จ่ายแล้ว | จ่ายยาเสร็จสิ้น | ⚫ Gray | ดูประวัติ, คืนยา (ถ้ามีปัญหา) |
cancelled |
ยกเลิก | ยกเลิกใบสั่งยา | 🔴 Red | ดูเหตุผลการยกเลิก |
returned |
คืนยาแล้ว | คืนยาบางส่วนหรือทั้งหมด | 🟣 Purple | ดูรายละเอียดการคืน |
UI Components
1. Layout Structure
┌────────────────────────────────────────────────────────────────┐
│ Navigation Bar │
│ [🏥 HIS] [💊 ระบบเภสัชกรรม] [🏠 หน้าหลัก] │
└────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────┐
│ Header: จ่ายยาผู้ป่วยนอก (OPD) │
│ [รอจัดยา: 5] [รอจ่ายเงิน: 3] [รอรับยา: 2] [จ่ายแล้ว: 10] │
└────────────────────────────────────────────────────────────────┘
┌──────────────────────┬─────────────────────────────────────────┐
│ Patient List (Left) │ Patient Details (Right) │
│ Width: 350px │ Flex: 1 │
│ │ │
│ [Search Box] │ ┌─────────────────────────────────────┐ │
│ [Filters] │ │ Patient Info Header │ │
│ │ │ - Name, HN, Clinic, Doctor │ │
│ ┌──────────────────┐ │ │ - Status Badge, Date │ │
│ │ ■ สมชาย ใจดี │ │ └─────────────────────────────────────┘ │
│ │ HN000123 │ │ │
│ │ [รอจัดยา] │ │ ┌───────────────────────────────────┐ │
│ └──────────────────┘ │ │ Tabs: [รายการยา] [ผลตรวจ] ... │ │
│ │ └───────────────────────────────────┘ │
│ ┌──────────────────┐ │ │
│ │ □ สมหญิง แข็งแรง │ │ ┌───────────────────────────────────┐ │
│ │ HN000456 │ │ │ Drug Interaction Alert (if any) │ │
│ │ [รอจ่ายเงิน] │ │ └───────────────────────────────────┘ │
│ └──────────────────┘ │ │
│ │ ┌───────────────────────────────────┐ │
│ ┌──────────────────┐ │ │ Medication List │ │
│ │ □ ประเสริฐ สุขดี │ │ │ □ Paracetamol 500mg x20 │ │
│ │ HN000789 │ │ │ □ Amoxicillin 500mg x21 │ │
│ │ [รอรับยา] │ │ │ ... │ │
│ └──────────────────┘ │ └───────────────────────────────────┘ │
│ │ │
│ ... │ ┌───────────────────────────────────┐ │
│ │ │ ยอดรวม: ฿150.00 │ │
│ │ └───────────────────────────────────┘ │
│ │ │
│ │ [พิมพ์ฉลาก] [ส่งจ่ายเงิน] [จ่ายยา] │
└──────────────────────┴─────────────────────────────────────────┘
2. Patient List Item
<div class="patient-item" onclick="selectPatient('OPD-Q-001')" data-status="waiting_prepare">
<div class="d-flex justify-content-between align-items-start mb-2">
<div>
<h6 class="mb-1">สมชาย ใจดี</h6>
<div class="text-muted small">
<span><i class="bi bi-person-badge"></i> HN000123</span>
</div>
</div>
<span class="badge status-waiting-prepare">รอจัดยา</span>
</div>
<div class="text-muted small">
<div><i class="bi bi-hospital"></i> คลินิกอายุรกรรม</div>
<div><i class="bi bi-person"></i> นพ.สมศักดิ์ แพทย์ดี</div>
<div><i class="bi bi-clock"></i> 10:30 น.</div>
</div>
<div class="mt-2">
<span class="badge bg-primary">
<i class="bi bi-capsule"></i> 3 รายการ
</span>
<span class="badge bg-success">
฿150.00
</span>
</div>
</div>
3. Action Buttons (ตามสถานะ)
Status: waiting_prepare
<button class="btn btn-primary" onclick="prepareForPayment()">
<i class="bi bi-cash-coin"></i> ส่งจ่ายเงิน
</button>
<button class="btn btn-success" onclick="skipPaymentAndDispense()">
<i class="bi bi-check-circle"></i> จ่ายยาเลย (ไม่เก็บเงิน)
</button>
Status: ready_to_dispense
<button class="btn btn-success btn-lg" onclick="confirmDispense()">
<i class="bi bi-check-circle-fill"></i> จ่ายยาให้ผู้ป่วย
</button>
Status: dispensed
<button class="btn btn-info" onclick="viewDispenseHistory()">
<i class="bi bi-clock-history"></i> ดูประวัติการจ่ายยา
</button>
<button class="btn btn-warning" onclick="handleMedicationReturn()">
<i class="bi bi-arrow-return-left"></i> คืนยา
</button>
Features หลัก
1. Real-time Queue Management
// อัปเดตคิวอัตโนมัติทุก 30 วินาที
setInterval(refreshQueue, 30000);
function refreshQueue() {
loadOPDQueue();
updateStats();
}
function updateStats() {
const stats = {
waiting: opdQueue.filter(p => p.status === 'waiting_prepare').length,
payment: opdQueue.filter(p => p.status === 'waiting_payment').length,
ready: opdQueue.filter(p => p.status === 'ready_to_dispense').length,
dispensed: opdQueue.filter(p => p.status === 'dispensed').length
};
document.getElementById('stat-waiting').textContent = `รอจัดยา: ${stats.waiting}`;
document.getElementById('stat-payment').textContent = `รอจ่ายเงิน: ${stats.payment}`;
document.getElementById('stat-ready').textContent = `รอรับยา: ${stats.ready}`;
document.getElementById('stat-dispensed').textContent = `จ่ายแล้ว: ${stats.dispensed}`;
}
2. Search & Filter
// ค้นหาผู้ป่วย
document.getElementById('searchPatient').addEventListener('input', (e) => {
const query = e.target.value.toLowerCase();
filterPatientList(query);
});
function filterPatientList(query) {
const filtered = opdQueue.filter(p => {
return p.patientName.toLowerCase().includes(query) ||
p.hn.toLowerCase().includes(query);
});
displayPatientList(filtered);
}
// กรองตามสถานะ
document.getElementById('filterStatus').addEventListener('change', (e) => {
const status = e.target.value;
if (status === 'all') {
displayPatientList(opdQueue);
} else {
const filtered = opdQueue.filter(p => p.status === status);
displayPatientList(filtered);
}
});
// กรองตามคลินิก
document.getElementById('filterClinic').addEventListener('change', (e) => {
const clinic = e.target.value;
if (clinic === 'all') {
displayPatientList(opdQueue);
} else {
const filtered = opdQueue.filter(p => p.clinic === clinic);
displayPatientList(filtered);
}
});
3. Drug Interaction Alert
async function displayDrugInteractions(medications) {
const interactions = await medicationService.checkDrugInteractions(medications);
const alertContainer = document.getElementById('interactionsAlert');
alertContainer.innerHTML = '';
if (interactions.length === 0) {
return;
}
interactions.forEach(interaction => {
const severityClass = {
'major': 'alert-danger',
'moderate': 'alert-warning',
'minor': 'alert-info'
}[interaction.severity];
const alertHTML = `
<div class="alert ${severityClass} alert-dismissible fade show">
<h6><i class="bi bi-exclamation-triangle-fill"></i>
Drug Interaction: ${interaction.severity.toUpperCase()}
</h6>
<p><strong>${interaction.drug1.name} ⚠️ ${interaction.drug2.name}</strong></p>
<p class="mb-2">${interaction.description}</p>
<p class="mb-0 small">
<strong>การจัดการ:</strong> ${interaction.management}
</p>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
`;
alertContainer.insertAdjacentHTML('beforeend', alertHTML);
});
}
4. Medication Template Integration
function showTemplateModal() {
const prescription = getCurrentPrescription();
// โหลด Templates ของแพทย์
const templates = medicationService.getTemplates(
prescription.doctorId,
'all' // personal + shared
);
// แสดง Modal
displayTemplateSelectionModal(templates);
}
function applyTemplate(templateId) {
const template = medicationService.getTemplateById(templateId);
// เพิ่มยาจาก Template เข้ารายการ
template.medications.forEach(med => {
addMedicationToList(med);
});
// คำนวณยอดรวม
calculateTotal();
// แจ้งเตือน
showSuccessAlert(`เพิ่มยาจาก Template "${template.name}" เรียบร้อยแล้ว`);
}
5. Prescription History
async function loadMedicationHistory(hn) {
const history = await medicationService.getPrescriptionHistory(hn, {
limit: 10,
sortBy: 'date',
order: 'desc'
});
displayHistory(history);
}
function displayHistory(history) {
const container = document.getElementById('historyContent');
if (history.length === 0) {
container.innerHTML = '<p class="text-muted">ไม่พบประวัติการใช้ยา</p>';
return;
}
const historyHTML = history.map(rx => `
<div class="card mb-2">
<div class="card-body">
<div class="d-flex justify-content-between">
<h6>${HIS.formatThaiDate(rx.visitDate)}</h6>
<span class="badge bg-info">${rx.clinicName}</span>
</div>
<p class="text-muted small mb-2">${rx.doctorName}</p>
<ul class="list-unstyled mb-0">
${rx.medications.map(med => `
<li class="small">
• ${med.medicationName} - ${med.dosage} ${med.frequency}
</li>
`).join('')}
</ul>
</div>
</div>
`).join('');
container.innerHTML = historyHTML;
}
การจัดการข้อมูล
Data Source
// หลัก: pharmacy-opd-queue.json
{
"prescriptionId": "OPD-Q-001",
"type": "OPD",
"visitId": "VISIT-2024-001",
"hn": "HN000123",
"patientName": "สมชาย ใจดี",
"clinic": "คลินิกอายุรกรรม",
"doctor": "นพ.สมศักดิ์ แพทย์ดี",
"doctorId": "DOC001",
"prescriptionDate": "2024-12-11T10:30:00",
"status": "waiting_prepare",
"items": [...]
}
// รอง: prescription-history.json (สำหรับดูประวัติ)
// รอง: pharmacy-return-history.json (สำหรับบันทึกการคืนยา)
Data Update Flow
// 1. อัปเดตสถานะใบสั่งยา
async function updatePrescriptionStatus(prescriptionId, newStatus) {
const prescription = opdQueue.find(p => p.prescriptionId === prescriptionId);
prescription.status = newStatus;
prescription.updatedAt = new Date().toISOString();
// ในระบบจริง: ส่ง API
// await fetch(`/api/pharmacy/opd-queue/${prescriptionId}`, {
// method: 'PATCH',
// body: JSON.stringify({ status: newStatus })
// });
// Mock: บันทึกใน localStorage
localStorage.setItem('his_opd_queue', JSON.stringify(opdQueue));
}
// 2. บันทึกการจ่ายยา
async function recordDispensing(dispensingData) {
// บันทึกรายละเอียดการจ่ายยา
const record = {
...dispensingData,
id: generateDispensingId(),
createdAt: new Date().toISOString()
};
// เพิ่มเข้าประวัติ
const history = JSON.parse(localStorage.getItem('his_dispensing_history') || '[]');
history.push(record);
localStorage.setItem('his_dispensing_history', JSON.stringify(history));
// อัปเดตสต็อคยา
await updateMedicationStock(dispensingData.items);
}
Use Cases
Use Case 1: จ่ายยาปกติ (Happy Path)
Scenario: ผู้ป่วยมารับยาตามปกติ ไม่มีปัญหา
- เภสัชกรเลือกผู้ป่วย "สมชาย ใจดี" จากคิว
- ตรวจสอบรายการยา 3 รายการ
- ระบบตรวจสอบ Drug Interaction → ไม่พบปัญหา
- เภสัชกรเลือกยาทั้งหมด
- คลิก "พิมพ์ฉลากยา"
- จัดเตรียมยาตามฉลาก
- คลิก "ส่งจ่ายเงิน" (สถานะ → waiting_payment)
- ผู้ป่วยไปจ่ายเงินที่เคาน์เตอร์การเงิน
- การเงินยืนยันการชำระเงิน (สถานะ → ready_to_dispense)
- เภสัชกรเรียกผู้ป่วยมารับยา
- ให้คำแนะนำการใช้ยา
- คลิก "จ่ายยาให้ผู้ป่วย" (สถานะ → dispensed)
ระยะเวลา: ~5-10 นาที
Use Case 2: พบ Drug Interaction
Scenario: พบยามีปฏิกิริยาระหว่างกัน
- เภสัชกรเลือกผู้ป่วย
- ระบบแจ้งเตือน: "Major Interaction: Warfarin ⚠️ Aspirin"
- เภสัชกร:
- ทางเลือก 1: โทรติดต่อแพทย์เพื่อเปลี่ยนยา
- ทางเลือก 2: Override + บันทึกเหตุผล (ถ้ามีเหตุผลทางการแพทย์)
- แพทย์เปลี่ยนคำสั่งเป็น Clopidogrel
- ดำเนินการจ่ายยาตามปกติ
ระยะเวลา: ~10-15 นาที
Use Case 3: คืนยา
Scenario: ผู้ป่วยคืนยาเพราะแพ้
- ผู้ป่วยกลับมาพร้อมยาที่ยังไม่เปิด
- เภสัชกรเลือกใบสั่งยาที่จ่ายไปแล้ว
- ไปที่แท็บ "คืนยา"
- เลือกรายการยาที่จะคืน (เช่น Amoxicillin)
- เลือกเหตุผล: "ผู้ป่วยแพ้ยา/มีอาการไม่พึงประสงค์"
- ระบุรายละเอียด: "ผื่นขึ้นทั้งตัวหลังรับประทาน 30 นาที"
- คลิก "บันทึกการคืนยา"
- ระบบอัปเดตสต็อคยา
- บันทึกประวัติคืนยา
หมายเหตุ: ควรแจ้งแพทย์เพื่อบันทึกการแพ้ยาในประวัติผู้ป่วย
Use Case 4: เพิ่มยาเองโดยเภสัชกร
Scenario: แพทย์ลืมสั่งยา เภสัชกรติดต่อแพทย์และได้รับอนุญาตให้เพิ่มยา
- เภสัชกรโทรหาแพทย์
- แพทย์อนุญาตให้เพิ่ม Omeprazole 20mg
- เภสัชกรคลิก "เพิ่มยา"
- ค้นหา "Omeprazole"
- เลือกยาที่ต้องการ
- กรอกข้อมูล:
- จำนวน: 30 เม็ด
- วิธีใช้: 1 เม็ด ก่อนอาหาร 30 นาที
- ความถี่: วันละ 1 ครั้ง
- ระยะเวลา: 30 วัน
- คลิก "เพิ่มเข้ารายการ"
- บันทึกหมายเหตุ: "เพิ่มยาตามคำสั่งปากเปล่าจากแพทย์"
- ดำเนินการจ่ายยาต่อ
หมายเหตุ: ต้องมีหลักฐานการสั่งเพิ่มเติมจากแพทย์ (โทรศัพท์/บันทึก)
สรุป
ระบบจ่ายยาผู้ป่วยนอก (OPD Dispensing) เป็นหัวใจหลักของระบบเภสัชกรรม ที่ต้องมีความรวดเร็ว แม่นยำ และปลอดภัย ด้วย Features ที่ครบครัน:
✅ Queue Management - จัดการคิวอย่างเป็นระบบ
✅ Safety Checks - ตรวจสอบปฏิกิริยายาและการแพ้
✅ Label Printing - พิมพ์ฉลากยาอัตโนมัติ
✅ Status Tracking - ติดตามสถานะแบบ Real-time
✅ Return Handling - รองรับการคืนยา
✅ History Tracking - บันทึกประวัติครบถ้วน
อัปเดตล่าสุด: 6 มกราคม 2026
เอกสารอ้างอิง: WORKFLOW_OVERVIEW.md