# xf_doc_approval_custom — Developer Guide ## Table of Contents 1. [Project Overview](#1-project-overview) 2. [Folder Structure](#2-folder-structure) 3. [How Odoo Inheritance Works](#3-how-odoo-inheritance-works) 4. [Inheriting a Model (Python)](#4-inheriting-a-model-python) 5. [Inheriting a View (XML)](#5-inheriting-a-view-xml) 6. [Inheriting CSS / Styles](#6-inheriting-css--styles) 7. [Inheriting Fonts (Google Fonts)](#7-inheriting-fonts-google-fonts) 8. [Key Techniques Used in This Module](#8-key-techniques-used-in-this-module) 9. [Docker Commands](#9-docker-commands) 10. [Rules — What NOT to Touch](#10-rules--what-not-to-touch) --- ## 1. Project Overview | Item | Value | |------|-------| | Base module | `xf_doc_approval` (located at `d:\project_v19s\xf_doc_approval`) | | Custom module | `xf_doc_approval_custom` (located at `d:\project_v19s\odoo19-dev\addons\xf_doc_approval_custom`) | | Odoo version | 19.0 | | Purpose | Extend the base module with Khmer language labels, new fields, redesigned form, QR code, and role-based comment visibility — **without modifying the original module** | ### What we changed vs the base module | Feature | What we did | |---------|-------------| | Navbar color | Changed to `#0a5e98` via CSS | | Font | Added Khmer font (Battambang) for Khmer text only | | State labels | Overrode all state labels to Khmer | | Section ក (Approvers) | Renamed to Khmer, added sent_date, round, Khmer column headers, document source row | | Section ខ (Content) | Completely new section with 8 custom fields in a styled table | | New fields | `document_type`, `decision_requester_id`, `reference_note`, `reference_file`, `doc_number`, `qr_code`, `document_source`, `description` (Html override) | | QR Code | Auto-generated when document is approved | | Auto numbering | `ir.sequence` → format `DOC/YYYY/MM/0001` | | Comment visibility | Step-based: each approver sees only notes from their step and earlier | | Button label | "Send for Approval" → "បញ្ជូន" | --- ## 2. Folder Structure ``` xf_doc_approval_custom/ │ ├── __manifest__.py ← Module declaration (name, depends, data files) ├── __init__.py ← Python package init (imports models/) │ ├── models/ │ ├── __init__.py ← Imports all model files │ ├── document_package.py ← Inherits xf.doc.approval.document.package │ └── document_approver.py ← Inherits xf.doc.approval.document.approver │ ├── views/ │ ├── assets.xml ← Registers CSS + Google Fonts injection │ └── document_package_form.xml ← Inherits and modifies the form view │ ├── data/ │ └── sequences.xml ← Creates ir.sequence for document numbering │ └── static/ └── src/ └── css/ └── navbar.css ← All custom CSS (navbar, table, section ខ) ``` --- ## 3. How Odoo Inheritance Works Odoo has **3 layers** you can extend without touching the original code: ``` Original Module Your Custom Module ───────────────── ──────────────────────────────────── models/ models/ ← _inherit = 'original.model.name' views/ views/ ← inherit_id = ref('original.view.id') static/css/ static/ ← registered via ir.asset record ``` The **golden rule**: never edit the original module. All changes live in your custom module. --- ## 4. Inheriting a Model (Python) ### Basic pattern ```python # models/my_model.py from odoo import api, fields, models class MyCustomExtension(models.Model): _inherit = 'original.model.name' # ← this is the key line # Add new fields my_new_field = fields.Char(string='My Field') # Override an existing field (change labels, default, etc.) state = fields.Selection( selection=[('draft', 'New Label'), ('done', 'Finished')], # Only list what you want to CHANGE. Odoo merges the rest. ) # Override an existing method def action_confirm(self): # Do something before result = super().action_confirm() # ← always call super() # Do something after return result ``` ### How we used it — `document_package.py` ```python class DocApprovalDocumentPackageCustom(models.Model): _inherit = 'xf.doc.approval.document.package' # 1. New fields added to existing model document_type = fields.Selection(...) doc_number = fields.Char(default='New') qr_code = fields.Image(max_width=0, max_height=0) # 2. Override existing field labels only state = fields.Selection( selection=[('draft', 'បង្កើតលិខិតឯកសារ'), ...] ) # 3. Override existing field type (Text → Html) description = fields.Html(translate=True, sanitize=False) # 4. Override a method — inject QR generation after approval def action_finish_approval(self): res = super().action_finish_approval() # run original logic first approved = self.filtered(lambda r: r.state == 'approved') approved._generate_qr_code() return res ``` ### Field types reference | Field | Use case | |-------|----------| | `fields.Char` | Short text (one line) | | `fields.Text` | Long plain text | | `fields.Html` | Rich text with editor toolbar | | `fields.Integer` | Whole number | | `fields.Float` | Decimal number | | `fields.Boolean` | True/False checkbox | | `fields.Date` | Date only | | `fields.Datetime` | Date + time | | `fields.Selection` | Dropdown list | | `fields.Many2one` | Link to one other record | | `fields.One2many` | List of related records | | `fields.Many2many` | Multiple related records | | `fields.Binary` | File / raw bytes | | `fields.Image` | Image file (served via /web/image/) | ### Important: `__init__.py` must import every model file ```python # models/__init__.py from . import document_package from . import document_approver ``` --- ## 5. Inheriting a View (XML) ### Basic pattern ```xml my.custom.view.name original.model.name ``` ### Finding the original view's `ref` 1. Go to **Settings → Technical → Views** 2. Search for the model name 3. The **External ID** column shows the ref, e.g. `xf_doc_approval.xf_doc_approval_document_package_form` ### xpath `expr` — how to select elements | Selector | Example | Selects | |----------|---------|---------| | By element name | `//form` | The `
` element | | By name attribute | `//group[@name='approvers']` | `` | | By field name | `//field[@name='user_id']` | `` | | By button name | `//button[@name='action_confirm']` | `