Supermarket and retail chain management — Specification
Product overview
A unified retail operations platform for a multi-country supermarket chain that enables daily operational execution (stock, receiving, tasks, replenishment) as the foundation, with analytics and cross-functional traceability built on top of the operational data it generates. Phase 1 is store operations only; warehouse and purchasing follow in Phase 2, then analytics deepens.
Problem statement
Store and warehouse staff lack a single mobile tool for daily tasks, leading to paper checklists, manual stock counts, and missed expiry dates. Regional managers and executives lack consistent, comparable metrics across countries, regions, and stores. Cross-border operations create currency, tax, language, and regulatory complexity that current tools do not handle consistently.
Target users & roles
- Store Employee — Frontline staff (cashiers, stockers, department leads) who execute daily store work on mobile with barcode scanning and minimal typing.
- Store Manager — Oversees store operations, approves replenishment requests, assigns tasks, and monitors store-level KPIs.
- Regional Manager — Compares store performance across a region and drills down from region to store to department.
- Executive — CEO/COO/CFO/CMO who need a company-wide overview with drill-down to any country, region, store, product, or transaction.
- Warehouse Staff — Receivers, pickers, and inventory controllers who manage incoming goods, storage, picking, and transfers (Phase 2).
- Purchasing Team — Manages suppliers, purchase orders, delivery schedules, and supplier performance (Phase 2).
- Marketing Team — Creates promotions and loyalty campaigns and measures promotion lift (Phase 2).
- Customer Service Agent — Handles complaints, refunds, and delivery problems by tracing order history (Phase 2).
- Finance Team — Monitors sales, margins, expenses, and payments, and reconciles with accounting systems (Phase 2).
- Platform (Automation Engine) — System-level actor that enforces thresholds, generates alerts, and syncs operational and analytical data.
User journeys
Store staff daily operations
- Store employee logs in on mobile with email/password and MFA.
- Employee lands on the 'today' screen showing tasks, alerts, and low-stock items aggregated in one glance.
- Employee scans a product barcode to check current stock level, batch list (for perishables), and expiry dates.
- For a low-stock item, employee creates a replenishment request; the platform may auto-suggest the request from thresholds.
- Employee completes assigned tasks and checklists, including store opening/closing checklists.
- Employee performs ad-hoc receiving by scanning items, confirming quantities, and recording source (warehouse transfer or direct supplier delivery).
- Employee reports damaged or expired goods, creating stock movements of type damage or expiry with optional photo.
- Employee logs out with all work recorded and synced.
Store manager replenishment approval
- Store manager receives a notification for a new replenishment request.
- Manager opens the replenishment request queue on mobile or desktop.
- Manager reviews the request (product, quantity, requested by, low-stock flag).
- Manager approves or follows up manually; the request status updates accordingly.
- Manager monitors the queue and store-level KPIs on the dashboard.
Executive drill-down
- Executive logs in on desktop and lands on the company-wide dashboard.
- Executive views operational KPIs (stockout rate, waste rate, task completion time, replenishment request queue status).
- Executive drills from company to country to region to store to department to product to transaction.
- Executive traces an alert or metric to the underlying operational record.
- Executive exports or schedules a report for later review.
Functional requirements
FR-001: Mobile 'today' screen for store staff
Store staff log in on mobile and land on a 'today' screen aggregating tasks, alerts, and low-stock items in one glance, so they can start daily work without navigating to separate modules.
Acceptance criteria:
- Given a store employee logs in on mobile, when they land on the 'today' screen, then they see tasks, alerts, and low-stock items aggregated in one glance.
FR-002: Barcode stock check
Store staff scan a product barcode (GTIN/EAN) to check current stock level, with product name and photo displayed on the scan confirmation screen, so they can verify stock without typing.
Acceptance criteria:
- Given a store employee scans a product barcode, when the scan is confirmed, then the system shows product name, photo, current stock level, and batch list for perishables.
FR-003: Replenishment request workflow
Store staff create replenishment requests from low-stock flags, the platform auto-suggests requests from thresholds, and the store manager approves or follows up. Requests sit in a visible store-level queue with status 'submitted, awaiting purchasing module'.
Acceptance criteria:
- Given a store employee creates a replenishment request from a low-stock flag, when the request is submitted, then it appears in the store-level queue with status 'submitted, awaiting purchasing module' and a notification goes to the store manager.
FR-004: Ad-hoc store-level receiving
Store staff perform ad-hoc receiving from any source (warehouse transfer or direct supplier delivery) without requiring a pre-existing transfer record, recording source at receive time, so goods can be received even before Phase 2 warehouse workflows exist.
Acceptance criteria:
- Given a store employee performs ad-hoc receiving, when they scan items and confirm quantities, then StockMovement records of type receipt are created with source recorded as warehouse transfer or direct supplier delivery.
FR-005: Cycle counting with ABC classification
The system generates a rolling cycle count schedule with ABC classification (A items weekly, B monthly, C quarterly) and flags overdue counts, so stock accuracy is maintained continuously without full-store shutdowns.
Acceptance criteria:
- Given a cycle count is performed, when a count correction is submitted, then a StockMovement record of type count_correction is created with the difference from the previous derived level.
FR-006: Damage and expiry reporting
Store staff report damaged or expired products, creating StockMovement records of type damage or expiry with optional photo attachments, so waste is tracked and traceable.
Acceptance criteria:
- Given a store employee reports a damaged or expired product, when the report is submitted, then a StockMovement record of type damage or expiry is created with optional photo attachment.
FR-007: Task and checklist management
Store managers assign tasks to employees with due dates, priorities, statuses, and checklist items; store opening and closing checklists are supported as task templates, so daily work is structured and measurable.
Acceptance criteria:
- Given a store manager assigns a task to an employee, when the employee completes the task and all checklist items, then the task status changes to completed and the completion time is recorded.
FR-008: Configurable alerts and escalation
The system generates alerts for low stock, expired products, missed deliveries, pricing errors, excessive waste, suspicious transactions, unanswered complaints, and staffing shortages, with configurable thresholds and escalation rules across in-app, email, SMS, and push channels.
Acceptance criteria:
- Given a low stock condition is detected, when the stock level falls below the reorder point, then an alert is generated with the configured channel and escalation rules.
FR-009: Role-specific dashboards with drill-down
The system provides role-specific dashboards with drill-down from company to country to region to store to department to product to transaction, so each role sees the KPIs relevant to their goals.
Acceptance criteria:
- Given an executive views the company dashboard, when they drill down from company to country to region to store to department to product to transaction, then each level shows the relevant KPIs and data.
FR-010: Audit logging
The system maintains an audit log of all changes to regulated data (stock movements, replenishment requests, receiving records, user permissions), with user_id, timestamp, entity_type, entity_id, old_value, and new_value, so changes are traceable and compliant.
Acceptance criteria:
- Given a user changes a regulated data record, when the change is saved, then an audit log entry is created with user_id, timestamp, entity_type, entity_id, old_value, and new_value.
FR-011: Soft delete and GDPR erasure
The system supports soft delete for user-facing entities (Product, Supplier, Customer, Task, Promotion) with deleted_at timestamp, while GDPR erasure of PII uses hard delete, so operational records are preserved while privacy is respected.
Acceptance criteria:
- Given a GDPR erasure request, when the request is processed, then PII is hard-deleted while operational records are preserved.
FR-012: Rate limiting and consistent errors
The system applies rate limiting and consistent error responses on every public-ish endpoint, so abuse is prevented and clients can handle failures predictably.
Acceptance criteria:
- Given a client exceeds the rate limit, when the request is made, then the system returns a consistent 429 error response with retry-after header.
FR-013: Timezone-aware storage and localization
The system stores all timestamps in UTC and displays them in the user's local timezone, with all user-facing text localizable, so multi-country operations show correct times and languages.
Acceptance criteria:
- Given a user in a different timezone views a timestamp, when the record is displayed, then the time is shown in the user's local timezone.
FR-014: List and field-level authorization
The system enforces list view-level and field-level authorization so users see only the stores and functions relevant to their roles, with sensitive fields (e.g., supplier cost) visible only to roles with the corresponding permission.
Acceptance criteria:
- Given a store employee views a product list, when the list is rendered, then only products from their assigned store are visible and cost fields are hidden.
FR-015: Multi-language UI and locale formats
The system supports multi-language UI and locale formats (date, time, number, currency) per country, so users in different countries see familiar formats.
Acceptance criteria:
- Given a user in a country with a different locale, when the UI is rendered, then dates, numbers, and currency are formatted according to that country's locale.
FR-016: Full-text search, faceted filters, saved views
The system supports full-text search, faceted filters, and saved searches/views across products, stock, tasks, and other entities, so users can find and return to relevant records quickly.
Acceptance criteria:
- Given a user searches for a product by partial name, when the search is executed, then matching products are returned with faceted filters available.
FR-017: Import/export and bulk operations
The system supports import/export (Excel/CSV) and bulk operations for products, stock counts, and other entities, so large data changes can be made efficiently.
Acceptance criteria:
- Given a user imports a CSV of products, when the import is processed, then valid rows are created and invalid rows are reported with errors.
FR-018: Scheduled reports
The system supports scheduled reports (daily, weekly, monthly) delivered via email or in-app, so stakeholders receive regular updates without manual effort.
Acceptance criteria:
- Given a user schedules a weekly report, when the schedule fires, then the report is generated and delivered via the configured channel.
FR-019: REST API and webhooks
The system exposes a REST API and webhooks for integration with external systems, so POS, WMS, e-commerce, and other systems can exchange data through the canonical integration layer.
Acceptance criteria:
- Given an external system sends a webhook, when the webhook is received, then the payload is validated and processed according to the integration contract.
FR-020: Job queue and scheduled jobs
The system supports a job queue and scheduled jobs for alerting, ETL/CDC, and scheduled reports, so background work is reliable and retryable.
Acceptance criteria:
- Given a scheduled job fails, when the failure occurs, then the job is retried with backoff and the failure is logged.
FR-021: Live updates for stock visibility
The system supports live updates (WebSockets/SSE) for real-time stock visibility and alert delivery, so users see changes without manual refresh.
Acceptance criteria:
- Given a stock movement is created, when the movement is committed, then connected clients receive a live update with the new stock level.
FR-022: Modular boundaries and API contracts
The system defines modular boundaries and API contracts for the 9 capability groups, with the canonical integration layer as the interface to external systems, so modules can evolve independently.
Acceptance criteria:
- Given a capability group changes internally, when the change is deployed, then external API contracts remain stable.
FR-023: Caching strategy
The system implements a caching strategy for near-real-time stock visibility and dashboard performance, with cache invalidation on stock movement creation, so reads are fast while data stays fresh.
Acceptance criteria:
- Given a stock movement is created, when the movement is committed, then the relevant cache entries are invalidated.
FR-024: DB optimization, timeouts, retries, graceful degradation
The system implements DB optimization (indexed FKs and frequently-filtered columns), timeouts and retries on integration calls, and graceful degradation when connectivity is lost, so the platform remains responsive and resilient.
Acceptance criteria:
- Given an integration call times out, when the timeout occurs, then the call is retried with backoff and the user sees a graceful degradation message.
FR-025: Automated testing, CI/CD, feature flags
The system implements automated testing (unit/integration/e2e), CI/CD pipelines, and feature flags with staged rollout, so changes are verified and released safely.
Acceptance criteria:
- Given a code change is committed, when the CI pipeline runs, then unit, integration, and e2e tests execute and block deployment on failure.
FR-026: Authentication and MFA
The system supports email/password login, MFA, password recovery, and SSO (OIDC/SAML) as advisory roadmap guidance, so internal users authenticate securely.
Acceptance criteria:
- Given a user logs in with email/password, when MFA is enabled, then the user is prompted for a second factor before access is granted.
FR-027: RBAC, ABAC, row-level, field-level security
The system supports RBAC, ABAC, row-level security, and field-level security, so access is controlled at the action, row, and field level.
Acceptance criteria:
- Given a user with a role lacking a permission, when they attempt the action, then the action is denied with a 403 error.
FR-028: Accessibility
The system supports keyboard navigation, contrast and typography, and screen reader support, targeting WCAG 2.1 AA compliance, so the platform is usable by all staff.
Acceptance criteria:
- Given a user navigates with a keyboard, when they tab through the UI, then all interactive elements are reachable and focus is visible.
FR-029: Design system and theming
The system implements a design system and theming, so the UI is consistent across modules and can be branded per country or legal entity.
Acceptance criteria:
- Given a theme is applied, when the UI is rendered, then colors, typography, and components follow the theme.
FR-030: Responsive layout for mobile-first workflows
The system implements responsive layout with mobile-first design for frontline staff and desktop for office staff, with large touch targets and barcode scanning as primary input on mobile.
Acceptance criteria:
- Given a store employee uses the app on a phone, when they interact with the UI, then touch targets are large enough and the layout adapts to the screen.
FR-031: Onboarding, navigation, forms, empty/error states
The system implements onboarding, navigation, task completion UX, forms and validation UX, and empty and error states, so users can learn and use the platform effectively.
Acceptance criteria:
- Given a new user logs in, when they complete onboarding, then they can navigate to their primary workflow.
FR-032: All-channel notifications with escalation
The system supports email, SMS, in-app, and push notifications with per-alert-type configuration and escalation rules, so critical alerts reach the right people on the right channel.
Acceptance criteria:
- Given a critical alert is generated, when the escalation rules are evaluated, then the notification is delivered via the configured channels in order.
FR-033: CRUD screens, multi-step workflows, approvals, SLAs, validation, auto-derivations
The system supports CRUD screens, multi-step workflows, approvals, SLAs and escalations, validation rules, and auto-derivations, so operational workflows are structured and enforceable.
Acceptance criteria:
- Given a multi-step workflow is started, when a step is completed, then the next step is enabled and the workflow state persists.
FR-034: Uploads, storage, and versioning
The system supports uploads and storage with versioning for photo attachments and document attachments, so evidence and records are retained with history.
Acceptance criteria:
- Given a user uploads a photo attachment, when the upload completes, then the file is stored and versioned.
FR-035: Dashboards and KPIs
The system supports dashboards and KPIs, with Phase 1 KPIs: stockout rate, waste rate, task completion time, and replenishment request queue status, so operational efficiency is visible.
Acceptance criteria:
- Given a store manager views the dashboard, when the page loads, then the Phase 1 KPIs are displayed with current values.
FR-036: Third-party connectors via canonical layer
The system supports third-party connectors via the canonical integration layer, so external systems integrate through a standardized contract.
Acceptance criteria:
- Given a third-party connector is registered, when a message is sent, then the message conforms to the canonical envelope and is routed correctly.
FR-037: Observability (logs, metrics, traces, SLOs)
The system implements logs, metrics, traces, SLOs, and alerting as advisory roadmap guidance, so operations can monitor and debug the platform.
Acceptance criteria:
- Given a request is processed, when the request completes, then logs, metrics, and traces are emitted.
FR-038: Latency targets
The system implements latency targets (p95/p99) as advisory roadmap guidance: p95 < 200ms for API responses, p99 < 500ms for dashboard queries.
Acceptance criteria:
- Given a dashboard query is executed, when the query completes, then the p99 latency is below 500ms.
FR-039: Collaboration and presence (advisory)
The system implements collaboration and presence as advisory roadmap guidance, so future multi-user editing and presence features have a foundation.
Acceptance criteria:
- Given collaboration features are enabled, when a user edits a record, then other users see presence indicators.
Screen / page inventory
- Login — Authenticate internal users on mobile and desktop.
- Elements: Email/password fields, MFA prompt, Password recovery link, SSO button (advisory)
- Today (store staff mobile) — Aggregate tasks, alerts, and low-stock items in one glance.
- Elements: Task list with due dates and priorities, Alert list with severity, Low-stock items with product name and current stock, Barcode scan button
- Role-specific dashboard (office staff desktop) — Show KPIs and shortcuts tailored to each role.
- Elements: KPI cards (stockout rate, waste rate, task completion time, replenishment request queue status), Drill-down links, Alert list, Recent activity
- Product stock check (mobile) — Scan barcode and show current stock.
- Elements: Barcode scan input, Product name and photo, Current stock level, Batch list (for perishables), Low-stock flag, Replenishment request button
- Replenishment request queue (store manager) — View and manage replenishment requests.
- Elements: Request list with status 'submitted, awaiting purchasing module', Product name and quantity, Requested by, Approve/follow-up buttons, Notification settings
- Store-level receiving (mobile) — Receive goods into the store.
- Elements: Barcode scan input, Product name and photo, Quantity input, Source selector (warehouse transfer or direct supplier delivery), Batch expiry date input (for perishables), Confirm button
- Cycle count (mobile) — Perform stock counts.
- Elements: Count schedule list, Product name and photo, Quantity input, Batch selector (for perishables), Count correction confirmation, Undo button
- Damage/expiry report (mobile) — Report damaged or expired products.
- Elements: Barcode scan input, Product name and photo, Quantity input, Damage type selector (expired, damaged, spoiled, theft), Photo attachment, Confirm button
- Task list and task detail (mobile) — View and complete tasks.
- Elements: Task list with due dates and priorities, Task detail with checklist items, Complete button, Photo attachment, Comment field
- Alert center (mobile and desktop) — View and manage alerts.
- Elements: Alert list with severity and type, Alert detail with related entity, Acknowledge/resolve buttons, Escalation status, Notification channel configuration
- Analytics drill-down (desktop) — Drill from company to country to region to store to department to product to transaction.
- Elements: KPI cards, Drill-down breadcrumbs, Charts, Tables, Export button
- Audit log (desktop) — View audit history.
- Elements: Filterable list of audit entries (user, timestamp, entity, old value, new value), Export button
- Settings (desktop) — Configure country-specific settings, alert thresholds, escalation rules, reorder points, and user permissions.
- Elements: Country selector, Alert threshold configuration, Escalation rule configuration, Reorder point configuration, User/role/permission management
Data model
User
| Field | Type | Notes |
|---|---|---|
user_id |
integer | Primary key, auto-increment. |
email |
string | Unique, indexed, max 255 chars. |
password_hash |
string | Hashed password, never stored in plaintext. |
mfa_enabled |
boolean | Whether multi-factor authentication is enabled. |
is_active |
boolean | Soft disable flag. |
created_at |
datetime | UTC timestamp. |
updated_at |
datetime | UTC timestamp. |
deleted_at |
datetime | Nullable, soft delete timestamp. |
Role
| Field | Type | Notes |
|---|---|---|
role_id |
integer | Primary key, auto-increment. |
name |
string | Unique, indexed, max 100 chars. |
description |
string | Max 500 chars. |
is_active |
boolean | Soft disable flag. |
created_at |
datetime | UTC timestamp. |
updated_at |
datetime | UTC timestamp. |
Permission
| Field | Type | Notes |
|---|---|---|
permission_id |
integer | Primary key, auto-increment. |
name |
string | Unique, indexed, max 100 chars. |
description |
string | Max 500 chars. |
is_active |
boolean | Soft disable flag. |
created_at |
datetime | UTC timestamp. |
updated_at |
datetime | UTC timestamp. |
UserRole
| Field | Type | Notes |
|---|---|---|
user_role_id |
integer | Primary key, auto-increment. |
user_id |
foreign_key->User | Indexed, not null. |
role_id |
foreign_key->Role | Indexed, not null. |
created_at |
datetime | UTC timestamp. |
RolePermission
| Field | Type | Notes |
|---|---|---|
role_permission_id |
integer | Primary key, auto-increment. |
role_id |
foreign_key->Role | Indexed, not null. |
permission_id |
foreign_key->Permission | Indexed, not null. |
created_at |
datetime | UTC timestamp. |
Employee
| Field | Type | Notes |
|---|---|---|
employee_id |
integer | Primary key, auto-increment. |
user_id |
foreign_key->User | Unique, indexed, not null. Employee is a profile of User; never holds role_id or permission enum. |
store_id |
foreign_key->Store | Nullable for warehouse/office staff. |
department |
string | Max 100 chars. |
job_title |
string | Max 100 chars. |
employment_type |
enum(full_time, part_time, contract) | Valid values: full_time, part_time, contract. |
is_active |
boolean | Soft disable flag. |
created_at |
datetime | UTC timestamp. |
updated_at |
datetime | UTC timestamp. |
Country
| Field | Type | Notes |
|---|---|---|
country_id |
integer | Primary key, auto-increment. |
name |
string | Unique, indexed, max 100 chars. |
iso_code |
string | Unique, indexed, 2-3 chars (ISO 3166). |
currency_code |
string | 3 chars (ISO 4217). |
language_code |
string | 2-5 chars (BCP 47). |
data_residency_region |
string | Region where PII must be stored (e.g., EU, US). |
is_active |
boolean | Soft disable flag. |
created_at |
datetime | UTC timestamp. |
updated_at |
datetime | UTC timestamp. |
Store
| Field | Type | Notes |
|---|---|---|
store_id |
integer | Primary key, auto-increment. |
name |
string | Indexed, max 200 chars. |
country_id |
foreign_key->Country | Indexed, not null. |
region |
string | Max 100 chars. |
address |
string | Max 500 chars. |
timezone |
string | IANA timezone name (e.g., Europe/Paris). |
store_type |
enum(supermarket, convenience, online_only) | Valid values: supermarket, convenience, online_only. |
phone_main |
string | Max 30 chars. |
phone_mobile |
string | Max 30 chars. |
phone_fax |
string | Max 30 chars. |
phone_emergency |
string | Max 30 chars. |
is_active |
boolean | Soft disable flag. |
created_at |
datetime | UTC timestamp. |
updated_at |
datetime | UTC timestamp. |
deleted_at |
datetime | Nullable, soft delete timestamp. |
Warehouse
| Field | Type | Notes |
|---|---|---|
warehouse_id |
integer | Primary key, auto-increment. |
name |
string | Indexed, max 200 chars. |
country_id |
foreign_key->Country | Indexed, not null. |
address |
string | Max 500 chars. |
timezone |
string | IANA timezone name. |
storage_capacity |
integer | Capacity in cubic meters. |
temperature_zones |
string | Comma-separated list: frozen, chilled, ambient. |
is_active |
boolean | Soft disable flag. |
created_at |
datetime | UTC timestamp. |
updated_at |
datetime | UTC timestamp. |
deleted_at |
datetime | Nullable, soft delete timestamp. |
Product
| Field | Type | Notes |
|---|---|---|
product_id |
integer | Primary key, auto-increment. |
parent_product_id |
foreign_key->Product | Nullable, self-reference for variant grouping. |
name |
string | Indexed, max 200 chars. |
description |
string | Max 2000 chars. |
brand_id |
foreign_key->Brand | Indexed, not null. |
category_id |
foreign_key->Category | Indexed, not null. |
supplier_id |
foreign_key->Supplier | Indexed, not null. |
barcode |
string | GTIN/EAN, 8-14 digits, unique per country. |
unit_of_measure |
string | e.g., each, kg, liter. |
is_perishable |
boolean | Whether batch tracking applies. |
is_active |
boolean | Soft disable flag. |
created_at |
datetime | UTC timestamp. |
updated_at |
datetime | UTC timestamp. |
deleted_at |
datetime | Nullable, soft delete timestamp. |
Brand
| Field | Type | Notes |
|---|---|---|
brand_id |
integer | Primary key, auto-increment. |
name |
string | Unique, indexed, max 100 chars. |
created_at |
datetime | UTC timestamp. |
updated_at |
datetime | UTC timestamp. |
Category
| Field | Type | Notes |
|---|---|---|
category_id |
integer | Primary key, auto-increment. |
name |
string | Unique, indexed, max 100 chars. |
created_at |
datetime | UTC timestamp. |
updated_at |
datetime | UTC timestamp. |
Supplier
| Field | Type | Notes |
|---|---|---|
supplier_id |
integer | Primary key, auto-increment. |
name |
string | Indexed, max 200 chars. |
contact_name |
string | Max 100 chars. |
contact_email |
string | Max 255 chars. |
phone_main |
string | Max 30 chars. |
phone_mobile |
string | Max 30 chars. |
phone_fax |
string | Max 30 chars. |
phone_emergency |
string | Max 30 chars. |
payment_terms |
string | Max 200 chars. |
lead_time_days |
integer | Expected lead time in days. |
country_id |
foreign_key->Country | Indexed, not null. |
is_active |
boolean | Soft disable flag. |
created_at |
datetime | UTC timestamp. |
updated_at |
datetime | UTC timestamp. |
deleted_at |
datetime | Nullable, soft delete timestamp. |
StockMovement
| Field | Type | Notes |
|---|---|---|
movement_id |
integer | Primary key, auto-increment. |
product_id |
foreign_key->Product | Indexed, not null. |
location_type |
enum(store, warehouse) | Valid values: store, warehouse. Polymorphic pair with location_id. |
location_id |
integer | ID of Store or Warehouse depending on location_type. |
batch_id |
foreign_key->Batch | Nullable, for perishable products. |
movement_type |
enum(receipt, sale, transfer_in, transfer_out, damage, count_correction, expiry) | Valid values: receipt, sale, transfer_in, transfer_out, damage, count_correction, expiry. |
quantity |
integer | Signed quantity; positive for inflows, negative for outflows. |
timestamp |
datetime | UTC timestamp of the movement. |
source |
enum(POS, manual, WMS) | Valid values: POS, manual, WMS. |
user_id |
foreign_key->User | Nullable, user who recorded the movement. |
created_at |
datetime | UTC timestamp. |
updated_at |
datetime | UTC timestamp. |
Batch
| Field | Type | Notes |
|---|---|---|
batch_id |
integer | Primary key, auto-increment. |
product_id |
foreign_key->Product | Indexed, not null. |
receipt_id |
foreign_key->StockMovement | References the receipt StockMovement that created this batch. |
expiry_date |
datetime | Expiry date for perishable products. |
received_quantity |
integer | Quantity received in this batch. |
remaining_quantity |
integer | Derived from StockMovement sum for this batch. |
location_type |
enum(store, warehouse) | Valid values: store, warehouse. Polymorphic pair with location_id. |
location_id |
integer | ID of Store or Warehouse depending on location_type. |
created_at |
datetime | UTC timestamp. |
updated_at |
datetime | UTC timestamp. |
ReplenishmentRequest
| Field | Type | Notes |
|---|---|---|
request_id |
integer | Primary key, auto-increment. |
product_id |
foreign_key->Product | Indexed, not null. |
store_id |
foreign_key->Store | Indexed, not null. |
quantity |
integer | Requested quantity, positive. |
status |
enum(submitted, awaiting_purchasing_module, approved, rejected) | Valid values: submitted, awaiting_purchasing_module, approved, rejected. |
requested_by |
foreign_key->Employee | Indexed, not null. |
approved_by |
foreign_key->Employee | Nullable, set when approved. |
created_at |
datetime | UTC timestamp. |
updated_at |
datetime | UTC timestamp. |
deleted_at |
datetime | Nullable, soft delete timestamp. |
ReceivingRecord
| Field | Type | Notes |
|---|---|---|
receiving_id |
integer | Primary key, auto-increment. |
store_id |
foreign_key->Store | Indexed, not null. |
source |
enum(warehouse_transfer, direct_supplier_delivery) | Valid values: warehouse_transfer, direct_supplier_delivery. |
received_by |
foreign_key->Employee | Indexed, not null. |
received_at |
datetime | UTC timestamp. |
notes |
string | Max 2000 chars. |
created_at |
datetime | UTC timestamp. |
updated_at |
datetime | UTC timestamp. |
ReceivingLine
| Field | Type | Notes |
|---|---|---|
receiving_line_id |
integer | Primary key, auto-increment. |
receiving_id |
foreign_key->ReceivingRecord | Indexed, not null. |
product_id |
foreign_key->Product | Indexed, not null. |
batch_id |
foreign_key->Batch | Nullable, for perishable products. |
quantity |
integer | Received quantity, positive. |
expiry_date |
datetime | Nullable, for perishable products. |
created_at |
datetime | UTC timestamp. |
updated_at |
datetime | UTC timestamp. |
Task
| Field | Type | Notes |
|---|---|---|
task_id |
integer | Primary key, auto-increment. |
title |
string | Max 200 chars. |
description |
string | Max 2000 chars. |
assigned_to |
foreign_key->Employee | Indexed, not null. |
store_id |
foreign_key->Store | Nullable, for store tasks. |
warehouse_id |
foreign_key->Warehouse | Nullable, for warehouse tasks. |
due_date |
datetime | UTC timestamp. |
priority |
enum(low, medium, high, critical) | Valid values: low, medium, high, critical. |
status |
enum(open, in_progress, completed, overdue, cancelled) | Valid values: open, in_progress, completed, overdue, cancelled. |
created_at |
datetime | UTC timestamp. |
updated_at |
datetime | UTC timestamp. |
deleted_at |
datetime | Nullable, soft delete timestamp. |
ChecklistItem
| Field | Type | Notes |
|---|---|---|
checklist_item_id |
integer | Primary key, auto-increment. |
task_id |
foreign_key->Task | Indexed, not null. |
description |
string | Max 500 chars. |
is_completed |
boolean | Completion flag. |
completed_at |
datetime | Nullable, UTC timestamp. |
completed_by |
foreign_key->Employee | Nullable. |
created_at |
datetime | UTC timestamp. |
updated_at |
datetime | UTC timestamp. |
Notification
| Field | Type | Notes |
|---|---|---|
notification_id |
integer | Primary key, auto-increment. |
user_id |
foreign_key->User | Indexed, not null. |
alert_type |
enum(low_stock, expired_product, missed_delivery, pricing_error, excessive_waste, suspicious_transaction, unanswered_complaint, staffing_shortage) | Valid values: low_stock, expired_product, missed_delivery, pricing_error, excessive_waste, suspicious_transaction, unanswered_complaint, staffing_shortage. |
channel |
enum(in_app, email, sms, push) | Valid values: in_app, email, sms, push. |
status |
enum(pending, sent, delivered, failed) | Valid values: pending, sent, delivered, failed. |
escalation_level |
integer | 1, 2, or 3. |
related_entity_type |
string | Entity type this notification relates to. |
related_entity_id |
integer | ID of the related entity. |
created_at |
datetime | UTC timestamp. |
sent_at |
datetime | Nullable, UTC timestamp. |
delivered_at |
datetime | Nullable, UTC timestamp. |
AuditLog
| Field | Type | Notes |
|---|---|---|
audit_id |
integer | Primary key, auto-increment. |
user_id |
foreign_key->User | Indexed, not null. |
timestamp |
datetime | UTC timestamp. |
entity_type |
string | Entity type that was changed. |
entity_id |
integer | ID of the changed entity. |
action |
enum(create, update, delete, approve, reject) | Valid values: create, update, delete, approve, reject. |
old_value |
json | JSON snapshot of the previous state. |
new_value |
json | JSON snapshot of the new state. |
created_at |
datetime | UTC timestamp. |
PurchaseOrder
| Field | Type | Notes |
|---|---|---|
po_id |
integer | Primary key, auto-increment. Phase 2 entity. |
supplier_id |
foreign_key->Supplier | Indexed, not null. |
warehouse_id |
foreign_key->Warehouse | Indexed, not null. |
order_date |
datetime | UTC timestamp. |
expected_delivery_date |
datetime | UTC timestamp. |
status |
enum(draft, submitted, confirmed, received, cancelled) | Valid values: draft, submitted, confirmed, received, cancelled. |
total_cost |
decimal | Derived from line items. |
currency_code |
string | 3 chars (ISO 4217). |
created_at |
datetime | UTC timestamp. |
updated_at |
datetime | UTC timestamp. |
Delivery
| Field | Type | Notes |
|---|---|---|
delivery_id |
integer | Primary key, auto-increment. Phase 2 entity. |
po_id |
foreign_key->PurchaseOrder | Indexed, not null. |
actual_delivery_date |
datetime | Nullable, UTC timestamp. |
received_by |
foreign_key->Employee | Nullable. |
status |
enum(scheduled, in_transit, received, delayed, missed) | Valid values: scheduled, in_transit, received, delayed, missed. |
notes |
string | Max 2000 chars. |
created_at |
datetime | UTC timestamp. |
updated_at |
datetime | UTC timestamp. |
Promotion
| Field | Type | Notes |
|---|---|---|
promotion_id |
integer | Primary key, auto-increment. Phase 2 entity. |
name |
string | Max 200 chars. |
start_date |
datetime | UTC timestamp. |
end_date |
datetime | UTC timestamp. |
discount_type |
enum(percentage, fixed_amount, bogo) | Valid values: percentage, fixed_amount, bogo. |
discount_value |
decimal | Discount amount or percentage. |
approval_status |
enum(draft, pending_approval, approved, rejected, active, ended) | Valid values: draft, pending_approval, approved, rejected, active, ended. |
created_by |
foreign_key->User | Indexed, not null. |
approved_by |
foreign_key->User | Nullable. |
created_at |
datetime | UTC timestamp. |
updated_at |
datetime | UTC timestamp. |
deleted_at |
datetime | Nullable, soft delete timestamp. |
Customer
| Field | Type | Notes |
|---|---|---|
customer_id |
integer | Primary key, auto-increment. Phase 2 entity. |
loyalty_card_number |
string | Unique, nullable, indexed. |
name |
string | Max 200 chars. |
email |
string | Max 255 chars. |
phone |
string | Max 30 chars. |
country_id |
foreign_key->Country | Indexed, not null. |
created_at |
datetime | UTC timestamp. |
updated_at |
datetime | UTC timestamp. |
deleted_at |
datetime | Nullable, soft delete timestamp. |
LoyaltyAccount
| Field | Type | Notes |
|---|---|---|
loyalty_id |
integer | Primary key, auto-increment. Phase 2 entity. |
customer_id |
foreign_key->Customer | Indexed, not null. |
points_balance |
integer | Current loyalty points. |
tier |
enum(bronze, silver, gold) | Valid values: bronze, silver, gold. |
joined_date |
datetime | UTC timestamp. |
is_active |
boolean | Soft disable flag. |
created_at |
datetime | UTC timestamp. |
updated_at |
datetime | UTC timestamp. |
Order
| Field | Type | Notes |
|---|---|---|
order_id |
integer | Primary key, auto-increment. Phase 2 entity. |
customer_id |
foreign_key->Customer | Nullable for anonymous in-store purchases. |
store_id |
foreign_key->Store | Nullable for online orders. |
order_type |
enum(in_store, online, click_and_collect, home_delivery) | Valid values: in_store, online, click_and_collect, home_delivery. |
order_date |
datetime | UTC timestamp. |
status |
enum(placed, picking, packed, shipped, delivered, cancelled, refunded) | Valid values: placed, picking, packed, shipped, delivered, cancelled, refunded. |
total_amount |
decimal | Order total. |
currency_code |
string | 3 chars (ISO 4217). |
payment_status |
string | Payment status from provider. |
created_at |
datetime | UTC timestamp. |
updated_at |
datetime | UTC timestamp. |
OrderLine
| Field | Type | Notes |
|---|---|---|
order_line_id |
integer | Primary key, auto-increment. Phase 2 entity. |
order_id |
foreign_key->Order | Indexed, not null. |
product_id |
foreign_key->Product | Indexed, not null. |
quantity |
integer | Ordered quantity. |
unit_price |
decimal | Price per unit. |
subtotal |
decimal | quantity * unit_price. |
status |
enum(picked, missing, substituted, returned) | Valid values: picked, missing, substituted, returned. |
created_at |
datetime | UTC timestamp. |
updated_at |
datetime | UTC timestamp. |
Shift
| Field | Type | Notes |
|---|---|---|
shift_id |
integer | Primary key, auto-increment. |
employee_id |
foreign_key->Employee | Indexed, not null. |
store_id |
foreign_key->Store | Indexed, not null. |
start_time |
datetime | UTC timestamp. |
end_time |
datetime | UTC timestamp. |
role |
enum(cashier, stocker, department_lead, manager) | Valid values: cashier, stocker, department_lead, manager. |
created_at |
datetime | UTC timestamp. |
updated_at |
datetime | UTC timestamp. |
ExchangeRate
| Field | Type | Notes |
|---|---|---|
exchange_rate_id |
integer | Primary key, auto-increment. |
from_currency |
string | 3 chars (ISO 4217). |
to_currency |
string | 3 chars (ISO 4217). |
rate |
decimal | Exchange rate. |
effective_date |
datetime | UTC timestamp when the rate applies. |
created_at |
datetime | UTC timestamp. |
Business rules
- Batch consumption follows FEFO (first-expired-first-out) for perishables: when a sale or transfer reduces batch quantity, the system consumes from the batch with the earliest expiry_date first. Enforced in StockMovement creation logic.
- Stock level must never be negative. Any StockMovement that would result in a negative derived level is rejected with an error. Count corrections that reveal negative stock trigger an alert for investigation.
- Expiry alert triggers when a batch has expiry_date within N days (configurable, default 7) and the batch's remaining quantity > 0. For non-perishables without batch tracking, no expiry alert is generated.
- Low stock flag is derived by comparing current stock level to a reorder point per product-location. Reorder point is a configurable threshold, defaulting to a fixed quantity or days-of-supply based on recent sales velocity.
- Replenishment requests are created by store staff from low-stock flags, auto-suggested by the platform based on thresholds, and approved or followed up by the store manager. Human approval step retained.
- Batch-level count corrections for perishables are applied to the batch with the earliest expiry_date. This preserves batch accuracy for the stock most likely to expire soon and does not require the counter to identify a specific batch.
- Pricing error alert triggers when a price change exceeds X% (configurable, default 20%) from the previous price, or when a price is below cost. The alert includes product, old price, new price, and the user who made the change.
- Suspicious transaction alert triggers when a refund exceeds X (configurable, default 500 in local currency), or when a single employee processes more than Y refunds (configurable, default 10) in a day, or when a refund is processed without a matching order.
- Staffing shortage alert triggers when scheduled hours for a store on a given day fall below X% (configurable, default 80%) of the required hours for that store's expected workload. Required hours are derived from historical sales volume and task load.
- A promotion cannot become active until it has approval_status = approved. Approval requires a user with the approve_promotion permission. The approver cannot be the same user who created the promotion.
- A delivery is marked as delayed when actual_delivery_date > expected_delivery_date. A delivery is marked as missed when actual_delivery_date is null and current_date > expected_delivery_date + grace_period (configurable, default 1 day). Both trigger alerts with escalation rules.
- Order total = sum(OrderLine.subtotal) - sum(OrderLine.discount) + tax + delivery_fee. OrderLine.subtotal = quantity * unit_price. Discounts are applied per line item, not at the order level, unless a promotion specifies order-level discount.
Permissions
| Role | Capabilities |
|---|---|
| Store Employee | view stock levels; scan products; create replenishment requests; complete assigned tasks and checklists; perform ad-hoc receiving; report damage/expiry |
| Store Manager | inherit Store Employee permissions; approve replenishment requests; assign tasks; view store-level dashboards; configure store-level alert thresholds |
| Warehouse Staff | view stock levels; perform receiving; perform picking; perform transfers; perform replenishment |
| Warehouse Manager | inherit Warehouse Staff permissions; approve transfers; view warehouse-level dashboards; configure warehouse-level alert thresholds |
| Purchasing Team | view supplier data; create purchase orders; view delivery schedules; view supplier performance |
| Marketing Team | create promotions; view promotion lift; view loyalty campaign data |
| Customer Service Agent | view customer data; view order history; view complaints; view refunds |
| Finance Team | view sales; view margins; view expenses; view payments; view reconciliation data |
| Regional Manager | view all stores in their region; compare store performance; drill down to store level |
| Executive | view company-wide data; view all countries; view all regions; view all stores; read-only access to dashboards and drill-downs |
| Platform (Automation Engine) | enforce thresholds; generate alerts; sync data; service-level access only |
Integrations
- POS systems: receive sales transactions via canonical integration layer (store_id, product_id, quantity, unit_price, timestamp, payment method).
- WMS systems: receive stock movements (receipts, picks, transfers) via canonical integration layer where a mature WMS exists.
- E-commerce platforms: receive online orders via webhooks (customer_id, order lines, delivery address, order type).
- Supplier feeds: receive product catalogs, price lists, and delivery schedules via canonical integration layer or webhooks.
- Accounting systems: send sales summaries, margin calculations, and payment reconciliations via canonical integration layer.
- Delivery partner systems: receive delivery status updates via webhooks (order_id, status, timestamp, failure reason).
- Payment providers: receive payment confirmations and refund statuses via webhooks (order_id, payment_id, amount, currency, status).
Non-functional requirements
- Offline support: service worker architecture, local caching, queued writes for simple actions, server-side conflict resolution for stock movements, staleness indicators on all stock displays.
- ETL/CDC pipeline: data quality checks, schema versioning, reconciliation between operational and analytical stores, near-real-time for operational workflows, batch ETL for analytics.
- Timezone-aware storage: all timestamps stored in UTC, displayed in user's local timezone, localization readiness for all user-facing text.
- Rate limiting and consistent error responses on every public-ish endpoint.
- Accessibility: keyboard navigation, contrast and typography, screen reader support, WCAG 2.1 AA compliance target.
- Responsive layout: mobile-first for frontline staff, desktop for office staff, large touch targets, barcode scanning as primary input, minimal typing on mobile.
- Caching strategy: near-real-time stock visibility with explicit staleness indicators, cache invalidation on stock movement creation.
- DB optimization: indexed foreign-key columns and frequently-filtered columns, timeouts and retries on integration calls, graceful degradation when connectivity is lost.
- Automated testing (unit/integration/e2e), CI/CD pipelines, feature flags and staged rollout.
- Latency targets (advisory): p95 < 200ms for API responses, p99 < 500ms for dashboard queries.
- Observability (advisory): logs, metrics, traces, SLOs and alerting.
- Data residency and GDPR: PII stored in-region per Country.data_residency_region, GDPR erasure workflow with hard delete of PII while preserving operational records, consent management.
Edge cases
- User imports CSV with 50,000 rows: the system must process the import asynchronously via the job queue, report invalid rows with errors, and not block the UI.
- Store employee is offline when completing a task: the action is queued locally and synced when connectivity is restored, with conflict resolution for stock movements.
- A batch has expiry_date within 7 days but remaining_quantity is 0: no expiry alert is generated because the batch is empty.
- A replenishment request is created but the store manager never approves it: the request remains in the queue with status 'submitted, awaiting purchasing module' and is visible for follow-up.
- A store employee scans a barcode that does not exist in the product catalog: the system shows an error and offers to create a new product or retry the scan.
- A count correction would result in negative stock: the correction is rejected with an error and an alert is triggered for investigation.
- A delivery is marked as missed but the supplier delivered late: the delivery status is 'missed' and a missed-delivery alert is generated with escalation rules.
- A GDPR erasure request is received for a customer with purchase history: PII is hard-deleted while operational records (orders, stock movements) are preserved with anonymized customer reference.
Out of scope
- POS system (integrate only, do not replace).
- E-commerce platform (integrate only, do not host storefront).
- Accounting system (integrate only, do not maintain ledger).
- Payroll/HR system (out of scope).
- Full WMS replacement (lightweight workflows only, integrate with mature WMS).
- External-facing portals for customers or suppliers in the initial release.
- Warehouse receiving in Phase 1.
- Purchasing fulfillment in Phase 1.
- Supplier performance score (Phase 2 context only).
- Replenishment cycle time (hidden from Phase 1 dashboards).
- Customer self-service portal.
- Supplier self-service portal.
- Native mobile apps (deferred unless barcode scanning or push reliability demands).
- Advanced predictive analytics (demand forecasting, ML-based replenishment).
Assumptions & open items
Assumed:
- The spec map covers the full product vision, with Phase 1 detailed and later phases summarized.
- Functional requirements are written at the user-story level (FR-001, FR-002, etc.), each testable and traceable.
- The permissions section specifies what each role can do at the action level per entity, with row-level and field-level security noted.
- Acceptance criteria are written in Given/When/Then format, observable and testable.
- The out of scope section mirrors all rejected scope from earlier boards.
- The assumptions section captures every unconfirmed claim being relied on.
- Strict operating model alignment, with no new roles, entities, or stages introduced without a decision element.
- Phase 1 KPIs only (stockout rate, waste rate, task completion time, replenishment request queue status), with Supplier Performance Score and Replenishment Cycle Time excluded from Phase 1 dashboards.
- Alert thresholds default to 7 days for expiry, 20% for pricing errors, 500 local currency for suspicious refunds, 10 refunds per employee per day, 80% for staffing shortage.
- Latency targets are p95 < 200ms for API responses, p99 < 500ms for dashboard queries.
- WCAG 2.1 AA compliance target for accessibility.
- Exchange rates are stored as snapshots at transaction time for cross-country reporting.
Coverage notes
- Functional requirements: 39 (with acceptance criteria: 39)
- Open assumptions: 12 (unresolved/conflicted: 0)
- Entities in data model: 31
- Screens: 13, Roles: 10, Journeys: 3