Skip to content

Sales Invoice Complete Walkthrough - Example-Based Documentation

Overview

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


Example Payload

json

{

  "invoiceDate": "2026-01-28T16:14:36",

  "invoiceDueDate": "2026-01-28T14:14:36.711Z",

  "description": "",

  "warehouseId": 48,

  "warehouseName": "Default Warehouse 1",

  "customerId": 433,

  "customerName": "dubai ",

  "customerCreditLimit": 0,

  "pricePolicyId": 42,

  "currencyId": 4,

  "currencyName": "Saudi Riyal",

  "currencyNameAr": "ريال سعودي",

  "currencyRate": 1,

  "paymentTermId": 38,

  "isLinkedToAdvancedPayment": false,

  "sourceType": "Direct",

  "salesInvoiceDetails": [

    {

      "itemId": 422,

      "itemCode": "IDEF_00004",

      "itemName": "وشاح",

      "itemVariantId": 543,

      "categoryId": 109,

      "categoryType": "Storable",

      "isServiceItem": false,

      "uomId": "79415be3-345f-4d51-87ac-84ec87e7a0b0",

      "uomCode": "G8roW",

      "quantity": "1",

      "price": 1000,

      "cost": 99.63636609,

      "costCenterId": 87,

      "costCenterName": "مركز تكلفة 3",

      "discountPercentage": 0,

      "discountAmount": 0,

      "isVatIncluded": false,

      "vatPercentage": 15,

      "taxId": 460,

      "grandTotal": 1150,

      "trackingType": "NoTracking",

      "hasExpiryDate": false,

      "invoiceEntryMode": "Manual"

    }

  ],

  "salesInvoiceBalances": [

    {

      "dueAmount": 1150,

      "dueDate": "2026-01-28",

      "dueBalance": 1150

    }

  ]

}

Phase 1: Add Sales Invoice (Draft)

Handler: AddSalesInvoiceCommandHandler

Action: Creates draft invoice, no inventory or GL impact

Calculations (Before Saving)


Line Item Calculations:

  Quantity × Price = Net Amount

  1 × 1000 = 1000 SAR

  

  VAT Amount (if not included):

  1000 × 15% = 150 SAR

  

  Grand Total:

  1000 + 150 = 1150 SAR

  

Header Totals:

  TotalNetAmount = 1000 SAR

  TotalDiscount = 0 SAR

  TotalAfterDiscount = 1000 SAR

  GrandTotal = 1150 SAR

  LocalGrandTotal = 1150 SAR (currencyRate = 1)

Table 1: SalesInvoiceHeader (INSERT)

Operation: INSERT new record

| Column Name | Value | Source | Notes |

|------------|-------|--------|-------|

| Id | [Auto-Generated GUID] | System | e.g., 3fa85f64-5717-4562-b3fc-2c963f66afa6 |

| Code | "SI-2026-0001" | Sequence Service | Generated by UpdateLastSequenceCommand |

| InvoiceDate | 2026-01-28 16:14:36 | Payload | Transaction date |

| InvoiceDueDate | 2026-01-28 | Payment Term Calculation | Last installment date from payment term |

| Description | "" (empty) | Payload | Optional notes |

| Status | "Draft" | SetAsDraft() | Transaction status enum |

| WarehouseId | 48 | Payload | Source warehouse |

| WarehouseName | "Default Warehouse 1" | Payload | Display name |

| CustomerId | 433 | Payload | Customer identifier |

| CustomerName | "dubai " | Payload | Customer display name |

| CustomerCreditLimit | 0 | Payload | Credit limit for validation |

| PricePolicyId | 42 | Payload | Active price policy |

| CurrencyId | 4 | Payload | Transaction currency |

| CurrencyName | "Saudi Riyal" | Payload | Currency display name |

| CurrencyNameAr | "ريال سعودي" | Payload | Arabic currency name |

| CurrencyRate | 1.0 | Payload | Exchange rate to base currency |

| PaymentTermId | 38 | Payload | Payment schedule template |

| SalesManId | NULL | Payload (null) | No salesman assigned |

| SalesManName | NULL | Payload (empty) | No salesman name |

| FinancialYearPeriodId | [Period ID] | GetCurrentPeriodId() | e.g., 25 for Jan 2026 period |

| SourceType | "Direct" | Payload | SalesSourceType enum |

| ApplicationSource | "ERP" | Default | Not POS or Mobile |

| TotalDiscount | 0 | CalculateHeaderTotals() | Sum of line discounts |

| LocalTotalDiscount | 0 | × CurrencyRate | Base currency discount |

| TotalNetAmount | 1000 | CalculateHeaderTotals() | Sum of line net amounts |

| TotalAfterDiscount | 1000 | CalculateHeaderTotals() | Net - Discount |

| LocalTotalAfterDiscount | 1000 | × CurrencyRate | Base currency total |

| GrandTotal | 1150 | CalculateHeaderTotals() | Total + VAT |

| LocalGrandTotal | 1150 | × CurrencyRate | Base currency grand total |

| IsLinkedToAdvancedPayment | false | Payload | No advance payment |

| ReconciledAmount | 0 | Default | No reconciliation yet |

| AmountToReconcile | 1150 | SetAmountToReconcile() | Full amount outstanding |

