Appearance
Purchase Invoice Complete Walkthrough - Example-Based Documentation
Overview
This document demonstrates the complete lifecycle of a purchase invoice using a real payload example, showing exactly which tables and columns are affected during both Add (Draft) and Post phases.
Example Payload
json
{
"id": "73b9e8d2-fe21-4ef7-2e68-08ddfb5b40cf",
"invoiceDate": "2026-01-28T23:06:04",
"description": null,
"warehouseId": 53,
"warehouseName": "Default Warehouse",
"vendorId": 44,
"vendorName": "مؤسسة هدية الجودة للدعاية والاعلان",
"currencyRate": 1,
"paymentTermId": 9,
"reference": "",
"currencyId": 4,
"currencyName": "Saudi Riyal",
"currencyNameAr": "ريال سعودي",
"isExternalPurchase": false,
"invoiceDetails": [
{
"id": "2b55acb1-180e-48d6-9107-08ddfb5b40dd",
"itemId": 169,
"itemCode": "4137",
"itemName": "نوت وسط",
"itemVariantId": 245,
"categoryId": 50,
"categoryType": "Storable",
"uomId": "56835e4d-4593-4481-9839-10a6321a42d3",
"uomCode": "G8roW",
"quantity": 600,
"cost": 10,
"netCost": 10,
"subCost": 6000,
"discountPercentage": 0,
"discountAmount": 0,
"vatPercentage": 15,
"vatAmount": 900,
"totalAfterDiscount": 6000,
"grandTotal": 6900,
"isServiceItem": false,
"trackingType": "NoTracking",
"hasExpiryDate": false,
"stockInEntryMode": "Manual",
"taxId": 18,
"vatId": 18,
"vatName": "الضريبة القياسية"
}
],
"sourceId": "73b9e8d2-fe21-4ef7-2e68-08ddfb5b40cf",
"sourceType": 2,
"sourceCode": "PO-2025-00004"
}Phase 1: Add Purchase Invoice (Draft)
Handler: AddPurchaseInvoiceCommandHandler
Action: Creates draft purchase invoice — no inventory or GL impact in this phase.
Calculations (Before Saving)
Line Item Calculations:
Quantity × Cost = Net Amount
600 × 10 = 6,000 SARVAT Amount (if not included): 6,000 × 15% = 900 SAR
Grand Total: 6,000 + 900 = 6,900 SAR
Header Totals:
- TotalNetAmount = 6,000 SAR
- TotalDiscount = 0 SAR
- TotalAfterDiscount = 6,000 SAR
- GrandTotal = 6,900 SAR
- LocalGrandTotal = 6,900 SAR (currencyRate = 1)
Table 1: PurchaseInvoiceHeader (INSERT)
Operation: INSERT new record
| Column Name | Value | Source | Notes |
|---|---|---|---|
| Id | [Auto-Generated GUID] | System | e.g., 73b9e8d2-fe21-4ef7-2e68-08ddfb5b40cf |
| Code | "PI-2026-0001" | Sequence Service | Generated by UpdateLastSequenceCommand |
| InvoiceDate | 2026-01-28 23:06:04 | Payload | Transaction date |
| InvoiceDueDate | calculated from payment term | PaymentTerm | Last installment date |
| VendorId | 44 | Payload | Vendor identifier |
| VendorName | "مؤسسة هدية الجودة للدعاية والاعلان" | Payload | Display name |
| WarehouseId | 53 | Payload | Receiving warehouse |
| Status | "Draft" | SetAsDraft() | Draft status |
| CurrencyId | 4 | Payload | Saudi Riyal |
| CurrencyRate | 1.0 | Payload | Exchange rate to base currency |
| TotalNetAmount | 6000.00 | CalculateHeaderTotals() | Sum of line net amounts |
| GrandTotal | 6900.00 | CalculateHeaderTotals() | Total + VAT |
| IsExternalPurchase | false | Payload | |
| CreatedDate | [Current Timestamp] | Audit | |
| CreatedBy | [Current User ID] | Security | |
| TenantId | [Tenant GUID] | Multi-tenancy |
Table 2: PurchaseInvoiceDetail (INSERT)
Operation: INSERT new record (one per line item)
| Column Name | Value | Source | Notes |
|---|---|---|---|
| Id | [Auto-Generated GUID] | System | e.g., 2b55acb1-180e-48d6-9107-08ddfb5b40dd |
| PurchaseInvoiceHeaderId | [Header GUID] | FK | Parent invoice |
| ItemId | 169 | Payload | Inventory item |
| ItemCode | "4137" | Payload | |
| ItemName | "نوت وسط" | Payload | |
| ItemVariantId | 245 | Payload | |
| UomId | "56835e4d-4593-4481-9839-10a6321a42d3" | Payload | |
| Quantity | 600.0 | Payload | |
| Cost | 10.00 | Payload | Unit cost |
| SubCost | 6000.00 | Computed | |
| DiscountPercentage | 0 | Payload | |
| VatPercentage | 15.0 | Payload | |
| VatAmount | 900.00 | Computed | |
| TotalAfterDiscount | 6000.00 | Computed | |
| GrandTotal | 6900.00 | Computed | |
| TrackingType | "NoTracking" | Payload | |
| HasExpiryDate | false | Payload | |
| StockInEntryMode | "Manual" | Payload | |
| CreatedDate | [Current Timestamp] | Audit | |
| CreatedBy | [Current User ID] | Security |
Table 3: PurchaseInvoiceTracking (INSERT)
Operation: INSERT when detail.InvoiceTracking is provided (NoTracking record created if present)
| Column Name | Value | Source | Notes |
|---|---|---|---|
| Id | [Auto-Generated GUID] | System | |
| PurchaseInvoiceDetailId | [Detail GUID] | FK | |
| TrackingNo | NULL | NoTracking | |
| Quantity | 600.0 | Detail.Quantity | |
| ExpireDate | NULL | No expiry | |
| TrackingType | "NoTracking" | Detail.TrackingType | |
| CreatedDate | [Current Timestamp] | Audit |
Table 4: PurchaseInvoiceBalance (INSERT)
Operation: INSERT new record(s) per payment term installment
| Column Name | Value | Source | Notes |
|---|---|---|---|
| Id | [Auto-Generated GUID] | System | |
| PurchaseInvoiceHeaderId | [Header GUID] | FK | |
| DueDate | [From PaymentTermId 9] | Payment Term | |
| DueAmount | 6,900.00 | Payment Term % of GrandTotal | |
| DueBalance | 6,900.00 | Initially = DueAmount | |
| CreatedDate | [Current Timestamp] | Audit |
Summary of Add Phase (Draft)
Tables Affected:
- ✅ PurchaseInvoiceHeader — 1 INSERT
- ✅ PurchaseInvoiceDetail — 1 INSERT
- ✅ PurchaseInvoiceTracking — 1 INSERT (if tracking object present)
- ✅ PurchaseInvoiceBalance — 1 INSERT (installment record)
Inventory Impact: NONE — No stock movement in draft phase
GL Impact: NONE — No journal entries in draft phase
Validations Performed:
- ✅ Invoice date in open financial period
- ✅ Vendor exists and has payable account configured (validation step)
- ✅ Price/cost and tax metadata read from Tax table
- ✅ Payment term applied
Phase 2: Post Purchase Invoice
Handler: PostPurchaseInvoiceCommandHandler
Action: Finalizes invoice, creates stock-in and accounting journal entries.
Prerequisites Check (before posting)
- ✅ Invoice Status = Draft
- ✅ Period allows posted journals
- ✅ Vendor has payable account configured
- ✅ Item 169 has inventory asset account configured (for storable items)
- ✅ Tax 18 has GL account configured
- ✅ Warehouse 53 exists and accepts stock-in
Table 1: PurchaseInvoiceHeader (UPDATE)
Operation: UPDATE existing record
| Column Name | Before (Draft) | After (Posted) | Change Description |
|---|---|---|---|
| Status | "Draft" | "Posted" | Status changed to Posted |
| StockInId | NULL | [StockIn GUID] | Reference to stock-in transaction |
| StockInCode | NULL | "SI-2026-0001" | Stock-in sequential code (example) |
| InvoiceJournalId | NULL | [Journal GUID] | Purchase journal entry ID |
| InvoiceJournalCode | NULL | "JE-2026-0200" | Journal entry code |
| PostedDate | NULL | 2026-01-28 23:10:00 | Timestamp of posting |
| UpdatedDate/By | [Original] | [Current] | Audit fields |
Table 2: StockInHeader (INSERT - Inventory Module)
Operation: INSERT new record (created by Inventory service)
| Column Name | Value | Source | Notes |
|---|---|---|---|
| Id | [Auto-Generated GUID] | System | |
| Code | "SI-2026-0001" | Sequence Service | |
| TransactionDate | 2026-01-28 23:06:04 | Invoice.InvoiceDate | |
| WarehouseId | 53 | Invoice.WarehouseId | |
| SourceDocumentType | "PurchaseInvoice" | Fixed | |
| SourceDocumentId | [Invoice Header GUID] | Invoice.Id | |
| TotalCost | 6000.00 | Sum of detail costs | |
| JournalId | [Journal GUID] | Inventory journal created by Inventory service | |
| CreatedDate | 2026-01-28 23:10:00 | Audit |
Table 3: StockInDetail (INSERT - Inventory Module)
Operation: INSERT new record (per inventory line)
| Column Name | Value | Source | Notes |
|---|---|---|---|
| Id | [Auto-Generated GUID] | System | |
| StockInHeaderId | [Header GUID] | FK | |
| ItemId | 169 | Detail.ItemId | |
| ItemCode | "4137" | Detail.ItemCode | |
| ItemVariantId | 245 | Detail.ItemVariantId | |
| UomId | "56835e4d-4593-4481-9839-10a6321a42d3" | Detail.UomId | |
| Quantity | 600.0 | Detail.Quantity | |
| UnitCost | 10.00 | Detail.Cost | |
| LineCost | 6000.00 | Quantity × UnitCost | |
| TrackingType | "NoTracking" | Detail.TrackingType | |
| SourceDetailId | [Invoice Detail GUID] | Link to invoice line | |
| CreatedDate | 2026-01-28 23:10:00 | Audit |
Table 4: StockInTracking (INSERT)
Operation: INSERT new record (created by Inventory service)
| Column Name | Value | Source | Notes |
|---|---|---|---|
| Id | [Auto-Generated GUID] | System | |
| StockInDetailId | [StockIn Detail GUID] | FK | |
| TrackingNo | NULL | NoTracking | |
| Quantity | 600.0 | StockInDetail.Quantity | |
| UnitCost | 10.00 | Unit cost | |
| CreatedDate | 2026-01-28 23:10:00 | Audit |
Table 5: ItemStockDetail (INSERT / UPDATE - Inventory Module)
Operation: UPDATE or INSERT depending on existing stock record
Before Post (example):
- AvailableQuantity = 0 (no prior on-hand)
After Post:
- AvailableQuantity = 600.0 — increased by 600
- TotalCost = 6000.00
- UnitCost = 10.00 (WAC or last cost, depending on policy)
Update Logic (example):
- If existing ItemStockDetail exists for ItemVariantId 245 & WarehouseId 53, update AvailableQuantity += 600 and adjust TotalCost / UnitCost per costing method; otherwise insert a new ItemStockDetail record.
Table 6: JournalEntry (INSERT - Purchase Journal)
Operation: INSERT new record (created by Accounting service)
| Column Name | Value | Source | Notes |
|---|---|---|---|
| Id | [Auto-Generated GUID] | System | |
| Code | "JE-2026-0200" | Sequence Service | |
| JournalDate | 2026-01-28 23:06:04 | Invoice.InvoiceDate | |
| SourceDocument | "PurchaseInvoice" | Fixed | |
| SourceDocumentId | [Invoice Header GUID] | Invoice.Id | |
| TotalDebit | 6900.00 | Sum debits | |
| TotalCredit | 6900.00 | Balanced totals | |
| FinancialYearPeriodId | [Period ID] | Invoice.FinancialYearPeriodId | |
| CreatedDate | 2026-01-28 23:10:00 | Audit |
Table 7: JournalEntryLine (INSERT Multiple - Purchase Journal)
Operation: INSERT 3 records (example)
Line 1: Inventory Asset (Debit)
- AccountId: 1030 (Inventory) — Debit 6000.00
Line 2: VAT Input (Debit)
- AccountId: 2040 (VAT Input/Recoverable) — Debit 900.00
Line 3: Accounts Payable (Credit)
- AccountId: 2010 (Vendor Payable) — Credit 6900.00
Accounting Summary (JE-2026-0200)
- DR Inventory 6,000.00
- DR VAT Input 900.00
- CR Vendor Payable 6,900.00
Table 8: VendorFinancial (UPDATE)
Operation: UPDATE existing vendor financials
Before Post:
- OutstandingPayable = X
After Post:
- OutstandingPayable += 6,900.00
Update SQL (example):
sql
UPDATE VendorFinancial
SET OutstandingPayable = OutstandingPayable + 6900.00,
TotalPurchases = TotalPurchases + 6000.00
WHERE VendorId = 44Summary of Post Phase
Tables Affected:
- ✅ PurchaseInvoiceHeader — 1 UPDATE (status, journal/stock references)
- ✅ StockInHeader — 1 INSERT (inventory transaction created by Inventory service)
- ✅ StockInDetail — 1 INSERT (line detail)
- ✅ StockInTracking — 1 INSERT (NoTracking record to store quantity)
- ✅ ItemStockDetail — 1 INSERT/UPDATE (increased AvailableQuantity)
- ✅ JournalEntry — 1 INSERT (Purchase journal in Accounting service)
- ✅ JournalEntryLine — 3 INSERTS (Inventory, VAT, Payable)
- ✅ VendorFinancial — 1 UPDATE (outstanding payable increased)
Inventory Impact:
- Item: 4137 (نوت وسط)
- Warehouse: Default Warehouse (53)
- Before: Available Qty = (example) 0
- After: Available Qty = 600
GL Impact (Combined Journals):
- DR Inventory (Asset) +6,000.00
- DR VAT Input (Asset/Recoverable) +900.00
- CR Vendor Payable (Liability) +6,900.00
Complete Transaction Flow Summary
Step-by-Step Execution
User submits Add Purchase Invoice request
AddPurchaseInvoiceCommandHandler.Handle()
Validate invoice date → Get FinancialYearPeriodId
Validate vendor accounting setup, tax metadata, UOM & item existence
Generate sequence code → "PI-2026-0001"
Calculate line totals and header totals
Create payment term balances (DueDate(s), DueAmount(s))
INSERT PurchaseInvoiceHeader (Status = Draft)
INSERT PurchaseInvoiceDetail (line(s))
INSERT PurchaseInvoiceTracking (if present)
INSERT PurchaseInvoiceBalance (installment(s))SaveChanges → Database transaction committed (Draft returned)
User triggers Post request
PostPurchaseInvoiceCommandHandler.Handle()
Validate preconditions (status, period, vendor accounts, tax GL)
Build purchase journal payload and call Accounting service → returns JournalId/Code
- Accounting creates JournalEntry and JournalEntryLines
Call Inventory service AddPostedStockIn() → returns StockInId/Code and Inventory Journal references
- Inventory creates StockInHeader, StockInDetail, StockInTracking, updates ItemStockDetail
Update PurchaseInvoiceHeader with StockIn and Journal references, set Status = Posted, set PostedDate
Update VendorFinancial (Outstanding payable)
SaveChanges → All references persisted
Publish webhook/event (if enabled)
Return InvoiceId → Frontend receives posted invoice
Database Transaction Isolation
Add Phase (Draft)
- Single DB transaction: INSERT header, details, balances → COMMIT
Post Phase
- Main transaction in Purchase.Invoice service:
- Call Accounting.AddJournal (external)
- Call Inventory.AddPostedStockIn (external)
- Update PurchaseInvoiceHeader, VendorFinancial
- COMMIT
- External services (Accounting, Inventory) run their own transactions and return identifiers which are persisted by the Purchase service.
Error Scenarios
Add Phase Validation Failures
- Invalid Period → "Invoice date must be in an open financial period"
- Missing Vendor → "Vendor not found"
- Price/Cost missing → "Item cost is required for storable items"
- Missing Tax metadata → "Tax configuration for taxId 18 is missing"
Post Phase Validation Failures
- Invoice Not Draft → "Invoice must be in draft status to post"
- Missing Vendor Payable Account → "Vendor payable account not configured"
- Missing Item Inventory Account → "Inventory GL account not configured for item 169"
- Missing Tax GL Account → "Tax account required for tax 18"
- Period Closed → "Cannot post journals in closed period"
End of Walkthrough This example demonstrates a typical purchase invoice with:
- ✅ Single storable item (NoTracking)
- ✅ Direct purchase (possibly from PO)
- ✅ No discount on line
- ✅ VAT not included (calculated separately)
- ✅ Single payment installment (payment term id 9)
- ✅ Inventory stock-in on post
- ✅ Purchase journal created (Inventory + VAT Debit, Vendor Payable Credit)
Document Version: 1.0
Example Date: 2026-01-28
Currency: Saudi Riyal (SAR)
Total Amount: 6,900.00 SAR (including 15% VAT)