Skip to content

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 SAR

  • VAT 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 NameValueSourceNotes
Id[Auto-Generated GUID]Systeme.g., 73b9e8d2-fe21-4ef7-2e68-08ddfb5b40cf
Code"PI-2026-0001"Sequence ServiceGenerated by UpdateLastSequenceCommand
InvoiceDate2026-01-28 23:06:04PayloadTransaction date
InvoiceDueDatecalculated from payment termPaymentTermLast installment date
VendorId44PayloadVendor identifier
VendorName"مؤسسة هدية الجودة للدعاية والاعلان"PayloadDisplay name
WarehouseId53PayloadReceiving warehouse
Status"Draft"SetAsDraft()Draft status
CurrencyId4PayloadSaudi Riyal
CurrencyRate1.0PayloadExchange rate to base currency
TotalNetAmount6000.00CalculateHeaderTotals()Sum of line net amounts
GrandTotal6900.00CalculateHeaderTotals()Total + VAT
IsExternalPurchasefalsePayload
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 NameValueSourceNotes
Id[Auto-Generated GUID]Systeme.g., 2b55acb1-180e-48d6-9107-08ddfb5b40dd
PurchaseInvoiceHeaderId[Header GUID]FKParent invoice
ItemId169PayloadInventory item
ItemCode"4137"Payload
ItemName"نوت وسط"Payload
ItemVariantId245Payload
UomId"56835e4d-4593-4481-9839-10a6321a42d3"Payload
Quantity600.0Payload
Cost10.00PayloadUnit cost
SubCost6000.00Computed
DiscountPercentage0Payload
VatPercentage15.0Payload
VatAmount900.00Computed
TotalAfterDiscount6000.00Computed
GrandTotal6900.00Computed
TrackingType"NoTracking"Payload
HasExpiryDatefalsePayload
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 NameValueSourceNotes
Id[Auto-Generated GUID]System
PurchaseInvoiceDetailId[Detail GUID]FK
TrackingNoNULLNoTracking
Quantity600.0Detail.Quantity
ExpireDateNULLNo expiry
TrackingType"NoTracking"Detail.TrackingType
CreatedDate[Current Timestamp]Audit

Table 4: PurchaseInvoiceBalance (INSERT)

Operation: INSERT new record(s) per payment term installment

Column NameValueSourceNotes
Id[Auto-Generated GUID]System
PurchaseInvoiceHeaderId[Header GUID]FK
DueDate[From PaymentTermId 9]Payment Term
DueAmount6,900.00Payment Term % of GrandTotal
DueBalance6,900.00Initially = 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)

  1. ✅ Invoice Status = Draft
  2. ✅ Period allows posted journals
  3. ✅ Vendor has payable account configured
  4. ✅ Item 169 has inventory asset account configured (for storable items)
  5. ✅ Tax 18 has GL account configured
  6. ✅ Warehouse 53 exists and accepts stock-in

Table 1: PurchaseInvoiceHeader (UPDATE)

Operation: UPDATE existing record

Column NameBefore (Draft)After (Posted)Change Description
Status"Draft""Posted"Status changed to Posted
StockInIdNULL[StockIn GUID]Reference to stock-in transaction
StockInCodeNULL"SI-2026-0001"Stock-in sequential code (example)
InvoiceJournalIdNULL[Journal GUID]Purchase journal entry ID
InvoiceJournalCodeNULL"JE-2026-0200"Journal entry code
PostedDateNULL2026-01-28 23:10:00Timestamp of posting
UpdatedDate/By[Original][Current]Audit fields

Table 2: StockInHeader (INSERT - Inventory Module)

Operation: INSERT new record (created by Inventory service)

Column NameValueSourceNotes
Id[Auto-Generated GUID]System
Code"SI-2026-0001"Sequence Service
TransactionDate2026-01-28 23:06:04Invoice.InvoiceDate
WarehouseId53Invoice.WarehouseId
SourceDocumentType"PurchaseInvoice"Fixed
SourceDocumentId[Invoice Header GUID]Invoice.Id
TotalCost6000.00Sum of detail costs
JournalId[Journal GUID]Inventory journal created by Inventory service
CreatedDate2026-01-28 23:10:00Audit

Table 3: StockInDetail (INSERT - Inventory Module)

Operation: INSERT new record (per inventory line)

Column NameValueSourceNotes
Id[Auto-Generated GUID]System
StockInHeaderId[Header GUID]FK
ItemId169Detail.ItemId
ItemCode"4137"Detail.ItemCode
ItemVariantId245Detail.ItemVariantId
UomId"56835e4d-4593-4481-9839-10a6321a42d3"Detail.UomId
Quantity600.0Detail.Quantity
UnitCost10.00Detail.Cost
LineCost6000.00Quantity × UnitCost
TrackingType"NoTracking"Detail.TrackingType
SourceDetailId[Invoice Detail GUID]Link to invoice line
CreatedDate2026-01-28 23:10:00Audit

Table 4: StockInTracking (INSERT)

Operation: INSERT new record (created by Inventory service)

Column NameValueSourceNotes
Id[Auto-Generated GUID]System
StockInDetailId[StockIn Detail GUID]FK
TrackingNoNULLNoTracking
Quantity600.0StockInDetail.Quantity
UnitCost10.00Unit cost
CreatedDate2026-01-28 23:10:00Audit

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 NameValueSourceNotes
Id[Auto-Generated GUID]System
Code"JE-2026-0200"Sequence Service
JournalDate2026-01-28 23:06:04Invoice.InvoiceDate
SourceDocument"PurchaseInvoice"Fixed
SourceDocumentId[Invoice Header GUID]Invoice.Id
TotalDebit6900.00Sum debits
TotalCredit6900.00Balanced totals
FinancialYearPeriodId[Period ID]Invoice.FinancialYearPeriodId
CreatedDate2026-01-28 23:10:00Audit

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 = 44

Summary 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

  1. User submits Add Purchase Invoice request

  2. AddPurchaseInvoiceCommandHandler.Handle()

  3. Validate invoice date → Get FinancialYearPeriodId

  4. Validate vendor accounting setup, tax metadata, UOM & item existence

  5. Generate sequence code → "PI-2026-0001"

  6. Calculate line totals and header totals

  7. Create payment term balances (DueDate(s), DueAmount(s))

  8. INSERT PurchaseInvoiceHeader (Status = Draft)
    INSERT PurchaseInvoiceDetail (line(s))
    INSERT PurchaseInvoiceTracking (if present)
    INSERT PurchaseInvoiceBalance (installment(s))

  9. SaveChanges → Database transaction committed (Draft returned)

  10. User triggers Post request

  11. PostPurchaseInvoiceCommandHandler.Handle()

  12. Validate preconditions (status, period, vendor accounts, tax GL)

  13. Build purchase journal payload and call Accounting service → returns JournalId/Code

    • Accounting creates JournalEntry and JournalEntryLines
  14. Call Inventory service AddPostedStockIn() → returns StockInId/Code and Inventory Journal references

    • Inventory creates StockInHeader, StockInDetail, StockInTracking, updates ItemStockDetail
  15. Update PurchaseInvoiceHeader with StockIn and Journal references, set Status = Posted, set PostedDate

  16. Update VendorFinancial (Outstanding payable)

  17. SaveChanges → All references persisted

  18. Publish webhook/event (if enabled)

  19. 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)

Internal Documentation — Microtec