| IsFullyReconciled | false | Computed | Not reconciled |

| ZatcaStatus | "NotSend" | SetZatcaStatusAsNotSend() | Saudi E-Invoicing status |

| ZatcaErrorMessage | NULL | Default | No ZATCA error |

| ZatcaLastTryDate | NULL | Default | No ZATCA attempt yet |

| PrintCount | 0 | Default | Not printed |

| StockOutId | NULL | Draft state | Set on post |

| StockOutCode | NULL | Draft state | Set on post |

| InvoiceJournalId | NULL | Draft state | Set on post |

| InvoiceJournalCode | NULL | Draft state | Set on post |

| StockOutJournalId | NULL | Draft state | Set on post |

| StockOutJournalCode | NULL | Draft state | Set on post |

| ExternalIdentifier | NULL | Not external | External system ref |

| ExternalCode | NULL | Not external | External invoice code |

| POSSessionId | NULL | Not POS | POS session ref |

| BranchId | NULL | Not POS | Branch ref |

| CreatedDate | [Current Timestamp] | Audit | Auto-set |

| CreatedBy | [Current User ID] | Security | From ISecurityService |

| TenantId | [Tenant GUID] | Multi-tenancy | From ISecurityService |

| IsDeleted | false | Default | Soft delete flag |


Table 2: SalesInvoiceDetail (INSERT)

Operation: INSERT new record (one per line item)

| Column Name | Value | Source | Notes |

|------------|-------|--------|-------|

| Id | [Auto-Generated GUID] | System | e.g., 7a93c8e2-1234-5678-9abc-def012345678 |

| SalesInvoiceHeaderId | [Header GUID from above] | FK | Parent invoice |

| ItemId | 422 | Payload | Inventory item |

| ItemCode | "IDEF_00004" | Payload | Item code |

| ItemName | "وشاح" | Payload | Item name (scarf) |

| ItemVariantId | 543 | Payload | Item variant |

| ItemVariantCode | "0000" | Payload | Default variant |

| ItemVariantNameEn | "default category for inventory items" | Payload | Variant name |

| ItemVariantNameAr | "-" | Payload | Arabic variant name |

| CategoryId | 109 | Payload | Item category |

| ItemCategoryNameEn | "default category for inventory items" | Payload | Category name |

| ItemCategoryNameAr | "الفئة الافتراضية للاصناف المخزنية" | Payload | Arabic category |

| CategoryType | "Storable" | Payload | CategoryType enum |

| IsServiceItem | false | Payload / Computed | Not a service |

| IsProductItem | true | Computed | Storable item |

| UomId | 79415be3-345f-4d51-87ac-84ec87e7a0b0 | Payload | Unit of measure |

| UOMCode | "G8roW" | Payload | UOM code |

| UOMNameEn | "BaseUOM" | Payload | UOM name |

| UOMNameAr | "وحدة القياس الأساسية" | Payload | Arabic UOM |

| Quantity | 1.0 | Payload | Quantity sold |

| Price | 1000.00 | Payload | Unit price (SAR) |

| SubPrice | 1000.00 | Computed | Price in base UOM |

| Cost | 99.64 | Payload | Unit cost (COGS) |

| SubCost | 99.64 | Computed | Cost in base UOM |

| DiscountPercentage | 0 | Payload | No discount |

| DiscountAmount | 0 | CalculateAllFields() | Discount amount |

| LocalDiscountAmount | 0 | × CurrencyRate | Base currency discount |

| NetAmount | 1000.00 | CalculateAllFields() | Qty × Price |

| LocalNetAmount | 1000.00 | × CurrencyRate | Base currency net |

| TotalAfterDiscount | 1000.00 | CalculateAllFields() | Net - Discount |

| LocalTotalAfterDiscount | 1000.00 | × CurrencyRate | Base currency total |

| IsVatIncluded | false | Payload | VAT not in price |

| TaxId | 460 | Payload | VAT/Tax definition |

| VatPercentage | 15.0 | Payload | 15% VAT |

| VatAmount | 150.00 | CalculateAllFields() | 1000 × 15% |

| LocalVatAmount | 150.00 | × CurrencyRate | Base currency VAT |

| GrandTotal | 1150.00 | CalculateAllFields() | Total + VAT |

| LocalGrandTotal | 1150.00 | × CurrencyRate | Base currency grand |

| BarCode | "" (empty) | Payload | No barcode used |

| BarCodeId | NULL | Payload | No barcode |

| TrackingType | "NoTracking" | Payload | No tracking |

| HasExpiryDate | false | Payload | No expiry |

| InvoiceEntryMode | "Manual" | Payload | Entry mode enum |

| CostCenterId | 87 | Payload | Cost center |

| CostCenterName | "مركز تكلفة 3" | Payload | Cost center name |

| CostCenterNameAr | "مركز تكلفة 3" | Payload | Arabic cost center |

| RemainingServiceQuantity | 0 | Not service | Only for services |

| Notes | "" (empty) | Payload | Line notes |

| Description | "(IDEF_00004) وشاح--" | Payload | Line description |

| SourceId | NULL | Not from SO | Sales order ID |

| SourceDetailId | NULL | Not from SO | Sales order detail |

