Odoo nổi tiếng vì “dễ bắt đầu”: cài lên là có CRM, Sales, Inventory, Accounting… Nhưng khi dự án đi vào vận hành thật, dữ liệu tăng lên, user tăng lên, nghiệp vụ bắt đầu “có ngoại lệ”, bạn sẽ thấy một sự thật: Odoo chỉ dễ khi bạn làm đúng cách. Làm sai một vài thói quen (compute phụ thuộc sai, vòng lặp N+1, record rule thiết kế không khéo, override thiếu super(), lạm dụng sudo()) là hệ thống sẽ chậm dần, số liệu lệch dần, và migration thì… “cầu trời”.

Bài viết này tổng hợp theo góc nhìn dev (và cả BA/Test có thể dùng), tập trung vào 5 phần:

  • 🧩 Kiến trúc module: hiểu để không “lạc rừng”
  • 🧱 Inherit/Override + compute/onchange/constrains: viết đúng để bền
  • 🕵️ Debug nhanh: có quy trình thì bug không còn đáng sợ
  • 🚀 Performance: tránh Odoo chậm dần theo thời gian
  • 🧳 Migration: chia nhỏ để sống, chứ đừng “all-in”

Bạn có thể coi đây như một “bản đồ” để build module sạch, tối ưu đúng cách, và chuẩn bị cho upgrade/migration mà không bị vỡ dây chuyền.

🧩 1) Kiến trúc module Odoo: 1 module sạch nhìn phát hiểu ngay

Một module Odoo tốt không chỉ là “chạy được”, mà là đọc được – bảo trì được – nâng cấp được. Đa số dự án rối là vì module bị trộn: logic nghiệp vụ nằm rải rác trong wizard, view, JS, hoặc action server; security thêm chỗ này thiếu chỗ kia; data/init không có thứ tự rõ ràng.

✅ Anatomy của một module “đúng chuẩn”

Thông thường bạn sẽ thấy các phần:

  • __manifest__.py: mô tả module, dependencies, data files load
  • models/: business objects + fields + methods
  • views/: form/tree/search/kanban + view inheritance (xpath)
  • security/: ir.model.access.csv, record rules, groups
  • data/: sequence, mail template, cron, config parameters…
  • wizard/: các flow “tạm thời” (nhập liệu nhiều bước, chọn lọc)
  • report/: QWeb report
  • controllers/: API/website endpoints
  • static/: JS/CSS/XML assets (web)

🧼 Checklist module “clean” (rất đáng làm ngay từ đầu)

  • ✅ Naming rõ ràng: x_project_* hoặc prefix theo công ty/nhóm module
  • _description có ý nghĩa, model/field có comment ngắn
  • ✅ Multi-company: có company_id (khi cần), domain/constraint rõ
  • ✅ Security đầy đủ: access + record rule (đừng để user test bằng admin)
  • ✅ Data load tách bạch: config, sequence, master data demo… theo file riêng
  • ✅ Tránh “logic trong view”: hạn chế attrs quá phức tạp thay cho rule backend
  • ✅ Tránh sudo() theo phản xạ: dùng sai là tạo lỗ hổng phân quyền khó debug

Một nguyên tắc giúp module bền:

“Logic nghiệp vụ nên nằm ở model (backend). View chỉ là cách hiển thị.”

🧱 2) Inherit vs Override: viết đúng để không vỡ khi upgrade

Odoo cho bạn nhiều cách “chèn” vào luồng sẵn có. Nhưng càng linh hoạt thì càng dễ… tự bẫy mình. Hai câu hỏi bạn nên tự hỏi trước khi code:

  1. Mình cần bổ sung hay thay đổi hành vi?
  2. Cái mình đang làm có ảnh hưởng đến batch/hiệu năngupgrade/migration không?

🧬 2.1 _inherit vs _name: phân biệt cho chuẩn

  • _inherit = ‘model.existing: kế thừa và mở rộng model có sẵn
  • _name = ‘model.new’: tạo model mới
  • _inherit + _name (delegation) thường dùng khi muốn “bọc” model

