Mục lục bài viết25 mục
- 1.Phạm vi MVP và nguyên tắc cắt giảm
- 2.Danh mục tính năng chi tiết theo module cho MVP
- 3.Tính năng bổ sung khuyến nghị và cờ bật tắt
- 4.Quy trình nhập liệu hóa đơn mua bán hằng ngày, duyệt và ghi sổ
- 5.Luồng kỹ thuật ghi sổ an toàn đúng một lần
- 6.Quản lý công nợ AR AP, hạn mức và nhắc nợ
- 7.Quản lý hàng tồn kho và định giá xuất kho
- 8.Quy tắc đánh số chứng từ, kỳ kế toán và khóa sổ
- 9.Báo cáo vận hành tối thiểu
- 10.Phân quyền, phân tách dữ liệu và nhật ký kiểm toán
- 11.Kiến trúc triển khai on-prem web cho MVP
- 12.Whitelist IP theo lớp và truy cập ngoài trụ sở qua VPN
- 13.Bảo mật dữ liệu, sao lưu và khôi phục sau sự cố
- 14.Giao diện nhập liệu tối ưu cho kế toán
- 15.Kiểm soát chất lượng dữ liệu trước ghi sổ
- 16.Tích hợp AI kiểm tra đầu vào, tiêu chí cảnh báo, giải thích và fallback
- 17.Luồng AI validate token và SLA độ trễ
- 18.Nhập khẩu dữ liệu và tích hợp cơ bản CSV Excel, e invoice và ngân hàng
- 19.Tùy biến cấu hình hệ thống và mở rộng có điều kiện
- 20.Kế hoạch kiểm thử, UAT, dữ liệu mẫu và KPI chất lượng dữ liệu
- 21.Vận hành, support, log và giám sát hệ thống
- 22.Lộ trình MVP đến mở rộng theo MoSCoW và tiêu chí Go Live
- 23.Checklist phỏng vấn yêu cầu còn mở và mặc định MVP
- 24.Lỗi thường gặp và lưu ý khi đặc tả on prem, IP và AI
- 25.Kết luận
Trong tài liệu này, mình dẫn bạn từ yêu cầu tới đặc tả MVP on-prem: module kế toán cốt lõi, luồng nhập-ghi sổ, kho và công nợ, đánh số và kỳ, báo cáo, bảo mật triển khai, whitelist IP và VPN, AI kiểm tra trước ghi sổ, import CSV. Các phần chưa chốt dùng mặc định MVP để minh họa.
Phạm vi MVP và nguyên tắc cắt giảm
Mục tiêu của MVP là chạy trơn các nghiệp vụ mua bán hàng ngày, công nợ, kho và ghi sổ tổng trên máy chủ tại công ty, có giới hạn IP và bước AI kiểm tra trước khi ghi sổ. Mình liệt kê module cốt lõi bắt buộc, kèm các giả định mặc định tối thiểu. Những hạng mục chưa xác định gắn nhãn Tùy chọn cần hỏi tiếp để bạn chốt ở vòng phỏng vấn yêu cầu.
Module cốt lõi bắt buộc cho MVP
- Danh mục và cấu hình nền tảng: danh mục tài khoản, khách hàng, nhà cung cấp, hàng hóa, đơn vị tính, kho, thuế suất cơ bản, điều khoản thanh toán, người dùng và vai trò.
- Mua hàng: phiếu nhập mua, hóa đơn mua, chiết khấu dòng và chứng từ, cập nhật công nợ phải trả, liên kết kho.
- Bán hàng: phiếu xuất bán, hóa đơn bán, công nợ phải thu, chiết khấu theo dòng và theo hóa đơn.
- Kho: nhập, xuất, điều chuyển nội bộ, kiểm kê cơ bản, chặn âm kho, tính giá xuất kho bình quân tức thời.
- Công nợ AR/AP: theo dõi hóa đơn, thanh toán, hạn mức tín dụng theo đối tác, tuổi nợ, nhắc nợ cơ bản.
- Sổ cái: tự động hạch toán từ bán hàng, mua hàng, kho; bút toán tổng hợp; mở và khóa kỳ; quy tắc đánh số chứng từ.
- Báo cáo vận hành tối thiểu: sổ chi tiết công nợ phải thu phải trả, thẻ kho, tồn kho theo kho – mặt hàng, nhật ký mua – bán, tổng hợp thuế ở mức cơ bản không đặc thù quốc gia.
- Hệ thống và bảo mật: phân quyền theo vai trò và phạm vi dữ liệu, nhật ký kiểm toán thao tác, triển khai on-prem web, whitelist IP ở firewall và reverse proxy, không expose Internet, truy cập từ xa qua VPN.
- AI kiểm tra đầu vào: rule cứng + phát hiện bất thường thống kê trên dữ liệu có cấu trúc, chỉ cảnh báo trước khi ghi sổ, fail open khi quá thời gian.
- Nhập khẩu và trao đổi dữ liệu: import CSV/Excel cho danh mục và số dư đầu kỳ, export CSV cơ bản.
Giả định mặc định tối thiểu cho MVP
- Mô hình triển khai: web client nội bộ qua HTTPS, backend và DB chạy on-prem, reverse proxy làm điểm chặn, truy cập ngoài trụ sở bắt buộc VPN full tunnel.
- Phạm vi nghiệp vụ: 1 trụ sở, 1 kho, đơn tiền tệ, không quản lý lô – seri, không cho âm kho, phương pháp xuất kho bình quân tức thời.
- AI: on-prem, chỉ cảnh báo, không chặn ghi sổ, hiển thị giải thích ngắn, tự hạ cấp mức cảnh báo khi trễ.
- Tích hợp: chưa tích hợp hóa đơn điện tử, chưa tích hợp ngân hàng, chưa OCR, import CSV/Excel là chính.
- Báo cáo: tập trung báo cáo vận hành và kiểm soát hàng ngày, chưa làm BI nâng cao.
- Hiệu năng mục tiêu: quy mô người dùng đồng thời nhỏ trong SME, ưu tiên độ ổn định và đúng sổ sách hơn tiện ích nâng cao.
Tùy chọn cần hỏi tiếp
- Chuẩn kế toán và yêu cầu thuế theo quốc gia để cấu hình tài khoản, thuế suất, biểu mẫu.
- Phạm vi hóa đơn điện tử và tích hợp với cơ quan thuế.
- Số lượng kho, số chi nhánh và quy tắc phân tách dữ liệu theo đơn vị.
- Đa tiền tệ, tỷ giá, và chính sách đánh giá chênh lệch.
- Phương pháp tính giá xuất kho ưu tiên nếu không dùng bình quân tức thời, và cho phép âm kho hay không.
- Quản lý số lô, số seri, hạn dùng.
- Số lượng người dùng đồng thời, mục tiêu SLA hiệu năng và dung lượng dữ liệu.
- Có cần desktop client thay cho web client và yêu cầu đặc thù thiết bị.
- Có cần OCR đọc hóa đơn scan, mức độ chính xác mong đợi và quy trình hiệu đính.
- Tích hợp ngân hàng và đối soát, cơ chế nhận sao kê và tần suất.
- Mô hình AI cụ thể, tiêu chí cảnh báo, cơ chế gán nhãn hành vi và xử lý ngoại lệ.
- Chính sách lưu trữ dữ liệu, thời gian lưu audit log và tiêu chuẩn tuân thủ.
Ví dụ điền sẵn cho MVP
- Đơn vị: Công ty TNHH ABC, 1 trụ sở Hà Nội.
- Kho: KHO_TONG.
- Tiền tệ: VND.
- Tính giá xuất kho: bình quân tức thời.
- Công nợ: hạn mức cố định theo khách hàng – nhà cung cấp, cảnh báo quá hạn 7 – 15 – 30 ngày.
- Truy cập mạng: LAN nội bộ 192.168.10.0/24, pool VPN 10.255.0.0/24.
- AI: rule cứng + phát hiện bất thường, cảnh báo High – Medium – Low, không chặn.
- Import: danh mục hàng hóa, đối tác và số dư đầu kỳ qua CSV chuẩn.
Biến thể có biến số để bạn thay thế
- BRANCHCOUNT, WAREHOUSECOUNT, CURRENCY, COSTMETHOD, ALLOWNEGATIVESTOCK, LOTSERIALENABLED, LANCIDRLIST, VPNPOOLCIDR, OCRENABLED, EINVOICESCOPE, BANKRECONSCOPE, CONCURRENCYTARGET, DATARETENTIONMONTHS.
Danh mục tính năng chi tiết theo module cho MVP
Dưới đây là bảng tính năng dạng template cho MVP. Mỗi dòng gồm Module | Tính năng | Mô tả ngắn | Điều kiện kích hoạt | Tiêu chí chấp nhận | Mức độ. Danh sách đã điền sẵn để bạn giao cho đội phát triển ước lượng và thiết kế.
Bảng tính năng MVP điền sẵn
- Bán hàng | Phiếu xuất bán trực tiếp | Lập phiếu xuất kho bán hàng, cập nhật tồn và tạo bút toán doanh thu, giá vốn | Kỳ kế toán đang mở, quyền Post_Sales, hàng hóa có trong danh mục | Lưu nháp, ghi sổ thành công, trừ tồn đúng kho, sinh bút toán doanh thu và giá vốn cân đối | Must
- Bán hàng | Hóa đơn bán | Phát hành hóa đơn bán liên kết phiếu xuất hoặc độc lập | Quy tắc đánh số đã cấu hình, kỳ đang mở | Tổng hóa đơn bằng tổng dòng, thuế tính đúng, phát sinh công nợ AR | Must
- Bán hàng | Chiết khấu dòng và hóa đơn | Áp chiết khấu theo dòng và tổng chứng từ | Bật tham số chiết khấu trong hệ thống | Thành tiền sau chiết khấu, thuế trên giá sau chiết, hạch toán chênh lệch theo tài khoản cấu hình | Should
- Bán hàng | Chặn bán vượt hạn mức | Kiểm tra hạn mức và tuổi nợ trước khi ghi sổ | Thiết lập hạn mức khách hàng, bật chính sách chặn | Vượt hạn mức hoặc nợ quá hạn bị chặn hoặc yêu cầu override theo vai trò, log đầy đủ | Must
- Mua hàng | Phiếu nhập mua | Lập phiếu nhập kho từ nhà cung cấp, cập nhật tồn | Kỳ đang mở, quyền Post_Purchase | Tăng tồn đúng kho, hạch toán theo sơ đồ tài khoản đã cấu hình, cho phép lưu nháp và ghi sổ | Must
- Mua hàng | Hóa đơn mua | Ghi nhận hóa đơn nhà cung cấp, liên kết phiếu nhập | Có phiếu nhập hoặc nhập trực tiếp, số hóa đơn duy nhất theo NCC trong kỳ mở | Phát sinh công nợ AP, thuế khấu trừ theo mã thuế, khớp tổng và thuế | Must
- Mua hàng | Cảnh báo chênh lệch PO, GR, Invoice | Cảnh báo lệch giá hoặc số lượng giữa đơn, nhận, hóa đơn | Có liên kết giữa PO, phiếu nhập, hóa đơn, bật ngưỡng cảnh báo | Lệch vượt ngưỡng hiển thị cảnh báo High, bắt buộc người duyệt xác nhận | Should
- Mua hàng | Đơn đặt hàng nội bộ | Tạo PO nội bộ trước nhập | Bật module PO | Chuyển PO thành phiếu nhập, theo dõi trạng thái PO, đóng PO khi đủ | Could
- Kho | Nhập kho khác | Nhập điều chỉnh tăng không qua mua | Quyền Kho_Adjust | Tăng tồn theo lý do điều chỉnh, hạch toán tài khoản điều chỉnh đúng cấu hình | Should
- Kho | Xuất kho khác | Xuất điều chỉnh giảm không qua bán | Quyền Kho_Adjust | Không cho âm kho, giảm tồn và hạch toán điều chỉnh đúng | Should
- Kho | Điều chuyển nội bộ | Chuyển hàng giữa kho nguồn và kho đích | Có tối thiểu 2 kho trong danh mục | Tồn giảm kho nguồn, tăng kho đích, giữ nguyên giá trị, số tham chiếu đối ứng | Should
- Kho | Kiểm kê và điều chỉnh | Ghi nhận chênh lệch kiểm kê thực tế | Có phiếu kiểm kê đã tạo | Tự sinh phiếu điều chỉnh tăng, giảm theo kết quả kiểm kê, log người duyệt | Should
- Kho | Chặn âm kho | Ngăn ghi chứng từ làm tồn âm theo mặt hàng, kho | Bật kiểm soát tồn, cấu hình phương pháp tính giá | Ghi sổ bị từ chối nếu tồn sau xuất < 0, hiển thị lý do | Must
- Công nợ | Phát sinh AR tự động | Tạo phát sinh phải thu từ hóa đơn bán | Hóa đơn bán đã ghi sổ | Sổ chi tiết AR cập nhật đúng đối tượng, đối chiếu khớp sổ cái | Must
- Công nợ | Phát sinh AP tự động | Tạo phát sinh phải trả từ hóa đơn mua | Hóa đơn mua đã ghi sổ | Sổ chi tiết AP cập nhật đúng đối tượng, đối chiếu khớp sổ cái | Must
- Công nợ | Thu tiền, Chi tiền, Cấn trừ | Ghi nhận thanh toán và cấn trừ theo hóa đơn | Danh mục tài khoản tiền đã cấu hình | Số dư công nợ giảm đúng, hỗ trợ cấn trừ nhiều hóa đơn, chênh lệch nhỏ hạch toán điều chỉnh | Must
- Công nợ | Hạn mức tín dụng và tuổi nợ | Thiết lập hạn mức, chặn bán theo chính sách, nhắc nợ | Thiết lập hạn mức, lịch nhắc | Báo cáo vượt hạn mức, danh sách nhắc nợ theo 0-30-60-90 ngày, gửi thông báo nội bộ | Should
- Sổ cái | Hạch toán tự động từ nghiệp vụ | Sinh bút toán từ mua, bán, kho | Cấu hình sơ đồ định khoản | Bút toán cân đối, có tham chiếu chứng từ gốc, drill back được | Must
- Sổ cái | Bút toán tổng hợp thủ công | Nhập, duyệt, ghi sổ bút toán tay | Quyền GL_Manual và phê duyệt | Kiểm tra cân đối Nợ = Có, lưu nháp, phê duyệt, ghi sổ, audit đầy đủ | Should
- Sổ cái | Mở và khóa kỳ | Quản lý trạng thái kỳ kế toán | Quyền Kế toán trưởng | Chỉ chứng từ trong kỳ mở được ghi, mọi lần khóa mở đều có audit | Must
- Sổ cái | Sơ đồ định khoản tham số hóa | Khai báo mapping tài khoản theo loại chứng từ | Hoàn tất cấu hình | Cho phép đổi mapping không cần deploy, có lịch sử thay đổi | Should
- Hệ thống | Danh mục nền | Quản lý tài khoản, đối tác, hàng hóa, kho, thuế, điều khoản thanh toán | Quyền Admin | Tạo, sửa, vô hiệu hóa, ràng buộc tham chiếu chặt chẽ, kiểm tra trùng mã | Must
- Hệ thống | Phân quyền và phạm vi dữ liệu | Vai trò theo chức năng và theo chi nhánh, kho | Vai trò được tạo, người dùng gán vai trò | Người dùng chỉ thấy và tác động trong phạm vi được cấp, MFA với quyền ghi sổ | Must
- Hệ thống | Whitelist IP và VPN | Chỉ cho phép dải IP nội bộ và IP VPN truy cập | Reverse proxy đã cấu hình allowlist, VPN cấp IP từ pool riêng | Kết nối ngoài dải bị chặn tại proxy, log IP nguồn, thời gian, kết quả | Must
- Hệ thống | Nhật ký kiểm toán | Ghi log bất biến thao tác quan trọng | Bật audit trong ứng dụng và DB | Log gồm user, thời điểm, hành động, dữ liệu chính, không thể sửa, chỉ thêm | Must
- Hệ thống | Quy tắc đánh số chứng từ | Định dạng số theo năm, tháng, module, chi nhánh | Khai báo quy tắc theo module | Phát số tuần tự, không trùng trong kỳ, hỗ trợ reserve số an toàn | Must
- Hệ thống | Idempotency commit | Đảm bảo ghi sổ đúng một lần khi mạng chập chờn | API commit dùng idempotency_key | Retry không nhân bản, trả cùng biên nhận, có bảng theo dõi trạng thái | Must
- Báo cáo | Sổ chi tiết AR/AP | Theo dõi công nợ theo đối tượng và hóa đơn | Đã có phát sinh AR, AP | Số dư đầu, phát sinh, số dư cuối khớp sổ cái, lọc theo kỳ và đối tượng | Must
- Báo cáo | Thẻ kho | Biến động tồn theo mặt hàng, kho | Chứng từ kho đã ghi | Tồn đầu, nhập, xuất, tồn cuối chính xác, drill về chứng từ | Must
- Báo cáo | Nhật ký mua, bán | Danh sách chứng từ mua, bán theo ngày | Chọn kỳ cần xem | Lọc, sắp xếp, tổng hợp số tiền và thuế, xuất CSV | Should
- Báo cáo | Tổng hợp thuế cơ bản | Tổng hợp VAT đầu vào, đầu ra theo mã thuế | Mã thuế đã cấu hình | Số liệu khớp sổ cái, có đối chiếu nhanh chênh lệch | Should
- AI kiểm tra | Rule cứng trước ghi sổ | Bắt buộc trường, khớp tổng, ngày trong kỳ, trùng số hóa đơn | Bật rule set | Vi phạm bị chặn, hiển thị lý do rõ ràng, có quyền override theo vai trò | Must
- AI kiểm tra | Phát hiện bất thường thống kê | Outlier đơn giá, tổng hóa đơn, tài khoản bất thường | Đủ dữ liệu lịch sử tối thiểu, bật AI | Hiện cảnh báo Low, Medium, High, không chặn, p95 ≤ 1.2 s khi ghi sổ | Must
- AI kiểm tra | Giải thích cảnh báo | Lý do ngắn gọn và gợi ý sửa | Bật explain | Mỗi cảnh báo có 1 giải thích và 1 gợi ý hành động, người dùng có thể Proceed | Should
- AI kiểm tra | Fail-open và tự hạ cấp | Không chặn nghiệp vụ khi AI lỗi hoặc trễ | Cấu hình SLA | Tự ẩn cảnh báo Low khi vượt ngưỡng trễ, log sự kiện và khôi phục khi ổn | Must
- Import Export | Import danh mục CSV, Excel | Nạp khách hàng, nhà cung cấp, hàng hóa, kho | File theo mẫu, quyền Import | Kiểm tra dữ liệu, báo lỗi theo dòng, import idempotent theo mã | Must
- Import Export | Import số dư đầu kỳ | Nạp số dư công nợ và tồn kho ban đầu | Chưa phát sinh trong kỳ đầu | Số dư sau import khớp báo cáo kiểm tra, có biên bản đối soát | Must
- Import Export | Export CSV | Xuất nhật ký mua, bán, sổ chi tiết, thẻ kho | Quyền Export | File CSV UTF-8, dấu phân cách cấu hình, số thập phân chuẩn | Must
- Import Export | Template mapping tùy biến | Lưu cấu hình ánh xạ cột theo nguồn | Bật Custom Mapping | Định nghĩa và tái sử dụng mapping, kiểm tra bắt buộc trường, log thay đổi | Should
Biến thể template tham số hóa
- MODULENAME | FEATURENAME | SHORTDESCRIPTION | ACTIVATIONCONDITION | ACCEPTANCE_CRITERIA | MOSCOW
- Bán hàng | FEATURENAME | SHORTDESCRIPTION | ROLEORFLAGREQUIRED | ACCEPTANCECRITERIA | MOSCOW
- Mua hàng | FEATURENAME | SHORTDESCRIPTION | ROLEORFLAGREQUIRED | ACCEPTANCECRITERIA | MOSCOW
- Kho | FEATURENAME | SHORTDESCRIPTION | ROLEORFLAGREQUIRED | ACCEPTANCECRITERIA | MOSCOW
- AI kiểm tra | FEATURENAME | SHORTDESCRIPTION | DATAHISTORYREQUIREMENT | ACCEPTANCE_CRITERIA | MOSCOW
Tính năng bổ sung khuyến nghị và cờ bật tắt
Những tính năng dưới đây giúp mở rộng năng lực vận hành mà không làm phức tạp MVP. Mình đề xuất triển khai theo cờ bật tắt, có điều kiện sẵn sàng rõ ràng và lộ trình cuốn chiếu để tránh gián đoạn.
- OCR hóa đơn
- Mục tiêu: đọc PDF ảnh hóa đơn để điền sẵn các trường số hóa đơn, ngày, nhà cung cấp, dòng tiền hàng, thuế.
- Cách làm thực dụng on-prem: 1 service OCR chạy nội bộ, đọc file đính kèm kéo thả hoặc hộp thư nội bộ chuyển tiếp, đẩy kết quả vào form ở trạng thái Nháp để người dùng soát lại.
- Điều kiện sẵn sàng: bộ mẫu 100 hóa đơn phổ biến của doanh nghiệp; workflow xác minh 2 trường trọng yếu bắt buộc đúng tổng tiền và tổng thuế; quy trình fallback về nhập tay khi chất lượng ảnh kém.
- Tiêu chí chấp nhận: p95 thời gian trích xuất dưới 4 giây mỗi hóa đơn; tỷ lệ yêu cầu sửa tay dưới 30 phần trăm ở tháng chạy thử; không ghi sổ tự động.
- Đối soát ngân hàng
- Mục tiêu: khớp giao dịch sổ phụ với hóa đơn AR AP để giảm thao tác thủ công.
- Giai đoạn 1: import sổ phụ định dạng CSV MT940 camt.053; quy tắc khớp theo số tiền ngày tham chiếu; tạo đề xuất khớp và cho người dùng xác nhận.
- Giai đoạn 2: tích hợp API ngân hàng chỉ khi có kênh doanh nghiệp on-prem hoặc egress an toàn đã chuẩn hóa.
- Điều kiện sẵn sàng: mẫu file sổ phụ chuẩn, quy tắc đặt cọc phí nhỏ lệch ± x đồng, người duyệt cuối cùng được phân quyền rõ.
- Barcode và tem nhãn
- Mục tiêu: tăng tốc nhập xuất và kiểm kho.
- Cách làm: chấp nhận máy quét USB HID hoạt động như bàn phím là baseline; tùy chọn quét camera trên trình duyệt cho kho nhỏ. Hỗ trợ mã Code128 và EAN13.
- Điều kiện sẵn sàng: kho đã dán tem chuẩn, kiểm thử tỉ lệ nhận đúng trên môi trường kho đạt trên 98 phần trăm với máy quét USB.
- Mobile kiểm kho
- Mục tiêu: kiểm kê và điều chuyển bằng điện thoại.
- Cách làm: PWA chạy nội bộ, hỗ trợ offline ngắn hạn và đồng bộ khi có mạng; quét barcode và nhập số lượng nhanh.
- Điều kiện sẵn sàng: phủ sóng Wi-Fi ổn trong kho, danh sách thiết bị được quản lý, chính sách đăng xuất tự động khi idle.
- mTLS nội bộ
- Mục tiêu: ràng buộc thiết bị công ty khi truy cập web qua client certificate giữa trình duyệt và reverse proxy.
- Điều kiện sẵn sàng: có CA nội bộ, quy trình cấp phát và thu hồi chứng thư, bộ máy tính được quản lý tập trung. Bật thử nghiệm với nhóm nhỏ trước, giữ tài khoản break glass không cần mTLS để xử lý sự cố.
- SSO doanh nghiệp
- Mục tiêu: đăng nhập một lần với danh tính nội bộ.
- Cách làm: kết nối IdP on-prem qua SAML 2.0 hoặc OpenID Connect, ánh xạ nhóm sang vai trò ứng dụng, vẫn giữ tài khoản nội bộ khẩn cấp.
- Điều kiện sẵn sàng: IdP sẵn có, chính sách thuộc tính chuẩn hóa email nhóm đơn vị, bài test đăng nhập và thu hồi quyền.
- Phê duyệt đa cấp
- Mục tiêu: kiểm soát rủi ro chứng từ giá trị lớn hoặc nhạy cảm.
- Cách làm: định nghĩa luồng maker checker theo ngưỡng tiền loại chứng từ nhóm nhà cung cấp. Hỗ trợ 2 đến 3 cấp duyệt, tự động nhắc và escalates khi quá hạn.
- Điều kiện sẵn sàng: sơ đồ phê duyệt được duyệt bởi kế toán trưởng, SLA duyệt theo ngày làm việc, báo cáo tồn duyệt.
- API mở
- Mục tiêu: tích hợp với hệ thống khác trong nội bộ như DMS WMS.
- Cách làm: REST API nội bộ có idempotency, rate limit, key có phạm vi quyền, chỉ lộ trong mạng tin cậy hoặc qua VPN. Nhật ký đầy đủ request response đã ẩn thông tin nhạy cảm.
- Điều kiện sẵn sàng: danh mục API đọc ghi, chính sách khóa API quay vòng, IP allowlist riêng cho ứng dụng tích hợp.
Template cờ cấu hình và điều kiện sẵn sàng
- Trường thông tin nên được theo dõi cho từng cờ: tên cờ, mô tả, mặc định, điều kiện sẵn sàng, bước rollout, chủ sở hữu, chỉ số thành công, fallback. Dùng định dạng máy đọc dưới đây để đội phát triển nhập thẳng vào cấu hình.
Ví dụ điền sẵn
- OCR_INVOICE|OCR hóa đơn PDF ảnh|off|Có 100 mẫu test và workflow xác minh 2 trường tổng tiền tổng thuế|Pilot 10 người dùng 2 tuần rồi mở 50 phần trăm|Kế toán trưởng|Tỷ lệ sửa tay dưới 30 phần trăm, p95 dưới 4 giây|Tắt OCR và chuyển về nhập tay nếu lỗi vượt ngưỡng
- BANK_RECON|Đối soát ngân hàng CSV|off|Có mẫu CSV MT940 hoặc camt.053 và quy tắc khớp đã duyệt|Pilot 1 tài khoản ngân hàng 2 tuần|PM nội bộ|Tỷ lệ auto-match ≥ 60 phần trăm, sai khớp dưới 2 phần trăm|Chỉ ghi nhận đề xuất, không tự hạch toán
- BARCODE|Quét barcode|on|Kho có tem Code128 hoặc EAN13, test nhận đúng ≥ 98 phần trăm với máy quét USB|Bật toàn cục, camera-quét chỉ cho kho A pilot|Trưởng kho|Tốc độ nhập tăng ≥ 30 phần trăm ở ca pilot|Rơi về nhập tay khi camera-quét lỗi
- MOBILE_STOCKTAKE|Kiểm kho mobile PWA|off|Phủ Wi-Fi ổn, danh sách thiết bị quản lý, đăng xuất idle 10 phút|Pilot 1 kho trong 2 tuần|Trưởng kho|Hoàn thành kiểm kê nhanh hơn 20 phần trăm, không mất dữ liệu offline|Khóa offline, chỉ cho online realtime
- MTLS_INTERNAL|mTLS trình duyệt đến proxy|off|CA nội bộ và quy trình cấp thu hồi cert hoạt động|Pilot 5 máy quản trị trong 2 tuần|IT nội bộ|100 phần trăm đăng nhập mTLS thành công, không phát sinh lockout|Cho phép VPN + IP whitelist không mTLS
- SSO|SSO qua SAML OIDC|off|IdP hoạt động, mapping nhóm sang vai trò đã test|Pilot phòng Kế toán 2 tuần|IT nội bộ|0 sự cố đăng nhập, thu hồi quyền phản ánh trong 15 phút|Mở lại local login cho nhóm bị lỗi
- MULTILEVELAPPROVAL|Phê duyệt đa cấp|off|Sơ đồ duyệt và SLA được phê duyệt|Pilot cho hóa đơn mua > 100 triệu|Kế toán trưởng|100 phần trăm chứng từ vượt ngưỡng có dấu vết duyệt đủ cấp|Tự chuyển 1 cấp duyệt khi quá hạn 2 ngày
- OPEN_API|API mở nội bộ|off|Danh mục endpoint, rate limit, key rotation, IP allowlist|Pilot 1 ứng dụng tích hợp|PM nội bộ|Không quá 1 phần ngàn lỗi 5xx, audit đầy đủ|Chỉ bật read-only và giới hạn IP
Mẫu tham số hóa
- FEATUREKEY|FEATURENAME|DEFAULTONoff|READINESSCONDITION|ROLLOUTSTEPS|OWNER|SUCCESSMETRIC|FALLBACKBEHAVIOR
Lưu ý triển khai theo cờ
- Chạy pilot có thời hạn và tắt tự động nếu KPI không đạt sau 2 tuần.
- Mọi cờ bật mới phải có nhật ký thay đổi và người chịu trách nhiệm rõ ràng.
- Không bật đồng thời mTLS và SSO diện rộng nếu chưa có break glass account và hướng dẫn khôi phục khi trình duyệt không có chứng thư.
Quy trình nhập liệu hóa đơn mua bán hằng ngày, duyệt và ghi sổ
Luồng chuẩn tách rõ hai tuyến: hóa đơn đầu vào mua hàng và hóa đơn đầu ra bán hàng. Cả hai dùng cùng bộ trạng thái và bước kiểm tra để đảm bảo số liệu đúng ngay từ đầu, tránh sửa hồi tố. Mình mô tả theo maker checker một cấp mặc định: Người nhập tạo và soạn thảo, Người duyệt kiểm tra và duyệt, hệ thống ghi sổ tự động sau khi đỗ điều kiện hoặc theo lệnh Ghi sổ của Người duyệt.
Các bước thực hiện cho hóa đơn mua – đầu vào
- Tạo chứng từ
- Người nhập chọn Loại chứng từ: Hóa đơn mua.
- Trạng thái khởi tạo: Nháp.
- Nhập phần đầu chứng từ
- Nhà cung cấp, mã NCC, điều khoản thanh toán, ngày hóa đơn, số hóa đơn, tiền tệ, tỷ giá nếu khác nội tệ, kho nhận, liên kết PO nếu có.
- Hệ thống tự gợi ý hạn thanh toán từ điều khoản và đơn vị tiền tệ mặc định.
- Nhập dòng hàng
- Mỗi dòng gồm: mã hàng, tên hàng, đơn vị, số lượng, đơn giá chưa thuế, mã thuế, kho – vị trí, tài khoản hạch toán gợi ý cho hàng hóa – thuế – công nợ.
- Cho phép thêm chi phí mua phân bổ theo trọng số số lượng hoặc giá trị, xem trước phân bổ.
- Kiểm tra tức thời khi nhập
- Rule cứng chạy mỗi lần người dùng dừng gõ ngắn: bắt buộc trường, tổng dòng khớp tổng chứng từ, ngày trong kỳ mở, số hóa đơn không trùng trong cùng nhà cung cấp và kỳ, mã thuế hợp lệ, không cho số lượng âm, tỷ giá hợp lệ theo ngày.
- Cảnh báo AI nhẹ: đơn giá bất thường so với lịch sử nhà cung cấp – mặt hàng, tài khoản hạch toán lệch so với chứng từ tương tự, tổng tiền hóa đơn lệch lớn so với PO liên kết. Cảnh báo không chặn ở MVP. SLA p95 hiển thị dưới 1.2 giây.
- Lưu và gửi duyệt
- Người nhập bấm Gửi duyệt. Trạng thái chuyển Chờ duyệt. Nếu còn lỗi rule cứng, hệ thống chặn và liệt kê vị trí cần sửa.
- Nếu cảnh báo AI cấp High, yêu cầu người nhập xác nhận đã kiểm tra trước khi gửi.
- Duyệt
- Người duyệt mở chứng từ, xem tab Lỗi – Cảnh báo, có ba lựa chọn: Duyệt – Trả lại – Hủy. SLA duyệt nội bộ: trong 4 giờ làm việc hoặc trước cuối ngày. Hệ thống nhắc sau 2 giờ, escalates cho Kế toán trưởng khi quá hạn cuối ngày.
- Ghi sổ
- Khi Duyệt, hệ thống chạy kiểm tra gating lần cuối và Ghi sổ ngay. Trạng thái Đang ghi sổ trong tích tắc rồi Đã ghi sổ. SLA p95 dưới 2.0 giây.
- Kết quả: tạo bút toán sổ cái, sổ phải trả, cập nhật tồn kho nhập, ghi thuế đầu vào theo cấu hình. Cấp số chứng từ và biên nhận ghi sổ.
- Ngoại lệ
- Trả lại: trạng thái Bị trả lại, bắt buộc nhập lý do, quay về Người nhập sửa.
- Hủy: chỉ cho phép trước khi ghi sổ. Sau khi ghi sổ, phải lập chứng từ đảo hoặc điều chỉnh đúng quy trình.
Các bước thực hiện cho hóa đơn bán – đầu ra
- Tạo chứng từ
- Người nhập chọn Loại chứng từ: Hóa đơn bán. Có thể tạo từ Đơn bán hoặc Phiếu xuất đã giao hàng.
- Trạng thái Nháp.
- Nhập phần đầu
- Khách hàng, mã KH, điều khoản thanh toán, ngày hóa đơn, số hóa đơn, kho xuất, tham chiếu đơn hàng hoặc giao hàng.
- Nhập dòng hàng
- Mã hàng, tên hàng, đơn vị, số lượng, đơn giá bán chưa thuế, mã thuế, kho – vị trí, tài khoản doanh thu – công nợ gợi ý.
- Nếu tạo từ Phiếu xuất: khóa số lượng theo giao hàng, chỉ được sửa đơn giá và chiết khấu trong biên cấu hình.
- Kiểm tra tức thời
- Rule cứng: tổng dòng khớp tổng chứng từ, ngày trong kỳ mở, không cho xuất vượt tồn, số hóa đơn không trùng theo khách hàng và kỳ, mã thuế hợp lệ.
- Cảnh báo AI: đơn giá bán bất thường so với lịch sử khách hàng – mặt hàng, chiết khấu khác thường, tài khoản doanh thu khác lệch. Không chặn.
- Gửi duyệt và ghi sổ
- Mặc định cần 1 cấp duyệt. Với giá trị nhỏ dưới ngưỡng cấu hình có thể auto-duyệt. Sau duyệt, hệ thống Ghi sổ tạo bút toán sổ cái, sổ phải thu, xuất kho – giá vốn, thuế đầu ra. Trạng thái Đã ghi sổ.
Trạng thái chứng từ và chuyển trạng thái
- Nháp: chỉ Người nhập chỉnh sửa. Xóa được.
- Chờ duyệt: đóng băng một số trường khóa như nhà cung cấp – khách hàng, số hóa đơn. Người duyệt có thể xem – duyệt – trả lại – hủy.
- Bị trả lại: quay về Người nhập, giữ lịch sử nhận xét. Khi gửi lại, tạo phiên bản duyệt mới.
- Đã duyệt chờ ghi sổ: dùng khi tách duyệt và ghi sổ thủ công. Người duyệt hoặc Kế toán trưởng bấm Ghi sổ.
- Đang ghi sổ: ngắn, không cho thao tác khác.
- Đã ghi sổ: khóa chỉnh sửa. Mọi thay đổi phải qua chứng từ điều chỉnh hoặc đảo.
- Hủy: chỉ trước ghi sổ, ghi lý do, lưu audit.
Vai trò tham gia và quyền tối thiểu
- Người nhập: tạo – sửa – gửi duyệt, xem cảnh báo, không được ghi sổ.
- Người duyệt: xem toàn bộ, duyệt – trả lại – hủy trước ghi sổ, được ghi sổ nếu chính sách cho phép.
- Kế toán trưởng: quyền override các chặn rule cứng theo chính sách, cấu hình ngưỡng duyệt, theo dõi SLA duyệt, ghi sổ hàng loạt.
- Thủ kho: xác nhận giao nhận, ảnh hưởng đến khả năng xuất kho của hóa đơn bán nếu ràng buộc theo giao hàng.
- Quản trị hệ thống: không tham gia nghiệp vụ thường nhật, chỉ cấu hình quy tắc, kỳ kế toán, quyền.
SLA xử lý gợi ý để vận hành trơn tru
- Kiểm tra tức thời khi nhập: p95 dưới 1.2 giây.
- Duyệt: trong 4 giờ làm việc, tự nhắc 2 giờ, escalate cuối ngày.
- Ghi sổ: p95 dưới 2.0 giây mỗi chứng từ, batch 50 chứng từ p95 dưới 30 giây.
- Vòng đời chứng từ thường: tạo đến ghi sổ hoàn tất trong cùng ngày làm việc.
Mẫu form bắt buộc – bản điền sẵn hoàn chỉnh
- Loại chứng từ: Hóa đơn mua
- Nhà cung cấp: Công ty Thiết Bị Văn Phòng ABC
- Mã NCC: NCC-ABC
- Điều khoản thanh toán: Net 30
- Ngày hóa đơn: 15/07/2026
- Số hóa đơn: 0001234
- Tiền tệ: VND
- Kho nhận: Kho Tổng
- Ghi chú: Mua văn phòng phẩm tháng 7
- Dòng 1: Mã hàng SP001 – Bút bi TL027 – ĐVT chiếc – SL 100 – Đơn giá 4,500 – Thuế 8% – Kho Kho Tổng – TK gợi ý: Hàng tồn kho, Thuế đầu vào, Phải trả
- Dòng 2: Mã hàng SP002 – Giấy A4 80gsm – ĐVT ram – SL 50 – Đơn giá 48,000 – Thuế 8% – Kho Kho Tổng – TK gợi ý: Hàng tồn kho, Thuế đầu vào, Phải trả
- Phí vận chuyển phân bổ: 120,000
- Tổng tiền hàng: 2,850,000
- Thuế: 228,000
- Tổng thanh toán: 2,998,000
- Cảnh báo AI: Không có cảnh báo
- Trạng thái hiện tại: Chờ duyệt
Mẫu form bắt buộc – bản có biến số để tái sử dụng
- Loại chứng từ: {LOAICHUNGTUAPAR}
- Nhà cung cấp hoặc Khách hàng: {TENDOITAC}
- Mã đối tác: {MADOITAC}
- Điều khoản thanh toán: {TERMS}
- Ngày hóa đơn: {NGAY_HD}
- Số hóa đơn: {SO_HD}
- Tiền tệ: {CURRENCY}
- Kho nhận hoặc xuất: {KHO}
- Ghi chú: {GHI_CHU}
- Dòng i: Mã {MAHANGi} – Tên {TENHANGi} – ĐVT {DONVIi} – SL {SOLUONGi} – Đơn giá {DONGIAi} – Thuế {VATi}% – Kho {KHOi} – TK gợi ý {TK_i}
- Tổng tiền hàng: {TONG_HANG}
- Thuế: {TONG_THUE}
- Tổng thanh toán: {TONGTHANHTOAN}
- Cảnh báo AI: {CANHBAOAI}
- Trạng thái hiện tại: {TRANG_THAI}
Gợi ý thao tác nhanh để đạt SLA
- Tạo từ PO hoặc Phiếu xuất để tự điền đối tác, điều khoản, kho và dòng hàng. Giảm lỗi gõ tay.
- Dùng tìm kiếm theo mã hàng và phím tắt thêm dòng để nhập nhanh. Hạn chế đổi cửa sổ khi đối chiếu hóa đơn gốc.
- Với hóa đơn bán, xác nhận giao hàng trước để tránh bị chặn xuất vượt tồn khi ghi sổ.
Luồng kỹ thuật ghi sổ an toàn đúng một lần
Mục tiêu là hiệu lực đúng một lần ở máy chủ tổng kể cả khi mạng chập chờn. Mình kết hợp 6 điểm tựa: idempotency key do client sinh, validate token gắn chặt với payload, giao dịch ACID bao trùm mọi ghi sổ, transactional outbox để phát sự kiện sau commit, biên nhận commit trả về cho client, và hàng đợi retry bền vững ở cả client lẫn server.
Thành phần và hợp đồng API tối thiểu
- POST /api/v1/validate: nhận payload chứng từ, chạy rule cứng và AI, trả về validation_token.
- POST /api/v1/vouchers/commit: nhận payload + validation_token. Header Idempotency-Key bắt buộc.
- GET /api/v1/jobs/{job_id}: kiểm tra trạng thái job khi server trả 202.
Bảng trạng thái idempotency và outbox trên DB
-- Idempotency state
CREATE TABLE idempotency_keys (
tenant_id TEXT NOT NULL,
idempotency_key TEXT NOT NULL,
request_hash TEXT NOT NULL, -- SHA-256 của JSON đã chuẩn hóa
status TEXT NOT NULL CHECK (status IN ('in_progress','committed','failed_retryable','failed_permanent')),
locked_until TIMESTAMPTZ,
receipt_json JSONB, -- snapshot response khi committed
snapshot_json JSONB, -- lưu hash_payload, rules_version, ai_model_version, findings, approved_at, user_id
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (tenant_id, idempotency_key)
);
-- Transactional outbox
CREATE TABLE outbox_events (
id BIGSERIAL PRIMARY KEY,
tenant_id TEXT NOT NULL,
aggregate_type TEXT NOT NULL, -- 'voucher'
aggregate_id TEXT NOT NULL, -- voucher_uid
event_type TEXT NOT NULL, -- 'voucher_posted'
payload_json JSONB NOT NULL,
status TEXT NOT NULL CHECK (status IN ('pending','published','failed')) DEFAULT 'pending',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
published_at TIMESTAMPTZ
);
-- Server job queue cho xử lý nền
CREATE TABLE jobs (
id BIGSERIAL PRIMARY KEY,
tenant_id TEXT NOT NULL,
idempotency_key TEXT NOT NULL,
job_type TEXT NOT NULL, -- 'commit_voucher'
payload_json JSONB NOT NULL,
status TEXT NOT NULL CHECK (status IN ('queued','in_progress','done','failed_retryable','failed_permanent')) DEFAULT 'queued',
attempts INT NOT NULL DEFAULT 0,
next_run_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (tenant_id, idempotency_key)
);
Quy tắc idempotency key
- Sinh tại client ngay khi người dùng bấm Gửi ghi sổ. Dạng đề xuất: {tenant}-{module}-{ULID}.
- Lưu cùng payload và dùng làm voucher_uid khi upsert server.
- Server chuẩn hóa JSON rồi băm SHA-256 để so khớp reuse đúng payload.
Template idempotency key
- Bản hoàn chỉnh: ACME-AP-01JZ3QZ4E9S7W1Z5C1N0X
- Bản có biến số: {TENANT}-{MODULE}-{ULID}
Validation token
- Token do server ký, gắn chặt với hash payload và phiên bản kiểm tra, có hạn dùng ngắn.
- Cho phép late-commit dùng snapshot nếu token hết hạn nhưng versions không đổi và hash trùng.
Ví dụ validation token trả về
{
"token_id": "vldt_7f3c9b",
"hash_payload": "b3d1b7a9...8e4c",
"rules_version": "2026-07-15.3",
"ai_model_version": "risk-lite-1.4",
"issued_at": "2026-07-15T08:05:10Z",
"expires_at": "2026-07-15T08:20:10Z",
"sig": "MEUCIQD9j...mQ=="
}
Giao dịch ACID bao trùm và đánh số chứng từ
- Server nhận commit, kiểm:
- Idempotency-Key chưa bị khóa hoặc cho phép claim lại khi locked_until hết hạn.
- hashpayload của request khớp validationtoken hoặc snapshot.
- validationtoken còn hạn hoặc snapshot hợp lệ cùng rulesversion và aimodelversion.
- Trong một transaction duy nhất:
- Upsert voucher với voucheruid = idempotencykey, lineno có unique theo voucheruid.
- Hạch toán sổ cái, sổ công nợ, sổ kho, tất cả có unique theo (voucheruid, lineno).
- Cấp số chứng từ:
- Hoặc cấp trong transaction ngay trước COMMIT.
- Hoặc reserve trước theo idempotency_key, chỉ mark used khi commit thành công. Nếu địa phương không cho phép bỏ số, bắt buộc dùng cơ chế reserve.
- Ghi audit log và ghi outbox_events trong cùng transaction.
- Commit xong mới có worker phát outbox ra các đích nội bộ khác.
Biên nhận commit
- Server trả commit_receipt để client hiển thị trạng thái bền vững và đối soát.
Ví dụ commit_receipt
{
"voucher_uid": "ACME-AP-01JZ3QZ4E9S7W1Z5C1N0X",
"voucher_number": "AP/2026/07/000123",
"committed_at": "2026-07-15T08:05:12.345Z",
"db_txn": "tx_9f1a6c",
"hash_payload": "b3d1b7a9...8e4c",
"posting_version": 1
}
Hàng đợi retry client và server
- Client offline queue:
- Mỗi chứng từ có trạng thái: queued, sent, acked, failed.
- Phân loại lỗi: 5xx và lỗi mạng thì retry vô hạn với exponential backoff và jitter; 4xx nghiệp vụ thì chuyển failed để người dùng sửa.
- Có thể gửi batch 20 chứng từ, mỗi chứng từ giữ idempotency riêng.
- Server durable queue:
- Khi cần xử lý nền, commit trả 202 Accepted cùng jobid. Worker FIFO dùng SELECT FOR UPDATE SKIP LOCKED để lấy job, đặt inprogress, xử lý commit trong transaction, sau đó mark done.
Mã phản hồi và kịch bản lỗi tiêu biểu
- 200 OK: Commit thành công, trả commit_receipt. Nếu client gửi lại cùng Idempotency-Key, luôn trả lại snapshot 200.
- 202 Accepted: Commit đang xử lý nền, trả jobid. Client poll GET /jobs/{jobid} hoặc gửi lại cùng Idempotency-Key sau vài giây.
- 409 Conflict IdempotencyMismatch: Idempotency-Key đã tồn tại nhưng request_hash khác. Client phải tạo key mới cho payload mới.
- 409 Conflict BusinessDuplicate: Trùng nghiệp vụ theo quy tắc doanh nghiệp như nhà cung cấp + số hóa đơn + ngày. Trả gợi ý liên kết hoặc bỏ qua.
- 428 Precondition Required: validationtoken hết hạn và rulesversion hoặc aimodelversion đã thay đổi. Yêu cầu re-validate.
- 503 Service Unavailable Retry-After: Lỗi tạm như deadlock. Client retry tự động với backoff.
Ví dụ hoàn chỉnh luồng validate rồi commit
- Client gọi validate
POST /api/v1/validate
Content-Type: application/json
{
"tenant_id": "ACME",
"voucher_type": "AP",
"header": { "vendor_code": "NCC-ABC", "invoice_no": "0001234", "invoice_date": "2026-07-15" },
"lines": [
{ "line_no": 1, "sku": "SP001", "qty": 100, "unit_price": 4500, "tax_code": "VAT8" },
{ "line_no": 2, "sku": "SP002", "qty": 50, "unit_price": 48000, "tax_code": "VAT8" }
]
}
Response
200 OK
Content-Type: application/json
{
"validation_token": { ... như ví dụ trên ... },
"findings": { "rules": [], "ai": [] }
}
- Client gọi commit
POST /api/v1/vouchers/commit
Content-Type: application/json
Idempotency-Key: ACME-AP-01JZ3QZ4E9S7W1Z5C1N0X
{
"tenant_id": "ACME",
"voucher_type": "AP",
"voucher_uid": "ACME-AP-01JZ3QZ4E9S7W1Z5C1N0X",
"validation_token": { "token_id": "vldt_7f3c9b", "hash_payload": "b3d1b7a9...8e4c", "expires_at": "2026-07-15T08:20:10Z", "sig": "MEUCIQD9j...mQ==" },
"header": { "vendor_code": "NCC-ABC", "invoice_no": "0001234", "invoice_date": "2026-07-15" },
"lines": [
{ "line_no": 1, "sku": "SP001", "qty": 100, "unit_price": 4500, "tax_code": "VAT8" },
{ "line_no": 2, "sku": "SP002", "qty": 50, "unit_price": 48000, "tax_code": "VAT8" }
]
}
Response
200 OK
Content-Type: application/json
{
"commit_receipt": {
"voucher_uid": "ACME-AP-01JZ3QZ4E9S7W1Z5C1N0X",
"voucher_number": "AP/2026/07/000123",
"committed_at": "2026-07-15T08:05:12.345Z",
"db_txn": "tx_9f1a6c",
"hash_payload": "b3d1b7a9...8e4c",
"posting_version": 1
}
}
Template request commit để tái sử dụng
{
"tenant_id": "{TENANT}",
"voucher_type": "{MODULE_AP_AR_SALES_PURCHASE}",
"voucher_uid": "{TENANT}-{MODULE}-{ULID}",
"validation_token": {
"token_id": "{TOKEN_ID}",
"hash_payload": "{SHA256_OF_NORMALIZED_JSON}",
"expires_at": "{ISO_TIMESTAMP}",
"sig": "{SERVER_SIGNATURE}"
},
"header": { /* trường phần đầu chứng từ */ },
"lines": [ /* mảng dòng hàng với line_no tăng dần */ ]
}
Gợi ý tham số vận hành
- TTL validation_token: 15 phút.
- Idempotency retention: 180 ngày.
- Client backoff: base 2 giây, tối đa 60 giây, jitter 20 phần trăm.
- idempotencylocktimeout: 60 giây rồi cho phép claim lại.
Cách xử lý late-commit không cần re-validate
- Nếu token hết hạn nhưng server còn snapshot gắn với idempotencykey, hashpayload trùng và versions không đổi, cho phép commit, log reason=late-commit-using-snapshot. Nếu không, trả 428 yêu cầu validate lại.
Quản lý công nợ AR AP, hạn mức và nhắc nợ
Để vận hành công nợ trơn tru, bạn cần đồng bộ 5 mảnh ghép: danh mục khách hàng và nhà cung cấp đủ trường, hạn mức tín dụng và cách đo exposure theo thời gian thực, tuổi nợ AR AP theo ngày đến hạn, lịch nhắc nợ nhiều cấp và cơ chế khóa mở bán hàng dựa trên hạn mức. Dưới đây là template triển khai thực tế cho MVP.
Danh mục khách hàng và nhà cung cấp
- Trường bắt buộc tối thiểu
- code, namelegal, nametrade, taxid, addressbilling, address_shipping
- payment_term: kiểu NetD hoặc COD, ví dụ Net30
- grace_days: số ngày ân hạn sau due để chưa coi là quá hạn
- credit_limit: số tiền hạn mức tín dụng, currency theo hệ thống MVP 1 tiền tệ
- credittemplimit, credittempuntil: hạn mức tạm và ngày hết hiệu lực
- creditblockflag, blockreason, blockuntil: trạng thái chặn bán nợ
- dunningpolicy: mã lịch nhắc nợ áp dụng, ví dụ DUNNSTD
- araccount, apaccount: tài khoản công nợ mặc định
- contactarname, contactaremail, contactarphone
- branch, owneruser, salesrep
- preferences: channel nhắc nợ email sms print, ngôn ngữ, giờ nhận thông báo
- Quy tắc dữ liệu
- code là duy nhất trong tenant
- payment_term NetD yêu cầu D là số nguyên dương
- credit_limit ≥ 0. Nếu 0 coi như chỉ bán tiền mặt
- Khi credittemplimit có giá trị, hệ thống dùng max(creditlimit, credittemplimit) đến credittemp_until
- Mẫu import CSV danh mục khách hàng
customer_code,name_legal,name_trade,tax_id,payment_term,grace_days,credit_limit,credit_temp_limit,credit_temp_until,dunning_policy,ar_account,contact_ar_name,contact_ar_email,contact_ar_phone,branch,owner_user,credit_block_flag,block_reason,block_until
CUS-ABC,Công ty ABC Việt Nam,ABC VN,0101234567,Net30,3,500000000,,,"DUNN_STD",131,Nguyễn Lan,congno@abc.com,0901234567,HN,nv001,false,,
Biến thể có biến số
{customer_code},{name_legal},{name_trade},{tax_id},{payment_term},{grace_days},{credit_limit},{credit_temp_limit},{credit_temp_until},{dunning_policy},{ar_account},{contact_ar_name},{contact_ar_email},{contact_ar_phone},{branch},{owner_user},{credit_block_flag},{block_reason},{block_until}
Thiết lập hạn mức tín dụng và đo exposure
- Công thức exposure theo khách hàng tại thời điểm kiểm tra
- exposure = ARopenunpaid + ARunpostedpending
- ARopenunpaid: tổng dư chưa thanh toán của các hóa đơn đã ghi sổ trừ đi tiền cọc và credit memo chưa phân bổ
- ARunpostedpending: hóa đơn đang chờ ghi sổ trong ngày. Mặc định không cộng đơn bán chưa xuất hóa đơn. Có tùy chọn IncludeSO với hệ số rủi ro soweight 0 đến 1, mặc định 0 ở MVP
- Ngưỡng và hành vi
- Cảnh báo mềm khi exposure ≥ 90 phần trăm hạn mức hiệu lực
- Chặn cứng khi exposure > 100 phần trăm hoặc có khoản quá hạn lớn hơn gracedays + overduethreshold. overdue_threshold mặc định 0 ngày ở MVP
- Tạm nâng: Approver có thể đặt credittemplimit và credittempuntil. Hệ thống ghi nhật ký và tự hạ về credit_limit khi hết hạn
- Tự mở khóa: nếu creditblockflag đang true nhưng exposure giảm còn dưới 90 phần trăm và không còn quá hạn, job đêm tự clear block_flag
- Điểm kiểm soát theo luồng bán hàng
- Khi xác nhận đơn bán: kiểm hạn mức để tránh nhận đơn vượt kiểm soát
- Khi ghi sổ hóa đơn bán: bắt buộc pass hạn mức hoặc có override hợp lệ
- Quyền override
- Chỉ vai trò Kế toán trưởng hoặc ApproverSalesCredit được override chặn cứng. Phải nhập lý do và chọn thời hạn hiệu lực override cho đúng chứng từ
Tuổi nợ AR AP
- Phương pháp: theo due date và asofdate. Buckets mặc định AR và AP
- Not due, 1-30, 31-60, 61-90, >90 ngày
- Phân bổ thanh toán: FIFO theo due date. Thanh toán từng phần ghi theo thứ tự cũ nhất trước. Credit memo tự động bù trừ theo hóa đơn cùng khách hàng
- Bản ghi cần có: invoiceno, invoicedate, duedate, amount, paidamount, balance, currency, branch, status
- Ví dụ tuổi nợ AR as_of 2026-07-31
customer_code,customer_name,invoice_no,invoice_date,due_date,days_past_due,balance,bucket
CUS-ABC,Công ty ABC Việt Nam,AR-000345,2026-06-20,2026-07-20,11,125000000,31-60
CUS-ABC,Công ty ABC Việt Nam,AR-000412,2026-07-10,2026-08-09,-9,80000000,Not due
CUS-XYZ,Công ty XYZ,AR-000299,2026-03-15,2026-04-14,108,45000000,>90
Biến thể có biến số
{customer_code},{customer_name},{invoice_no},{invoice_date},{due_date},{days_past_due},{balance},{bucket}
Lịch nhắc nợ nhiều cấp
- Chính sách mẫu DUNN_STD tính theo ngày quá hạn
- Level 0: T – 3 ngày trước hạn gửi lời nhắc lịch sự kèm số tiền sắp đến hạn
- Level 1: D+1 email nhắc lần 1, đính kèm sao kê chi tiết
- Level 2: D+7 email nhắc lần 2, mạnh hơn, sao chép salesrep và owneruser
- Level 3: D+14 tạo tác vụ gọi điện cho owneruser, ghi kết quả cuộc gọi vào dunninglog
- Level 4: D+30 email cảnh báo cuối cùng và đặt creditblockflag nếu còn dư nợ quá hạn
- Quy tắc gửi
- Khung giờ 08:00 đến 18:00 ngày làm việc, tránh gửi cuối tuần và ngày nghỉ cấu hình
- Throttle tối đa 1 email mỗi 72 giờ cho cùng khách hàng
- Không gửi khi có disputeflag trên hóa đơn hoặc đang có paymentplan_active
- Log và kiểm soát
- Lưu dunninglog gồm customercode, invoiceno, level, channel, sentat, result, userowner, nextaction. Lưu tối thiểu 24 tháng
- Email nhắc nợ mẫu đã điền
Chủ đề: Nhắc thanh toán hóa đơn quá hạn 11 ngày - Công ty ACME
Kính gửi chị Lan - Công ty ABC Việt Nam,
Hệ thống công nợ của ACME ghi nhận các khoản đến hạn của Công ty ABC Việt Nam:
- Hóa đơn AR-000345 ngày 20-06-2026, hạn 20-07-2026, quá hạn 11 ngày, số tiền còn lại 125.000.000 VND
- Tổng số tiền quá hạn: 125.000.000 VND
Đề nghị chị thu xếp thanh toán trước ngày 05-08-2026. Vui lòng phản hồi xác nhận lịch thanh toán để mình cập nhật. Đính kèm là sao kê chi tiết.
Thông tin chuyển khoản:
Công ty ACME - STK 123456789 tại Vietcombank CN Hà Nội - Nội dung: ABC thanh toán AR-000345
Nếu đã thanh toán, xin bỏ qua email này. Mọi thắc mắc xin liên hệ 0901 234 567 hoặc congno@acme.vn.
Trân trọng,
Nguyễn Minh - Kế toán công nợ ACME
Biến thể có biến số
Chủ đề: Nhắc thanh toán hóa đơn quá hạn {{days_past_due}} ngày - {{company_sender}}
Kính gửi {{contact_name}} - {{customer_name}},
Hệ thống công nợ của {{company_sender}} ghi nhận các khoản đến hạn của {{customer_name}}:
- Hóa đơn {{invoice_no}} ngày {{invoice_date}}, hạn {{due_date}}, quá hạn {{days_past_due}} ngày, số tiền còn lại {{balance}} {{currency}}
- Tổng số tiền quá hạn: {{total_overdue}} {{currency}}
Đề nghị {{contact_name_short}} thu xếp thanh toán trước ngày {{expected_pay_date}}. Vui lòng phản hồi xác nhận lịch thanh toán. Đính kèm là sao kê chi tiết.
Thông tin chuyển khoản:
{{bank_beneficiary}} - STK {{bank_account}} tại {{bank_name}} - Nội dung: {{payment_reference}}
Nếu đã thanh toán, xin bỏ qua email này. Liên hệ {{ar_phone}} hoặc {{ar_email}} khi cần hỗ trợ.
Trân trọng,
{{ar_officer_name}} - {{company_sender}}
Quy tắc khóa mở bán hàng theo hạn mức
- Điều kiện chặn
- exposure > creditlimit hiệu lực hoặc có bất kỳ hóa đơn quá hạn quá gracedays + overdue_threshold
- creditblockflag = true do người quản trị hoặc qua Level 4 dunning
- Điểm áp dụng chặn
- Sales Order Confirm: chặn tạo đơn có hiệu lực. Cho phép lưu dự thảo
- AR Invoice Post: chặn ghi sổ. Bắt buộc override nếu vẫn muốn ghi
- Mở khóa tự động và thủ công
- Tự mở khi exposure ≤ 90 phần trăm và không còn quá hạn. Job đêm chạy và ghi log reason=auto_clear
- Thủ công: Approver ghi lý do, thời hạn. Hệ thống gửi thông báo cho Kế toán trưởng
- Pseudocode kiểm hạn mức tại thời điểm xác nhận đơn
effective_limit = max(credit_limit, credit_temp_limit_if_valid)
if has_overdue_days > grace_days + overdue_threshold then block("Overdue")
else if exposure_after_this_order > effective_limit then
if exposure_ratio >= 0.9 then warn("Near limit")
if exposure_after_this_order > effective_limit then hard_block("Exceeds limit")
end
Template báo cáo công nợ và thông báo
- AR Aging tổng hợp theo khách hàng
as_of,customer_code,customer_name,not_due,days_1_30,days_31_60,days_61_90,days_gt_90,total_overdue,total_balance,exposure_ratio
2026-07-31,CUS-ABC,Công ty ABC Việt Nam,80000000,0,125000000,0,0,125000000,205000000,0.41
2026-07-31,CUS-XYZ,Công ty XYZ,0,0,0,0,45000000,45000000,45000000,0.90
Biến thể có biến số
{as_of},{customer_code},{customer_name},{not_due},{days_1_30},{days_31_60},{days_61_90},{days_gt_90},{total_overdue},{total_balance},{exposure_ratio}
- AP Aging theo nhà cung cấp để lập kế hoạch chi trả
as_of,vendor_code,vendor_name,not_due,days_1_30,days_31_60,days_61_90,days_gt_90,total_overdue,total_balance,next_payment_date
2026-07-31,V-HTE,Công ty HTE,120000000,30000000,0,0,0,30000000,150000000,2026-08-05
- Statement of Account theo khách hàng trong tháng
customer_code,customer_name,period,opening_balance,debit,credit,closing_balance
CUS-ABC,Công ty ABC Việt Nam,2026-07,150000000,180000000,125000000,205000000
- Thông báo khóa hạn mức gửi nội bộ khi tự động chặn
Chủ đề: Tự động chặn tín dụng CUS-ABC do quá hạn
Hệ thống đã đặt credit_block_flag cho CUS-ABC - Công ty ABC Việt Nam lúc 2026-07-31 18:30 do:
- Overdue > 0 ngày sau grace 3 ngày
- Exposure 205.000.000 VND trên hạn mức 200.000.000 VND
Owner: nv001. Vui lòng xử lý và cập nhật kế hoạch thu.
Biến thể có biến số
Chủ đề: Tự động chặn tín dụng {{customer_code}} do quá hạn
Hệ thống đã đặt credit_block_flag cho {{customer_code}} - {{customer_name}} lúc {{blocked_at}} do:
- Overdue > 0 ngày sau grace {{grace_days}} ngày
- Exposure {{exposure}} {{currency}} trên hạn mức {{effective_limit}} {{currency}}
Owner: {{owner_user}}. Vui lòng xử lý và cập nhật kế hoạch thu.
Thiết lập và vận hành
- Cấu hình mặc định MVP
- payment_term: Net15, Net30, Net45
- grace_days: 0 đến 5 ngày, mặc định 0
- buckets tuổi nợ: Not due, 1-30, 31-60, 61-90, >90
- overdue_threshold: 0 ngày
- so_weight: 0 để không cộng đơn bán vào exposure
- Nhật ký kiểm toán
- Mọi thay đổi creditlimit, credittemplimit, blockflag, override và gửi nhắc nợ đều lưu user, thời điểm, lý do, trước và sau thay đổi
- Lưu ý thực thi
- Khoản tạm ứng và cọc cần được hạch toán vào AR và trừ khỏi exposure ngay khi nhận. Nếu hệ thống chưa có quy trình cọc, đặt cờ ngăn không cho tính exposure thiếu phần cọc và yêu cầu đối chiếu tay trong báo cáo Statement of Account đến khi quy trình cọc được bật.
Quản lý hàng tồn kho và định giá xuất kho
Bạn cần một khung nhất quán cho dữ liệu kho, chứng từ và cách tính giá vốn để số liệu không lệch khi hồi tố. Mình mặc định phương pháp bình quân tức thời theo từng vật tư – từng kho, không cho âm kho, đặt ngưỡng hồi tố theo D và Z để kiểm soát chi phí tính lại. Nếu tần suất giao dịch dày và phải nhập hồi tố thường xuyên, bạn có thể chọn bình quân cuối ngày như một tùy chọn cấu hình.
Danh mục vật tư – kho – vị trí
- Vật tư hàng hóa
- Trường tối thiểu: itemcode, itemname, uom, category, isinventoryitem, glinventoryaccount, glcogsaccount, active_flag.
- Tham số tồn kho: reorderpoint, reorderqty, leadtimedays.
- Định giá: valuation_method mặc định MovingAverageRealtime cho MVP. Có thể khai báo DailyMovingAverage nếu bạn chọn bình quân cuối ngày.
- Quy tắc dữ liệu: itemcode duy nhất trong tenant, uom bắt buộc, isinventory_item phải bật mới cho phép ghi sổ kho.
- Kho
- Trường: warehousecode, warehousename, branch, address, allownegativestock default false, owner_user.
- Liên kết tài khoản: glinventoryaccount nếu khác mặc định ở vật tư.
- Vị trí trong kho – nếu chưa cần, dùng 1 vị trí mặc định
- Trường: bincode, warehousecode, bin_type [RECEIVING, PICKING, STORAGE].
- Nếu không bật theo dõi vị trí, hệ thống tự gắn bin_code = DEFAULT.
- Thiết lập vật tư – kho
- Trường: itemcode, warehousecode, safetystock, maxstock, onhand, onorder, reserved, avgcostcurrent, lastmovementat.
- Khóa duy nhất theo cặp itemcode – warehousecode.
Chứng từ nhập – xuất – chuyển – điều chỉnh
- Nhập kho mua hàng
- Trường chính: voucherdate, vendorcode, warehousecode, referenceno, lines[itemcode, qty, unitcost, uom, tax_code, note].
- Ghi sổ: cập nhật onhand và avgcost theo bình quân tức thời. Thuế và chi phí mua bổ sung nếu có mà bạn chưa kịp phân bổ thì xử bằng Phiếu điều chỉnh giá trị sau này.
- Xuất kho bán hàng – xuất dùng nội bộ
- Trường chính: voucherdate, warehousecode, referenceno, reasoncode, lines[item_code, qty, uom, note].
- Ghi sổ: tính COGS = qty * avgcostcurrent tại thời điểm xuất. Không cho xuất nếu sẽ âm kho.
- Chuyển kho
- Trường: voucherdate, warehousefrom, warehouseto, lines[itemcode, qty].
- Ghi sổ: tại kho đi ghi nhận xuất theo avgcostcurrent của kho đi, kho đến nhận nhập với unit_cost bằng cost chuyển đến. Không nhập giá tay ở kho đến.
- Điều chỉnh tồn – kiểm kê
- Điều chỉnh tăng: yêu cầu nhập unit_cost để hình thành avg mới.
- Điều chỉnh giảm: dùng avgcostcurrent để tính giá vốn phần giảm. Bắt buộc reason_code, file đính kèm biên bản kiểm kê nếu có.
- Trả hàng mua
- Nếu liên kết với phiếu nhập gốc thì dùng đúng đơn giá gốc. Nếu không liên kết, hệ thống dùng avgcostcurrent và gắn cờ cảnh báo cần rà soát.
- Hàng bán bị trả lại
- Mặc định nhập lại theo avgcostcurrent tại ngày trả. Bạn có thể liên kết chứng từ bán gốc để dùng COGS gốc khi cần độ chính xác cao.
Chính sách không cho âm kho
- Kiểm tra tại thời điểm Ghi sổ: onhandavailable theo cặp vật tư – kho – vị trí nếu dùng vị trí.
- Khi hồi tố, hệ thống dựng lại dòng thời gian cho vật tư – kho và chặn nếu tại bất kỳ mốc nào sau hồi tố dẫn đến on_hand < 0. Người dùng phải bổ sung chứng từ nhập hợp lệ hoặc điều chỉnh ngày.
Thuật toán bình quân tức thời mặc định
- Cập nhật khi nhập
avg_new = (qty_old * avg_old + qty_in * unit_cost_in) / (qty_old + qty_in)
on_hand_new = qty_old + qty_in
- Tính giá vốn khi xuất
cogs = qty_out * avg_current
on_hand_new = on_hand_old - qty_out
- Thực thi
- Lưu avgcostcurrent với 4 chữ số thập phân, làm tròn tiền theo 2 chữ số khi ghi COGS.
- Khóa hàng tồn theo cặp itemcode – warehousecode khi ghi sổ để tránh điều kiện tranh chấp.
- Ví dụ nhanh
- Tồn đầu: 100 cái, avg = 50. Nhập 50 cái giá 70. avg mới = (10050 + 5070) / 150 = 56.6667.
- Xuất 80 cái. COGS = 80 * 56.6667 = 4.533.336, làm tròn 4.533.336 VND. Tồn còn 70 cái, avg giữ 56.6667.
Ngưỡng hồi tố D và Z
- Định nghĩa
- D: số ngày cho phép ghi chứng từ hồi tố tính từ ngày hiện tại và phải trong kỳ kế toán đang mở. Mặc định 14 ngày hoặc đến hết tháng hiện tại, lấy mốc ngắn hơn theo cấu hình của bạn.
- Z: số dòng chứng từ downstream tối đa cho cùng vật tư – kho cần tính lại kể từ chứng từ hồi tố đến hiện tại. Mặc định 100 dòng.
- Luồng kiểm tra trước khi ghi hồi tố
- Xác định phạm vi vật tư – kho bị ảnh hưởng.
- Ước lượng số dòng downstream và tổng ảnh hưởng COGS dự kiến.
- Nếu retrodays ≤ D và downstreamlines ≤ Z thì tính lại đồng bộ, khóa phạm vi trong lúc replay.
- Nếu vượt D hoặc Z thì chặn tự động tính lại và đề xuất Phiếu điều chỉnh giá vốn tại ngày hiện tại bù phần chênh lệch tổng. Chỉ người có quyền cao mới xác nhận.
- Ghi nhận kế toán
- Nếu cùng kỳ mở: bút toán chênh lệch COGS phát sinh tại đúng ngày các chứng từ xuất bị ảnh hưởng.
- Nếu chạm kỳ đã khóa: không hồi tính lịch sử. Lập phiếu điều chỉnh tại ngày hiện tại vào tài khoản chênh lệch giá vốn theo chính sách nội bộ.
Phương án bình quân cuối ngày như lựa chọn
- Hành vi
- Trong ngày, mọi xuất dùng avg đầu ngày. Cuối ngày hệ thống chốt avg cuối ngày = (tồn đầu ngày * avg đầu ngày + tổng nhập trong ngày theo giá) / tồn cuối ngày. Nếu có thay đổi so với avg đầu ngày, phát sinh bút toán điều chỉnh nội ngày.
- Lợi ích thực dụng
- Hồi tố chỉ phải tính theo số ngày bị ảnh hưởng thay vì từng dòng, cho phép bạn mở D lên 30 ngày và đặt Z_ngày ví dụ 10 ngày mà vẫn kiểm soát được hiệu năng.
- Đánh đổi
- Độ chính xác theo thời điểm trong ngày kém hơn bình quân tức thời và có thể xuất hiện điều chỉnh nội ngày. Nên dùng khi tần suất nhập xuất dày đặc và yêu cầu hồi tố rộng.
Template cảnh báo khi vượt ngưỡng
- Dùng ngay cho log và hiển thị toast hoặc dialog. Dòng máy đọc giữ đúng định dạng: bắt đầu bằng dấu gạch ngắn, trường ngăn bằng dấu gạch đứng.
- Bản điền sẵn ví dụ
- EVTRETROTHRESHOLD|item=MAT-001|warehouse=WH-HN|retrodays=17|downstreamlines=146|Dcfg=14|Zcfg=100|action=BLOCKAUTOREPLAY|suggest=CreateCurrentCOGSAdjustment|user=nv.kho01|voucher=GRN-000456|ts=2026-08-03T10:22:15+07:00
- EVTNEGSTOCKPREVENT|item=MAT-002|warehouse=WH-HN|qtyout=120|onhand=85|action=BLOCKPOST|suggest=AddReceiptOrSplitQty|user=nv.kho02|voucher=ISS-000789|ts=2026-08-03T10:24:40+07:00
- Biến thể có biến số
- EVTRETROTHRESHOLD|item={itemcode}|warehouse={warehousecode}|retrodays={retrodays}|downstreamlines={downstreamlines}|Dcfg={D}|Zcfg={Z}|action={action}|suggest={suggestion}|user={userid}|voucher={voucherno}|ts={iso_timestamp}
- EVTNEGSTOCKPREVENT|item={itemcode}|warehouse={warehousecode}|qtyout={qtyout}|onhand={onhand}|action={action}|suggest={suggestion}|user={userid}|voucher={voucherno}|ts={isotimestamp}
Gợi ý vận hành và cấu hình
- Tham số hóa
- D: 7 đến 30 ngày, mặc định 14.
- Z: 50 đến 500 dòng, mặc định 100. Nếu dữ liệu ít, bạn có thể hạ xuống 50 để bảo vệ hiệu năng.
- Ràng buộc kỳ: chỉ cho phép hồi tố trong kỳ mở. Khuyên khóa sổ tháng đúng hạn để giảm yêu cầu hồi tính sâu.
- Giao diện người dùng
- Trước khi ghi một chứng từ hồi tố, hiển thị bản tóm tắt: ngày hồi tố, downstream_lines ước lượng, tổng delta COGS ước lượng và hành động hệ thống sẽ thực hiện. Nếu bị chặn, show nút Tạo phiếu điều chỉnh hiện tại cùng lý do đã điền sẵn từ log.
- Nhật ký kiểm toán
- Lưu quyết định chặn hay cho phép, tham số D, Z tại thời điểm, danh sách chứng từ bị ảnh hưởng, tổng delta. Kèm người dùng thực hiện và thời điểm khóa – mở khóa phạm vi vật tư – kho.
Với khung này, bạn có ngay một module kho vận hành an toàn: số liệu nhất quán, rủi ro âm kho được chặn từ đầu, hồi tố trong giới hạn kiểm soát, và có lối thoát chuẩn qua phiếu điều chỉnh khi vượt ngưỡng.
Quy tắc đánh số chứng từ, kỳ kế toán và khóa sổ
Bạn cần một khung đánh số ổn định theo chi nhánh – kỳ và một cơ chế khóa kỳ chặt để số liệu không trôi. Mình đề xuất cấu trúc số chứng từ có thể reset theo tháng, hỗ trợ đặt trước số an toàn, tái sử dụng số theo idempotency, kèm quy trình mở – đóng kỳ và điều chỉnh sau khóa sổ có kiểm soát quyền.
Mẫu quy tắc đánh số theo năm – tháng – chi nhánh – phân hệ
- Cấu trúc đề xuất: BR-MOD-YYMM-SEQ4. Ví dụ: HN-AP-2607-0042.
- BR: mã chi nhánh, ví dụ HN, HCM.
- MOD: phân hệ, ví dụ AP – Mua vào, AR – Bán ra, INV – Kho, JV – Bút toán chung.
- YYMM: năm – tháng của ngày chứng từ.
- SEQ4: bộ đếm 4 chữ số, reset theo phạm vi bạn chọn.
- Phạm vi reset và duy nhất
- Reset theo tháng – theo chi nhánh – theo phân hệ là mặc định thực dụng: mỗi cặp BR – MOD – YYMM có một bộ đếm riêng.
- Uniqueness: duy nhất tối thiểu theo tenant – BR – MOD – YYMM – SEQ4. Nếu bạn chỉ reset theo năm, thay YYMM bằng YY và chấp nhận dãy dài hơn.
- Mẫu điền sẵn dùng ngay
- Quy tắc: HN-AP-YYMM-SEQ4
- Số đầu tiên tháng 07 năm 2026: HN-AP-2607-0001
- Tiếp theo: HN-AP-2607-0002
- Biến thể có biến số
- Mẫu: {branch}-{module}-{yy}{mm}-{seq4}
- Ví dụ theo năm thay vì tháng: {branch}-{module}-{yy}-{seq5}
Cơ chế đặt trước số và tái sử dụng theo idempotency
- Khi cần in sẵn phiếu hoặc cấp số cho hồ sơ giấy, cho phép Đặt số trước:
- Trạng thái số: RESERVED – USED – EXPIRED – VOIDED.
- Khi người dùng ghi sổ thành công trong transaction, số chuyển từ RESERVED sang USED. Nếu không ghi sổ trước hạn TTL, số chuyển EXPIRED.
- Với sai sót nội dung trước khi ghi sổ: người có quyền có thể VOID số đã RESERVED kèm lý do – số đó không quay lại pool nếu chính sách không cho lấp lỗ hổng số.
- Liên kết với idempotency để đúng một lần
- Mỗi yêu cầu commit có idempotencykey. Nếu đã RESERVED một số cho idempotencykey đó, hệ thống luôn tái sử dụng đúng số đó kể cả khi người dùng bấm Gửi lại.
- Nếu client đổi payload nhưng giữ cùng idempotency_key, server trả 409 Reuse Mismatch – không cấp số mới.
- Đồng thời – tránh trùng số
- Dùng một hàng numerators cho mỗi scope BR – MOD – YYMM, khóa SELECT FOR UPDATE trong transaction hoặc dùng database sequence per-scope. Cấp số ngay trước COMMIT.
- Nếu bạn bắt buộc không có lỗ hổng số: không dùng EXPIRED tự động. Mọi số RESERVED không dùng phải chuyển VOIDED kèm biên bản trên hệ thống và có báo cáo khoảng trống số để lưu hồ sơ.
Kỳ kế toán – mở và đóng
- Lịch kỳ: kỳ theo tháng, năm tài chính có tháng bắt đầu cấu hình được. Mỗi kỳ có trạng thái OPEN – SOFT_CLOSE – CLOSED.
- OPEN: được phép ghi sổ.
- SOFT_CLOSE: tạm khóa cho kiểm tra – chỉ Kế toán trưởng mở cổng tạm thời cho các bút toán điều chỉnh có kiểm soát.
- CLOSED: khóa cứng – cấm mọi ghi sổ vào kỳ này.
- Mở kỳ mới
- Tự động tạo kỳ tháng mới ở trạng thái OPEN khi bước sang tháng mới. Reset bộ đếm số theo chính sách.
- Đóng kỳ tháng
- Điều kiện tối thiểu: cân đối tổng – chi tiết AR – AP – Kho khớp GL, không còn chứng từ nháp trong kỳ, tất cả số RESERVED trong kỳ đã chuyển USED hoặc VOIDED có lý do.
- Thao tác đóng đưa kỳ sang CLOSED và đóng băng số liệu chi tiết của kỳ ở báo cáo snapshot.
Quy trình điều chỉnh sau khóa sổ và lối override
- Nguyên tắc vận hành
- Mặc định không mở lại kỳ đã CLOSED. Mọi điều chỉnh phát hiện sau khóa sổ hạch toán vào kỳ hiện tại bằng bút toán điều chỉnh, liên kết chứng từ gốc qua trường reference.
- Với chứng từ kho – giá vốn: không hồi tính kỳ đã khóa. Dùng phiếu điều chỉnh COGS tại ngày hiện tại và lưu giải thích.
- Ngoại lệ có kiểm soát
- Nếu bắt buộc chỉnh dữ liệu kỳ đã CLOSED, chỉ Kế toán trưởng hoặc Quản trị có thể bật cửa sổ mở tạm REOPEN trong tối đa N phút, kèm:
- MFA bắt buộc, lý do tối thiểu 15 ký tự, có thể yêu cầu phê duyệt kép.
- Chỉ phạm vi module – chi nhánh – ngày được chỉ định mới ghi sổ. Hết thời gian tự động đóng lại.
- Nhật ký kiểm toán lưu đầy đủ người duyệt, cửa sổ thời gian, danh sách chứng từ phát sinh.
- Quyền đổi số chứng từ thủ công
- Bị khóa mặc định. Chỉ dùng cho dữ liệu đầu kỳ hoặc sửa lỗi hiếm, yêu cầu quyền cao và lý do. Hệ thống kiểm tra trùng và log trước – sau. Không cho đổi số của chứng từ thuộc kỳ đã CLOSED.
Tiêu chí kỹ thuật – chấp nhận
- Unique index số chứng từ ở mức scope cấu hình. Không cho phép hai chứng từ cùng số trong cùng scope.
- Bảng numerators có khóa duy nhất theo tenant – branch – module – period. Mọi cấp số nằm trong transaction ghi sổ.
- Chính sách TTL đặt trước số, chính sách cho phép hoặc cấm lỗ hổng số và danh sách phân hệ có cấp số riêng được cấu hình trung tâm và kiểm soát thay đổi.
Mẫu sự kiện máy đọc – điền sẵn
- EVTNUMRESERVE|scope=HN-AP-2607|next=0042|voucher_no=HN-AP-2607-0042|status=RESERVED|user=kt.mua01|ts=2026-07-30T09:15:12+07:00
- EVTNUMUSE|voucher_no=HN-AP-2607-0042|idempotency=ACME-AP-01JZ3QZ4E9S7W1Z5C1N0X|status=USED|txid=db-778899|ts=2026-07-30T09:15:14+07:00
- EVTPERIODOPEN|period=2026-08|status=OPEN|by=sys|ts=2026-08-01T00:00:03+07:00
- EVTPERIODCLOSE|period=2026-07|status=CLOSED|by=kt.truong|checks=GL-AR-AP-INV-OK|ts=2026-08-05T18:30:00+07:00
- EVTPOSTCLOSEADJ|origvoucher=HN-AP-2607-0123|adjvoucher=HN-JV-2608-0007|postedin=2026-08|reason=Accrual for late vendor bill|user=kt.tonghop|ts=2026-08-06T10:05:21+07:00
- EVTOVERRIDE|type=REOPENPERIOD|period=2026-07|window=30m|approver1=kt.truong|approver2=it.admin|reason=Audit correction|ts=2026-08-06T09:45:00+07:00
Mẫu sự kiện máy đọc – biến số
- EVTNUMRESERVE|scope={branch}-{module}-{yy}{mm}|next={seq4}|voucherno={voucherno}|status={status}|user={userid}|ts={isotimestamp}
- EVTNUMUSE|voucherno={voucherno}|idempotency={idempotencykey}|status={status}|txid={dbtxnid}|ts={isotimestamp}
- EVTPERIODOPEN|period={yyyy}-{mm}|status=OPEN|by={actor}|ts={iso_timestamp}
- EVTPERIODCLOSE|period={yyyy}-{mm}|status=CLOSED|by={actor}|checks={checklistcode}|ts={isotimestamp}
- EVTPOSTCLOSEADJ|origvoucher={origno}|adjvoucher={adjno}|postedin={yyyy}-{mm}|reason={reason}|user={userid}|ts={isotimestamp}
- EVTOVERRIDE|type={overridetype}|period={yyyy}-{mm}|window={duration}|approver1={userid}|approver2={userid}|reason={reason}|ts={iso_timestamp}
Với khung này, bạn kiểm soát được tính duy nhất và khả năng truy vết số chứng từ, đóng – mở kỳ có kỷ luật, hạn chế tối đa mở lại kỳ, và vẫn có lối xử lý chuẩn cho ngoại lệ mà không làm bể số liệu.
Báo cáo vận hành tối thiểu
Bộ báo cáo dưới đây là tối thiểu để bạn vận hành hằng ngày, kiểm soát khớp số AR AP Kho và thuế, đồng thời tra cứu nhanh theo số chứng từ. Tất cả báo cáo mặc định chỉ lấy chứng từ đã ghi sổ, tôn trọng phân quyền theo chi nhánh và kho, có lọc kỳ ngày, xuất CSV XLSX và có liên kết drill down về chứng từ gốc.
- Sổ chi tiết công nợ AR AP
- Mục đích: xem theo đối tác số dư đầu kỳ, phát sinh Nợ Có và số dư cuối kỳ. Cho phép lọc theo loại sổ AR hay AP, đối tác, chi nhánh, kỳ ngày.
- Trình bày: nhóm theo đối tác. Mỗi đối tác có tổng hợp và có tùy chọn kèm nhật ký chứng từ chi tiết.
- Chỉ số bắt buộc: opening, debit, credit, closing. Tùy chọn hiển thị tuổi nợ theo bucket 1-30 31-60 61+.
- Tiêu chí chấp nhận: tổng phát sinh AR AP phải khớp sổ cái trong kỳ mở. Thời gian sinh báo cáo p95 dưới 2 giây với 5.000 dòng đối tác.
- Mẫu điền sẵn máy đọc:
- ARDETAIL|period=2026-07-01..2026-07-31|branch=HN|ledger=AR|partner=C0001|name=Cong ty Binh Minh|opening=12500000|debit=8000000|credit=5500000|closing=15000000|overdue130=2500000|overdue3160=0|overdue61_plus=0
- APDETAIL|period=2026-07-01..2026-07-31|branch=HN|ledger=AP|partner=V0123|name=VT Co.,Ltd|opening=3000000|debit=12000000|credit=9000000|closing=6000000|overdue130=0|overdue3160=0|overdue61_plus=0
- Mẫu biến số:
- ARDETAIL|period={yyyy-mm-dd}..{yyyy-mm-dd}|branch={branch}|ledger=AR|partner={partnercode}|name={partnername}|opening={amount}|debit={amount}|credit={amount}|closing={amount}|overdue130={amount}|overdue3160={amount}|overdue61_plus={amount}
- APDETAIL|period={yyyy-mm-dd}..{yyyy-mm-dd}|branch={branch}|ledger=AP|partner={vendorcode}|name={vendorname}|opening={amount}|debit={amount}|credit={amount}|closing={amount}|overdue130={amount}|overdue3160={amount}|overdue61_plus={amount}
- Bảng cân đối tồn kho theo kho mặt hàng
- Mục đích: theo dõi số lượng tồn đầu, nhập, xuất, tồn cuối từng SKU từng kho. Tùy chọn kèm trị giá theo giá vốn bình quân hiện hành.
- Lọc: kỳ ngày, kho, nhóm hàng, SKU. Tôn trọng chính sách không cho âm kho.
- Chỉ số: openingqty, inqty, outqty, closingqty, uom, avg_cost, valuation.
- Tiêu chí chấp nhận: tổng tồn cuối bằng tồn sổ của kho tại ngày cuối kỳ. p95 dưới 2 giây với 10.000 dòng SKU-kho.
- Mẫu điền sẵn máy đọc:
- INVBAL|period=2026-07-01..2026-07-31|warehouse=WH-HN|sku=SKU-001|name=Giay A4 80gsm|uom=ream|openingqty=120|inqty=80|outqty=150|closingqty=50|avgcost=45000|valuation=2250000
- INVBAL|period=2026-07-01..2026-07-31|warehouse=WH-HN|sku=SKU-002|name=But bi xanh|uom=pcs|openingqty=500|inqty=0|outqty=120|closingqty=380|avgcost=3000|valuation=1140000
- Mẫu biến số:
- INVBAL|period={yyyy-mm-dd}..{yyyy-mm-dd}|warehouse={warehousecode}|sku={sku}|name={skuname}|uom={uom}|openingqty={number}|inqty={number}|outqty={number}|closingqty={number}|avgcost={amount}|valuation={amount}
- Nhật ký mua bán
- Mục đích: danh sách chứng từ mua và bán trong kỳ để soát so với sổ cái, kho và thuế. Dùng để đối chiếu chéo nhanh giữa các đội Mua hàng Bán hàng Kế toán thuế.
- Lọc: kỳ ngày, chi nhánh, loại chứng từ AR hay AP, trạng thái POSTED VOIDED.
- Cột tối thiểu: date, module, voucherno, partnercode, partnername, netamount, taxamount, totalamount, postedby, postedat.
- Tiêu chí chấp nhận: tổng net và thuế của nhật ký khớp tổng hợp thuế cùng kỳ. p95 dưới 1.5 giây với 5.000 chứng từ.
- Mẫu điền sẵn máy đọc:
- TXNLOG|date=2026-07-12|module=AR|voucherno=HN-AR-2607-0153|partner=C0008|name=Minh Chau Mart|net=7136364|tax=713636|total=7850000|postedby=ar.user01|postedat=2026-07-12T10:35:22+07:00
- TXNLOG|date=2026-07-13|module=AP|voucherno=HN-AP-2607-0042|partner=V0123|name=VT Co.,Ltd|net=2000000|tax=200000|total=2200000|postedby=ap.user02|postedat=2026-07-13T16:02:11+07:00
- Mẫu biến số:
- TXNLOG|date={yyyy-mm-dd}|module={ARorAP}|voucherno={voucherno}|partner={code}|name={partnername}|net={amount}|tax={amount}|total={amount}|postedby={userid}|postedat={isotimestamp}
- Tổng hợp thuế cơ bản
- Mục đích: tổng hợp theo mã thuế suất cho cả bán ra và mua vào trong kỳ, phục vụ kê khai nội bộ không đặc thù quốc gia.
- Lọc: kỳ ngày, chi nhánh, chiều AR hoặc AP, mã thuế.
- Cột: direction, taxcode, rate, taxablebase, taxamount, voucherscount.
- Tiêu chí chấp nhận: cộng dồn theo mã khớp nhật ký mua bán. Rounding theo 2 chữ số thập phân có cấu hình. p95 dưới 1 giây với dưới 50 dòng mã thuế.
- Mẫu điền sẵn máy đọc:
- TAXSUM|period=2026-07|direction=AR|taxcode=TAX10|rate=0.10|taxablebase=35818182|taxamount=3581818|vouchers_count=124
- TAXSUM|period=2026-07|direction=AP|taxcode=TAX5|rate=0.05|taxablebase=9400000|taxamount=470000|vouchers_count=17
- Mẫu biến số:
- TAXSUM|period={yyyy-mm}|direction={ARorAP}|taxcode={code}|rate={decimal}|taxablebase={amount}|taxamount={amount}|vouchers_count={number}
- Truy vấn nhanh theo số chứng từ
- Mục đích: nhập số chứng từ là ra ngay thông tin chính và liên kết tới sổ cái, công nợ, kho, file đính kèm. Hỗ trợ tìm chính xác và bắt đầu bằng.
- Hành vi: ô tìm kiếm toàn hệ thống, ưu tiên match chính xác. Nếu nhiều module trùng số, hiển thị nhiều kết quả theo module.
- Trả về tối thiểu: voucherno, module, status, date, branch, partner, total, lines, hasattachment, glentries, inventorymoves.
- Chỉ mục kỹ thuật: index BTree trên voucher_no, module và prefix index để hỗ trợ tìm bắt đầu bằng. Kết quả p95 dưới 200 ms với kho dữ liệu 100k chứng từ.
- Mẫu điền sẵn máy đọc:
- VOUCHERLOOKUP|voucherno=HN-AR-2607-0153|module=AR|status=POSTED|date=2026-07-12|branch=HN|partner=C0008|total=7850000|lines=5|hasattachment=true|glentries=4|inventory_moves=0
- VOUCHERLOOKUP|voucherno=HN-AP-2607-0042|module=AP|status=POSTED|date=2026-07-13|branch=HN|partner=V0123|total=2200000|lines=3|hasattachment=false|glentries=3|inventory_moves=2
- Mẫu biến số:
- VOUCHERLOOKUP|voucherno={voucherno}|module={ARorAPorINV}|status={status}|date={yyyy-mm-dd}|branch={branch}|partner={code}|total={amount}|lines={number}|hasattachment={truefalse}|glentries={number}|inventory_moves={number}
Lưu ý vận hành
- Tất cả báo cáo chỉ lấy dữ liệu đã POSTED. Nếu cần kèm bản nháp, bật cờ Show Draft trong bộ lọc và phân biệt trạng thái bằng cột status.
- Xác thực khớp chéo tối thiểu mỗi kỳ: tổng ARDETAIL.debit credit phải khớp tổng phát sinh AR trên sổ cái, tổng INVBAL.valuation cuối kỳ khớp tài khoản hàng tồn kho, tổng TAX_SUM theo AR AP khớp nhật ký mua bán.
- Để giữ tốc độ, tạo index trên các trường khóa lọc chính gồm voucherno, date, branchid, partnerid, warehouseid, sku_id. Các báo cáo tổng hợp nên chạy trên snapshot cuối ngày khi có.
Phân quyền, phân tách dữ liệu và nhật ký kiểm toán
Mục tiêu ở đây là bảo vệ sổ sách bằng nguyên tắc tối thiểu quyền và tách dữ liệu theo chi nhánh – kho, đồng thời để lại dấu vết bất biến cho mọi thay đổi. Mình chuẩn hóa 4 vai trò, phạm vi truy cập theo chi nhánh – kho, yêu cầu MFA cho quyền nhạy cảm và thiết kế nhật ký kiểm toán append-only để đội phát triển triển khai ngay.
Định nghĩa vai trò
- Người nhập: tạo – sửa bản nháp, đính kèm chứng từ, tra cứu báo cáo cơ bản trong phạm vi được gán. Được Submit for Approval nhưng không Post, không Void, không Close Period.
- Người duyệt: rà soát và phê duyệt chứng từ trong phạm vi được gán. Không được phê duyệt chứng từ do chính mình tạo. Không Post, không Close Period.
- Kế toán trưởng: phê duyệt cuối cùng, Post lên sổ cái, mở – khóa kỳ, xác nhận điều chỉnh tồn kho và override có kiểm soát kèm lý do. Mặc định có quyền xem tất cả chi nhánh và kho hoặc theo gán cụ thể nếu muốn tách.
- Quản trị: quản lý người dùng, vai trò, tham số hệ thống, chính sách bảo mật và IP. Mặc định không có quyền Post – Approve – Void chứng từ để tách nhiệm vụ. Có quyền xem nhật ký kiểm toán.
Nguyên tắc phân tách dữ liệu theo chi nhánh – kho
- Mọi chứng từ và bút toán chứa branchid và, nếu liên quan kho, warehouseid. Mọi truy vấn dữ liệu đều filter theo danh sách branchid và warehouseid mà tài khoản được gán.
- Bản đồ quyền dữ liệu:
- Gán theo chi nhánh: user có thể thấy và thao tác trên mọi kho thuộc chi nhánh đó, trừ khi bị giới hạn thêm theo kho.
- Gán theo kho: user chỉ thấy và thao tác trên các kho được gán, kể cả khi có quyền chi nhánh.
- Tập quyền là giao cắt của vai trò và phạm vi dữ liệu. Ví dụ user có vai trò Người duyệt nhưng không có kho WH-HN thì không duyệt được chứng từ kho đó.
- Maker – checker:
- Người tạo không được phê duyệt hoặc post chính chứng từ đó.
- Tùy chọn hai người độc lập cho bước Approve và Post có thể bật ở module nhạy cảm như AP.
Ràng buộc MFA cho quyền nhạy cảm
- Yêu cầu step-up MFA ở lần đầu trong phiên hoặc sau 15 phút không hoạt động khi thực hiện các hành động:
- Approve, Post, Void chứng từ
- Close Period – Open Period
- Điều chỉnh tồn kho, điều chỉnh giá vốn
- Quản trị người dùng, phân quyền, đổi tham số thuế – tiền tệ
- Xuất dữ liệu chi tiết AR/AP, tải nhật ký kiểm toán thô
- Chấp nhận TOTP hoặc khóa bảo mật tiêu chuẩn WebAuthn. Nếu phiên đổi thiết bị hoặc IP mạng, bắt buộc step-up lại.
Quy định giảm rủi ro mặc định
- Nguyên tắc tối thiểu quyền: user mới chỉ có vai trò Người nhập và không có quyền cross-branch cho đến khi được gán.
- SoD tách vai trò: Quản trị mặc định không thể Post – Approve; Kế toán trưởng không thể đổi phân quyền người dùng.
- Hạn chế export: chỉ Kế toán trưởng mới được export chi tiết đối tác và sổ nhật ký, kèm MFA.
Nhật ký kiểm toán append-only
- Phạm vi ghi log: đăng nhập – đăng xuất – MFA, tạo – sửa – phê duyệt – post – void chứng từ, thay đổi danh mục đối tác – hàng – kho, nhập – xuất dữ liệu, mở – khóa kỳ, thay đổi cấu hình, thay đổi phân quyền, xem – tải audit log.
- Trường tối thiểu mỗi bản ghi: eventid, occurredat, userid, action, entitytype, entityid, branchid, warehouseid, clientip, useragent, beforehash, afterhash, diffjson, mfacontext, prevloghash, logchain_hash.
- Tính bất biến:
- Log chỉ cho phép INSERT, không UPDATE/DELETE.
- Mỗi bản ghi chứa prevloghash để tạo chuỗi băm phát hiện can thiệp.
- Phân quyền truy cập audit viewer chỉ đọc, xuất log yêu cầu MFA.
- Lưu trữ:
- Thời gian lưu mặc định 24 tháng – cấu hình được theo chính sách nội bộ.
- Snapshot định kỳ và sao lưu log cùng DB giao dịch để truy vết đầy đủ.
Mẫu ma trận quyền ở mức hành động
- Ý nghĩa nhanh cho đội phát triển:
- module: AR, AP, INV, GL, SYS, RPT, AUDIT
- action: VIEW, CREATE, EDIT, SUBMIT, APPROVE, POST, VOID, ADJUST, IMPORT, EXPORT, CONFIG, USERADMIN, PERIODCLOSE
- scope: OWNBRANCH, OWNWAREHOUSE, ALL
- mfa: YES, NO
- Bản điền sẵn máy đọc:
- PERM|role=NGUOINHAP|module=AP|action=CREATE|scope=OWNBRANCH|mfa=NO
- PERM|role=NGUOINHAP|module=AP|action=EDIT|scope=OWNBRANCH|mfa=NO
- PERM|role=NGUOINHAP|module=AP|action=SUBMIT|scope=OWNBRANCH|mfa=NO
- PERM|role=NGUOIDUYET|module=AP|action=APPROVE|scope=OWNBRANCH|mfa=YES
- PERM|role=KETOANTRUONG|module=AP|action=POST|scope=ALL|mfa=YES
- PERM|role=KETOANTRUONG|module=INV|action=ADJUST|scope=ALL|mfa=YES
- PERM|role=QUANTRI|module=SYS|action=USERADMIN|scope=ALL|mfa=YES
- PERM|role=QUAN_TRI|module=GL|action=POST|scope=ALL|mfa=NO|deny=TRUE
- PERM|role=KETOANTRUONG|module=GL|action=PERIOD_CLOSE|scope=ALL|mfa=YES
- PERM|role=KETOANTRUONG|module=RPT|action=EXPORT|scope=ALL|mfa=YES
- PERM|role=NGUOIDUYET|module=AP|action=POST|scope=OWNBRANCH|mfa=YES|deny=TRUE
- PERM|role=QUAN_TRI|module=AUDIT|action=VIEW|scope=ALL|mfa=YES
- Mẫu biến số:
- PERM|role={ROLECODE}|module={MODULE}|action={ACTION}|scope={OWNBRANCHorOWNWAREHOUSEorALL}|mfa={YESor_NO}[|deny=TRUE]
Ràng buộc maker – checker ở cấp chứng từ
- Rule mặc định:
- creatorid != approverid
- approverid != posterid
- Khi user có nhiều vai trò, hệ thống vẫn áp rule này, không gỡ bỏ do hợp nhất quyền.
Gợi ý triển khai nhanh cho đội phát triển
- Lớp ứng dụng: filter bắt buộc theo branchid và warehouseid từ token phiên. Với truy vấn báo cáo, luôn thêm điều kiện phạm vi trước khi chạy.
- Lớp DB: bật Row Level Security với policy theo userid và mối quan hệ user – branch – warehouse. Tạo view chỉ đọc cho auditevents.
- Kiểm tra quyền theo ma trận: tra cứu role-action-scope theo thứ tự deny trước allow, bắt MFA context trước khi thực thi action nhạy cảm.
Ví dụ nhật ký kiểm toán điền sẵn máy đọc
- AUDIT|eventid=0001A|occurredat=2026-07-12T10:35:22+07:00|userid=ap.user02|action=POST|entitytype=VOUCHERAP|entityid=HN-AP-2607-0042|branchid=HN|warehouseid=WH-HN|clientip=192.168.10.23|mfacontext=TOTP|beforehash=b0|afterhash=9f2c…|diffjson={"status":"APPROVED→POSTED"}|prevloghash=0000|logchain_hash=7ac4…
- Mẫu biến số:
- AUDIT|eventid={id}|occurredat={isotimestamp}|userid={user}|action={action}|entitytype={type}|entityid={id}|branchid={branch}|warehouseid={wh}|clientip={ip}|mfacontext={method}|beforehash={hash}|afterhash={hash}|diffjson={json}|prevloghash={hash}|logchain_hash={hash}
Nhờ khung này, bạn có thể gán đúng người đúng việc, giữ dữ liệu tách bạch theo chi nhánh – kho, và có chuỗi kiểm toán không thể xóa sửa, đủ bằng chứng khi đối soát và kiểm tra nội bộ.
Kiến trúc triển khai on-prem web cho MVP
Mục tiêu là cho mọi máy trạm nội bộ truy cập qua trình duyệt, còn máy chủ ứng dụng và cơ sở dữ liệu ở trong vùng máy chủ tách biệt, không mở bất kỳ cổng nào ra Internet. Điểm khống chế trung tâm là một reverse proxy nội bộ. Backend chỉ nghe trên localhost hoặc UNIX socket để không thể bị truy cập trực tiếp qua mạng.
Kiến trúc thành phần
- Client: trình duyệt trên máy trạm nội bộ hoặc máy từ xa đã vào VPN.
- Reverse proxy: Nginx hoặc HAProxy đặt trong VLAN máy chủ. Đây là cổng duy nhất mở cho client nội bộ qua HTTPS 443.
- Ứng dụng backend: chạy trên cùng máy với reverse proxy hoặc máy kế cận, nhưng chỉ nghe 127.0.0.1 hoặc UNIX socket để buộc đi qua proxy.
- Cơ sở dữ liệu: nằm trong VLAN máy chủ, chỉ cho phép kết nối từ IP của máy ứng dụng. Không có lộ trình từ VLAN người dùng tới DB.
- Lưu trữ file đính kèm và dịch vụ AI nội bộ: cùng VLAN máy chủ, chỉ mở cổng nội bộ giữa các máy chủ với nhau.
- DNS nội bộ và chứng thư TLS: tên miền nội bộ trỏ reverse proxy, dùng CA nội bộ hoặc chứng thư hợp lệ, không phát hành bản ghi công khai.
Phân lớp mạng và tường lửa
- Tách ít nhất 3 VLAN: người dùng, máy chủ, khách. Chỉ VLAN người dùng được phép đi tới reverse proxy cổng 443. VLAN khách bị chặn hoàn toàn.
- Trên firewall hoặc router L3: chặn toàn bộ lưu lượng từ Internet vào reverse proxy. Chỉ phép truy cập từ các dải IP nội bộ hợp lệ và dải IP của VPN đến cổng 443 của reverse proxy. Không NAT port mở ra ngoài.
- Chặn truy cập trực tiếp từ VLAN người dùng đến máy ứng dụng và DB. Chỉ reverse proxy có thể nói chuyện với backend qua loopback hoặc socket nội bộ.
- DB chỉ cho phép từ IP của máy ứng dụng. Không mở management port ra ngoài VLAN máy chủ.
- Nếu mạng có IPv6, chặn IPv6 tới reverse proxy và DB ở perimeter cho đến khi có kế hoạch địa chỉ và whitelist IPv6 tương ứng.
Vai trò của reverse proxy
- Terminate TLS, enforce whitelist IP theo CIDR dựa vào TCP source IP mà proxy nhìn thấy.
- Làm sạch header: không tin bất kỳ X-Forwarded-For do client gửi. Proxy tự đặt X-Forwarded-For và X-Real-IP để phục vụ log.
- Giới hạn tốc độ và kích thước request, bật HTTP keepalive hợp lý, cấu hình timeouts để chống treo kết nối.
- Không publish bất kỳ đường dẫn quản trị nội bộ của backend. Nếu có các endpoint admin, buộc chúng chỉ tồn tại trên localhost.
Cấu hình backend an toàn
- Ràng buộc cổng nghe về 127.0.0.1 hoặc UNIX socket. Không bind 0.0.0.0. Vô hiệu hóa mọi tính năng auto-discovery hay serve static ra ngoài.
- Phân tách người dùng DB ở mức quyền tối thiểu, dùng tài khoản riêng cho ứng dụng chỉ có quyền cần thiết.
- Ghi log đầy đủ IP gốc do proxy đặt, nhưng mọi kiểm soát truy cập theo IP thực hiện tại proxy hoặc firewall, không tại ứng dụng.
Mẫu Nginx trên máy reverse proxy cho app web on-prem
- Ý nghĩa: chặn mọi IP không thuộc whitelist, buộc HTTPS nội bộ, chuyển tiếp tới backend nghe localhost, làm sạch header.
# /etc/nginx/conf.d/accounting.conf
server {
listen 443 ssl http2;
server_name acct.local.company;
ssl_certificate /etc/pki/tls/certs/acct_local.crt;
ssl_certificate_key /etc/pki/tls/private/acct_local.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# IP allowlist theo CIDR nội bộ và pool VPN
allow 192.168.10.0/24;
allow 192.168.20.0/24;
allow 10.255.0.0/24;
deny all;
# Bảo vệ cơ bản
client_max_body_size 20m;
keepalive_timeout 65s;
send_timeout 30s;
# Xóa mọi X-Forwarded-For từ client, tự đặt lại
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
location / {
proxy_http_version 1.1;
proxy_read_timeout 30s;
proxy_connect_timeout 5s;
proxy_send_timeout 30s;
# Backend chỉ nghe localhost
proxy_pass http://127.0.0.1:3000;
}
}
Mẫu systemd buộc backend chỉ nghe localhost
# /etc/systemd/system/acct-backend.service
[Unit]
Description=Accounting App Backend
After=network.target
[Service]
User=acct
Group=acct
Environment="PORT=3000" "HOST=127.0.0.1"
ExecStart=/opt/acct/backend/bin/server --port ${PORT} --host ${HOST}
Restart=on-failure
AmbientCapabilities=CAP_NET_BIND_SERVICE
NoNewPrivileges=true
[Install]
WantedBy=multi-user.target
Ví dụ luật firewall host bổ trợ trên máy ứng dụng
- Ý nghĩa: chỉ cho phép proxy nội bộ truy cập cổng 3000 nếu lỡ backend bind TCP thay vì UNIX socket. Ưu tiên dùng socket hoặc loopback để bỏ hẳn nhu cầu rule này.
# UFW example
sudo ufw default deny incoming
sudo ufw allow from 127.0.0.1 to any port 3000 proto tcp
sudo ufw enable
Lý do chọn web client thay desktop cho MVP
- Cập nhật tập trung: vá lỗi và nâng cấp một chỗ ở máy chủ, toàn bộ người dùng có bản mới ngay. Desktop buộc đóng gói và phân phối, tăng gánh vận hành.
- Chuẩn hóa bảo mật và nhật ký: mọi truy cập đi qua proxy để enforce IP, TLS, rate limit và log. Desktop thường kéo theo nhiều biến thể mạng và thiết bị khó kiểm soát đồng nhất.
- Triển khai nhanh trong on-prem: không cần cài đặt trên từng máy, chỉ cần trình duyệt tiêu chuẩn. Hạ yêu cầu hỗ trợ IT nội bộ ở giai đoạn thử nghiệm.
- Phù hợp kiểm soát IP theo dải nội bộ và VPN: một điểm chặn dễ audit và vận hành. Desktop có thể mạnh về mTLS ràng buộc thiết bị, nhưng chi phí dựng PKI và quản lý chứng thư không hợp với MVP.
Whitelist IP theo lớp và truy cập ngoài trụ sở qua VPN
Mục tiêu là chỉ cho phép truy cập từ các dải IP nội bộ đã định danh và từ pool VPN riêng, chặn toàn bộ nguồn khác. Quyết định cho phép hay chặn phải dựa trên TCP source IP tại hop đầu tiên bạn kiểm soát như firewall hoặc reverse proxy, không dựa vào X-Forwarded-For. Phần dưới là template cấu hình kèm chính sách vận hành đủ để đội IT áp dụng ngay.
Các bước thực hiện
- Chốt dải CIDR whitelist
- LAN Kế toán: 192.168.10.0/24
- LAN Kho: 192.168.20.0/24
- Pool VPN full-tunnel: 10.255.0.0/24
- Không đưa Guest WiFi vào whitelist, ví dụ 192.168.50.0/24 bị chặn.
- Nếu có chi nhánh nối site-to-site về trụ sở, thêm từng subnet chi nhánh vào whitelist sau khi xác minh không trùng lặp địa chỉ.
- Firewall ACL tại perimeter hoặc router L3
- Ý tưởng: chỉ cho phép ba dải CIDR trên đến reverse proxy cổng 443, chặn tất cả nguồn khác. Không publish từ Internet.
- Ví dụ iptables trên firewall có IP reverse proxy là 192.168.10.5:
# Flush cẩn trọng nếu đang từ xa. Chạy trong bảo trì.
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# Cho phép quản trị nội bộ nếu cần
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Allow từ LAN kế toán và kho tới proxy 443
iptables -A INPUT -p tcp -s 192.168.10.0/24 -d 192.168.10.5 --dport 443 -j ACCEPT
iptables -A INPUT -p tcp -s 192.168.20.0/24 -d 192.168.10.5 --dport 443 -j ACCEPT
# Allow từ pool VPN tới proxy 443
iptables -A INPUT -p tcp -s 10.255.0.0/24 -d 192.168.10.5 --dport 443 -j ACCEPT
# Mặc định còn lại bị DROP
- Chặn IPv6 ở MVP nếu bạn chưa có kế hoạch địa chỉ rõ ràng:
ip6tables -P INPUT DROP
ip6tables -P FORWARD DROP
ip6tables -P OUTPUT DROP
# Hoặc tắt IPv6 trên host proxy, kiểm tra sau bằng: curl -6 https://acct.local.company # phải fail
- Kiểm tra: từ IP hợp lệ, curl https://acct.local.company trả 200. Từ IP ngoài whitelist, kết nối bị reset hoặc timeout. Đảm bảo không có NAT port nào tới 443 từ Internet.
- Reverse proxy Nginx enforce theo CIDR, không dựa X-Forwarded-For
# /etc/nginx/conf.d/accounting.conf
server {
listen 443 ssl http2;
server_name acct.local.company;
ssl_certificate /etc/pki/tls/certs/acct_local.crt;
ssl_certificate_key /etc/pki/tls/private/acct_local.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# Whitelist theo TCP source IP mà Nginx nhìn thấy
allow 192.168.10.0/24;
allow 192.168.20.0/24;
allow 10.255.0.0/24;
deny all;
client_max_body_size 20m;
keepalive_timeout 65s;
send_timeout 30s;
# Không tin X-Forwarded-For từ client. Ghi đè bằng $remote_addr để log phân tích.
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
access_log /var/log/nginx/acct_access.log main;
location / {
proxy_http_version 1.1;
proxy_read_timeout 30s;
proxy_connect_timeout 5s;
proxy_send_timeout 30s;
proxy_pass http://127.0.0.1:3000;
}
}
- Lưu ý: việc cho phép hay chặn chỉ dựa vào allow/deny CIDR. Header X-Forwarded-For chỉ để ghi log, không dùng để quyết định truy cập.
- Reverse proxy HAProxy enforce theo CIDR, không dựa X-Forwarded-For
# /etc/haproxy/haproxy.cfg
global
log /dev/log local0
maxconn 2000
defaults
mode http
log global
option httplog
timeout connect 5s
timeout client 30s
timeout server 30s
# Danh sách trắng IP ở file ngoài để vận hành thay đổi
# /etc/haproxy/allowlist.lst:
# 192.168.10.0/24
# 192.168.20.0/24
# 10.255.0.0/24
frontend fe_https
bind :443 ssl crt /etc/ssl/private/acct.pem
acl src_allowed src -f /etc/haproxy/allowlist.lst
tcp-request connection reject if !src_allowed
http-response set-header Strict-Transport-Security max-age=31536000
http-request del-header X-Forwarded-For
http-request set-header X-Forwarded-For %[src]
http-request set-header X-Real-IP %[src]
default_backend be_app
backend be_app
server app1 127.0.0.1:3000 check
- Lưu ý: quyết định reject nằm ở tcp-request connection dựa trên src, không dùng hdr_ip hay X-Forwarded-For.
- Chính sách VPN full tunnel cho làm việc từ xa
- Không whitelist IP nhà riêng. Bắt buộc vào VPN để có IP thuộc 10.255.0.0/24 rồi mới truy cập.
- OpenVPN server ép full-tunnel và cấp pool riêng:
# /etc/openvpn/server.conf
server 10.255.0.0 255.255.255.0
topology subnet
push "redirect-gateway def1"
push "dhcp-option DNS 192.168.10.10" # DNS nội bộ
client-config-dir /etc/openvpn/ccd
keepalive 10 60
inactive 900
reneg-sec 28800
# Không bật client-to-client. Dùng firewall chặn east-west giữa 10.255.0.0/24.
- WireGuard client ép full-tunnel:
# client wg0.conf
[Interface]
Address = 10.255.0.10/24
PrivateKey = <client-key>
DNS = 192.168.10.10
[Peer]
PublicKey = <server-key>
Endpoint = vpn.company.local:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
- Trên firewall nội bộ, chỉ cho phép VPN đến reverse proxy 443, chặn các đích khác trừ khi có nhu cầu quản trị đã duyệt:
iptables -A FORWARD -s 10.255.0.0/24 -d 192.168.10.5 -p tcp --dport 443 -j ACCEPT
iptables -A FORWARD -s 10.255.0.0/24 -j DROP
- Kiểm tra: từ client VPN, truy cập được app. Ping hay truy cập máy trạm khác qua VPN bị chặn.
- Vận hành danh sách trắng và nhật ký
- Nguồn sự thật: một file allowlist duy nhất được quản lý bằng kiểm soát thay đổi. Với Nginx dùng block allow/deny trong file cấu hình, với HAProxy dùng allowlist.lst. Bất kỳ thay đổi nào phải có ticket, thời hạn, người duyệt. Lịch xoá quyền tạm thời theo hạn.
- Ghi log đầy đủ tại reverse proxy và VPN:
# Nginx log_format main cần chứa $remote_addr và $host
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent "$http_referer" '
'"$http_user_agent"';
- Mẫu log máy đọc để đối soát whitelist, xuất từ proxy hoặc SIEM:
- time|component|action|srcip|matchedcidr|host|uri|user_id|result
- 2026-03-01T09:15:22Z|nginx|access_check|192.168.10.23|192.168.10.0/24|acct.local.company|/login|-|allow
- 2026-03-01T09:16:01Z|haproxy|access_check|203.113.0.10|-|acct.local.company|/|-|deny
- Bảo trì định kỳ: soát log deny để phát hiện thiết bị cấu hình sai, dọn CIDR không còn dùng, kiểm tra curl -6 để chắc chắn IPv6 vẫn bị chặn. Khi thay đổi VLAN hoặc site-to-site, cập nhật whitelist và chạy test kế bên.
Checklist kiểm tra nhanh sau triển khai
- Từ IP nội bộ hợp lệ: truy cập ok. Từ Guest WiFi: bị chặn. Từ Internet công cộng: không route vào proxy.
- Từ client VPN: truy cập ok. Truy cập máy nội bộ khác qua VPN: bị chặn.
- Đổi hoặc thêm CIDR trong allowlist và reload proxy: có hiệu lực tức thì, log ghi nhận matched_cidr đúng.
- Bật tạm IPv6 trên máy client và thử truy cập: bị chặn hoàn toàn. Nếu không, bổ sung chặn tại firewall ngay.
Với bộ cấu hình và chính sách trên, bạn có hai lớp chặn cứng ở firewall và reverse proxy, chỉ cho phép nguồn đã định danh và bắt buộc full-tunnel cho làm việc từ xa. Header X-Forwarded-For chỉ được đặt để phục vụ phân tích và truy vết, không tham gia quyết định quyền truy cập.
Bảo mật dữ liệu, sao lưu và khôi phục sau sự cố
Mình chốt một nền tảng an toàn vừa đủ cho on-prem: mã hóa đúng chỗ, bí mật được giữ tách biệt, lịch backup có kiểm tra restore thật, mục tiêu RPO RTO đo được, phân quyền chặt ở OS và DB, cùng kịch bản DR nội bộ có thể diễn tập. Bạn có thể triển khai ngay theo template dưới đây rồi tinh chỉnh.
Mã hóa dữ liệu
- In transit
- Web đã qua HTTPS ở reverse proxy. Bổ sung TLS cho kết nối ứng dụng đến DB, bật xác thực mạnh.
- PostgreSQL mẫu cấu hình:
# postgresql.conf
ssl = on
ssl_cert_file = 'server.crt'
ssl_key_file = 'server.key'
password_encryption = 'scram-sha-256'
wal_level = replica
archive_mode = on
archive_command = 'rsync -a %p backup@backup1:/var/backups/pg/wal/%f'
archive_timeout = 60
# pg_hba.conf
hostssl acctdb app_user 192.168.10.0/24 scram-sha-256
hostssl acctdb app_user 10.255.0.0/24 scram-sha-256
- Ghi chú triển khai: cấp cert nội bộ hợp lệ, tạo user app_user chỉ có quyền cần thiết, buộc client kết nối hostssl.
- At rest
- Mã hóa volume chứa DB và đính kèm bằng LUKS dm-crypt. Ví dụ gắn /dev/sdb cho dữ liệu:
# Tạo volume mã hóa
sudo cryptsetup luksFormat /dev/sdb
sudo cryptsetup open /dev/sdb pgdata_crypt
sudo mkfs.ext4 /dev/mapper/pgdata_crypt
sudo mkdir -p /var/lib/postgresql
sudo mount /dev/mapper/pgdata_crypt /var/lib/postgresql
# Gắn tự động khi khởi động: thêm vào /etc/fstab và /etc/crypttab, kiểm thử reboot bảo trì
- Với backup rời máy, dùng ổ đĩa ngoài hoặc NAS có volume LUKS riêng. Không để bản sao không mã hóa nằm ngoài khu vực kiểm soát.
Quản lý bí mật
- Lưu biến môi trường và DSN trong file chỉ đọc bởi service account ứng dụng. Dùng systemd để nạp bí mật, phân quyền 600.
# /etc/systemd/system/acct.service.d/override.conf
[Service]
EnvironmentFile=/etc/acct/.env
# /etc/acct/.env (chmod 600, chown acct:acct)
DB_DSN=postgresql://app_user:V3ryStr0ngP@ss@db.acct.local:5432/acctdb?sslmode=require
JWT_SECRET=8a9c3f1c1e0e4b30a3c9d07f2b5e6a91
- Tách vai trò: appuser cho ứng dụng, dbaadmin cho quản trị, repl_user cho sao chép. Xoay vòng mật khẩu mỗi 90 ngày hoặc sớm hơn nếu nhân sự biến động. Nếu đội IT sẵn sàng, dùng Vault nội bộ để cấp và xoay vòng tự động.
Lịch backup DB và file đính kèm
- Mục tiêu mặc định
- RPO DB: 15 phút nhờ WAL archiving.
- RTO hệ thống: 4 giờ với thủ tục khôi phục sẵn kịch bản.
- DB PostgreSQL
- Base backup hằng đêm 01:00, WAL archiving liên tục, thêm logical dump để khôi phục hạt mịn.
# Tài khoản backup@backup1 có SSH key hạn chế lệnh rsync
# Cron trên máy DB
# 1) Logical dump hằng đêm để khôi phục đối tượng riêng lẻ
0 2 * * * pg_dump -Fc -U app_owner acctdb > /var/backups/pg/dumps/acctdb_$(date +\%F).dump
# 2) Base backup nén hằng đêm
30 2 * * * pg_basebackup -U backup -D /var/backups/pg/base/$(date +\%F) -Ft -z -P
# 3) Dọn backup cũ, giữ 14 ngày dump và 7 bản base
0 4 * * * find /var/backups/pg/dumps -type f -mtime +14 -delete
15 4 * * * ls -1dt /var/backups/pg/base/* | tail -n +8 | xargs -r rm -rf
- Kiểm tra archive_command hoạt động bằng cách theo dõi WAL mới xuất hiện ở backup1 trong 1 phút sau giao dịch thử.
- File đính kèm
- Nếu lưu tại /srv/acct-attachments, đồng bộ gia tăng mỗi giờ về ổ backup đã mã hóa, giữ snapshot theo ngày.
# Cron trên máy ứng dụng
5 * * * * rsync -a --delete /srv/acct-attachments/ /backup/attachments/hourly/
10 0 * * * rsync -a --link-dest=/backup/attachments/hourly/ /srv/acct-attachments/ /backup/attachments/daily/$(date +\%F)/
- RPO đính kèm: 1 giờ. Giữ 14 snapshot ngày, xoá cũ theo lịch.
Kiểm tra restore định kỳ
- Bài kiểm tra hàng tháng, chạy trong giờ bảo trì đã thông báo:
- Khôi phục DB từ base backup gần nhất rồi áp WAL đến một thời điểm ngẫu nhiên trong 48 giờ gần nhất.
# Trên máy staging
sudo systemctl stop postgresql
rm -rf /var/lib/postgresql/data/*
tar -xzf /var/backups/pg/base/2026-07-15/base.tar.gz -C /var/lib/postgresql/data
echo "restore_command = 'cp /var/backups/pg/wal/%f %p'" >> /var/lib/postgresql/data/postgresql.auto.conf
echo "recovery_target_time = '2026-07-15 10:30:00+07'" >> /var/lib/postgresql/data/postgresql.auto.conf
touch /var/lib/postgresql/data/recovery.signal
sudo systemctl start postgresql
- Chạy pg_isready, kiểm tra số bản ghi mẫu và đối chiếu hash tổng một vài bảng chính.
- Khôi phục thư mục đính kèm vào staging, mở ngẫu nhiên 10 file để xác nhận tính toàn vẹn.
- Ghi lại tổng thời gian, so với RTO mục tiêu. Nếu vượt, tối ưu hoặc điều chỉnh hạ tầng.
Phân quyền OS và DB
- OS
- Mỗi dịch vụ một tài khoản hệ thống riêng: acct cho app, postgres cho DB, backup cho sao lưu. Không cấp shell cho tài khoản dịch vụ, đặt home no-login khi có thể.
- Quyền thư mục
- /var/lib/postgresql thuộc postgres:postgres, 700.
- /srv/acct-attachments thuộc acct:acct, 750. Chỉ app ghi, backup chỉ đọc qua sudo rule hẹp.
- /var/backups chỉ cho backup:backup, 700.
- DB
- Vai trò
- app_user: quyền CRUD trong schema app, không CREATE EXTENSION, không SUPERUSER.
- report_readonly: SELECT một chiều cho báo cáo ad-hoc.
- dba_admin: quyền quản trị, chỉ cấp cho ít người, đăng nhập MFA qua jumphost.
- Hạn chế kết nối bằng pg_hba theo CIDR nội bộ và VPN, buộc hostssl, scram-sha-256. Tắt user chưa dùng, khóa khi nhân sự nghỉ.
Kịch bản DR trong cùng on-prem
- Mô hình khuyến nghị: 2 máy vật lý trong cùng site, khác phòng hoặc nguồn điện. DB primary trên node A, standby hot trên node B qua streaming replication. Ứng dụng trỏ DB qua DNS nội bộ db.acct.local TTL 30 giây.
- Tạo standby nhanh bằng pg_basebackup
# Trên standby B
pg_basebackup -h A.db.local -U repl_user -D /var/lib/postgresql/data -R -P
# -R tự tạo standby.signal và primary_conninfo
sudo systemctl start postgresql
- Diễn tập failover bán thủ công
- Cắt ứng dụng vào chế độ bảo trì, dừng ghi mới.
- Trên standby B: promote
pg_ctl -D /var/lib/postgresql/data promote
- Cập nhật bản ghi DNS db.acct.local trỏ về B. Xóa cache DNS trên reverse proxy, kiểm tra ứng dụng hoạt động.
- Ghi sự cố, đo tổng thời gian đến khi người dùng quay lại thao tác được. So với RTO 4 giờ.
- Dựng lại standby mới từ node còn sống để phục hồi khả năng DR.
- Ghi chú vận hành
- Dùng replication slots để tránh mất WAL khi standby tạm ngưng.
- Sau khi failover, không bật lại primary cũ để ghi vào cụm mới khi chưa re-seed, tránh split brain.
- Lưu checklist giấy cho trường hợp mất truy cập wiki nội bộ.
Tiêu chí chấp nhận vận hành
- Mã hóa in transit bật ở web và DB, kiểm tra bằng openssl s_client và psql sslmode=require.
- Volume dữ liệu và backup nằm trên đĩa đã mã hóa, có biên bản thử unlock.
- Sao lưu chạy đúng lịch, có log thành công, có dọn cũ.
- Bài diễn tập restore hàng tháng hoàn tất dưới RTO 4 giờ, dữ liệu khớp mẫu. Nếu không đạt, ghi nguyên nhân và hành động khắc phục.
- Nhật ký thay đổi bí mật và quyền được lưu, có người duyệt, có ngày hết hạn cho quyền tạm.
Giao diện nhập liệu tối ưu cho kế toán
Mình thiết kế giao diện theo nguyên tắc ưu tiên tốc độ gõ, mắt không rời bảng, quyết định hiển thị rõ ràng ngay tại dòng đang nhập. Mọi thao tác thường ngày phải hoàn tất chỉ với bàn phím, chuột chỉ là hỗ trợ.
Bố cục lưới chi tiết kiểu Excel
- Con trỏ luôn nằm ở ô hiện tại, Tab sang phải, Shift+Tab sang trái, Enter xuống dòng, Ctrl+Enter kết thúc dòng và thêm dòng mới ngay dưới.
- Ô có validate hiển thị viền màu và tooltip lỗi ngay cạnh ô, không bật hộp thoại toàn màn hình.
- Hỗ trợ copy paste nhiều ô theo khối, nhân dòng theo mẫu, tự tính số tiền dòng khi rời ô số lượng hoặc đơn giá.
Phím tắt bắt buộc
- Ctrl+N tạo chứng từ mới, Ctrl+O mở tìm nhanh, Ctrl+S lưu nháp, Ctrl+Shift+S lưu và đánh số, Ctrl+P in PDF.
- Ctrl+D nhân bản dòng hiện tại, Ctrl+L thêm dòng trống dưới, Ctrl+Del xóa dòng hiện tại.
- F2 sửa nhanh ô, F4 mở danh mục chọn cho ô có danh mục, F6 chuyển tiêu đề chứng từ sang chi tiết, F7 chuyển chi tiết về tiêu đề.
- Ctrl+J mở bảng Cảnh báo, Ctrl+I đính kèm file, Ctrl+K đổi đối tác, Ctrl+R làm mới giá trị gợi ý, Ctrl+? danh sách phím tắt.
- Esc hủy chỉnh sửa ô, Alt+G ghi sổ khi không còn lỗi cứng.
Tìm kiếm nhanh theo số chứng từ và đối tác
- Ô tìm ở thanh tiêu đề tiếp nhận cú pháp tự nhiên có tiền tố: sct: số chứng từ, ncc: nhà cung cấp, kh: khách hàng, po: số PO.
- Gợi ý theo thời gian thực với tô đậm phần khớp, ưu tiên kết quả trong kỳ mở, bấm Enter để nhảy thẳng đến chứng từ hoặc điền đối tác vào form hiện tại.
- Hỗ trợ mã gần giống và thiếu dấu tiếng Việt, ví dụ tìm “Minh an” vẫn ra “Minh Ân Co., Ltd”.
Ví dụ hoàn chỉnh tìm nhanh
- Nhập: sct:AP-2026-00125 ncc:Minh Ân
- Kết quả: 1 hóa đơn mua AP-2026-00125 của Minh Ân Co., Ltd ngày 2026-07-18, tổng 152.400.000 VND. Enter để mở.
- Biến thể: sct:SỔ-CHỨNG-TỪ ncc:TÊN-ĐỐI-TÁC
Tự động hoàn dữ liệu an toàn
- Điền gợi ý dựa trên lịch sử gần nhất theo cặp đối tác + mã hàng + kho: tài khoản hạch toán, thuế suất, đơn vị tính, chiết khấu mặc định.
- Không tự thay đổi giá trị người dùng đã gõ. Khi gợi ý khác với giá trị hiện tại, hiển thị ghost text xám và chỉ áp dụng khi bạn bấm Tab hoặc Ctrl+R.
- Nguồn gợi ý có nhãn rõ ràng: Theo lịch sử 30 ngày gần nhất hoặc Theo hợp đồng số HĐ-023. Bạn có thể bấm Alt để xem 3 gợi ý gần nhất và chọn nhanh bằng số 1, 2, 3.
- Khi đổi đối tác giữa chừng, tất cả gợi ý phụ thuộc đối tác bị vô hiệu hóa cho đến khi xác nhận áp dụng lại bằng Ctrl+R.
Đính kèm chứng từ tiện tay
- Kéo thả file vào khu vực Đính kèm hoặc Ctrl+I rồi chọn. Hỗ trợ dán từ clipboard ảnh chụp màn hình.
- Tự đặt tên file theo số chứng từ sau khi ghi sổ, hiển thị thumbnail PDF và ảnh, cho phép xem nhanh trong panel phải.
- Quét virus phía server trước khi nhận, báo lỗi tức thì nếu vượt giới hạn dung lượng hoặc định dạng bị chặn.
Quét barcode là tùy chọn
- Khi con trỏ ở ô Mã hàng, máy quét dạng bàn phím sẽ đổ chuỗi barcode, hệ thống tra cứu tức thời và điền mã hàng, tên, đơn vị, giá gợi ý. Có âm báo thành công hoặc thất bại.
- Cấu hình theo người dùng: bật tắt chế độ chờ barcode, độ trễ kết thúc chuỗi 100 đến 300 ms để hợp với từng loại máy quét.
- Nếu trùng barcode nhiều mặt hàng, mở popover chọn 1 trong N, nhớ lựa chọn cho lần sau nếu bạn tick Ghi nhớ trong ca làm việc.
Tiêu chí khả dụng p95 theo thao tác
- Mở form chứng từ từ danh sách: p95 ≤ 400 ms trên mạng nội bộ.
- Thêm một dòng hàng trống: p95 ≤ 120 ms tính từ lúc bấm Ctrl+L đến khi con trỏ sẵn sàng ở ô Mã hàng.
- Tìm nhanh lần đầu và hiển thị 10 kết quả: p95 ≤ 300 ms với 100.000 chứng từ.
- Điền gợi ý từ lịch sử sau khi rời ô Mã hàng: p95 ≤ 80 ms.
- Lưu nháp: p95 ≤ 300 ms. Lưu và đánh số: p95 ≤ 600 ms.
- Đính kèm và hiển thị thumbnail ảnh 300 dpi: p95 ≤ 700 ms cho file 500 KB.
- Quét barcode đến khi ô Mã hàng được điền: p95 ≤ 200 ms.
- Danh sách phím tắt mở và sẵn sàng nhận phím tiếp: p95 ≤ 80 ms.
- Tất cả thao tác trên p99 không vượt quá 2 lần p95. Nếu vượt quá 30 phút liên tục, tự động ẩn gợi ý thấp ưu tiên để bảo toàn độ trễ cốt lõi.
Mẫu cấu hình phím tắt hoàn chỉnh
- Lưu nháp: Ctrl+S
- Lưu và đánh số: Ctrl+Shift+S
- Ghi sổ: Alt+G
- Mở tìm nhanh: Ctrl+O
- Thêm dòng: Ctrl+L
- Nhân bản dòng: Ctrl+D
- Đính kèm file: Ctrl+I
- Mở Cảnh báo: Ctrl+J
- Biến thể: HÀNH-ĐỘNG: TỔ-HỢP-PHÍM
Lưu ý triển khai cho đội phát triển
- Chặn phím tắt xung đột của trình duyệt nơi có thể, ví dụ bắt Ctrl+S trong vùng ứng dụng, nhưng tôn trọng Ctrl+L của trình duyệt khi không focus trong app.
- Bàn phím tiếng Việt có dấu: xử lý gõ chậm ở IME, chỉ kích hoạt tìm nhanh sau khi người dùng dừng gõ 250 đến 350 ms.
- Nhật ký khả dụng thu thập ẩn danh theo sự kiện giao diện với timestamp chính xác mili giây, gửi theo lô để không ảnh hưởng độ trễ cảm nhận.
Kiểm soát chất lượng dữ liệu trước ghi sổ
Mình chốt một lớp rule cứng chạy cả ở client khi bạn nhập và ở server khi bấm Ghi sổ để bảo đảm dữ liệu kế toán sạch ngay từ đầu. Client giúp bạn sửa nhanh, server là phán quyết cuối cùng nằm trong giao dịch ACID nên không có chuyện lọt lỗi hay ghi bán phần.
Bộ rule cứng tối thiểu
- Bắt buộc trường
- Loại chứng từ: Mua hoặc Bán
- Đối tác: nhà cung cấp hoặc khách hàng
- Số hóa đơn của đối tác
- Ngày hóa đơn
- Tiền tệ – mặc định VND cho MVP
- Ít nhất 1 dòng hàng. Mỗi dòng bắt buộc: mã hàng hoặc mô tả, kho, số lượng > 0, đơn giá ≥ 0, thuế suất hợp lệ
- Tổng trước thuế, thuế, tổng sau thuế ở phần tiêu đề
- Tài khoản hạch toán mặc định theo danh mục – có thể để hệ thống gợi ý nhưng phải có giá trị khi ghi
- Khớp tổng dòng – tổng chứng từ
- Tổng tiền trước thuế ở tiêu đề phải đúng bằng tổng cộng các dòng sau khi làm tròn theo số lẻ của tiền tệ
- Thuế từng dòng hoặc toàn chứng từ phải khớp thuế suất đã chọn
- Tổng sau thuế = trước thuế + thuế. Sai khác dù 1 đơn vị tiền nhỏ cũng bị chặn ở MVP
- Ngày nằm trong kỳ mở
- Ngày hóa đơn và ngày ghi sổ phải thuộc kỳ kế toán đang mở và không vượt sau ngày hệ thống hiện tại
- Chống trùng hóa đơn theo nhà cung cấp/khách hàng
- Không cho phép trùng cặp đối tác + số hóa đơn trong cùng năm tài chính, bất kể trạng thái chứng từ
- Nếu trùng gần giống theo khoảng trắng – ký tự đặc biệt – chữ hoa thường, hiển thị Cảnh báo nhưng rule cứng chỉ chặn trường hợp trùng tuyệt đối
- Không cho âm kho
- Mỗi dòng xuất kiểm tra tồn thực tế tại kho và thời điểm chứng từ. Nếu kết quả sau ghi sẽ âm, chặn và liệt kê các mã hàng – kho vi phạm để bạn xử lý trước
Cách hiển thị trong form
- Khi còn lỗi cứng: thanh lỗi đỏ ở đầu form nêu tổng số lỗi, nút Ghi sổ bị vô hiệu hóa, mỗi lỗi gắn icon ở đúng ô và tooltip hướng dẫn sửa
- Cảnh báo không chặn: xuất hiện trong panel Cảnh báo, không khóa nút Ghi sổ. Bạn có thể bấm Tôi đã kiểm tra để tiếp tục
Mẫu thông điệp lỗi hiển thị cho người dùng
- Lỗi: Thiếu trường bắt buộc – Bạn chưa chọn Nhà cung cấp
- Lỗi: Không khớp tổng – Tổng trước thuế ở tiêu đề 152.400.000 VND nhưng cộng dòng là 152.300.000 VND. Kiểm tra làm tròn hoặc đơn giá
- Lỗi: Ngày ngoài kỳ mở – Ngày hóa đơn 2026-07-31 không nằm trong kỳ 2026-07 đang mở
- Lỗi: Trùng hóa đơn – Số hóa đơn NCC INV-4573 đã tồn tại cho Minh Ân Co., Ltd ngày 2026-07-18. Mở chứng từ AP-2026-00125
- Lỗi: Âm kho – Hàng SKU-AX12 tại Kho Chính sẽ còn -5 sau khi xuất 10. Tồn hiện 5
API template trả về kết quả kiểm tra trước ghi sổ để client hiển thị và quyết định chặn
- Dùng JSON, mức chặn dựa vào level BLOCK. Mức WARN chỉ hiển thị.
Ví dụ hoàn chỉnh
{
"voucher_uid": "ACME-AP-01JZ3QZ4E9S7W1Z5C1N0X",
"result": "BLOCK",
"summary": {
"errors": 3,
"warnings": 1
},
"items": [
{
"code": "REQUIRED_FIELD_MISSING",
"level": "BLOCK",
"field": "partner_id",
"message": "Thiếu trường bắt buộc: Nhà cung cấp",
"suggestion": "Chọn nhà cung cấp trước khi tiếp tục"
},
{
"code": "HEADER_LINE_TOTAL_MISMATCH",
"level": "BLOCK",
"field": "header.total_before_vat",
"message": "Tổng trước thuế ở tiêu đề 152400000 không khớp tổng dòng 152300000",
"suggestion": "Kiểm tra đơn giá hoặc số lẻ làm tròn của từng dòng"
},
{
"code": "DUPLICATE_VENDOR_INVOICE",
"level": "BLOCK",
"field": "header.vendor_invoice_no",
"message": "Trùng hóa đơn INV-4573 với Minh Ân Co., Ltd trong năm 2026",
"link": "/ap/vouchers/AP-2026-00125",
"suggestion": "Mở chứng từ đã có hoặc đổi số hóa đơn"
},
{
"code": "INVENTORY_WOULD_GO_NEGATIVE",
"level": "BLOCK",
"field": "lines[2].sku",
"message": "SKU-AX12 tại Kho Chính sẽ âm 5 sau khi xuất",
"context": {
"onhand": 5,
"request_out": 10,
"would_be": -5
},
"suggestion": "Giảm số lượng hoặc nhập kho bổ sung trước khi xuất"
}
]
}
Biến thể cho tích hợp
- voucher_uid – chuỗi duy nhất do client sinh
- result – BLOCK hoặc OK hoặc WARN_ONLY
- items[].code – REQUIREDFIELDMISSING, HEADERLINETOTALMISMATCH, DUPLICATEVENDORINVOICE, INVENTORYWOULDGONEGATIVE, PERIOD_CLOSED
- items[].level – BLOCK hoặc WARN
- items[].field – đường dẫn trường vi phạm, ví dụ header.invoice_date, lines[0].quantity
- items[].message – câu tiếng Việt dễ hiểu
- items[].suggestion – gợi ý sửa ngắn gọn
- items[].link – đường dẫn mở nhanh chứng từ liên quan nếu có
Ràng buộc cứng ở server để chống trùng nghiệp vụ
- Dựng unique index theo cặp đối tác + số hóa đơn trong một năm tài chính. Ví dụ với bảng apinvoices có cột fiscalyear:
CREATE UNIQUE INDEX ux_ap_vendor_no_year
ON ap_invoices (vendor_id, vendor_invoice_no_normalized, fiscal_year);
- vendorinvoiceno_normalized là phiên bản đã loại khoảng trắng thừa và chuẩn hóa chữ hoa thường. Chuẩn hóa dùng cùng hàm ở client để cảnh báo sớm, nhưng chặn vẫn do server thực thi.
Thứ tự thực thi khi bấm Ghi sổ
- Client gọi Validate – nhận JSON như trên, nếu có level BLOCK thì hiển thị và dừng
- Nếu OK hoặc chỉ WARN, server mở transaction, kiểm tra lại đầy đủ các rule ở phía server để chống lách
- Nếu pass, ghi sổ và trả biên nhận. Nếu fail ở bước 2 do điều kiện thay đổi, trả về BLOCK với danh sách lỗi cập nhật và không có ghi bán phần
Lưu ý vận hành
- Kiểm tra âm kho và trùng hóa đơn luôn thực hiện ở server trong cùng transaction với ghi sổ để tránh điều kiện tranh chấp. Client chỉ là hỗ trợ trải nghiệm nhanh.
Tích hợp AI kiểm tra đầu vào, tiêu chí cảnh báo, giải thích và fallback
Mình thiết kế lớp AI như một chốt kiểm chất lượng song song với rule cứng. Dữ liệu đi qua AI là dữ liệu có cấu trúc ngay trên form nhập, không OCR ở MVP, tối ưu cho độ trễ thấp và khả năng truy vết. AI mặc định chạy on-prem, chỉ cảnh báo – không chặn. Khi lỗi hoặc quá hạn, hệ thống tự động fallback về rule cứng và cho phép bạn tiếp tục theo quyền.
Phạm vi dữ liệu có cấu trúc đưa vào AI
- Tiêu đề chứng từ: loại chứng từ, đối tác, số hóa đơn đối tác, ngày hóa đơn, tiền tệ, tổng trước thuế, thuế, tổng sau thuế, điều khoản thanh toán, kho đích hoặc nguồn, số PO nếu có.
- Dòng hàng: sku, tên hàng, số lượng, đơn giá, thuế suất, kho, tài khoản hạch toán gợi ý.
- Bối cảnh: kỳ kế toán đang mở, nhóm nhà cung cấp – khách hàng, nhóm hàng, lịch sử 90 ngày gần nhất rút gọn theo nhóm.
- Không gửi file đính kèm và văn bản tự do vào AI ở MVP.
Kiến trúc kiểm tra theo lớp
- Lớp rule engine – ràng buộc cứng và cảnh báo quy tắc
- Thực thi tại server và phản hồi tức thời cho client. Kết quả có thể chặn theo phần trước.
- Một phần rule cũng sinh cảnh báo nhẹ như trùng gần giống số hóa đơn, thay vì chặn.
- Lớp phát hiện bất thường thống kê – dữ liệu có cấu trúc
- So sánh các đặc trưng đã chuẩn hóa theo lịch sử gần đây và cấu hình:
- Outlier đơn giá theo cặp nhà cung cấp – sku – đơn vị tính.
- Tổng hóa đơn lệch bất thường so với PO liên quan hoặc median 30 ngày.
- Điều khoản thanh toán khác thường so với lịch sử đối tác.
- Gợi ý GL account dựa trên map lịch sử – cảnh báo nếu chọn khác nhóm phổ biến.
- Trùng lặp theo băm gần giống của cặp nhà cung cấp – số hóa đơn – ngày.
- Mỗi kiểm tra sinh score phụ 0..1. Hợp nhất thành risk_score 0..1 bằng trọng số có cấu hình.
- Lớp giải thích bằng LLM nhỏ – tùy chọn
- Chỉ dùng để diễn giải cảnh báo ngắn gọn và gợi ý sửa định dạng. Không quyết định chặn hay cho điểm.
- Có thể tắt hẳn ở MVP để tiết kiệm tài nguyên. Khi bật, chạy on-prem, mô hình cỡ nhỏ 7B quantized là đủ cho phần giải thích tiếng Việt thông thường.
Ngưỡng cảnh báo và chính sách can thiệp
- Mapping mặc định: High nếu risk_score ≥ 0.85, Medium 0.70 – 0.85, Low 0.50 – 0.70. Dưới 0.50 thì ẩn.
- MVP: AI chỉ cảnh báo, không chặn. Chặn chỉ thuộc về rule cứng xác định được tính đúng sai.
- Tự hạ cấp khi hệ thống bận: nếu độ trễ AI vượt SLA kéo dài, ẩn cấp Low – chỉ hiển thị Medium và High.
Cơ chế nhãn hành vi và KPI False Positive
- Gán nhãn tự động từ hành vi:
- Nếu người nhập sửa theo đúng gợi ý và ghi, coi là True Positive.
- Nếu người nhập giữ nguyên và bút toán vẫn được duyệt ở vòng soát xét hằng ngày, coi là False Positive.
- Bổ sung nhãn nhẹ:
- Với High, bắt buộc chọn 1 trong 3 nút: Sửa theo gợi ý – Hợp lệ – Chuyển kế toán trưởng.
- Với Medium – Low, chỉ cần tick Tôi đã kiểm tra.
- KPI FP theo cấp độ tính theo cửa sổ trượt 2 tuần hoặc tối thiểu 500 chứng từ:
- High FP ≤ 5 phần trăm, Medium FP ≤ 12 phần trăm, Low FP ≤ 20 phần trăm.
- Lấy mẫu kiểm tra chéo 10 phần trăm các cảnh báo Medium – Low đã bị coi là giả để ước lượng sai lệch nhãn.
Audit chi tiết – bắt buộc lưu khi có cảnh báo AI
- Nhật ký append-only, lưu tối thiểu 24 tháng hoặc theo chính sách kế toán nội bộ.
- Trường khuyến nghị: voucheruid, userid, role, issuedat, aimodelversion, rulesversion, riskscore, severity, danh sách findings với top 3 feature và weight, suggestiontext, useraction, userreason, commitreceiptid nếu có.
Ví dụ audit log hoàn chỉnh
{
"voucher_uid": "ACME-AP-01JZ3QZ4E9S7W1Z5C1N0X",
"user_id": "u_kt_hoa",
"role": "DataEntry",
"issued_at": "2026-07-20T09:15:42.311Z",
"ai_model_version": "stat-v1.3.2",
"rules_version": "rules-2026-07-01",
"risk_score": 0.88,
"severity": "HIGH",
"findings": [
{"code": "UNIT_PRICE_OUTLIER", "score": 0.92, "feature": "sku:SKU-AX12 vendor:VINHQUANG mean:102000 curr:128000 z:3.1"},
{"code": "TERMS_DEVIATE", "score": 0.55, "feature": "vendor_terms hist:30d curr:60d"},
{"code": "GL_MISMATCH", "score": 0.49, "feature": "suggest:1561-chiphimua curr:6418-quangcao"}
],
"suggestion_text": "Đơn giá SKU-AX12 cao hơn mức thường thấy với NCC Vinh Quang khoảng 25 phần trăm. Kiểm tra giá niêm yết hoặc chương trình khuyến mại.",
"user_action": "PROCEED_WITH_REASON",
"user_reason": "Giá tăng do điều chỉnh tháng 7 theo phụ lục hợp đồng",
"commit_receipt_id": "CR-AP-20260720-000345"
}
Mẫu cho tích hợp có biến số
- voucher_uid – chuỗi duy nhất chứng từ
- severity – HIGH, MEDIUM, LOW
- findings[].code – UNITPRICEOUTLIER, TERMSDEVIATE, GLMISMATCH, DUP_APPROX
- suggestion_text – có thể rỗng nếu tắt LLM
Triển khai on-prem là mặc định
- AI-validator chạy như service nội bộ sau firewall, có versioning và canary 5 đến 10 phần trăm trước khi rollout.
- Health check định kỳ. Khi lỗi hoặc timeout, trả về trạng thái degraded – hệ thống fallback sang rule-only và gắn cờ cần rà soát sau.
Tùy chọn cloud kèm redaction nghiêm ngặt
- Chỉ dùng khi thiếu tài nguyên on-prem hoặc cần năng lực giải thích tự nhiên tốt hơn. Toàn bộ outbound đi qua redaction proxy on-prem.
- Bị cấm gửi ở dạng nguyên gốc: tên đối tác, mã số thuế, địa chỉ, email, điện thoại, số tài khoản ngân hàng, số hóa đơn, file đính kèm.
- Được phép gửi sau biến đổi: số tiền, thuế suất, ngày tháng, nhóm hàng, nhóm đối tác, vector lịch sử đã bút danh. Thay định danh bằng hash có salt theo phiên.
- Log chỉ lưu payload đã redacted.
Ví dụ redaction trước khi gọi cloud
{
"before": {
"vendor_name": "Công ty TNHH Minh Ân",
"vendor_tax_id": "0312345678",
"vendor_invoice_no": "INV-4573",
"invoice_date": "2026-07-18",
"currency": "VND",
"lines": [
{"sku": "SKU-AX12", "qty": 10, "unit_price": 128000, "vat_rate": 0.1}
],
"gl_suggest": "1561"
},
"after": {
"vendor_id_hash": "vndr_a9f0e61-20260718",
"vendor_invoice_no_hash": "inv_3b5d5c3-20260718",
"invoice_date": "2026-07-18",
"currency": "VND",
"lines": [
{"sku_group_id": "grp_ax", "qty": 10, "unit_price": 128000, "vat_rate": 0.1}
],
"gl_suggest": "1561"
}
}
Cấu hình ngưỡng và hành vi – mẫu điền sẵn
ai_validator:
enabled: true
on_prem: true
use_llm_explainer: false
thresholds:
high: 0.85
medium: 0.70
low: 0.50
degrade_policy:
hide_low_when_p95_latency_ms_over: 1200
window_minutes: 180
kpi_fp_targets:
high: 0.05
medium: 0.12
low: 0.20
retention_months_audit: 24
Biến thể có thể thay đổi: thresholds.high, thresholds.medium, thresholds.low, retentionmonthsaudit.
Ví dụ phản hồi AI gắn vào panel Cảnh báo trên form
{
"voucher_uid": "ACME-AP-01JZ3QZ4E9S7W1Z5C1N0X",
"risk_score": 0.78,
"severity": "MEDIUM",
"warnings": [
{
"code": "UNIT_PRICE_OUTLIER",
"message": "Đơn giá SKU-AX12 cao hơn lịch sử 15 phần trăm so với NCC này trong 30 ngày gần nhất",
"suggestion": "Đối chiếu báo giá tháng 7 hoặc nhập mức chiết khấu",
"explain": "hist_mean=111000 curr=128000 n=6 z=2.4"
},
{
"code": "GL_MISMATCH",
"message": "Tài khoản 6418 không thường dùng cho SKU-AX12",
"suggestion": "Cân nhắc 1561 theo lịch sử",
"explain": "top_gl=1561 freq=82 phần trăm"
}
],
"llm_explainer_used": false,
"fallback": false
}
Điểm mấu chốt để vận hành mượt là giữ mô hình gọn, giải thích ngắn gọn, đo FP định kỳ và coi AI là tay vịn cho người nhập chứ không phải cổng chặn. Khi cần mở rộng năng lực, bạn chỉ việc bật LLM giải thích hoặc redaction proxy cho cloud mà không thay đổi luồng nghiệp vụ.
Luồng AI validate token và SLA độ trễ
Mình thiết kế kiểm tra AI theo 2 pha để vừa hỗ trợ người nhập theo thời gian thực vừa đảm bảo tính nhất quán khi ghi sổ. SLA được đo ở phía server, auto degrade khi nghẽn, và luôn fail open để không chắn dòng nghiệp vụ.
Hai pha kiểm tra và SLA
- Kiểm tra nền khi nhập
- Kích hoạt khi người dùng dừng gõ 800 ms. Gửi bản rút gọn của chứng từ hiện tại cho AI-validator.
- Mục tiêu p95 ≤ 1.0 s. p99 ≤ 1.6 s. Timeout cứng 1.8 s.
- Hết thời gian hoặc lỗi dịch vụ thì hiển thị trạng thái AI tạm không phản hồi. Không chặn, rule cứng vẫn chạy tức thời trong app.
- Kiểm tra gating khi bấm Ghi sổ
- Gọi đồng bộ một lượt kiểm cuối cùng trước commit để sinh validation_token.
- Mục tiêu p95 ≤ 1.2 s. p99 ≤ 2.0 s. Timeout cứng 2.5 s.
- Quá timeout hoặc AI down thì fail open. Chỉ còn rule cứng chạy local. Ghi cờ fallback vào audit để kế toán trưởng có thể soát lại sau.
Sẵn sàng dịch vụ
- Mục tiêu sẵn sàng AI p99.9 theo ngày làm việc. Nếu AI down liên tục quá 5 phút, hệ thống tự chuyển sang chế độ rule-only cho đến khi healthcheck ổn định 1 phút liên tục.
Validation token gắn hash payload và versions
- Khi gating pass, server phát hành validation_token ràng buộc chặt với payload:
- hash_payload: SHA-256 của JSON đã canonicalize.
- aimodelversion, rules_version: phiên bản đã dùng để đánh giá.
- issuedat, expiresat: thời điểm cấp và hết hạn. TTL mặc định 15 phút, có thể cấu hình theo nghiệp vụ.
- chữ ký số của server: ký toàn bộ trường trên, kèm thuật toán và khóa ký.
- Ở bước Commit, client gửi lại đúng payload kèm idempotencykey và validationtoken. Server kiểm:
- Hash payload khớp.
- Token còn hạn và chữ ký hợp lệ.
- Nếu idempotency_key đã committed thì trả receipt cũ.
- Nếu hash không khớp thì 409 Validation Mismatch và từ chối.
Snapshot cho late commit khi token hết hạn
- Để tránh bắt bạn re-validate vô ích khi mạng chập chờn, server lưu snapshot kết quả validate gần nhất gắn với idempotencykey: hashpayload, aimodelversion, rulesversion, aifindings, approved_at.
- Tại Commit, nếu token hết hạn nhưng:
- hash_payload trùng với snapshot, và
- aimodelversion, rules_version chưa đổi
thì cho phép commit dựa trên snapshot. Ghi reason = late-commit-using-snapshot vào audit.
- Nếu versions đã đổi hoặc không có snapshot, trả 428 Precondition Required yêu cầu re-validate.
Tiêu chí tự hạ cấp khi trễ
- Cửa sổ đo: tính p95, p99 trên sliding window 5 phút, cập nhật mỗi 30 giây.
- Degrade level 1: nếu p95 của pha gating > 1.2 s kéo dài 180 phút liên tục, tự ẩn cảnh báo Low và tắt phần giải thích nâng cao. Khôi phục sau khi p95 ≤ 1.2 s trong 60 phút.
- Degrade level 2: nếu error_rate của AI > 20 phần trăm hoặc timeout liên tiếp 5 phút, chuyển rule-only. Khôi phục khi healthcheck pass liên tục 1 phút.
Ví dụ API hoàn chỉnh
- Gating validate để lấy token
- Ý nghĩa: gọi kiểm tra cuối trước commit, nhận token ràng buộc với payload.
POST /ai/validate HTTP/1.1
Content-Type: application/json
{
"voucher_uid": "ACME-AP-01JZ3QZ4E9S7W1Z5C1N0X",
"payload": {
"type": "AP_INVOICE",
"partner_id": "VEN_00123",
"invoice_no": "INV-4573",
"invoice_date": "2026-07-18",
"currency": "VND",
"lines": [
{"sku": "SKU-AX12", "qty": 10, "unit_price": 128000, "vat_rate": 0.1, "warehouse": "HCM-A"}
],
"gl_suggest": "1561"
}
}
HTTP/1.1 200 OK
Content-Type: application/json
{
"risk_score": 0.31,
"severity": "LOW",
"validation_token": {
"hash_payload": "a3e1f2b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f70",
"ai_model_version": "stat-v1.3.2",
"rules_version": "rules-2026-07-01",
"issued_at": "2026-07-20T09:16:02.120Z",
"expires_at": "2026-07-20T09:31:02.120Z",
"alg": "Ed25519",
"kid": "ai-signer-01",
"signature": "mN3yJ5R6r1Y0lXqGxW8o9QvCMd7e0b5c2aZkL3hP7sQ9T2uV1wXyZ0aBcDeFgHiJkLmNoPQrStUvWxYz12"
}
}
- Commit với idempotency và validation_token
- Ý nghĩa: ghi sổ an toàn đúng một lần, xác nhận token khớp payload và còn hiệu lực hoặc dùng snapshot khi đủ điều kiện.
POST /vouchers/commit HTTP/1.1
Idempotency-Key: ACME-AP-01JZ3QZ4E9S7W1Z5C1N0X
Content-Type: application/json
{
"payload": {
"type": "AP_INVOICE",
"partner_id": "VEN_00123",
"invoice_no": "INV-4573",
"invoice_date": "2026-07-18",
"currency": "VND",
"lines": [
{"sku": "SKU-AX12", "qty": 10, "unit_price": 128000, "vat_rate": 0.1, "warehouse": "HCM-A"}
],
"gl_suggest": "1561"
},
"validation_token": {
"hash_payload": "a3e1f2b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f70",
"ai_model_version": "stat-v1.3.2",
"rules_version": "rules-2026-07-01",
"issued_at": "2026-07-20T09:16:02.120Z",
"expires_at": "2026-07-20T09:31:02.120Z",
"alg": "Ed25519",
"kid": "ai-signer-01",
"signature": "mN3yJ5R6r1Y0lXqGxW8o9QvCMd7e0b5c2aZkL3hP7sQ9T2uV1wXyZ0aBcDeFgHiJkLmNoPQrStUvWxYz12"
}
}
HTTP/1.1 200 OK
Content-Type: application/json
{
"commit_receipt": {
"voucher_uid": "ACME-AP-01JZ3QZ4E9S7W1Z5C1N0X",
"voucher_number": "AP-20260718-000345",
"committed_at": "2026-07-20T09:16:03.014Z",
"hash_payload": "a3e1f2b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f70",
"posting_version": 42,
"ai_used": true,
"degraded": false
}
}
Biến thể có biến số
{
"risk_score": RISK_SCORE_0_TO_1,
"severity": "LOW|MEDIUM|HIGH",
"validation_token": {
"hash_payload": "HASH_HEX_64",
"ai_model_version": "AI_MODEL_VER",
"rules_version": "RULES_VER",
"issued_at": "ISO8601_Z",
"expires_at": "ISO8601_Z",
"alg": "EdDSA|RS256",
"kid": "KEY_ID",
"signature": "BASE64URL_SIG"
}
}
Theo đúng thực tế vận hành, bạn sẽ thấy hai lợi ích rõ ràng: người nhập có phản hồi nhanh ngay trên form, còn bước ghi sổ thì được bảo vệ bởi token ràng buộc payload và cơ chế snapshot để không phải quay lại kiểm tra khi chỉ gặp trễ mạng. Hệ thống luôn ưu tiên tính liên tục, nhưng không đánh đổi chất lượng dữ liệu vì mọi ngoại lệ đều được ghi nhận vào audit để soát xét.
Nhập khẩu dữ liệu và tích hợp cơ bản CSV Excel, e invoice và ngân hàng
Mục tiêu ở đây là bạn có thể nạp nhanh danh mục và số dư đầu kỳ từ CSV hoặc Excel, kiểm tra dữ liệu chặt trước khi ghi, có log lỗi rõ ràng để sửa, đồng thời xác định khung tích hợp hóa đơn điện tử và ngân hàng ở giai đoạn sau theo nguyên tắc webhook hoặc polling phù hợp môi trường on-prem.
Bộ template CSV Excel cho MVP
- Danh mục đối tác: partnercode, partnername, type, tax_id, phone, email, address.
- Danh mục vật tư hàng hóa: sku, name, uom, vatrate, isactive.
- Danh mục kho: warehousecode, warehousename, location.
- Số dư đầu kỳ công nợ: partnercode, type, openbalance, balance_date, note.
- Số dư đầu kỳ tồn kho: warehousecode, sku, qtyopen, unitcostopen, balance_date.
- Số dư đầu kỳ sổ cái: glaccount, debitopen, creditopen, balancedate.
Ví dụ CSV hoàn chỉnh nhập danh mục vật tư
sku,name,uom,vat_rate,is_active
SKU-AX12,Bu lông M6 x 20 cái,PCS,0.10,true
SKU-BR01,Vòng bi 6203ZZ,PCS,0.08,true
SKU-SVC1,Phí vận chuyển,LSP,0.00,true
Biến thể có biến số
sku,name,uom,vat_rate,is_active
{{SKU_CODE}},{{ITEM_NAME}},{{UOM_CODE}},{{VAT_RATE_DECIMAL}},{{TRUE_FALSE}}
Ánh xạ cột và cấu hình import
- Chọn template chuẩn hoặc tự map cột theo tên tiêu đề file.
- Hỗ trợ chuẩn hóa kiểu dữ liệu, định dạng ngày, tách dấu phẩy ngăn cách nghìn, chuyển true false.
- Cho phép upsert theo khóa tự nhiên để tránh tạo trùng.
Ví dụ cấu hình mapping hoàn chỉnh
{
"template": "items",
"key_fields": ["sku"],
"columns": {
"sku": {"from": "sku", "type": "string", "required": true, "trim": true},
"name": {"from": "name", "type": "string", "required": true, "max_len": 255},
"uom": {"from": "uom", "type": "string", "required": true},
"vat_rate": {"from": "vat_rate", "type": "number", "min": 0, "max": 1, "default": 0},
"is_active": {"from": "is_active", "type": "boolean", "default": true}
},
"date_format": "yyyy-MM-dd",
"thousand_separator": ",",
"decimal_separator": ".",
"mode": "upsert"
}
Biến thể có biến số
{
"template": "{{TEMPLATE_CODE}}",
"key_fields": ["{{KEY1}}","{{KEY2_OPTIONAL}}"],
"columns": {
"{{FIELD}}": {"from": "{{HEADER}}", "type": "{{TYPE}}", "required": {{REQ_BOOL}}}
},
"date_format": "{{DATE_PATTERN}}",
"thousand_separator": "{{THS}}",
"decimal_separator": "{{DEC}}",
"mode": "{{insert|upsert|validate_only}}"
}
Quy trình nhập một file
- Tải file CSV hoặc Excel. Với Excel, lấy sheet đầu tiên, hàng 1 là tiêu đề. Mình khuyến nghị lưu ngày theo ISO 8601 để khỏi lệch vùng.
- Chọn template hoặc để hệ thống gợi ý dựa trên tiêu đề cột trùng khớp.
- Ánh xạ cột. Lưu mapping để tái sử dụng lần sau.
- Dry-run validate:
- Kiểu dữ liệu, trường bắt buộc, khoảng hợp lệ vat_rate 0 đến 1.
- Khóa tự nhiên duy nhất, ví dụ sku duy nhất hoặc cặp warehouse_code + sku ở tồn kho.
- Ràng buộc tham chiếu: warehousecode phải tồn tại khi nhập tồn kho, partnercode phải có khi nhập công nợ.
- Với số dư: tổng debitopen bằng tổng creditopen khi chốt sổ cái.
- Xem trước kết quả: số dòng tạo mới, cập nhật, bỏ qua. Tải log lỗi về nếu có.
- Commit theo lô. Mỗi lô đi trong một transaction theo nhóm đối tượng để không tạo bán phần. Dòng lỗi bị bỏ qua không chặn cả lô, nhưng luôn có log chi tiết.
- Lưu biên nhận import và khóa mapping với version.
Kiểm tra dữ liệu chi tiết
- Trim, chuẩn hóa Unicode NFC, bỏ ký tự điều khiển.
- Chuẩn hóa mã thành chữ hoa không khoảng trắng đầu cuối.
- Số âm chỉ được phép cho open_balance khi là công nợ phải thu âm hay phải trả âm theo nghiệp vụ đã xác nhận với kế toán trưởng.
- Với tồn kho đầu kỳ: unitcostopen không âm, qtyopen không âm. Nếu qtyopen > 0 thì unitcostopen phải > 0.
- Với số dư công nợ: type chỉ nhận AR hoặc AP. balance_date nằm trong kỳ mở sẽ dùng cho đối soát đầu kỳ.
Mẫu log lỗi máy đọc
- 2026-07-20T09:22:11Z|items.csv|Sheet1|3|vat_rate|ERROR|E_NUM_RANGE|Giá trị ngoài khoảng 0..1|1.5|trace-01K8X2
- 2026-07-20T09:22:11Z|opening_inventory.xlsx|Sheet1|2|warehouse_code|ERROR|E_REF_NOT_FOUND|Kho không tồn tại|HCM-Z|trace-01K8X3
- 2026-07-20T09:22:11Z|ap_ar_open.csv|Sheet1|5|partner_code|ERROR|E_DUP_KEY|Trùng khóa trong file|VEN_00123|trace-01K8X4
- 2026-07-20T09:22:11Z|gl_open.csv|Sheet1|TOTAL|-|ERROR|E_GL_IMBALANCE|Tổng dư Nợ khác tổng dư Có|Debit=1,000,000|trace-01K8X5
Ví dụ CSV số dư đầu kỳ công nợ hoàn chỉnh
partner_code,type,open_balance,balance_date,note
CUS_0001,AR,12500000,2026-07-01,Nợ đầu kỳ theo biên bản 30-06
VEN_0010,AP,8400000,2026-07-01,Công nợ phải trả đầu kỳ
Ví dụ CSV số dư đầu kỳ tồn kho hoàn chỉnh
warehouse_code,sku,qty_open,unit_cost_open,balance_date
HCM-A,SKU-AX12,120,118000,2026-07-01
HCM-A,SKU-BR01,45,52000,2026-07-01
Nguyên tắc ghi nhận khi import số dư
- Mỗi file là một batch có id và idempotency key. Gửi lại cùng key sẽ trả kết quả cũ, không nhân đôi.
- Số dư đầu kỳ ghi vào sổ phụ tương ứng và sinh bút toán tổng hợp cân bằng vào sổ cái nếu chọn chế độ tự bút toán đối ứng. Nếu chọn thủ công, hệ thống chỉ cảnh báo mất cân đối và yêu cầu bạn nhập bút toán tổng hợp riêng.
- Sau khi chốt import đầu kỳ và mở kỳ vận hành, khóa chức năng đổi số dư trừ khi có quyền Kế toán trưởng và quy trình điều chỉnh rõ ràng.
Excel lưu ý kỹ thuật
- Chấp nhận .xlsx. Mặc định đọc kiểu General theo ô và chuyển sang kiểu trường đã map. Công thức sẽ lấy giá trị đã tính.
- Ngày tháng cần xác định rõ date system của file. Nếu bạn dùng định dạng khác ISO, cấu hình date_format trùng khớp trước khi import.
- Mã hóa UTF-8 khi xuất ra CSV để tránh lỗi dấu tiếng Việt.
Phạm vi tích hợp hóa đơn điện tử và ngân hàng giai đoạn sau
- MVP hỗ trợ nhập file đối soát từ nhà cung cấp hoặc ngân hàng ở dạng CSV chuẩn hóa như bảng sao kê, hóa đơn đã phát hành. Đây là bước đệm trước khi kết nối API.
- Khi sẵn sàng kết nối thời gian thực, có 2 hướng tương thích on-prem:
- Webhook qua kênh tin cậy: dựng endpoint nội bộ và chỉ cho phép truy cập qua VPN site-to-site hoặc kênh trung gian do bạn kiểm soát. Mỗi sự kiện kèm chữ ký HMAC, replay id và idempotency key.
- Polling chủ động: service nội bộ gọi API đối tác theo lịch, phân trang theo sinceid hoặc sincetime, lưu watermark để không bỏ sót.
Ví dụ payload webhook trung gian cho sao kê ngân hàng
{
"event": "bank.statement.created",
"id": "evt_01JZ3QZ4E9S7W1Z5",
"sent_at": "2026-07-20T02:12:33Z",
"signature": "hmac-sha256=2c3b9e8f1d0a7c...",
"data": {
"account_no": "****1234",
"currency": "VND",
"statement_date": "2026-07-19",
"transactions": [
{"txn_id": "TXN001", "posted_at": "2026-07-19T03:15:02Z", "amount": 2500000, "type": "CR", "desc": "TT KH CUS_0001 SO INV-4573"},
{"txn_id": "TXN002", "posted_at": "2026-07-19T05:10:12Z", "amount": -820000, "type": "DR", "desc": "Phí chuyển khoản"}
]
}
}
Ví dụ polling GET theo since_time
GET /api/v1/bank/transactions?account=****1234&since_time=2026-07-19T00:00:00Z&limit=500 HTTP/1.1
Authorization: Bearer REDACTED
Nguyên tắc webhook và polling áp dụng chung
- Xác thực và toàn vẹn: với webhook, bắt buộc kiểm HMAC hoặc chữ ký bất đối xứng và kiểm tra timestamp lệch tối đa N phút. Với polling, dùng OAuth2 hoặc API key cất trong vault, kết nối TLS, có backoff.
- Idempotency: lưu seen_ids và watermark để bỏ qua trùng lặp, đảm bảo mỗi sự kiện chỉ ghi nhận một lần.
- Không phơi bày dịch vụ ra Internet công cộng ở MVP. Nếu cần webhook, đi qua VPN hoặc relay an toàn do bạn quản trị. Nếu chưa sẵn sàng, chọn polling.
Tùy chọn đối soát bán tự động ở MVP
- Nạp sao kê ngân hàng CSV, map cột date, amount, desc, reference.
- Quy tắc ghép: đối sánh theo số tiền, khoảng ngày, và mẫu chuỗi tham chiếu như INV-xxxx hoặc tên đối tác. Hệ thống gợi ý ghép và bạn xác nhận theo lô.
Vận hành và khôi phục
- Mọi import có biên nhận gồm batchid, filename, rowok, rowfailed, startedat, finishedat, checksum. Có thể chạy lại với cùng batch_id để tái tạo kết quả.
- Khi rollback một batch số dư đầu kỳ, hệ thống xóa các bản ghi đã tạo trong batch và phục hồi số liệu trước đó nếu có snapshot. Với batch danh mục, rollback có điều kiện nếu chưa phát sinh giao dịch liên quan.
Tùy biến cấu hình hệ thống và mở rộng có điều kiện
Cấu hình phải tối giản để chạy ổn ngay, nhưng có cờ để mở rộng theo giai đoạn mà không đập vỡ dữ liệu. Mình nhóm tham số thành 7 cụm chính và nêu rõ mặc định cho MVP, kèm đánh dấu mở khóa theo giai đoạn.
- Thuế
- Bật thuế gián thu: enabled [MVP: true].
- Cách nhập số: taxinclusivedefault [MVP: false] – tổng tiền chưa bao gồm thuế.
- Danh mục thuế suất: vat_rates [MVP: chỉ có 0.0] – thêm các mức khác ở Phase 2 khi chốt chuẩn kế toán.
- Quy tắc làm tròn: roundingmode [MVP: halfup], rounding_scale [MVP: 0 chữ số cho tiền tệ không có lẻ hoặc 2 cho có lẻ].
- Kiểu tính dòng hay hóa đơn: lineleveltax [MVP: true].
- Phase 2: thuế suất nhiều mức theo nhóm hàng, miễn giảm đặc thù, báo cáo thuế tùy chỉnh.
- Tiền tệ
- basecurrency [MVP: VND ví dụ], multicurrency_enabled [MVP: false].
- Phase 2: bật đa tiền tệ, exchangeratesource, ratetype [closing, average], FXgainlossaccount, quy tắc làm tròn riêng cho ngoại tệ.
- Đa kho
- multiwarehouseenabled [MVP: false], default_warehouse [MVP: W1].
- Phase 2: nhiều kho, quyền theo kho, điều chuyển nội bộ, vị trí kệ đơn giản.
- Phương pháp tính giá ưu tiên
- costmethod [MVP: movingaverage].
- costrecalcwindow_days [MVP: 14] – cửa sổ hồi tính.
- maxrecalclines [MVP: 100].
- Phase 2: dayendaverage như lựa chọn thay thế. Phase 3: FIFO theo lớp.
- Lô – seri
- lottrackingenabled [MVP: false], serialtrackingenabled [MVP: false].
- Phase 2: lô và hạn dùng theo SKU chọn lọc, bắt buộc nhập lô khi xuất. Phase 3: seri đơn chiếc, truy vết hai chiều.
- Âm kho
- allownegativeinventory [MVP: false] – chặn xuất khi âm.
- exception_role [MVP: none].
- Phase 2: cho phép âm có kiểm soát theo kho hoặc SKU, ngưỡng âm và phê duyệt.
- Tham số AI
- aienabled [MVP: true], aimode [MVP: onprem], airule_engine [MVP: on].
- riskthresholds [MVP: High ≥ 0.85, Medium 0.70, Low 0.50], aiblock_commit [MVP: false].
- latencybudgetms [MVP: background 1000, gating 1200], timeout_ms [MVP: background 1800, gating 2500].
- autodegradelow_alerts [MVP: true].
- auditdetaillevel [MVP: full].
- Phase 2: cloudassistwithredaction, clientcert_required, chặn theo chính sách cho cảnh báo High.
- Chính sách kỳ kế toán
- periodtype [MVP: monthly], openperiods [MVP: 1 kỳ hiện tại], allowbackdatedays [MVP: 14].
- monthcloserequiredrole [MVP: ChiefAccountant], reopenclosed_periods [MVP: false].
- postingdatepolicy [MVP: posting_date must be within open period].
- Phase 2: nhiều kỳ mở song song theo phân hệ, lịch khóa tự động cuối tháng, checklist khóa sổ bắt buộc.
- Chính sách số chứng từ – liên quan kỳ
- numberingscope [MVP: theo năm], reserveonvalidate [MVP: false], reusereservedonretry [MVP: true].
- skipnumbersallowed [MVP: true] – nếu địa phương cấm nhảy số, chuyển sang Phase 2: reserveonvalidate true và tracking trạng thái số.
- Quyền đổi cấu hình và nhật ký
- configchangerequiresrole [MVP: Admin], twoperson_rule [MVP: false].
- auditconfigchange [MVP: append_only, đầy đủ diff].
Ví dụ tệp cấu hình hoàn chỉnh cho MVP
{
"tax": {
"enabled": true,
"tax_inclusive_default": false,
"vat_rates": [0.0],
"rounding_mode": "half_up",
"rounding_scale": 0,
"line_level_tax": true,
"stage": "MVP"
},
"currency": {
"base_currency": "VND",
"multi_currency_enabled": false,
"stage": "MVP"
},
"inventory": {
"multi_warehouse_enabled": false,
"default_warehouse": "W1",
"cost_method": "moving_average",
"cost_recalc_window_days": 14,
"max_recalc_lines": 100,
"lot_tracking_enabled": false,
"serial_tracking_enabled": false,
"allow_negative_inventory": false,
"stage": "MVP"
},
"ai": {
"ai_enabled": true,
"ai_mode": "on_prem",
"ai_rule_engine": "on",
"risk_thresholds": {"high": 0.85, "medium": 0.7, "low": 0.5},
"ai_block_commit": false,
"latency_budget_ms": {"background": 1000, "gating": 1200},
"timeout_ms": {"background": 1800, "gating": 2500},
"auto_degrade_low_alerts": true,
"audit_detail_level": "full",
"stage": "MVP"
},
"periods": {
"period_type": "monthly",
"open_periods": 1,
"allow_backdate_days": 14,
"month_close_required_role": "ChiefAccountant",
"reopen_closed_periods": false,
"posting_date_policy": "must_be_in_open_period",
"stage": "MVP"
},
"numbering": {
"numbering_scope": "year",
"reserve_on_validate": false,
"reuse_reserved_on_retry": true,
"skip_numbers_allowed": true,
"stage": "MVP"
},
"governance": {
"config_change_requires_role": "Admin",
"two_person_rule": false,
"audit_config_change": "append_only"
}
}
Biến thể có biến số
{
"tax": {
"enabled": {{BOOL}},
"tax_inclusive_default": {{BOOL}},
"vat_rates": [{{RATE_DECIMAL_LIST}}],
"rounding_mode": "{{half_up|half_even}}",
"rounding_scale": {{0|2}},
"line_level_tax": {{BOOL}},
"stage": "{{MVP|Phase2|Phase3}}"
},
"currency": {
"base_currency": "{{ISO_4217}}",
"multi_currency_enabled": {{BOOL}},
"stage": "{{MVP|Phase2}}"
},
"inventory": {
"multi_warehouse_enabled": {{BOOL}},
"default_warehouse": "{{CODE}}",
"cost_method": "{{moving_average|day_end_average|fifo}}",
"cost_recalc_window_days": {{INT}},
"max_recalc_lines": {{INT}},
"lot_tracking_enabled": {{BOOL}},
"serial_tracking_enabled": {{BOOL}},
"allow_negative_inventory": {{BOOL}},
"stage": "{{MVP|Phase2|Phase3}}"
},
"ai": {
"ai_enabled": {{BOOL}},
"ai_mode": "{{on_prem|cloud_redacted}}",
"ai_rule_engine": "{{on|off}}",
"risk_thresholds": {"high": {{DEC}}, "medium": {{DEC}}, "low": {{DEC}}},
"ai_block_commit": {{BOOL}},
"latency_budget_ms": {"background": {{INT}}, "gating": {{INT}}},
"timeout_ms": {"background": {{INT}}, "gating": {{INT}}},
"auto_degrade_low_alerts": {{BOOL}},
"audit_detail_level": "{{minimal|full}}",
"stage": "{{MVP|Phase2}}"
},
"periods": {
"period_type": "{{monthly|quarterly}}",
"open_periods": {{INT}},
"allow_backdate_days": {{INT}},
"month_close_required_role": "{{ROLE}}",
"reopen_closed_periods": {{BOOL}},
"posting_date_policy": "{{must_be_in_open_period|allow_cross_period_with_approval}}",
"stage": "{{MVP|Phase2}}"
},
"numbering": {
"numbering_scope": "{{year|month|branch_year}}",
"reserve_on_validate": {{BOOL}},
"reuse_reserved_on_retry": {{BOOL}},
"skip_numbers_allowed": {{BOOL}},
"stage": "{{MVP|Phase2}}"
},
"governance": {
"config_change_requires_role": "{{ROLE}}",
"two_person_rule": {{BOOL}},
"audit_config_change": "{{append_only|full_diff_signed}}"
}
}
Lưu ý vận hành
- Mọi thay đổi tham số có ảnh hưởng tới tính giá, kỳ, thuế phải tạo ticket kèm lý do và thời hạn hiệu lực. Hệ thống ghi audit diff cấu hình, người duyệt và thời điểm.
- Khi bật một tham số Phase 2 trở lên, chạy UAT tối thiểu 1 vòng với dữ liệu bản sao, đặc biệt với costmethod, multiwarehouse, multi_currency để tránh méo số liệu đang vận hành.
Kế hoạch kiểm thử, UAT, dữ liệu mẫu và KPI chất lượng dữ liệu
Mình gom thành bộ kiểm thử end to end có dữ liệu mẫu ẩn danh, tiêu chí pass rõ ràng, kế hoạch pilot UAT và KPI theo dõi chất lượng dữ liệu lẫn độ trễ AI. Bạn có thể chạy ngay trên bản cài on-prem, không cần thêm công cụ ngoài trừ một tracker lỗi.
Kịch bản test end to end tối thiểu
- Mua hàng nhập kho và ghi nhận công nợ phải trả
- Tiền điều kiện: có NCC V001, SKU H001, kho W1, tồn đầu kỳ H001 = 50 cái, giá bình quân 1.000.000.
- Bước:
a. Tạo hóa đơn mua INV-AP-001 cho V001 ngày hôm nay, H001 số lượng 20, đơn giá 1.100.000, thuế suất 0. b. Lưu nháp, quan sát thẻ Cảnh báo AI. c. Bấm Ghi sổ.
- Kỳ vọng:
- AI cảnh báo Medium do đơn giá lệch so với lịch sử, thời gian phản hồi không vượt SLA.
- Hóa đơn được ghi sổ vì cảnh báo AI chỉ cảnh báo, không chặn.
- Tồn H001 = 70, giá bình quân mới = 1.028.571, AR/AP cập nhật công nợ phải trả, sổ cái phát sinh đúng tài khoản.
- Tiêu chí pass: không âm kho, bút toán cân, công nợ tăng đúng số tiền, latency AI trong ngưỡng.
- Bán hàng xuất kho và ghi nhận công nợ phải thu
- Tiền điều kiện: có KH C001, tồn H001 = 70, avg = 1.028.571.
- Bước:
a. Tạo hóa đơn bán INV-AR-001 cho C001, H001 số lượng 30, đơn giá bán 1.500.000, thuế suất 0. b. Bấm Ghi sổ, sau đó lập tiếp INV-AR-002 với số lượng 50 để thử âm kho.
- Kỳ vọng:
- INV-AR-001 ghi sổ, COGS = 30 x 1.028.571, tồn còn 40.
- INV-AR-002 bị chặn bởi rule không cho âm kho, yêu cầu điều chỉnh.
- Tiêu chí pass: COGS theo avg tại thời điểm xuất, không có bút toán bán phần.
- Phát hiện trùng số hóa đơn nhà cung cấp
- Bước:
a. Lặp lại tạo hóa đơn mua cho V001 với cùng số INV-AP-001 trong kỳ mở.
- Kỳ vọng: bị chặn bởi rule trùng hóa đơn theo NCC + số hóa đơn + kỳ, không cần AI.
- Tiêu chí pass: mã 409 Business Duplicate hoặc thông báo tương đương, không phát sinh bản ghi mới.
- Giao dịch hồi tố trong ngưỡng cho phép
- Tiền điều kiện: có lịch sử 10 ngày gần nhất cho H001, tổng downstream < 100 dòng.
- Bước:
a. Tạo hóa đơn mua INV-AP-RETRO cách hôm nay 10 ngày với H001 số lượng 10, đơn giá 950.000. b. Ghi sổ.
- Kỳ vọng: hệ thống replay trong ngưỡng, cập nhật avg và COGS các xuất sau ngày hồi tố trong kỳ còn mở, audit log ghi đầy đủ.
- Tiêu chí pass: không chạm kỳ đã khóa, thời gian xử lý không vượt 5 giây cho phạm vi nhỏ, tồn không âm tại mọi mốc.
- Idempotency và mạng chập chờn
- Bước:
a. Khi bấm Ghi sổ một chứng từ, ngắt mạng máy trạm ngay sau khi gửi. b. Kết nối lại, bấm Gửi lại.
- Kỳ vọng: server trả receipt cũ theo idempotency, không ghi đúp, voucher_number giữ nguyên.
- Tiêu chí pass: 0 bản ghi trùng lặp trong DB sổ cái, sổ kho, công nợ.
Mẫu test case điền sẵn
- ID: TC-E2E-AP-INV-001
- Mục tiêu: Mua hàng nhập kho, AI cảnh báo Medium, ghi sổ thành công, công nợ AP tăng đúng, avg cost cập nhật.
- Dữ liệu: Vendor V001, SKU H001, Kho W1, Tồn đầu H001 50 cái avg 1.000.000, Hóa đơn INV-AP-001 ngày 2026-08-10, SL 20, ĐG 1.100.000, VAT 0.
- Tiền điều kiện: User vai trò Nhập liệu, IP thuộc whitelist, kỳ tháng hiện tại đang mở.
- Bước:
- Nhập dữ liệu như trên, dừng gõ 1 giây chờ AI nền.
- Quan sát thẻ Cảnh báo hiển thị Medium với giải thích lệch so với lịch sử 30 ngày.
- Bấm Ghi sổ.
- Kỳ vọng chi tiết:
- AI background p95 ≤ 1.0 s, gating p95 ≤ 1.2 s.
- Không có rule cứng nào chặn.
- Inventory: tồn mới 70, avg mới 1.028.571, lưu đúng 6 chữ số sau dấu phẩy ở sổ phụ, hiển thị 0 chữ số theo cấu hình tiền tệ.
- AP: tăng 22.000.000 vào công nợ V001.
- Audit: lưu modelversion, rulesversion, score, top 3 feature, user hành động Proceed.
- Pass: tất cả kỳ vọng đạt, không có lỗi 5xx, audit đầy đủ.
Mẫu có biến số ngay sau
- ID: {{TC_CODE}}
- Mục tiêu: {{BUSINESS_GOAL}}
- Dữ liệu: Vendor {{VENDORCODE}}, SKU {{SKUCODE}}, Kho {{WHCODE}}, Tồn đầu {{QTY0}} avg {{AVG0}}, Hóa đơn {{DOCNO}} ngày {{DOCDATE}}, SL {{QTYIN}}, ĐG {{PRICEIN}}, VAT {{VATRATE}}.
- Tiền điều kiện: {{ROLES}}, IP {{CIDRALLOWED}}, kỳ {{OPENPERIOD}}.
- Bước: {{STEP_LIST}}
- Kỳ vọng chi tiết: {{EXPECTATIONS}}
- Pass: {{PASS_CRITERIA}}
Dữ liệu mẫu ẩn danh để nạp nhanh
- vendors.csv
vendor_id,vendor_name,tax_id,credit_days
V001,Anpha Supplies,*********,30
V002,Beta Trading,*********,15
V003,Delta Parts,*********,0
- customers.csv
customer_id,customer_name,tax_id,credit_days
C001,Sun Retail,*********,15
C002,Moon Mart,*********,0
C003,Star Shop,*********,7
- items.csv
sku,sku_name,uom
H001,Motor A,pcs
H002,Bearing B,pcs
H003,Seal C,pcs
H004,Grease D,tube
- opening_inventory.csv
sku,warehouse,qty,avg_cost
H001,W1,50,1000000
H002,W1,200,50000
H003,W1,0,0
H004,W1,100,20000
- samplehistorypurchases.csv để AI có nền so sánh
date,vendor_id,sku,qty,unit_price
2026-07-15,V001,H001,30,1000000
2026-07-28,V001,H001,20,1000000
Tiêu chí pass tổng thể UAT
- Dữ liệu và nghiệp vụ:
- 0 âm kho xảy ra khi ghi sổ.
- 100 phần trăm chứng từ có ngày ngoài kỳ bị chặn bởi rule cứng.
- 100 phần trăm trùng hóa đơn NCC trong kỳ bị chặn.
- Hiệu ứng đúng một lần: 0 bản ghi trùng khi thao tác retry trên tối thiểu 100 lần gửi lại có chủ đích.
- AI:
- Latency: p95 background ≤ 1.0 s, p95 gating ≤ 1.2 s, p99 gating ≤ 2.0 s.
- FP theo cấp: High ≤ 5 phần trăm, Medium ≤ 12 phần trăm, Low ≤ 20 phần trăm, đo trên tối thiểu 500 chứng từ hoặc toàn bộ 2 tuần, lấy lớn hơn.
- Cơ chế tự hạ cấp hoạt động khi p95 gating vượt 1.2 s liên tục 3 giờ, sau đó tự phục hồi.
- Bảo mật và truy cập:
- Truy cập từ IP ngoài whitelist bị chặn 100 phần trăm.
- VPN pool vào được và truy vết user theo user_id VPN trong log.
Kế hoạch pilot UAT
- Phạm vi và thời lượng: 2 tuần trên dữ liệu thật, 5 đến 10 người dùng đại diện Kế toán mua bán, kho, kế toán trưởng, 1 đến 2 người dùng từ xa qua VPN để kiểm chứng full tunnel và whitelist.
- Chuẩn bị:
- Nạp danh mục và số dư đầu kỳ từ CSV mẫu.
- Bật audit full, bật AI on-prem rule engine và bất thường thống kê, tắt LLM.
- Thiết lập whitelist IP, VPN pool, backup mỗi đêm, điểm khôi phục thủ công đã kiểm tra trước.
- Lịch chạy:
- Ngày 1 đến 2: chạy kịch bản chuẩn với dữ liệu mẫu ở môi trường UAT.
- Ngày 3 đến 12: chạy trên dữ liệu thật, ghi nhận KPI hàng ngày.
- Ngày 13: tổng hợp KPI, chạy lại các case đã fail sau khi fix.
- Ngày 14: hội đồng duyệt nghiệm thu gồm PM nội bộ và kế toán trưởng ký biên bản.
- Tiêu chí Go Live:
- Đạt tất cả tiêu chí pass tổng thể ở trên.
- Không còn lỗi Severity 1 và 2 mở.
- Sao lưu và khôi phục thử thành công trong 30 phút với RPO tối đa 24 giờ.
KPI theo dõi trong pilot
- KPI chất lượng dữ liệu: số âm kho, số chứng từ bị chặn do ngoài kỳ, số duplicate bị chặn, tỷ lệ ghi sổ thành công lần đầu, số lần retry có receipt khớp.
- KPI AI: p95 và p99 latency theo 2 pha, uptime AI theo ngày, FP theo cấp độ, số cảnh báo bị ẩn do auto degrade.
- KPI vận hành: thời gian phản hồi p95 của màn hình ghi sổ, số lỗi 5xx theo ngày, số lần timeout AI.
Quy trình ghi nhận lỗi và fix
- Ghi nhận:
- Công cụ: dùng Jira hoặc GitLab Issues.
- Mỗi lỗi bắt buộc có: Test ID, môi trường, bước tái hiện, payload rút gọn, idempotencykey, requestid, validationtoken hash, userid, thời điểm, trích log reverse proxy remote_addr và app log, ảnh chụp màn hình, mức độ nghiêm trọng.
- Phân loại mức độ:
- S1 sản xuất dừng hoặc sai lệch số liệu kế toán, S2 chức năng chính lỗi nhưng có workaround, S3 lỗi giao diện hoặc hiệu năng không chặn việc, S4 đề xuất cải tiến.
- SLA xử lý:
- S1: hotfix trong 24 giờ, S2: bản vá trong 3 ngày làm việc, S3: trong 7 ngày, S4: đưa vào backlog release định kỳ.
- Quy trình fix:
- Triage hàng ngày 15 phút, gán người chịu trách nhiệm.
- Sửa trên nhánh hotfix, viết unit test và test hồi quy tối thiểu cho module chạm.
- Triển khai canary 10 phần trăm người dùng nội bộ, theo dõi 2 giờ.
- Mở rộng toàn bộ nếu ổn, cập nhật CHANGELOG, liên kết ticket với commit.
- Re-test test case gốc và các case liên quan, ký xác nhận bởi tester nghiệp vụ.
- Đóng lỗi: chỉ đóng khi có bằng chứng test đính kèm và số liệu sổ cái, kho, công nợ sau fix vẫn cân.
Gợi ý thực thi nhanh
- Chạy batch test theo thứ tự 1 đến 5, mỗi ngày ít nhất 50 chứng từ, đảm bảo đến cuối tuần thứ hai vượt 500 chứng từ để đo FP. Với môi trường có ít giao dịch, kéo dài pilot hoặc nạp thêm dữ liệu mẫu để đủ cửa sổ đo lường.
Vận hành, support, log và giám sát hệ thống
Mình tổ chức vận hành theo 4 lớp, gom log về một chỗ, theo dõi sức khỏe bằng chỉ số có ngưỡng, và có sổ tay thao tác nhanh để giảm thời gian khôi phục.
Quy trình support và phân cấp
- L0 Key User nghiệp vụ: tiếp nhận phản ánh, thu thập bắt buộc requestid, thời điểm, userid, IP máy trạm, ảnh màn hình. Tạo ticket.
- L1 IT nội bộ: kiểm tra whitelist IP hoặc VPN, kiểm tra dịch vụ chạy, trạng thái mạng nội bộ, đính log trích từ reverse proxy và app.
- L2 App admin: đọc log có request_id, kiểm tra DB, hàng đợi, AI validator, khôi phục tạm bằng chế độ chỉ-rule khi AI quá tải, triển khai cấu hình khẩn cấp theo runbook.
- L3 Đội phát triển: phân tích lỗi phần mềm, đưa hotfix, canary và phát hành.
SLA gợi ý
- S1 dừng vận hành hoặc sai lệch sổ: triage 15 phút, workaround 1 giờ, hotfix trong 24 giờ.
- S2 chức năng chính lỗi nhưng có lối vòng: triage 1 giờ, bản vá trong 3 ngày làm việc.
- S3 giao diện hoặc hiệu năng: triage trong ngày, bản vá trong 7 ngày.
- S4 cải tiến: vào lịch phát hành định kỳ.
Tập trung log tại reverse proxy và ứng dụng
- Reverse proxy là nguồn chân lý về truy cập. Bật access log JSON và chuẩn hóa X-Request-ID để tương quan end to end. Ví dụ Nginx:
# /etc/nginx/conf.d/log_json.conf
log_format json_ups escape=json
'{ "time":"$time_iso8601", "remote_addr":"$remote_addr", "status":$status,'
' "method":"$request_method", "uri":"$uri", "bytes":$bytes_sent,'
' "ref":"$http_referer", "ua":"$http_user_agent",'
' "req_time":$request_time, "ups_time":"$upstream_response_time",'
' "xff":"$http_x_forwarded_for", "x_req_id":"$upstream_http_x_request_id" }';
access_log /var/log/nginx/access.json.log json_ups;
# gắn ID tương quan đơn giản, app sẽ echo lại X-Request-ID vào response
proxy_set_header X-Request-ID $remote_addr-$msec-$connection;
proxy_set_header X-Forwarded-For $remote_addr;
- Ứng dụng log JSON theo dòng, in ra stdout để journald thu gom. Mỗi log bắt buộc có requestid, userid, action, outcome, latency_ms. Ví dụ một dòng:
{"ts":"2026-08-10T09:15:21.134Z","lvl":"INFO","svc":"app","request_id":"192.168.10.15-1723271721.134-1024","user_id":"u_thao","route":"POST /api/v1/vouchers/commit","idempotency_key":"ACME-AP-01JZ3QZ4E9S7W1Z5C1N0X","ai_model_version":"1.3.2","rules_version":"2026.08.01","latency_ms":412,"result":"committed","voucher_uid":"ACME-AP-01JZ3QZ4E9S7W1Z5C1N0X"}
- Nhật ký kiểm toán là kho append-only riêng, giữ tối thiểu 24 tháng, không quay vòng ngắn ngày như log vận hành.
Chỉ số sức khỏe và cảnh báo
- Health checks:
- /healthz: ứng dụng sẵn sàng xử lý, DB ping ok.
- /readyz: phụ thuộc ngoài sẵn sàng như hàng đợi, AI validator, quyền ghi đĩa.
- Telemetry nên có Prometheus /metrics. Tối thiểu: requestcount, requestdurationbucket, dbconninuse, queuedepth, aigatinglatency, aidegradedstate, proxy5xxrate, diskfree_bytes.
- Alert gợi ý bằng Prometheus:
groups:
- name: ops.rules
rules:
- alert: CpuHigh
expr: avg(rate(node_cpu_seconds_total{mode!="idle"}[5m])) by (instance) > 0.85
for: 10m
labels: {severity: warning}
annotations: {summary: "CPU > 85% 10m", runbook: "check-process"}
- alert: DiskLow
expr: node_filesystem_avail_bytes{mountpoint="/"} < 20e9
for: 5m
labels: {severity: critical}
annotations: {summary: "Disk free < 20GB", runbook: "free-space"}
- alert: Nginx5xxSpike
expr: sum(rate(nginx_http_requests_total{status=~"5.."}[5m])) by (instance)
/ clamp_min(sum(rate(nginx_http_requests_total[5m])) by (instance), 1) > 0.01
for: 5m
labels: {severity: critical}
annotations: {summary: "Nginx 5xx > 1% 5m", runbook: "trace-5xx"}
- alert: AppLatencyHigh
expr: histogram_quantile(0.95, sum by (le) (rate(app_request_duration_seconds_bucket{route="/api/v1/vouchers/commit"}[5m]))) > 1.2
for: 10m
labels: {severity: warning}
annotations: {summary: "p95 commit > 1.2s", runbook: "latency-commit"}
- alert: AIGatingSlowOrDown
expr: (histogram_quantile(0.99, sum by (le) (rate(ai_gating_latency_seconds_bucket[5m]))) > 2.0)
or (max(ai_degraded_state) == 1)
for: 10m
labels: {severity: warning}
annotations: {summary: "AI gating chậm hoặc degrade", runbook: "ai-degrade"}
- alert: DbConnSaturation
expr: db_conn_in_use / db_conn_max > 0.8
for: 10m
labels: {severity: warning}
annotations: {summary: "DB connections > 80%", runbook: "db-conn"}
- Bạn có thể thay bằng công cụ khác miễn đo được các chỉ số tương đương. Quan trọng là có p95 latency, 5xx rate, dung lượng đĩa, kết nối DB và tình trạng AI.
Vệ sinh log và lưu trữ
- Xoay vòng log Nginx 14 ngày, nén, gửi tín hiệu reopen file sau xoay vòng:
# /etc/logrotate.d/nginx
/var/log/nginx/*.log {
daily
rotate 14
compress
missingok
notifempty
create 0640 www-data adm
sharedscripts
postrotate
[ -f /run/nginx.pid ] && kill -USR1 `cat /run/nginx.pid`
endscript
}
- Journald giữ tối đa 2 GB hoặc 14 ngày, tùy điều kiện máy chủ:
# /etc/systemd/journald.conf.d/limiter.conf
[Journal]
SystemMaxUse=2G
MaxRetentionSec=14day
- Lịch dọn rác file đính kèm mồ côi theo TTL do app đánh dấu. Lưu trữ dài hạn: mỗi tuần gom gói log vận hành cũ hơn 14 ngày lên NAS nội bộ, giữ 90 ngày. Audit log giữ 24 tháng theo chính sách kế toán.
Sổ tay vận hành tóm tắt
- Kiểm tra nhanh dịch vụ:
# kiểm tra reverse proxy và app
systemctl status nginx
systemctl status app.service
curl -ks https://127.0.0.1/healthz
curl -ks https://127.0.0.1/readyz
- Truy vết theo request_id:
# tìm trong access log và app log cùng request_id
REQ="192.168.10.15-1723271721.134-1024"
grep "$REQ" /var/log/nginx/access.json.log
journalctl -u app.service -o cat | grep "$REQ"
- Khôi phục tạm khi AI quá tải: chuyển app sang chỉ-rule bằng biến cấu hình và reload dịch vụ, đồng thời mở ticket điều tra:
# ví dụ dùng biến môi trường và reload
sudo systemctl edit app.service # thêm Environment=AI_MODE=rules_only
sudo systemctl daemon-reload && sudo systemctl restart app.service
Runbook điền sẵn 1: Người dùng không truy cập được, nhận 403 tại trang đăng nhập
- L0 cung cấp IP máy trạm và thời điểm. L1 xác minh IP thuộc dải whitelist hoặc VPN pool.
- Kiểm tra log Nginx có deny:
# xem lỗi truy cập bị chặn theo IP
grep '"status":403' /var/log/nginx/access.json.log | tail -50
- Nếu đang ở ngoài trụ sở, kiểm tra máy có kết nối VPN và IP được cấp từ pool hợp lệ:
ip addr show # xác minh có IP 10.255.0.x chẳng hạn
- Nếu IP hợp lệ mà vẫn 403, soát lại cấu hình allow CIDR ở Nginx và firewall, áp thay đổi qua ticket đã duyệt, reload:
sudo nginx -t && sudo systemctl reload nginx
- Xác nhận người dùng truy cập lại, cập nhật ticket kèm đoạn log trước và sau khi sửa.
Runbook template để bạn điền khi bổ sung tình huống mới
- Sự cố: {{MÔ TẢ NGẮN}}
- Dấu hiệu: {{LOG HOẶC MÀN HÌNH}}
- Nguyên nhân thường gặp: {{DANH SÁCH}}
- Bước xử lý:
- {{KIỂM TRA_1}}
- {{KIỂM TRA_2}}
- {{SỬA_1}}
- {{XÁC NHẬN}}
- Ai làm: {{L1/L2/L3}}
- Bằng chứng cần lưu vào ticket: {{LOG, ẢNH, REQUEST_ID}}
- Điều kiện đóng: {{TIÊU CHÍ}}
Lộ trình MVP đến mở rộng theo MoSCoW và tiêu chí Go Live
Mình đóng khung ưu tiên theo MoSCoW để bạn chốt phạm vi V1 và nhìn rõ các mốc nâng cấp. Sau đó là tiêu chí Go Live, ngưỡng hiệu năng đồng thời tối thiểu và lộ trình các nâng cấp lớn như FIFO, mTLS, OCR, e‑invoice, đối soát ngân hàng.
MoSCoW cho phạm vi phát hành
- Must
- Web client on‑prem, reverse proxy nội bộ, ACL IP ở firewall và proxy, bắt buộc VPN khi truy cập từ xa.
- Xác thực, phân quyền theo vai trò, MFA với quyền nhạy cảm.
- Quy trình nhập và ghi sổ hóa đơn mua vào – bán ra, phiếu nhập – xuất – điều chỉnh kho, công nợ AR – AP, chính sách không cho âm kho.
- Định giá xuất kho theo bình quân tức thời, kỳ kế toán và khóa sổ, quy tắc đánh số chứng từ.
- Ghi sổ đúng một lần với idempotency, validation token, giao dịch ACID, outbox, retry an toàn.
- AI kiểm tra đầu vào: rule cứng + phát hiện bất thường thống kê, chỉ cảnh báo không chặn, audit chi tiết.
- Import CSV – Excel cho danh mục và số dư đầu kỳ, báo cáo vận hành tối thiểu, sao lưu và kiểm tra khôi phục, giám sát – log tập trung.
- Should
- Duyệt 1 cấp cho chứng từ, đa kho, phân quyền theo chi nhánh – kho, hạn mức tín dụng và lịch nhắc nợ cấu hình.
- Xuất – in báo cáo chuẩn sang Excel, template dunning email.
- Đối soát ngân hàng thủ công từ file CSV chuẩn của ngân hàng, màn hình mapping – tick đối trừ.
- API nội bộ có kiểm soát để nạp dữ liệu chuẩn hóa.
- Could
- OCR hóa đơn, mobile kiểm kho, barcode, SSO nội bộ, LLM nhỏ để giải thích cảnh báo AI, rule builder UI.
- mTLS client – proxy với CA nội bộ để ràng buộc thiết bị.
- E‑invoice tích hợp với nhà cung cấp, webhook – polling tự động nhận hóa đơn đầu vào.
- Đối soát ngân hàng tự động qua sao kê điện tử hoặc API ngân hàng.
- Won’t trong MVP
- FIFO, lô – seri, đa tiền tệ, hợp nhất nhiều pháp nhân, split‑tunnel VPN, mở app ra Internet, AI chặn ghi sổ.
Tiêu chí Go Live bắt buộc
- Chức năng
- 100% hạng Must đạt tiêu chí chấp nhận, tối thiểu 70% hạng Should được bật hoặc có workaround được đội nghiệp vụ chấp thuận.
- Không còn lỗi mức P1 hở sổ hoặc sai lệch số liệu, P2 còn tối đa 3 lỗi có workaround rõ ràng.
- Dữ liệu và quy trình
- Import danh mục và số dư đầu kỳ kiểm thử 2 lượt, lệch tổng sổ cái bằng 0 sau import.
- Runbook vận hành, backup hàng ngày đã test restore thành công trên môi trường cách ly trong 30 ngày gần nhất.
- Bảo mật – truy cập
- ACL IP tại firewall và reverse proxy đã kiểm tra thực chiến: 3 IP hợp lệ truy cập được, 3 IP ngoài dải bị chặn 403. VPN full‑tunnel hoạt động, tài khoản admin có MFA.
- Hiệu năng – đồng thời tối thiểu
- 20 người dùng hoạt động đồng thời trong LAN đạt p95 thời gian commit chứng từ ≤ 1.2 s, p99 ≤ 2.0 s.
- AI gating p95 ≤ 1.2 s, p99 ≤ 2.0 s, tỉ lệ fail‑open ≤ 1% phiên bản ghi.
- Throughput duy trì tối thiểu 30 chứng từ phút trong 15 phút kiểm thử tải, 5xx rate ≤ 0.5%, không timeout ở commit.
- DB CPU trung bình < 70%, bộ nhớ app < 75%, không bão hòa connection pool.
- Chất lượng AI
- Tỷ lệ cảnh báo giả 2 tuần gần nhất: High ≤ 5%, Medium ≤ 12%, Low ≤ 20%. Nếu chưa đủ dữ liệu, đo trên ít nhất 500 chứng từ gần nhất.
- Sẵn sàng vận hành
- Monitoring có p95 latency, 5xx rate, queue depth, dung lượng đĩa, health AI. Nhật ký kiểm toán bật và lưu về kho bất biến. Key user và IT đã qua tập huấn.
Ví dụ biên bản chốt Go Live – trích yếu
- Kết quả kiểm thử tải 20 user đồng thời ngày 2026‑08‑12: commit p95 1.08 s, p99 1.86 s; AI gating p95 0.94 s, p99 1.74 s; throughput 32 chứng từ phút; 5xx 0.2%.
- AI FP tuần 32‑33: High 3.2%, Medium 9.5%, Low 17.8%.
- ACL IP: pass 6 ca kiểm tra allow – deny. Backup restore: pass bản full ngày 2026‑08‑10.
- Trạng thái Must 100% pass, Should 78% pass. Quyết định Go Live: Đồng ý.
Mốc nâng cấp đề xuất
- R1 – MVP GA: toàn bộ Must.
- R1.1 – Hardening sau Go Live 2 đến 4 tuần
- Tối ưu hiệu năng theo số liệu thực tế, bổ sung Should còn thiếu, dọn nợ kỹ thuật, tinh chỉnh ngưỡng AI, thêm màn hình đối soát ngân hàng thủ công CSV nếu chưa có.
- R2 – Security và tích hợp nền tảng
- mTLS client – proxy với CA nội bộ, SSO nếu có directory. E‑invoice môi trường test – sản xuất theo lộ trình nhà cung cấp. Đối soát ngân hàng bán tự động qua sao kê điện tử.
- R2.1 – Kho và giá vốn nâng cao
- FIFO theo từng kho – mặt hàng, cơ chế hồi tính có ngưỡng an toàn, báo cáo layer cost. Tùy chọn lô – seri cho ngành cần truy vết.
- R2.2 – Nhập liệu và AI nâng cao
- OCR hóa đơn, chuẩn hóa tên đối tác bằng embedding, LLM nhỏ để giải thích cảnh báo, chính sách cho phép chặn theo cảnh báo High đã qua hiệu chỉnh.
- R3 – Mở rộng nghiệp vụ
- Đa tiền tệ, đa pháp nhân, tích hợp ngân hàng qua API chính thức, tự động thu thập e‑invoice đầu vào qua webhook.
Ghi chú triển khai thực tế
- Trước mỗi mốc, chạy lại bài kiểm thử tải đồng thời với số người dùng mục tiêu mới và kiểm tra lại FP AI. Nếu bất kỳ tiêu chí nào không đạt, hoãn phát hành và quay về nhánh hardening thay vì nới lỏng ngưỡng.
Checklist phỏng vấn yêu cầu còn mở và mặc định MVP
Mình chuẩn hóa checklist dưới dạng máy đọc để bạn chốt nhanh các quyết định còn mở và biết rõ mặc định đang áp dụng cho MVP. Bạn có thể dán nguyên khối điền sẵn vào tài liệu đặc tả v1, rồi dùng bản template trống ngay dưới để phỏng vấn và cập nhật. Mỗi dòng là một hạng mục cần chốt, kèm gợi ý phương án, mặc định MVP hiện tại và ghi chú làm rõ.
Checklist điền sẵn cho MVP
- Mục | Câu hỏi chốt | Lựa chọn gợi ý | Mặc định MVP | Ghi chú làm rõ
- Chuẩn kế toán và thuế | Doanh nghiệp áp dụng chuẩn kế toán quốc gia nào, cơ chế thuế GTGT/VAT, bộ mã thuế và cách hạch toán thuế? | Chuẩn kế toán nội địa theo dồn tích; VAT khấu trừ hoặc trực tiếp; bộ mã thuế cấu hình | Hạch toán kép theo dồn tích; bộ mã thuế cấu hình thủ công; chưa phát sinh báo cáo điện tử | Xác nhận biểu mẫu báo cáo thuế tối thiểu cần xuất để đối chiếu nội bộ
- Hóa đơn điện tử | Có phát hành/nhận hóa đơn điện tử và tích hợp với nhà cung cấp hay cơ quan thuế không? | Không tích hợp; Tích hợp nhận; Tích hợp phát hành | Không tích hợp trong MVP | Nhập tay số hóa đơn, ngày, mã tra cứu; đính kèm PDF nếu có
- Đa tiền tệ | Có dùng ngoại tệ khi mua bán hoặc ghi sổ, nguồn tỷ giá, cách quy đổi? | Đơn tiền tệ; Đa tiền tệ giao dịch; Đa tiền tệ và đánh giá lại | Đơn tiền tệ 1 đồng nội tệ | Chốt tên đồng nội tệ khi khởi tạo hệ thống
- Kho và chi nhánh | Số lượng kho và chi nhánh dùng app, có phân quyền theo kho/chi nhánh? | 1 kho 1 chi nhánh; Nhiều kho; Nhiều chi nhánh | 1 kho, 1 chi nhánh | Nếu nhiều chi nhánh, cung cấp danh sách để phân quyền
- Phương pháp tính giá xuất kho | Ưu tiên phương pháp nào cho hàng tồn kho? | Bình quân tức thời; Bình quân cuối ngày; FIFO | Bình quân tức thời, không cho âm kho | Mức hồi tố mặc định trong kỳ đang mở, có ngưỡng an toàn
- Lô và số seri | Cần theo dõi lô, hạn dùng, số seri cho mặt hàng nào? | Không; Theo lô; Theo seri; Lô và seri | Tắt quản lý lô/seri | Nếu cần, liệt kê nhóm hàng bắt buộc theo lô/seri
- Người dùng đồng thời và hiệu năng | Số user hoạt động đồng thời giờ cao điểm, mục tiêu độ trễ ghi sổ? | 10; 20; 30+ | 20 đồng thời trong LAN, commit p95 ≤ 1.2 s | Cung cấp khung giờ cao điểm để test tải
- OCR hóa đơn | Có cần OCR đọc ảnh/PDF hóa đơn để giảm nhập tay không, SLA độ chính xác? | Không; OCR nhận diện tiêu đề; OCR đầy đủ dòng hàng | Tắt OCR ở MVP | Nếu cần sau này, cho biết ngân sách và mức chính xác mong muốn
- Tích hợp ngân hàng | Phạm vi đối soát ngân hàng và kênh lấy dữ liệu sao kê? | Không; CSV thủ công; API chính thức | CSV thủ công qua màn hình đối soát | Cho biết ngân hàng sử dụng và mẫu CSV
- AI kiểm tra đầu vào | Triển khai AI on-prem hay cloud, có cần LLM giải thích tự nhiên? | On-prem rule + thống kê; On-prem có LLM; Cloud có redaction | On-prem rule engine + thống kê, không LLM | Nếu cân nhắc cloud, xác nhận trường cần ẩn danh trước khi gửi
- Ngưỡng cảnh báo AI | Ngưỡng High, Medium, Low và chính sách chặn hay chỉ cảnh báo? | Chỉ cảnh báo; Cảnh báo và chặn High | Chỉ cảnh báo; High ≥ 0.85, Medium 0.7–0.85, Low 0.5–0.7 | Có cơ chế fail-open khi quá SLA
- Quy trình ngoại lệ AI | Ai được override cảnh báo/rule và yêu cầu nhập lý do? | Người nhập; Người duyệt; Kế toán trưởng | Người nhập được Proceed; High phải nhập lý do; rule cứng chỉ Kế toán trưởng override | Ghi nhận đầy đủ audit, giải thích top feature
- Lưu trữ và TTL | Thời gian lưu chứng từ, file đính kèm, audit log, AI findings, idempotency key, log ứng dụng? | Theo pháp lý; 24–60 tháng; Không TTL tự xóa | Chứng từ và file không TTL tự xóa; audit 24 tháng; AI findings 12 tháng; idempotency 180 ngày; log ứng dụng 90 ngày online, lưu trữ 12 tháng | Xác nhận tối thiểu pháp lý của bạn để hiệu chỉnh
- Mạng và truy cập | Dải CIDR nội bộ, pool VPN, có dùng IPv6 nội bộ, chính sách truy cập từ xa? | Chỉ LAN; LAN + VPN full-tunnel; LAN + VPN split | Whitelist theo CIDR nội bộ và pool VPN riêng; VPN full-tunnel; chặn IPv6 | Cung cấp danh sách subnet cần allow và pool VPN
- Kỳ kế toán và khóa sổ | Chu kỳ kỳ kế toán, thời điểm khóa sổ, điều chỉnh sau khóa? | Tháng; Quý; Theo chính sách nội bộ | Khóa sổ theo tháng; điều chỉnh vào kỳ hiện tại | Chỉ rõ ngày khóa sổ chuẩn để cấu hình
- Đánh số chứng từ | Mẫu số chứng từ theo năm/tháng/chi nhánh, xử lý bỏ số? | Theo năm-tháng-chi nhánh; Theo sổ riêng | Cấp số trong transaction, reserve theo idempotency, không bỏ số sau commit | Cung cấp prefix mong muốn cho từng module
Template trống để phỏng vấn
- Mục | Câu hỏi chốt | Lựa chọn gợi ý | Mặc định MVP | Trả lời doanh nghiệp
- Chuẩn kế toán và thuế | Doanh nghiệp áp dụng chuẩn kế toán quốc gia nào, cơ chế thuế GTGT/VAT, bộ mã thuế và cách hạch toán thuế? | Chuẩn kế toán nội địa theo dồn tích; VAT khấu trừ hoặc trực tiếp; bộ mã thuế cấu hình | Hạch toán kép theo dồn tích; bộ mã thuế cấu hình thủ công; chưa phát sinh báo cáo điện tử | TBD
- Hóa đơn điện tử | Có phát hành/nhận hóa đơn điện tử và tích hợp với nhà cung cấp hay cơ quan thuế không? | Không tích hợp; Tích hợp nhận; Tích hợp phát hành | Không tích hợp trong MVP | TBD
- Đa tiền tệ | Có dùng ngoại tệ khi mua bán hoặc ghi sổ, nguồn tỷ giá, cách quy đổi? | Đơn tiền tệ; Đa tiền tệ giao dịch; Đa tiền tệ và đánh giá lại | Đơn tiền tệ 1 đồng nội tệ | TBD
- Kho và chi nhánh | Số lượng kho và chi nhánh dùng app, có phân quyền theo kho/chi nhánh? | 1 kho 1 chi nhánh; Nhiều kho; Nhiều chi nhánh | 1 kho, 1 chi nhánh | TBD
- Phương pháp tính giá xuất kho | Ưu tiên phương pháp nào cho hàng tồn kho? | Bình quân tức thời; Bình quân cuối ngày; FIFO | Bình quân tức thời, không cho âm kho | TBD
- Lô và số seri | Cần theo dõi lô, hạn dùng, số seri cho mặt hàng nào? | Không; Theo lô; Theo seri; Lô và seri | Tắt quản lý lô/seri | TBD
- Người dùng đồng thời và hiệu năng | Số user hoạt động đồng thời giờ cao điểm, mục tiêu độ trễ ghi sổ? | 10; 20; 30+ | 20 đồng thời trong LAN, commit p95 ≤ 1.2 s | TBD
- OCR hóa đơn | Có cần OCR đọc ảnh/PDF hóa đơn để giảm nhập tay không, SLA độ chính xác? | Không; OCR nhận diện tiêu đề; OCR đầy đủ dòng hàng | Tắt OCR ở MVP | TBD
- Tích hợp ngân hàng | Phạm vi đối soát ngân hàng và kênh lấy dữ liệu sao kê? | Không; CSV thủ công; API chính thức | CSV thủ công qua màn hình đối soát | TBD
- AI kiểm tra đầu vào | Triển khai AI on-prem hay cloud, có cần LLM giải thích tự nhiên? | On-prem rule + thống kê; On-prem có LLM; Cloud có redaction | On-prem rule engine + thống kê, không LLM | TBD
- Ngưỡng cảnh báo AI | Ngưỡng High, Medium, Low và chính sách chặn hay chỉ cảnh báo? | Chỉ cảnh báo; Cảnh báo và chặn High | Chỉ cảnh báo; High ≥ 0.85, Medium 0.7–0.85, Low 0.5–0.7 | TBD
- Quy trình ngoại lệ AI | Ai được override cảnh báo/rule và yêu cầu nhập lý do? | Người nhập; Người duyệt; Kế toán trưởng | Người nhập được Proceed; High phải nhập lý do; rule cứng chỉ Kế toán trưởng override | TBD
- Lưu trữ và TTL | Thời gian lưu chứng từ, file đính kèm, audit log, AI findings, idempotency key, log ứng dụng? | Theo pháp lý; 24–60 tháng; Không TTL tự xóa | Chứng từ và file không TTL tự xóa; audit 24 tháng; AI findings 12 tháng; idempotency 180 ngày; log ứng dụng 90 ngày online, lưu trữ 12 tháng | TBD
- Mạng và truy cập | Dải CIDR nội bộ, pool VPN, có dùng IPv6 nội bộ, chính sách truy cập từ xa? | Chỉ LAN; LAN + VPN full-tunnel; LAN + VPN split | Whitelist theo CIDR nội bộ và pool VPN riêng; VPN full-tunnel; chặn IPv6 | TBD
- Kỳ kế toán và khóa sổ | Chu kỳ kỳ kế toán, thời điểm khóa sổ, điều chỉnh sau khóa? | Tháng; Quý; Theo chính sách nội bộ | Khóa sổ theo tháng; điều chỉnh vào kỳ hiện tại | TBD
- Đánh số chứng từ | Mẫu số chứng từ theo năm/tháng/chi nhánh, xử lý bỏ số? | Theo năm-tháng-chi nhánh; Theo sổ riêng | Cấp số trong transaction, reserve theo idempotency, không bỏ số sau commit | TBD
Lỗi thường gặp và lưu ý khi đặc tả on prem, IP và AI
Đây là những lỗi mình thấy hay lặp lại khi đặc tả và triển khai on-prem cho app kế toán có giới hạn IP và AI kiểm tra đầu vào, kèm cách tránh và quyết định mặc định an toàn. Bạn có thể dùng như checklist rà soát cuối trước khi bàn giao đặc tả v1.
- Sai lầm: dùng X-Forwarded-For để quyết định cho phép truy cập
- Vì sao nguy hiểm: header này có thể bị giả mạo. Nếu có đường đi vòng vào app hoặc proxy tin nhầm upstream, kiểm soát IP coi như vô hiệu.
- Cách làm đúng: enforce allowlist dựa trên TCP source IP ở firewall hoặc reverse proxy. Chỉ dùng X-Forwarded-For cho logging.
- Mặc định an toàn: chặn mọi truy cập trực tiếp vào app server, backend chỉ nghe localhost hoặc UNIX socket.
- Ví dụ Nginx đúng kiểu allowlist theo CIDR và không tin X-Forwarded-For của client:
server {
listen 443 ssl http2;
# Không listen IPv6 ở MVP để tránh lỗ IPv6
# listen [::]:443 ssl http2; # bỏ dòng này
server_name acc.local;
# Whitelist theo TCP source IP
allow 192.168.10.0/24;
allow 192.168.20.0/24;
allow 10.255.0.0/24; # pool VPN
deny all;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr; # chuẩn hóa, không xâu chuỗi
}
}
Biến thể thay thế bằng HAProxy ACL:
acl src_allowed src -f /etc/haproxy/allowlist.lst
http-request deny if !src_allowed
- Sai lầm: quên chặn IPv6
- Vì sao nguy hiểm: nhiều hệ điều hành client và server bật IPv6 mặc định. Nếu proxy hoặc firewall không lọc IPv6, người dùng có thể đi đường tắt ngoài whitelist IPv4.
- Cách làm đúng: tắt listener IPv6 của reverse proxy, chặn IPv6 tại firewall, hoặc whitelist IPv6 bài bản khi bạn có kế hoạch địa chỉ rõ ràng.
- Mặc định an toàn: chặn toàn bộ IPv6 tới reverse proxy ở MVP.
- Ví dụ chặn nhanh bằng nftables:
sudo nft add table inet filter
sudo nft 'add chain inet filter input { type filter hook input priority 0; policy accept; }'
sudo nft 'add rule inet filter input ip6 nexthdr tcp tcp dport 443 drop'
Và bảo đảm cấu hình web server không có listen [::]:443.
- Sai lầm: bật VPN split-tunnel bừa bãi
- Vì sao nguy hiểm: lưu lượng đi Internet vẫn chạy ngoài VPN, tạo khe hở piggyback, DNS rò rỉ, khó enforce allowlist.
- Cách làm đúng: full-tunnel mặc định. DNS trỏ nội bộ. Chặn client-to-client trên VPN và chỉ cho phép đích là reverse proxy.
- Mặc định an toàn: full-tunnel cho mọi truy cập ngoài trụ sở. Nếu đường truyền HQ chưa đủ, giới hạn số phiên từ xa thay vì nới split-tunnel.
- Ví dụ WireGuard client full-tunnel:
[Interface]
Address = 10.255.0.10/24
DNS = 192.168.10.10
PrivateKey = <redacted>
[Peer]
PublicKey = SERVER_PUBKEY_BASE64
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = vpn.acme.local:51820
PersistentKeepalive = 25
Và chặn client-to-client ở server:
# Chỉ cho phép tới reverse proxy 192.168.10.100:443, chặn phần còn lại
iptables -A FORWARD -i wg0 -o eth0 -d 192.168.10.100 -p tcp --dport 443 -j ACCEPT
iptables -A FORWARD -i wg0 -j DROP
ip6tables -A FORWARD -i wg0 -j DROP
- Sai lầm: cho AI quyền chặn ghi sổ ở MVP
- Vì sao nguy hiểm: độ trễ và false positive làm tắc luồng, đặc biệt khi mạng nội bộ hoặc tài nguyên on-prem không ổn định.
- Cách làm đúng: AI chỉ cảnh báo, rule cứng mới chặn. Timeout thì fail-open về rule cứng.
- Mặc định an toàn: cảnh báo 3 mức, không chặn. High yêu cầu nhập lý do nếu vẫn ghi.
- Sai lầm: bỏ idempotency khi commit
- Vì sao nguy hiểm: mạng chập chờn sẽ tạo ghi đúp hoặc ghi bán phần, số liệu kế toán lệch khó truy vết.
- Cách làm đúng: bắt buộc Idempotency-Key cho mọi API commit, lưu snapshot phản hồi theo key và upsert theo khóa tự nhiên.
- Mặc định an toàn: key theo ULID, lưu tối thiểu 180 ngày.
- Ví dụ gọi API có idempotency:
curl -X POST https://acc.local/api/v1/commit \
-H "Authorization: Bearer eyJhbGciOi..." \
-H "Idempotency-Key: ACME-AP-01JZ3QZ4E9S7W1Z5C1N0X" \
-H "Content-Type: application/json" \
-d '{"tenant_id":"acme","voucher":{"uid":"ACME-AP-01JZ3QZ4E9S7W1Z5C1N0X","type":"AP","date":"2026-07-15","lines":[{"sku":"SP-001","qty":10,"price":125000}],"tax_code":"VAT10"}}'
Biến thể khi bạn đổi prefix tenant và module: TENANT-MODULE-ULID, ví dụ BETA-SO-01JZ3QZ4E9S7W1Z5C1N0Y.
- Sai lầm: cho phép âm kho
- Vì sao nguy hiểm: bình quân tức thời bị méo, phải hồi tính phức tạp, báo cáo tồn và COGS lệch theo ngày.
- Cách làm đúng: chặn xuất nếu sẽ âm tại thời điểm xuất. Nếu cần ngoại lệ hiếm, bắt buộc phê duyệt và bút toán điều chỉnh tạm thời có flag.
- Mặc định an toàn: không cho âm kho ở MVP. Có cờ mở ở giai đoạn sau kèm kiểm soát chặt.
- Sai lầm: có backup nhưng không test restore định kỳ
- Vì sao nguy hiểm: tới lúc sự cố mới phát hiện bản backup hỏng, thiếu quyền, thiếu dữ liệu đính kèm.
- Cách làm đúng: lịch backup hàng ngày và lịch test restore tối thiểu hàng tháng vào môi trường tách biệt. Kiểm tra tính toàn vẹn dữ liệu chính và file đính kèm.
- Mặc định an toàn: RPO 24 giờ, RTO thực tế từ bài test gần nhất. Ghi biên bản lần test restore với thời gian và bước thực hiện.
- Ví dụ kịch bản test nhanh với PostgreSQL:
# Backup
ts=$(date +%F)
pg_dump -Fc -f /backups/app_${ts}.dump appdb
# Restore test
createdb appdb_restore_test_${ts}
pg_restore -d appdb_restore_test_${ts} /backups/app_${ts}.dump
# Kiểm chứng một vài bảng trọng yếu
psql -d appdb_restore_test_${ts} -c "SELECT count(*) FROM vouchers;"
psql -d appdb_restore_test_${ts} -c "SELECT count(*) FROM inventory_ledger;"
# Dọn sau kiểm thử
dropdb appdb_restore_test_${ts}
Biến thể khi lưu trữ đính kèm trên filesystem: mount snapshot read-only vào môi trường test và kiểm tra tỷ lệ file orphan so với DB.
- Sai lầm: mở whitelist cho WiFi khách hoặc IP không kiểm soát
- Vì sao nguy hiểm: phá vỡ giả định thiết bị tin cậy, dễ bị dựng proxy bắc cầu.
- Cách làm đúng: chỉ whitelist VLAN nội bộ đã định danh hoặc pool VPN. Không whitelist IP công cộng nhà riêng nhân sự. Vendor truy cập qua VPN tạm thời có thời hạn.
- Mặc định an toàn: deny-all rồi allow theo CIDR đã duyệt, thay đổi phải qua quy trình phê duyệt.
- Sai lầm: quên log và audit cho quyết định AI và kiểm soát IP
- Vì sao nguy hiểm: không truy vết được vì sao chứng từ đi qua cảnh báo hoặc bị chặn.
- Cách làm đúng: log tại reverse proxy source IP thật và quyết định allow deny, log tại ứng dụng gồm model version, rules version, điểm rủi ro, hành động người dùng.
- Mặc định an toàn: audit log append-only, lưu tối thiểu 12 tháng cho AI và 24 tháng cho thao tác kế toán.
Tóm tắt quyết định mặc định an toàn để đóng đặc tả:
- IP allowlist tại firewall và reverse proxy theo TCP source IP, không dựa X-Forwarded-For để quyết định truy cập.
- Chặn IPv6 tới ứng dụng ở MVP, chỉ bật khi có kế hoạch địa chỉ và whitelist tương ứng.
- VPN full-tunnel cho truy cập ngoài trụ sở, chặn client-to-client, DNS nội bộ.
- AI chỉ cảnh báo, không chặn. Timeout fail-open về rule cứng. High yêu cầu lý do nếu override.
- Idempotency bắt buộc, key theo ULID, retention tối thiểu 180 ngày.
- Không cho âm kho ở MVP. Hồi tính trong kỳ theo ngưỡng an toàn đã cấu hình.
- Backup hàng ngày và test restore hàng tháng, có biên bản và số liệu kiểm chứng.
Kết luận
Mình tin rằng với đặc tả này, bạn có thể triển khai nhanh một MVP kế toán on-prem tối giản nhưng an toàn, vận hành ổn định, kiểm soát rủi ro và sẵn sàng mở rộng. Lưu ý quan trọng: luôn thực thi whitelist IP ở lớp firewall hoặc reverse proxy bằng CIDR, không dựa vào X-Forwarded-For.