| ExternalIdentifier | NULL | Not external | External ref |

| ItemStockBatchHeaderId | NULL | NoTracking | Batch header |

| PricePolicyDetailId | 498 | Price policy | Price policy detail |

| CreatedDate | [Current Timestamp] | Audit | Auto-set |

| CreatedBy | [Current User ID] | Security | From ISecurityService |

| TenantId | [Tenant GUID] | Multi-tenancy | From ISecurityService |


Table 3: SalesInvoiceTracking (INSERT)

Operation: INSERT new record

| Column Name | Value | Source | Notes |

|------------|-------|--------|-------|

| Id | [Auto-Generated GUID] | System | e.g., e0f0ee32-0f31-41c4-b1ca-d20f79eac9fd |

| SalesInvoiceDetailId | [Invoice Detail GUID] | FK | Parent detail line |

| TrackingNo | NULL | N/A | No tracking number for NoTracking |

| Quantity | 1.0 | Detail.Quantity | Quantity tracked |

| ExpireDate | NULL | N/A | No expiry date |

| HasExpiryDate | false | Detail.HasExpiryDate | No expiry tracking |

| TrackingType | "NoTracking" | Detail.TrackingType | Tracking type enum |

| CreatedDate | 2026-01-28 16:15:03 | Audit | Timestamp |

| CreatedBy | [Current User ID] | Security | User who created |

| TenantId | [Tenant GUID] | Multi-tenancy | Tenant isolation |

Tracking Record Behavior:

  • NoTracking: Tracking record created with NULL TrackingNo, stores quantity

  • NoTracking + Expiry: Tracking record with system-generated batch and expiry date

  • Serial: One record per serial number, Quantity = 1 each

  • Batch: One or more records per batch number with quantities


Table 4: SalesInvoiceBalance (INSERT)

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

Note: Payment Term ID 38 is used. Assuming it has one installment (Net 0 - immediate payment).

| Column Name | Value | Source | Notes |

|------------|-------|--------|-------|

| Id | [Auto-Generated GUID] | System | e.g., 9b12f5a3-2345-6789-bcde-f012345678ab |

| SalesInvoiceHeaderId | [Header GUID] | FK | Parent invoice |

| DueDate | 2026-01-28 | Payment Term | From payment term calculation |

| DueAmount | 1150.00 | Payment Term % | 100% of grand total |

| LocalDueAmount | 1150.00 | × CurrencyRate | Base currency due |

| DueBalance | 1150.00 | Initially = DueAmount | Outstanding balance |

| LocalDueBalance | 1150.00 | Initially = LocalDueAmount | Base currency outstanding |

| CreatedDate | [Current Timestamp] | Audit | Auto-set |

| CreatedBy | [Current User ID] | Security | From ISecurityService |

| TenantId | [Tenant GUID] | Multi-tenancy | From ISecurityService |

Payment Term Logic:

If payment term has multiple installments (e.g., 30% in 10 days, 70% in 30 days), multiple balance records are created:


Balance 1: DueAmount = 345 (30%), DueDate = 2026-02-07

Balance 2: DueAmount = 805 (70%), DueDate = 2026-02-27

Table 5: SalesInvoiceSource (NO INSERT)

Operation: NONE - Direct sale (not from Sales Order)

Reason: SourceType = "Direct", no Sales Order references

When created:

  • SourceType = "SalesOrder"

  • Links invoice to originating sales order(s)


Summary of Add Phase (Draft)

Tables Affected:

  1. SalesInvoiceHeader - 1 INSERT

  2. SalesInvoiceDetail - 1 INSERT

  3. SalesInvoiceTracking - 1 INSERT (even for NoTracking, stores quantity)

  4. SalesInvoiceBalance - 1 INSERT

  5. SalesInvoiceSource - 0 records (Direct sale)

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 (FinancialYearPeriodId = 25)

✅ Customer credit limit check (0 limit, passed)

✅ No overdue unpaid invoices for customer (if enabled)

✅ Cost center provided (87) as required by accounting settings

✅ Price matches price policy (PricePolicyDetailId = 498)


Phase 2: Post Sales Invoice

Handler: PostSalesInvoiceCommandHandler

Action: Finalizes invoice, creates inventory and GL transactions

Prerequisites Check

Before posting, system validates:

  1. ✅ Invoice Status = Draft

  2. ✅ Period allows posted journals (Jan 2026 period open)

  3. ✅ Customer has receivable account configured

  4. ✅ Item 422 has revenue account configured

  5. ✅ Tax 460 has GL account configured

  6. ✅ COGS account exists in inventory settings

  7. ✅ Warehouse 48 has available stock (Quantity ≥ 1 for Item 422/Variant 543)


Table 1: SalesInvoiceHeader (UPDATE)

Operation: UPDATE existing record

| Column Name | Before (Draft) | After (Posted) | Change Description |

|------------|----------------|----------------|-------------------|

| Status | "Draft" | "Posted" | Status changed to Posted |

| StockOutId | NULL | [StockOut GUID] | Reference to stock out transaction |

| StockOutCode | NULL | "SO-2026-0001" | Stock out sequential code |

| InvoiceJournalId | NULL | [Journal GUID] | Sales journal entry ID |

