Mỗi thẻ, mỗi đơn hàng, mỗi hoá đơn đều có một mã tăng dần: TV-2026-0001, 0002, 0003... Ai sinh ra con số đó, và làm sao đảm bảo hai người tạo cùng lúc không trùng mã? Câu trả lời là ir.sequence — bộ đếm của Odoo. Nhưng nó có một hành vi khiến nhiều người hoảng: mã đôi khi nhảy số, từ 0002 vọt sang 0004. Không phải bug — đó là đánh đổi có chủ đích, và hiểu nó quyết định bạn dùng đúng chế độ.
Khai một sequence và gắn vào create
Sequence là một bản ghi cấu hình: có code để gọi, prefix (kèm placeholder năm), padding (đệm số 0). Rồi override create để cấp mã tự động:

Hình 1: ir.sequence với code="quan.the.thanh.vien", prefix="TV-%(year)s-" (Odoo thay %(year)s bằng năm hiện tại), padding=4. Trong create (override kiểu @api.model_create_multi), khi mã chưa có hoặc còn là gợi ý -###, ta gọi next_by_code('quan.the.thanh.vien') để xin số kế tiếp. Đây là cách chuẩn: sequence lo phần đếm và chống trùng, model chỉ việc lấy số khi tạo bản ghi.
Đo: mã tự tăng, và chỗ nó nhảy số
Tạo vài thẻ rồi xem mã — kèm một tình huống cố ý để lộ khoảng trống:

Hình 2: Hai thẻ đầu ra TV-2026-0001 và 0002 — đúng như mong đợi. Rồi tôi gọi next_by_code một lần mà không tạo bản ghi (mô phỏng một giao dịch xin số xong rồi rollback): số 0003 bị tiêu mất. Thẻ tạo tiếp theo nhận TV-2026-0004 — nhảy qua 0003. Đây chính là "khoảng trống" mà chế độ standard để lại.
Vì sao lại thế? Chế độ standard dùng sequence của PostgreSQL — cực nhanh và không khoá hàng, nhưng bản chất của nó là "cấp số rồi quên": đã cấp 0003 thì dù giao dịch của bạn rollback, số đó không được trả lại. Đổi lấy tốc độ, ta chấp nhận vài lỗ hổng trong dãy số.
standard hay no_gap
Odoo cho chọn giữa hai chế độ, và lựa chọn phụ thuộc vào việc dãy số của bạn có được phép trống hay không:

Hình 3: standard nhanh, không khoá hàng, nhưng có khoảng trống — hợp cho mã nội bộ (thẻ thành viên, ticket) nơi trống vài số chẳng ai bận tâm. no_gap đảm bảo dãy số liền mạch tuyệt đối bằng cách khoá hàng khi cấp số, nên chậm hơn khi nhiều người tạo cùng lúc (họ phải xếp hàng) — nhưng bắt buộc cho hoá đơn, chứng từ pháp lý mà luật đòi đánh số liên tục không được nhảy.
Vài lưu ý khi dùng
next_by_codetiêu số ngay khi gọi. Đừng gọi nó rồi mới kiểm tra điều kiện có tạo bản ghi hay không — nếu bỏ, số đã mất. Gọi ở bước cuối, sát lúccreatechắc chắn xảy ra.prefixnhận placeholder ngày:%(year)s,%(month)s,%(day)s, và%(range_year)s(theo khoảng ngày cấu hình) — tiện cho mã reset theo năm (TV-2026-...,TV-2027-...).paddingchỉ là hiển thị.padding=4cho0001; khi vượt9999nó vẫn chạy tiếp10000, không tràn.- Đừng để mã sinh ở
default_get. Default chạy khi mở form, kể cả khi người dùng bấm Huỷ — sẽ tiêu số vô ích. Cấp mã trongcreate(như trên) mới đúng thời điểm.
Ba ý mang về
ir.sequencecấp mã tăng dần (code,prefixkèm%(year)s,padding); lấy số bằngnext_by_code(...), gọi trongcreateoverride — sequence lo phần đếm và chống trùng.- Chế độ
standardcó khoảng trống: dùng PostgreSQL sequence, nhanh, không khoá, nhưng rollback vẫn tiêu số (đo thật:0002→0004). Chấp nhận được với mã nội bộ. - Cần liền mạch tuyệt đối (hoá đơn, chứng từ pháp lý) thì dùng
no_gap— khoá hàng, chậm hơn khi tải cao, đổi lấy dãy số không nhảy.
Mã số là một thuộc tính của bản ghi. Nhưng bản ghi còn cần đính kèm — hợp đồng scan, ảnh CMND, PDF hoá đơn. Phần sau mổ xẻ ir.attachment — đính kèm file trong code: lưu ở đâu, gắn vào bản ghi nào, và cách tạo/đọc từ Python.