📌 Mục tiêu: ưu tiên mở rộng trước, ghi đè khi thật cần.

🧮 2.2 Compute / Related / Onchange / Constrains: dùng đúng vai trò

✅ Compute field (compute=…)

Dùng để tính toán từ field khác. Đây là nơi dễ gây lỗi số liệu nhất.

Bẫy phổ biến #1: @api.depends thiếu dependency → dữ liệu “đứng hình”
Bẫy phổ biến #2: compute không set cho mọi record → record cũ giữ giá trị cũ
Bẫy phổ biến #3: store=True + dependency rộng → recompute hàng loạt, lag theo thời gian

Tip: nếu compute “đắt”, cân nhắc store + depends thật chặt; nếu không chắc, để store=False (đổi lại sẽ tính khi đọc).

✅ Related field (related=…)

Tiện cho việc hiển thị, nhưng cẩn thận hiệu năng nếu chain dài hoặc list view đọc nhiều.

✅ Onchange (@api.onchange)

Chỉ dùng cho UX/UI. Đừng dùng onchange để “ép luật nghiệp vụ” vì:

  • Import Excel / API / automated action có thể bypass
  • Khi backend write trực tiếp, onchange không chạy

✅ Constraints (@api.constrains)

Đây mới là nơi đặt luật bắt buộc.

🧱 2.3 Override create/write và “workflow methods” cho đúng cách

Nhiều dự án cần can thiệp vào:

  • create, write
  • action_confirm, button_validate, action_post, unlink

4 quy tắc vàng khi override:

  1. Luôn gọi super() (trừ trường hợp bạn biết rõ vì sao không gọi)
  2. Xử lý theo recordset/batch, tránh loop + search trong loop
  3. Không dùng sudo() để “chữa cháy” (nếu cần, giải thích rõ lý do)
  4. Tách logic ra hàm riêng để test/đọc dễ

Ví dụ tư duy “đúng”: override chỉ điều phối, còn nghiệp vụ nằm trong hàm riêng.

🕵️ 3) Debug Odoo nhanh: có quy trình thì bug không còn đáng sợ

Bug Odoo thường không nằm ở “syntax”, mà nằm ở:

  • context sai (company, lang, timezone)
  • record rule / access rights
  • compute stale
  • view inheritance xpath không match
  • data inconsistent

🧭 Quy trình debug 5 bước (rất nên áp dụng)

  1. Reproduce đúng role user (đừng test bằng admin)
  2. Khoanh vùng: UI? ORM? security? data?
  3. Check context: company, allowed_company_ids, active_id(s)
  4. Check security: groups + access + record rules
  5. Trace query / compute: tìm N+1, depends sai, recompute bất thường

🧨 “Đau” nhất: lỗi phân quyền kiểu “user không thấy dữ liệu”

Cách xử lý nhanh:

  • Xác định model nào không thấy (ví dụ sale.order)
  • Kiểm tra ir.model.access.csv (read permission)
  • Kiểm tra record rule (domain filter)

Kiểm tra multi-company: record thuộc company nào, user đang ở company nào

📝 Mẹo “nhỏ nhưng mạnh”

  • Khi log, luôn log kèm: user_id, company_id, record ids, state
  • Khi sửa bug, thêm 1–2 test scenario/UAT step để tránh tái phát

🚀 4) Performance: vì sao Odoo chậm dần theo thời gian?

Odoo có thể chạy rất mượt ở giai đoạn demo (dữ liệu ít), nhưng khi lên production, số record tăng từ 1k → 100k → 1M, mọi “thói quen xấu” sẽ bộc lộ.

🐌 Nguyên nhân phổ biến #1: N+1 Query

Triệu chứng:

  • list view mở chậm
  • report chạy lâu
  • cron chạy càng ngày càng dài

Nguyên nhân:

  • loop từng record rồi mỗi vòng lại search, read, mapped không batch

✅ Cách nghĩ đúng:

  • gom dữ liệu bằng search_read, read_group, hoặc batch prefetch
  • hạn chế query trong vòng lặp