| InvoiceJournalCode | NULL | "JE-2026-0123" | Journal entry code |

| StockOutJournalId | NULL | [Journal GUID] | COGS journal entry ID |

| StockOutJournalCode | NULL | "JE-2026-0124" | COGS journal code |

| PostedDate | NULL | 2026-01-28 16:20:00 | Timestamp of posting |

| UpdatedDate | [Original] | 2026-01-28 16:20:00 | Last update timestamp |

| UpdatedBy | [Original] | [Current User ID] | Updated by user |

All other columns remain unchanged


Table 2: StockOutHeader (INSERT - Inventory Module)

Operation: INSERT new record

Created By: IInventoryPublicApi.AddPostedStockOut()

| Column Name | Value | Source | Notes |

|------------|-------|--------|-------|

| Id | [Auto-Generated GUID] | System | e.g., 4d82b6c1-3456-7890-cdef-0123456789bc |

| Code | "SO-2026-0001" | Sequence Service | Stock out sequential code |

| TransactionDate | 2026-01-28 16:14:36 | Invoice.InvoiceDate | Transaction date |

| TransactionType | "StockOut" | Fixed | Inventory decrease |

| WarehouseId | 48 | Invoice.WarehouseId | Issuing warehouse |

| WarehouseName | "Default Warehouse 1" | Invoice.WarehouseName | Display name |

| SourceDocumentType | "SalesInvoice" | Fixed | Source document enum |

| SourceDocumentId | [Invoice Header GUID] | Invoice.Id | Reference to invoice |

| SourceDocumentCode | "SI-2026-0001" | Invoice.Code | Invoice code |

| Status | "Posted" | Fixed | Always posted |

| TotalCost | 99.64 | Sum of detail costs | Total COGS |

| JournalId | [Journal GUID] | Created journal | COGS journal ID |

| JournalCode | "JE-2026-0124" | Created journal | COGS journal code |

| CreatedDate | 2026-01-28 16:20:00 | Audit | Posting timestamp |

| CreatedBy | [Current User ID] | Security | Posting user |

| TenantId | [Tenant GUID] | Multi-tenancy | Tenant isolation |


Table 3: StockOutDetail (INSERT - Inventory Module)

Operation: INSERT new record (one per inventory line)

Created By: IInventoryPublicApi.AddPostedStockOut()

| Column Name | Value | Source | Notes |

|------------|-------|--------|-------|

| Id | [Auto-Generated GUID] | System | e.g., 5e93c7d2-4567-8901-def0-123456789cde |

| StockOutHeaderId | [StockOut Header GUID] | FK | Parent stock out |

| ItemId | 422 | Detail.ItemId | Inventory item |

| ItemCode | "IDEF_00004" | Detail.ItemCode | Item code |

| ItemName | "وشاح" | Detail.ItemName | Item name |

| ItemVariantId | 543 | Detail.ItemVariantId | Item variant |

| ItemVariantCode | "0000" | Detail.ItemVariantCode | Variant code |

| UomId | 79415be3-345f-4d51-87ac-84ec87e7a0b0 | Detail.UomId | Unit of measure |

| UOMCode | "G8roW" | Detail.UOMCode | UOM code |

| Quantity | 1.0 | Detail.Quantity | Quantity issued |

| UnitCost | 99.64 | Weighted Avg Cost | COGS per unit |

| LineCost | 99.64 | Quantity × UnitCost | Total line COGS |

| TrackingType | "NoTracking" | Detail.TrackingType | Tracking method |

| HasExpiryDate | false | Detail.HasExpiryDate | Expiry flag |

| SourceDetailId | [Invoice Detail GUID] | Detail.Id | Link to invoice line |

| SourceDetailCode | [Generated] | System | Reference code |

| CreatedDate | 2026-01-28 16:20:00 | Audit | Posting timestamp |

| CreatedBy | [Current User ID] | Security | Posting user |

| TenantId | [Tenant GUID] | Multi-tenancy | Tenant isolation |

COGS Calculation:

  • System uses Weighted Average Cost from ItemStockBatchHeader

  • For Item 422, Variant 543, Warehouse 48

  • Current available stock: Quantity = 10, Total Cost = 996.36, WAC = 99.64


Table 4: StockOutTracking (INSERT)

Operation: INSERT new record

Created By: IInventoryPublicApi.AddPostedStockOut()

Note: Even for NoTracking items, a tracking record is created to store the quantity

| Column Name | Value | Source | Notes |

|------------|-------|--------|-------|

| Id | [Auto-Generated GUID] | System | Tracking record ID |

| StockOutDetailId | [StockOut Detail GUID] | FK | Parent stock out detail |

| TrackingNo | NULL | N/A | No tracking number for NoTracking |

| Quantity | 1.0 | StockOutDetail.Quantity | Quantity issued |

| ExpireDate | NULL | N/A | No expiry date |

| UnitCost | 99.64 | Weighted Average Cost | COGS per unit |

| CreatedDate | 2026-01-28 16:20:00 | Audit | Timestamp |

| CreatedBy | [Current User ID] | Security | Posting user |

| TenantId | [Tenant GUID] | Multi-tenancy | Tenant isolation |

