แผน Q2 — roots_hemms_spare_parts (Final 1.0)¶
เวอร์ชันแผน: Final 1.1 — Q2.1–Q2.8 commit แล้ว 2026-05-22 อนุมัติ: 2026-05-22 Module ID: roots_hemms_spare_parts Layer: 3 (เลนส์โรงพยาบาล) — ห่อหุ้มโมดูล OCA Layer 2 maintenance_stock สถานะ: Q2.1–Q2.8 ✅ commit แล้ว | Q2.9–Q2.11 ค้างอยู่
0. บทสรุปผู้บริหาร¶
สิ่งที่เราสร้าง: โมดูลอะไหล่สไตล์โรงพยาบาลบนฐานของ OCA maintenance_stock ซึ่ง (a) ให้ maintenance.equipment แต่ละตัวมี เทมเพลตอะไหล่ที่กรองได้, (b) สร้างร่าง stock.picking อัตโนมัติ เมื่อ PM cron สร้างคำขอ, และ © ให้ช่างเทคนิคมีมุมมองเฉพาะ "My Open Requests" เพื่อติดตาม picking ที่ค้างอยู่ของตน พร้อม เลข Stock Request แบบรันนิ่ง
ทำไม: OCA maintenance_stock ให้ลิงก์ picking↔request แต่ จงใจเว้นช่องว่างสามจุดที่สำคัญต่อ UX ของโรงพยาบาล — ไม่มีเทมเพลตการใช้สิ้นเปลือง, ไม่มีการสร้าง picking อัตโนมัติ, ไม่มี การติดตามแบบงานซ่อมบำรุง เราเติมเต็มทั้งสามจุดที่ Layer 3
ความพยายาม: 11 commit / ~3 เซสชันพัฒนา, เป้าหมายการทดสอบ 26 รายการ, เอกสาร 5 หน้า
ชัยชนะหลัก (จัดลำดับตามผลกระทบ):
- ⭐ สร้างร่าง picking อัตโนมัติบน PM cron (Q2.6) — ช่างคลิก Validate ไม่ใช่ New
- ⭐ มุมมอง My Open Requests (Q2.8) — ติดตามแบบบริการตนเองด้วย Running No
SR/2026/00001 - ⭐ ตัวกรองชนิด PM บนเทมเพลต (Q2.4) — เฉพาะอะไหล่ที่เกี่ยวข้องตามชนิดงานซ่อมบำรุง
License: LGPL-3 เหนือ dependency แบบ AGPL-3 — เข้ากันได้ที่การแจกจ่าย
สถานะของคำถามที่ยังเปิด: ไม่มีเหลือ (ล็อกทั้งหมดใน §2)
1. วัตถุประสงค์¶
ช่างเทคนิค BME ของโรงพยาบาลจำเป็นต้องรู้ว่า เครื่องมือแพทย์ชิ้นหนึ่ง ๆ อาจต้องใช้อะไหล่ใดระหว่างการซ่อม/PM — แล้วติดตามแต่ละคำขอจนเสร็จสิ้น — โดยไม่ต้องท่องจำ SKU หรือถามวิศวกรอาวุโส โมดูลนี้ มอบเทมเพลตอะไหล่ที่กำหนดค่าได้ให้ข้อมูลหลักของเครื่องมือแพทย์ ซึ่ง ไหลเข้าสู่ร่าง picking โดยอัตโนมัติเมื่อเปิดคำขอซ่อมบำรุง พร้อม มุมมองติดตามเฉพาะเพื่อติดตามผล
ขอบเขต — ที่รวม¶
- Vendor OCA
maintenance_stock(ข้อกำหนดเบื้องต้น Layer 2) - โมเดลใหม่
hemms.equipment.spare.line(o2m บนmaintenance.equipment) roots_is_critical_spare+roots_name_thบนproduct.templatename_getสองภาษาเพื่อให้ค้นหาชื่อทั้ง EN และ TH ได้- qty_available แบบ inline + ป้ายระดับวิกฤตในมุมมองเทมเพลต
- แพร่ระดับความวิกฤตของเครื่องมือแพทย์ (สีแดง) → line
roots_is_criticalโดยอัตโนมัติ - กิจกรรมแจ้งสต็อกต่ำสำหรับอะไหล่วิกฤต
- สร้างร่าง picking อัตโนมัติ บน PM cron, กรองตามชนิดของคำขอ
- ร่าง picking ยังคงแก้ไขได้เต็มที่ (เพิ่ม / ลบ / เปลี่ยนจำนวน) จนกว่าจะ validate — ยืนยันในพฤติกรรม Q2.6
- ปุ่มสถิติสรุปอะไหล่บนฟอร์มเครื่องมือแพทย์
- เลขรันนิ่ง Stock Request
roots_request_noผ่านir.sequenceรหัสhemms.stock.request(prefix กำหนดค่าได้ผ่าน UI, ค่าเริ่มต้นSR/%(year)s/padding 5 →SR/2026/00001) - มุมมองติดตาม Stock Request (List / Form / Kanban) พร้อมฟิลด์คำนวณ เปิด/ค้าง — หน้าเริ่มต้น = "My Open Requests"
- ปุ่มสถิติ "Pending Items" บนฟอร์ม
maintenance.request
ขอบเขต — ที่ตัดออก (เลื่อนออกไป)¶
- ❌ ปักหมุด "Used last time" (F4) — Q3
- ❌ ฝัง move-line แบบหน้าเดียวในฟอร์มคำขอ (S5) — Q3
- ❌ วิซาร์ด "Copy from similar equipment" (S6) — Q3
- ❌ ตัวป้องกันความเข้ากันได้ผ่าน product brand m2m (S4) — Q3
- ❌ จองสต็อกอัตโนมัติเมื่อเปลี่ยน stage (A2) — Q3
- ❌ คืน picking อัตโนมัติเมื่อยกเลิกคำขอ (A3) — Q3
- ❌ ติดตามที่มาของ line "manual vs template" — Q3 analytics
- ❌ เรียนรู้จำนวนอัตโนมัติ (A5), แจ้งเตือนต้นทุนแผนก (A7) — Q4+
- ❌ การกำหนดเส้นทางหลายคลัง, ระบบอัตโนมัติ PO
2. การตัดสินใจด้านการออกแบบ (ล็อกแล้ว)¶
| # | การตัดสินใจ | ตัวเลือก |
|---|---|---|
| D1 | เทมเพลตบนเครื่องมือแพทย์ vs หมวดหมู่ | เครื่องมือแพทย์ (คัดลอกหมวดหมู่ = วิซาร์ด Q3) |
| D2 | ค่าเริ่มต้นการสร้าง picking อัตโนมัติ | เลือกเข้าร่วมต่อเครื่องมือแพทย์, ค่าเริ่มต้น True เมื่อ allow_consumptions=True |
| D3 | การอนุมัติเมื่อจำนวนเกินค่าเริ่มต้นใน Q2 | ไม่ — กลับมาทบทวนใน Q3 หลังได้ feedback จากการสาธิต |
| D4 | การตั้งชื่อฟิลด์ | prefix roots_* บนโมเดลที่ inherit; ชื่อล้วนบนโมเดลใหม่ hemms.* |
| D5 | License | LGPL-3 (roots) เหนือ AGPL-3 (maintenance_stock) |
| D6 | รูปแบบ sequence ของ Stock Request | SR/%(year)s/00001 (อิงปี, padding 5) — admin แก้ไขได้ผ่าน Settings, ไม่ฮาร์ดโค้ด ใน Python; อยู่ในข้อมูล ir.sequence |
| D7 | หน้าเริ่มต้นสำหรับเมนูติดตาม | My Open Requests (state ∉ done/cancel AND user_id=uid OR create_uid=uid) |
| D8 | ความสามารถในการแก้ไขร่าง picking | แก้ไขได้เต็มที่ (เพิ่ม / ลบ / เปลี่ยนจำนวน) จนกว่าจะ Validate — อาศัยพฤติกรรม stock เริ่มต้นของ Odoo |
3. โมเดลข้อมูล¶
maintenance.equipment (inherit)
├─ roots_auto_generate_picking (Boolean, default by allow_consumptions)
├─ roots_spare_line_ids (o2m → hemms.equipment.spare.line)
└─ roots_spare_line_count (Integer, computed)
hemms.equipment.spare.line (NEW)
├─ equipment_id (m2o → maintenance.equipment, ondelete=cascade)
├─ product_id (m2o → product.product, ondelete=restrict)
├─ default_qty (Float, default 1.0)
├─ kind_ids (m2m → maintenance.kind) ⭐ filter axis
├─ position_note (Char)
├─ roots_is_critical (Boolean, computed+stored, writable)
├─ qty_available (Float, related, readonly)
└─ sequence (Integer)
product.template (inherit)
├─ roots_is_critical_spare (Boolean)
└─ roots_name_th (Char)
stock.picking (inherit — Stock Request tracking)
├─ roots_request_no (Char, readonly, copy=False, index=True, tracking=True)
├─ roots_is_open (Boolean, computed+stored)
├─ roots_pending_line_count (Integer, computed)
├─ roots_qty_progress (Float, computed) sum(quantity)/sum(uom_qty)*100
├─ roots_pending_qty_total (Float, computed)
└─ roots_scheduled_overdue (Boolean, computed)
ir.sequence (data record, NOT hardcoded)
├─ code : "hemms.stock.request"
├─ name : "HEMMS Stock Request"
├─ prefix : "SR/%(year)s/"
├─ padding: 5
└─ company_id: False
4. แผนการพัฒนา (11 ขั้นตอน)¶
| # | ขั้นตอน | Commit prefix | ผลลัพธ์สำคัญ |
|---|---|---|---|
| Q2.1 ✅ | Vendor OCA maintenance_stock 18.0 → addons/oca/ | 3650615 | ติดตั้งได้สะอาด |
| Q2.2 ✅ | โครงโมดูล (manifest + โมเดลว่าง + security) | 8332871 | ติดตั้งได้; ยังไม่มีฟีเจอร์ |
| Q2.3 ✅ | roots_is_critical_spare + roots_name_th บน product.template + _compute_display_name สองภาษา + _name_search EN/TH | ca55074 | "แบตเตอรี่" จับคู่กับ "Battery" ได้ |
| Q2.4 ✅ | โมเดลใหม่ hemms.equipment.spare.line + o2m บนเครื่องมือแพทย์ + มุมมอง inline (qty_available, ป้ายวิกฤต, แท็กชนิด) | 5910394 | Admin ตั้งเทมเพลต; ช่างเห็นรายการที่กรองตามชนิด |
| Q2.5 ✅ | ir.cron สำหรับกิจกรรมแจ้งสต็อกต่ำของอะไหล่วิกฤตรายวัน | f1340c3 | ป้ายวิกฤต + flow กิจกรรม |
| Q2.6 ✅ | override maintenance.request.create() → สร้างร่าง stock.picking อัตโนมัติจากเทมเพลตที่กรองตาม maintenance_kind_id | ad0805d | คำขอ PM เปิดขึ้นพร้อมร่าง picking ที่กรอกไว้ล่วงหน้า; line ยังแก้ไขได้ |
| Q2.7 ✅ | ปุ่มสถิติสรุปอะไหล่บนฟอร์มเครื่องมือแพทย์ ("N Spare Parts · N Critical Low") | c2dc019 | เจาะดูจากเครื่องมือแพทย์ได้ในคลิกเดียว |
| Q2.8 ✅ ⭐ | การติดตาม Stock Request — ir.sequence + roots_request_no + ฟิลด์ค้างที่คำนวณ + มุมมอง List/Kanban + ตัวกรองค้นหา + ปุ่มสถิติ "Pending SR" บนฟอร์มคำขอ + เมนู | 3f36d84 | ช่างเปิดเมนู "Stock Requests", เห็น SR/2026/00001, จำนวนค้างต่อ picking |
| Q2.9 | การทดสอบ (9 unit + 17 integration = 26) | test(spare): add unit + integration tests [Q2.9] | CI เขียว |
| Q2.10 | เอกสารโมดูล (5 หน้า) ใต้ docs/modules/spare-parts/ | docs(spare): add module documentation [Q2.10] | mkdocs serve สะอาด |
| Q2.11 | ข้อมูลสาธิต — เครื่องช่วยหายใจ/MRI/เครื่องกระตุกหัวใจ พร้อมเทมเพลตอะไหล่ที่สมจริง | chore(spare): demo data for 3 critical equipment classes [Q2.11] | smoke test ผ่านบน DB ใหม่ |
Dependencies: maintenance_stock (OCA — ต้อง vendor), roots_hemms_pm, roots_hemms_asset_master, roots_hemms_criticality, stock, mail
5. ข้อกำหนดมุมมอง (รายละเอียด Q2.8)¶
5.1 โครงสร้างเมนู¶
Maintenance
└─ Operations
└─ Stock Requests (ใบเบิก)
├─ My Open Requests ← default landing (D7)
├─ All My Requests
├─ Team Open
└─ Backorders
└─ Configuration
└─ Stock Request Sequence ← ir.sequence shortcut
5.2 มุมมอง List (ค่าเริ่มต้น = My Open Requests)¶
| คอลัมน์ | แหล่งที่มา |
|---|---|
| Request No. | roots_request_no |
| Equipment | equipment_id (พร้อมรูปภาพ) |
| For Request | maintenance_request_id |
| State | state (ป้าย) |
| Progress | roots_qty_progress (widget แถบความคืบหน้า) |
| Pending Items | roots_pending_line_count |
| Scheduled | scheduled_date (สีแดงถ้าเกินกำหนด) |
- ตัวกรองเริ่มต้น: state ∉ done/cancel AND ของฉัน
- จัดกลุ่มเริ่มต้นตาม: State
- การตกแต่ง: แถวเป็นสีแดงเมื่อ
roots_scheduled_overdue=True
5.3 มุมมอง Form — แท็บ notebook "Pending Items"¶
| คอลัมน์ | แหล่งที่มา |
|---|---|
| Product | move.product_id |
| Ordered | move.product_uom_qty |
| Received | move.quantity |
| Pending | product_uom_qty - quantity |
| Available | product.qty_available (ป้ายแดงถ้าเป็น 0) |
| Critical | product.roots_is_critical_spare |
- ตัวกรอง: แสดงเฉพาะ move ที่
state ∉ (done, cancel) - ส่วนหัวแสดง
roots_request_noอย่างเด่นชัด + ป้าย state + แถบความคืบหน้า
5.4 มุมมอง Kanban¶
- คอลัมน์ตาม
state: Draft / Waiting / Ready / Partial / Done (ซ่อนคอลัมน์ Partial โดยค่าเริ่มต้น) - การ์ด: ภาพย่อเครื่องมือแพทย์, request no, จำนวนรายการ, แถบความคืบหน้า, ป้ายเกินกำหนด
5.5 ตัวกรองค้นหา¶
- ☑ Mine (
user_id=uid OR create_uid=uid) - ☑ Open Only (
state ∉ done, cancel) - ☐ Overdue (
scheduled_date < today AND roots_is_open) - ☐ Backorders (
backorder_id != False) - ☐ Has Critical Spare (
mapped("move_ids.product_id.roots_is_critical_spare")มี True) - ☐ Scheduled This Week
- จัดกลุ่มตาม: State / Equipment / Kind / Scheduled Date
5.6 ปุ่มสถิติ "Pending Items" บน maintenance.request¶
เพิ่มปุ่มสถิติที่ 4 ข้าง 3 ปุ่มของ OCA (Picking list / Stock Moves / Product Moves):
เปิดรายการของ stock.move ที่ picking_id.maintenance_request_id=request.id AND state ∉ done, cancel
6. ฟีเจอร์ UX ที่บันทึกไว้¶
| ฟีเจอร์ | จากการวิเคราะห์ | อยู่ในขั้นตอน Q2 ใด |
|---|---|---|
| เทมเพลตอะไหล่ของเครื่องมือแพทย์ (โมเดลข้อมูล A3) | ข้อกำหนดหลัก | Q2.4 |
| เติม picking อัตโนมัติจากเทมเพลต (F1) | จุดติดขัด | Q2.6 |
| กรองล่วงหน้าตามชนิด PM (F2) | จุดติดขัด | Q2.4 + Q2.6 |
| qty_available inline + ป้ายวิกฤต (F3) | จุดติดขัด | มุมมอง Q2.4 |
| ค้นหาสองภาษา (F5) | จุดติดขัด | Q2.3 |
| widget สรุปอะไหล่ (S1) | ทำให้ง่ายขึ้น | Q2.7 |
| สลับซ่อนของหมดสต็อก (S3) | ทำให้ง่ายขึ้น | มุมมอง Q2.4 |
| สร้างร่าง picking อัตโนมัติบน PM cron (A1) | ทำอัตโนมัติ ⭐ | Q2.6 |
| กิจกรรมแจ้งสต็อกต่ำ (A4) | ทำอัตโนมัติ | Q2.5 |
| แพร่ความวิกฤตอัตโนมัติ (A6) | ทำอัตโนมัติ | Q2.5 |
| เลขรันนิ่ง Stock Request (ใหม่) | การสืบย้อน UX | Q2.8 |
| มุมมอง My Open Requests (ใหม่) | บริการตนเอง UX | Q2.8 |
| Pending Items ต่อ picking (ใหม่) | การมองเห็น UX | Q2.8 |
| ร่าง picking ที่แก้ไขได้ (ยืนยันแล้ว) | ความเป็นจริงของงานภาคสนาม | Q2.6 (พฤติกรรม Odoo เริ่มต้น, ล็อกด้วยการทดสอบ) |
7. กรณีทดสอบ (26)¶
Unit (test_spare_unit.py) — 9 การทดสอบ¶
| # | การทดสอบ | ตรวจสอบ |
|---|---|---|
| U1 | test_critical_spare_default_false | ค่าเริ่มต้นของ flag = False |
| U2 | test_bilingual_name_get | name_get() คืนค่า "EN / TH" เมื่อตั้ง roots_name_th |
| U3 | test_bilingual_search_by_thai | name_search("แบตเตอรี่") พบ product ที่ roots_name_th="แบตเตอรี่" |
| U4 | test_spare_line_default_qty | line ใหม่มี qty เริ่มต้น = 1.0 |
| U5 | test_spare_line_critical_propagation | ความวิกฤตเครื่องมือแพทย์=สีแดง → line roots_is_critical=True |
| U6 | test_auto_generate_flag_follows_allow_consumptions | allow_consumptions=True → roots_auto_generate_picking=True |
| U7 | test_request_no_assigned_on_create_with_maintenance_link | picking ที่มี maintenance_request_id → roots_request_no ถูกเติม |
| U8 | test_request_no_skipped_for_non_maintenance_picking | picking ที่ไม่มีลิงก์ซ่อมบำรุง → roots_request_no ว่าง |
| U9 | test_request_no_uses_ir_sequence_code | รหัส sequence = hemms.stock.request มีอยู่; prefix ไม่ฮาร์ดโค้ดใน Python |
Integration (test_spare_integration.py) — 17 การทดสอบ¶
| # | การทดสอบ | ตรวจสอบ |
|---|---|---|
| I1 | test_add_spare_line_to_equipment | Admin เพิ่ม product + qty + kind ได้ |
| I2 | test_template_filtered_by_kind_on_request_view | การเปิดคำขอชนิด=Calibration แสดงเฉพาะ line ที่แท็กชนิดนั้น |
| I3 | test_pm_cron_creates_draft_picking_from_template | PM cron → คำขอ + ร่าง picking จากเทมเพลต |
| I4 | test_pm_cron_picking_lines_filtered_by_kind | การสร้าง picking อัตโนมัติรวมเฉพาะ line เทมเพลตที่ตรงกับชนิด |
| I5 | test_validating_picking_reduces_qty_available | validate picking → qty_available ลดลง |
| I6 | test_critical_spare_below_min_creates_activity | อะไหล่วิกฤต < ขั้นต่ำ → กิจกรรมสำหรับ BME Manager |
| I7 | test_non_critical_spare_below_min_no_activity | ไม่วิกฤต → ไม่มีกิจกรรม |
| I8 | test_equipment_summary_count_excludes_archived_products | จำนวนปุ่มสถิติไม่นับที่ archive แล้ว |
| I9 | test_corrective_request_does_not_auto_generate | คำขอที่ไม่ใช่ PM → ไม่สร้าง picking อัตโนมัติ |
| I10 | test_auto_generate_disabled_skips_picking | roots_auto_generate_picking=False → ไม่มีร่าง |
| I11 | test_technician_can_add_non_template_line_to_draft_picking | เพิ่ม line นอกเทมเพลต, validate สำเร็จ |
| I12 | test_technician_can_remove_template_line_from_draft_picking | ลบ line เทมเพลต, validate สำเร็จ |
| I13 | test_technician_can_change_qty_on_template_line | qty 1→2, qty_available ลด 2 เมื่อ validate |
| I14 | test_pending_line_count_excludes_done_moves | จำนวนค้างไม่รวม move ที่ done/cancel |
| I15 | test_qty_progress_computes_correctly_on_partial_validation | validate บางส่วน → progress=50%, state=partial |
| I16 | test_open_filter_excludes_done_and_cancel | ตัวกรอง "Open Only" ไม่รวม done + cancel |
| I17 | test_my_filter_includes_responsible_and_creator | ตัวกรอง "Mine" รวมทั้ง user_id และ create_uid |
Acceptance smoke (ทำด้วยมือ)¶
- ตั้งค่าเครื่องช่วยหายใจ → ความวิกฤต=สีแดง →
allow_consumptions=True→ เพิ่ม 3 line เทมเพลต (Filter ×1 kind=Filter Replacement, Battery ×1 kind=Battery, Sensor ×1 kind=Calibration) - แนบ
maintenance.plankind=Filter Replacement, interval=3 เดือน - รัน PM cron ด้วยมือ
- ตรวจสอบ: คำขออยู่ใน stage PM Scheduled; ร่าง picking แนบมาพร้อม
roots_request_no=SR/2026/00001มี เฉพาะ line Filter เท่านั้น - เปิดเมนู "My Open Requests" → เห็น SR/2026/00001 พร้อม progress 0%
- เปิด picking → แท็บ "Pending Items" → เห็น line Filter ที่ Pending=1
- เพิ่ม line Battery ที่ไม่ได้กำหนด → บันทึก → Pending=2
- Validate → state=done → แถวหายไปจาก "My Open Requests"
- ถ้าจำนวน Filter ต่ำกว่าขั้นต่ำ → กิจกรรมไปยัง BME Manager
8. คำถามที่ยังเปิด¶
ไม่มี ชื่อโมดูล, ขอบเขต, โมเดลข้อมูล, การตัดสินใจด้านการออกแบบ (D1–D8), รูปแบบ sequence, และมุมมองหน้าเริ่มต้น ยืนยันทั้งหมดเมื่อ 2026-05-22
9. รายการตรวจสอบเพื่อทำงานต่อ¶
เสร็จแล้ว (ณ 2026-05-22): Q2.1–Q2.8 commit ทั้งหมดบน main
เซสชันถัดไปเริ่มที่ Q2.9:
- อ่านไฟล์นี้ (v1.1)
- รันการทดสอบจาก §7 — implement
tests/test_spare_unit.py+tests/test_spare_integration.py - commit เป็น
test(spare): add unit + integration tests [Q2.9] - ดำเนินการต่อไปยัง Q2.10 (เอกสาร) และ Q2.11 (ข้อมูลสาธิต) เพื่อปิด Q2