# 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