Phần trước cron chỉ gọi một dòng model.cron_ra_soat_han_the(). Nhưng ô Mã của cron thật ra nhận cả một đoạn Python nhiều dòng — bạn viết logic thẳng ở đó, không cần method riêng. Chỉ có điều: đoạn code này chạy trong một sandbox. Odoo đưa sẵn một số biến, và chặn phần lớn những gì Python bình thường cho phép. Biết chính xác có gì và thiếu gì là điều kiện để không ngồi sửa mù.
Viết Python thẳng trong ô Mã
state="code" nghĩa là code được safe_eval(..., mode="exec") — chạy như một script. Đây là đoạn thay cho việc gọi method:

Hình 1: Cùng logic rà soát hạn thẻ, nhưng viết thẳng trong ô Mã thay vì gọi method. Chú ý ngay dòng đầu: không có fields trong sandbox, nên fields.Date.today() sẽ lỗi — phải dùng datetime.date.today(). model là recordset rỗng của model gắn với cron, nên ta search để tự tìm việc. log(...) ghi một dòng vào bảng nhật ký.
Chính xác có những biến gì
Đừng đoán — Odoo dựng một eval_context cố định. Lấy đúng nó ra và liệt kê:

Hình 2: Danh sách biến thật mà _get_eval_context cấp cho code của cron/server action trong Odoo 19. Tôi chạy chính đoạn code qua safe_eval với ngữ cảnh đó: VIP-0001 (đang dùng, cấp 03/09) chuyển hết hạn, và log(..., level="warning") ghi thật một dòng ir.logging đọc lại được. Sau khi đo tôi hoàn nguyên thẻ và tắt cron để scheduler nền không tiếp tục đổi dữ liệu demo.
Nhóm lại cho dễ nhớ:

Hình 3: Thẻ tra cứu. Nhớ hai điều: record/records chỉ có khi chạy từ một bản ghi/lựa chọn (server action trên list/form) — với cron thuần thì chúng là None, nên cron phải dùng model.search. Và Command có sẵn để thao tác trường quan hệ nhiều-nhiều ngay trong code (Command.link, Command.create...).
Sandbox chặn gì
safe_eval không phải Python đầy đủ. Nó chặn có chủ đích để một dòng code cron không thể phá hệ thống:
- Không
import. Muốnrequests,os,subprocess? Không được. Chỉ có các tên trongeval_context. Cần thư viện ngoài thì viết vào method của module (Python thật) rồi cron gọi method đó. - Không truy cập
__dunder__(__class__,__globals__...) — chặn đường leo ra khỏi sandbox. - Không mở file, không
exec/evallồng nhau. log(message, level)là kênh ghi vết chính:levelnhận'info','warning','error'... và dòng ghi hiện ở Cài đặt → Kỹ thuật → Nhật ký. Rất hữu ích để biết cron đã chạy và làm gì mà không cần chờ.
Quy tắc thực dụng: code trong ô Mã nên ngắn — điều phối, gọi method, ghi log. Logic phức tạp (nhiều bước, cần thư viện, cần test) thì đặt trong method Python của module rồi cron chỉ gọi một dòng. Ô Mã tiện cho việc nhỏ và cho người vận hành chỉnh nhanh mà không phải sửa mã nguồn.
Ba ý mang về
state="code"chạy Python nhiều dòng quasafe_evalvớieval_contextcố định:env,model(recordset rỗng),record/records,log,datetime,dateutil,UserError,Command,b64encode... — không cófields, nên dùngdatetime.date.today().- Sandbox chặn
import,__dunder__, mở file,exec— cần thư viện ngoài thì viết vào method module rồi cron gọi method. log(msg, level)ghiir.logging(xem ở Nhật ký) — kênh theo dõi cron đã chạy; giữ code trong ô Mã ngắn gọn, đẩy logic nặng vào method.
Cron chạy theo đồng hồ. Nhưng có một cơ chế khác chạy theo sự kiện dữ liệu — mỗi khi một bản ghi được tạo/sửa. Phần sau so sánh Automated Action với cron: cái nào chạy theo lịch, cái nào theo trigger, và khi nào dùng cái nào.