Skip to content

Purchase Return Invoice Complete Walkthrough - Example-Based Documentation

Overview

This document demonstrates the complete lifecycle of a purchase return invoice using a real payload example, showing exactly which tables and columns are affected during both Add (Draft) and Post phases.

A purchase return invoice (Return Invoice) references a previously-posted purchase invoice and returns some or all of the purchased goods back to the vendor. The system copies item data, costs, taxes, and tracking information from the original invoice — the user only needs to specify which lines to return and how many units.

Example Payload

json
{
  "invoiceHeaderId": "73b9e8d2-fe21-4ef7-2e68-08ddfb5b40cf",
  "returnInvoiceDate": "2026-02-05T10:00:00",
  "description": null,
  "returnInvoiceDetails": [
    {
      "invoiceDetailId": "2b55acb1-180e-48d6-9107-08ddfb5b40dd",
      "toReturnQuantity": 200
    }
  ]
}

Context: This return references the purchase invoice PI-2026-0001 (600 units of "نوت وسط" @ 10 SAR each). The user is returning 200 of those 600 units.


Phase 1: Add Return Invoice (Draft)

Handler: AddReturnInvoiceCommandHandler
Action: Creates draft return invoice — no inventory or GL impact in this phase. Item data, cost, discount, and tax information are copied from the original invoice detail.

Calculations (Before Saving)

Line Item Calculations (copied from original invoice, recalculated for return quantity):

  • OriginalQuantity = 600 (from original invoice detail)

  • ToReturnQuantity = 200 (user input)

  • TransactionRemainQuantity = current available stock checked at creation time

  • SubCost = ToReturnQuantity × Cost
    200 × 10 = 2,000 SAR

  • DiscountAmount = Cost × (DiscountPercentage / 100)
    10 × (0 / 100) = 0 SAR

  • NetAmount = Cost − DiscountAmount
    10 − 0 = 10 SAR

  • TotalAfterDiscount = NetAmount × ToReturnQuantity
    10 × 200 = 2,000 SAR

  • VatAmount (if not included):
    2,000 × 15% = 300 SAR

  • GrandTotal = TotalAfterDiscount + VatAmount
    2,000 + 300 = 2,300 SAR

Header Totals:

  • TotalNetAmount = 2,000 SAR
  • TotalDiscount = 0 SAR
  • TotalAfterDiscount = 2,000 SAR
  • GrandTotal = 2,300 SAR
  • LocalGrandTotal = 2,300 SAR (currencyRate = 1)

Table 1: ReturnInvoiceHeader (INSERT)

Operation: INSERT new record

Column NameValueSourceNotes
Id[Auto-Generated GUID]System
Code"RI-2026-0001"Sequence ServiceGenerated by UpdateLastSequenceCommand
ReturnInvoiceDate2026-02-05 10:00:00PayloadMust be ≥ original invoice date
InvoiceHeaderId73b9e8d2-fe21-4ef7-2e68-08ddfb5b40cfPayloadReference to original purchase invoice
WarehouseId53Copied from InvoiceHeaderSetHeaderData() copies from original
WarehouseName"Default Warehouse"Copied from InvoiceHeader
VendorId44Copied from InvoiceHeader
VendorName"مؤسسة هدية الجودة للدعاية والاعلان"Copied from InvoiceHeader
CurrencyId4Copied from InvoiceHeaderSaudi Riyal
CurrencyRate1.0Copied from InvoiceHeader
PaymentTermId9Copied from InvoiceHeader
Status"Draft"SetAsDraft()Draft status
TotalNetAmount2000.00CalculateHeaderTotals()Sum of detail net amounts
TotalDiscount0.00CalculateHeaderTotals()
TotalAfterDiscount2000.00CalculateHeaderTotals()
GrandTotal2300.00CalculateHeaderTotals()Total + VAT
LocalGrandTotal2300.00CalculateHeaderTotals()GrandTotal × CurrencyRate
FinancialYearPeriodId[Period ID]Period ValidationCurrent open financial period
DescriptionNULLPayload
CreatedDate[Current Timestamp]Audit
CreatedBy[Current User ID]Security
TenantId[Tenant GUID]Multi-tenancy

Table 2: ReturnInvoiceDetail (INSERT)

Operation: INSERT new record (one per return line)