Tracking Record Behavior:

  • NoTracking: Tracking record created with NULL TrackingNo, stores quantity and cost

  • Serial: One record per serial number issued, Quantity = 1 each

  • Batch: One or more records per batch number with quantities

  • Expiry: One record per expiry batch issued with expiry date


Table 5: ItemStockDetail (UPDATE - Inventory Module)

Operation: UPDATE existing record

Mechanism: Reduces AvailableQuantity

Before Post:

sql

ItemId: 422

ItemVariantId: 543

WarehouseId: 48

TrackingNo: NULL (NoTracking)

TrackingType: NoTracking

HasExpiryDate: false

Quantity: 10.0

AvailableQuantity: 10.0

TotalCost: 996.36

UnitCost: 99.64 (Weighted Average)

After Post:

sql

ItemId: 422

ItemVariantId: 543

WarehouseId: 48

TrackingNo: NULL (NoTracking)

TrackingType: NoTracking

HasExpiryDate: false

Quantity: 10.0              -- Unchanged (historical record)

AvailableQuantity: 9.0      -- Reduced by 1

TotalCost: 996.36           -- Unchanged

UnitCost: 99.64             -- Unchanged

Update Logic:

sql

UPDATE ItemStockDetail

SET AvailableQuantity = AvailableQuantity - 1.0

WHERE ItemVariantId = 543

  AND WarehouseId = 48

  AND TrackingType = 'NoTracking'

  AND HasExpiryDate = false

