ADR-0005. Capture snapshot pricing (v1 + v2) khi checkout vào order item
| Trường | Giá trị |
|---|---|
| Trạng thái | Accepted |
| Ngày | 2026-03-20 |
| Người quyết định | sale-team, pricing-team |
| Thay thế | — |
Bối cảnh
- Quy tắc pricing trong
@nx/pricingthay đổi thường xuyên — khuyến mãi, hiệu chỉnh giá, override fare. - Hóa đơn POS phát hành phải phản ánh giá tại thời điểm checkout — không phải giá live 3 tuần sau khi accounting đối chiếu.
- Pricing v1 trả về điểm giá đơn giản. v2 trả về object snapshot phong phú với rule trace, mức discount đã áp dụng, chi tiết quy đổi tiền tệ.
Quyết định
Tại checkout (CheckoutService.checkout), sale gọi PricingNetworkService.calculate() (v1) VÀ calculateV2() (v2) cho item của order. Cả hai kết quả được lưu trên mỗi SaleOrderItem:
unitPrice← kết quả v1 (giá bán hiển thị)priceMetadata(jsonb) ← snapshot v2 với pricing trace đầy đủfareId,fareProvider← tham chiếu tới rule đã định giá dòng
Snapshot v2 là source-of-truth cho refund, audit, và khai thuế. v1 là cái khách thấy.
Hệ quả
| Ưu | Nhược |
|---|---|
| Hóa đơn bất biến sau checkout | Độ trễ pricing call nhân đôi |
| Audit trail đầy đủ per dòng | priceMetadata jsonb phình to |
| Thay đổi rule không bao giờ ảnh hưởng order quá khứ | Pricing service phải đến được khi checkout |
| Refund biết chính xác discount nào đã áp dụng | Logic refund phải đọc snapshot v2, không re-call pricing |
Phương án thay thế đã cân nhắc
| Phương án | Ưu | Nhược | Lý do từ chối |
|---|---|---|---|
Chỉ lưu unitPrice (không snapshot) | Storage nhỏ hơn | Không audit được rule nào áp dụng | Không đủ cho refund và thuế |
| Re-call pricing mỗi lần đọc | Luôn "fresh" | Refund phản ánh giá hiện tại, không phải giá đã trả | Sai từ thiết kế |
| Snapshot chỉ khi order hoàn tất | Storage trước thanh toán nhỏ hơn | Mất pricing nếu order bị hủy trước khi hoàn tất | Khoảng trống audit |
Tham chiếu
sale/src/services/checkout.service.tssale/src/services/pricing-network.service.tscore/src/models/schemas/sale/sale-item/schema.ts(priceMetadata,fareId,fareProvider)