Supermarket and retail chain management application — Specification
Product overview
A unified retail operations command center that gives every role — from shop floor to executive suite — one traceable source of truth for stock, orders, suppliers, and performance across hundreds of locations and multiple countries. The core differentiator is traceability: any user can answer 'why' questions (why is milk out of stock, why is this delivery late, why is this store wasting more) in seconds without calling another department.
Problem statement
Users cannot easily trace everyday situations like why milk is out of stock or which supplier caused a delayed delivery because data is siloed across systems. The underlying job-to-be-done is operational visibility: giving every role the right information at the right time to make decisions without calling other departments. Improving stock availability is the primary business driver; waste reduction and executive visibility are important secondary outcomes.
Target users & roles
- Store Employee — Shop floor staff using mobile devices for stock checks, price lookups, task completion, barcode scanning, and initiating replenishment.
- Warehouse Team — Receiving, putaway, picking, transfers, and replenishment staff using desktop terminals or rugged handheld devices with barcode scanners.
- Purchasing Manager — Back-office user managing suppliers, purchase orders, delivery schedules, and costs. Approves exceptions and large orders.
- Regional Manager — Read-only dashboard user who compares store performance and escalates cross-store transfer exceptions.
- Platform Automation — Non-human system actor that builds and serves traces, auto-approves routine replenishment, generates alerts, and drives monitoring.
- Governance & Audit — Non-human system actor that records audit logs, enforces GDPR controls, and provides traceability for compliance.
User journeys
Stock Issue Resolution (Primary)
- Store Employee sees a low-stock alert on their mobile device or scans a product showing a problem.
- Employee opens the product's stock status screen showing current quantity, recent movements, and open purchase orders.
- Employee taps 'Trace' to see a summary trace with root cause highlighted in a one-line explanation.
- Employee progressively drills down into full movement history, related orders, and supplier details.
- Employee taps 'Replenish' to trigger a suggested purchase order or stock transfer.
- System pre-fills the order with recommended quantity based on sales velocity and lead time.
- Employee confirms and submits; the order auto-approves if within thresholds or routes to Purchasing Manager for exception approval.
- Warehouse Team receives the delivery, records batch numbers and expiry dates, and puts stock away.
- Stock becomes available for the store, and the employee sees confirmation with expected arrival date.
Purchase Order to Shelf (Secondary)
- Purchasing Manager creates a purchase order from a replenishment suggestion or manually.
- The order routes to the supplier with expected delivery date.
- Warehouse Team receives the delivery, scanning items and recording batch numbers and expiry dates for perishables.
- System updates inventory levels and flags any discrepancies (short shipment, damaged goods).
- Stock is put away and available for store replenishment.
- Store team requests a transfer or receives an auto-generated replenishment suggestion.
- Warehouse picks and ships the transfer.
- Store receives and confirms; shelf is restocked.
Functional requirements
FR-001: Barcode Scan Stock Lookup
The system shall allow a store employee to scan a product barcode and view current stock level, recent stock movements, and open purchase orders for that product at their location within 3 seconds. This serves the traceability problem (claim_008) and mobile scanning context (claim_015).
Acceptance criteria:
- Given a store employee scans a product barcode on their mobile device, when the product exists in the catalog, then the system displays current stock level, recent movements, and open purchase orders within 3 seconds.
FR-002: Summary Trace with Progressive Drill-Down
The system shall display a summary trace for any product, order, or delivery, showing root cause with a one-line explanation, with one-tap progressive drill-down to full movement history, related orders, and supplier details. This operationalizes the confirmed traceability UX standard (claim_sol_014).
Acceptance criteria:
- Given a store employee opens a product's stock status, when the product is out of stock, then the system displays a summary trace showing root cause (e.g., 'Supplier delivery delayed by 3 days') with one-tap access to full movement history.
FR-003: Hybrid Replenishment Approval
The system shall auto-approve replenishment orders within configured thresholds (quantity and cost) and route exceptions to a Purchasing Manager for approval. Thresholds are tenant-level configuration, not hard-coded constants. This implements the confirmed hybrid approval decision (claim_om_003).
Acceptance criteria:
- Given a store employee triggers a replenishment suggestion, when the order total is below the auto-approval threshold, then the system auto-approves the order and routes it to the supplier without Purchasing Manager intervention.
- Given a store employee triggers a replenishment suggestion, when the order total exceeds the threshold, then the system routes it to a Purchasing Manager for approval.
FR-004: Batch and Expiry Capture
The system shall require batch number and expiry date for all perishable products during receiving, and shall prevent sale or transfer of expired batches. This enforces the confirmed full batch tracking decision (claim_data_013).
Acceptance criteria:
- Given a warehouse team member receives a delivery of perishable products, when they scan items, then the system requires batch number and expiry date before confirming receipt.
- Given a batch has an expiry date in the past, when any user attempts to sell or transfer that batch, then the system rejects the action with an error message and marks the batch as expired.
FR-005: Ledger-Style Stock Movements
The system shall record every stock change as a StockMovement with type, quantity, source, destination, batch (if applicable), reference, and timestamp. No stock change shall occur without a corresponding StockMovement record. This enforces the confirmed ledger model (claim_data_003).
Acceptance criteria:
- Given any stock change occurs (receipt, transfer, sale, return, damage, count adjustment, expiry), when the change is processed, then a StockMovement record is created with type, quantity, source, destination, batch, reference, and timestamp.
FR-006: Effective-Dated Price Records
The system shall support effective-dated price records with start/end dates, allowing historical price lookup and future price scheduling. No two active price records for the same product and country shall have overlapping effective date ranges. This enforces the confirmed price versioning decision (claim_data_004).
Acceptance criteria:
- Given a purchasing manager creates a new price record, when the effective date range overlaps an existing active price for the same product and country, then the system rejects the new record with an error.
FR-007: Multi-Currency Storage
The system shall store both base currency and local currency amounts for all financial transactions, with exchange rate snapshots at transaction time. This enforces the confirmed multi-currency decision (claim_data_012).
Acceptance criteria:
- Given a financial transaction occurs in a local currency, when the transaction is recorded, then the system stores both local currency amount and base currency amount with an exchange rate snapshot.
FR-008: Offline Tolerance for Mobile Tasks
The system shall support offline tolerance for store-floor and warehouse mobile tasks (stock counts, receiving, transfers) with local queueing and sync when connection returns. This operationalizes the confirmed offline tolerance principle (claim_sol_013).
Acceptance criteria:
- Given a store employee is in an area with no connectivity, when they perform a stock count or receiving task, then the system queues the data locally and syncs when connection returns, without data loss.
FR-009: GDPR Controls
The system shall support GDPR controls including consent tracking, erasure capability, and data residency controls for customer PII. This enforces the confirmed GDPR compliance requirement (claim_data_009).
Acceptance criteria:
- Given a customer requests data erasure under GDPR, when the request is processed, then the system deletes or anonymizes all customer PII within 30 days and records the erasure in the audit log.
FR-010: Immutable Financial Records
The system shall make financial data (sales, margins, payments) immutable once posted, with corrections applied as reversing entries rather than in-place edits. This enforces the confirmed financial immutability rule (claim_data_010).
Acceptance criteria:
- Given a payment record has status 'completed', when any user attempts to edit the record in place, then the system rejects the edit and requires a reversing entry.
FR-011: Bulk Operations and Import/Export
The system shall support bulk operations (bulk updates, Excel/CSV imports and exports) for product catalog, price changes, and stock counts. This is a phase-1 capability for inventory and purchasing per the original prompt.
Acceptance criteria:
- Given a purchasing manager uploads a CSV file with product catalog data, when the import is processed, then the system validates each row and reports success or error per row.
FR-012: Multi-Language and Locale Support
The system shall support multi-language UI (at minimum: English plus the primary languages of the 4-10 operating countries) and multi-currency display with locale-specific number and date formats. This enforces the confirmed multi-country requirement (claim_031).
Acceptance criteria:
- Given a user selects their preferred language, when they navigate the UI, then all labels and messages display in the selected language.
FR-013: Timezone-Aware Timestamps
The system shall store all timestamps in UTC and display them in the user's local timezone. Shift scheduling and delivery schedules must be timezone-aware. This enforces the confirmed multi-timezone requirement (claim_017).
Acceptance criteria:
- Given a user in a different timezone views a timestamp, when the timestamp is displayed, then it appears in the user's local timezone.
FR-014: Audit Logging
The system shall maintain an audit log of all data changes, including who made the change, when, and what changed. Audit logs must be immutable and retained for at least 7 years for financial data. This enforces the confirmed audit requirement (claim_data_010).
Acceptance criteria:
- Given any data change occurs, when the change is committed, then an audit log entry is created with user, timestamp, and change details.
FR-015: Role-Based Access Control with Location Scoping
The system shall enforce role-based access control with location scoping. Each user is assigned a role and one or more locations (store, warehouse, region, country). Permissions are role + location scope combined. This enforces the confirmed permission model (claim_data_006).
Acceptance criteria:
- Given a store employee attempts to view another store's data, when the request is processed, then the system denies access and shows a scope boundary message.
FR-016: KPI Dashboards with Drill-Down
The system shall support the operating model KPIs: daily sales, profit, stock levels, popular products, low-stock items, waste, delivery delays, customer satisfaction, employee workload, and store comparisons. Each KPI must be drillable from company level to country, region, store, department, product, or transaction. This enforces the confirmed KPI set (claim_spec_009).
Acceptance criteria:
- Given a regional manager opens the store comparison dashboard, when they select two or more stores, then the system displays side-by-side KPIs with drill-down to transaction level for each store.
FR-017: Barcode Conflict Resolution
The system shall handle barcode conflicts gracefully with store context and supplier data for resolution. Barcodes are not globally unique. This addresses the confirmed barcode data quality risk (claim_data_007).
Acceptance criteria:
- Given a barcode that maps to multiple products, when a user scans it, then the system displays a disambiguation list with store context and supplier data for selection.
FR-018: Received Unconfirmed State
The system shall support a 'received_unconfirmed' state in the PurchaseOrder status enum to handle asynchronous supplier confirmations without blocking downstream workflows. This implements the user's explicit request (claim_data_008b).
Acceptance criteria:
- Given a warehouse team records physical receipt of a delivery, when supplier confirmation has not yet arrived, then the PurchaseOrder enters 'received_unconfirmed' state and downstream workflows (putaway, transfer, replenishment) are not blocked.
Screen / page inventory
- Mobile Dashboard (Store Employee) — Landing screen with today's alerts, low-stock items, and pending tasks.
- Elements: Alert list, Low-stock widget, Task list, Barcode scan button
- Product Stock Status (Store Employee, mobile) — Show current quantity, recent movements, open POs, and summary trace for a scanned or searched product.
- Elements: Barcode scan, Quantity on hand, Quantity available, Batch list, Summary trace card, Trace button, Replenish button, Transfer button
- Trace Detail (Store Employee, mobile) — Progressive drill-down into full movement history, related orders, and supplier details.
- Elements: Movement timeline, Order links, Supplier info, Root cause explanation, Corrective action buttons
- Replenishment Suggestion (Store Employee, mobile) — Show suggested order with pre-filled quantity based on sales velocity and lead time.
- Elements: Product, Suggested quantity, Supplier, Expected delivery date, Confirm button, Edit button
- Purchase Order Approval (Purchasing Manager, desktop) — Review and approve exception replenishment orders.
- Elements: Order details, Supplier, Cost, Reason for exception, Approve button, Reject button, Edit button
- Delivery Receiving (Warehouse Team, desktop or rugged handheld) — Scan items, record batch numbers and expiry dates, flag discrepancies.
- Elements: PO lookup, Line item list, Barcode scan, Batch number input, Expiry date input, Condition dropdown, Discrepancy flag, Confirm Receipt button
- Stock Count (Store Employee or Warehouse Team, mobile or handheld) — Enter counted quantities and flag discrepancies.
- Elements: Product scan, Counted quantity input, Expected quantity, Discrepancy flag, Notes, Submit Count button
- Executive Dashboard (Regional Manager, desktop) — Compare store performance with drill-down from company to transaction.
- Elements: KPI cards (daily sales, profit, stock levels, waste, delivery delays), Store comparison table, Drill-down charts, Export button
- Product Catalog Management (Purchasing Manager, desktop) — Create, edit, and manage products, variants, barcodes, and categories.
- Elements: Product list with filters, Product form, Variant management, Barcode validation, Bulk import/export
- Supplier Management (Purchasing Manager, desktop) — Manage supplier directory, performance scores, and delivery schedules.
- Elements: Supplier list, Supplier detail with reliability score, PO history, Delivery schedule
- Stock Transfer Management (Warehouse Team, desktop) — Create and track stock transfers between locations.
- Elements: Transfer list, Transfer form with from/to locations, Line items with batch selection, Status tracking
Data model
Product
| Field | Type | Notes |
|---|---|---|
id |
uuid | Primary key |
sku |
string | Unique per country, required |
gtin_barcode |
string | Optional, validated for check digit, not globally unique |
name |
string | Required |
description |
string | Optional |
brand_id |
foreign_key->Brand | Required |
category_id |
foreign_key->Category | Required |
parent_product_id |
foreign_key->Product | Nullable, for variants |
is_global |
boolean | Default true |
country_code |
string | Nullable for local products |
unit_of_measure |
enum(each,kg,liter,pack) | Required |
created_at |
datetime | UTC |
updated_at |
datetime | UTC |
deleted_at |
datetime | Soft delete, nullable |
Store
| Field | Type | Notes |
|---|---|---|
id |
uuid | Primary key |
name |
string | Required |
address |
string | Required |
country_code |
string | Required |
region_id |
foreign_key->Region | Required |
timezone |
string | IANA timezone identifier |
opening_hours |
string | Structured opening hours |
status |
enum(active,inactive) | Required |
phone_primary |
string | Typed contact field |
phone_emergency |
string | Typed contact field |
email_orders |
string | Typed contact field |
email_manager |
string | Typed contact field |
created_at |
datetime | UTC |
updated_at |
datetime | UTC |
deleted_at |
datetime | Soft delete, nullable |
Warehouse
| Field | Type | Notes |
|---|---|---|
id |
uuid | Primary key |
name |
string | Required |
address |
string | Required |
country_code |
string | Required |
region_id |
foreign_key->Region | Required |
capacity |
integer | Optional |
status |
enum(active,inactive) | Required |
created_at |
datetime | UTC |
updated_at |
datetime | UTC |
deleted_at |
datetime | Soft delete, nullable |
Supplier
| Field | Type | Notes |
|---|---|---|
id |
uuid | Primary key |
name |
string | Required |
contact_info |
string | Required |
payment_terms |
string | Optional |
lead_time_days |
integer | Required |
reliability_score |
decimal | Derived, updated nightly |
status |
enum(active,inactive) | Required |
created_at |
datetime | UTC |
updated_at |
datetime | UTC |
deleted_at |
datetime | Soft delete, nullable |
PurchaseOrder
| Field | Type | Notes |
|---|---|---|
id |
uuid | Primary key |
supplier_id |
foreign_key->Supplier | Required, indexed |
warehouse_id |
foreign_key->Warehouse | Destination, required, indexed |
order_date |
datetime | Required |
expected_delivery_date |
datetime | Required |
actual_delivery_date |
datetime | Nullable |
status |
enum(draft,sent,confirmed,partial,received,received_unconfirmed,cancelled) | Required, indexed. 'received_unconfirmed' handles asynchronous supplier confirmations. |
total_cost |
decimal | Derived from lines |
created_at |
datetime | UTC |
updated_at |
datetime | UTC |
PurchaseOrderLine
| Field | Type | Notes |
|---|---|---|
id |
uuid | Primary key |
purchase_order_id |
foreign_key->PurchaseOrder | Required, indexed |
product_id |
foreign_key->Product | Required, indexed |
quantity_ordered |
integer | Required, > 0 |
quantity_received |
integer | Default 0, <= quantity_ordered |
unit_cost |
decimal | Required, > 0 |
StockMovement
| Field | Type | Notes |
|---|---|---|
id |
uuid | Primary key |
product_id |
foreign_key->Product | Required, indexed |
batch_id |
foreign_key->Batch | Nullable, required for perishable products, indexed |
location_id |
uuid | Store or warehouse, required, indexed |
movement_type |
enum(receipt,transfer_out,transfer_in,sale,return,damage,count_adjustment,expiry) | Required, indexed |
quantity_change |
integer | Positive or negative, required |
reference_type |
string | Links to PurchaseOrder, StockTransfer, Sale, etc. |
reference_id |
uuid | Links to PurchaseOrder, StockTransfer, Sale, etc. |
created_at |
datetime | UTC, required |
created_by |
foreign_key->Employee | Required |
InventoryLevel
| Field | Type | Notes |
|---|---|---|
id |
uuid | Primary key |
product_id |
foreign_key->Product | Required, indexed |
batch_id |
foreign_key->Batch | Nullable, indexed |
location_id |
uuid | Store or warehouse, required, indexed |
quantity_on_hand |
integer | Required, >= 0 |
quantity_reserved |
integer | Default 0 |
quantity_available |
integer | Derived: quantity_on_hand - quantity_reserved |
reorder_point |
integer | Optional |
max_stock |
integer | Optional |
last_counted_at |
datetime | Nullable |
StockTransfer
| Field | Type | Notes |
|---|---|---|
id |
uuid | Primary key |
from_location_id |
uuid | Store or warehouse, required |
to_location_id |
uuid | Store or warehouse, required |
status |
enum(draft,in_transit,received,cancelled) | Required, indexed |
created_at |
datetime | UTC |
shipped_at |
datetime | Nullable |
received_at |
datetime | Nullable |
StockTransferLine
| Field | Type | Notes |
|---|---|---|
id |
uuid | Primary key |
stock_transfer_id |
foreign_key->StockTransfer | Required, indexed |
product_id |
foreign_key->Product | Required, indexed |
batch_id |
foreign_key->Batch | Nullable, indexed |
quantity_sent |
integer | Required, > 0 |
quantity_received |
integer | Default 0, <= quantity_sent |
Batch
| Field | Type | Notes |
|---|---|---|
id |
uuid | Primary key |
product_id |
foreign_key->Product | Required, indexed |
batch_number |
string | Required |
expiry_date |
datetime | Required, > received_date |
received_date |
datetime | Required |
supplier_id |
foreign_key->Supplier | Nullable |
status |
enum(active,expired,quarantined,depleted) | Required, indexed |
Customer
| Field | Type | Notes |
|---|---|---|
id |
uuid | Primary key |
first_name |
string | Required |
last_name |
string | Required |
email |
string | Required, unique |
phone |
string | Optional, validated format |
address |
string | Required for delivery |
country_code |
string | Required |
consent_gdpr |
boolean | Required |
consent_marketing |
boolean | Optional |
created_at |
datetime | UTC |
updated_at |
datetime | UTC |
deleted_at |
datetime | Soft delete, nullable |
LoyaltyAccount
| Field | Type | Notes |
|---|---|---|
id |
uuid | Primary key |
customer_id |
foreign_key->Customer | Required, indexed |
points_balance |
integer | Default 0 |
tier |
enum(bronze,silver,gold,platinum) | Derived from points earned in trailing 12 months |
joined_at |
datetime | Required |
Order
| Field | Type | Notes |
|---|---|---|
id |
uuid | Primary key |
customer_id |
foreign_key->Customer | Required, indexed |
loyalty_account_id |
foreign_key->LoyaltyAccount | Nullable |
store_id |
foreign_key->Store | For click-and-collect, nullable |
delivery_address |
string | For home delivery, nullable |
order_date |
datetime | Required |
status |
string | Required |
total_amount |
decimal | Required |
currency |
string | Required, ISO 4217 |
Employee
| Field | Type | Notes |
|---|---|---|
id |
uuid | Primary key |
first_name |
string | Required |
last_name |
string | Required |
email |
string | Required, unique |
phone |
string | Optional |
role_id |
foreign_key->Role | Required |
status |
enum(active,inactive) | Required |
hire_date |
datetime | Required |
EmployeeLocation
| Field | Type | Notes |
|---|---|---|
id |
uuid | Primary key |
employee_id |
foreign_key->Employee | Required, indexed |
location_id |
uuid | Store or warehouse, required |
location_type |
enum(store,warehouse,region,country) | Required |
is_primary |
boolean | Default false |
Shift
| Field | Type | Notes |
|---|---|---|
id |
uuid | Primary key |
employee_id |
foreign_key->Employee | Required, indexed |
location_id |
uuid | Store or warehouse, required |
start_time |
datetime | Required |
end_time |
datetime | Required, > start_time |
role_during_shift |
foreign_key->Role | Required |
Task
| Field | Type | Notes |
|---|---|---|
id |
uuid | Primary key |
location_id |
uuid | Store or warehouse, required |
assigned_to |
foreign_key->Employee | Required |
title |
string | Required |
description |
string | Optional |
due_at |
datetime | Required |
status |
enum(open,in_progress,completed,cancelled) | Required |
priority |
enum(low,medium,high,critical) | Required |
Price
| Field | Type | Notes |
|---|---|---|
id |
uuid | Primary key |
product_id |
foreign_key->Product | Required, indexed |
country_code |
string | Nullable for global |
currency |
string | Required, ISO 4217 |
amount_base |
decimal | Required |
amount_local |
decimal | Required |
exchange_rate_snapshot |
decimal | Required |
effective_from |
datetime | Required |
effective_to |
datetime | Nullable |
status |
enum(draft,approved,active,expired) | Required |
approved_by |
foreign_key->Employee | Nullable |
approved_at |
datetime | Nullable |
Promotion
| Field | Type | Notes |
|---|---|---|
id |
uuid | Primary key |
name |
string | Required |
description |
string | Optional |
country_code |
string | Nullable |
start_date |
datetime | Required |
end_date |
datetime | Required |
discount_type |
enum(percentage,fixed,bogo) | Required |
discount_value |
decimal | Required |
status |
enum(draft,approved,active,expired) | Required |
PromotionProduct
| Field | Type | Notes |
|---|---|---|
id |
uuid | Primary key |
promotion_id |
foreign_key->Promotion | Required, indexed |
product_id |
foreign_key->Product | Required, indexed |
Complaint
| Field | Type | Notes |
|---|---|---|
id |
uuid | Primary key |
customer_id |
foreign_key->Customer | Required, indexed |
order_id |
foreign_key->Order | Nullable |
type |
enum(product_quality,delivery_delay,wrong_item,missing_item,other) | Required |
description |
string | Required |
status |
enum(open,investigating,resolved,closed) | Required |
priority |
enum(low,medium,high,critical) | Required |
assigned_to |
foreign_key->Employee | Nullable |
created_at |
datetime | Required |
resolved_at |
datetime | Nullable |
Return
| Field | Type | Notes |
|---|---|---|
id |
uuid | Primary key |
order_id |
foreign_key->Order | Required, indexed |
customer_id |
foreign_key->Customer | Required, indexed |
reason |
string | Required |
status |
enum(requested,approved,received,refunded,rejected) | Required |
refund_amount |
decimal | Required |
created_at |
datetime | Required |
Payment
| Field | Type | Notes |
|---|---|---|
id |
uuid | Primary key |
order_id |
foreign_key->Order | Required, indexed |
amount |
decimal | Required |
currency |
string | Required, ISO 4217 |
method |
string | Required |
status |
enum(pending,completed,failed,reversed) | Required |
transaction_reference |
string | Required |
created_at |
datetime | Required |
Brand
| Field | Type | Notes |
|---|---|---|
id |
uuid | Primary key |
name |
string | Required, unique |
Category
| Field | Type | Notes |
|---|---|---|
id |
uuid | Primary key |
name |
string | Required, unique |
parent_category_id |
foreign_key->Category | Nullable, for hierarchical categories |
Region
| Field | Type | Notes |
|---|---|---|
id |
uuid | Primary key |
name |
string | Required |
country_code |
string | Required |
Role
| Field | Type | Notes |
|---|---|---|
id |
uuid | Primary key |
name |
string | Required, unique |
description |
string | Optional |
AuditLog
| Field | Type | Notes |
|---|---|---|
id |
uuid | Primary key |
entity_type |
string | Required |
entity_id |
uuid | Required |
action |
enum(create,update,delete,read) | Required |
changed_by |
foreign_key->Employee | Required |
changed_at |
datetime | Required, UTC |
before_data |
json | Snapshot before change |
after_data |
json | Snapshot after change |
ExchangeRate
| Field | Type | Notes |
|---|---|---|
id |
uuid | Primary key |
from_currency |
string | Required, ISO 4217 |
to_currency |
string | Required, ISO 4217 |
rate |
decimal | Required |
effective_date |
datetime | Required |
source |
enum(ecb,open_exchange_rates,manual) | Required |
Business rules
- BR-001: StockMovement.quantity_change must never result in InventoryLevel.quantity_on_hand < 0. Any movement that would cause negative stock is rejected with an error.
- BR-002: Price.effective_from must be >= today for new price records. Price.effective_to must be > Price.effective_from. No two active Price records for the same product and country can have overlapping effective date ranges.
- BR-003: PurchaseOrderLine.quantity_received must be <= PurchaseOrderLine.quantity_ordered. Receiving more than ordered requires a new purchase order or an explicit override with manager approval.
- BR-004: Batch.expiry_date must be > Batch.received_date. Batches with expiry_date in the past are automatically marked as expired. Expired batches cannot be sold or transferred; they can only be moved to waste or returned to supplier.
- BR-005: StockTransfer.status transitions: draft → in_transit → received. A transfer cannot be marked received unless all StockTransferLine.quantity_sent > 0. Received quantity can be less than sent (damage in transit) but never more.
- BR-006: Payment records are immutable once status = 'completed'. Corrections require a reversing Payment record with negative amount and a reference to the original payment.
- BR-007: Customer PII can only be accessed by users with customer_service or admin role. Customer.consent_gdpr must be true before any marketing communication. Customer data deletion requests must be fulfilled within 30 days (GDPR).
- BR-008: Employee can only be assigned to locations within their country. Regional managers can view all stores in their region but cannot edit data outside their region. Corporate users have full access.
- BR-009: Replenishment auto-approval thresholds: orders with total cost below a configurable threshold (default: 5,000 base currency) and quantity below a configurable threshold (default: 100 units) are auto-approved. All other orders require Purchasing Manager approval. Thresholds are tenant-level configuration, not hard-coded constants.
- BR-010: PurchaseOrder status transitions must include received_unconfirmed as a valid state. A PO enters received_unconfirmed when physical receipt is recorded but supplier confirmation has not yet arrived. Downstream workflows (putaway, transfer, replenishment) must not be blocked by this state.
Permissions
| Role | Capabilities |
|---|---|
| Store Employee | read: product, inventory_level, stock_movement, purchase_order (own store); create: stock_count, replenishment_suggestion, stock_transfer_request; update: stock_level (own store); delete: none |
| Warehouse Team | read: product, inventory_level, stock_movement, purchase_order, stock_transfer (own warehouse); create: delivery_receipt, stock_transfer, stock_count; update: inventory_level (own warehouse), stock_transfer (own warehouse); delete: none |
| Purchasing Manager | read: product, supplier, purchase_order, price, inventory_level (own country); create: purchase_order, price_change_request, supplier; update: purchase_order, price, supplier (own country); approve: purchase_order_exception, price_change; delete: none |
| Regional Manager | read: dashboard, kpi, store_comparison, stock_movement, purchase_order (own region); escalate: cross_store_transfer_exception; create: none; update: none; delete: none |
| Platform Automation | read: all operational data; create: alert, stock_movement (system-generated), trace; approve: routine_replenishment (within thresholds); update: inventory_level (system-generated); delete: none |
| Governance & Audit | read: audit_log, all data (for compliance); create: audit_log_entry; enforce: gdpr_controls, rbac; update: none; delete: none |
Integrations
- POS integration: provides sales transactions (product, quantity, price, timestamp, store), payment details, and customer loyalty id if captured. Primary source of sales data. Integration via iPaaS middleware.
- E-commerce integration: provides online orders (customer, items, quantities, delivery address, order status), and inventory levels if the e-commerce platform manages its own stock. Integration via iPaaS middleware.
- Accounting system integration: receives sales summaries, payment records, and expense data. The accounting system is the system of record for general ledger, not this app. Integration via iPaaS middleware.
- Supplier feeds: provide product catalogs (sku, barcode, description, unit cost), availability, and lead times. Supplier data quality varies significantly and requires validation on import. Integration via iPaaS or custom connectors.
- Delivery partner integrations: provide tracking numbers, delivery status updates, and proof of delivery. This data links to Order for the 'why did an online order arrive incomplete' traceability scenario. Integration via iPaaS middleware.
- Warehouse management system (WMS) integration: provides receiving confirmations, picking status, and stock counts. If the WMS is the system of record for warehouse operations, this app syncs with it rather than replacing it. Integration via iPaaS or custom connectors.
- Exchange rate provider integration: automated daily feed from ECB or Open Exchange Rates. The feed must provide daily rates for all currencies in use. Manual override by finance team is supported for exceptional cases.
Non-functional requirements
- NFR-001: The system shall support 1,000-5,000 concurrent users across all roles with p95 latency < 500ms for API responses and p99 latency < 2s for dashboard queries. These latency targets are tenant-level configuration, not hard-coded constants.
- NFR-002: Data freshness SLAs: stock levels and order status within 60 seconds, dashboards within 15 minutes, scheduled reports daily. These SLAs are tenant-level configuration, not hard-coded constants.
- NFR-003: The system shall store all timestamps in UTC and display them in the user's local timezone. Shift scheduling and delivery schedules must be timezone-aware.
- NFR-004: The system shall support multi-language UI (at minimum: English, plus the primary languages of the 4-10 operating countries) and multi-currency display with locale-specific number and date formats.
- NFR-005: The system shall comply with GDPR: consent tracking, erasure capability within 30 days, data residency controls, and audit logging of PII access.
- NFR-006: The system shall support offline tolerance for store-floor and warehouse mobile tasks with local queueing and sync when connection returns. Offline data must not be lost on app restart.
- NFR-007: The system shall maintain an audit log of all data changes, including who made the change, when, and what changed. Audit logs must be immutable and retained for at least 7 years for financial data. The retention period is tenant-level configuration, not a hard-coded constant.
- NFR-008: The system shall support the operating model KPIs: daily sales, profit, stock levels, popular products, low-stock items, waste, delivery delays, customer satisfaction, employee workload, and store comparisons. Each KPI must be drillable from company level to country, region, store, department, product, or transaction.
Edge cases
- User imports CSV with 50,000 rows: the system must process the import asynchronously, report per-row errors, and not block the UI. Rows with validation errors are rejected with specific error messages.
- Barcode scan returns multiple products: the system displays a disambiguation list with store context and supplier data for selection.
- Supplier delivery confirmation arrives hours after physical receipt: the PurchaseOrder enters 'received_unconfirmed' state, and downstream workflows (putaway, transfer, replenishment) are not blocked.
- Store employee enters wrong quantity during stock count (e.g., types 100 instead of 10): the system flags counts that deviate >50% from expected and requires confirmation before applying the adjustment.
- POS or e-commerce integration fails: the system detects the failure, surfaces an alert, and marks affected data as stale rather than silently showing incorrect data.
- Exchange rate provider outage: the system uses the last known rate and alerts the finance team to manually override if necessary.
- Batch expires while in transit: the receiving warehouse must be able to mark the batch as expired upon receipt and route it to waste or return to supplier.
Out of scope
- Point-of-sale system (POS is an integration target)
- E-commerce storefront (online shops are integration targets)
- Accounting general ledger and tax filing (accounting system is an integration target)
- Payroll (out of scope)
- Employee scheduling optimization (shift planning is in scope for assignment and tracking, but automated scheduling optimization is out of scope)
- Equipment maintenance management (equipment issues are tracked as tasks, but full CMMS is out of scope)
- SMS notifications, push notifications, passwordless authentication, onboarding flows, and theming (not in selected feature tags)
- Deferred (not out of scope, but later phases): Customer & Order Resolution (complaint management, returns and refunds, loyalty account management, unified customer service view) and Marketing & Promotions (promotion creation and approval workflow, loyalty campaign management, promotion effectiveness analysis)
Assumptions & open items
Assumed:
- Auto-approval thresholds default to 5,000 base currency and 100 units. These are configurable defaults, not hard-coded values.
- Data freshness SLAs default to stock levels within 60 seconds, dashboards within 15 minutes, scheduled reports daily. These are configurable defaults.
- Latency targets default to p95 < 500ms and p99 < 2s. These are configurable defaults.
- Audit log retention period defaults to 7 years for financial data. This is a configurable default.
- Primary languages for multi-language UI are English plus the primary languages of the 4-10 operating countries. The exact language list is a launch-configuration item.
- The specific regulatory requirements per country (GDPR, local labor laws, food safety regulations, tax compliance) are not fully enumerated. The spec assumes GDPR as the baseline but does not cover country-specific regulations in detail.
Coverage notes
- Functional requirements: 18 (with acceptance criteria: 18)
- Open assumptions: 6 (unresolved/conflicted: 0)
- Entities in data model: 30
- Screens: 11, Roles: 6, Journeys: 2