Table 6: JournalEntry (INSERT #1 - Sales Journal)

Operation: INSERT new record

Created By: IAccountingPublicApi.AddJournal()

Purpose: Record sales revenue and receivable

| Column Name | Value | Source | Notes |

|------------|-------|--------|-------|

| Id | [Auto-Generated GUID] | System | e.g., 6f04d8e3-5678-9012-ef01-23456789def0 |

| Code | "JE-2026-0123" | Sequence Service | Journal entry code |

| JournalDate | 2026-01-28 16:14:36 | Invoice.InvoiceDate | Transaction date |

| SourceDocument | "SalesInvoice" | Fixed | SourceDocument enum |

| SourceDocumentId | [Invoice Header GUID] | Invoice.Id | Link to invoice |

| SourceDocumentCode | "SI-2026-0001" | Invoice.Code | Invoice reference |

| RefrenceNumber | "SI-2026-0001" | Invoice.Code | Reference |

| Type | "Sales" | Fixed | JournalEntryType enum |

| Status | "Posted" | Fixed | Always posted |

| Description | "" | Invoice.Description | Journal description |

| TotalDebit | 1150.00 | Sum of debits | Total debits |

| TotalCredit | 1150.00 | Sum of credits | Total credits (balanced) |

| FinancialYearPeriodId | 25 | Invoice.FinancialYearPeriodId | Accounting period |

| CreatedDate | 2026-01-28 16:20:00 | Audit | Posting timestamp |

| CreatedBy | [Current User ID] | Security | Posting user |

| TenantId | [Tenant GUID] | Multi-tenancy | Tenant isolation |


Table 7: JournalEntryLine (INSERT Multiple - Sales Journal)

Operation: INSERT 3 records

Created By: IAccountingPublicApi.AddJournal()

Line 1: Accounts Receivable (Debit)

| Column Name | Value | Source | Notes |

|------------|-------|--------|-------|

| Id | [Auto-Generated GUID] | System | Line 1 ID |

| JournalEntryId | [Journal GUID above] | FK | Parent journal |

| AccountId | 1010 | Customer.ReceivableAccountId | AR account |

| AccountCode | "1010" | Account master | Account code |

| AccountName | "Accounts Receivable" | Account master | Account name |

| AccountNameAr | "العملاء" | Account master | Arabic name |

| DebitAmount | 1150.00 | Invoice.LocalGrandTotal | Amount due from customer |

| CreditAmount | 0.00 | N/A | No credit |

| LineDescription | "" | Invoice.Description | Line description |

| CurrencyId | 4 | Base Currency | Saudi Riyal |

| CurrencyRate | 1.0 | Always 1 for base | Base currency rate |

| CostCenterId | NULL | N/A | No cost center for AR |

| CreatedDate | 2026-01-28 16:20:00 | Audit | Posting timestamp |

Line 2: VAT Output (Credit)

| Column Name | Value | Source | Notes |

|------------|-------|--------|-------|

| Id | [Auto-Generated GUID] | System | Line 2 ID |

| JournalEntryId | [Journal GUID above] | FK | Parent journal |

| AccountId | 2030 | Tax.AccountId | VAT payable account |

| AccountCode | "2030" | Account master | Account code |

| AccountName | "VAT Output" | Account master | Account name |

| AccountNameAr | "ضريبة المخرجات" | Account master | Arabic name |

| DebitAmount | 0.00 | N/A | No debit |

| CreditAmount | 150.00 | Detail.LocalVatAmount | VAT collected |

| LineDescription | "Total VAT" | Localized string | VAT line description |

| CurrencyId | 4 | Base Currency | Saudi Riyal |

| CurrencyRate | 1.0 | Always 1 | Base currency rate |

| CostCenterId | NULL | N/A | No cost center |

| CreatedDate | 2026-01-28 16:20:00 | Audit | Posting timestamp |

Line 3: Sales Revenue (Credit)

| Column Name | Value | Source | Notes |

|------------|-------|--------|-------|

| Id | [Auto-Generated GUID] | System | Line 3 ID |

| JournalEntryId | [Journal GUID above] | FK | Parent journal |

| AccountId | 4010 | Item.RevenueAccountId | Sales revenue account |

| AccountCode | "4010" | Account master | Account code |

| AccountName | "Sales Revenue" | Account master | Account name |

| AccountNameAr | "إيرادات المبيعات" | Account master | Arabic name |

| DebitAmount | 0.00 | N/A | No debit |

| CreditAmount | 1000.00 | Detail.LocalTotalAfterDiscount | Sales revenue |

| LineDescription | "(IDEF_00004) وشاح--" | Detail.Description | Item description |

| CurrencyId | 4 | Base Currency | Saudi Riyal |

| CurrencyRate | 1.0 | Always 1 | Base currency rate |

| CostCenterId | 87 | Detail.CostCenterId | Cost center tracking |

| CostCenterPercentage | 100 | Default | 100% allocation |

| CreatedDate | 2026-01-28 16:20:00 | Audit | Posting timestamp |

Sales Journal Summary:


JE-2026-0123 - Sales Journal

  DR  1010  Accounts Receivable       1,150.00

      CR  2030  VAT Output                       150.00

      CR  4010  Sales Revenue                  1,000.00

                                     ---------  ---------

      Total                          1,150.00   1,150.00

Table 8: JournalEntry (INSERT #2 - COGS Journal)

Operation: INSERT new record

Created By: IInventoryPublicApi.AddPostedStockOut() (Inventory Module)

Purpose: Record cost of goods sold and inventory reduction

| Column Name | Value | Source | Notes |

|------------|-------|--------|-------|

| Id | [Auto-Generated GUID] | System | e.g., 7g15e9f4-6789-0123-f012-3456789def01 |

| Code | "JE-2026-0124" | Sequence Service | Journal entry code |

| JournalDate | 2026-01-28 16:14:36 | Invoice.InvoiceDate | Transaction date |

| SourceDocument | "StockOut" | Fixed | SourceDocument enum |

| SourceDocumentId | [StockOut Header GUID] | StockOut.Id | Link to stock out |

| SourceDocumentCode | "SO-2026-0001" | StockOut.Code | Stock out reference |

| RefrenceNumber | "SO-2026-0001" | StockOut.Code | Reference |

| Type | "Inventory" | Fixed | JournalEntryType enum |

| Status | "Posted" | Fixed | Always posted |

| Description | "COGS for SI-2026-0001" | Generated | COGS description |

| TotalDebit | 99.64 | Sum of debits | Total debits |

| TotalCredit | 99.64 | Sum of credits | Total credits (balanced) |

| FinancialYearPeriodId | 25 | StockOut.FinancialYearPeriodId | Accounting period |

| CreatedDate | 2026-01-28 16:20:00 | Audit | Posting timestamp |

| CreatedBy | [Current User ID] | Security | Posting user |

| TenantId | [Tenant GUID] | Multi-tenancy | Tenant isolation |


Table 9: JournalEntryLine (INSERT Multiple - COGS Journal)

Operation: INSERT 2 records

Created By: Inventory Module

Line 1: Cost of Goods Sold (Debit)

| Column Name | Value | Source | Notes |

|------------|-------|--------|-------|

| Id | [Auto-Generated GUID] | System | Line 1 ID |

| JournalEntryId | [COGS Journal GUID] | FK | Parent journal |

| AccountId | 5010 | InventorySettings.CostOfGoodsSoldAccountId | COGS expense account |

| AccountCode | "5010" | Account master | Account code |

| AccountName | "Cost of Goods Sold" | Account master | Account name |

| AccountNameAr | "تكلفة البضاعة المباعة" | Account master | Arabic name |

| DebitAmount | 99.64 | StockOutDetail.LineCost | Expense recognition |

| CreditAmount | 0.00 | N/A | No credit |

| LineDescription | "COGS - وشاح" | Generated | Item name |

| CurrencyId | 4 | Base Currency | Saudi Riyal |

| CurrencyRate | 1.0 | Always 1 | Base currency rate |

| CostCenterId | 87 | Detail.CostCenterId | Cost center tracking |

| CostCenterPercentage | 100 | Default | 100% allocation |

| CreatedDate | 2026-01-28 16:20:00 | Audit | Posting timestamp |

Line 2: Inventory Asset (Credit)

| Column Name | Value | Source | Notes |

|------------|-------|--------|-------|

| Id | [Auto-Generated GUID] | System | Line 2 ID |

| JournalEntryId | [COGS Journal GUID] | FK | Parent journal |

| AccountId | 1030 | InventorySettings.InventoryAssetAccountId | Inventory asset account |

| AccountCode | "1030" | Account master | Account code |

| AccountName | "Inventory" | Account master | Account name |

| AccountNameAr | "المخزون" | Account master | Arabic name |

| DebitAmount | 0.00 | N/A | No debit |

| CreditAmount | 99.64 | StockOutDetail.LineCost | Inventory reduction |

| LineDescription | "Stock out - وشاح" | Generated | Item name |

| CurrencyId | 4 | Base Currency | Saudi Riyal |

| CurrencyRate | 1.0 | Always 1 | Base currency rate |

| CostCenterId | NULL | N/A | No cost center for asset |

| CreatedDate | 2026-01-28 16:20:00 | Audit | Posting timestamp |

COGS Journal Summary:


JE-2026-0124 - COGS Journal

  DR  5010  Cost of Goods Sold           99.64

      CR  1030  Inventory                        99.64

                                        ------  ------

      Total                              99.64   99.64

Table 10: CustomerFinancial (UPDATE)

Operation: UPDATE existing record

Before Post:

sql

CustomerId: 433

CustomerName: "dubai"

TotalSales: 0.00

TotalReturns: 0.00

OutstandingBalance: 0.00

CreditLimit: 0.00

AvailableCredit: 0.00

After Post:

sql

CustomerId: 433

CustomerName: "dubai"

TotalSales: 1150.00              -- += Invoice.LocalGrandTotal

TotalReturns: 0.00               -- Unchanged

OutstandingBalance: 1150.00      -- += Invoice.LocalGrandTotal

CreditLimit: 0.00                -- Unchanged

AvailableCredit: -1150.00        -- CreditLimit - OutstandingBalance

Update SQL:

sql

UPDATE CustomerFinancial

SET TotalSales = TotalSales + 1150.00,

    OutstandingBalance = OutstandingBalance + 1150.00,

    AvailableCredit = CreditLimit - (OutstandingBalance + 1150.00)

WHERE CustomerId = 433

Table 11: SalesMan (NO UPDATE)

Operation: NONE - No salesman assigned

Reason: SalesManId is NULL in the invoice

When updated:

  • If SalesManId is provided, updates salesman's TotalSales and OutstandingBalance

Summary of Post Phase

Tables Affected:

  1. SalesInvoiceHeader - 1 UPDATE (status, journal IDs)

  2. StockOutHeader - 1 INSERT (inventory transaction)

  3. StockOutDetail - 1 INSERT (line detail)

  4. StockOutTracking - 1 INSERT (even for NoTracking, stores quantity and cost)

  5. ItemStockDetail - 1 UPDATE (reduced AvailableQuantity)

  6. JournalEntry - 2 INSERTS (Sales + COGS journals)

  7. JournalEntryLine - 5 INSERTS (3 sales + 2 COGS)

  8. CustomerFinancial - 1 UPDATE (balances)

  9. SalesMan - 0 updates (no salesman)

Inventory Impact:


Item: IDEF_00004 (وشاح)

Warehouse: Default Warehouse 1 (48)

Before: Available Qty = 10

After:  Available Qty = 9

COGS Recognized: 99.64 SAR

GL Impact (Combined Journals):


Chart of Accounts Impact:

  Account 1010 (Accounts Receivable)   DR  1,150.00

  Account 2030 (VAT Output)                CR    150.00

  Account 4010 (Sales Revenue)             CR  1,000.00

  Account 5010 (COGS)                  DR     99.64

  Account 1030 (Inventory)                 CR     99.64

  

Balance Sheet Impact:

  Assets:

    Accounts Receivable  +1,150.00

    Inventory              -99.64

    Net Assets           +1,050.36

  

  Liabilities:

    VAT Payable          +150.00

  

Income Statement Impact:

  Revenue:

    Sales Revenue        +1,000.00

  

  Expenses:

    Cost of Goods Sold     +99.64

  

  Gross Profit           +900.36

Financial Ratios:


Gross Profit Margin = (Revenue - COGS) / Revenue

                    = (1000 - 99.64) / 1000

                    = 90.04%

  

Markup = (Selling Price - Cost) / Cost

       = (1000 - 99.64) / 99.64

       = 903.6%

Complete Transaction Flow Summary

Step-by-Step Execution


1. User submits Add Sales Invoice request

   ↓

2. AddSalesInvoiceCommandHandler.Handle()

   ↓

3. Validate invoice date → Get FinancialYearPeriodId = 25

   ↓

4. Validate price policy → Match price 1000 to PricePolicyDetailId = 498

   ↓

5. Validate cost center activated → Check costCenterId = 87 provided

   ↓

6. Validate customer credit limit → 0 limit, no overdue invoices

   ↓

7. Generate sequence code → "SI-2026-0001"

   ↓

8. Calculate line totals:

   - NetAmount = 1 × 1000 = 1000

   - VatAmount = 1000 × 15% = 150

   - GrandTotal = 1000 + 150 = 1150

   ↓

9. Calculate header totals:

   - TotalNetAmount = 1000

   - TotalAfterDiscount = 1000

   - GrandTotal = 1150

   ↓

10. Create payment term balances:

    - DueDate = 2026-01-28

    - DueAmount = 1150

    ↓

11. INSERT SalesInvoiceHeader (Status = Draft)

    INSERT SalesInvoiceDetail (1 line)

    INSERT SalesInvoiceBalance (1 installment)

    ↓

12. SaveChanges → Database transaction committed

    ↓

13. Return InvoiceId → Frontend receives draft invoice

  

--- DRAFT PHASE COMPLETE ---

  

14. User clicks "Post" button

    ↓

15. PostSalesInvoiceCommandHandler.Handle()

    ↓

16. Validate preconditions:

    - Invoice is Draft ✓

    - Period allows posting ✓

    - Customer has receivable account ✓

    - Item has revenue account ✓

    - Tax has GL account ✓

    - COGS account exists ✓

    - Warehouse has available stock (Qty ≥ 1) ✓

    ↓

17. Create Sales Journal (JE-2026-0123):

    DR  Accounts Receivable  1,150

        CR  VAT Output                 150

        CR  Sales Revenue            1,000

    ↓

18. Call IInventoryPublicApi.AddPostedStockOut():

    - Create StockOutHeader (SO-2026-0001)

    - Create StockOutDetail (Qty 1, Cost 99.64)

    - Update ItemStockDetail (AvailableQty 10 → 9)

    - Create COGS Journal (JE-2026-0124):

      DR  COGS                    99.64

          CR  Inventory                    99.64

    - Return StockOutId, JournalId

    ↓

19. Update SalesInvoiceHeader:

    - Status = Posted

    - StockOutId = [GUID]

    - StockOutCode = "SO-2026-0001"

    - InvoiceJournalId = [GUID]

    - InvoiceJournalCode = "JE-2026-0123"

    - StockOutJournalId = [GUID]

    - StockOutJournalCode = "JE-2026-0124"

    - PostedDate = 2026-01-28 16:20:00

    ↓

20. Update CustomerFinancial:

    - TotalSales += 1150

    - OutstandingBalance += 1150

    ↓

21. SaveChanges → All transactions committed

    ↓

22. Publish webhook event (if enabled)

    ↓

23. Return InvoiceId → Frontend receives posted invoice

  

--- POST PHASE COMPLETE ---

Database Transaction Isolation

Add Phase (Draft)

sql

BEGIN TRANSACTION

  

INSERT INTO SalesInvoiceHeader VALUES (...)

INSERT INTO SalesInvoiceDetail VALUES (...)

INSERT INTO SalesInvoiceBalance VALUES (...)

  

COMMIT TRANSACTION

Duration: ~50ms (single database transaction)

Post Phase

sql

BEGIN TRANSACTION -- Main Transaction

  

-- Step 1: Create Sales Journal

EXEC Accounting.AddJournal

  INSERT INTO JournalEntry VALUES (...)

  INSERT INTO JournalEntryLine VALUES (...) -- 3 lines

  

-- Step 2: Create Stock Out (nested transaction)

EXEC Inventory.AddPostedStockOut

  INSERT INTO StockOutHeader VALUES (...)

  INSERT INTO StockOutDetail VALUES (...)

  UPDATE ItemStockDetail SET AvailableQuantity -= 1

  

  -- Create COGS Journal

  INSERT INTO JournalEntry VALUES (...)

  INSERT INTO JournalEntryLine VALUES (...) -- 2 lines

  

-- Step 3: Update Invoice

UPDATE SalesInvoiceHeader

  SET Status = 'Posted',

      StockOutId = @StockOutId,

      InvoiceJournalId = @JournalId,

      ...

  

-- Step 4: Update Customer

UPDATE CustomerFinancial

  SET TotalSales += 1150,

      OutstandingBalance += 1150

  

COMMIT TRANSACTION

Duration: ~200-500ms (multiple module integrations)


Error Scenarios

Add Phase Validation Failures

| Error Condition | Error Message | Resolution |

|----------------|---------------|------------|

| Invalid Period | "Invoice date must be in an open financial period" | Change invoice date or open period |

| Customer Credit Exceeded | "Grand total exceeds customer credit limit" | Increase credit limit or reduce amount |

| Cost Center Missing | "Cost center required for all invoice details" | Provide cost center ID |

| Price Policy Mismatch | "Price does not match active price policy" | Use price from policy or override |

| Customer Has Overdue | "Customer has outstanding invoices: SI-2026-0001" | Customer must pay overdue invoices |

Post Phase Validation Failures

| Error Condition | Error Message | Resolution |

|----------------|---------------|------------|

| Invoice Not Draft | "Invoice must be in draft status to post" | Cannot post already-posted invoice |

| Insufficient Stock | "Insufficient stock for item IDEF_00004" | Receive more inventory or reduce qty |

| Missing GL Account | "Customer receivable account not configured" | Configure customer accounting |

| Missing Item Account | "Item revenue account not found for item 422" | Configure item GL accounts |

| Missing Tax Account | "Tax definition account required for tax 460" | Configure tax GL account |

| Period Closed | "Cannot post journals in closed period" | Reopen period or change invoice date |


End of Walkthrough

This example demonstrates a typical sales invoice with:

  • ✅ Single storable item (no tracking)

  • ✅ Direct sale (not from sales order)

  • ✅ No discount

  • ✅ VAT not included in price

  • ✅ Single payment installment

  • ✅ Cost center tracking

  • ✅ Weighted average cost method

Document Version: 1.0

Example Date: 2026-01-28

Currency: Saudi Riyal (SAR)

Total Amount: 1,150.00 SAR (including 15% VAT)

Internal Documentation — Microtec