1. Bối cảnh nghiệp vụ
Một tập đoàn sản xuất linh kiện điện tử có 12 nhà máy và hơn 40 phòng ban vận hành một hệ thống ERP tập trung để quản lý toàn bộ chuỗi mua hàng, từ khi nhân viên lập yêu cầu mua hàng (Purchase Requisition - PR) cho đến khi kế toán khoá sổ kỳ tài chính. Mỗi tháng hệ thống xử lý khoảng 25.000 đơn mua hàng (Purchase Order - PO) với tổng giá trị giao dịch dao động 800 tỷ đến 1.200 tỷ đồng, trải rộng từ mua nguyên vật liệu sản xuất, thiết bị văn phòng, đến dịch vụ bảo trì nhà máy. Vì giá trị giao dịch lớn và liên quan trực tiếp đến báo cáo tài chính hợp nhất phải nộp cho uỷ ban chứng khoán, bất kỳ lỗi nào trong quy trình duyệt mua hàng hoặc hạch toán sổ cái đều có thể dẫn đến sai lệch báo cáo tài chính, vi phạm kiểm soát nội bộ SOX (Sarbanes-Oxley), và trong trường hợp nghiêm trọng nhất là gian lận nội bộ không bị phát hiện.
Phạm vi bài viết bao trùm toàn bộ chuỗi Procure-to-Pay (P2P): PR → duyệt PR → PO → GRN (Goods Receipt Note - phiếu nhận hàng) → Invoice (hoá đơn nhà cung cấp) → 3-way match (đối chiếu 3 chiều PO-GRN-Invoice) → bút toán GL → khoá kỳ kế toán. Ràng buộc nghiệp vụ quan trọng nhất là ma trận duyệt theo hạn mức (approval matrix): mỗi cấp quản lý chỉ được duyệt PO trong một ngưỡng giá trị nhất định, vượt ngưỡng bắt buộc phải đẩy lên cấp cao hơn, và người lập yêu cầu tuyệt đối không được đồng thời là người duyệt (nguyên tắc phân tách trách nhiệm - Segregation of Duties, viết tắt SoD). Ràng buộc thứ hai là bất biến kế toán cơ bản nhất: tổng giá trị Nợ (debit) phải luôn bằng tổng giá trị Có (credit) trong mọi bút toán ghi vào sổ cái, nếu không hệ thống phải từ chối ghi nhận thay vì chấp nhận một bút toán lệch.
Phạm vi tự động hoá
- Kiểm thử API cho engine duyệt theo hạn mức (approval matrix) và nguyên tắc SoD
- Kiểm thử tích hợp 3-way match giữa PO, GRN, Invoice trước khi cho phép thanh toán
- Kiểm thử hạch toán GL và cơ chế khoá kỳ kế toán (period close)
2. Kiến trúc hệ thống & luồng dữ liệu
Kiến trúc gồm 5 module chính: Procurement Service (quản lý PR/PO), Approval Workflow Engine (điều phối luồng duyệt đa cấp dựa trên approval matrix cấu hình theo phòng ban và giá trị), Warehouse/GRN Service (ghi nhận hàng thực nhận), Accounts Payable (đối chiếu hoá đơn và 3-way match), và General Ledger Service (ghi bút toán và quản lý kỳ kế toán). Khi PR được tạo, Approval Workflow Engine tra bảng cấu hình hạn mức để xác định cấp duyệt cần thiết: nếu giá trị PR trong hạn mức của trưởng nhóm, hệ thống tự động gán duyệt cấp 1; nếu vượt, tự động đẩy tiếp lên giám đốc phòng ban hoặc giám đốc tài chính tuỳ ngưỡng cấu hình. Sau khi PO được duyệt và hàng về, GRN được tạo để xác nhận số lượng/chất lượng thực nhận, sau đó Accounts Payable chạy 3-way match so khớp PO (đã duyệt), GRN (đã nhận), và Invoice (nhà cung cấp gửi) trước khi cho phép thanh toán.
Điểm khó khi kiểm thử
Điểm khó nhất nằm ở việc approval matrix không phải một quy tắc tĩnh đơn giản mà là tổ hợp nhiều chiều: phòng ban, loại chi phí (CAPEX hay OPEX), giá trị giao dịch, và thậm chí đơn vị tiền tệ nếu công ty có giao dịch quốc tế. Một PO giá trị 200 triệu đồng ở phòng IT có thể cần duyệt cấp giám đốc, nhưng cùng giá trị đó ở phòng sản xuất lại chỉ cần trưởng nhóm do khác nhóm chi phí đã cấu hình sẵn ngưỡng khác nhau — nếu tester chỉ kiểm thử theo giá trị mà bỏ qua chiều phòng ban/loại chi phí sẽ bỏ lọt rất nhiều lỗi cấu hình sai. Điểm khó thứ hai là việc phát hiện vi phạm SoD không chỉ đơn giản là so sánh userId người tạo và người duyệt, mà còn phải kiểm tra trường hợp một người có nhiều tài khoản hoặc được uỷ quyền tạm thời (delegate approval) trong lúc quản lý đi công tác — đây là lỗ hổng kiểm soát nội bộ tinh vi mà kiểm toán viên thường xoáy sâu vào khi audit ERP.
Trong một đợt kiểm toán nội bộ, đội audit phát hiện 14 PO có giá trị từ 80-95 triệu đồng được duyệt bởi chính người tạo PR, dù hạn mức tự duyệt tối đa của họ chỉ là 20 triệu. Điều tra sâu cho thấy nguyên nhân là một tính năng 'uỷ quyền tạm thời' (delegate) được bật cho một trưởng phòng đi công tác đã không bị tắt đúng hạn, khiến người được uỷ quyền (chính là cấp dưới đã tạo PR) vô tình có luôn quyền duyệt của cấp trên trong 3 tuần liên tiếp.
3. Mô hình dữ liệu & bất biến (oracle)
Mô hình dữ liệu cốt lõi gồm bảng PurchaseRequest (id, createdBy, department, expenseType, amount, status), PurchaseOrder (id, prId, approvedBy, approvalTier, amount, status), GoodsReceiptNote (id, poId, receivedQty, receivedAt), Invoice (id, poId, grnId, invoiceAmount, matchStatus), LedgerEntry (id, sourceDocId, debitAccount, debitAmount, creditAccount, creditAmount, postedAt, periodId), và AccountingPeriod (id, yearMonth, status: OPEN/CLOSED). Trạng thái PO đi theo máy trạng thái hữu hạn: DRAFT → PENDING_APPROVAL → APPROVED → PO_ISSUED → PARTIALLY_RECEIVED/FULLY_RECEIVED → INVOICED → PAID, với khả năng REJECTED ở bước duyệt bất kỳ trước APPROVED.
Bất biến nghiệp vụ bắt buộc (oracle)
- Bất biến 1: Σ debitAmount = Σ creditAmount cho mọi bút toán cùng sourceDocId, sai lệch phải bằng 0
- Bất biến 2: cấp duyệt của approvedBy phải ≥ cấp yêu cầu theo approval matrix ứng với amount, department, expenseType
- Bất biến 3: createdBy của PR ≠ approvedBy của PO cùng chuỗi tài liệu tại mọi thời điểm (SoD)
- Bất biến 4: khi AccountingPeriod.status = CLOSED, không LedgerEntry nào thuộc periodId đó được sửa/xoá — chỉ được thêm bút toán điều chỉnh (adjusting entry) ở kỳ mới
- Bất biến 5: matchStatus = MATCHED chỉ khi |Invoice.invoiceAmount − PO.amount| ≤ dung sai cấu hình VÀ GRN.receivedQty khớp PO.orderedQty
💬 Bình luận (0)
Chưa có bình luận nào. Hãy là người đầu tiên!