Column NameValueSourceNotes
Id[Auto-Generated GUID]System
ReturnInvoiceHeaderId[Header GUID]FKParent return invoice
InvoiceDetailId2b55acb1-180e-48d6-9107-08ddfb5b40ddPayload / Copied from DetailReference to original invoice line
ItemId169Copied from InvoiceDetailSetDetailData() copies all item info
ItemCode"4137"Copied from InvoiceDetail
ItemName"نوت وسط"Copied from InvoiceDetail
ItemVariantId245Copied from InvoiceDetail
UomId"56835e4d-4593-4481-9839-10a6321a42d3"Copied from InvoiceDetail
ToReturnQuantity200.0PayloadUser-specified return quantity
OriginalQuantity600.0Copied from InvoiceDetailOriginal invoice quantity
TransactionRemainQuantity[Available Qty in Stock]Stock check at creationQuantity available for return at time of creation
Cost10.00Copied from InvoiceDetailUnit cost from original invoice
SubCost2000.00Computed — SetSubCost()ToReturnQuantity × Cost
DiscountPercentage0Copied from InvoiceDetail
DiscountAmount0.00ComputedCost × (DiscountPercentage / 100)
NetAmount10.00ComputedCost − DiscountAmount
TotalAfterDiscount2000.00ComputedNetAmount × ToReturnQuantity
VatPercentage15.0Copied from InvoiceDetail
VatAmount300.00Computed — CalculateTotalVatAmount()Sum of all tax amounts on this line
GrandTotal2300.00Computed — CalculateGrandTotal()TotalAfterDiscount + VatAmount
LocalGrandTotal2300.00ComputedGrandTotal × CurrencyRate
TrackingType"NoTracking"Copied from InvoiceDetail
HasExpiryDatefalseCopied from InvoiceDetail
InvoiceEntryMode"Manual"Copied from InvoiceDetail
CreatedDate[Current Timestamp]Audit
CreatedBy[Current User ID]Security

Table 3: ReturnInvoiceDetailTax (INSERT)

Operation: INSERT tax records (one per tax on the detail line, copied from original invoice detail)

Column NameValueSourceNotes
Id[Auto-Generated GUID]System
ReturnInvoiceDetailId[Detail GUID]FK
TaxId18Copied from InvoiceDetailTax
TaxCalculationEffect"Add"Copied from InvoiceDetailTaxAdd = increases total; Deduct = decreases total
TaxValueType"Percent"Copied from InvoiceDetailTaxPercent or Amount
VatPercentage15.0Copied
VatAmount300.00Computed — CalculateVatAmount()TotalAfterDiscount × VatPercentage
LocalVatAmount300.00ComputedVatAmount × CurrencyRate
CreatedDate[Current Timestamp]Audit

Table 4: ReturnInvoiceTracking (INSERT)

Operation: INSERT when detail.TrackingType = "NoTracking" (a NoTracking record is created to store quantity)

Column NameValueSourceNotes
Id[Auto-Generated GUID]System
ReturnInvoiceDetailId[Detail GUID]FK
TrackingType"NoTracking"Detail.TrackingType
Quantity200.0Detail.ToReturnQuantity
VendorBatchNoNULLNoTracking
ExpireDateNULLNo expiry
HasExpiryDatefalseDetail.HasExpiryDate
CreatedDate[Current Timestamp]Audit

Summary of Add Phase (Draft)

Tables Affected:

  • ✅ ReturnInvoiceHeader — 1 INSERT
  • ✅ ReturnInvoiceDetail — 1 INSERT
  • ✅ ReturnInvoiceDetailTax — 1 INSERT (one per tax on the line)
  • ✅ ReturnInvoiceTracking — 1 INSERT (if NoTracking entry)

Inventory Impact: NONE — No stock movement in draft phase
GL Impact: NONE — No journal entries in draft phase

Key Difference from Purchase Invoice Add:
Unlike adding a purchase invoice (where the user provides all item/cost data), adding a return invoice only requires the original InvoiceDetailId and the return quantity. All item data (ItemId, Cost, DiscountPercentage, VatPercentage, TrackingType, etc.) is copied automatically from the original invoice detail via SetDetailData(). There is also no payment balance table (PurchaseInvoiceBalance) for return invoices.

Validations Performed:

  • ✅ Return invoice date must be ≥ original invoice date
  • ✅ Return invoice date in open financial period
  • ✅ At least one return detail line with ToReturnQuantity > 0
  • ✅ ToReturnQuantity ≤ available quantity in stock (checked per detail)
  • ✅ Original invoice detail exists on the referenced invoice

Phase 2: Post Return Invoice

