Appearance
Landed Cost
| Backlog field | Value |
|---|---|
| Story ID | INV-DASH-017 |
| 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
Allocate freight, customs, insurance, and other acquisition costs to purchase-invoice inventory so item cost reflects the full landed value.
User stories
- As a cost accountant, I want to open a purchase invoice and add additional cost lines so that full acquisition cost is captured.
- As a cost accountant, I want to allocate each cost using an allowed allocation basis so that allocation follows company policy.
- As a reviewer, I want to compare invoice value, allocated amount, and adjusted item cost before finalization.
- As an auditor, I want to view, search, print, export, and trace landed-cost calculations.
Acceptance criteria
- A Landed Cost references an eligible purchase invoice and its inventory lines.
- Additional cost lines have valid expense/account, currency/amount, and allocation method data.
- Allocation produces deterministic per-line allocated amounts with an explicit rounding rule.
- Total allocated local amount equals the additional cost total within the configured rounding tolerance.
- Adjusted item cost equals original inventory cost plus allocated landed cost.
- Editing recalculates dependent allocations and totals before save.
- Final posting updates inventory valuation/accounting exactly once and preserves the calculation for audit.
- Delete/edit are blocked after finalization except through an approved reversal process.
- Add, edit, view, list, delete, printout, and export actions follow permissions.
Posting details
- Landed Cost is posted as part of the eligible Purchase Invoice flow. It validates invoice/vendor/currency/tax data, service-cost lines, allocation bases, allocation totals and rounding, item variants, cost centers, duplicate posting, and the receiving stock context before returning posting groups to the caller.
- Expense account resolution is service item first, then item category; failure at both levels blocks Post. Cost-center resolution follows the corresponding item then category mapping. Vendor payable resolves vendor first, then vendor category. Tax accounts resolve from the applicable tax group/configuration, and inventory capitalization uses the purchase/Inventory Goods-in-Transit and warehouse mappings.
- The Landed Cost handler delegates the assembled expense, vendor, VAT/tax, currency, and cost-center groups to Purchase Invoice, and returns deterministic per-item/per-variant allocated amounts to the delegated Stock In valuation flow. It does not independently commit a disconnected journal or stock update.
- Successful posting increases the received inventory valuation and item cost basis by the allocated amounts, preserves the original and allocated values for audit, links the purchase, landed-cost, stock, and journal effects, and prevents the same cost from being capitalized twice.
- Allocation, Purchase Invoice accounting, Stock In valuation, tax/vendor effects, links, and statuses commit atomically under the parent transaction. Any missing account, invalid allocation, delegate failure, or persistence error leaves every affected document unchanged. Reversal must remove the capitalization and reverse the related purchase/accounting effects together.
General ledger posting
| Component | Debit | Credit | Amount basis | Account resolution |
|---|---|---|---|---|
| Capitalized landed cost | Inventory/Warehouse through Stock In valuation | Goods in Transit / purchase clearing | Allocated item amount | Purchase and Inventory source mappings. |
| Service/expense charge | Service Expense | Vendor Payable | Charge local amount before/with tax | Expense: service item then category. Payable: vendor then vendor category. |
| Recoverable tax | Input Tax | Vendor Payable / tax clearing | Calculated local tax | Tax then Tax Group mapping. |
Module and data effects
| Affected area | Effect | Conditional/no-effect rule |
|---|---|---|
| Purchase Invoice | Receives expense, vendor, tax, currency, and cost-center posting groups. | Landed Cost does not commit a disconnected purchase journal. |
| Inventory/Stock In | Adds allocations to received layers and item cost basis. | Service/non-capitalizable allocations do not change item quantity. |
| Quantity | No additional units are received solely because landed cost is added. | Only valuation changes for already received eligible units. |
| Vendor/Tax | Updates payable and recoverable tax through Purchase posting. | No vendor/tax effect for internal allocations with no vendor/tax line. |
| Audit | Stores original cost, basis, allocation, links, and final cost. | Duplicate capitalization is blocked. |
Core acceptance scenarios
- Given valid service expense lines and allocations totaling each local amount within tolerance, when Landed Cost posts through Purchase Invoice, then expense/vendor/VAT groups and item-variant allocations are returned to Purchase/Stock In and capitalized atomically.
- Given item expense is missing, when resolution runs, then category expense is used; if both are missing, Post fails. Vendor payable resolves vendor then vendor category.
- Given any allocation, currency, tax, vendor, account, or duplicate-post rule fails, when Post is attempted, then no purchase, stock valuation, or journal effect is committed.
Data classification
| Data | Rule |
|---|---|
| Purchase invoice | Required and must be eligible; posting locates Landed Cost by purchase invoice ID. |
| Cost lines | At least one required for add/edit. |
| Service item | Required, must exist, and must be Item Type Service. |
| Vendor | Required per cost line. |
| Allocation method | Required enum. |
| Currency | ID and English/Arabic names required. |
| Amount | Required and greater than zero. |
| Exchange rate | Required and greater than zero; must equal 1 when line currency is company currency. |
| Local amount | Derived/validated as Amount multiplied by Exchange Rate within 0.01 tolerance. |
| Tax | Optional; when selected its definition/account must exist and VAT is included in vendor payable. |
| Description | Optional. |
| Allocations | Optional while editing, but if present every row is validated and active allocations must total Local Amount within 0.01. |
| Allocation row | Requires invoice detail, UOM identity/names, item variant identity/names, and non-negative quantities/prices/amount. Inactive rows must allocate zero. |
Duplicate invoice details in one cost line are rejected.
Account and cost-center resolution
- Expense account: service item expense account, then service item category expense account, otherwise fail.
- Cost center: service item cost center, then category cost center, otherwise no cost-center allocation.
- Vendor payable: vendor payable account, then vendor category payable account, otherwise fail.
- VAT: selected tax definition's account; missing tax blocks posting.
Clicking Post
- Finds the Landed Cost for the purchase invoice and rejects an already-posted record.
- Confirms Accounting can add a posted journal.
- Resolves service items, expense accounts/cost centers, vendor payable accounts, and tax definitions.
- Groups expense totals by expense account/cost center, vendor totals by payable account, and VAT by tax account.
- Marks Landed Cost Posted and returns the grouped accounting amounts plus allocated cost by item variant to the purchase/stock-in workflow.
- The purchase Stock In applies allocated item cost to inventory valuation; its journal incorporates the returned expense entries so inventory receives the landed amount.
Posting failure
Posting stops for missing service items, expense/payable/tax accounts, invalid allocation totals, invalid exchange-rate math, unavailable Accounting posting, or repeat posting. No partial purchase/stock/journal effect should commit.
Status, action, and reversal controls
| Action | Required state/authority | Preconditions and affected data | Failure/reversal rule |
|---|---|---|---|
| Add/Edit allocation | Landed Cost Add/Edit; parent eligible | Purchase invoice/receipt lines, charge currency, allocation method, expense/vendor/tax and cost centers | Recalculation replaces Draft allocations only; received quantity does not change |
| Review | View authority | Original cost, charge totals, basis, allocated/adjusted values visible | Review creates no journal or valuation change |
| Finalize/Post through Purchase | Parent Purchase Post authority | Allocations equal charges within tolerance; all accounts/currency/tax/stock context valid | Purchase journal groups and Stock In capitalization commit under parent or all roll back |
| Delete | Draft plus Delete | Not finalized/capitalized | Posted allocation requires parent reversal, not deletion |
| Reverse | Purchase return/reversal authority | Remaining inventory/layers and vendor/tax dependencies permit reversal | Remove only attributable capitalization and reverse payable/tax/accounting consistently |
Reconciliation controls
| Control | Required equality |
|---|---|
| Sum of item allocations | Each landed-cost charge local amount within rounding tolerance |
| Adjusted receipt value | Original eligible inventory value plus capitalized allocation |
| Quantity | Unchanged by landed cost alone |
| Vendor/expense/tax groups | Purchase journal detail and parent payable totals |
| Capitalized amount | Increase in linked Stock In valuation/layers without duplicate application |
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) | LandedCostListComponent | List/navigation | Permission: AppInventoryPermissions.LANDED_COST_List; Guards: PolicyAuthGuard | Explicit frontend feature rule | apps/inventory/src/app/remote-entry/transactions/transactions.module.ts:343 |
| add/:invoiceId | AddLandedCostComponent | Add | Permission: AppInventoryPermissions.Purchase_LandedCost_Add; Guards: PolicyAuthGuard | Explicit frontend feature rule | apps/inventory/src/app/remote-entry/transactions/transactions.module.ts:373 |
| edit/:landedCostId/:invoiceId | AddLandedCostComponent | Add | Permission: AppInventoryPermissions.Purchase_LandedCost_Edit; Guards: PolicyAuthGuard | Explicit frontend feature rule | apps/inventory/src/app/remote-entry/transactions/transactions.module.ts:385 |
| fixed-cost/:id | ItemFixedCostComponent | View/detail | No route-level permission/guard found in source window | Feature token match | apps/inventory/src/app/remote-entry/items/items.module.ts:147 |
| landed-cost | LandedCostMainComponent | List/navigation | Permission: AppInventoryPermissions.LANDED_COST_List; Guards: PolicyAuthGuard | Explicit frontend feature rule | apps/inventory/src/app/remote-entry/transactions/transactions.module.ts:335 |
| view/:id | LandedCostViewComponent | View/detail | Permission: AppInventoryPermissions.LANDED_COST_VIEW; Guards: PolicyAuthGuard | Explicit frontend feature rule | apps/inventory/src/app/remote-entry/transactions/transactions.module.ts:353 |
Observed frontend fields and validation
| Component | Control | Observed frontend rule | Component source |
|---|---|---|---|
| AddLandedCostComponent | creationDate | Visible control; optional or API-dependent | apps/inventory/src/app/remote-entry/transactions/pages/landed-cost/add-landed-cost/add-landed-cost.component.ts |
| AddLandedCostComponent | vendorId | Visible control; optional or API-dependent | apps/inventory/src/app/remote-entry/transactions/pages/landed-cost/add-landed-cost/add-landed-cost.component.ts |
| AddLandedCostComponent | warehouseName | Visible control; optional or API-dependent | apps/inventory/src/app/remote-entry/transactions/pages/landed-cost/add-landed-cost/add-landed-cost.component.ts |
| ItemFixedCostComponent | fixedCost | Required in component validator | apps/inventory/src/app/remote-entry/items/pages/item-definition/item-fixed-cost/item-fixed-cost.component.ts |
| ItemFixedCostComponent | itemVariantId | Required in component validator | apps/inventory/src/app/remote-entry/items/pages/item-definition/item-fixed-cost/item-fixed-cost.component.ts |
| ItemFixedCostComponent | uomId | Required in component validator | apps/inventory/src/app/remote-entry/items/pages/item-definition/item-fixed-cost/item-fixed-cost.component.ts |
Observed frontend actions
| Component | Click action/handler | Business review requirement | Component source |
|---|---|---|---|
| AdditionalCostsComponent | openServiceItemDialog | 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/pages/landed-cost/add-landed-cost/components/additional-costs/additional-costs.component.ts |
| AdjustedValuesComponent | valuesChanged | 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/landed-cost/add-landed-cost/components/adjusted-values/adjusted-values.component.ts |
| ItemFixedCostComponent | addLineFixed | 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/items/pages/item-definition/item-fixed-cost/item-fixed-cost.component.ts |
| ItemFixedCostComponent | onCancel | Leave or close without persisting; warn when the current form has unsaved changes and restore the prior list/filter context. | apps/inventory/src/app/remote-entry/items/pages/item-definition/item-fixed-cost/item-fixed-cost.component.ts |
| ItemFixedCostComponent | onDeleteFixedCost | 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/items/pages/item-definition/item-fixed-cost/item-fixed-cost.component.ts |
| ItemFixedCostComponent | onSaveFixedCost | Validate the concrete form and line collection, prevent duplicate submission, persist once, show API field/business errors in context, and navigate/refresh only after success. | apps/inventory/src/app/remote-entry/items/pages/item-definition/item-fixed-cost/item-fixed-cost.component.ts |
| LandedCostListComponent | 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/landed-cost/landed-cost-list/landed-cost-list.component.ts |
| LandedCostListComponent | 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/landed-cost/landed-cost-list/landed-cost-list.component.ts |
| LandedCostListComponent | onVeiw | 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/landed-cost/landed-cost-list/landed-cost-list.component.ts |
| LandedCostListComponent | 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/landed-cost/landed-cost-list/landed-cost-list.component.ts |
| LandedCostListComponent | routeToJournalentryView | 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/landed-cost/landed-cost-list/landed-cost-list.component.ts |
| LandedCostListComponent | routeToPurchaseInvoiceView | 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/landed-cost/landed-cost-list/landed-cost-list.component.ts |
| LandedCostListComponent | routeToStockInView | 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/landed-cost/landed-cost-list/landed-cost-list.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.LANDED_COST_List; Guards: PolicyAuthGuard
- add/:invoiceId - Add: Render permitted fields, required indicators, defaults, dependent lookups, validation, Save/Cancel, loading, success, and API-error states. Security observed: Permission: AppInventoryPermissions.Purchase_LandedCost_Add; Guards: PolicyAuthGuard
- edit/:landedCostId/:invoiceId - Add: Render permitted fields, required indicators, defaults, dependent lookups, validation, Save/Cancel, loading, success, and API-error states. Security observed: Permission: AppInventoryPermissions.Purchase_LandedCost_Edit; Guards: PolicyAuthGuard
- fixed-cost/: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: No route-level permission/guard found in source window
- landed-cost - 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.LANDED_COST_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.LANDED_COST_VIEW; Guards: PolicyAuthGuard