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:

Ảnh chụp đoạn mã nền tối hai phần. Phần trên data sequence.xml khai record ir.sequence id seq_the_thanh_vien với code quan.the.thanh.vien mã để gọi, prefix TV gạch ngang phần trăm year phẩy s gạch ngang với chú thích phần trăm year s là năm hiện tại, padding 4 với chú thích đệm 0 ra 0001, implementation standard với chú thích standard hoặc no_gap. Phần dưới models override create với api model_create_multi def create self vals_list lặp qua vals lấy ten từ name strip nếu rỗng hoặc kết thúc bằng gạch ba thăng thì gán vals name bằng self env ir.sequence next_by_code quan.the.thanh.vien, cuối cùng return super create vals_list

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:

Ảnh chụp phiên odoo shell nền tối, The create hang_the bac ra TV-2026-0001, The create hang_the vang ra TV-2026-0002, rồi chú thích tiêu 1 số nhưng không tạo thẻ mô phỏng giao dịch rollback, gọi env ir.sequence next_by_code quan.the.thanh.vien trả về TV-2026-0003 màu hồng với chú thích số này đã bị tiêu không quay lại, tiếp theo The create hang_the bac ra TV-2026-0004 với chú thích nhảy 0003 khoảng trống, và chú thích standard nhanh nhưng có khoảng trống cần liền mạch hoá đơn thì dùng no_gap

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:

Ảnh sơ đồ so sánh hai cột trên nền kem tiêu đề Hai chế độ ir.sequence chọn theo nhu cầu. Cột trái standard: cơ chế PostgreSQL sequence, tốc độ nhanh không khoá hàng, khoảng trống CÓ vì rollback vẫn tiêu số, hợp cho mã nội bộ thẻ ticket trống vài số không sao. Cột phải no_gap: cơ chế đọc ghi bản ghi và khoá hàng, tốc độ chậm hơn khi tải cao vì nối hàng, khoảng trống KHÔNG liền mạch tuyệt đối, hợp cho hoá đơn chứng từ pháp lý cần liên tục. Dòng dưới ghi đo thật với standard tiêu số 0003 rồi tạo thẻ tiếp ra TV-2026-0004, 0003 mất luôn, số hoá đơn thì không được phép nhảy như vậy

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_code tiê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úc create chắc chắn xảy ra.
  • prefix nhậ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-...).
  • padding chỉ là hiển thị. padding=4 cho 0001; khi vượt 9999 nó vẫn chạy tiếp 10000, 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ã trong create (như trên) mới đúng thời điểm.

Ba ý mang về

  1. ir.sequence cấp mã tăng dần (code, prefix kèm %(year)s, padding); lấy số bằng next_by_code(...), gọi trong create override — sequence lo phần đếm và chống trùng.
  2. Chế độ standard có 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ộ.
  3. 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.