diff --git a/digital_signature_kmitl/README.rst b/digital_signature_kmitl/README.rst
new file mode 100644
index 000000000..6a9d55eb9
--- /dev/null
+++ b/digital_signature_kmitl/README.rst
@@ -0,0 +1,28 @@
+=======================
+Digital Signature KMITL
+=======================
+
+Per-user digital signature storage and capture for KMITL approval workflows.
+
+Features
+========
+
+* Store personal digital signature on each user profile (``res.users.signature_image``)
+* Signature widget in user form and preferences for easy drawing/upload
+* QWeb helper template for consistent PDF signature block rendering
+* Foundation module for approval document signature capture
+
+Usage
+=====
+
+#. Install module: ``digital_signature_kmitl``
+#. Go to Settings > Users & Companies > Users
+#. Open your user profile
+#. Go to "Preferences" tab → "ลายเซ็นดิจิตอล" section
+#. Draw or upload your signature
+#. Save
+
+Contributors
+============
+
+* Aginix Technologies
diff --git a/digital_signature_kmitl/__init__.py b/digital_signature_kmitl/__init__.py
new file mode 100644
index 000000000..6ab96431a
--- /dev/null
+++ b/digital_signature_kmitl/__init__.py
@@ -0,0 +1 @@
+from . import models as models # noqa: F401
diff --git a/digital_signature_kmitl/__manifest__.py b/digital_signature_kmitl/__manifest__.py
new file mode 100644
index 000000000..54d1bcbb2
--- /dev/null
+++ b/digital_signature_kmitl/__manifest__.py
@@ -0,0 +1,19 @@
+{
+ "name": "Digital Signature Kmitl",
+ "version": "16.0.1.0.0",
+ "summary": "Per-user digital signature stored on res.users, with QWeb helper for PDF reports.",
+ "category": "KMITL",
+ "author": "Aginix Technologies",
+ "website": "https://github.com/aginix/kmitl",
+ "depends": [
+ "base",
+ "web",
+ ],
+ "data": [
+ "views/res_users_views.xml",
+ "views/signature_templates.xml",
+ ],
+ "installable": True,
+ "auto_install": False,
+ "license": "LGPL-3",
+}
diff --git a/digital_signature_kmitl/models/__init__.py b/digital_signature_kmitl/models/__init__.py
new file mode 100644
index 000000000..35f4e21de
--- /dev/null
+++ b/digital_signature_kmitl/models/__init__.py
@@ -0,0 +1 @@
+from . import res_users as res_users # noqa: F401
diff --git a/digital_signature_kmitl/models/res_users.py b/digital_signature_kmitl/models/res_users.py
new file mode 100644
index 000000000..1ccff7c62
--- /dev/null
+++ b/digital_signature_kmitl/models/res_users.py
@@ -0,0 +1,21 @@
+from odoo import fields, models
+
+
+class ResUsers(models.Model):
+ _inherit = "res.users"
+
+ signature_image = fields.Image(
+ string="Digital Signature",
+ max_width=1024,
+ max_height=1024,
+ attachment=True,
+ help="Personal digital signature, captured into approval documents at approval time.",
+ )
+
+ @property
+ def SELF_READABLE_FIELDS(self):
+ return super().SELF_READABLE_FIELDS + ["signature_image"]
+
+ @property
+ def SELF_WRITEABLE_FIELDS(self):
+ return super().SELF_WRITEABLE_FIELDS + ["signature_image"]
diff --git a/digital_signature_kmitl/views/res_users_views.xml b/digital_signature_kmitl/views/res_users_views.xml
new file mode 100644
index 000000000..b973142db
--- /dev/null
+++ b/digital_signature_kmitl/views/res_users_views.xml
@@ -0,0 +1,38 @@
+
+
+
+ res.users.form.signature
+ res.users
+
+
+
+
+
+
+
+
+
+
+
+ res.users.preferences.form.signature
+ res.users
+
+
+
+
+
+
+
+
+
+
diff --git a/digital_signature_kmitl/views/signature_templates.xml b/digital_signature_kmitl/views/signature_templates.xml
new file mode 100644
index 000000000..d0ce8565b
--- /dev/null
+++ b/digital_signature_kmitl/views/signature_templates.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
(ลงชื่อ)
+
+
+
+
+
+
+
+
+
+
+
+ ()
+
+
+
+
diff --git a/purchase_work_acceptance_signature_kmitl/README.rst b/purchase_work_acceptance_signature_kmitl/README.rst
new file mode 100644
index 000000000..43c08f2a0
--- /dev/null
+++ b/purchase_work_acceptance_signature_kmitl/README.rst
@@ -0,0 +1,48 @@
+============================================
+Purchase Work Acceptance Digital Signature
+============================================
+
+Automatic signature capture for Work Acceptance documents.
+
+Features
+========
+
+* Snapshot procurement officer signature (``responsible_signature``) on document acceptance
+* Snapshot committee member signatures (``committee.signature_image``) when they approve
+* Immutable signature storage ensures audit trail and PDF consistency
+* Automatic fallback to dotted signature lines for users without signatures
+* Supports both paperless (tier validation) and paper-based approval workflows
+
+Installation
+============
+
+Install in order:
+
+#. ``digital_signature_kmitl``
+#. ``purchase_work_acceptance_signature_kmitl``
+
+Configuration
+=============
+
+No additional configuration required. Signatures are automatically captured from user profiles
+when committee members or procurement officers approve the work acceptance.
+
+Usage
+=====
+
+#. Ensure users have digital signatures set in their profiles
+#. Create a work acceptance with committee members
+#. Committee members approve via tier validation or wizard
+#. Signatures are automatically captured into the document
+#. Print PDF to see signature images (or dotted lines if signature not set)
+
+PDF Reports
+===========
+
+* ``report_work_acceptance`` — Work Acceptance (พ.36) with committee signatures
+* ``report_committee_acceptance`` — Committee Acceptance (ใบตรวจการรับพัสดุ) with committee signatures
+
+Contributors
+============
+
+* Aginix Technologies
diff --git a/purchase_work_acceptance_signature_kmitl/__init__.py b/purchase_work_acceptance_signature_kmitl/__init__.py
new file mode 100644
index 000000000..6ab96431a
--- /dev/null
+++ b/purchase_work_acceptance_signature_kmitl/__init__.py
@@ -0,0 +1 @@
+from . import models as models # noqa: F401
diff --git a/purchase_work_acceptance_signature_kmitl/__manifest__.py b/purchase_work_acceptance_signature_kmitl/__manifest__.py
new file mode 100644
index 000000000..e89273d3c
--- /dev/null
+++ b/purchase_work_acceptance_signature_kmitl/__manifest__.py
@@ -0,0 +1,20 @@
+{
+ "name": "Purchase Work Acceptance Digital Signature",
+ "version": "16.0.1.0.0",
+ "summary": "Snapshot committee + procurement officer signatures into Work Acceptance "
+ "documents at approval time, and render them in the PDF report.",
+ "category": "KMITL",
+ "author": "Aginix Technologies",
+ "website": "https://github.com/aginix/kmitl",
+ "depends": [
+ "digital_signature_kmitl",
+ "purchase_work_acceptance_kmitl",
+ ],
+ "data": [
+ "reports/report_work_acceptance.xml",
+ "reports/report_committee_acceptance.xml",
+ ],
+ "installable": True,
+ "auto_install": False,
+ "license": "LGPL-3",
+}
diff --git a/purchase_work_acceptance_signature_kmitl/models/__init__.py b/purchase_work_acceptance_signature_kmitl/models/__init__.py
new file mode 100644
index 000000000..5e95b9be9
--- /dev/null
+++ b/purchase_work_acceptance_signature_kmitl/models/__init__.py
@@ -0,0 +1,2 @@
+from . import work_acceptance as work_acceptance # noqa: F401
+from . import work_acceptance_committee as work_acceptance_committee # noqa: F401
diff --git a/purchase_work_acceptance_signature_kmitl/models/work_acceptance.py b/purchase_work_acceptance_signature_kmitl/models/work_acceptance.py
new file mode 100644
index 000000000..670b830a7
--- /dev/null
+++ b/purchase_work_acceptance_signature_kmitl/models/work_acceptance.py
@@ -0,0 +1,45 @@
+import logging
+
+from odoo import api, fields, models
+
+_logger = logging.getLogger(__name__)
+
+
+class WorkAcceptance(models.Model):
+ _inherit = "work.acceptance"
+
+ responsible_signature = fields.Image(
+ string="Receiver Signature",
+ attachment=True,
+ copy=False,
+ readonly=True,
+ help="Signature of the procurement officer (responsible_id) captured at acceptance.",
+ )
+
+ def button_accept(self, force=False):
+ result = super().button_accept(force=force)
+ for rec in self:
+ if (
+ rec.state == "accept"
+ and not rec.responsible_signature
+ and rec.responsible_id
+ ):
+ signature = rec.responsible_id.signature_image
+ if signature:
+ rec.with_context(skip_validation_check=True).write(
+ {"responsible_signature": signature}
+ )
+ else:
+ _logger.warning(
+ "User %s has no signature_image set, "
+ "responsible signature not captured for WA %s",
+ rec.responsible_id.name,
+ rec.name,
+ )
+ return result
+
+ @api.model
+ def _get_under_validation_exceptions(self):
+ res = super()._get_under_validation_exceptions()
+ res.append("responsible_signature")
+ return res
diff --git a/purchase_work_acceptance_signature_kmitl/models/work_acceptance_committee.py b/purchase_work_acceptance_signature_kmitl/models/work_acceptance_committee.py
new file mode 100644
index 000000000..c1d83f482
--- /dev/null
+++ b/purchase_work_acceptance_signature_kmitl/models/work_acceptance_committee.py
@@ -0,0 +1,49 @@
+import logging
+
+from odoo import api, fields, models
+
+_logger = logging.getLogger(__name__)
+
+
+class WorkAcceptanceCommittee(models.Model):
+ _inherit = "work.acceptance.committee"
+
+ signature_image = fields.Image(
+ string="Committee Signature",
+ attachment=True,
+ copy=False,
+ readonly=True,
+ help="Signature of the committee member captured when the row is marked accepted.",
+ )
+
+ def write(self, vals):
+ result = super().write(vals)
+ if vals.get("status") == "accept":
+ for rec in self:
+ if not rec.signature_image:
+ user = rec.employee_id.user_id
+ if not user:
+ _logger.warning(
+ "Employee %s has no linked user, "
+ "signature not captured for committee record %s",
+ rec.employee_id.display_name,
+ rec.id,
+ )
+ elif user.signature_image:
+ super(WorkAcceptanceCommittee, rec).write(
+ {"signature_image": user.signature_image}
+ )
+ else:
+ _logger.warning(
+ "User %s has no signature_image set, "
+ "signature not captured for committee record %s",
+ user.name,
+ rec.id,
+ )
+ return result
+
+ @api.model
+ def _get_under_validation_exceptions(self):
+ res = super()._get_under_validation_exceptions()
+ res.append("signature_image")
+ return res
diff --git a/purchase_work_acceptance_signature_kmitl/reports/report_committee_acceptance.xml b/purchase_work_acceptance_signature_kmitl/reports/report_committee_acceptance.xml
new file mode 100644
index 000000000..d99e8539e
--- /dev/null
+++ b/purchase_work_acceptance_signature_kmitl/reports/report_committee_acceptance.xml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
(ลงชื่อ)
+
+
+
+
+
+
+
+
+
+ ()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
(ลงชื่อ)
+
+
+
+
+
+
+
+
+
+ ()
+
+
+
+
+
+
+
+
+
diff --git a/purchase_work_acceptance_signature_kmitl/reports/report_work_acceptance.xml b/purchase_work_acceptance_signature_kmitl/reports/report_work_acceptance.xml
new file mode 100644
index 000000000..b076d3202
--- /dev/null
+++ b/purchase_work_acceptance_signature_kmitl/reports/report_work_acceptance.xml
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
ลงชื่อ
+
+
+
+
+
+
+
ผู้รับพัสดุ/เจ้าหน้าที่พัสดุ
+
+
+ ()
+
+
+
+
+
+
+
+
+
+
+
(ลงชื่อ)
+
+
+
+
+
+
+
+
+
+ ()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
(ลงชื่อ)
+
+
+
+
+
+
+
+
+
+ ()
+
+
+
+
+
+
+
+
+