Appearance
Inventory Opening Balance
| Backlog field | Value |
|---|---|
| Story ID | INV-DASH-018 |
| Epic | Inventory |
| Story type | Dashboard |
| Review status | Draft - business analyst and business owner review required |
| Source evidence | Inventory MFE route/form, Inventory.Apis controller, application command/query handlers, validators, domain entities, and posting services |
| Last source review | 2026-07-10 |
Purpose
Load the starting stock quantities, costs, warehouses, and tracking details when inventory begins operation or is migrated.
User stories
- As an implementation user, I want to enter opening stock by item and warehouse so that the system starts from an agreed balance.
- As an implementation user, I want to capture cost and tracking information so that valuation and traceability begin correctly.
- As an authorized accountant, I want to review and post the opening balance so that it becomes the inventory baseline.
- As an auditor, I want to view and print the source opening-balance document.
Acceptance criteria
- The document has a valid date/period and at least one item line.
- Each line has a valid warehouse, item variant, unit, quantity, and required cost/tracking data.
- Duplicate lines are rejected or consolidated deterministically.
- Tracking details reconcile with line quantities.
- Posting increases opening stock and creates configured opening accounting impact exactly once.
- Posting is blocked when the period is closed or required accounts/settings are missing.
- Posted opening balances are immutable through normal editing.
- Add, edit, view, list, delete, post, print, and sequence actions follow permissions.
Posting details
- Post validates a Draft opening document, allowed date/period, warehouse and item eligibility, positive base quantities, UOM conversions, costs, duplicate handling, and complete batch/serial/expiry allocations.
- The current Inventory opening-balance handler creates stock only: it creates the initial stock headers, quantity and tracking records, valuation layers, and weighted-moving-average/last-purchase cost baseline, then marks the document Posted.
- No Inventory journal is created by the current handler. The acceptance statement about a configured accounting impact is therefore a target/business-policy item, not implemented behavior. Accounting opening balances must be posted separately unless an approved integration is added; the documentation must not imply an automatic debit or credit.
- A posted warehouse opening becomes a prerequisite consumed by later Stock In and related inventory transactions. It establishes inventory availability and valuation but does not itself create customer, vendor, cash, tax, revenue, or expense balances.
- All stock, tracking, valuation, and status changes are atomic and idempotent. A failed Post leaves no partial opening quantities. Correction after posting requires an authorized reversal or adjustment process that preserves the original audit trail.
General ledger posting
| Condition | Debit | Credit | Implemented result |
|---|---|---|---|
| Current Inventory opening-balance Post | - | - | No journal is created by the current handler. |
| Separate Accounting opening process | Policy-defined opening asset/equity accounts | Policy-defined balancing accounts | Must be posted separately; it is not delegated automatically from Inventory. |
Module and data effects
| Affected area | Effect | Conditional/no-effect rule |
|---|---|---|
| Inventory quantity | Establishes opening on-hand/available quantity by warehouse/item. | Failed Post creates no quantity. |
| Valuation | Creates initial layers and WMA/last-purchase cost baseline. | No sales or purchase price effect. |
| Tracking | Establishes opening batch/serial/expiry balances. | Untracked items create no identities. |
| Later Inventory transactions | Satisfies the posted-opening prerequisite for Stock In and related operations. | Does not create a Purchase Invoice or Stock In source document. |
| Accounting/Finance | No automatic GL, receivable, payable, tax, or cash effect. | Separate opening journal is required by Finance policy. |
Core acceptance scenarios
- Given valid Draft opening quantities, costs, UOMs, warehouses, and tracking, when Post is clicked, then stock headers/layers and WMA/last-purchase costs update and the opening balance becomes Posted.
- Given the current implementation, when opening balance posts, then no Inventory journal is created; Accounting opening must be handled separately until Finance confirms another policy.
- Given later Stock In requires warehouse opening, when opening is absent/unposted, then the receipt is blocked.
Required and optional data
- Required: warehouse, opening-balance date, and at least one item line.
- Required per line: item, variant, UOM, positive quantity, and cost.
- Conditional: vendor batch for batch tracking; serial ID for serial tracking; expiry date for expiry-enabled stock; a system batch is used for untracked expiry stock.
- Optional: descriptions/notes where exposed.
- Derived: base UOM, base quantity, UOM cost, fixed cost, subcost, weighted-average cost, and last-purchase cost.
Clicking Post
- Accepts only Draft and revalidates active item/variant/UOM/warehouse references.
- Converts entered quantity and cost to base UOM values.
- Creates or updates item/warehouse and batch/serial stock headers.
- Adds inbound stock-detail history with source type Inventory Opening Balance.
- Increases available quantity and recalculates Weighted Average and Last Purchase costs.
- Marks the opening balance Posted and commits.
GL effect
The current PostInvOpeningBalanceCommandHandler does not create a journal or resolve Warehouse/Adjustment/Opening accounts. This is a confirmed code behavior, not an assumed business rule. Finance must confirm whether the opening GL balance is expected to be loaded separately through Accounting Opening Balance Journals.
Downstream effect
Stock In posting checks that a warehouse opening balance has been posted. Therefore a missing/unposted warehouse opening balance blocks later receipts.
Status, action, and reversal controls
| Action | Required state/authority | Preconditions and affected data | Failure/reversal rule |
|---|---|---|---|
| Add/Save | Opening Add; Draft | Warehouse/date, positive item/UOM quantity, cost, tracking | Draft has no stock baseline |
| Edit/Delete | Draft plus permission | No Posted opening or later stock dependency | Posted baseline is immutable through Draft workflow |
| Post | Draft plus Post | Valid migration scope, duplicates, conversions, cost and tracking | Creates opening quantity/layers/WMA/LPC/tracking and Posted state atomically; current handler creates no GL |
| View/Print | View/print authority | Authorized record | Read-only migration evidence |
| Correct | Approved migration correction process | Later Stock In/Out/transfer/count dependencies assessed | Use controlled reversal/adjustment; separately reconcile Accounting opening because it was not delegated |
Reconciliation controls
| Control | Required equality |
|---|---|
| Opening base quantity | Sum of opening tracking identities |
| Opening value | Quantity x accepted opening cost, subject to precision policy |
| Created inventory baseline | Posted opening details/layers by warehouse and item |
| Inventory GL | No automatic journal in current handler; must reconcile to separate Accounting opening policy |
Frontend page coverage
The following UI coverage is sourced from the pulled MFE route configuration. A Module-level supporting artifact mapping is evidence of a reachable screen, but requires BA confirmation that it belongs to this story rather than a more specific feature story.
| Route/page | Component | Mode | Route permission/guard | Mapping confidence | Source |
|---|---|---|---|---|---|
| (module default) | ListOpeningBalanceComponent | List/navigation | Permission: AppInventoryPermissions.InventoryOpeningBalance_List; Guards: PolicyAuthGuard | Explicit frontend feature rule | apps/inventory/src/app/remote-entry/transactions/modules/opening-balance/opening-balance-routing.module.ts:25 |
| (module default) | OpeningBalanceComponent | List/navigation | Permission: AppInventoryPermissions.InventoryOpeningBalance_List; Guards: PolicyAuthGuard | Explicit frontend feature rule | apps/inventory/src/app/remote-entry/transactions/modules/opening-balance/opening-balance-routing.module.ts:14 |
| add | AddOpeningBalanceComponent | Add | Permission: AppInventoryPermissions.InventoryOpeningBalance_Add; Guards: PolicyAuthGuard | Explicit frontend feature rule | apps/inventory/src/app/remote-entry/transactions/modules/opening-balance/opening-balance-routing.module.ts:58 |
| edit/:id | EditOpeningBalanceComponent | Edit | Permission: AppInventoryPermissions.InventoryOpeningBalance_Edit; Guards: PolicyAuthGuard, preventPostedGuard | Explicit frontend feature rule | apps/inventory/src/app/remote-entry/transactions/modules/opening-balance/opening-balance-routing.module.ts:35 |
| opening-balance | Lazy-loaded route | List/navigation | Guards: PolicyAuthGuard | Explicit frontend feature rule | apps/inventory/src/app/remote-entry/transactions/transactions.module.ts:328 |
| sequence | SequenceComponent | Sequence | No route-level permission/guard found in source window | Explicit frontend feature rule | apps/inventory/src/app/remote-entry/transactions/modules/opening-balance/opening-balance-routing.module.ts:69 |
| sequence | SequenceComponent | Sequence | Guards: PolicyAuthGuard | Feature token match | apps/inventory/src/app/remote-entry/transactions/transactions.module.ts:112 |
| sequence | SequenceComponent | Sequence | Permission: AppInventoryPermissions.StockOut_View; Guards: PolicyAuthGuard | Feature token match | apps/inventory/src/app/remote-entry/transactions/transactions.module.ts:176 |
| sequence | SequenceComponent | Sequence | No route-level permission/guard found in source window | Feature token match | apps/inventory/src/app/remote-entry/transactions/transactions.module.ts:264 |
| sequence | SequenceComponent | Sequence | No route-level permission/guard found in source window | Feature token match | apps/inventory/src/app/remote-entry/transactions/transactions.module.ts:316 |
| sequence | SequenceComponent | Sequence | Permission: AppInventoryPermissions.Purchase_LandedCost_Add | Feature token match | apps/inventory/src/app/remote-entry/transactions/transactions.module.ts:363 |
| transactions | Lazy-loaded route | List/navigation | No route-level permission/guard found in source window | Feature token match | apps/inventory/src/app/remote-entry/entry.routes.ts:27 |
| view/:id | ViewOpeningBalanceComponent | View/detail | Permission: AppInventoryPermissions.InventoryOpeningBalance_View; Guards: PolicyAuthGuard | Explicit frontend feature rule | apps/inventory/src/app/remote-entry/transactions/modules/opening-balance/opening-balance-routing.module.ts:47 |
Observed frontend fields and validation
| Component | Control | Observed frontend rule | Component source |
|---|---|---|---|
| HeaderOpeningBalanceComponent | accumulatedDepOpeningBalanceJournalEntryLineId | Required in component validator | apps/fixedAssets/src/app/remote-entry/transactions/assets-openinga-balance/component/header-opening-balance/header-opening-balance.component.ts |
| HeaderOpeningBalanceComponent | assetOpeningBalanceJournalEntryLineId | Required in component validator | apps/fixedAssets/src/app/remote-entry/transactions/assets-openinga-balance/component/header-opening-balance/header-opening-balance.component.ts |
| HeaderOpeningBalanceComponent | openingBalanceJournalEntryLineId | Required in component validator | apps/fixedAssets/src/app/remote-entry/transactions/assets-openinga-balance/component/header-opening-balance/header-opening-balance.component.ts |
Observed frontend actions
| Component | Click action/handler | Business review requirement | Component source |
|---|---|---|---|
| BodyOpeningBalanceComponent | deleteAssetsLine | Available only in deletable states with Delete permission; identify the record in confirmation, call delete once, remove/refresh the list row on success, and retain the row with the API reason on failure. | apps/fixedAssets/src/app/remote-entry/transactions/assets-openinga-balance/component/body-opening-balance/body-opening-balance.component.ts |
| BodyOpeningBalanceComponent | openDialog | Open the item/lookup/tracking dialog with the current warehouse, item, UOM, and remaining quantity context; accepted values must return to the correct line and cancel must not mutate it. | apps/fixedAssets/src/app/remote-entry/transactions/assets-openinga-balance/component/body-opening-balance/body-opening-balance.component.ts |
| ListOpeningBalanceComponent | deleteOpeningBalance | Available only in deletable states with Delete permission; identify the record in confirmation, call delete once, remove/refresh the list row on success, and retain the row with the API reason on failure. | apps/inventory/src/app/remote-entry/transactions/modules/opening-balance/pages/list-opening-balance/list-opening-balance.component.ts |
| ListOpeningBalanceComponent | onEdit | Open the identified editable record only with Edit permission; posted/locked records remain blocked by route or action state and list context must be recoverable on return. | apps/inventory/src/app/remote-entry/transactions/modules/opening-balance/pages/list-opening-balance/list-opening-balance.component.ts |
| ListOpeningBalanceComponent | onPost | Available only for a postable Draft and Post permission; confirm the document, prevent a second submission, call the posting API, and refresh status, journal/stock links, and row actions. Failure must keep the pre-post state. | apps/inventory/src/app/remote-entry/transactions/modules/opening-balance/pages/list-opening-balance/list-opening-balance.component.ts |
| ListOpeningBalanceComponent | onUnPost | Available only for a postable Draft and Post permission; confirm the document, prevent a second submission, call the posting API, and refresh status, journal/stock links, and row actions. Failure must keep the pre-post state. | apps/inventory/src/app/remote-entry/transactions/modules/opening-balance/pages/list-opening-balance/list-opening-balance.component.ts |
| ListOpeningBalanceComponent | onView | Open the authorized record in view mode with its status, totals, lines, source and generated-document links; handle deleted, forbidden, and missing records without exposing stale data. | apps/inventory/src/app/remote-entry/transactions/modules/opening-balance/pages/list-opening-balance/list-opening-balance.component.ts |
| ListOpeningBalanceComponent | printout | Generate the printout for the selected record and authorized branch/tenant; use posted/current values and surface print, template, ZATCA/ETA, or popup failures without changing transaction state. | apps/inventory/src/app/remote-entry/transactions/modules/opening-balance/pages/list-opening-balance/list-opening-balance.component.ts |
Page-specific frontend acceptance criteria
- (module default) - List/navigation: Load an authorized paged/searchable list or navigation page; preserve filters/sort/paging and expose only permitted row and bulk actions. Security observed: Permission: AppInventoryPermissions.InventoryOpeningBalance_List; Guards: PolicyAuthGuard
- add - Add: Render permitted fields, required indicators, defaults, dependent lookups, validation, Save/Cancel, loading, success, and API-error states. Security observed: Permission: AppInventoryPermissions.InventoryOpeningBalance_Add; Guards: PolicyAuthGuard
- edit/:id - Edit: Load the identified record, enforce state/permission field locks, preserve concurrency identity, validate changes, and return without losing filters. Security observed: Permission: AppInventoryPermissions.InventoryOpeningBalance_Edit; Guards: PolicyAuthGuard, preventPostedGuard
- opening-balance - List/navigation: Load an authorized paged/searchable list or navigation page; preserve filters/sort/paging and expose only permitted row and bulk actions. Security observed: Guards: PolicyAuthGuard
- sequence - Sequence: Show current numbering scope and permit only authorized, valid sequence configuration without retroactively renumbering posted documents. Security observed: No route-level permission/guard found in source window
- transactions - List/navigation: Load an authorized paged/searchable list or navigation page; preserve filters/sort/paging and expose only permitted row and bulk actions. Security observed: No route-level permission/guard found in source window
- view/:id - View/detail: Load an authorized immutable/detail representation with status-aware actions, linked records, print/export where exposed, and not-found/forbidden handling. Security observed: Permission: AppInventoryPermissions.InventoryOpeningBalance_View; Guards: PolicyAuthGuard