แผน Q4 — roots_hemms_signature (Final 1.0)¶
เวอร์ชันแผน: Final 1.0 — อนุมัติแล้ว, พร้อมดำเนินการ อนุมัติ: 2026-05-22 Module ID: roots_hemms_signature Layer: 3 (เลนส์โรงพยาบาล) — ห่อหุ้มโมดูล OCA Layer 2 sign_oca (จะ vendor ใน Q4.1) การ implement: Q4.1–Q4.3 ship พร้อมกัน; Q4.4 ถูกบล็อกจนกว่า Q3 เสร็จ; Q4.5–Q4.6 ตามหลัง Q4.4
0. บทสรุปผู้บริหาร¶
สิ่งที่เราสร้าง: Vendor OCA sign_oca และเพิ่ม wrapper roots_hemms_signature แบบบางที่เชื่อมระเบียน HEMMS สองตัวเข้ากับเอนจินการลงนาม:
hemms.asset.inspection— ประธานกรรมการ + กรรมการ 2 คน ลงนาม รายงาน ปจป. ประจำปีภายใน Odoo แทนการลงบนกระดาษhemms.service.contract— vendor ลงนามผ่านลิงก์อีเมล portal; ผู้มีอำนาจลงนามของโรงพยาบาลร่วมลงนามในแอป
wrapper เปิดเผย action_send_for_signature() บนทั้งสองโมเดล, แสดง ปุ่มอัจฉริยะ sign_request_count, แนบ PDF ที่เซ็นแล้วฉบับสมบูรณ์ไปยัง chatter ของระเบียนหลักเมื่อผู้ลงนามครบทุกคนเสร็จ, และ ship เทมเพลตสาธิตที่กำหนดค่าไว้ล่วงหน้าเพื่อให้การสาธิตพร้อมใช้ทันที
ทำไม: sign_oca เป็นงาน OCA แบบ Beta-แต่ใช้งานได้ และให้ ลายเซ็นอิเล็กทรอนิกส์เทียบเท่า ETDA-Level-1 (รูปภาพ + hash แบบลูกโซ่ + audit log) โดยไม่ต้องใช้โมดูล sign ของ Odoo Enterprise ที่ปิด source สองสะพานที่เลือก คือจุดสัมผัสที่กระดาษออฟไลน์เจ็บปวดที่สุด — คณะกรรมการ ปจป. ต้องการร่องรอยการตรวจสอบที่สะอาดสำหรับ HA Thailand, และการ ตามล่าลายเซ็นกระดาษจาก vendor บนสัญญาบริการเป็นการเสียเวลาที่รู้กันดี
ความพยายาม: 6 commit / ~1.5 เซสชันพัฒนา, เป้าหมายการทดสอบ 14 รายการ, เอกสาร 4 หน้า
ชัยชนะหลัก (จัดลำดับตามผลกระทบ):
- ⭐ vendor ลงนามสัญญาผ่านลิงก์อีเมล (Q4.4) — กำจัดการ ตามล่ากระดาษ; vendor รับอีเมล, ลงนามในเบราว์เซอร์, โรงพยาบาลเห็น PDF ที่เซ็นแล้วใน chatter
- ⭐ คณะกรรมการลงนาม ปจป. ในแอปพร้อมร่องรอยการตรวจสอบ (Q4.3) — แทนที่ วงจรพิมพ์-แล้วเซ็นบนกระดาษด้วยร่องรอยดิจิทัลแบบ hash ที่เหมาะกับการตรวจสอบของ HA Thailand
- ⭐ เทมเพลตสาธิตที่สร้างไว้ล่วงหน้า (Q4.3 + Q4.4) — ติดตั้งใหม่ให้ ลายเซ็นอิเล็กทรอนิกส์ใช้งานได้ตั้งแต่วันแรก; ไม่ต้องตั้งเทมเพลตสำหรับการสาธิต
License: LGPL-3 (wrapper) เหนือ dependency แบบ AGPL-3 (sign_oca) — เข้ากันได้ที่การแจกจ่าย รูปแบบเดียวกับ maintenance_stock (Q2.1) และ maintenance_partner (Q3)
สถานะของคำถามที่ยังเปิด: ทั้ง 5 ข้อแก้ไขแล้ว 2026-05-22 — OQ1 ลงนาม ออฟไลน์ด้วยมือสำหรับการลงนามภายใน (T1/T2/T6/T7 เลื่อนออกไป); OQ2 ไม่มีการ integrate workflow; OQ3 ไม่กั้น state บนสัญญา Q3; OQ4 ส่วนขยาย _check_signed; OQ5 ขอบเขตที่ทำให้ง่ายขึ้น (รายการ Q4 ที่เกี่ยวกับสัญญาย้ายไป Q5+) ดู §8 สำหรับคำตอบที่ล็อกแล้ว
1. วัตถุประสงค์¶
ทีม BME ของโรงพยาบาลและ vendor ภายนอกทุกวันนี้ลงนามระเบียนสำคัญ บนกระดาษ:
- รายงานการตรวจสอบ ปจป. ประจำปี — ประธาน + กรรมการ 2 คนลงนาม สำเนาพิมพ์ 3 ชุด (แฟ้มหลักฐาน HA Thailand)
- สัญญา Service / MA กับ vendor — การส่งกระดาษไป-กลับใช้เวลา 2–3 สัปดาห์ต่อสัญญา
Q4 นำลายเซ็นอิเล็กทรอนิกส์มาสู่จุดสัมผัสสองจุดนี้เท่านั้น การลงนาม HEMMS อื่น ๆ (การลงนาม stage-4 ของ BME Manager, การรับของเจ้าหน้าที่หอผู้ป่วย, รายงาน PM รายเดือน) ยังอยู่บนกระดาษสำหรับ V1 (ดู OQ1, §8)
ขอบเขต — ที่รวม¶
- Q4.1: Vendor OCA
sign_ocaใต้addons/oca/sign_oca - Q4.2: โครงโมดูล
roots_hemms_signature(manifest, security, data, demo scaffolding) — LGPL-3, depends onsign_oca+roots_hemms_asset_inspection - Q4.3: สะพานบน
hemms.asset.inspection: sign_request_ids(o2m ผ่าน domainrecord_ref)sign_request_count(computed, stored, sudo)roots_signed_pdf(Binary) +roots_signed_pdf_filename(Char)- เมธอด
action_send_for_signature() - ปุ่มอัจฉริยะ "📝 N Sign Requests" บนฟอร์ม
- ปรับ qweb signature block ใหม่: ถ้ามี
roots_signed_pdf, ฝัง รูปภาพที่เซ็นแล้ว; มิฉะนั้นคงกล่องว่างสไตล์กระดาษ (fallback) - Q4.4: สะพานบน
hemms.service.contract(depends on Q3 เสร็จ): - ฟิลด์ 4 ตัว + ปุ่ม + เมธอด เดียวกัน
- vendor ลงนามผ่านลิงก์อีเมล portal; ผู้ลงนามของโรงพยาบาลในแอป
- Q4.5: ส่วนขยาย
_check_signedบนsign.oca.request: - เมื่อ state เปลี่ยนเป็น
2_signedANDrecord_refเป็นหนึ่งใน สองโมเดลที่เราเชื่อม → เขียน PDF ที่เซ็นแล้วไปยังroots_signed_pdfroots_signed_pdf_filenameของหลัก, โพสต์ข้อความ chatter
- ไม่เลื่อน state บนหลัก (OQ3 + OQ2)
- ข้อมูลสาธิต:
- 3 ระเบียน sign.oca.role: Committee Chair, Committee Member, Hospital Signer, Vendor Signer
- เทมเพลตสาธิต 1 ตัวสำหรับการตรวจสอบ ปจป. (PDF + การวางฟิลด์สำหรับ 3 ลายเซ็น + chair signed_on + member signed_on × 2)
- เทมเพลตสาธิต 1 ตัวสำหรับสัญญาบริการ (PDF + ฟิลด์ลายเซ็น 2 ตัว)
- สองภาษา: ไฟล์ TH .po ครอบคลุมเฉพาะสตริงของ wrapper (การแปล TH ของ core sign_oca เป็นความรับผิดชอบของ OCA)
ขอบเขต — ที่ตัดออก (เลื่อนออกไป)¶
- ❌ การลงนามภายใน T1 / T2 / T6 / T7 (BME Manager / Ward / PM) — กระดาษสำหรับ V1 (OQ1) — Q5+
- ❌ การบังคับลำดับการลงนามแบบตามลำดับ (เช่น ประธานต้องลงนามสุดท้าย) — OQ2 — Q5+
- ❌ การกั้น state (สัญญา Q3 ต้องเซ็นก่อน active) — OQ3 — Q5+
- ❌ การแจ้งเตือนการต่ออายุหลายขั้น (90/60/30) — OQ5 — Q5
- ❌ การตรวจจับการละเมิด SLA — OQ5 — Q5
- ❌ scorecard ผลงาน vendor — OQ5 — Q5
- ❌ การกำหนดเส้นทาง auto-RFQ ของสัญญา spare-only — OQ5 — Q5
- ❌ เทมเพลตสัญญา + วิซาร์ดคัดลอก — OQ5 — Q5
- ❌ OTP / 2FA ตอนลงนาม — roadmap ของ sign_oca, ไม่ได้สร้าง — Q6+
- ❌ ลายเซ็นรับรอง PKI / ETDA Level 2 (pyHanko / endevise) — roadmap ของ sign_oca, ไม่ได้สร้าง — Q6+
- ❌ สะพานบน
maintenance.request— OQ1 — Q5+ - ❌ การกั้น workflow ตามความครอบคลุมสัญญา — OQ5 — Q5
2. การตัดสินใจด้านการออกแบบ (ล็อกแล้ว)¶
| # | การตัดสินใจ | ตัวเลือก |
|---|---|---|
| D1 | รูปแบบการสร้าง | โมดูล wrapper แบบบาง roots_hemms_signature ที่จำลองตาม source ของ agreement_sign_oca อย่างแม่นยำ ~150 LoC + 1 มุมมองวิซาร์ดต่อโมเดลที่เชื่อม อย่า ติดตั้ง OCA agreement_sign_oca หรือ project_task_sign_oca — มุ่งโมเดลอื่น |
| D2 | License | LGPL-3 (wrapper) เหนือ AGPL-3 (sign_oca) — รูปแบบเขตแดนเดียวกับ Q2.1 / Q3 |
| D3 | การลงนามภายใน (T1/T2/T6/T7) | กระดาษสำหรับ V1 ไม่มี UI e-sign บน maintenance.request (OQ1) บล็อกลายเซ็นแบบ static ใน PM workflow คงไว้; เจ้าหน้าที่หอผู้ป่วยลงนามบนกระดาษที่พิมพ์ |
| D4 | การจัดลำดับภายใน Q4 | Q4.1 → Q4.2 → Q4.3 → (รอ Q3) → Q4.4 → Q4.5 → Q4.6 Q4.3 ship และสาธิตได้ก่อน Q3 ลง |
| D5 | hook การเสร็จสิ้น | ส่วนขยาย _check_signed บน sign.oca.request (OQ4) เมื่อคำขอเปลี่ยนเป็น 2_signed, lookup record_ref; ถ้าเป็น hemms.asset.inspection หรือ hemms.service.contract, เขียน PDF สุดท้ายไปยัง roots_signed_pdf ของหลัก, โพสต์ chatter ไม่เลื่อน state (OQ3) |
| D6 | การจัดลำดับ workflow | ไม่มี (OQ2) ผู้ลงนามทุกคนในคำขอลงนามตามลำดับใดก็ได้ พฤติกรรม "all signed → final PDF" ของ sign_oca คงเป็นค่าเริ่มต้น |
| D7 | การกั้น state ของสัญญา Q3 | ไม่มี (OQ3) สัญญากลายเป็น state=active ได้โดยไม่มีคำขอที่เซ็นแล้ว การลงนามเป็นทางเลือก / ขนานกับวงจรชีวิต |
| D8 | เทมเพลตสาธิต | Ship สร้างไว้ล่วงหน้า แต่ละเทมเพลต = PDF ฐาน (แบบฟอร์ม ปจป. placeholder / สัญญา MA ทั่วไป) + การวางฟิลด์ที่ seed ผ่านข้อมูล XML ด้วย noupdate=1 Admin แก้ไขทีหลังได้ |
| D9 | รูปแบบฟิลด์สะพาน | สะท้อนชุดฟิลด์ agreement_sign_oca: sign_request_ids (o2m ผ่าน record_ref) + sign_request_count (Integer, stored, sudo computed ผ่าน read_group) + roots_signed_pdf (Binary) + roots_signed_pdf_filename (Char) ปุ่มอัจฉริยะแสดงเฉพาะเมื่อ count > 0 |
| D10 | การตั้งชื่อฟิลด์ | prefix roots_* บนฟิลด์ของโมเดลที่ inherit (roots_signed_pdf); ชื่อล้วนบนฟิลด์เชิงเทคนิคที่สะท้อนรูปแบบ OCA (sign_request_ids, sign_request_count — เหมือน agreement_sign_oca) ตรงกับ Q3 D9 |
| D11 | fallback ของ qweb | รายงาน ปจป. คงกล่องลายเซ็นว่างสไตล์กระดาษ เป็น fallback เมื่อ roots_signed_pdf ไม่ตั้ง เมื่อตั้งแล้ว ฝัง PDF ที่เซ็นแล้วผ่าน <img> (render เป็น PNG โดย _get_pdf_page_*) เพื่อให้สำเนาที่พิมพ์แสดงรูปลายเซ็นอิเล็กทรอนิกส์จริง |
| D12 | บทบาทที่ seed | 4 บทบาท (noupdate=1): role_committee_chair, role_committee_member, role_hospital_signer, role_vendor_signer ทั้งหมดมี partner_resolution='expression' เพื่อให้สะพานเติมอัตโนมัติจากระเบียนหลัก (เช่น {{object.chair_id.id}}) |
| D13 | Sequence | sign_oca มี ir.sequence ของตัวเองสำหรับ secure_sequence_number — เรา ไม่ เพิ่มอีกตัวขนานกัน reference เดิมของระเบียนหลัก (AI/2026/00001, SC/2026/00001) คือสิ่งที่คนเห็น |
3. โมเดลข้อมูล¶
ไม่มีโมเดลธุรกิจใหม่ มีสองสะพานเท่านั้น:
hemms.asset.inspection (INHERIT)
├─ sign_request_ids One2many ('sign.oca.request', compute via
│ record_ref domain, sudo)
├─ sign_request_count Integer (computed, stored, sudo)
├─ roots_signed_pdf Binary (final composited PDF after all signed)
└─ roots_signed_pdf_filename Char (e.g. "AI-2026-00001-signed.pdf")
+ method action_send_for_signature() → opens sign.oca.request form
+ method action_view_sign_requests() → list domain'd to this record
hemms.service.contract (INHERIT, Q4.4)
├─ sign_request_ids One2many (same shape)
├─ sign_request_count Integer
├─ roots_signed_pdf Binary
└─ roots_signed_pdf_filename Char
+ same two methods
sign.oca.request (INHERIT, Q4.5)
└─ _check_signed() extended:
super(); if state == '2_signed' and record_ref:
model, rid = record_ref._name, record_ref.id
if model in {'hemms.asset.inspection', 'hemms.service.contract'}:
record_ref.sudo().write({
'roots_signed_pdf': self.data,
'roots_signed_pdf_filename': f'{record_ref.display_name}-signed.pdf',
})
record_ref.message_post(
body=_("✅ Document signed by all parties — see attachment"),
attachments=[(filename, self.data)],
)
4. เลย์เอาต์โมดูล¶
addons/roots/roots_hemms_signature/
__init__.py
__manifest__.py # LGPL-3, depends on sign_oca + roots_hemms_asset_inspection + roots_hemms_service_contracts
README.rst
models/
__init__.py
hemms_asset_inspection.py # Q4.3 — bridge
hemms_service_contract.py # Q4.4 — bridge (blocked by Q3)
sign_oca_request.py # Q4.5 — _check_signed extension
views/
hemms_asset_inspection_views.xml # smart button + send-for-sig button
hemms_service_contract_views.xml # smart button + send-for-sig button
data/
sign_oca_role_data.xml # 4 roles, noupdate=1
demo/
sign_oca_template_inspection.xml # 1 template + field placements
sign_oca_template_contract.xml # 1 template + field placements
pdf/
inspection_template.pdf # placeholder ปจป. PDF (binary, base64-encoded in XML)
contract_template.pdf # placeholder MA contract PDF
report/
hemms_asset_inspection_report_override.xml # Q4.3 — qweb rewire (fallback empty boxes vs embed signed image)
security/
ir.model.access.csv # mostly inherited
tests/
__init__.py
test_inspection_bridge.py # Q4.6 — 5 tests
test_contract_bridge.py # Q4.6 — 5 tests
test_check_signed_hook.py # Q4.6 — 4 tests
i18n/
th.po # wrapper strings only
5. จุดสัมผัส UX¶
5.1 ฟอร์ม hemms.asset.inspection (Q4.3)¶
[ Header buttons ]
[ Send for Signature ] ← visible if state=='submitted' AND sign_request_count==0
[ Print Report ] ← existing
…
[ Statbox ]
[ N Equipment ] [ M Lines ] [ 📝 K Sign Requests ⭐ NEW ]
↑ visible if sign_request_count > 0
[ Form body ] …unchanged…
การคลิก "Send for Signature": 1. Render PDF ปจป. เดิมผ่านรายงาน qweb ปัจจุบัน 2. สร้าง sign.oca.request ด้วยผู้ลงนาม 3 คน (ประธาน + กรรมการ 2 คน) ที่ resolve จาก chair_id, committee_member_1_id, committee_member_2_id 3. เปิดฟอร์มคำขอใหม่
5.2 ฟอร์ม hemms.service.contract (Q4.4)¶
รูปแบบเดียวกัน แต่ผู้ลงนามคือ: partner_id (vendor — portal) + roots_hospital_signer_id (ผู้มีอำนาจของโรงพยาบาล — ผู้ใช้ Odoo ภายใน)
5.3 sign.oca.request เปิดจากสะพาน¶
UI มาตรฐานของ sign_oca — ไม่มีการปรับแต่ง ปุ่ม Configure document → ลากฟิลด์ลายเซ็นลงบนหน้า → Send → ผู้ลงนามคลิกไอคอนดินสอใน systray (ภายใน) หรือลิงก์อีเมล (portal)
5.4 รายงาน PDF ปจป. หลังลงนาม¶
เมื่อกรรมการครบทั้ง 3 คนลงนามแล้ว:
report/hemms_asset_inspection_report_override.xml (Q4.3)
<template id="report_hemms_asset_inspection" inherit_id="roots_hemms_asset_inspection.report_hemms_asset_inspection">
<xpath expr="//table[@class='signature-block']" position="replace">
<t t-if="doc.roots_signed_pdf">
<!-- embed signed PDF page image -->
<img t-att-src="'data:application/pdf;base64,%s' % doc.roots_signed_pdf.decode()"
style="width:100%; max-height: 200px;"/>
</t>
<t t-else="">
<!-- existing 3-box empty signature block (paper fallback) -->
<table class="signature-block"> … existing markup … </table>
</t>
</xpath>
</template>
6. ฟีเจอร์ UX ที่บันทึกไว้¶
| ฟีเจอร์ | จากการวิเคราะห์ | อยู่ในขั้นตอน Q4 ใด |
|---|---|---|
Vendor sign_oca core | OCA repo | Q4.1 |
| โครง wrapper | รูปแบบโครงการ | Q4.2 |
| seed 4 sign.oca.role (chair / member / hospital / vendor) | บัญชีบทบาท | Q4.2 |
| สะพาน inspection — 4 ฟิลด์ + 2 เมธอด | T3 | Q4.3 |
| ปุ่มอัจฉริยะ "📝 N Sign Requests" บน inspection | T3 | Q4.3 |
| เทมเพลตสาธิต — การตรวจสอบ ปจป. (PDF + 3 ลายเซ็น + 2 วันที่ signed_on) | OQ "เทมเพลตสาธิตสร้างไว้ล่วงหน้า" | Q4.3 |
| fallback qweb (กล่องว่าง ↔ PDF ที่เซ็นแล้วฝัง) | D11 | Q4.3 |
| สะพานสัญญา — 4 ฟิลด์ + 2 เมธอด | T4/T5 | Q4.4 |
| เทมเพลตสาธิต — สัญญา MA (PDF + 2 ลายเซ็น) | OQ "เทมเพลตสาธิตสร้างไว้ล่วงหน้า" | Q4.4 |
| การลงนาม portal สำหรับ vendor (ลิงก์อีเมล) | sign_oca + dep portal | Q4.4 |
hook _check_signed แนบ PDF สุดท้ายไปยัง chatter | OQ4 | Q4.5 |
| .po สองภาษา (TH) สำหรับสตริง wrapper | มาตรฐาน | Q4.6 |
| เอกสาร 4 หน้า (ภาพรวม / ตั้งค่า / คู่มือ inspection / คู่มือสัญญา) | มาตรฐาน | Q4.6 |
7. กรณีทดสอบ (14)¶
Unit (test_inspection_bridge.py) — 5 การทดสอบ¶
| # | การทดสอบ | ตรวจสอบ |
|---|---|---|
| U1 | test_sign_request_count_initially_zero | inspection ใหม่ → sign_request_count == 0, ปุ่มอัจฉริยะมองไม่เห็น |
| U2 | test_action_send_for_signature_creates_request | เรียกเมธอดบน inspection ที่ submit แล้ว → สร้าง sign.oca.request หนึ่งตัวที่ record_ref ชี้กลับ, ผู้ลงนาม 3 คน resolve จาก chair_id + committee_member_1_id + committee_member_2_id |
| U3 | test_action_send_blocked_when_inspection_draft | เรียกบน state=='draft' raise UserError("Submit the inspection first") |
| U4 | test_sign_request_count_reflects_active_only | สร้างสองคำขอ, ยกเลิกหนึ่ง → count == 1 (ตัวกรอง active) |
| U5 | test_action_send_for_signature_button_invisible_when_count_gt_zero | มีคำขออยู่ → ปุ่มส่วนหัว "Send for Signature" มองไม่เห็น (view attrs) |
Unit (test_contract_bridge.py) — 5 การทดสอบ¶
| # | การทดสอบ | ตรวจสอบ |
|---|---|---|
| U6 | test_contract_sign_request_count_initially_zero | สัญญาใหม่ → sign_request_count == 0 |
| U7 | test_contract_action_send_creates_request_with_vendor_and_signer | สัญญาที่มี partner_id (vendor, portal) + roots_hospital_signer_id → คำขอที่มีผู้ลงนาม 2 คน, role ของ vendor = role_vendor_signer, role ของผู้ลงนามโรงพยาบาล = role_hospital_signer |
| U8 | test_contract_action_send_requires_hospital_signer | roots_hospital_signer_id ว่าง → UserError("Set hospital authorized signer") |
| U9 | test_contract_action_send_requires_vendor_email | partner ของ vendor ไม่มีอีเมล → UserError("Vendor needs an email address for portal signing") |
| U10 | test_contract_state_NOT_gated_by_signed | สัญญาเปลี่ยน draft→active ได้แม้ sign_request_count==0 (ล็อก OQ3) |
Integration (test_check_signed_hook.py) — 4 การทดสอบ¶
| # | การทดสอบ | ตรวจสอบ |
|---|---|---|
| I1 | test_check_signed_attaches_pdf_to_inspection | สร้างคำขอบน inspection → ผู้ลงนามทั้ง 3 ลงนาม → roots_signed_pdf ของหลักถูกเติม, roots_signed_pdf_filename = AI-2026-00001-signed.pdf, โพสต์ข้อความ chatter พร้อมไฟล์แนบ |
| I2 | test_check_signed_attaches_pdf_to_contract | flow เดียวกันสำหรับสัญญา — vendor ลงนามผ่าน portal, โรงพยาบาลลงนามในแอป → PDF สุดท้ายไปลงที่ contract.roots_signed_pdf |
| I3 | test_check_signed_does_NOT_advance_parent_state | หลังลงนามครบ, inspection.state ไม่เปลี่ยน (ยังเป็น submitted); contract.state ไม่เปลี่ยน (ยังเป็นค่าเดิม) ตรงกับล็อก OQ3 + OQ2 |
| I4 | test_check_signed_ignores_unrelated_record_ref | คำขอที่สร้างด้วย record_ref ชี้ไปที่เช่น res.partner → การลงนามเสร็จปกติ, hook ของเราไม่ crash, ระเบียนหลักไม่ถูกแตะ |
Acceptance smoke (ทำด้วยมือ)¶
- DB ใหม่ → ติดตั้ง
roots_hemms_signature→ ยืนยันว่า 4 บทบาท + 2 เทมเพลตปรากฏใต้ Sign / Settings - เปิด inspection ที่ seed
AI/2026/00001(state=submitted) → ส่วนหัวแสดง "Send for Signature"; คลิก → ฟอร์ม sign.oca.request เปิดพร้อมผู้ลงนาม 3 คนเติมไว้ - คลิก "Configure Document" → ลากฟิลด์ลายเซ็น 3 ฟิลด์ลงบนตัวอย่าง PDF → Send
- ผู้ใช้กรรมการสามคนเห็นไอคอนดินสอใน systray → คลิก → canvas ลงนาม → submit
- หลังผู้ลงนามคนที่ 3 ลงนาม → chatter ของ inspection แสดง "✅ Document signed by all parties" + ไฟล์แนบ PDF ที่เซ็นแล้ว;
roots_signed_pdfถูกเติม; state ของ inspection ไม่เปลี่ยน (ยังเป็นsubmitted) - พิมพ์รายงาน ปจป. → signature block ตอนนี้แสดง PDF ที่เซ็นแล้วฝังไว้ (ไม่ใช่กล่องว่าง)
- เปิดสัญญาที่ seed
SC/2026/00001→ ส่วนหัวแสดง "Send for Signature"; คลิก → คำขอเปิดพร้อม vendor (portal) + ผู้ลงนามโรงพยาบาล (ภายใน) - log อีเมลของ vendor แสดงลิงก์ "You have a document to sign" → คลิก → หน้า portal → ลงนาม → submit
- ผู้ลงนามโรงพยาบาล (ผู้ใช้ Odoo ภายใน) → ดินสอใน systray → ลงนาม
- chatter ของสัญญาแสดง "✅ Document signed by all parties" + PDF ที่เซ็นแล้ว; state ของสัญญาไม่เปลี่ยน (OQ3)
- ยกเลิกคำขอกลางทาง →
sign_request_countลดลง;roots_signed_pdfของระเบียนหลักยังว่าง - ลอง "Send for Signature" สองครั้งติดกันบน inspection เดียวกัน → ครั้งที่สองสร้างคำขอที่สอง (ไม่มี constraint ความไม่ซ้ำ — ตัวเลือกการออกแบบ; กลับมาทบทวน Q5+)
8. คำถามที่ยังเปิด — แก้ไขแล้ว 2026-05-22¶
ทั้ง 5 ข้อล็อกตอนร่างแผน บันทึกการตัดสินใจไว้เพื่อเป็นหลักฐาน
| # | คำถาม | คำตอบ |
|---|---|---|
| OQ1 | พยาบาลหอผู้ป่วยลงนามเอง (ผู้ใช้ portal) หรือ BME Manager ลงนามแทนพร้อมบันทึก audit พยาน? | ลงนามออฟไลน์ด้วยมือสำหรับตอนนี้ — การลงนามภายใน (T1/T2/T6/T7) ยังอยู่บนกระดาษสำหรับ V1 เลื่อน e-sign สำหรับสิ่งเหล่านั้นไป Q5+ |
| OQ2 | การตรวจสอบ ปจป. — ลงนามตามลำดับ (ประธานสุดท้าย) หรือขนาน (ลำดับใดก็ได้)? | ไม่มีการ integrate workflow — อยู่นอกขอบเขต ผู้ลงนามทุกคนลงนามตามลำดับใดก็ได้ พฤติกรรมเริ่มต้นของ sign_oca |
| OQ3 | สัญญา Q3 state=active ควรต้องลงนามเสร็จก่อนหรือไม่? | ไม่ วงจรชีวิตสัญญาเป็นอิสระจาก e-sign การลงนามเป็นทางเลือก / ขนาน |
| OQ4 | จุด hook สำหรับ "all signed → attach PDF" — override action_sign หรือส่วนขยาย _check_signed? | ส่วนขยาย _check_signed — สะอาดกว่า, ไม่แตะตรรกะการเปลี่ยน state ของ core AGPL |
| OQ5 | Q4 อยู่ในขอบเขตเฉพาะลายเซ็นอิเล็กทรอนิกส์ หรือรวมรายการ Q4 ที่เกี่ยวกับสัญญาด้วย (การต่ออายุหลายขั้น, การละเมิด SLA, scorecard vendor)? | เฉพาะลายเซ็นอิเล็กทรอนิกส์ รายการที่เกี่ยวกับสัญญาย้ายไป Q5+ เพื่อเลี่ยงขอบเขตบานปลาย |
การชี้แจงรอง (เดิมพันต่ำกว่า, ล็อกได้ตอนนี้หรือใน code review):
- "Send for Signature" ควรซ่อนเมื่อมีคำขอที่ไม่ถูกยกเลิกอยู่แล้ว เพื่อป้องกันการซ้ำโดยไม่ตั้งใจหรือไม่? — แผน: แสดงเสมอ แต่ จัดลำดับใหม่ให้คำขอที่มีอยู่เห็นง่ายขึ้น constraint ความไม่ซ้ำ เป็นการขัดเกลา Q5+ (การทดสอบ U5 จับค่าเริ่มต้นแสดงเสมอ; กลับมาทบทวนหาก รบกวนในการสาธิต)
- PDF สาธิตควร ship เป็นเอกสารสไตล์ ปจป. / สไตล์ MA สองภาษาจริง หรือ placeholder ทั่วไป? — แผน: placeholder เพียงพอ สำหรับการสาธิต; PDF สไตล์จริงเป็นการขัดเกลาเมื่อรู้ว่า โรงพยาบาลนำร่องต้องการอะไร
- เราควรเพิ่ม hook วงจรชีวิต
ir.attachmentเพื่อให้ PDF ที่เซ็นแล้ว ปรากฏในแท็บ Documents ของหลัก (ไม่ใช่แค่ chatter) หรือไม่? — แผน: เฉพาะ chatter สำหรับ V1, แท็บ Documents เป็น Q5+
9. รายการตรวจสอบเพื่อทำงานต่อ¶
เมื่อเซสชันถัดไปเริ่ม:
- อ่านแผนนี้ (§2 การตัดสินใจ, §4 เลย์เอาต์โมดูล, §7 รายการทดสอบ)
- ตรวจสอบสถานะ Q3 — ถ้า Q3.x ยังดำเนินอยู่, เริ่มเฉพาะ Q4.1–Q4.3; หยุดก่อน Q4.4 จนกว่า Q3 จะ ship
- รัน
git status— ยืนยัน tree สะอาดจาก baseline Q3.1 - ดำเนินการ Q4.1:
chore: vendor sign_oca from OCA/sign 18.0 - clone OCA/sign tag
18.0ไปยัง temp dir - คัดลอก
sign_oca/เข้าaddons/oca/sign_oca/ - อัปเดตรายการ vendored Layer-2 ใน
docs/developer/architecture.md - commit
- Q4.2: โครงโมดูล + manifest + 4 sign.oca.role seed
- Q4.3: สะพาน inspection + override qweb + เทมเพลตสาธิต (1 PDF + XML การวางฟิลด์)
- หยุด — รอ Q3 เสร็จก่อน Q4.4
- หลัง Q3 ship: Q4.4 สะพานสัญญา + เทมเพลตสาธิต
- Q4.5: ส่วนขยาย
_check_signed - Q4.6: การทดสอบ + เอกสาร 4 หน้า (
docs/modules/signature/)
10. ลำดับ commit¶
| # | Commit | หมายเหตุ |
|---|---|---|
| Q4.1 | chore: vendor sign_oca from OCA/sign 18.0 | เพิ่ม addons/oca/sign_oca/ |
| Q4.2 | feat(sign): roots_hemms_signature skeleton + 4 role seeds | Manifest LGPL-3, depends on sign_oca + roots_hemms_asset_inspection |
| Q4.3 | feat(sign): bridge on hemms.asset.inspection + qweb signature-block rewire + demo template | T3 เสร็จ end-to-end |
| Q4.4 | feat(sign): bridge on hemms.service.contract + demo template | ถูกบล็อกจนกว่า Q3 เสร็จ T4/T5 เสร็จ end-to-end |
| Q4.5 | feat(sign): _check_signed extension attaches signed PDF to parent chatter | จุด hook ตาม OQ4 |
| Q4.6 | test(sign): 14 unit+integration tests + docs(sign): 4-page docs under docs/modules/signature/ | สรุปปิดงาน |
สถานะแผน: Final 1.0 — อนุมัติ 2026-05-22, พร้อมดำเนินการ Q4.1