PLMP Employee Web — User Manual
This manual documents every screen in apps/employee-web, the internal staff application for the Product Lifecycle Management Platform (PLMP). It is organized by workspace — the left-hand navigation area you switch between using the “Workspace” dropdown at the top of the app’s own left nav — and then by individual screen. Every screenshot on this page is a real capture taken from the running local application, signed in as a full-access demo account.
A note on permissions. employee-web does not show or hide screens based on role — every signed-in employee can open every screen; the backend API enforces permission and two-factor rules when a button is actually clicked. Several privileged write actions in this manual were attempted live and rejected by the API with the exact on-screen message quoted, which is expected, correct behavior for this demo account’s session — not a bug.
Signing In & Always-Available Utilities
The login gate every employee passes through, plus the Search and My Work utilities available from the top bar regardless of workspace, plus the shared Home landing screen.
Employee Login
Purpose
Lets you sign in to the system with your work email and password.
What you see
- A “PLMP Employee Web” title/logo.
- An “Employee Login” form with Email and Password fields.
- An inline error message if sign-in fails (e.g. “Invalid email or password.”).
What you can do
- Enter your email and password and press Sign in. This calls
POST /login. Two outcomes are possible: - If the account does not have two-factor authentication turned on, a successful password check signs you in immediately.
- If the account has two-factor authentication confirmed, the server returns
two_factor: trueand you are shown the Two-Factor Authentication screen instead — you are not yet signed in at this point.
Related screens
Leads to the Two-Factor Authentication screen (if 2FA is on) and then into whichever workspace/screen the app defaults to (Operations → Home).
Constraints to know
A wrong email/password shows a generic “Invalid email or password.” message; it does not tell you which field was wrong (standard security practice, not a bug).
Two-Factor Authentication
Purpose
Completes sign-in for accounts protected by two-factor authentication, by asking for a one-time code.
What you see
- A prompt asking for the code from your authenticator app (or, if switched, a recovery code).
- A single code input field.
- An inline error if the code is rejected.
What you can do
- Enter your 6-digit authenticator code and press Verify — calls
POST /two-factor-challengewith{ code }. - Click Use a recovery code instead to switch the form to accept a one-time recovery code (
{ recovery_code }). You can toggle back with Use an authenticator code instead.
Related screens
On success, proceeds into the app exactly like a normal sign-in.
Constraints to know
Until this step succeeds, the session is not authenticated — refreshing or navigating away leaves you back at the login form. Full enrollment/management of two-factor authentication is not part of this app — only the login-time challenge exists here.
Global Search
Purpose
Lets you quickly find a specific Customer, Product, Asset, Quote, Order, Purchase Order, Service Ticket, or Return Request by typing an ID, without needing to know which workspace it lives in.
What you see
- A single search box (“Search by id…”).
- After searching, results grouped into cards by record type, each a two-column table: ID and Details (every other field, shown generically as
key: valuepairs). - An empty-state message (“No results for '...'.”) if nothing matches.
What you can do
Type a value and press Search (or Enter) — calls GET /search, a single read-only endpoint that fans out across the Customer, Product, Asset, Quote, Order, Purchase Order, Service Ticket, and Return Request lookups.
Related screens
None directly clickable — results are raw data in-page; you take the ID you find here and look the record up in its own workspace screen.
Constraints to know
This screen deliberately has no domain-specific knowledge of what a record “looks like” — it renders every field generically. It matches by internal ID, not a human-facing order/invoice number.
My Work
Genuinely empty for this account — expected behavior, not a bugPurpose
Gives you a single place to see the Service Tickets assigned to you and any approval requests waiting on your decision, instead of checking each module separately.
What you see
- My Service Tickets card: Ticket, Description, Priority (badge), Status (badge).
- My Approvals card: Type, Action, Requested by, Requested (timestamp).
What you can do
Filter your own ticket list with the Filter my tickets… search box. Both tables load via a single call to GET /my-work; there are no action buttons on this screen.
Related screens
None directly linked — you'd go to the Dispatch workspace's Service Dispatch screen to act on a ticket.
Constraints to know
The My Approvals table is genuinely, correctly empty for everyone today — no domain has yet wired a real approval workflow through the underlying approval mechanism.
Home
Purpose
A simple landing/boot-verification screen confirming the system is up, rather than a business dashboard.
What you see
A “Welcome back” heading and a “Platform status” card with four badge rows: Liveness, Readiness, Database, and Redis.
What you can do
Nothing beyond viewing — it calls GET /health/live and GET /health/ready on load. No buttons or filters.
Related screens
None — from here you switch workspaces using the nav dropdown.
Constraints to know
This screen carries no business functionality — it is infrastructure health-check content only.
Operations Workspace
General staff day-to-day master-data and lookup screens: Customers, Products, Assets, Orders.
Customers List
Purpose
Lets you find a customer account by number, name, or organisation, and register a brand-new customer.
What you see
- A “Search customers” card with a text box and Search button.
- A list of customer cards: customer number as the bold heading, the customer's name/organisation as the subtitle, Status as a colored badge, and Type as a small fact. Each card is one click target — there's no separate row/button layout anymore.
- A “Register customer” card below.
What you can do
- Search — submits to
GET /customers?q=.... - Click a card — opens the Customer 360 detail view (replaces the old per-row “View 360” button; clicking anywhere on the card does the same thing).
- Register customer form — Given name, Family name, Account type, and (if Organisation) Organisation legal name/registration number; submits to
POST /customers.
Related screens
Clicking a card opens Customer 360; its Related section links conceptually to Assets, viewed fully on the Assets screen. The Assets screen's own “Register asset” form can now search for this customer by name/number instead of requiring you to already know its raw id.
Constraints to know
Registering a customer is privileged — requires domains.customer.register and confirmed two-factor authentication. Live-tested here: this demo account's recovery-code-based session was rejected with “You do not have permission to register customers, or two-factor authentication has not been confirmed for your account.” — a real, expected constraint, not a bug.
Customer 360
Purpose
Shows everything currently known about one customer account in one place, including the assets they own.
What you see
- A detail panel titled with the Customer number, subtitled “Customer account — individual/organisation”, with a status badge.
- Overview table: ID, Customer number, Account type, Status.
- Related section: a table of the customer's owned Assets, loaded from
GET /assets?owner_customer_account_id=..., or “No owned assets.” if none.
What you can do
View only — no edit or status-change buttons in the current build.
Related screens
Reached from the Customers list; its Related-assets table doesn't link onward — look the same asset numbers up on the Assets screen for full detail.
Constraints to know
This is read-only — there is no way to edit a customer's details or change their status from this screen.
Products List
Purpose
Lets you find a product SKU by code/name, model, or brand, and register a new SKU.
What you see
- A “Search products” card with a text box and Search button.
- A list of product cards: SKU code as the heading, SKU name as the subtitle, Model and Brand as small facts. Click anywhere on a card to open its 360 view.
- A “Register product” card below.
What you can do
- Search — calls
GET /products?q=.... - Click a card — opens the Product 360 detail view (replaces the old per-row “View 360” button).
- Register product form — Brand name, Model code, Model name, SKU code, SKU name, Serialization policy; submits to
POST /products.
Related screens
Clicking a card opens the Product 360, whose Related section shows Assets of this SKU. Several other screens (Assets, Sales Quotes, Inventory) can now search for a specific SKU by this same code/name instead of requiring a raw id.
Constraints to know
Registering a product is privileged — requires domains.product.register and confirmed two-factor authentication. Live-tested here: attempting to register “Manual Demo Brand” with this demo account's recovery-code session was rejected with “You do not have permission to register products, or two-factor authentication has not been confirmed for your account.” This is the real, on-screen error text for this permission-gated action.
Product 360
Purpose
Shows the full definition of one SKU and every physical Asset that has been created against it.
What you see
- Detail panel titled with the SKU name, subtitled “Brand — Model (SKU code)”, with the Serialization policy shown as a badge.
- Overview table: ID, Brand, Model code, Model name, SKU code, SKU name, Serialization policy.
- Related section: a table of Assets of this SKU, loaded from
GET /assets?sku_id=..., or “No assets of this SKU yet.”
What you can do
View only — no edit or action buttons.
Related screens
Reached from the Products list; conceptually related to the Assets screen.
Constraints to know
Read-only — no way to change a SKU's fields, retire it, or otherwise act on it from this screen.
Assets List
Purpose
Lets you look up a specific physical Asset by its asset number, and register a newly-created Asset against a product model/SKU and owner.
What you see
- A “Search assets” card with a text box and Search button.
- A list of asset cards: asset number as the heading, the owner's name as the subtitle, Lifecycle status as a badge. Click anywhere on a card to open its 360 view.
- A “Register asset” card below, now with two search-and-select fields instead of raw id boxes (see “What you can do”).
What you can do
- Search — calls
GET /assets?q=.... - Click a card — opens the Asset 360 detail view.
- Register asset form — a single “Product / SKU” search field (type a SKU code, SKU name, or model name; picking a result fills both the product model id and the SKU id together — there is no way to submit a model with no specific SKU through this field), and an “Owner customer account id” search field (type a customer number or name); submits to
POST /assets.
Related screens
Click a card to open the Asset 360. Assets also appear as “Related” data inside the Customer 360 and Product 360 screens.
Constraints to know
Registering an asset is privileged (domains.asset.register + confirmed two-factor). The Product / SKU and Owner customer account fields are searchable pickers, not free-typed text — you can no longer submit an unverified raw id here. If the product/SKU or customer you need doesn't show up in the search, it needs to be registered on the Products or Customers screen first.
Asset 360
Purpose
Gives the full identity and history picture for one physical asset — what it is, who owns it, its serial/identifier numbers, and where it has been located over time.
What you see
- Detail panel titled with the Asset number, subtitled “Product model {id}”, with the Lifecycle status shown as a badge.
- Overview table: ID, Asset number, Product model id, SKU id, Lifecycle status, Owner customer account id.
- Related — Identifiers table: Type, Value, Primary flag (supports multi-identifier/componentized assets).
- Related — Location assignments table: Service location, Effective from, Effective to (“Current” if still open-ended).
What you can do
View only — no buttons to edit the asset, change ownership, or add identifiers/locations from this screen.
Related screens
Reached from the Assets list, and reachable indirectly via Customer 360 / Product 360 “Related” tables (which don't link directly — re-search by asset number here).
Constraints to know
This view is read-only history — it correctly shows multiple identifiers and a location history rather than a single current location, consistent with Asset identity/ownership/location all being effective-dated and never overwritten in place.
Orders List
Purpose
Lets you see every Sales Order and its current status, and filter down to orders in a particular state.
What you see
- A “Sales orders” card with a Filter by status dropdown (All / Draft / Pending validation / Confirmed / On hold / Cancelled).
- A list of order cards: order number as the heading, customer name as the subtitle, Status as a badge. Click anywhere on a card to open its detail view.
What you can do
- Change the status filter — reloads from
GET /orders?status_code=.... - Click a card — opens the order's detail view (replaces the old per-row “View” button).
Related screens
Clicking a card opens Order Detail. Orders originate from an accepted Quote in the Sales workspace's Quotes screen.
Constraints to know
This list only reads existing orders — it has no “create order” button; new orders are created from an accepted Quote, not from here.
Order Detail & Lifecycle Actions
Purpose
Lets you see one order's full status and history, and carry out controlled lifecycle actions — confirm, hold/release, cancel, or record a formal amendment — without ever freely typing a new status.
What you see
- Detail panel titled with the Order number, subtitled with the Customer account ID, with a status badge.
- Overview table: ID, Order number, Customer account id, Quote id, Status, Version (concurrency control), Held reason, Held by, Cancelled reason, Cancelled by, Replacement order id.
- History table (amendment history): Field, Original → revised value, Reason, Requested by, Approved by, When.
- Action buttons that vary with the order's current status.
What you can do
- Confirm (Draft/Pending validation) —
POST /orders/{id}/confirm. - Place on hold (Confirmed) — opens a form requiring a Hold reason, then
POST /orders/{id}/hold. - Release hold (On hold) —
POST /orders/{id}/release-holddirectly. - Record amendment (Confirmed/On hold) — pick a material field, Original/Revised value, and a required reason;
POST /orders/{id}/amendments. - Cancel order (Confirmed/On hold) — requires a Cancellation reason and optional Replacement order id;
POST /orders/{id}/cancel.
Related screens
Reached from the Orders list. A cancellation can reference a Replacement order id, implying a link to another Order (looked up separately, e.g. via Global Search).
Constraints to know
The server enforces the real transition rules regardless of which button the UI shows — an illegal transition is rejected (HTTP 422) even if a button were somehow clicked. Concurrent edits fail with a conflict message (optimistic concurrency via Version) rather than silently overwriting. There is no generic status dropdown — each action is a separate, explicit business Command.
Sales Workspace
Quote lifecycle: create, list, review, accept, and convert to Sales Order.
Quotes
Purpose
Lets sales staff create a Quote for a customer, search/browse existing Quotes, and move a Quote through its lifecycle — Draft → Customer Acceptance → converting to a real Sales Order.
What you see
- A Create quote card: a Customer account search field, Originating opportunity id (optional — still a raw text field; no opportunity picker exists yet), and repeatable line rows, each with one combined Product / SKU search field and a Quantity.
- A Quotes list card with filters (a Customer account search field, and Status) and a list of quote cards: quote number as the heading, customer name as the subtitle, Status as a badge, Opportunity as a small fact.
- A detail panel when a quote is selected: overview table plus a line items table.
What you can do
- Create quote —
POST /quotes, then opens the new quote's detail. Picking a customer and a product/SKU from the search fields fills the underlying ids automatically; there is no way to submit the form with a typed-but-unselected id. - Filter/search the list, then Search —
GET /quotes. - Click a card —
GET /quotes/{id}(replaces the old per-row “View” button). - On a Draft quote: Accept quote —
POST /quotes/{id}/accept(Customer Acceptance, not an internal approval). - On an Accepted quote: Convert to order — creates a new Sales Order and shows its ID.
Related screens
Converting creates a record you'd next look up on the Operations workspace's Orders screen (no direct link — only the new order's ID as text).
Constraints to know
- Accept quote only appears in Draft; Convert to order only once Accepted.
- “Accept” means Customer Acceptance, not an internal sales-manager approval — PLMP's separate “Quotation Approval” concept has no backend action wired up yet (PDQ-009).
- There is no “Revise Quote” action — lines cannot be edited once created.
- The Customer account and Product / SKU fields require picking a real search result — if you type but don't select before submitting, that field's id stays empty and the server rejects the request (422), same as leaving it blank.
Reports Workspace
Operational reporting and export surfaces.
Time to Final Resolution
Purpose
Shows managers/operations staff how long it typically takes, by product brand, from a technician's first visit on a service ticket to that ticket's final resolution.
What you see
- A Generate a report run card with optional Period from/to pickers and a Generate report button.
- A Look up a previously generated run card (Report run id + Look up).
- A “Recent runs” list of cards — each showing the run's timestamp as the heading, a status badge (e.g. COMPLETED), and Period/Brands as small facts. Click a card to load that run instead of typing its id.
- Once a run is loaded: a status line, a bar chart (average hours to resolution by brand), and a table (Product Brand, Resolutions, Avg/Min/Max time to resolution).
What you can do
- Generate report —
POST /reports/time-to-final-resolution/generate, then auto-loads viaGET /reports/runs/{id}. - Look up an existing run id directly, or click a card in Recent runs — both call
GET /reports/runs/{id}. - Export CSV once a run is loaded (built entirely in-browser; disabled if no rows).
Related screens
None — a standalone reporting screen.
Constraints to know
Generating a report requires a specific reporting permission and confirmed two-factor authentication; looking up an existing run by ID (or clicking a Recent runs card) has no such restriction. If no qualifying resolutions exist, the table explains why rather than showing an empty grid with no context.
Field Service Workspace
The technician-facing side of PLMP — currently just a link-out, no in-app functionality.
Technician App (external link)
Purpose
Gives office/dispatch staff a quick way to grab the link (or hand it to a technician) for downloading the technician mobile app — a convenience shortcut, not a business screen.
What you see / can do
Nothing renders inside employee-web itself; clicking the nav item opens this public, no-login download page (QR code, app store links, and a “continue to the PLMP web portal” fallback) in a new tab.
Related screens
None inside employee-web — the Field Service workspace has no other screens beyond the shared Home screen and this external link.
Constraints to know
This is explicitly a link-out, not a page. An earlier authenticated in-app “Technician App” page was retired in favor of this public download page, so don't expect technician data or history inside employee-web.
Inventory Workspace
Stock balances, reservations, transfers, counts, and conditions.
Stock Balances
Shown here with no data yet — no warehouses configured in this environmentPurpose
Lets you check how much of a product is on hand, reserved, and actually available to promise, in a specific warehouse.
What you see
- A warehouse picker at the top.
- A Balances table: Product model, SKU, On hand, Reserved, Available (green badge if >0).
- A “Filter by product model…” search box.
What you can do
Pick a warehouse to load its balances — GET /stock-balances?warehouse_id=. No write actions — read-only by design.
Related screens
To change a balance, go to Stock Reservations, Warehouse Transfers, Stock Counts, or Stock Conditions — none of those edit this table directly.
Constraints to know
This screen only displays a maintained projection of stock, derived from posted Stock Movements recorded elsewhere; there is no “edit quantity” control anywhere on it.
Stock Reservations
Purpose
Lets you set aside available stock for a specific Sales Order (or other source) without physically moving it, and release a reservation once it's no longer needed.
What you see
- Warehouse picker.
- A Reserve stock form: a combined Product / SKU search field (replacing the old separate Product model id/SKU id text fields), Quantity, Source reference type/id, Priority.
- A Reservations table (unchanged): Product model, Qty, Source, Priority, Status (badge), Reserved at, Actions.
What you can do
- Submit the form —
POST /stock-reservations(ReserveStock). Picking a result from the Product / SKU search fills both the product model id and SKU id together. - Release an active reservation —
POST /stock-reservations/{id}/release.
Related screens
Works against the balances shown on Stock Balances; source references typically point back to a Sales Order.
Constraints to know
Releasing an already-released reservation is rejected (422) rather than silently succeeding — a legitimate concurrency case. Reserving stock is always a dedicated business action, never a direct quantity edit. The Product / SKU field always resolves to a specific SKU — there is no way to reserve “just a model” with no SKU through this field, even though the raw field was previously optional.
Warehouse Transfers
Purpose
Lets you move stock from the warehouse you're viewing to another warehouse, recording it as a proper transfer rather than manually adjusting two separate balances.
What you see
- Source/viewing warehouse picker.
- A Transfer stock form: Destination warehouse (excludes the source), a combined Product / SKU search field (replacing the old separate Product model id/SKU id text fields), Quantity.
- A Transfer history table (unchanged): Transfer #, Direction (Outbound/Inbound badge), Product model, Qty, Status.
What you can do
Submit the form — POST /warehouse-transfers (TransferStock), posting two linked Stock Movement entries. Picking a result from the Product / SKU search fills both the product model id and SKU id together.
Related screens
Complements Stock Balances — a completed transfer changes both warehouses' on-hand quantity there.
Constraints to know
You cannot select the same warehouse as both source and destination. There is no direct “edit balance” step — a transfer always posts two linked ledger movements. The Product / SKU field always resolves to a specific SKU.
Stock Counts
Starting a new count is permission-gated for this demo session — shown here with only the one existing (already-reconciled) countPurpose
Lets you run a cycle or physical stock count in a warehouse — start a count, record what you counted, then reconcile so any variance gets corrected.
What you see
- Warehouse picker with a Start count button.
- A list of count cards: the count's started-at timestamp as the heading, Status as a badge. Click a card to open its detail.
- Selecting a count opens a detail panel: overview table, a “Record count line” form (while open) — now with one combined Product / SKU search field instead of two separate text fields — and a lines table with Variance badges.
What you can do
- Start count —
POST /stock-counts(StartStockCount). - Click a card — opens that count's detail (replaces the old “View”/“Selected” button; there's no separate visual highlight for the currently-open count in the list anymore, only the detail panel below tells you which one you're looking at).
- Submit “Record count line” —
POST /stock-counts/{id}/lines(re-entering the same product/SKU replaces the prior line). Picking a result from the Product / SKU search fills both ids together. - Reconcile (only while open) —
POST /stock-counts/{id}/reconcile, posting one corrective Stock Movement per non-zero-variance line.
Related screens
Reconciling here is what ultimately corrects the numbers on Stock Balances for this warehouse.
Constraints to know
A count's system quantity is never overwritten directly — a real corrective Stock Movement is posted per line, only on explicit Reconcile. Recording lines is only possible while the count is “open.”
Stock Conditions
Purpose
Lets you record that a quantity of stock has moved into a particular physical/quality condition — e.g. damaged, QC hold, returned quarantine — and see the full history for a warehouse.
What you see
- Warehouse picker.
- A form: a combined Product / SKU search field (replacing the old separate Product model id/SKU id text fields), Quantity, Condition (dropdown: Available New, QC Hold, Damaged, Returned Quarantine, Serviceable Used, Repaired, Standby), Reason.
- A Condition history table (unchanged) with color-coded condition badges.
What you can do
Submit the form — POST /stock-conditions (SetStockCondition). Picking a result from the Product / SKU search fills both the product model id and SKU id together.
Related screens
A separate concept from Stock Balances' on-hand/reserved/available numbers — this is quality/handling state, not location or reservation.
Constraints to know
Strictly append-only history — there is no edit or delete of a previously recorded condition entry; the fix for a mistake is a new correcting transition.
Dispatch Workspace
Service ticket dispatch and installation/commissioning management.
Service Dispatch
Purpose
Lets a dispatcher see every open Service Ticket across the whole company, assign a technician, and record what happened — a visit's restoration, formal resolution, and SLA breach status.
What you see
- Ticket queue card: Status filter, Unassigned only checkbox, Refresh.
- A list of ticket cards: ticket number as the heading, description as the subtitle, Priority as the badge, Status and Assigned technician as small facts.
- Selecting a ticket opens a detail panel: Assign technician form, Evaluate SLA form, Record restoration form, Record resolution form, SLA exceptions list, and Visits/Restorations/Resolutions history.
What you can do
- Filter/refresh —
GET /service-tickets/dispatch-queue. - Click a card — opens that ticket's detail (replaces the old per-row “View” button).
- Assign technician —
POST /service-tickets/{id}/assign. - Evaluate SLA —
POST /service-tickets/{id}/sla-evaluations. - Record restoration —
POST /service-tickets/{id}/restorations. - Record resolution —
POST /service-tickets/{id}/resolutions.
Related screens
Tickets assigned to the signed-in user also show up (read-only) on My Work. No cross-link into Asset/Customer 360.
Constraints to know
A Visit, Restoration, Resolution, and Closure are four separate facts — recording a restoration never resolves or closes the ticket, and this screen has no “close ticket” action at all. The Ticket queue deliberately shows every ticket company-wide, unlike the self-scoped My Work view.
Installation Jobs
Purpose
Lets a dispatcher run a checklist-driven installation for a delivered Asset — open the job, tick checklist items, record commissioning tests, mark complete, and record customer acceptance.
What you see
- Open an installation job form: Delivery line id, Checklist item code/description, Mandatory checkbox.
- Installation job queue: Status filter, Refresh, and a list of job cards: job id as the heading, delivery line id/asset id as the subtitle, Status as a badge.
- Selecting a job: Accepted at field, Checklist list, Record commissioning form, Commissioning attempts history, and Complete job / Accept installation buttons.
What you can do
- Open an installation job —
POST /installation-jobs(one job per Delivery Line with an allocated Asset). - Click a card — opens that job's detail (replaces the old per-row “View” button).
- Complete a checklist item —
POST /installation-jobs/{id}/checklist-items/{itemId}/complete. - Record commissioning —
POST /installation-jobs/{id}/commissioning. - Complete job / Accept installation — each disabled once already done.
Related screens
Works against an existing Delivery Line and Asset — looked up via Assets or Global Search; no direct link back.
Constraints to know
Exactly five explicit business actions, no generic “change status” control. No “replace the Asset” workflow surfaces here — permanent Asset replacement, if needed, happens elsewhere.
Procurement Workspace
Purchase orders, goods receipt, and supplier performance.
Purchase Orders
Purpose
Lets purchasing/warehouse staff create a purchase order to a supplier, record what was actually received, see discrepancies, and check a supplier's performance score.
What you see
- Create purchase order form: Supplier name, Expected delivery date, repeatable Lines (Product model id, SKU id, Quantity — these two id fields are still plain text on this screen; they were not converted to search fields in this pass).
- A list of PO cards: PO number as the heading, supplier name as the subtitle, Status as a badge, Expected delivery and Received as small facts.
- Selecting a PO: overview + Lines table, Receive goods form (while open), Related panel (Supplier + Performance Score), History panel (discrepancies).
What you can do
- Create purchase order — then auto-selects the new PO's detail.
- Click a card — opens that PO's detail (replaces the old per-row “View” button).
- Receive goods (while status is “open”) — records the receipt, flips status to “received”, and generates Discrepancy records if quantities don't match.
- Compute score — recalculates the supplier's performance score.
Related screens
None directly linked — cross-reference received stock on Inventory workspace screens separately.
Constraints to know
Receive goods disappears once the PO's status flips to “received” — no re-receiving or reopening from this screen. The Supplier Performance Score only updates on explicit Compute score.
Spares & Returns Workspace
Spare compatibility, reservation/issue, Core Returns, Repair Orders, and customer Returns.
Spare Compatibility
Purpose
Lets you record which spare parts fit which product models (and vice versa), and look that mapping up before reserving or issuing a part for a repair.
What you see
- Declare compatibility card — a SKU search field (picking a result sets only the SKU id; the compatible model is a separate, different id, not derived from the SKU pick) and a “Compatible product model id” text field.
- Retire compatibility card (Compatibility id).
- Compatible spares for a product model lookup (still a raw “Product model id” text field — no search endpoint exists for a bare model), and Compatible product models for a spare lookup (its “SKU id” field is also now a search-and-select box).
What you can do
- Declare compatibility —
POST /spares/compatibility. - Retire compatibility —
POST /spares/compatibility/{id}/retire. - Two read-only lookups by product model id / SKU id.
Related screens
Feeds into Spare Reservations & Issues — check compatibility here before reserving a SKU against a Service Ticket there, where the SKU id field is now the same kind of search box.
Constraints to know
Both mutating actions are permission- and MFA-gated on the backend — a 403 here is expected for a non-authorized session, not a bug. Retiring is the only way to remove a mapping — there is no plain delete/update. The “Compatible product model id” fields on this screen deliberately remain raw text — there is no backend search for a bare product model yet.
Spare Reservations & Issues
Purpose
Lets you reserve a spare part against a specific Service Ticket, cancel a reservation, and record that a spare was actually handed out (issued) for the repair.
What you see
- Load a service ticket card (Service ticket id).
- Once loaded: Reserve a spare (now with a SKU search field instead of a raw text box), Cancel a reservation, and Issue a spare (also now a SKU search field) cards, plus Reservations and Issues tables.
What you can do
- Load —
GET /service-tickets/{ticketId}/spares. - Reserve spare —
POST /service-tickets/{ticketId}/spare-reservations. Picking a search result sets the SKU id; the field ignores the product-model half of the match, since this action only ever needed the SKU. - Cancel reservation —
POST .../spare-reservations/{reservationId}/cancel. - Issue spare —
POST /service-tickets/{ticketId}/spare-issues, optionally linked to a reservation and/or warehouse. Its SKU field is the same kind of search box.
Related screens
Works with Spare Compatibility (check fit first — its own SKU id fields are now search boxes too), Service Dispatch (where the ticket is managed), and Inventory (warehouse side of an issued spare).
Constraints to know
Issuing a spare with a warehouse id posts a real Stock Movement (IssueStock) — a genuine ledger deduction. All three mutating actions are permission- and MFA-gated. You must load a ticket before any reserve/cancel/issue form will submit.
Return Requests
Purpose
Lets you record a customer's request to return a product, approve or reject it, and then walk the returned item through inspection and a final disposition decision.
What you see
- Create a return request card — a Customer account search field, plus Sales order id, Asset id, and Reason code as raw text fields (unconverted).
- Look up a return request card — shows Approve/Reject buttons while “pending”.
- Look up a return item card — shows an inspection form or a disposition form depending on state.
What you can do
- Create return request —
POST /return-requests. Picking a customer from the search field fills the underlying customer account id. - Approve / Reject —
POST /return-requests/{id}/approveor/reject. - Record inspection —
POST /return-items/{id}/inspection. - Record disposition —
POST /return-items/{id}/disposition.
Related screens
Core Returns and Repair Orders are a related but distinct workflow (supplier core exchanges / depot repairs), reached independently.
Constraints to know
You cannot approve/reject outside “pending” status, nor record a disposition before an inspection. Sales order id and Asset id still require manual entry — there is no lookup/autocomplete for either yet, only for the customer.
Core Returns
Purpose
Lets warehouse/service staff track a defective part (“core”) that a technician owes back after a spare part was issued, walking it from declared owed through received, inspected, and disposed.
What you see
A Load a spare issue form, then a Core card showing status, and (once a Core Return is started) received condition, inspection outcome, and disposition.
What you can do
- Declare core owed back —
POST /spare-issues/{id}/core. - Record core return received —
POST /spares/cores/{coreId}/returns. - Record inspection —
POST /spares/core-returns/{id}/inspection. - Record disposition —
POST /spares/core-returns/{id}/disposition.
Related screens
The Core Return Dashboard shows the same data across every Spare Issue at once. Getting a Spare Issue id typically comes from Spare Reservations & Issues.
Constraints to know
Strict one-way sequence: received → inspected → disposed. Known limitation (PDQ-046-01): no monetary credit amount, credit mechanism, or approval workflow anywhere — only the physical facts are tracked.
Core Return Dashboard
Shown here with no data yet — all four pipeline counts are genuinely 0Purpose
Gives a manager or purchasing/service lead a single view of every core owed back across all Spare Issues, grouped by pipeline stage.
What you see
Four summary count cards (Awaiting technician return, Received, Inspected, Disposed) and a filterable Cores table.
What you can do
Read-only — loads via GET /spares/cores on page load. Go to Core Returns to act on any individual core.
Related screens
Takes you conceptually to Core Returns by SKU id / Spare Issue id / Core id shown here.
Constraints to know
Deliberately does not show “overdue for credit” or total financial exposure, even though mentioned in the underlying business requirement — no due-date or monetary-amount field exists to compute them from (PDQ-046-01). The screen refuses to fabricate numbers it can't back up.
Repair Orders
Shown here with no data yetPurpose
Lets you register outside repair vendors and track a broken unit's repair job for a Service Ticket, from creation through vendor dispatch, acceptance, in-progress repair, outcome, and closing.
What you see
- Register a repair vendor card and Active repair vendors table.
- Load a service ticket's repair orders card.
- Once loaded: Create a repair order card, Record a repair outcome card, and a Repair orders table with per-row, status-specific action buttons.
What you can do
- Register vendor —
POST /repair-vendors. - Create repair order —
POST /service-tickets/{id}/repair-orders. - Row actions: Send to vendor, Record vendor acceptance, Record in progress, Close — each visible only in the matching status.
- Record outcome — moves an order from
in_repairtooutcome_recorded.
Related screens
The Repair Dashboard shows this data aggregated across every ticket. A Repair Order is typically created against a ticket found via Service Dispatch.
Constraints to know
Strict six-stage lifecycle: created → sent_to_vendor → vendor_accepted → in_repair → outcome_recorded → closed. Known limitations (PDQ-047-01/02/03): no cost/SLA field, and no maker-checker gate on any transition.
Repair Dashboard
Shown here with no data yet — all six stage counts are genuinely 0Purpose
Gives a manager a portfolio view of every Repair Order across every Service Ticket, showing how many sit at each lifecycle stage.
What you see
Six summary count cards and a filterable Repair orders table (Service ticket id, Repair vendor id, Status, Outcome, Created, Repair order id).
What you can do
Read-only — loads via GET /repair-orders. Go to Repair Orders (with the Service Ticket id shown) to actually progress a job.
Related screens
Feeds into the Repair Orders screen for taking action on any individual repair job.
Constraints to know
Deliberately omits “vendor turnaround,” “cost saving vs new,” and “repeat failure rate” metrics called for by the business requirement — no turnaround/SLA, cost, or repeat-failure field exists to compute them from (PDQ-047-01/02).
Finance Workspace
Chart of Accounts, Fiscal Periods, Vouchers, Trial Balance, Receivables, Payables, MIS Dashboards, Period Close, Tally Integration, Dimensions, Intercompany, Inventory Accounting, Tax, Banking, and Fixed Assets — the largest workspace in the app.
Chart of Accounts
Shown here with no chart yet — the seeded “Default Legal Entity” has none configuredPurpose
Lets accounting staff set up and browse the official Chart of Accounts a legal entity books its transactions against, including account groups and which ledgers allow manual entries.
What you see
- A Legal entity picker (shared across Finance screens).
- A Chart of accounts table for the selected entity (one active chart per entity).
- If none exists: a Create chart of accounts form (Code, Name, Accounting basis, Currency).
- Once a chart exists: Account groups and Ledgers tables, each with its own create form.
What you can do
- Pick a Legal entity to load its chart, groups, and ledgers.
- Create chart of accounts, Create account group, Create ledger.
Related screens
Ledgers created here are exactly what you pick from when posting a Voucher, and what appear as rows on the Trial Balance report.
Constraints to know
Exactly one active Chart of Accounts per legal entity. You cannot create an Account Group before a Chart exists, nor a Ledger before at least one Account Group exists.
Fiscal Periods
Shown here with no fiscal years yet for this legal entityPurpose
Lets accounting staff define a legal entity's fiscal years and accounting periods, and control which periods are open for posting.
What you see
- Legal entity picker, Fiscal year picker, Create fiscal year form.
- Accounting periods table: Period, Name, Start, End, Status (badge), Actions.
- Create accounting period form; an inline Reopen form when reopening.
What you can do
- Soft-close (while OPEN), Hard-close (while SOFT_CLOSED), Reopen (any non-OPEN period, requires a typed reason).
- Create fiscal year and Create accounting period.
Related screens
The periods managed here gate which Accounting date is valid when posting a Voucher, and feed the Period Close screen.
Constraints to know
Explicit lifecycle only: OPEN → SOFT_CLOSED → HARD_CLOSED, never a free-form status jump. Reopening requires a reason or the form won't submit.
Vouchers
Shown here with no vouchers posted yetPurpose
Lets accounting staff record and review accounting vouchers — the debit/credit postings that make up the general ledger — and reverse a voucher posted in error.
What you see
- Legal entity picker, Recent vouchers table (Number, Type, Voucher date, Currency, Status, View).
- A Voucher detail panel with a Lines table after clicking View.
- A Post a voucher form: type, dates, currency, narration, reference, repeatable Lines (Ledger id, Entry side, Amount).
What you can do
- View a voucher to load its full line-item detail.
- Reverse voucher (only while POSTED) — posts an offsetting reversal and marks the original REVERSED.
- Post voucher — submits header + all lines as one voucher.
Related screens
Posted vouchers feed Trial Balance and Receivables/Payables reports; Ledgers are set up on Chart of Accounts; periods are managed on Fiscal Periods.
Constraints to know
Lines must balance (debits = credits) before posting. Amounts are exact-decimal text, never floating point. A voucher is never edited in place — only Reverse voucher corrects a posted one.
Trial Balance
Purpose
Lets accounting staff produce a live trial balance — total debits and credits by ledger — as of any date, to check the books are in balance.
What you see
Legal entity picker, Currency and As-of-date report parameters, and (once produced) a Balanced/Not balanced card with a ledger table and totals.
What you can do
Produce trial balance — the only action; then filter the rows with the search box.
Related screens
Figures are derived entirely from Vouchers posted against Ledgers on Chart of Accounts — there's nothing to edit here.
Constraints to know
A read-only, always-recomputed report. If a total looks wrong, the fix is a correcting Voucher, not anything on this page.
Receivables
Purpose
Lets AR staff manage a single customer's Accounts Receivable subledger — open the account, see what they owe, review the running-balance ledger, record a payment, and apply it against open receivables.
What you see
- Open a customer financial account card — now with a Customer account search field instead of a raw id box — and Load an existing one card — still a raw “Customer financial account id” lookup box, a different id, unaffected.
- Once loaded: account summary, Receivables table, Ledger table, Recognise a receivable form, Record a payment form, Allocate a payment form with a running allocations list.
What you can do
- Open account — picking a customer from the search field fills the underlying customer account id. / Load.
- Recognise receivable — ties a new amount owed to a source document.
- Record payment — captures the resulting Customer Payment id.
- Allocate payment and Unallocate.
Related screens
The Customer account search field on this screen searches the same Customers list shown on the Operations workspace's Customers screen. Chart of Accounts and Trial Balance reflect the postings this screen produces.
Constraints to know
Recording a payment never auto-settles an invoice — allocation is a deliberate, separate step, and can be partially or entirely unallocated later. A 403 here can mean missing 2FA confirmation, not just role.
Payables
Shown here with no account loaded yetPurpose
The supplier-side mirror of Receivables — open a supplier's financial account, see what's owed, review the ledger, record a payment made, and apply it against outstanding bills.
What you see
Open / Load a supplier financial account cards, then account summary, Payables table, Ledger table, Recognise a payable form, Record a payment form, Allocate a payment form.
What you can do
- Open account / Load.
- Recognise payable, Record payment, Allocate payment, Unallocate.
Related screens
Supplier ids and source Purchase Order numbers come from Procurement; postings show up in Chart of Accounts / Trial Balance.
Constraints to know
Same recording-vs-allocation separation as Receivables. The Payable dropdown only shows payables not already SETTLED. This is a single-account-at-a-time tool — no “all overdue suppliers” list.
MIS Dashboards
Purpose
Gives finance leadership a one-screen snapshot of cash, receivables, payables, inventory value, profitability, period-close progress, and outstanding exceptions for a chosen company and date.
What you see
- Dashboard parameters form (Legal entity id, Currency, optional As of date).
- Six KPI cards: Cash & Bank, Accounts Receivable, Accounts Payable, Inventory Value, Close Status, Critical Exceptions.
- Profitability (MTD), Tax (GST) Reconciliation, and Fixed Asset Reconciliation cards.
What you can do
Load dashboards — fires nine parallel read-only calls; each card loads and fails independently.
Related screens
None of the cards link onward — visit Receivables, Payables, Period Close, Tax, or Fixed Assets separately to act.
Constraints to know
Performs no calculations of its own. Every card can independently show DATA_INCOMPLETE rather than a fabricated number when configuration is missing — exactly what's captured in the screenshot above.
Period Close
Shown here with no period close runs yetPurpose
Lets you look up a Period Close Run — the record tracking an accounting period moving through checks, soft close, hard close, and certification — to see its current state.
What you see
A Legal entity picker, a browse list of Period Close Runs (Id, Status badge, Started at), and a manual Period close run id lookup.
What you can do
Pick a legal entity to load its runs, then View a row or type an id directly — both display the full record.
Related screens
None — a standalone lookup.
Constraints to know
Read-only — the actual close actions (start, run checks, soft-close, hard-close, certify) exist as backend commands but have no buttons here. Treat this as a status-lookup tool, not the close workflow itself.
Tally Integration
Shown here with no Tally company mappings yetPurpose
Lets finance/IT staff look up how a company is mapped to Tally (the external accounting package) and check export batches and reconciliation runs.
What you see
A Legal entity picker, a browsable Tally Company Mapping list, and three id-only lookup panels: Mapping Version, Export Batch, Reconciliation Run.
What you can do
Browse/View a Company Mapping, or type any Mapping Version / Export Batch / Reconciliation Run id and press Look up.
Related screens
None — a standalone set of lookups.
Constraints to know
Read-only inspection only — no new export, reconciliation run, or mapping can be created here. Only Company Mapping supports browsing; the other three need an id you already have.
Dimensions
Shown here with no governed dimension values yetPurpose
Lets you look up the governed cost-center/department-style Accounting Dimension tags used to classify transactions, and see which tags apply to a specific voucher line.
What you see
An Accounting Dimension Value panel (browsable list + id lookup) and a Voucher Line Dimension Tags panel (lookup by voucher line id).
What you can do
Browse/View a dimension value, or look up the tags recorded against a specific voucher line.
Related screens
None directly — get a voucher line id from Vouchers and bring it here.
Constraints to know
Read-only — no way to create a dimension value or tag a voucher line from this screen. No separate “Dimension Type” lookup exists.
Intercompany
Shown here with no intercompany transactions yetPurpose
Lets you look up cross-legal-entity (“intercompany”) transactions and their reconciliation runs, to check how a transaction was split across companies and whether it has settled.
What you see
An Intercompany Transaction panel (browsable + id lookup) and an Intercompany Reconciliation Run panel (id lookup only).
What you can do
Browse/View a transaction (including its per-company legs), or look up a reconciliation run by id.
Related screens
None directly linked.
Constraints to know
Read-only lookups only — no create/settle/reconcile action exists here, and no summary dashboard was built for this area. Reconciliation Run has no browsable list.
Inventory Accounting
Shown here with no valuation layers matched yetPurpose
Lets accounting staff look up how the value of stock on hand is tracked (FIFO-style valuation layers), and lets one person request — and a different person approve — a correction when it's wrong.
What you see
A Find valuation layers search form and results table, a Valuation layer detail card, a Request inventory value adjustment form, and an Approve inventory value adjustment form.
What you can do
- Find layers —
GET /finance/inventory-valuation-layers. - Request adjustment —
POST /finance/inventory-value-adjustments. - Approve adjustment —
POST /finance/inventory-value-adjustments/{id}/approve.
Related screens
Works alongside Trial Balance and Chart of Accounts; layers originate from goods-receipt/stock-movement activity in Inventory and Procurement.
Constraints to know
Maker-checker control — the requester cannot also approve; the server rejects a same-identity approval. There is no way to directly edit a valuation layer's value in place.
Tax
Purpose
Lets tax/compliance staff define tax rules (rates and treatments), get a second person to approve each rule before it takes effect, and check that GST collected/paid matches the general ledger.
What you see
A Request tax rule form, a Tax rule detail card, an Approve tax rule form, and a GST reconciliation form/results table.
What you can do
- Request tax rule —
POST /finance/tax-rules. - Approve tax rule —
POST /finance/tax-rules/{id}/approve. - Reconcile —
GET /finance/gst-reconciliation.
Related screens
Ties into Chart of Accounts / Trial Balance and whichever sales/purchase screens generate taxable transactions.
Constraints to know
Maker-checker — the approver must differ from the requester. Transaction type and Tax treatment are restricted to a fixed, pre-approved list.
Banking
Purpose
Lets finance staff open a bank account record linked to the ledger, and reconcile the company's book balance for that account against the bank statement balance.
What you see
An Open bank account form, a Bank account detail card, and a Bank reconciliation form/results table.
What you can do
- Open bank account —
POST /finance/bank-accounts, wrapping an existing ledger of type BANK. - Reconcile —
GET /finance/bank-reconciliation.
Related screens
The Bank ledger id comes from Chart of Accounts; reconciled totals would be cross-checked against Vouchers/Trial Balance.
Constraints to know
A bank account must link to a pre-existing BANK-type ledger. Reconciliation is a read-only computed comparison, not an editable worksheet.
Fixed Assets
Purpose
Lets finance staff set up fixed-asset categories, register a specific fixed asset against one, formally capitalise it once ready for use, and record moves to a new location or custodian.
What you see
Register fixed asset class card, Register fixed asset card, a Fixed asset detail card, a Capitalise fixed asset form, and a Transfer fixed asset form.
What you can do
- Register class —
POST /finance/fixed-asset-classes. - Register asset — creates it in DRAFT status.
- Capitalise — moves DRAFT to capitalised with cost/dates.
- Transfer — updates location/custodian within the same legal entity.
Related screens
No cross-link to the operational Assets screen — a PLMP “Asset” a customer owns is a separate concept from a Finance “Fixed Asset” the company capitalises on its own books.
Constraints to know
Registration and capitalisation are deliberately two separate steps. Transfer only works within the same legal entity. Nothing here lets you delete or directly overwrite an asset's cost or status.