Có những việc không nên đợi ai bấm nút: chuyển thẻ quá hạn sang "hết hạn", gửi nhắc gia hạn mỗi sáng, dọn dữ liệu tạm ban đêm. Odoo lo phần này bằng ir.cron — "tác vụ đã lên lịch". Bạn khai một record trỏ tới method của model, đặt chu kỳ, và scheduler của Odoo tự gọi định kỳ. Bài này dựng một cron rà soát hạn thẻ, test nó mà không phải chờ, và chỉ ra vài field Odoo 19 đã bỏ khiến code cũ gãy.
Method chạy nền: tự tìm bản ghi
Khác với action chạy trên bản ghi người dùng chọn, method của cron không nhận active_ids — nó tự đi tìm việc. Ở đây: tìm mọi thẻ đang dùng mà ngày cấp đã quá hạn, rồi chuyển hết hạn:

Hình 1: Method cron_ra_soat_han_the không có tham số — vì khi cron gọi, self là recordset rỗng, nên method phải tự search ra việc. Bên dưới là record ir.cron: model_id trỏ model, state="code" với code gọi chính method đó qua biến model (recordset rỗng của model). interval_number + interval_type đặt chu kỳ. Bọc trong <data noupdate="1"> để nâng cấp module không ghi đè phần người vận hành đã chỉnh tay (đổi giờ chạy, tắt bật).
Một điểm Odoo 19 đã đổi khiến nhiều ví dụ cũ gãy: numbercall và doall đã bị bỏ. Tôi khai numbercall theo thói quen và module không cài được ("Invalid field 'numbercall' in 'ir.cron'"). Bỏ nó đi là xong; nextcall thì có sẵn default là thời điểm hiện tại nên không cần khai.
Xem tác vụ đã lên lịch
Sau khi cài, cron hiện trong Cài đặt → Kỹ thuật → Tác vụ đã lên lịch:

Hình 2: Cron dưới dạng form. Thực thi Mỗi 1 Ngày là chu kỳ; Đang hoạt động bật/tắt; Ngày thực hiện tiếp theo (nextcall) là lần chạy kế; Mức độ ưu tiên khi nhiều cron tới hạn cùng lúc. Tab Mã chứa model.cron_ra_soat_han_the(). Đáng chú ý nút Chạy thủ công — bấm là chạy ngay, cực tiện để thử mà không đợi tới giờ.
Test cron: đừng chờ scheduler
Chờ tới nextcall để biết cron đúng hay sai là cực hình. Cách thực dụng: gọi thẳng method trong odoo shell và đo kết quả:

Hình 3: Gọi thẳng The.cron_ra_soat_han_the() — nó trả về 1 (một thẻ chuyển hết hạn). Đo chính xác: VIP-0001 cấp ngày 03/09, quá 12 ngày so với hôm nay 18/09, nên bị chuyển; VIP-0003 cấp 17/09 mới một ngày nên không đụng tới. Logic lọc chạy đúng như thiết kế. (Đo xong tôi hoàn nguyên VIP-0001 về đang dùng để giữ dữ liệu demo.) Trên giao diện, nút "Chạy thủ công" làm đúng việc này mà không cần shell.
Vài lưu ý khi khai cron
noupdate="1"gần như bắt buộc choir.cron. Người vận hành hay chỉnh giờ chạy, tắt tạm khi bảo trì — nếu khôngnoupdate, mỗi lần-umodule là mọi chỉnh tay bị ghi đè về mặc định.- Method phải nhẹ và idempotent. Cron có thể chạy lại (server khởi động lại, chạy tay). Lọc đúng điều kiện (
trang_thai='dang_dung') để chạy hai lần không gây tác dụng phụ — thẻ đã hết hạn thì lần sau không khớp nữa. interval_typenhậnminutes,hours,days,weeks,months. Đừng đặt phút quá nhỏ cho việc nặng — cron chạy trong worker nền, việc dài làm nghẽn các cron khác.- Cron chạy dưới
user_id(mặc định OdooBot/superuser). Nếu cần quyền hạn chế hơn, đổi Scheduler User; nhưng thường để superuser cho nó thấy hết dữ liệu.
Ba ý mang về
ir.cronlà việc chạy nền theo lịch: một record trỏmodel_id+state="code"gọi method của model;interval_number/interval_typeđặt chu kỳ. Method tựsearchvìself/modellà recordset rỗng.- Odoo 19 đã bỏ
numbercallvàdoall— code cũ khai chúng sẽ không cài được;nextcallcó default nên không cần khai. - Test bằng cách gọi thẳng method (hoặc nút "Chạy thủ công"), đừng chờ scheduler; bọc record trong
<data noupdate="1">để giữ chỉnh tay của người vận hành.
Cron này mới chỉ gọi một method. Nhưng ô Mã cho phép viết hẳn một đoạn Python ngay tại đó — với những biến và giới hạn riêng. Phần sau đào vào cron thực thi code Python: state="code", các biến env/model/log có sẵn, và sandbox chặn gì.