Skip to content

Domain Model

Schema + repository do @nx/core sở hữu. Nguồn: packages/core/src/models/schemas/invoice/*.../tax/tax-info/*. Schema Invoiceinvoice; TaxInfo nằm trong schema dùng chung tax. Không có FK xuyên schema — chỉ InvoiceIssuance/InvoiceAuditTracing khai báo FK (cả hai tới Invoice).

1. ERD đầy đủ

2. Entities

Invoice

Thuộc tínhGiá trị
Tableinvoice.Invoice
Sourcepackages/core/src/models/schemas/invoice/invoice/schema.ts
Soft-delete
Owner columninvoiceConfigMappingId (→ merchant qua mapping)

Trường (chọn lọc):

TrườngKiểuBắt buộcMặc địnhMô tả
idtextSnowflakePK
sourceTypetext001_SALE_ORDERLoại chứng từ gốc (soft ref xuyên schema)
sourceId / sourceNumbertextId / số đơn hàng
origintext000_ORIGINORIGIN/ADJUSTMENT/REPLACEMENT
invoiceTypetextSnapshot config (VAT/SALE/POS/…)
invoiceSymbol / invoiceCategory / yeartext/int/intSnapshot serial
hasCqtCodebooleantrueCó mã cơ quan thuế
taxMethodtextDEDUCTION/DIRECT/UNKNOWN
autoRelease/autoSign/autoSendCqtbooleanSnapshot chính sách
issuanceModetextSnapshot mode của config
invoiceConfigMappingIdtextMapping định tuyến
invoiceNumbertextGán khi thành công
issuanceStatustextPENDINGXem enum
retryCountint0
parentIdtextHoá đơn được sửa (adjust/replace)
invoiceRequestIdtextNguồn thông tin người mua
claimQrDataUrltextQR data URL cho buyer-claim
metadatajsonberrorMessage, permanent, …

Enum trạng thái (issuanceStatus):

Giá trịMô tả
PENDINGĐã tạo, chờ phát hành (trạng thái khởi đầu)
PROCESSINGWorker đang gọi provider
SUCCESSĐã phát hành; gán invoiceNumber
FAILEDLỗi vĩnh viễn / cạn retry / DLQ
CANCELLEDHuỷ sau khi phát hành

Enum origin: 000_ORIGIN, 100_ADJUSTMENT, 200_REPLACEMENT.

Index chính:

TênCộtLoại
UQ partial(sourceType, sourceId) where origin=ORIGIN AND deletedAt IS NULLUnique partial
UQ partial(invoiceSymbol, invoiceNumber) where deletedAt IS NULLUnique partial
partial(invoiceConfigMappingId, createdAt) where PENDING AND issuanceMode=SCHEDULEDCron nhặt

InvoiceRequest

Thuộc tínhGiá trị
Tableinvoice.InvoiceRequest
Source.../invoice-request/schema.ts
Soft-delete
TrườngKiểuBắt buộcMặc địnhMô tả
sourceType/sourceId/sourceNumbertextSALE_ORDERChứng từ gốc
flowTypetext000_DIRECTDIRECT / 100_BUYER_CLAIM
statustextACTIVATEDACTIVATED/DEACTIVATED/CANCELLED
requestedBytextNgười nhập
buyerInfojsonb{}TBuyerInfo (name, taxCode, address, …)
claimTokentextToken tự khai báo (UQ partial)
claimDeadlinetimestamptzHết cửa sổ claim
claimStatetextPENDING/CLAIMED/EXPIRED
claimSubmittedAttimestamptz

Bất biến: một request ACTIVATED mỗi (sourceType, sourceId) (UQ partial index).

InvoiceProvider

Thuộc tínhGiá trị
Tableinvoice.InvoiceProvider
Soft-delete
TrườngKiểuBắt buộcGhi chú
merchantInvoiceProfileIdtextProfile sở hữu
providertextInvoiceProviders (hiện tại VNPAY)
environmenttextDEVELOPMENT/PRODUCTION
username / passwordtextpassword mã hoá AES-256-GCM
webhookSecrettextđã mã hoá
webhookUUID / webhookEventTypes / webhookStatustext/array/textđăng ký webhook

Bất biến: UQ (merchantInvoiceProfileId, provider) và UQ name (cả hai partial trên deletedAt IS NULL).

InvoiceProviderConfig

TrườngKiểuBắt buộcMặc địnhGhi chú
providerIdtextInvoiceProvider
invoiceType/invoiceSymbol/invoiceCategory/yearhỗn hợpSerial
issuanceModetext100_MANUALREAL_TIME/MANUAL/SCHEDULED/BUYER_SELF_SERVICE
issuanceModeMetadatajsonbclaimWindowMinutes, claimIssueTiming
autoRelease/autoSign/autoSendCqt/isSendMailbooleanfalseChính sách xuất
retryMetadatajsonb{max:3, delays:[5,15,60]}Chính sách retry
defaultBuyerInfojsonb"Người mua không lấy hoá đơn"Người mua fallback
deliveryChannelsjsonb[RECEIPT_QR]RECEIPT_QR/EMAIL/SMS

Bất biến: UQ (providerId, invoiceSymbol, invoiceType) partial.

InvoiceConfigMapping

TrườngKiểuBắt buộcGhi chú
principalType / principalIdtextvd SALE_CHANNEL → id kênh
providerConfigIdtextInvoiceProviderConfig
merchantIdtextChủ sở hữu
statustextACTIVATED/…

Bất biến: một mapping active mỗi (principalType, principalId) (UQ partial).

InvoiceIssuance

TrườngKiểuBắt buộcGhi chú
invoiceIdtextFK → Invoice.id; UQ partial (một mỗi hoá đơn)
invoiceRequestIdtextFK → InvoiceRequest.id
providertextProvider phát hành
externalId/externalRef/taxAuthorityCodetextRef của provider
providerStatustextVNPAY: new/released/signed/…
taxAuthorityStatusintVNPAY tvanStatus 1–8
requestIdtext${invoiceId}:${retryCount}
requestPayload/responsePayload/buyerInfoSnapshotjsonbBlob của provider

InvoiceAuditTracing

TrườngKiểuBắt buộcGhi chú
invoiceIdtextFK → Invoice.id
eventTypetextvd ISSUE_ATTEMPT
eventOutcometextmặc định SUCCESS
issuanceStatusBefore/Aftertextvết chuyển trạng thái
message / detailstext/jsonbcon người + có cấu trúc
triggeredBytextactor / system:*
occurredAttimestamptznow()

MerchantInvoiceProfile

TrườngKiểuBắt buộcGhi chú
merchantIdtextUQ partial (một profile mỗi merchant)
taxInfoIdtexttax.TaxInfo
taxMethodtextDEDUCTION/DIRECT/UNKNOWN
businessTypetextHOUSEHOLD/BUSINESS
sharingPolicytextPRIVATE/ALL_BRANCHES/WHITELIST

TaxInfo (schema dùng chung tax)

TrườngKiểuBắt buộcGhi chú
principalTypetextMerchant / Organizer
principalIdtext
taxCode / fullName / addressLinetextĐịnh danh người bán/người mua có thẩm quyền
cityCode/districtCode/wardsCode/fullAddresstextĐịa chỉ VN
managingTaxAuthority/chapter/departmenttextTrường registry GDT

Bất biến: UQ (principalType, principalId) partial. Ghi bởi TaxInfoService.syncTaxInfo từ merchant CDC — xem Integration §3.

3. Bất biến xuyên entity

Bất biếnCách thực thi
Một hoá đơn ORIGIN mỗi đơn nguồnUQ partial (sourceType, sourceId) where origin=ORIGIN AND deletedAt IS NULL
Duy nhất (invoiceSymbol, invoiceNumber) chính thức khi đã gánUQ partial index
Một InvoiceIssuance mỗi hoá đơnUQ partial (invoiceId); upsert theo mỗi retry (mới nhất thắng)
Một InvoiceRequest active mỗi nguồnUQ partial (sourceType, sourceId) where ACTIVATED
Một config mapping mỗi principalUQ partial (principalType, principalId)
TaxInfo là định danh người bán có thẩm quyềnsyncTaxInfo upsert theo (principalType, principalId); diff vs hàng đã lưu
(businessType, taxMethod) → tập invoiceType cho phépmap ALLOWED_INVOICE_TYPES (guard tầng service)
Chuỗi điều chỉnh/thay thếInvoice.parentId tự tham chiếu (tầng service, không FK DB)

4. Hành vi Soft-delete

Hành viChi tiết
Mặc định đọcdeletedAt IS NULL (SoftDeletableRepository)
Unique indexTất cả partial trên deletedAt IS NULL — hàng đã soft-delete giải phóng slot
Hard-deleteMặc định không dùng
RestoreChưa hiện thực

5. Trang liên quan

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.