Appearance
Sales Returns
| Backlog field | Value |
|---|---|
| Story ID | AP-SAL-014 |
| Epic | Sales |
| Story type | Business workflow |
| Review status | Draft - business analyst and business owner review required |
| Source evidence | AppsPortal.Apis controller, application command/query handlers, validators, domain entities, and matching MFE workflow where available |
| Last source review | 2026-07-10 |
User story
As sales users, I want to return eligible invoiced quantities and post the customer credit effect so that stock and receivables are corrected without exceeding the source sale.
Posting reference: AppsPortal Posting Ledger explains debit/credit construction. Delegation and Interaction Contracts explains module ownership, delegated commands, linked records, commit boundaries, and reversal propagation.
Acceptance criteria
- Only authorized users can view or execute the feature's actions.
- Required business data, active dependencies, tenant/branch scope, and current document state are validated.
- Invalid requests return actionable validation errors without applying a partial change.
- Search, list, view, print, and export operations use the same authorized business scope.
- Material create, edit, status, approval, posting, reversal, and integration actions are auditable where applicable.
- Financial or stock effects are applied atomically and no more than once.
Posting details
- Validate Draft return, posted source invoice, open period, customer, positive quantities within remaining returnable quantity, tax, and warehouse/UOM/tracking.
- Resolve Customer Receivable and Sales Return from customer then category; resolve Tax from Tax then Tax Group.
- Sales delegates the commercial reversal journal to Accounting: debit Sales Return and applicable tax reversal; credit Customer Receivable.
- Delegate Stock In to Inventory for storable items: debit Warehouse GL, credit COGS, restore stock/tracking, and return linked IDs. Restore customer/salesperson credit and update source return quantities/status.
- All effects commit together; any source, account, period, tax, or Inventory failure leaves both return and source unchanged.
General ledger posting
| Component | Debit | Credit | Amount/account rule |
|---|---|---|---|
| Commercial return | Sales Return | Customer Receivable | Customer then category accounts; returned selling value. |
| Tax reversal | Output Tax | Customer Receivable / tax clearing | Tax then Tax Group; direction follows original tax. |
| Storable-item receipt | Warehouse GL | Cost of Goods Sold | Returned inventory cost, created by Stock In. |
| Service return | - | - | No Warehouse/COGS entry; commercial return still posts. |
Other module and document effects
| Area | Effect | Conditional/no effect |
|---|---|---|
| Inventory | Storable lines create Stock In, restore layers/tracking, and link stock journal. | Service lines create no Stock In. |
| Source Sales Invoice | Increases returned quantity and updates partial/full return status. | Over-return is blocked. |
| Customer/Finance | Reduces receivable/due balance and restores eligible credit exposure. | Refund cash requires a separate Payment Out unless POS orchestration supplies it. |
Core acceptance scenarios
- Given a valid Draft return within the source invoice's remaining returnable quantity, when the user clicks Post, then commercial reversal, Stock In, credit restoration, and source return status commit together.
- Given the source quantity, period, account, tax, warehouse, or tracking validation fails, when the operation is attempted, then the return remains Draft and the source/stock remain unchanged.
- Given the same Post request is retried after success, when the source is no longer Draft, then no duplicate journal, stock, balance, payment, or lifecycle effect is created.
Required and conditional data
An eligible source Sales Invoice, Draft return, customer, return date/open period, returned lines, UOM, positive quantities not exceeding returnable quantity, price/tax snapshots, and warehouse/tracking for storable items are required. Reason/description and settlement choice are optional or policy-driven.
Account resolution and Post effects
- Receivable: customer account then customer category.
- Sales Return: customer account then customer category.
- VAT: tax account then tax-group account according to tax effect.
- Stock: linked Stock In debits Warehouse GL and credits Inventory Settings COGS for storable items.
Post credits receivable by return grand total, debits Sales Return by local total after discount, applies VAT reversal lines, restores stock through linked Stock In, updates source invoice remaining/return status, restores customer and salesperson credit where implemented, links journals/stock documents, and marks the return Posted atomically.
Posting is blocked for non-Draft state, closed period, missing accounts/tax, quantity beyond the source, invalid tracking/warehouse, or unavailable Stock In capability.
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) | ListSalesReturnInvoiceComponent | List/navigation | Permission: AppSalesPermissions.ReturnSalesInvoice_List; Guards: PolicyAuthGuard | Explicit frontend feature rule | apps/sales/src/app/remote-entry/transaction/transaction.module.ts:268 |
| add | AddSalesReturnInvoiceComponent | Add | Permission: AppSalesPermissions.ReturnSalesInvoice_Add; Guards: PolicyAuthGuard | Explicit frontend feature rule | apps/sales/src/app/remote-entry/transaction/transaction.module.ts:302 |
| edit/:id | EditSalesReturnInvoiceComponent | Edit | Permission: AppSalesPermissions.ReturnSalesInvoice_Edit; Guards: PolicyAuthGuard, preventPostedGuard | Explicit frontend feature rule | apps/sales/src/app/remote-entry/transaction/transaction.module.ts:313 |
| return-sales-invoice | MainSalesReturnInvoiceComponent | List/navigation | Permission: AppSalesPermissions.ReturnSalesInvoice_List; Guards: PolicyAuthGuard | Explicit frontend feature rule | apps/sales/src/app/remote-entry/transaction/transaction.module.ts:260 |
| view/:id | ViewSalesReturnInvoiceComponent | View/detail | Permission: AppSalesPermissions.ReturnSalesInvoice_View; Guards: PolicyAuthGuard | Explicit frontend feature rule | apps/sales/src/app/remote-entry/transaction/transaction.module.ts:279 |
Observed frontend fields and validation
| Component | Control | Observed frontend rule | Component source |
|---|---|---|---|
| AddSalesReturnInvoiceComponent | customerId | Required in component validator | apps/sales/src/app/remote-entry/transaction/pages/return-sales-invoice/add-sales-return-invoice/add-sales-return-invoice.component.ts |
| AddSalesReturnInvoiceComponent | description | Visible control; optional or API-dependent | apps/sales/src/app/remote-entry/transaction/pages/return-sales-invoice/add-sales-return-invoice/add-sales-return-invoice.component.ts |
| AddSalesReturnInvoiceComponent | returnDate | Required in component validator | apps/sales/src/app/remote-entry/transaction/pages/return-sales-invoice/add-sales-return-invoice/add-sales-return-invoice.component.ts |
| AddSalesReturnInvoiceComponent | salesInvoiceId | Required in component validator | apps/sales/src/app/remote-entry/transaction/pages/return-sales-invoice/add-sales-return-invoice/add-sales-return-invoice.component.ts |
| AddSalesReturnInvoiceComponent | toReturnQTY | Visible control; optional or API-dependent | apps/sales/src/app/remote-entry/transaction/pages/return-sales-invoice/add-sales-return-invoice/add-sales-return-invoice.component.ts |
| AddSalesReturnInvoiceComponent | warehouseId | Visible control; optional or API-dependent | apps/sales/src/app/remote-entry/transaction/pages/return-sales-invoice/add-sales-return-invoice/add-sales-return-invoice.component.ts |
| EditSalesReturnInvoiceComponent | description | Visible control; optional or API-dependent | apps/sales/src/app/remote-entry/transaction/pages/return-sales-invoice/edit-sales-return-invoice/edit-sales-return-invoice.component.ts |
| EditSalesReturnInvoiceComponent | returnDate | Required in component validator | apps/sales/src/app/remote-entry/transaction/pages/return-sales-invoice/edit-sales-return-invoice/edit-sales-return-invoice.component.ts |
| EditSalesReturnInvoiceComponent | salesInvoiceId | Required in component validator | apps/sales/src/app/remote-entry/transaction/pages/return-sales-invoice/edit-sales-return-invoice/edit-sales-return-invoice.component.ts |
| EditSalesReturnInvoiceComponent | toReturnQTY | Required in component validator | apps/sales/src/app/remote-entry/transaction/pages/return-sales-invoice/edit-sales-return-invoice/edit-sales-return-invoice.component.ts |
| EditSalesReturnInvoiceComponent | warehouseId | Required in component validator | apps/sales/src/app/remote-entry/transaction/pages/return-sales-invoice/edit-sales-return-invoice/edit-sales-return-invoice.component.ts |
Observed frontend actions
| Component | Click action/handler | Business review requirement | Component source |
|---|---|---|---|
| AddSalesReturnInvoiceComponent | deleteRow | 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/sales/src/app/remote-entry/transaction/pages/return-sales-invoice/add-sales-return-invoice/add-sales-return-invoice.component.ts |
| AddSalesReturnInvoiceComponent | openCustomerAdvancedSearch | Document the enabled state, permission, input context, resulting API or navigation effect, success refresh, and concrete failure behavior for this handler. | apps/sales/src/app/remote-entry/transaction/pages/return-sales-invoice/add-sales-return-invoice/add-sales-return-invoice.component.ts |
| EditSalesReturnInvoiceComponent | deleteRow | 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/sales/src/app/remote-entry/transaction/pages/return-sales-invoice/edit-sales-return-invoice/edit-sales-return-invoice.component.ts |
| ListSalesReturnInvoiceComponent | 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/sales/src/app/remote-entry/transaction/pages/return-sales-invoice/list-sales-return-invoice/list-sales-return-invoice.component.ts |
| ListSalesReturnInvoiceComponent | 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/sales/src/app/remote-entry/transaction/pages/return-sales-invoice/list-sales-return-invoice/list-sales-return-invoice.component.ts |
| ListSalesReturnInvoiceComponent | 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/sales/src/app/remote-entry/transaction/pages/return-sales-invoice/list-sales-return-invoice/list-sales-return-invoice.component.ts |
| ListSalesReturnInvoiceComponent | onPrintOutWithZatcaConfirm | 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/sales/src/app/remote-entry/transaction/pages/return-sales-invoice/list-sales-return-invoice/list-sales-return-invoice.component.ts |
| ListSalesReturnInvoiceComponent | 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/sales/src/app/remote-entry/transaction/pages/return-sales-invoice/list-sales-return-invoice/list-sales-return-invoice.component.ts |
| ListSalesReturnInvoiceComponent | 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/sales/src/app/remote-entry/transaction/pages/return-sales-invoice/list-sales-return-invoice/list-sales-return-invoice.component.ts |
| ListSalesReturnInvoiceComponent | routeToStock | Navigate only when the transaction produced a linked Stock In/Stock Out record and Inventory view permission is available; service-only/no-stock cases expose no stock link. | apps/sales/src/app/remote-entry/transaction/pages/return-sales-invoice/list-sales-return-invoice/list-sales-return-invoice.component.ts |
| ViewSalesReturnInvoiceComponent | 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/sales/src/app/remote-entry/transaction/pages/return-sales-invoice/view-sales-return-invoice/view-sales-return-invoice.component.ts |
| ViewSalesReturnInvoiceComponent | routeToSalesInvoiceView | 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/sales/src/app/remote-entry/transaction/pages/return-sales-invoice/view-sales-return-invoice/view-sales-return-invoice.component.ts |
| ViewSalesReturnInvoiceComponent | routeToStock | Navigate only when the transaction produced a linked Stock In/Stock Out record and Inventory view permission is available; service-only/no-stock cases expose no stock link. | apps/sales/src/app/remote-entry/transaction/pages/return-sales-invoice/view-sales-return-invoice/view-sales-return-invoice.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: AppSalesPermissions.ReturnSalesInvoice_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: AppSalesPermissions.ReturnSalesInvoice_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: AppSalesPermissions.ReturnSalesInvoice_Edit; Guards: PolicyAuthGuard, preventPostedGuard
- return-sales-invoice - 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: AppSalesPermissions.ReturnSalesInvoice_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: AppSalesPermissions.ReturnSalesInvoice_View; Guards: PolicyAuthGuard