Appearance
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-27Table 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:
✅ SalesInvoiceHeader - 1 INSERT
✅ SalesInvoiceDetail - 1 INSERT
✅ SalesInvoiceTracking - 1 INSERT (even for NoTracking, stores quantity)
✅ SalesInvoiceBalance - 1 INSERT
❌ 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:
✅ Invoice Status = Draft
✅ Period allows posted journals (Jan 2026 period open)
✅ Customer has receivable account configured
✅ Item 422 has revenue account configured
✅ Tax 460 has GL account configured
✅ COGS account exists in inventory settings
✅ 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 -- UnchangedUpdate Logic:
sql
UPDATE ItemStockDetail
SET AvailableQuantity = AvailableQuantity - 1.0
WHERE ItemVariantId = 543
AND WarehouseId = 48
AND TrackingType = 'NoTracking'
AND HasExpiryDate = falseTable 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.00Table 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.64Table 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.00After 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 - OutstandingBalanceUpdate SQL:
sql
UPDATE CustomerFinancial
SET TotalSales = TotalSales + 1150.00,
OutstandingBalance = OutstandingBalance + 1150.00,
AvailableCredit = CreditLimit - (OutstandingBalance + 1150.00)
WHERE CustomerId = 433Table 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:
✅ SalesInvoiceHeader - 1 UPDATE (status, journal IDs)
✅ StockOutHeader - 1 INSERT (inventory transaction)
✅ StockOutDetail - 1 INSERT (line detail)
✅ StockOutTracking - 1 INSERT (even for NoTracking, stores quantity and cost)
✅ ItemStockDetail - 1 UPDATE (reduced AvailableQuantity)
✅ JournalEntry - 2 INSERTS (Sales + COGS journals)
✅ JournalEntryLine - 5 INSERTS (3 sales + 2 COGS)
✅ CustomerFinancial - 1 UPDATE (balances)
❌ 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 SARGL 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.36Financial 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 TRANSACTIONDuration: ~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 TRANSACTIONDuration: ~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)