Handler: PostReturnInvoiceCommandHandler
Action: Finalizes return invoice, creates stock-out (goods leave warehouse back to vendor) and reverse accounting journal entries.

Prerequisites Check (before posting)

  1. ✅ Return Invoice Status = Draft
  2. ✅ ReturnInvoiceDate ≥ original InvoiceDate
  3. ✅ Period allows posted journals
  4. ✅ Vendor has payable account configured
  5. ✅ Item 169 has inventory asset account configured (for storable items)
  6. ✅ Tax 18 has GL account configured

Table 1: ReturnInvoiceHeader (UPDATE)

Operation: UPDATE existing record

Column NameBefore (Draft)After (Posted)Change Description
Status"Draft""Posted"Status changed to Posted via SetAsPosted()
StockOutIdNULL[StockOut GUID]Reference to stock-out transaction
StockOutCodeNULL"SO-2026-0001"Stock-out sequential code (example)
StockOutJournalIdNULL[StockOut Journal GUID]Journal created by Inventory service
StockOutJournalCodeNULL"JE-2026-0201"Stock-out journal code
InvoiceJournalIdNULL[Journal GUID]Purchase return journal entry ID
InvoiceJournalCodeNULL"JE-2026-0202"Purchase return journal entry code
PostedDateNULL2026-02-05 10:05:00Timestamp of posting
UpdatedDate/By[Original][Current]Audit fields

Table 2: StockOutHeader (INSERT - Inventory Module)

Operation: INSERT new record (created by Inventory service via InternalAddPostedStockOutCommand)

Column NameValueSourceNotes
Id[Auto-Generated GUID]System
Code"SO-2026-0001"Sequence Service
TransactionDate2026-02-05 10:00:00ReturnInvoice.ReturnInvoiceDate
WarehouseId53ReturnInvoice.WarehouseId
SourceDocumentType"ReturnPurchaseInvoice"FixedIdentifies this is from a purchase return
SourceDocumentId[Return Invoice Header GUID]ReturnInvoice.Id
TotalCost2000.00Sum of detail total after discount
JournalId[StockOut Journal GUID]Inventory journal from Inventory service
CreatedDate2026-02-05 10:05:00Audit

Table 3: StockOutDetail (INSERT - Inventory Module)

Operation: INSERT new record (per inventory line)

Column NameValueSourceNotes
Id[Auto-Generated GUID]System
StockOutHeaderId[StockOut Header GUID]FK
ItemId169Detail.ItemId
ItemCode"4137"Detail.ItemCode
ItemVariantId245Detail.ItemVariantId
UomId"56835e4d-4593-4481-9839-10a6321a42d3"Detail.UomId
Quantity200.0Detail.ToReturnQuantityQuantity leaving the warehouse
UnitCost10.00Detail.Cost
LineCost2000.00Quantity × UnitCost
TrackingType"NoTracking"Detail.TrackingType
SourceDetailId[Return Invoice Detail GUID]Link to return invoice line
CreatedDate2026-02-05 10:05:00Audit

Table 4: StockOutTracking (INSERT - Inventory Module)

Operation: INSERT new record (NoTracking record to store quantity)

Column NameValueSourceNotes
Id[Auto-Generated GUID]System
StockOutDetailId[StockOut Detail GUID]FK
TrackingNoNULLNoTracking
Quantity200.0StockOutDetail.Quantity
UnitCost10.00Unit cost
CreatedDate2026-02-05 10:05:00Audit

Table 5: ItemStockDetail (UPDATE - Inventory Module)

Operation: UPDATE existing stock record (goods leave warehouse)

Before Post (example):

  • AvailableQuantity = 600.0 (all units from original purchase invoice still on hand)

After Post:

  • AvailableQuantity = 400.0 — decreased by 200 (returned to vendor)
  • TotalCost adjusted accordingly

Update Logic (example):

  • Find existing ItemStockDetail for ItemVariantId 245 & WarehouseId 53
  • AvailableQuantity -= 200
  • TotalCost -= 2,000.00 (UnitCost × ReturnQuantity)
  • UnitCost recalculated per costing method (WAC or last cost)

Table 6: JournalEntry (INSERT - Purchase Return Journal)

Operation: INSERT new record (created by Accounting service)

Column NameValueSourceNotes
Id[Auto-Generated GUID]System
Code"JE-2026-0202"Sequence Service
JournalDate2026-02-05 10:00:00ReturnInvoice.ReturnInvoiceDate
SourceDocument"ReturnPurchaseInvoice"Fixed
SourceDocumentId[Return Invoice Header GUID]ReturnInvoice.Id
TotalDebit2300.00Sum debitsBalanced entry
TotalCredit2300.00Sum credits
FinancialYearPeriodId[Period ID]ReturnInvoice.FinancialYearPeriodId
CreatedDate2026-02-05 10:05:00Audit

