Appearance
Inventory Reconciliation
| Backlog field | Value |
|---|---|
| Story ID | INV-DASH-016 |
| 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
Compare physical counts with system stock and post controlled adjustments for variances.
User stories
- As a stock controller, I want to start a reconciliation for a warehouse and selected items so that I can record physical counts.
- As a stock controller, I want to enter counts manually or upload an Excel file so that large counts are practical.
- As a supervisor, I want to review expected, actual, and variance quantities/values before posting.
- As an auditor, I want to view, print, and export the reconciliation and its adjustments.
Acceptance criteria
- A reconciliation identifies its warehouse, count date, and item scope.
- Expected quantity is retained as an auditable snapshot or is clearly refreshed before posting.
- Actual quantities cannot be negative and tracked-item counts reconcile by tracking number.
- Import validates template, item identity, duplicates, units, tracking, and numeric values before confirmation.
- Variance is calculated as actual quantity minus expected quantity.
- Posting creates the required stock increase/decrease and accounting adjustment exactly once.
- Zero-variance lines do not create unnecessary stock movements.
- Posted reconciliations cannot be edited through the draft workflow.
- List, view, edit, delete, post, printout, export, and import actions follow their permissions.
Posting details
- Post freezes the reviewed system-count snapshot, validates the physical quantities and tracking totals, confirms the warehouse/open period, and ensures the count still owns the stock lock. Variance is physical quantity minus system quantity; zero-variance lines produce no stock or journal entry.
- A shortage delegates to Stock Out. The current implementation debits the configured Cost of Goods Sold account and credits the Warehouse GL for the shortage value. A surplus delegates to Stock In, debiting the Warehouse GL and crediting the configured Goods in Transit account.
- These COGS and Goods-in-Transit mappings are the implemented reconciliation behavior; they must not be described as a generic adjustment account unless the code and Finance policy are changed. Missing settings or warehouse accounts block Post.
- Posting records the delegated Stock Out/Stock In identifiers, their stock layers and tracking movements, linked journals, final variance values, reconciliation status, and closure of the physical-count lock.
- All variance delegates, journals, reconciliation links, status, and lock release commit together. Failure leaves the reconciliation Draft/open with the lock intact and no partial adjustment. Any approved reversal must undo each linked variance movement and restore a consistent count state.
General ledger posting
| Variance | Debit | Credit | Amount basis | Delegate |
|---|---|---|---|---|
| Shortage: physical below system | Cost of Goods Sold | Warehouse GL | Shortage quantity x issue cost | Stock Out |
| Surplus: physical above system | Warehouse GL | Goods in Transit | Surplus quantity x receipt cost | Stock In |
| Zero variance | - | - | None | No stock delegate or journal |
Module and data effects
| Affected area | Effect | Conditional/no-effect rule |
|---|---|---|
| Inventory | Shortages reduce layers; surpluses create layers; balances become physical count. | Zero-variance lines do nothing. |
| Tracking | Adjusts tracked quantities using the approved physical identities. | No tracking effect on zero variance. |
| Physical count | Stores linked adjustment IDs, marks reconciliation Posted, and closes lock. | Failure keeps Draft/open and retains lock. |
| Accounting | Posts only the shortage/surplus rows above. | No customer, vendor, tax, or cash effect. |
Core acceptance scenarios
- Given a Draft physical count, when Post is clicked, then shortages delegate to Stock Out, surpluses delegate to Stock In, zero variances create no movement, linked IDs are stored, and the count lock closes atomically.
- Given any variance delegate cannot post, when reconciliation Post is attempted, then the count remains Draft/open and no partial variance movement is committed.
Required and derived data
- Required header: warehouse, transaction/count date, and at least one item count line.
- Required line: item, variant, UOM, system count, physical count, and tracking identity where applicable.
- Optional: description and lines with no variance.
- Derived: variance, shortage/surplus classification, cost from the configured costing method, and linked adjustment document codes.
- Physical counts cannot be negative. Serial/batch/expiry data must match item configuration.
While a count is open
The warehouse is marked as having an open Inventory Count. Other Stock In/Out posting checks this condition and is blocked so system quantities do not change during the physical count.
Clicking Post
- Requires a Draft with at least one item and revalidates active references.
- Closes the warehouse count lock.
- For
System Count > Physical Count, creates a posted Stock Out for the shortage. - For
Physical Count > System Count, creates a posted Stock In for the surplus. - Uses Weighted Average or Last Purchase cost from Inventory settings; service/missing-stock cost is zero.
- Preserves batch/serial/expiry identity on adjustment lines.
- Stores linked Stock Out/Stock In IDs and codes, sets reconciliation date, and marks the count Posted.
- Zero-variance lines create no adjustment movement.
GL effect
- Shortage: debit Inventory Settings COGS and credit Warehouse GL.
- Surplus: debit Warehouse GL and credit Goods in Transit under the current internal Stock In default.
The use of COGS/Goods in Transit rather than the Adjustment account is source-confirmed current behavior and should be reviewed with Finance.
Status, action, and reversal controls
| Action | Required state/authority | Preconditions and affected data | Failure/reversal rule |
|---|---|---|---|
| Start count | Reconciliation Add | Warehouse/date/item scope; no conflicting count | Captures expected snapshot and locks affected stock scope |
| Enter/import counts | Draft/open count plus Edit/Import | Non-negative physical values, unique valid rows, tracking totals | Invalid file/row does not silently change accepted counts |
| Review | View authority | Expected, physical, variance quantity/value available | Review itself posts nothing |
| Post | Draft plus Post | Count owns lock, open period, valid costs/accounts/tracking | Shortage Stock Out, surplus Stock In, journals, links, status, and lock release are atomic |
| Reverse | Approved adjustment reversal | No later dependent stock and allowed period/count policy | Reverse every linked variance movement and re-establish a consistent counted balance; zero-variance lines remain no-op |
Reconciliation controls
| Control | Required equality |
|---|---|
| Variance | Physical quantity minus expected snapshot |
| Shortage quantity/value | Linked Stock Out and COGS/Warehouse journal |
| Surplus quantity/value | Linked Stock In and Warehouse/GIT journal |
| Posted final balance | Approved physical count by tracking identity |
| Zero variance | No stock detail and no journal line |
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) | InventReconcileListComponent | List/navigation | Permission: AppInventoryPermissions.InventoryReconcile_List; Guards: PolicyAuthGuard | Explicit frontend feature rule | apps/inventory/src/app/remote-entry/transactions/transactions.module.ts:284 |
| edit/:id | EditInventReconcileComponent | Edit | Permission: AppInventoryPermissions.InventoryReconcile_Edit; Guards: PolicyAuthGuard, preventPostedGuard | Explicit frontend feature rule | apps/inventory/src/app/remote-entry/transactions/transactions.module.ts:295 |
| inventory-reconciliation | MainInventReconcileListComponent | List/navigation | Permission: AppInventoryPermissions.InventoryReconcile_List; Guards: PolicyAuthGuard | Explicit frontend feature rule | apps/inventory/src/app/remote-entry/transactions/transactions.module.ts:276 |
| view/:id | ViewInventReconcileComponent | View/detail | Permission: AppInventoryPermissions.InventoryReconcile_View; Guards: PolicyAuthGuard | Explicit frontend feature rule | apps/inventory/src/app/remote-entry/transactions/transactions.module.ts:306 |
Observed frontend fields and validation
| Component | Control | Observed frontend rule | Component source |
|---|---|---|---|
| ActualInventoryTableComponent | categoryId | Visible control; optional or API-dependent | apps/inventory/src/app/remote-entry/transactions/components/actual-inventory-table/actual-inventory-table.component.ts |
| ActualInventoryTableComponent | itemVariantId | Required in component validator | apps/inventory/src/app/remote-entry/transactions/components/actual-inventory-table/actual-inventory-table.component.ts |
| ActualInventoryTableComponent | physicalCount | Required in component validator | apps/inventory/src/app/remote-entry/transactions/components/actual-inventory-table/actual-inventory-table.component.ts |
| ActualInventoryTableComponent | status | Visible control; optional or API-dependent | apps/inventory/src/app/remote-entry/transactions/components/actual-inventory-table/actual-inventory-table.component.ts |
| ActualInventoryTableComponent | trackingNo | Visible control; optional or API-dependent | apps/inventory/src/app/remote-entry/transactions/components/actual-inventory-table/actual-inventory-table.component.ts |
| EditInventReconcileComponent | countDate | Visible control; optional or API-dependent | apps/inventory/src/app/remote-entry/transactions/pages/inventory-reconcile/edit-invent-reconcile/edit-invent-reconcile.component.ts |
| EditInventReconcileComponent | description | Visible control; optional or API-dependent | apps/inventory/src/app/remote-entry/transactions/pages/inventory-reconcile/edit-invent-reconcile/edit-invent-reconcile.component.ts |
| EditInventReconcileComponent | reconcileType | Visible control; optional or API-dependent | apps/inventory/src/app/remote-entry/transactions/pages/inventory-reconcile/edit-invent-reconcile/edit-invent-reconcile.component.ts |
| EditInventReconcileComponent | transactionCode | Visible control; optional or API-dependent | apps/inventory/src/app/remote-entry/transactions/pages/inventory-reconcile/edit-invent-reconcile/edit-invent-reconcile.component.ts |
| EditInventReconcileComponent | warehouseName | Visible control; optional or API-dependent | apps/inventory/src/app/remote-entry/transactions/pages/inventory-reconcile/edit-invent-reconcile/edit-invent-reconcile.component.ts |
Observed frontend actions
| Component | Click action/handler | Business review requirement | Component source |
|---|---|---|---|
| ActualInventoryTableComponent | getFile | Document the enabled state, permission, input context, resulting API or navigation effect, success refresh, and concrete failure behavior for this handler. | apps/inventory/src/app/remote-entry/transactions/components/actual-inventory-table/actual-inventory-table.component.ts |
| ActualInventoryTableComponent | onAddLine | Add one initialized detail row without changing existing rows; apply defaults and line validators, preserve stable row identity, and exclude incomplete rows from Save/Post. | apps/inventory/src/app/remote-entry/transactions/components/actual-inventory-table/actual-inventory-table.component.ts |
| ActualInventoryTableComponent | onApplyFilter | Document the enabled state, permission, input context, resulting API or navigation effect, success refresh, and concrete failure behavior for this handler. | apps/inventory/src/app/remote-entry/transactions/components/actual-inventory-table/actual-inventory-table.component.ts |
| ActualInventoryTableComponent | onDeleteByForm | 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/components/actual-inventory-table/actual-inventory-table.component.ts |
| ActualInventoryTableComponent | onResetExcel | Document the enabled state, permission, input context, resulting API or navigation effect, success refresh, and concrete failure behavior for this handler. | apps/inventory/src/app/remote-entry/transactions/components/actual-inventory-table/actual-inventory-table.component.ts |
| ActualInventoryTableComponent | onResetFilter | Document the enabled state, permission, input context, resulting API or navigation effect, success refresh, and concrete failure behavior for this handler. | apps/inventory/src/app/remote-entry/transactions/components/actual-inventory-table/actual-inventory-table.component.ts |
| ActualInventoryTableComponent | 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/inventory/src/app/remote-entry/transactions/components/actual-inventory-table/actual-inventory-table.component.ts |
| ActualInventoryTableComponent | toggleSidebar | Document the enabled state, permission, input context, resulting API or navigation effect, success refresh, and concrete failure behavior for this handler. | apps/inventory/src/app/remote-entry/transactions/components/actual-inventory-table/actual-inventory-table.component.ts |
| EditInventReconcileComponent | onOpenAttachment | Document the enabled state, permission, input context, resulting API or navigation effect, success refresh, and concrete failure behavior for this handler. | apps/inventory/src/app/remote-entry/transactions/pages/inventory-reconcile/edit-invent-reconcile/edit-invent-reconcile.component.ts |
| InventReconcileListComponent | navigateHelpPageComponent | Document the enabled state, permission, input context, resulting API or navigation effect, success refresh, and concrete failure behavior for this handler. | apps/inventory/src/app/remote-entry/transactions/pages/inventory-reconcile/invent-reconcile-list/invent-reconcile-list.component.ts |
| InventReconcileListComponent | onAdd | Document the enabled state, permission, input context, resulting API or navigation effect, success refresh, and concrete failure behavior for this handler. | apps/inventory/src/app/remote-entry/transactions/pages/inventory-reconcile/invent-reconcile-list/invent-reconcile-list.component.ts |
| InventReconcileListComponent | onDelete | 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/pages/inventory-reconcile/invent-reconcile-list/invent-reconcile-list.component.ts |
| InventReconcileListComponent | 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/pages/inventory-reconcile/invent-reconcile-list/invent-reconcile-list.component.ts |
| InventReconcileListComponent | 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/pages/inventory-reconcile/invent-reconcile-list/invent-reconcile-list.component.ts |
| InventReconcileListComponent | 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/pages/inventory-reconcile/invent-reconcile-list/invent-reconcile-list.component.ts |
| InventReconcileListComponent | 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/pages/inventory-reconcile/invent-reconcile-list/invent-reconcile-list.component.ts |
| ViewInventReconcileComponent | onOpenAttachment | Document the enabled state, permission, input context, resulting API or navigation effect, success refresh, and concrete failure behavior for this handler. | apps/inventory/src/app/remote-entry/transactions/pages/inventory-reconcile/view-invent-reconcile/view-invent-reconcile.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.InventoryReconcile_List; 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.InventoryReconcile_Edit; Guards: PolicyAuthGuard, preventPostedGuard
- inventory-reconciliation - 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.InventoryReconcile_List; Guards: PolicyAuthGuard
- 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.InventoryReconcile_View; Guards: PolicyAuthGuard