Loạt bài qua ta chủ yếu thêm thứ mới. Nhưng nhu cầu hay gặp hơn cả là sửa thứ đã có: bạn muốn kanban của Odoo thêm một hành vi, muốn form controller làm thêm một việc khi lưu, muốn một widget lõi cư xử khác đi. Bạn không thể sửa file lõi (addons chỉ đọc, và sửa là mất khi nâng cấp), cũng không nên chép cả file ra rồi đăng ký đè (chép 500 dòng chỉ để đổi 3 dòng, rồi phải theo dõi mọi bản Odoo). Odoo cho một công cụ đúng cho việc này: patch.
patch làm gì
patch(objToPatch, extension) chèn các method trong extension vào objToPatch ngay tại chỗ, giữ nguyên mọi thứ khác. Vá Class.prototype là vá mọi instance của class đó — cả những cái do lõi hay module khác tạo ra. Trong method vá, super trỏ tới bản gốc, nên bạn gọi lại được logic cũ rồi thêm phần của mình:

Hình 1: Vá method setup của KanbanRenderer lõi. super.setup(...arguments) gọi bản gốc trước — bỏ dòng này là kanban mất toàn bộ khởi tạo và vỡ. Sau đó thêm phần của ta (đọc số bản ghi, log). patch nhận KanbanRenderer.prototype, nên bản vá áp cho mọi kanban trong hệ thống, không riêng module này.
Bằng chứng patch thật sự chạy
Khai file vào bundle, nâng cấp, rồi mở kanban thẻ thành viên (một view kanban có sẵn, ta không đụng tới arch của nó). Bắt console trình duyệt:

Hình 2: Console thật khi mở kanban (bắt bằng Playwright). Dòng [PATCH] ... 4 bản ghi chứng minh: setup() của KanbanRenderer lõi đã chạy qua bản vá, và đọc được this.props.list (4 thẻ đang hiển thị). Ta không chép kanban_renderer.js — chỉ thêm một setup mới, super.setup() vẫn thực thi toàn bộ khởi tạo gốc.
Điểm mấu chốt: view kanban đó là của Odoo/của model, không phải thứ ta viết. Vậy mà setup của nó chạy thêm code của ta — vì patch vá vào chính prototype dùng chung.
Ba quy tắc khi patch
- Luôn gọi
supertrong method vá (trừ khi cố ý thay hẳn, hiếm). Quênsuper.setup()là bạn thay thế bản gốc chứ không phải bổ sung, và component vỡ theo cách khó lần....argumentschuyển nguyên tham số xuống. - Patch prototype = áp cho mọi instance. Đây vừa là sức mạnh (đổi hành vi toàn hệ thống trong một chỗ) vừa là rủi ro (một lỗi trong bản vá làm hỏng mọi kanban, không riêng của bạn). Vá thứ dùng chung thì phải cực kỳ cẩn thận và phòng thủ (dùng
?.,??như trong ví dụ). - Patch chỉ để đổi hành vi thứ đã có. Cần một thành phần mới thì đăng ký vào registry (client action, field widget, view...) như các bài trước — đừng patch để "thêm". Patch là dao mổ cho việc sửa, không phải búa để đóng mọi đinh.
patch vs kế thừa vs registry
Ba cách mở rộng, ba việc khác nhau:
- Registry (
category(...).add): thêm một thành phần mới (view, field, action, service). - Kế thừa + spread (
{ ...kanbanView, Renderer }): tạo một biến thể dùng quajs_class, không đụng bản gốc — chỉ nơi nào khaijs_classmới nhận. - patch: sửa tại chỗ bản gốc, áp cho tất cả ngay lập tức, kể cả code không biết tới bạn.
Chọn patch khi bạn thật sự muốn đổi hành vi mặc định ở khắp nơi; chọn kế thừa khi chỉ muốn một biến thể cục bộ.
Ba ý mang về
patch(objToPatch, extension)sửa một class/component có sẵn ngay tại chỗ — không chép file, không mất khi nâng cấp. VáClass.prototypeáp cho mọi instance, kể cả của lõi và module khác.- Trong method vá,
supergọi bản gốc. Gần như luôn phảisuper.method(...arguments)để giữ hành vi cũ rồi mới thêm phần của mình; quên là component vỡ. - Patch để sửa, registry để thêm, kế thừa js_class để tạo biến thể cục bộ. Vì patch áp toàn hệ thống, một lỗi trong bản vá làm hỏng mọi chỗ — vá phòng thủ và chỉ khi thật cần.
Ta đã dùng notification.add nhiều lần mà chưa mổ kỹ. Phần sau đào vào notification service: các loại thông báo (thành công, cảnh báo, lỗi), thông báo dính (sticky), nút hành động trong thông báo, và khi nào nên dùng nó thay cho dialog.