Table 7: JournalEntryLine (INSERT Multiple - Purchase Return Journal)

Operation: INSERT 3 records (example — 1 vendor line + 1 stock/GIT line + 1 VAT line)

Line 1: Vendor Payable (Debit)

  • AccountId: 2010 (Vendor Payable) — Debit 2,300.00
  • Source: PostReturnInvoiceCommandHandler — Vendor line uses GrandTotal (TotalAfterDiscount + taxes)

Line 2: Inventory / Goods-In-Transit (Credit)

  • AccountId: 1030 (Inventory Asset / GIT) — Credit 2,000.00
  • Source: PostReturnInvoiceCommandHandler — Stock line uses TotalAfterDiscount (excl. VAT)

Line 3: VAT Input (Credit)

  • AccountId: 2040 (VAT Input/Recoverable) — Credit 300.00
  • Source: PostReturnInvoiceCommandHandler — Tax line per tax account; Add-effect taxes are credited (reversing the input VAT originally claimed on the purchase)

Accounting Summary (JE-2026-0202)

  • DR Vendor Payable 2,300.00
  • CR Inventory Asset 2,000.00
  • CR VAT Input 300.00

Note on Tax Lines: Tax lines are grouped by GL account and tax calculation effect. If a tax has TaxCalculationEffect = Add, the line is Credit (reverses the input VAT recovered on the original purchase). If TaxCalculationEffect = Deduct, the line is Debit.

Summary of Post Phase

Tables Affected:

  • ✅ ReturnInvoiceHeader — 1 UPDATE (status, stock-out and journal references)
  • ✅ StockOutHeader — 1 INSERT (inventory transaction created by Inventory service)
  • ✅ StockOutDetail — 1 INSERT (line detail)
  • ✅ StockOutTracking — 1 INSERT (NoTracking record to store quantity)
  • ✅ ItemStockDetail — 1 UPDATE (decreased AvailableQuantity by 200)
  • ✅ JournalEntry — 1 INSERT (Purchase Return journal in Accounting service)
  • ✅ JournalEntryLine — 3 INSERTS (Vendor Payable debit, Inventory credit, VAT credit)

Inventory Impact:

  • Item: 4137 (نوت وسط)
  • Warehouse: Default Warehouse (53)
  • Before: Available Qty = 600
  • After: Available Qty = 400 (200 returned to vendor)

GL Impact (Combined Journals):

  • DR Vendor Payable (Liability) −2,300.00 (reduces outstanding payable to vendor)
  • CR Inventory Asset −2,000.00 (goods leave warehouse)
  • CR VAT Input −300.00 (reverses recoverable VAT from original purchase)

Complete Transaction Flow Summary

Step-by-Step Execution

  1. User submits Add Return Invoice request with InvoiceHeaderId and return details

  2. AddReturnInvoiceCommandHandler.Handle()

  3. Fetch original PurchaseInvoiceHeader (with details, tracking, taxes) by InvoiceHeaderId

  4. Validate return invoice date ≥ original invoice date

  5. Get current FinancialYearPeriodId (validate date is in open period)

  6. Map command to ReturnInvoiceHeader entity; call SetHeaderData() to copy warehouse/vendor/currency/payment term from original invoice

  7. For each ReturnInvoiceDetail:
    a. Fetch original InvoiceDetail by InvoiceDetailId
    b. Call SetDetailData() → copies ItemId, Cost, Discount, VatPercentage, TrackingType, etc.
    c. Set ToReturnQuantity and OriginalQuantity
    d. Check available stock quantity → SetTransactionRemainQuantity()
    e. Validate ToReturnQuantity ≤ available quantity
    f. CalculateAllFields(currencyRate) → SubCost, DiscountAmount, NetAmount, TotalAfterDiscount, VatAmount, GrandTotal
    g. AddTaxesFromInvoice() → copies ReturnInvoiceDetailTax records from original detail taxes

  8. Validate at least one detail line has ToReturnQuantity > 0

  9. CalculateHeaderTotals() → aggregate TotalNetAmount, TotalAfterDiscount, GrandTotal

  10. Generate sequence code → "RI-2026-0001"

  11. SetAsDraft() → Status = Draft

  12. INSERT ReturnInvoiceHeader, ReturnInvoiceDetail(s), ReturnInvoiceDetailTax(es), ReturnInvoiceTracking(s)

  13. SaveChanges → Database transaction committed (Draft returned)

  14. User triggers Post request with ReturnInvoiceHeader Id

  15. PostReturnInvoiceCommandHandler.Handle()

  16. Fetch ReturnInvoiceHeader with all details, taxes, and tracking

  17. Validate preconditions (Status = Draft, date check, vendor payable account, period open)

  18. Build InternalAddPostedStockOutCommand (mapped via AutoMapper from ReturnInvoiceHeader/Detail/Tracking)

  19. Call Inventory service AddPostedStockOut() → returns StockOutId, StockOutCode, StockOutJournalId, StockOutJournalCode

    • Inventory creates StockOutHeader, StockOutDetail, StockOutTracking, decreases ItemStockDetail
  20. Build purchase return journal payload (Vendor debit line, Stock/GIT credit line, Tax lines grouped by account)

  21. Call Accounting service AddJournal() → returns InvoiceJournalId, InvoiceJournalCode

    • Accounting creates JournalEntry and JournalEntryLines
  22. Call SetJournalData() on ReturnInvoiceHeader → stores StockOut and Journal IDs/Codes

  23. SetAsPosted() → Status = Posted, PostedDate = now

  24. SaveChanges → All references persisted

  25. Return ReturnInvoiceId → Frontend receives posted return invoice