🧯 Nguyên nhân #2: Compute store “quá rộng”

store=True rất hữu ích, nhưng nếu depends không chặt, mỗi lần update 1 field sẽ khiến recompute hàng loạt.

✅ Tips:

  • depends càng cụ thể càng tốt
  • tách compute lớn thành nhiều compute nhỏ
  • cân nhắc compute on-demand nếu không cần filter/sort theo field đó

🧱 Nguyên nhân #3: Domain nặng + thiếu index

Các màn hình lọc theo những field không index sẽ càng ngày càng chậm.

✅ Tips:

  • xác định field hay lọc/sort trong bảng lớn
  • cân nhắc index (tuỳ phiên bản Odoo và chiến lược DB)
  • tránh domain “mơ hồ” kiểu search([]) hoặc domain quá rộng

📊 “Đo” trước khi tối ưu

Tối ưu mà không đo rất dễ… tối ưu nhầm chỗ.

Bạn nên có:

  • baseline: thời gian mở list view / chạy report
  • so sánh trước/sau: thời gian + số query

🧳 5) Migration (12 → 17/18/…): chia lớp để sống

Migration là phần “đốt ngân sách” nhanh nhất nếu bạn không có chiến lược. Một kế hoạch migration tốt không cần hoa mỹ, chỉ cần rõ ràng và kiểm soát rủi ro.

🧩 Tư duy đúng: migration là 3 lớp

  1. Data model: field, type, constraints, mapping dữ liệu
  2. Business logic: override, compute, workflows
  3. UI/Report: views, QWeb, JS/OWL custom

✅ Làm theo thứ tự:

  • đảm bảo data model ổn → logic ổn → UI/report hoàn chỉnh

🧨 Rủi ro hay gặp

  • Custom can thiệp sâu vào core workflow (stock/accounting)
  • QWeb/JS tự viết nhiều
  • record rule phức tạp (nhất là multi-company)
  • module phụ thuộc 3rd-party thay đổi

🗺️ Plan migration gợi ý (thực dụng)

  • Freeze requirement (đừng thêm scope giữa đường)
  • Migrate theo “nhóm nghiệp vụ”: Sales → Inventory → Accounting
  • Mỗi nhóm có UAT scenario end-to-end
  • Chốt “Definition of Done”:
    • không blocker
    • đối soát số liệu ok
    • performance đạt baseline

✅ Tổng kết: 10 thói quen giúp bạn “dev Odoo tăng uy tín”

  1. Viết module theo cấu trúc rõ ràng: tách bạch models/, views/, security/, data/, tránh nhồi logic vào view/JS.
  2. Ưu tiên _inherit để mở rộng trước khi override hành vi core (để code dễ đọc, dễ bảo trì, ít vỡ khi upgrade).
  3. Compute đúng dependency (@api.depends)luôn set giá trị cho toàn bộ recordset, tránh dữ liệu “stale” hoặc record cũ giữ giá trị sai.
  4. Luật nghiệp vụ bắt buộc đặt ở @api.constrains (backend), không dựa vào onchange vì import/API/cron có thể bypass UI.
  5. Override có super() và xử lý theo batch: code theo recordset, hạn chế query trong loop, tránh write/create từng dòng.
  6. Không lạm dụng sudo() để chữa cháy: nếu cần nâng quyền, phải có lý do rõ ràng và giới hạn phạm vi (đúng model/đúng bước).
  7. Debug theo quy trình: reproduce (đúng role) → check context → check security → kiểm tra data/state → trace query/compute.
  8. Tránh N+1: gom dữ liệu bằng batch, ưu tiên read_group cho thống kê/báo cáo thay vì tự cộng từng record.
  9. Tối ưu dựa trên đo lường: có baseline (thời gian, số query), tối ưu xong phải đo lại để chứng minh hiệu quả.
  10. Migration chia lớp & chia sprint: tách data model / business logic / UI-report, UAT theo flow end-to-end và chốt tiêu chí Done rõ ràng.
Guest