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:

Ảnh chụp đoạn mã Python nền tối chạy trong ô Mã của cron, chú thích không có fields nên dùng datetime bản an toàn có date và timedelta, gán han bằng datetime date today trừ timedelta 12 ngày, chú thích model là recordset rỗng của model_id nên tự search, gán qua_han bằng model search theo trang_thai bằng dang_dung và ngay_cap nhỏ hơn hoặc bằng han, gọi qua_han write đặt trang_thai het_han, chú thích log ghi vào ir logging xem ở Cài đặt Kỹ thuật Nhật ký, gọi log với chuỗi Cron ra soat phần trăm d the het han và level warning, và chú thích raise UserError để báo lỗi gán action để trả về một action

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ê:

Ảnh chụp phiên odoo shell nền tối, gọi sa gạch dưới get_eval_context của sa gán vào ctx với chú thích đúng ngữ cảnh Odoo cấp cho cron, sorted các khoá của ctx in ra danh sách Command UserError gạch dưới logger b64decode b64encode datetime dateutil env float_compare json log model record records request time timezone uid user, gọi safe_eval của code với ctx mode exec với chú thích VIP-0001 dang_dung chuyển het_han, đọc ir logging mới nhất trả về chuỗi Cron ra soat 1 the het han với chú thích log level warning đã ghi thật, và chú thích đo xong hoàn nguyên thẻ và tắt cron để scheduler không đổi dữ liệu demo

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ớ:

Ảnh thẻ tra cứu nền tối liệt kê các biến có sẵn khi cron hoặc server action chạy code trên Odoo 19 chia bốn nhóm. Nhóm ORM gồm env là môi trường, model là recordset rỗng của model_id, record là một bản ghi từ active_id, records là nhiều từ active_ids, user và uid. Nhóm Trợ giúp gồm log nhận message và level ghi ir logging, _logger là logger hệ thống, Command cho trường x2many, float_compare, b64encode và b64decode, json. Nhóm Ngày giờ gồm datetime có date và timedelta, dateutil, time, timezone. Nhóm Lỗi gồm UserError raise để báo người dùng, và request. Dòng cuối ghi sandbox chặn import tuỳ tiện truy cập dunder mở file exec eval chỉ dùng được các tên trên

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ốn requests, os, subprocess? Không được. Chỉ có các tên trong eval_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/eval lồng nhau.
  • log(message, level) là kênh ghi vết chính: level nhậ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ề

  1. state="code" chạy Python nhiều dòng qua safe_eval với eval_context cố định: env, model (recordset rỗng), record/records, log, datetime, dateutil, UserError, Command, b64encode... — không có fields, nên dùng datetime.date.today().
  2. 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.
  3. log(msg, level) ghi ir.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.