Database Transaction Isolation

Add Phase (Draft)

  • Single DB transaction: INSERT header, details, detail taxes, tracking → COMMIT

Post Phase

  • Semaphore lock acquired before processing (prevents duplicate posts)
  • Call Inventory.AddPostedStockOut (external) → returns StockOutId/Code and StockOut Journal references
  • Call Accounting.AddJournal (external) → returns InvoiceJournalId/Code
  • Update ReturnInvoiceHeader with all references, set Status = Posted
  • COMMIT
  • External services (Inventory, Accounting) run their own transactions and return identifiers persisted by the Return Invoice service

Error Scenarios

Add Phase Validation Failures

  • Return Date Before Invoice Date → "Return invoice date must be on or after the original invoice date"
  • Invalid Period → "Invoice date must be in an open financial period"
  • No Valid Details → "At least one return detail with quantity greater than zero is required"
  • Quantity Exceeds Available → "Return quantity exceeds available quantity in stock"
  • Original Invoice Not Found → "Referenced invoice header not found"
  • Original Invoice Detail Not Found → "Invoice detail not found on the referenced invoice"

Post Phase Validation Failures

  • Invoice Not Draft → "Invoice must be in draft status to post"
  • Return Date Before Original Invoice Date → "Return invoice date must be on or after the original invoice date"
  • 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"
  • Stock Out Fails → Inventory service error propagated (stock out not created, header not updated)

Key Differences: Purchase Invoice vs. Return Invoice

AspectPurchase InvoiceReturn Invoice
DirectionGoods IN from vendorGoods OUT back to vendor
Payload contentFull item, cost, tax, and warehouse dataOnly InvoiceHeaderId, InvoiceDetailId, ToReturnQuantity
Item/cost data sourceUser inputCopied automatically from original invoice
Balance tablePurchaseInvoiceBalance (payment installments)None
Inventory operationStockIn (goods arrive at warehouse)StockOut (goods leave warehouse)
Inventory quantityAvailableQuantity += quantityAvailableQuantity −= quantity
GL — Inventory accountDebit (asset increases)Credit (asset decreases)
GL — Vendor PayableCredit (liability increases — we owe more)Debit (liability decreases — we owe less)
GL — VAT InputDebit (recoverable VAT increases)Credit (recoverable VAT decreases)
Code prefixPI-RI-

End of Walkthrough
This example demonstrates a typical purchase return invoice with:

  • ✅ Single storable item (NoTracking) — partial return (200 of 600 units)
  • ✅ References original purchase invoice PI-2026-0001
  • ✅ No discount on line
  • ✅ VAT not included in cost (calculated separately at 15%)
  • ✅ Inventory stock-out on post (200 units removed from warehouse)
  • ✅ Purchase return journal created (Vendor Payable debit, Inventory + VAT Input credit)

Document Version: 1.0
Example Date: 2026-02-05
Currency: Saudi Riyal (SAR)
Total Return Amount: 2,300.00 SAR (including 15% VAT on 200 units @ 10 SAR)

Internal Documentation — Microtec