ข้ามไปที่เนื้อหา

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: ผู้ป่วยมารับยาตามปกติ ไม่มีปัญหา

  1. เภสัชกรเลือกผู้ป่วย "สมชาย ใจดี" จากคิว
  2. ตรวจสอบรายการยา 3 รายการ
  3. ระบบตรวจสอบ Drug Interaction → ไม่พบปัญหา
  4. เภสัชกรเลือกยาทั้งหมด
  5. คลิก "พิมพ์ฉลากยา"
  6. จัดเตรียมยาตามฉลาก
  7. คลิก "ส่งจ่ายเงิน" (สถานะ → waiting_payment)
  8. ผู้ป่วยไปจ่ายเงินที่เคาน์เตอร์การเงิน
  9. การเงินยืนยันการชำระเงิน (สถานะ → ready_to_dispense)
  10. เภสัชกรเรียกผู้ป่วยมารับยา
  11. ให้คำแนะนำการใช้ยา
  12. คลิก "จ่ายยาให้ผู้ป่วย" (สถานะ → dispensed)

ระยะเวลา: ~5-10 นาที


Use Case 2: พบ Drug Interaction

Scenario: พบยามีปฏิกิริยาระหว่างกัน

  1. เภสัชกรเลือกผู้ป่วย
  2. ระบบแจ้งเตือน: "Major Interaction: Warfarin ⚠️ Aspirin"
  3. เภสัชกร:
  4. ทางเลือก 1: โทรติดต่อแพทย์เพื่อเปลี่ยนยา
  5. ทางเลือก 2: Override + บันทึกเหตุผล (ถ้ามีเหตุผลทางการแพทย์)
  6. แพทย์เปลี่ยนคำสั่งเป็น Clopidogrel
  7. ดำเนินการจ่ายยาตามปกติ

ระยะเวลา: ~10-15 นาที


Use Case 3: คืนยา

Scenario: ผู้ป่วยคืนยาเพราะแพ้

  1. ผู้ป่วยกลับมาพร้อมยาที่ยังไม่เปิด
  2. เภสัชกรเลือกใบสั่งยาที่จ่ายไปแล้ว
  3. ไปที่แท็บ "คืนยา"
  4. เลือกรายการยาที่จะคืน (เช่น Amoxicillin)
  5. เลือกเหตุผล: "ผู้ป่วยแพ้ยา/มีอาการไม่พึงประสงค์"
  6. ระบุรายละเอียด: "ผื่นขึ้นทั้งตัวหลังรับประทาน 30 นาที"
  7. คลิก "บันทึกการคืนยา"
  8. ระบบอัปเดตสต็อคยา
  9. บันทึกประวัติคืนยา

หมายเหตุ: ควรแจ้งแพทย์เพื่อบันทึกการแพ้ยาในประวัติผู้ป่วย


Use Case 4: เพิ่มยาเองโดยเภสัชกร

Scenario: แพทย์ลืมสั่งยา เภสัชกรติดต่อแพทย์และได้รับอนุญาตให้เพิ่มยา

  1. เภสัชกรโทรหาแพทย์
  2. แพทย์อนุญาตให้เพิ่ม Omeprazole 20mg
  3. เภสัชกรคลิก "เพิ่มยา"
  4. ค้นหา "Omeprazole"
  5. เลือกยาที่ต้องการ
  6. กรอกข้อมูล:
  7. จำนวน: 30 เม็ด
  8. วิธีใช้: 1 เม็ด ก่อนอาหาร 30 นาที
  9. ความถี่: วันละ 1 ครั้ง
  10. ระยะเวลา: 30 วัน
  11. คลิก "เพิ่มเข้ารายการ"
  12. บันทึกหมายเหตุ: "เพิ่มยาตามคำสั่งปากเปล่าจากแพทย์"
  13. ดำเนินการจ่ายยาต่อ

หมายเหตุ: ต้องมีหลักฐานการสั่งเพิ่มเติมจากแพทย์ (โทรศัพท์/บันทึก)


สรุป

ระบบจ่ายยาผู้ป่วยนอก (OPD Dispensing) เป็นหัวใจหลักของระบบเภสัชกรรม ที่ต้องมีความรวดเร็ว แม่นยำ และปลอดภัย ด้วย Features ที่ครบครัน:

Queue Management - จัดการคิวอย่างเป็นระบบ
Safety Checks - ตรวจสอบปฏิกิริยายาและการแพ้
Label Printing - พิมพ์ฉลากยาอัตโนมัติ
Status Tracking - ติดตามสถานะแบบ Real-time
Return Handling - รองรับการคืนยา
History Tracking - บันทึกประวัติครบถ้วน


อัปเดตล่าสุด: 6 มกราคม 2026
เอกสารอ้างอิง: WORKFLOW_OVERVIEW.md