PRD: Hạch toán tài chính theo sự kiện (seam bán hàng → sổ cái)
| Module | Tài chính (CORE-12) | PRD ID | PRD-EVT-001 |
| Trạng thái | Shipped | Chủ sở hữu | Finance squad |
| Ngày | 2026-06-15 | Phiên bản | v1.0 |
| Packages | @nx/finance · @nx/core | URD | EVT · VCH · WAL |
TL;DR
Tài chính không ghi các dòng tiền thường nhật bằng tay và không chen vào đường găng của đơn bán. Thay vào đó, nó đăng ký nhận các sự kiện vận hành vốn đã xảy ra - một thanh toán bán hàng thành công, một đơn mua hàng được nhận, hàng xuất kho cho một đơn bán, một biến động giá trị tồn kho, một merchant được tạo - và hạch toán mỗi sự kiện thành một phiếu cân bằng qua luồng sự kiện, bất đồng bộ. Seam này là at-least-once: một sự kiện chỉ được xác nhận sau khi hạch toán của nó commit, nên một sự cố sẽ phát lại sự kiện thay vì làm mất nó; và mọi hạch toán đều idempotent - một sự kiện phát lại phát lại phiếu nó đã tạo thay vì hạch toán hai lần. Hành động bán, mua, hay kho gốc không bao giờ phải chờ sổ sách bắt kịp.
1. Bối cảnh & Vấn đề
Bộ máy phiếu (PRD-VCH-001) định nghĩa cái gì là một phiếu cân bằng và cách dựng sẵn tài khoản tiền. Nó không định nghĩa làm sao phần còn lại của hệ thống báo cho Tài chính biết có gì đó đã xảy ra. Nếu hạch toán là đồng bộ - đơn bán gọi Tài chính nội tuyến và chờ - thì một lượt ghi sổ chậm hoặc thất bại sẽ làm chậm hoặc chặn việc thu tiền của khách. Điều đó không chấp nhận được tại điểm bán.
Gắn sổ sách vào request thanh toán cũng khiến cả hai phía mong manh: một sự cố Tài chính sẽ làm đình trệ bán hàng, và một request thanh toán bị thử lại có nguy cơ hạch toán doanh thu hai lần. Merchant cần sổ sách đầy đủ và đúng về sau, chứ không phải đồng bộ và mong manh.
Bước tăng này đặc tả seam khép lại khoảng trống đó: các dịch vụ vận hành phát sự kiện, và một consumer Tài chính chuyên dụng đọc chúng ngoài luồng, phân giải mỗi sự kiện về tài khoản và đối tác của nó, rồi hạch toán phiếu tương ứng - một cách tin cậy (không sự kiện nào âm thầm mất), idempotent (không sự kiện nào hạch toán hai lần), và không bao giờ chặn hành động đã tạo ra nó.
2. Mục tiêu & Phi mục tiêu
Mục tiêu
- Hạch toán phiếu tài chính bất đồng bộ từ sự kiện vận hành, không bao giờ trên đường găng của hành động gốc.
- Đăng ký nhận đầy đủ các sự kiện liên quan tiền: thanh toán bán hàng thành công, nhận đơn mua hàng, xuất kho cho đơn bán, điều chỉnh tồn kho, tạo merchant.
- Bảo đảm xử lý at-least-once: chỉ xác nhận một sự kiện sau khi hạch toán của nó commit; một handler thất bại để lại sự kiện cho phát lại.
- Làm mọi hạch toán tự động idempotent - một sự kiện phát lại phát lại phiếu sẵn có thay vì tạo phiếu thứ hai.
- Phân giải mỗi sự kiện - vốn chỉ mang định danh - về tài khoản, danh mục, đối tác, và tham chiếu nguồn đọc được trước khi hạch toán.
- Ngắt sạch khi không có gì để ghi (không định tuyến tài chính, cơ sở giá vốn bằng 0, chênh lệch giá trị bằng 0, không có tiền thừa) - không phiếu rỗng hay không cân bằng.
Phi mục tiêu
- Bản thân mô hình phiếu/sổ cái - cân bằng, loại tài khoản, đánh số, hủy bằng bút toán đảo - đặc tả trong PRD-VCH-001.
- Hạch toán đồng bộ tại thời điểm request - bị bác bỏ rõ ràng; hạch toán là theo sự kiện.
- Tính số lượng và định giá kho (Tồn kho) - Tài chính tiêu thụ cơ sở giá vốn kết quả, không tính nó.
- Xử lý cổng thanh toán (Payment) - Tài chính phản ứng với một thanh toán đã thành công, không cấp phép thanh toán.
- Sổ đối tác, phải thu/phải trả, và P&L (LDG) - một bước tăng riêng.
3. Chỉ số thành công
| Chỉ số | Mục tiêu / tín hiệu |
|---|---|
| Không chặn | Việc thu tiền không bao giờ chờ một lượt ghi phiếu; đơn bán hoàn tất bất kể độ trễ Tài chính |
| Độ phủ hạch toán | Mọi sự kiện liên quan tiền cần ghi phiếu đều tạo đúng một phiếu |
| Idempotency | Một sự kiện phát lại không bao giờ tạo phiếu thứ hai - nó phát lại phiếu đầu |
| Độ bền | Không sự kiện liên quan tiền nào mất khi handler sự cố; nó được phát lại và hạch toán |
| Truy vết | Mọi phiếu tự hạch toán liên kết về chứng từ nguồn và sự kiện gốc của nó |
| Ngắt sạch | Sự kiện không có gì để ghi không để lại phiếu và không lỗi |
4. Chân dung & Tình huống
| Chân dung | Mục tiêu trong tính năng |
|---|---|
| Chủ / Kế toán | Tin rằng bán hàng, mua hàng, biến động kho lên sổ tự động và đầy đủ |
| Thu ngân | Thu tiền tức thì - việc sổ sách bắt kịp là vô hình và không bao giờ làm chậm đơn bán |
| Hệ thống (consumer Tài chính) | Đọc mỗi sự kiện vận hành, phân giải nó, và hạch toán đúng phiếu đúng một lần |
| Vận hành / Trực | Dựa vào phát lại cho lỗi nhất thời; xem các trường hợp bỏ qua cố ý trong log |
Tình huống cốt lõi: thu ngân thu tiền → đơn bán phát một sự kiện thanh toán thành công và đơn hoàn tất ngay → consumer Tài chính đọc sự kiện khỏi luồng, phân giải tài khoản đã định tuyến và danh mục Sale, rồi hạch toán một phiếu thu cân bằng liên kết tới đơn → nếu consumer sự cố giữa chừng, sự kiện được phát lại và hạch toán một lần → nếu cùng sự kiện được giao hai lần, lần giao thứ hai phát lại phiếu sẵn có thay vì hạch toán hai lần.
5. User Story
- Là thu ngân, tôi muốn thanh toán hoàn tất tức thì, để sổ chậm hay sập không bao giờ giữ chân khách.
- Là kế toán, tôi muốn một đơn được thanh toán, một PO được nhận, và hàng xuất kho tự hạch toán phiếu, để sổ luôn đầy đủ mà không nhập tay.
- Là chủ, tôi muốn một sự cố Tài chính nhất thời bắt kịp về sau thay vì làm mất bút toán, để sổ không bao giờ thiếu một đơn bán.
- Là hệ thống, tôi muốn một sự kiện phát lại phát lại phiếu sẵn có, để một lần thử lại hay phát lại không bao giờ hạch toán doanh thu hay giá vốn hai lần.
- Là trực, tôi muốn sự kiện không có gì để ghi được bỏ qua lặng lẽ và ghi log, để không bị gọi vì những phi-sự-kiện.
- Là kế toán, tôi muốn mỗi phiếu tự hạch toán liên kết tới đơn, PO, hay biến động nguồn của nó, để truy vết bất kỳ hạch toán nào về nguyên nhân.
6. Yêu cầu chức năng
| # | Yêu cầu | URD ref |
|---|---|---|
| FR-1 | Tài chính tiêu thụ sự kiện vận hành bất đồng bộ qua luồng sự kiện; hành động gốc không bao giờ bị chặn để chờ hạch toán | URD-EVT-001 |
| FR-2 | Đăng ký nhận: thanh toán bán hàng thành công, nhận đơn mua hàng, xuất kho cho đơn bán, điều chỉnh tồn kho, và tạo merchant | URD-EVT-002 |
| FR-3 | Một thanh toán bán hàng thành công hạch toán một phiếu thu; một phiếu chi trả lại tiền thừa khi tiền khách đưa vượt tổng đơn | URD-EVT-003 · URD-VCH-003 |
| FR-4 | Một đơn mua hàng được nhận hạch toán một phiếu chi cho nhà cung cấp, thêm vế tài sản tồn kho khi giá trị nhận dương | URD-EVT-004 · URD-VCH-004 |
| FR-5 | Hàng xuất kho cho một đơn bán hạch toán một phiếu giá vốn (giá vốn so với tài khoản kiểm soát tồn kho) khi cơ sở giá vốn dương | URD-EVT-005 · URD-VCH-005 |
| FR-6 | Một biến động giá trị tồn kho do người vận hành hạch toán một phiếu điều chỉnh tồn kho một dòng vào tài khoản kiểm soát tồn kho | URD-EVT-006 · URD-VCH-005 |
| FR-7 | Một merchant mới dựng sẵn tài khoản mặc định và tài khoản kiểm soát nội bộ từ sự kiện vòng đời merchant | URD-EVT-007 · URD-WAL-003 |
| FR-8 | Mọi hạch toán tự động đều idempotent - một sự kiện phát lại phát lại phiếu sẵn có, không bao giờ tạo phiếu thứ hai | URD-EVT-008 · URD-VCH-006 |
| FR-9 | Một sự kiện chỉ được xác nhận sau khi hạch toán của nó commit; một handler thất bại để lại sự kiện cho phát lại (at-least-once) | URD-EVT-009 |
| FR-10 | Sự kiện chỉ mang định danh; worker phân giải đầy đủ danh tính nguồn và đối tác (NCC, mã PO, tài khoản) trước khi hạch toán | URD-EVT-010 · URD-VCH-007 |
| FR-11 | Hạch toán ngắt sạch không tạo phiếu khi không có gì để ghi (không định tuyến tài chính, cơ sở giá vốn bằng 0, chênh lệch giá trị bằng 0, không có tiền thừa) | URD-EVT-011 |
| FR-12 | Consumer rút cạn công việc đang xử lý và đóng sạch khi nhận tín hiệu tắt | URD-EVT-012 |
Toàn văn yêu cầu và tiêu chí chấp nhận nằm trong Finance URD - EVT. PRD này tham chiếu chúng thay vì lặp lại. Bản thân mô hình phiếu/sổ cái đặc tả trong PRD-VCH-001.
7. Yêu cầu phi chức năng
| Khu vực | Yêu cầu |
|---|---|
| Tách rời | Hạch toán hoàn toàn bất đồng bộ qua luồng sự kiện; không hành động vận hành nào chờ một lượt ghi Tài chính |
| Đảm bảo giao | At-least-once - một sự kiện chỉ được commit sau khi handler của nó thành công; một sự cố phát lại nó |
| Idempotency | Hạch toán được khóa theo định danh duy nhất của sự kiện gốc (hoặc chứng từ nguồn của nó); một lần phát lại phát lại phiếu sẵn có |
| Toàn vẹn dữ liệu | Một phiếu và các dòng sổ cái của nó được ghi cùng nhau và cân bằng; không hạch toán dở dang nào sống sót qua một sự cố |
| Tenancy & authz | Mọi hạch toán giới hạn theo merchant; worker phân giải tài khoản trong merchant của sự kiện |
| Kháng lỗi | Một tài khoản kiểm soát thiếu hoặc nguồn chưa phân giải phát lỗi để sự kiện được phát lại, không âm thầm bỏ rơi |
| Độ chính xác | Số học tiền tệ dùng float(value, 4); một loại tiền tệ mỗi phiếu, mặc định VND |
| i18n | Lý do phiếu và tên đối tác song ngữ ({ en, vi }) |
8. UX & Luồng
Hạch toán theo sự kiện, không theo màn hình. Bước tăng này không có UI Tài chính - seam chạy phía sau các hành động thường ngày của merchant.
Bản đồ chủ đề-tới-hạch toán:
9. Dữ liệu & Miền
| Khái niệm | Vai trò trong seam |
|---|---|
| Sự kiện vận hành | Một sự thật được phát (thanh toán thành công, nhận PO, xuất kho, điều chỉnh tồn kho, tạo merchant) mang định danh, không mang bản ghi đầy đủ |
| Consumer Tài chính | Bộ đăng ký chuyên dụng đọc mỗi sự kiện, định tuyến tới handler, hạch toán, rồi xác nhận |
| Tham chiếu nguồn | Liên kết mà một phiếu đã hạch toán mang về chứng từ nguồn của nó (đơn bán, PO, biến động kho) |
| Khóa idempotency sự kiện | Định danh duy nhất của sự kiện gốc dùng để phát hiện một lần phát lại và phát lại phiếu sẵn có |
Voucher / LedgerLine | Chứng từ kế toán cân bằng và các dòng của nó mà handler phát hành - đặc tả trong PRD-VCH-001 |
Chỉ mang tính khái niệm - hợp đồng sự kiện, chủ đề, và đấu nối consumer nằm trong tài liệu dev finance; schema phiếu nằm trong mô hình miền finance.
10. Phụ thuộc & Giả định
Phụ thuộc
- Bộ máy phiếu (PRD-VCH-001, VCH) - seam phát hành các phiếu mà bộ máy này cân bằng và đánh số.
- Tài khoản (WAL) - tài khoản mặc định và kiểm soát phải được dựng trước khi hạch toán định tuyến; sự kiện merchant dựng chúng.
- Payment (
@nx/core) - phát sự kiện thanh-toán-thành-công dẫn động phiếu thu. - Inventory (
@nx/inventory) - phát sự kiện nhận-PO, xuất-kho, và điều-chỉnh-tồn-kho. - Commerce / Merchant (
@nx/commerce) - phát sự kiện vòng đời merchant kích hoạt dựng tài khoản.
Giả định
- Các dịch vụ vận hành phát sự kiện của chúng một cách tin cậy; việc của seam là tiêu thụ chúng bền bỉ.
- Mỗi sự kiện mang đủ định danh (merchant, chứng từ nguồn, tài khoản, số tiền/cơ sở giá vốn) để phân giải và hạch toán.
- Tài khoản kiểm soát nội bộ của merchant tồn tại trước khi hạch toán giá vốn và điều chỉnh tồn kho được thực hiện.
11. Rủi ro & Câu hỏi mở
| Rủi ro / câu hỏi | Giảm thiểu / trạng thái |
|---|---|
| Một sự kiện phát lại có thể hạch toán hai lần | Hạch toán idempotent - khóa theo id sự kiện; một lần phát lại phát lại phiếu sẵn có |
| Một sự cố handler có thể làm mất một sự kiện tiền | At-least-once - sự kiện chỉ được xác nhận sau khi hạch toán commit, nên sự cố phát lại nó |
| Hạch toán đồng bộ sẽ chặn đơn bán | Bị bác bỏ theo thiết kế - hạch toán hoàn toàn bất đồng bộ qua luồng sự kiện |
| Một tài khoản kiểm soát thiếu khi sự kiện giá vốn/điều chỉnh đến | Handler phát lỗi thay vì âm thầm bỏ qua, nên sự kiện được phát lại khi tài khoản tồn tại |
| Sự kiện không có gì để ghi | Cố ý ngắt sạch (không định tuyến tài chính, giá vốn 0, chênh lệch 0, không tiền thừa) - ghi log, không phiếu, không lỗi |
| Sự kiện độc luôn thất bại | Phát lại được vận hành giới hạn; thất bại dai dẳng nổi lên trong log lỗi để xem xét |
12. Kế hoạch phát hành & Tiêu chí ra mắt
| Khía cạnh | Kế hoạch |
|---|---|
| Phase | P1 (nền tảng) - EVT trong danh mục tính năng URD, cùng VCH và WAL |
| Triển khai | Mọi merchant; không feature flag - seam luôn bật |
| Di trú | Không - consumer đăng ký nhận sự kiện vốn đã được bán hàng, payment, và tồn kho phát |
| Tiêu chí ra mắt | Một đơn được thanh toán, một PO được nhận, và hàng xuất kho mỗi cái hạch toán phiếu của nó bất đồng bộ; một sự kiện phát lại hạch toán một lần; một sự cố handler phát lại và hạch toán một lần; sự kiện không có gì để ghi không để lại phiếu |
| Giám sát | Lượng hạch toán và tỷ lệ lỗi theo chủ đề, số lần phát-lại idempotent, số lần bỏ-qua cố ý, độ trễ consumer |
13. FAQ
Việc thu tiền có chờ sổ sách cập nhật không? Không - đơn bán hoàn tất ngay và phát một sự kiện; Tài chính hạch toán phiếu về sau, qua luồng. Việc sổ bắt kịp là vô hình với thu ngân.
Điều gì xảy ra nếu Tài chính sập khi một đơn được thanh toán? Sự kiện chờ trên luồng. Khi Tài chính hồi phục nó đọc sự kiện và hạch toán phiếu - không mất gì, vì một sự kiện chỉ được xác nhận sau khi hạch toán commit.
Cùng một sự kiện có thể hạch toán hai lần không? Không - mọi hạch toán đều idempotent. Một sự kiện phát lại được phát hiện theo định danh và phát lại phiếu nó đã tạo thay vì hạch toán phiếu thứ hai.
Vì sao đôi khi một đơn bán bị "bỏ qua" trong log? Đó là phát lại idempotent - lần giao thứ hai tìm thấy phiếu sẵn có và phát lại thay vì hạch toán hai lần.
Sự kiện nào không tạo phiếu? Sự kiện không có gì để ghi - một thanh toán không định tuyến tài chính, một lần xuất kho cơ sở giá vốn 0, một biến động kho chênh lệch giá trị 0, hoặc một đơn bán không có tiền thừa. Chúng được ngắt sạch và ghi log, không phải lỗi.
Logic cân bằng và đánh số ở đâu? Trong bộ máy phiếu (PRD-VCH-001). PRD này phủ seam giao sự kiện tới bộ máy đó một cách tin cậy và đúng một lần.
Tham chiếu
- URD: Tài chính - EVT · Phiếu & Hạch toán · Tài khoản
- Xây trên: Phiếu & hạch toán · Danh mục tài chính
- Module: Tài chính - tổng quan + truy vết
- Thượng nguồn: Module Payment · Module Tồn kho
- Dev: @nx/finance · @nx/core · mô hình miền