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

แผน 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 สองตัวเข้ากับเอนจินการลงนาม:

  1. hemms.asset.inspection — ประธานกรรมการ + กรรมการ 2 คน ลงนาม รายงาน ปจป. ประจำปีภายใน Odoo แทนการลงบนกระดาษ
  2. 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 หน้า

ชัยชนะหลัก (จัดลำดับตามผลกระทบ):

  1. vendor ลงนามสัญญาผ่านลิงก์อีเมล (Q4.4) — กำจัดการ ตามล่ากระดาษ; vendor รับอีเมล, ลงนามในเบราว์เซอร์, โรงพยาบาลเห็น PDF ที่เซ็นแล้วใน chatter
  2. คณะกรรมการลงนาม ปจป. ในแอปพร้อมร่องรอยการตรวจสอบ (Q4.3) — แทนที่ วงจรพิมพ์-แล้วเซ็นบนกระดาษด้วยร่องรอยดิจิทัลแบบ hash ที่เหมาะกับการตรวจสอบของ HA Thailand
  3. เทมเพลตสาธิตที่สร้างไว้ล่วงหน้า (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 on sign_oca + roots_hemms_asset_inspection
  • Q4.3: สะพานบน hemms.asset.inspection:
  • sign_request_ids (o2m ผ่าน domain record_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_signed AND record_ref เป็นหนึ่งใน สองโมเดลที่เราเชื่อม → เขียน PDF ที่เซ็นแล้วไปยัง roots_signed_pdf
    • roots_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 (ทำด้วยมือ)

  1. DB ใหม่ → ติดตั้ง roots_hemms_signature → ยืนยันว่า 4 บทบาท + 2 เทมเพลตปรากฏใต้ Sign / Settings
  2. เปิด inspection ที่ seed AI/2026/00001 (state=submitted) → ส่วนหัวแสดง "Send for Signature"; คลิก → ฟอร์ม sign.oca.request เปิดพร้อมผู้ลงนาม 3 คนเติมไว้
  3. คลิก "Configure Document" → ลากฟิลด์ลายเซ็น 3 ฟิลด์ลงบนตัวอย่าง PDF → Send
  4. ผู้ใช้กรรมการสามคนเห็นไอคอนดินสอใน systray → คลิก → canvas ลงนาม → submit
  5. หลังผู้ลงนามคนที่ 3 ลงนาม → chatter ของ inspection แสดง "✅ Document signed by all parties" + ไฟล์แนบ PDF ที่เซ็นแล้ว; roots_signed_pdf ถูกเติม; state ของ inspection ไม่เปลี่ยน (ยังเป็น submitted)
  6. พิมพ์รายงาน ปจป. → signature block ตอนนี้แสดง PDF ที่เซ็นแล้วฝังไว้ (ไม่ใช่กล่องว่าง)
  7. เปิดสัญญาที่ seed SC/2026/00001 → ส่วนหัวแสดง "Send for Signature"; คลิก → คำขอเปิดพร้อม vendor (portal) + ผู้ลงนามโรงพยาบาล (ภายใน)
  8. log อีเมลของ vendor แสดงลิงก์ "You have a document to sign" → คลิก → หน้า portal → ลงนาม → submit
  9. ผู้ลงนามโรงพยาบาล (ผู้ใช้ Odoo ภายใน) → ดินสอใน systray → ลงนาม
  10. chatter ของสัญญาแสดง "✅ Document signed by all parties" + PDF ที่เซ็นแล้ว; state ของสัญญาไม่เปลี่ยน (OQ3)
  11. ยกเลิกคำขอกลางทาง → sign_request_count ลดลง; roots_signed_pdf ของระเบียนหลักยังว่าง
  12. ลอง "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. รายการตรวจสอบเพื่อทำงานต่อ

เมื่อเซสชันถัดไปเริ่ม:

  1. อ่านแผนนี้ (§2 การตัดสินใจ, §4 เลย์เอาต์โมดูล, §7 รายการทดสอบ)
  2. ตรวจสอบสถานะ Q3 — ถ้า Q3.x ยังดำเนินอยู่, เริ่มเฉพาะ Q4.1–Q4.3; หยุดก่อน Q4.4 จนกว่า Q3 จะ ship
  3. รัน git status — ยืนยัน tree สะอาดจาก baseline Q3.1
  4. ดำเนินการ Q4.1: chore: vendor sign_oca from OCA/sign 18.0
  5. clone OCA/sign tag 18.0 ไปยัง temp dir
  6. คัดลอก sign_oca/ เข้า addons/oca/sign_oca/
  7. อัปเดตรายการ vendored Layer-2 ใน docs/developer/architecture.md
  8. commit
  9. Q4.2: โครงโมดูล + manifest + 4 sign.oca.role seed
  10. Q4.3: สะพาน inspection + override qweb + เทมเพลตสาธิต (1 PDF + XML การวางฟิลด์)
  11. หยุด — รอ Q3 เสร็จก่อน Q4.4
  12. หลัง Q3 ship: Q4.4 สะพานสัญญา + เทมเพลตสาธิต
  13. Q4.5: ส่วนขยาย _check_signed
  14. 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