การพัฒนาโมดูล¶
หน้านี้เป็นคู่มือเชิงปฏิบัติสำหรับการสร้าง โมดูลใหม่ ที่ ขยาย HEMMS — ไม่ว่าจะเป็นการร่วมพัฒนาให้ชุมชนใน roots_hemms_* หรือส่วนขยายส่วนตัวของลูกค้าที่อยู่ในรีโพของคุณเอง โดยถือว่า คุณได้อ่าน สถาปัตยกรรม แล้วและเข้าใจ โมเดลสามชั้น
กฎ 3 ชั้น (สิ่งสำคัญที่สุดในหน้านี้)¶
โค้ดของคุณอยู่ใน Layer 3 ไม่ใช่ Layer 1 (Odoo base) ไม่ใช่ Layer 2 (OCA building blocks)
| ชั้น | สิ่งที่อยู่ที่นั่น | เมื่อใดที่คุณแตะต้องมัน |
|---|---|---|
| Layer 1 — Odoo base | maintenance.request, res.partner, hr.employee, … | ไม่แตะโดยตรงเด็ดขาด ให้ _inherit จาก Layer 3 |
Layer 2 — OCA-vendored (addons/oca/) | maintenance_plan, maintenance_stock, sign_oca, … | ไม่แตะโดยตรงเด็ดขาด ตรึงเวอร์ชันเมื่อ vendor แก้บั๊กที่ upstream ที่ OCA |
Layer 3 — roots_hemms_* (addons/roots/) | การกำกับดูแล การปฏิบัติตามข้อกำหนด ขั้นตอนการทำงานของไทยที่เฉพาะโรงพยาบาล | นี่คือที่ที่โค้ดของคุณอยู่ |
ทำไม? เพราะ Layer 1 และ Layer 2 เป็น upstream ที่คุณไม่ได้เป็นเจ้าของ ทุก การเปลี่ยนแปลงที่คุณทำที่นั่นคือการเปลี่ยนแปลงที่คุณต้องดูแลไปตลอดทุก ครั้งที่ upstream เหล่านั้นปล่อยเวอร์ชัน Layer 3 เป็นของคุณ — ของ Trinity Roots หรือ ของลูกค้าคุณ — และผ่านการอัปเกรดได้อย่างสะอาด
โมดูลใหม่อยู่ที่ไหน¶
addons/
├── oca/ # vendored OCA — don't put your code here
└── roots/
├── roots_hemms_workflow/
├── roots_hemms_criticality/
├── ...
└── roots_<your_module>/ # ← your code goes here
ชื่อโฟลเดอร์ คือ ชื่อทางเทคนิค (__manifest__.py ถูก อ่านตามชื่อไดเรกทอรี) สำหรับการร่วมพัฒนา HEMMS ให้ใช้คำนำหน้า roots_hemms_ สำหรับส่วนขยายส่วนตัวของลูกค้า ให้ใช้ roots_<customer>_ เพื่อให้เห็นได้ชัดในแวบเดียวว่าโมดูลนี้ไม่ได้เป็นส่วนหนึ่ง ของชุดสาธารณะ
เทมเพลต manifest¶
ใช้สิ่งนี้เป็นจุดเริ่มต้น — คัดลอกจากโมดูล roots_hemms_* ที่มีอยู่ แทนที่จะคิดค้นคีย์ขึ้นมาเอง:
{
"name": "Roots HEMMS — Your Module",
"version": "18.0.1.0.0",
"category": "Maintenance",
"summary": "One-sentence description — what it adds, not how.",
"author": "Trinity Roots Co., Ltd.",
"website": "https://roots.tech",
"license": "LGPL-3",
"development_status": "Alpha",
"maintainers": ["yourname"],
"depends": [
# Depend on the most specific HEMMS module that gives you what
# you need, NOT on `maintenance` directly. See "Dependency hygiene"
# below.
"roots_hemms_pm",
],
"data": [
"security/ir.model.access.csv",
"views/yourmodel_views.xml",
],
"installable": True,
"application": False,
}
คีย์ที่เราตั้งเสมอ:
version: เป็น18.0.<major>.<minor>.<patch>เสมอ (5 ส่วน — ตาม ธรรมเนียมของ Odoo)license:LGPL-3สำหรับโมดูลสาธารณะ AGPL-3 เฉพาะเมื่อพึ่งพา โมดูล OCA ที่ใช้ใบอนุญาต AGPL และคุณยอมรับนัยที่ตามมาdevelopment_status:Alphaจนกว่าจะเสถียร;Betaหลังจากมีลูกค้า จริงใช้งานอยู่;Stableหลังจากอยู่ใน production หนึ่งปี;Matureหลังจากสองปี ดูเกณฑ์ได้ในสกิลoca-coding-standardmaintainers: GitHub handle ของคุณauthor:"Trinity Roots Co., Ltd."สำหรับการร่วมพัฒนา HEMMS-core
สุขอนามัยของ dependency¶
พึ่งพาโมดูล HEMMS ที่เฉพาะเจาะจงที่สุด ที่ให้สิ่งที่คุณ ต้องการ
| คุณต้องการ... | พึ่งพา |
|---|---|
| เครื่องมือแพทย์พร้อม brand/model + condition + criticality | roots_hemms_asset_master และ roots_hemms_criticality |
| ขั้นตอนการทำงาน 5 ขั้น + SLA + audit | roots_hemms_workflow |
| ชนิด PM + ขั้น PM Scheduled | roots_hemms_pm |
| บรรทัดอะไหล่ | roots_hemms_spare_parts |
| การค้นหาความครอบคลุมของสัญญา | roots_hemms_service_contracts |
การลงนามผ่าน sign_oca | roots_hemms_signature |
| MoPH device-class + EM-score waterfall | roots_hemms_ha_report |
อย่าพึ่งพา maintenance ของ Odoo โดยตรง
หากมีโมดูล HEMMS ที่ขยาย maintenance สำหรับ กรณีใช้งานของคุณอยู่แล้ว ให้พึ่งพามัน การพึ่งพา maintenance โดยตรง เป็นการสร้าง hospital lens ที่ขนานกันขึ้นมาใหม่ ซึ่งเป็นการเบี่ยงเบนแบบ เดียวกับที่กฎ 3 ชั้นมีไว้เพื่อป้องกัน
หลักการตั้งชื่อฟิลด์¶
ฟิลด์เฉพาะของโรงพยาบาลบนโมเดล Odoo / OCA ที่ สืบทอด มาใช้คำนำหน้า roots_ ส่วนโมเดลใหม่ที่นิยามโดย HEMMS ใช้คำนำหน้า _name ว่า hemms. ฟิลด์ของตัวเองไม่จำเป็นต้องมีคำนำหน้าซ้ำซ้อน
class MaintenanceEquipment(models.Model):
_inherit = "maintenance.equipment"
roots_floor_number = fields.Integer() # ✅ prefixed
roots_room_code = fields.Char() # ✅ prefixed
class HemmsCalibrationCert(models.Model):
_name = "hemms.calibration.cert" # ✅ hemms. prefix
_description = "Equipment Calibration Certificate"
name = fields.Char() # ✅ no redundant prefix
expiry_date = fields.Date() # ✅ no redundant prefix
equipment_id = fields.Many2one("maintenance.equipment") # ✅ standard Odoo suffix
กฎ suffix ชื่อฟิลด์ (ตามมาตรฐานการเขียนโค้ดของ Odoo):
_idสำหรับ Many2one_idsสำหรับ One2many และ Many2many_countสำหรับ compute ที่เก็บค่าและคืนค่า Integer แบบนับจำนวน_dateสำหรับ Date / Datetime
อ่านผลลัพธ์ของสกิล odoo-coding-standard สำหรับชุดเต็ม
ลำดับ attribute ของโมเดล¶
ภายในคลาสของโมเดล attribute เรียงตามลำดับนี้ (สะท้อนโค้ด HEMMS ที่มีอยู่):
class HemmsExample(models.Model):
# 1. Model attributes (_name, _description, _inherit, _order, _rec_name)
_name = "hemms.example"
_description = "Example Model"
_inherit = ["mail.thread"]
_order = "name"
# 2. SQL constraints
_sql_constraints = [
("name_uniq", "unique(name)", "Name must be unique."),
]
# 3. Fields, grouped by type with a comment header
# ── Char / Text ──────────────────────────────────────────────────
name = fields.Char(required=True)
# ── Many2one ─────────────────────────────────────────────────────
partner_id = fields.Many2one("res.partner")
# 4. Compute methods (in field-declaration order)
@api.depends("name")
def _compute_display_name(self):
...
# 5. CRUD lifecycle (create / write / unlink) overrides
@api.model_create_multi
def create(self, vals_list):
...
# 6. Constraints
@api.constrains("name")
def _check_name(self):
...
# 7. Public action methods (button handlers, named `action_*`)
def action_do_the_thing(self):
...
# 8. Private helpers (named `_helper_*` or just `_*`)
def _helper_internal(self):
...
การเขียนการทดสอบ¶
การทดสอบอยู่ใต้ tests/ ในแต่ละโมดูลและทำตามรูปแบบ TransactionCase ของ Odoo การทดสอบที่มีอยู่เป็นตัวอย่างอ้างอิงที่ดีที่สุด — อ่าน addons/roots/roots_hemms_service_contracts/tests/test_contract_unit.py สำหรับตัวอย่างเทสต์หน่วย และ test_contract_integration.py สำหรับตัวอย่าง เทสต์การรวมระบบ
from odoo.tests import TransactionCase, tagged
@tagged("post_install", "-at_install")
class TestYourFeature(TransactionCase):
"""Unit tests for roots_hemms_yourmodule.YourFeature."""
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.equipment = cls.env["maintenance.equipment"].create({
"name": "Test Equipment",
"roots_value_tier": "high",
"roots_risk_tier": "high",
})
def test_u01_red_criticality_propagates(self):
"""U1: high value + high risk → criticality_color == 'red'."""
self.assertEqual(self.equipment.roots_criticality_color, "red")
หลักการติดแท็ก:
post_install— การทดสอบส่วนใหญ่ ทำให้แน่ใจว่าโมดูลอื่นถูกโหลดแล้วat_installเฉพาะสำหรับค่าคงที่ ณ เวลาโหลดโมดูล-at_installลบล้างค่าเริ่มต้นเพื่อไม่ให้การทดสอบรันสองครั้ง
หลักการตั้งชื่อ:
- เทสต์หน่วย:
test_uNN_<short_description>(ตรงกับรายการ unit ใน §7 ของแผน) - เทสต์การรวมระบบ:
test_iNN_<short_description>
อย่า import คลาสของโมเดล — ผ่าน self.env["..."] เสมอ
การรันการทดสอบบนเครื่อง¶
# Run all tests for one module:
make test-module MODULE=roots_hemms_yourmodule
# Or, manually:
odoo --test-enable \
--test-tags=/roots_hemms_yourmodule \
--stop-after-init \
-i roots_hemms_yourmodule \
-d test_yourmodule
--test-tags=/<module> จำกัดขอบเขตให้โมดูลของคุณเพื่อให้การทดสอบของโมดูลอื่น ไม่รัน
การเชื่อมต่อ CI¶
CI รันทุกการทดสอบบนทุก PR ผ่าน leaf module ทั้งสี่ใน .github/workflows/tests.yml หากโมดูลของคุณอยู่ในกราฟ dependency ของ leaf เหล่านั้น (ส่วนใหญ่อยู่) คุณจะได้ความครอบคลุมจาก CI ฟรี — ไม่ต้องทำอะไร หากคุณเพิ่มโมดูล HEMMS ที่อยู่ยอดสุดของต้นไม้ (ไม่มีอะไรพึ่งพามัน) ให้เพิ่มเข้าไปใน MODULES_TO_TEST ใน tests.yml และ HEMMS_MODULES ใน Makefile ให้สอง ที่นี้สอดคล้องกันเสมอ
ดู CI/CD สำหรับรายละเอียดขั้นตอนการทำงานทั้งหมด
การตั้งค่า Pre-commit¶
ทุก commit จะรัน toolchain pre-commit ของ OCA ติดตั้งครั้งเดียวหลังจาก clone:
make docs-install # gets the venv set up
pip install pre-commit
pre-commit install # installs the .git/hooks/pre-commit shim
จากนั้น ทุก git commit จะรัน:
- ruff — lint + จัดรูปแบบ Python
- pylint_odoo — lint เฉพาะของ Odoo (ส่วนใหญ่เป็น UX/style)
- oca-checks-odoo-module — ตรวจ manifest + โครงสร้าง
- สุขอนามัย XML / YAML — ช่องว่างท้ายบรรทัด, newline ท้ายไฟล์ ฯลฯ
หาก hook ใดล้มเหลว commit จะถูกบล็อก แก้ไขแล้ว re-stage; อย่า --no-verify ข้าม hook ที่ล้มเหลว
ในการรัน hook เดียวกันบนทั้งต้นไม้:
อ่าน .pre-commit-config.yaml สำหรับสามการเบี่ยงเบนโดยตั้งใจจากค่าเริ่มต้นของ OCA (ข้าม oca-gen-addon-readme, ข้าม oca-fix-manifest-website, comment prettier-xml + eslint ออก)
มาตรฐานการเขียนโค้ด (อ้างอิงสกิล)¶
ไฟล์สกิลสองไฟล์ในรีโพนี้เข้ารหัสมาตรฐานหลัก เมื่อ สงสัย ให้เรียกใช้พวกมัน:
| สกิล | ครอบคลุม |
|---|---|
odoo-coding-standard | ธรรมเนียมทางการของ Odoo — ลำดับ attribute ของโมเดล, การเรียงลำดับชนิดฟิลด์, การเรียงลำดับเมท็อด, กฎ suffix ของฟิลด์, 5 idiom การเขียนโปรแกรมของ Odoo (ส่งต่อ context, คิดแบบขยายได้, อย่า commit transaction เอง, เลี่ยงการดักจับ exception, ใช้ _() อย่างถูกต้อง), รูปแบบข้อความ commit |
oca-coding-standard | ชั้นบังคับใช้ที่เข้มงวดกว่าของ OCA ที่อยู่ด้านบน — pylint_odoo, oca-checks-odoo-module, ส่วนเสริมของ manifest (development_status, maintainers), วงจรชีวิตความสมบูรณ์ของโมดูล, การสร้าง README จากชิ้นส่วน readme/ |
รูปแบบข้อความ commit¶
ทำตามธรรมเนียม OCA ที่ใช้ทั่วทั้งรีโพ:
[TAG](module): one-line summary [QN.M]
Optional longer body explaining the why.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
[TAG] เป็นหนึ่งใน feat, fix, test, docs, chore, style, refactor, build (module) คือโมดูลที่ได้รับผลกระทบ (รูปแบบสั้นก็ ใช้ได้: feat(spare) แทน feat(roots_hemms_spare_parts)) ส่วน suffix [QN.M] อ้างถึง task ในแผน — ดูตัวอย่าง commit ที่มีอยู่ใน git log
ตัวอย่างที่ทำเสร็จแล้ว: การเพิ่มโมดูล calibration-cert¶
ตัวอย่างเล็ก ๆ ที่ทำเสร็จแล้วแสดงทุกชิ้นส่วนรวมกัน
1. สร้างโครงโมดูล:
mkdir -p addons/roots/roots_hemms_calibration/{models,views,security,data,tests}
touch addons/roots/roots_hemms_calibration/{__init__.py,__manifest__.py}
touch addons/roots/roots_hemms_calibration/models/__init__.py
touch addons/roots/roots_hemms_calibration/tests/__init__.py
2. Manifest:
{
"name": "Roots HEMMS — Calibration Certificates",
"version": "18.0.1.0.0",
"category": "Maintenance",
"summary": "Track calibration certificates per equipment with vendor sign-off.",
"author": "Trinity Roots Co., Ltd.",
"website": "https://roots.tech",
"license": "LGPL-3",
"development_status": "Alpha",
"maintainers": ["yourname"],
"depends": [
"roots_hemms_asset_master", # depend on the specific HEMMS module
"roots_hemms_signature", # for sign_oca bridge later
],
"data": [
"security/ir.model.access.csv",
"views/hemms_calibration_cert_views.xml",
],
"installable": True,
"application": False,
}
3. โมเดล:
from odoo import fields, models
class HemmsCalibrationCert(models.Model):
_name = "hemms.calibration.cert"
_description = "Equipment Calibration Certificate"
_order = "expiry_date asc, id desc"
# ── Char / Text ──────────────────────────────────────────────────
name = fields.Char(required=True)
# ── Date ─────────────────────────────────────────────────────────
issued_date = fields.Date(required=True)
expiry_date = fields.Date(required=True)
# ── Many2one ─────────────────────────────────────────────────────
equipment_id = fields.Many2one(
"maintenance.equipment",
required=True,
ondelete="cascade",
index=True,
)
calibration_body_id = fields.Many2one("res.partner")
# ── Binary ───────────────────────────────────────────────────────
pdf_file = fields.Binary(attachment=True)
pdf_filename = fields.Char()
4. การทดสอบ:
from odoo.tests import TransactionCase, tagged
@tagged("post_install", "-at_install")
class TestCalibration(TransactionCase):
def test_u01_create_minimal_cert(self):
equipment = self.env["maintenance.equipment"].create({"name": "Eq"})
cert = self.env["hemms.calibration.cert"].create({
"name": "CAL-001",
"issued_date": "2026-01-01",
"expiry_date": "2027-01-01",
"equipment_id": equipment.id,
})
self.assertEqual(cert.equipment_id, equipment)
5. รัน CI บนเครื่อง:
6. Commit:
git add addons/roots/roots_hemms_calibration/
git commit -m "feat(calibration): add calibration-cert module skeleton"
นั่นคือลูปทั้งหมด ทำซ้ำกับฟีเจอร์ต่าง ๆ ภายใต้ด่าน lint + test เดียวกัน
หน้าที่เกี่ยวข้อง¶
- สถาปัตยกรรม — โมเดล 3 ชั้นที่โมดูลเหล่านี้อยู่ภายใน
- API Reference — public surface ของทุกโมดูล HEMMS ที่มีอยู่
- CI/CD — สิ่งที่ขั้นตอนการทำงาน lint และ test บังคับใช้
- การร่วมพัฒนา — ขั้นตอนการทำงานของเอกสาร / commit / PR
- Q-plan ใต้
docs/developer/q*-*-plan.md— ตัวอย่างที่ทำเสร็จแล้วของวิธี กำหนดขอบเขตและส่งมอบไตรมาสหนึ่ง