Access and dashboards
Page specs BO-01 to BO-08: sign-in, password reset, the app shell, and the general, sales, field, stock and finance dashboards.
These pages open the back-office and give every role its daily picture. The shell (BO-03) carries the global elements described in the overview. The general dashboard (BO-04) fixes the KPI definitions that the business dashboards and the reports reuse.
| ID | Screen | Section | Template | Release | Design | Build |
|---|---|---|---|---|---|---|
BO-01 | Sign in | Access | bespoke | V1 | Day 15 | DSH-01 |
BO-02 | Forgot / reset password | Access | template | V1 | Day 15 | DSH-01 |
BO-03 | App shell: navigation by role, global search, notifications | Access | bespoke | V1 | Day 15 | DSH-01 |
| ID | Screen | Section | Template | Release | Design | Build |
|---|---|---|---|---|---|---|
BO-04 | General dashboard | Dashboards | bespoke | V1 | Day 15 | DSH-02 |
BO-05 | Sales dashboard | Dashboards | template | V1 | Day 15 | DSH-02 |
BO-06 | Field dashboard & live map | Dashboards | bespoke | V1 | Day 15 | DSH-08 |
BO-07 | Stock dashboard | Dashboards | template | V1 | Day 15 | DSH-05 |
BO-08 | Finance dashboard | Dashboards | template | V1 | Day 15 | DSH-07 |
BO-01 — Sign in
| Field | Value |
|---|---|
| Template | Bespoke |
| Release | V1 |
| Design day | Day 15 — DES-15 |
| Build task | DSH-01 |
| Route | /sign-in (?next= an internal path) |
| Requirements | AUTH-01 · AUTH-08 · SEC-02 · AUD-01 |
| Permissions required | None (public page). After sign-in, at least one back-office permission. |
Purpose. Open a back-office session with an email or phone number and a password (cahier §3, §10). Refuse everyone who is not staff with an explicit message.
Who uses it. All six staff roles.
Entry points.
- Any back-office URL without a valid session (redirect with
next). - « Se déconnecter ».
- A
401on any call. - The end of the password reset (BO-02).
- The first-sign-in link sent with a temporary password when an administrator creates the account (BO-61).
Layout.
- Desktop: split screen.
- The left half is the brand panel: HOCO logo, the three brand marks HOCO · ACEFAST · BOROFONE, and the line « Back-office B2B ».
- The right half holds a 400 px form card: title « Connexion », the two fields, the primary button « Se connecter », and the link « Mot de passe oublié ? ».
- Tablet portrait: one column, with the brand panel reduced to a header band.
- Footer: build version. Outside production, a ribbon names the environment (« Staging », « Recette ») so no one confuses it with production.
| Field | Type | Validation | Default | Help text |
|---|---|---|---|---|
| Identifiant | Text, autocomplete="username" | Required. An email, or a phone in national or E.164 format (normalised before sending). | Empty, or the value passed by BO-02 | « Email ou numéro de téléphone » |
| Mot de passe | Password with a show/hide toggle, autocomplete="current-password" | Required | — | — |
First sign-in step. When me.get reports that the password is temporary (Assumption: a mustChangePassword flag), a second card replaces the form: « Choisissez votre mot de passe ». It has Nouveau mot de passe and Confirmation, with the live policy checklist from BO-02. No other page is reachable until the password is changed.
Data.
auth.login: sends the identifier and password. The API sets the httpOnly session cookie on.hoco.dz.me.get:userType, roles, permissions, status, temporary-password flag.auth.password.change: first sign-in.auth.logout: rejected user types.
Actions.
| Action | Endpoint | Behaviour | Audit |
|---|---|---|---|
| « Se connecter » | auth.login, then me.get | The button shows a spinner and the fields are disabled. On success, go to next if it is an internal back-office path, else to the home page (/ with dashboard.view, otherwise the first visible sidebar item). | LOGIN or LOGIN_FAILED, with IP, user agent and app DASHBOARD |
| « Mot de passe oublié ? » | — | Opens BO-02 with the identifier prefilled | — |
| « Enregistrer mon mot de passe » (first sign-in) | auth.password.change | Then continues to next | UPDATE on the user |
Rules.
- Rate limits (AUTH-07): 5 attempts per minute per identifier and IP, and a 15-minute lockout after 5 consecutive failures. While locked, the button is disabled and a countdown runs from
Retry-After: « Trop de tentatives. Réessayez dans 14:32. » - No account enumeration:
401 INVALID_CREDENTIALSalways reads « Identifiant ou mot de passe incorrect. » - Blocked accounts:
403 ACCOUNT_SUSPENDEDreads « Votre compte est suspendu. Contactez votre administrateur. »- A disabled account reads « Ce compte est désactivé. »
- Non-staff users: when
me.getreturns auserTypeother thanSTAFF, the page callsauth.logoutand shows « Ce compte n'a pas accès au back-office. Utilisez l'application HOCO Délégué ou HOCO Revendeur. » - Open-redirect guard:
nextmust be a relative path inside the back-office. Anything else is dropped. - Session: 7 days, sliding, per AUTHORING (decision pending).
- Password managers: correct autocomplete attributes, and paste is never blocked.
Real-time. None.
States.
- Submitting: the button spins and the fields are disabled.
- Wrong credentials: an inline error above the button; the password is cleared and gets focus.
- Locked: countdown and disabled button.
- Suspended or disabled: an error panel without a retry button.
- Errors:
503 MAINTENANCEshows the full-page maintenance message. A network failure reads « Connexion impossible — vérifiez votre réseau. » - Already signed in: the page redirects to the home page.
Acceptance criteria.
- A staff user with valid credentials lands on
next(or the home page), and the sidebar shows exactly the items their permissions allow. - After 5 wrong passwords, the next attempt is refused for 15 minutes and the countdown is shown.
- Delegate or reseller credentials never leave a back-office session cookie, and the message names their app.
- A suspended account sees the suspension message and no session is created.
- A user created with a temporary password cannot reach any page before setting a new password.
LOGINandLOGIN_FAILEDentries appear in the activity log with IP and appDASHBOARD.
BO-02 — Forgot / reset password
| Field | Value |
|---|---|
| Template | Form (three-step, on the public sign-in layout) |
| Release | V1 |
| Design day | Day 15 — DES-15 |
| Build task | DSH-01 |
| Route | /forgot-password |
| Requirements | AUTH-07 · SEC-02 |
| Permissions required | None (public page) |
Purpose. Let staff reset a forgotten password with a one-time code, without revealing whether an account exists.
Who uses it. All staff roles.
Entry points. « Mot de passe oublié ? » on BO-01, and the link in the « mot de passe réinitialisé » message an administrator triggers from BO-61.
Layout. Three steps in the same card, with a step indicator (1 Identifiant · 2 Code · 3 Nouveau mot de passe) and « Retour à la connexion » at the bottom.
| Step | Field | Type | Validation | Help text |
|---|---|---|---|---|
| 1 | Identifiant | Text | Required; an email or a phone | « Le code est envoyé par SMS au numéro du compte. » |
| 2 | Code | Six single-digit boxes, paste-aware, auto-advance | 6 digits | « Code valable 5 minutes » |
| 3 | Nouveau mot de passe | Password with a show toggle and a live checklist | At least 10 characters, at least one letter and one digit, different from the identifier (Assumption — AUTH-07 sets the final policy) | Checklist items turn green as they are met |
| 3 | Confirmation | Password | Must equal the new password | — |
Data.
auth.otp.sendwithpurpose: PASSWORD_RESET.auth.otp.verify, which returns a short-lived verification token.auth.password.resetwith the token and the new password.
Actions.
| Action | Endpoint | Behaviour |
|---|---|---|
| « Recevoir un code » | auth.otp.send | Always shows « Si un compte correspond, un code à 6 chiffres vient d'être envoyé. », then step 2 |
| « Vérifier » | auth.otp.verify | Wrong code: « Code incorrect — 3 essais restants. » Expired: « Code expiré. Demandez un nouveau code. » |
| « Renvoyer le code » | auth.otp.send | Disabled for 60 s with a countdown. At most 5 sends per hour. |
| « Enregistrer le mot de passe » | auth.password.reset | Then « Mot de passe modifié. Vos autres sessions ont été fermées. » and BO-01 with the identifier prefilled |
Rules.
- Code: 6 digits, valid 5 minutes, 5 attempts per code. Codes are sent at most once every 60 s and 5 times per hour per phone (AUTH-07).
- After a reset, every session of the user is revoked (SEC-02). A security message (« Votre mot de passe HOCO a été modifié ») goes out on the account's channel. It belongs to the
SECURITYcategory, which cannot be disabled. - Staff without a phone: the code goes by email. Assumption —
auth.otp.sendmust support email delivery for staff accounts (NOT-03). - The reset is recorded in the activity log (
UPDATEon the user, without the password).
Real-time. None.
States.
- Sending and verifying show spinners.
429 OTP_RESEND_TOO_SOONshows the remaining wait.429 TOO_MANY_ATTEMPTSreads « Trop de tentatives. Demandez un nouveau code. »- After a step-3 failure, step 3 is shown again with the server message.
Acceptance criteria.
- An unknown identifier and a known one produce the same screen and the same wording.
- A code older than 5 minutes is refused. The resend button stays disabled for 60 s after each send.
- After a reset, a session open in another browser is signed out at its next request.
- The new password signs in on BO-01. The old one returns
INVALID_CREDENTIALS.
BO-03 — App shell: navigation by role, global search, notifications
| Field | Value |
|---|---|
| Template | Bespoke |
| Release | V1 |
| Design day | Day 15 — DES-15 |
| Build task | DSH-01 |
| Route | Wraps every authenticated route |
| Requirements | ACL-05 · NOT-01 · SYN-07 · NFR-05 |
| Permissions required | A session. Each sidebar item needs its own permission (see sidebar). Search results are filtered by permission. |
Purpose. Frame every page: navigation computed from the user's permissions, global search, notifications, live events, and the user menu.
Who uses it. Every staff user, on every page.
Entry points. It loads after sign-in and wraps all authenticated routes.
Layout.
- Sidebar (256 px; 72 px icon rail when collapsed):
- HOCO logo with the environment ribbon outside production.
- Groups and items as in the sidebar table, with badges.
- The active item is highlighted. « Réduire le menu » at the bottom (shortcut
[).
- Top bar (56 px):
- Breadcrumb.
- Search field « Rechercher… » with the Ctrl K hint.
- Live-status dot (green « En direct », amber « Reconnexion… », grey « Hors ligne »).
- Bell with the unread badge.
- Avatar and user menu.
- Search palette: a centred modal of 640 px. The input sits on top and results are grouped below, with keyboard hints.
- Notifications drawer: 400 px on the right (see overview).
- Re-authentication dialog: « Confirmez votre mot de passe pour continuer », shown before sensitive actions.
- Global banners under the top bar: offline, maintenance announced, real-time interrupted.
Data.
- Boot sequence:
me.get(permissions → navigation tree).- Badge counters: list calls with
pageSize=1:admin.orders.toConfirm: ordersSUBMITTED+PENDING(withorders.validate). The response's oldestsubmittedAtturns the badge red after 2 h;admin.returns.list: returnsREQUESTEDplusAPPROVED/IN_TRANSITto receive;admin.resellers.validation.list;admin.payments.list:status=PENDING_VERIFICATION;admin.tickets.list: unassigned open tickets;admin.tasks.list: mine, overdue;admin.deliveries.list:TO_PREPARE.
notifications.unreadCount.admin.events.stream.
- On demand:
- Only the counters the user may see are requested.
Actions.
| Action | Endpoint | Behaviour |
|---|---|---|
| Open search (Ctrl K or click) | admin.search | Debounced 250 ms, at least 2 characters. Enter on an exact business number opens the record. |
| Open a notification | notifications.markRead (optimistic) | Marks it read, navigates to the deep link and closes the drawer |
| « Tout marquer comme lu » | notifications.markAllRead (optimistic) | The badge drops to 0 and is rolled back on error |
| Collapse or expand the sidebar | — | The choice is stored in the browser |
| « Se déconnecter » | auth.logout | Closes the event stream, clears the query cache, goes to BO-01 |
| Confirm the password (re-authentication) | auth.login with the current identifier (Assumption — a dedicated re-auth operation may replace it) | On success the pending action runs. On failure the dialog shows the error. |
Rules.
- Navigation follows permissions (ACL-05). Item requirements live in one configuration file (
navigation.ts), each item listing the permission keys it needs. A route guard applies the same keys, so a typed URL without the permission shows « Accès refusé ». The server stays the authority. - Permission refresh:
me.getis re-fetched on window focus, every 5 minutes and after any403, so permission changes made in BO-62 reach the shell without a new sign-in. - Counters: never computed for modules the user cannot view.
- Keyboard shortcuts:
- Ctrl K: search.
- Ctrl/Cmd+S: save the current form.
- Esc: close a drawer, dialog or palette.
[: collapse the sidebar.?: shortcut overlay.
Real-time. The shell owns the event stream and the toasts. Each event invalidates the matching queries and badges, as listed in live events.
States.
- Booting: logo, then a skeleton sidebar.
me.getfailing: full-page error with « Réessayer ».- Connectivity: offline banner; real-time indicator amber after 30 s without the stream.
503 MAINTENANCE: maintenance page.- Expired session: redirect to BO-01 with
next. - No permission at all (staff account without roles): page « Aucun accès n'est attribué à votre compte. Contactez votre administrateur. »
Acceptance criteria.
- A user holding only
orders.viewsees only the Commandes group (Commandes, Retours et SAV) and lands on/orders. Commandes à confirmer appears only withorders.validate. - A new order placed in the reseller app increments the Commandes à confirmer badge within 5 s, with no reload. Confirming it decrements the badge in every open tab.
- After a super admin removes
crm.viewfrom a role, a signed-in holder loses the CRM group within 5 minutes or at next focus, without signing out. - Ctrl K then
CMD-2026-000158+ Enter opens that order's detail. - A finance manager's search never returns orders or resellers they cannot view, and the server response contains no such group.
- Badges match the corresponding list totals.
- With the API stream stopped, the live dot turns amber within 30 s and recovers automatically when it returns.
BO-04 — General dashboard
| Field | Value |
|---|---|
| Template | Bespoke (Dashboard skeleton) |
| Release | V1 |
| Design day | Day 15 — DES-15 |
| Build task | DSH-02 |
| Route | / |
| Requirements | ANA-01 · ANA-02 · ANA-04 · ORD-14 · ACL-04 |
| Permissions required | dashboard.view to open the page. Each card also needs its module's view permission (table below). settings.administer to customise the layout of a role. |
Purpose. Give an immediate picture of activity, alerts and performance (cahier §4.1): revenue for the day, month and year; orders by status and average basket; active, new and inactive resellers; delegates on tour, visits done and conversion; best sellers, stock-outs and stock alerts; expected payments, credit used and unpaid amounts. All of it is filterable by date, wilaya, commune, delegate, brand and category. Since no order is confirmed automatically (ORD-14), the page also leads with the live count of orders waiting for confirmation.
Who uses it. Every role on arrival. Each role gets its own card layout. Direction uses it read-only as its main screen.
Entry points. The home page after sign-in, the logo, and Pilotage › Tableau de bord.
Layout
- Filter bar (sticky):
- Période, Wilaya, Commune (enabled once a wilaya is chosen), Délégué, Marque, Catégorie.
- « Réinitialiser ».
- « Mis à jour à 09:42 » and a refresh button.
- « Personnaliser » (only with
settings.administer).
- Chiffre d'affaires row:
- three KPI cards: CA du jour, CA du mois, CA de l'année;
- the chart Évolution du CA (two columns wide): daily buckets, weekly beyond 92 days, monthly beyond 366 days. The comparison period is a dashed line.
- Commandes row:
- Commandes à confirmer, the first card of the row, with an accent border while it is above 0. A pulse marks each live update.
- Commandes par statut: wide card, horizontal stacked bar with one clickable segment per status.
- Panier moyen.
- Revendeurs row: Revendeurs actifs, Nouveaux revendeurs, Revendeurs inactifs.
- Terrain row: Délégués en tournée, Visites réalisées, Taux de conversion (with a mini-funnel).
- Produits et stock row: Produits les plus vendus (list card, two columns wide), Ruptures, Alertes de stock.
- Finance row: Paiements attendus, Crédit utilisé, Impayés.
- « À traiter » panel: right column on large screens, last row otherwise. Each line is a count with a link, shown only with its permission:
- orders to confirm waiting more than 2 h, and orders on hold (
PENDING) for more than 24 h; - accounts and points of sale to validate;
- return requests to decide and after-sales claims without an assignee;
- payment declarations to verify;
- unassigned tickets;
- tours not started one hour after their planned start.
- orders to confirm waiting more than 2 h, and orders on hold (
Filters
| Filter | Control | URL parameter | Notes |
|---|---|---|---|
| Période | Presets + date-range picker | period = today (default), yesterday, last7, last30, month, lastMonth, year, custom; with custom, from and to (YYYY-MM-DD) | Custom range: at most 366 days, from ≤ to, never in the future. Days are business days in Africa/Algiers. |
| Wilaya | Searchable select (code + name) | wilaya (code, e.g. 31) | From reference.wilayas |
| Commune | Searchable select | commune (id) | From reference.communes. Cleared when the wilaya changes. |
| Délégué | Searchable select with avatars | delegate (id) | Active and suspended delegates |
| Marque | Select (HOCO, ACEFAST, BOROFONE) | brand (id) | — |
| Catégorie | Tree select | category (id) | Includes sub-categories |
Common definitions used by the formulas:
- Counted order: an order whose
validatedAtis set (a person confirmed it in the back-office; nothing is confirmed automatically) and whose current status is none ofCANCELLED,REFUSED,RETURNED. A later cancellation or return therefore removes the order from past periods too: figures are computed at query time. - Net HT:
subtotalHt − discountTotalof the order, so delivery fees and VAT are excluded. With a brand or category filter, only the net HT of the matching lines is summed. - Anchor date: the end date of the selected period (today by default).
- Activity window: the setting
crm.activityWindowDays, default 60 days. Assumption — this is the same window that drives the automaticACTIVEreseller status. - Geography: for orders, the order's delivery-address snapshot; for resellers, their main address; for tours and visits, the point of sale's commune.
- Delegate: for orders, the attributed delegate (the source delegate of an assisted order, else the reseller's assigned delegate at checkout; see contract notes); for resellers, the assigned delegate; for tours and visits, the tour's delegate.
- Amounts are in DA (integer centimes in the API).
- Decision to confirm: revenue is shown HT and on the validation basis, not the delivery or invoice basis (open decisions).
KPI cards
| Card | Key | Formula | Period behaviour | Filters applied | Needs | Click opens |
|---|---|---|---|---|---|---|
| CA du jour | revenue.day | Σ net HT of counted orders with validatedAt on the anchor date | Anchored on the anchor date. Δ vs the same weekday of the previous week; when the anchor is today, up to the same time of day. | All six | orders.view | BO-38, validated on that day |
| CA du mois | revenue.month | Same sum, validatedAt from the 1st of the anchor's month to the anchor date | Δ vs the same day span of the previous month (capped at that month's length) | All six | orders.view | BO-38, same range |
| CA de l'année | revenue.year | Same sum, from 1 January to the anchor date | Δ vs the same span of the previous year | All six | orders.view | BO-59 CA par période, by month |
| Commandes à confirmer | orders.toConfirm | Orders currently SUBMITTED or PENDING (the BO-41 queue). Second lines: how many are on hold (PENDING); the age of the oldest (« la plus ancienne : 3 h 10 »); the median confirmation delay (validatedAt − submittedAt) of orders confirmed today. Red tone once the oldest waits more than 2 h (setting orders.confirmationAlertMinutes, Assumption 120). | Live snapshot; ignores the period | Geography, delegate | orders.view | BO-41 with orders.validate, otherwise BO-38 ?status=SUBMITTED&status=PENDING |
| Commandes par statut | orders.byStatus | Open statuses (SUBMITTED, PENDING, VALIDATED, PREPARING, READY, SHIPPED): count of orders currently in the status. Closing statuses (DELIVERED, CANCELLED, REFUSED, RETURNED): count of orders that entered the status during the period (OrderStatusChange). DRAFT is never counted. | Period applies to closing statuses only; open statuses are a live snapshot | Geography, delegate; brand and category = orders with at least one matching line | orders.view | BO-38 ?status=<value> |
| Panier moyen | orders.averageBasket | Σ net HT of counted orders validated in the period ÷ number of those orders. « — » when there are none. | Period; Δ vs the previous period of equal length | All six | orders.view | BO-59 CA par période |
| Revendeurs actifs | resellers.active | Distinct resellers with at least one counted order validated in the activity window ending on the anchor date | Anchored; Δ vs the same count at the end of the previous period | Geography (main address), delegate (assigned); brand and category = orders containing them | crm.view | BO-09 ?activity=active |
| Nouveaux revendeurs | resellers.new | Resellers whose validation turned APPROVED during the period. Second line: how many of them already reached FIRST_ORDER. | Period | Geography, delegate | crm.view | BO-09 ?approvedFrom=…&approvedTo=… |
| Revendeurs inactifs | resellers.inactive | Resellers whose account was approved before the activity window started, whose status is neither SUSPENDED nor NOT_INTERESTED, with no counted order in the window ending on the anchor date | Anchored | Geography, delegate | crm.view | BO-09 ?activity=inactive |
| Délégués en tournée | field.delegatesOnTour | Delegates with a tour IN_PROGRESS or SUSPENDED now ÷ delegates with a tour dated today that is not CANCELLED. Second line: how many are suspended. | Live snapshot; ignores the period | Wilaya and commune (tours with at least one stop there), delegate | field.view | BO-06 |
| Visites réalisées | field.visitsDone | Visits DONE with a check-in in the period ÷ visits planned in the period (planned date in the period, status not CANCELLED), shown as « 46 / 58 · 79 % » | Period | Geography, delegate | field.view | BO-24, same period |
| Taux de conversion | field.conversion | Cohort of prospects created in the period with source DELEGATE_APP or BACK_OFFICE (N). Of those, reached ACCOUNT_CREATED so far (A) and reached FIRST_ORDER so far (F). Shows F ÷ N and the mini-funnel N → A → F (cahier §1.4). The tooltip warns that recent cohorts are still maturing. | Period = cohort creation dates | Geography, delegate (creating or assigned) | field.view + crm.view | BO-59 Conversion terrain |
| Produits les plus vendus | catalog.bestSellers | Top 10 products by quantity in counted orders validated in the period, with a « Quantité / CA HT » toggle. Each row: rank, product, brand, quantity, net HT, share of the period's revenue. | Period | All six | orders.view | The product's BO-28 |
| Ruptures | stock.outOfStock | Active variants of products ACTIVE or OUT_OF_STOCK whose available stock (physical − reserved; global, or summed over warehouses) is ≤ 0 | Live snapshot | Brand, category. Other filters show « non applicable ». | stock.view | BO-33 ?alert=OUT |
| Alertes de stock | stock.lowStock | Active variants with 0 < available ≤ their alert threshold | Live snapshot | Brand, category | stock.view | BO-33 ?alert=LOW |
| Paiements attendus | finance.expectedPayments | Σ remaining balance (total TTC − confirmed allocations) of invoices UNPAID or PARTIALLY_PAID with a due date on or after the anchor date. Second lines: the part due within 7 days; declarations PENDING_VERIFICATION (count and amount). | Snapshot on the anchor date | Geography, delegate (the invoice's reseller) | finance.view | BO-44 ?status=UNPAID&status=PARTIALLY_PAID |
| Crédit utilisé | finance.creditUsed | Σ used credit ÷ Σ credit limits over credit accounts with a limit above 0, shown as « 4,2 M DA / 9,0 M DA · 47 % ». Used credit = the exposure computed by billing: unpaid balances of invoices paid on credit, plus orders paid on credit that are validated but not yet invoiced. Second line: accounts at 90 % or more, and blocked accounts. | Snapshot | Geography, delegate | finance.view | BO-46 ?usage=90 |
| Impayés | finance.overdue | Σ remaining balance of OVERDUE invoices. Second line: number of resellers, and the oldest overdue age in days. | Snapshot | Geography, delegate | finance.view | BO-44 ?status=OVERDUE |
Default layout per role (editable, ANA-02). A role can only be given cards whose permission it holds.
| Role | Default cards |
|---|---|
| Super admin, Direction | All 18, plus Évolution du CA and « À traiter » |
| Commercial admin | Commandes à confirmer, revenue ×3, Évolution du CA, Commandes par statut, Panier moyen, resellers ×3, field ×3, Produits les plus vendus, « À traiter » |
| Stock manager | Commandes à confirmer (to anticipate picking), Commandes par statut, Produits les plus vendus, Ruptures, Alertes de stock |
| Finance manager | Revenue ×3, Panier moyen, Paiements attendus, Crédit utilisé, Impayés, « À traiter » |
| Delivery manager | Commandes à confirmer, Commandes par statut, Ruptures, « À traiter » |
Customisation mode.
- « Personnaliser » opens an edit mode with a Rôle selector.
- In edit mode, cards can be dragged on the grid, hidden (✕) or added from a palette of the cards the role is allowed. « Rétablir la disposition par défaut » resets the role.
- « Enregistrer » saves the layout for every user of that role.
Data.
admin.dashboard.kpis:- Query:
period/from/toplus the five dimension filters. - Response: one entry per permitted card (
key,value,unit,comparison: { value, deltaPct },secondary[],notApplicableFilters[],asOf).
- Query:
admin.dashboard.chartswithchartKey=revenue-trend,orders-by-status,top-productsorconversion-funnel(keys proposed for the contract).admin.dashboard.layout.getandadmin.dashboard.layout.update(roleKey).- Filter options:
reference.wilayas,reference.communes,admin.delegates.crud(GET),admin.brands.crud(GET),admin.categories.crud(GET). - « À traiter » counts come from the same list totals as the sidebar badges. The Commandes à confirmer card and its badge both read
admin.orders.toConfirm(pageSize=1, oldest first) when the user holdsorders.validate. Otherwise the KPI entryorders.toConfirmsupplies them.
Actions.
| Action | Endpoint | Confirmation | Optimistic | Audit |
|---|---|---|---|---|
| Change a filter | admin.dashboard.kpis, admin.dashboard.charts | — | Previous values stay dimmed until the new ones arrive | — |
| Click a card or a status segment | — | — | — | — |
| « Personnaliser » → « Enregistrer » | admin.dashboard.layout.update | « Appliquer cette disposition à tous les utilisateurs du rôle Responsable stock ? » | No | UPDATE on DashboardLayout |
| « Rétablir la disposition par défaut » | admin.dashboard.layout.update | « Rétablir la disposition par défaut pour ce rôle ? » | No | UPDATE |
Rules.
- Permission-aware (ANA-02, cahier §4.1):
- The server returns only the cards the user may see. The client never hides a value it received and never computes a KPI itself.
- Amounts that depend on purchase prices (margins) are not on this page.
- One set of definitions: the same formulas back BO-05, BO-08 and the reports in BO-59. A report run with the same filters returns the same total as the card.
- Updates after business events (cahier §4.1): see Real-time. The footer timestamp
asOfcomes from the server. - Filters that do not apply to a card (brand on finance cards, geography on stock cards) are listed in
notApplicableFilters. The card then shows the muted hint « Filtre Marque non applicable ».
Real-time.
order.created,order.status_changed→ Commandes à confirmer updates immediately (no debounce: +1 on a new order, −1 when an order leavesSUBMITTED/PENDING, then a background refetch to correct drift). Revenue, orders, basket, best sellers and reseller cards refetch, debounced 10 s.tour.started,tour.closed,visit.checked_in,visit.completed→ field cards.stock.alert→ stock cards.account.pending→ the « À traiter » panel.- Finance cards have no event: they refresh every 5 minutes and on focus.
States.
- Loading: one skeleton per card. The chart skeleton has axes.
- Empty period: « Aucune commande validée sur la période » inside the revenue cards, with values « 0,00 DA » and Δ « — ».
- Card error: that card alone shows « Indicateur indisponible » and « Réessayer ».
- No permitted card (
dashboard.viewwithout any module permission): « Aucun indicateur n'est disponible pour votre profil. » - Custom range over 366 days: the picker refuses it with « Période limitée à 366 jours ».
Acceptance criteria.
- With wilaya
31and brand ACEFAST, CA du mois equals the total of the CA par période report in BO-59 for the same filters. - Cancelling a validated order removes its amount from the three revenue cards within 15 s while the stream is connected.
- A finance manager's
admin.dashboard.kpisresponse contains no field or reseller cards. A commercial admin's response contains no finance cards. Both are checked on the API response, not only in the UI. - Mois précédent anchors CA du jour / du mois / de l'année on the last day of the previous month.
- Each card's ⓘ tooltip shows the definition from the table above, in French.
- After a super admin hides Ruptures for the stock-manager role, a stock manager no longer sees it after a reload.
- Délégués en tournée changes within 5 s of a delegate starting a tour.
- An order submitted from the reseller app raises Commandes à confirmer by 1 within 5 s. Confirming it in BO-41 lowers it by 1, and the order then counts in CA du jour under its validation time.
- With the oldest order to confirm waiting over 2 h, the card turns red and « À traiter » lists it.
BO-05 — Sales dashboard
| Field | Value |
|---|---|
| Template | Dashboard |
| Release | V1 |
| Design day | Day 15 — DES-15 |
| Build task | DSH-02 |
| Route | /dashboards/sales |
| Requirements | ANA-01 · ANA-03 · ANA-04 |
| Permissions required | dashboard.view + orders.view. The delegate ranking also needs field.view. |
Purpose. Break revenue down by period, zone, delegate, reseller, brand and product (cahier §4 « Indicateurs et rapports »), and track how much ordering has moved to the apps.
Who uses it. Commercial admin, direction, super admin. Finance reads it for context.
Entry points. Pilotage › Ventes, and « Voir le détail des ventes » from the revenue chart on BO-04.
Layout.
- Filter bar: the BO-04 filters plus Canal (
channel, multi-select ofOrderChannel) and Granularité (granularity=day,weekormonth; default chosen from the period length). - KPI cards:
- CA HT de la période.
- Commandes (counted orders).
- Panier moyen.
- Part des commandes digitales: orders from
RESELLER_APP+RESELLER_WEB÷ all counted orders, the cahier §1.4 digitalisation indicator. - Premières commandes: orders flagged first order and validated in the period (ORD-09).
- Taux d'annulation: orders that entered
CANCELLEDorREFUSEDin the period ÷ orders submitted in the period.
- Charts:
- CA par période (line, with the previous period dashed).
- CA par marque (donut over HOCO, ACEFAST, BOROFONE).
- CA par catégorie (horizontal bars).
- CA par wilaya (bars for the top 15, « Autres » grouped).
- Commandes par canal (stacked bars per bucket).
- Tables:
- Classement des délégués: delegate, CA HT, orders, first orders, Δ vs the previous period. Needs
field.view. - Top revendeurs: reseller, CA HT, orders, last order.
- Top produits: product, brand, quantity, CA HT.
- Classement des délégués: delegate, CA HT, orders, first orders, Δ vs the previous period. Needs
Data.
admin.dashboard.kpis(withdashboard=sales, Assumption).admin.dashboard.chartswithrevenue-trend,revenue-by-brand,revenue-by-category,revenue-by-wilaya,orders-by-channel,top-delegates,top-resellers,top-products.
Actions.
- Click any bar, slice or row to open the matching BO-59 report or list with the same filters (for example a wilaya bar → CA par zone with
wilayaset). - « Exporter » a chart's data as XLSX or CSV through
admin.exports.create(reports.export).
Rules. Definitions of counted orders, net HT, geography and delegate are exactly those of BO-04. A brand or category filter restricts lines, not orders, so the brand donut always sums to the filtered revenue.
Real-time. order.created and order.status_changed refresh the cards and charts (debounced 10 s).
States.
- Each chart has its own skeleton and error.
- « Aucune vente sur la période ».
- Without
field.view, the delegate ranking is not rendered.
Acceptance criteria.
- The donut slices sum to CA HT de la période to within 1 DA (rounding).
- The digital share for a day equals the BO-38 count with
channel=RESELLER_APP&channel=RESELLER_WEBdivided by all counted orders of that day. - A click on the ACEFAST slice opens CA par marque in BO-59 with
brandset to ACEFAST and the same period. - Changing the granularity to
weekregroups the trend without changing the period total.
BO-06 — Field dashboard & live map
| Field | Value |
|---|---|
| Template | Bespoke |
| Release | V1 |
| Design day | Day 15 — DES-15 |
| Build task | DSH-08 |
| Route | /dashboards/field (?mode=screen for the wall display) |
| Requirements | FLD-13 · SYN-07 · FLD-15 · INT-01 |
| Permissions required | dashboard.view + field.view |
Purpose. Follow today's field activity live (cahier §4.5 « Suivi du démarrage, des visites, des positions et de la clôture »): who is on tour, where, how far along, and what needs attention.
Who uses it. Commercial admin (field supervision), direction, super admin.
Entry points.
- Pilotage › Terrain.
- The Délégués en tournée card on BO-04.
- The live badge in the sidebar.
Layout.
- KPI strip (today):
- Délégués en tournée (
4 / 6). - Visites réalisées (
46 / 58). - Check-ins hors tolérance: arrivals beyond the GPS tolerance, 150 m by default.
- Nouveaux prospects, Comptes créés and Premières commandes, all for today.
- Délégués en tournée (
- Map (two thirds of the width, full height):
- Delegate markers: avatar initials, coloured by tour status.
IN_PROGRESSuses the progress tone,SUSPENDEDthe warning tone, and a position older than 15 min turns grey with the label « il y a 17 min ». - Stops: today's stops as small numbered dots coloured by
VisitStatus, with derived « En retard » in the danger tone. - Popover on a delegate marker: name,
TRNnumber, progress « 7 / 12 », current or next stop, time and accuracy of the last position, km today. Actions: « Ouvrir la tournée » (BO-23) and « Appeler ».
- Delegate markers: avatar initials, coloured by tour status.
- Side panel (one third):
- Tournées du jour, in-progress first. Each row: delegate,
TRN, status pill, a done/planned progress bar, late visits, last-position age, km. - Filter chips: Zone, Statut.
- Alertes feed, newest first:
- visit not done (with its reason);
- check-in out of tolerance (with the distance);
- position stale for more than 15 min while
IN_PROGRESS; - tour not started 60 min after its planned start;
- visit late, meaning still planned after its time slot. Assumption — the 60-min delay is a setting to confirm.
- Tournées du jour, in-progress first. Each row: delegate,
- « Mode écran » (
?mode=screen, large screens): full screen without the sidebar, larger markers, the map fitted to all delegates, the alerts feed only.
Data.
admin.live.delegates: delegates on tour with last position (lat,lng,accuracy,capturedAt), progress and next stop. Re-polled every 60 s as a safety net.admin.tours.list:from=to= today.admin.visits.list: today, for the alerts and stop dots.admin.dashboard.kpis: field cards.admin.zones.crud(GET): the zone filter.
Actions.
| Action | Endpoint | Behaviour |
|---|---|---|
| Click a delegate (map or list) | — | Opens the popover, then « Ouvrir la tournée » goes to BO-23 |
| « Appeler » | — | tel: link to the delegate's phone |
| Filter by zone or status | — | Updates zone and status in the URL and refits the map |
| « Mode écran » | — | Toggles ?mode=screen |
Rules.
- Positions exist only while a tour is
IN_PROGRESS(sampling every 5 min or 200 m; FLD-15).- After a suspension, the last known position stays visible in grey with its time.
- After the closing, the marker disappears.
- Nothing older than the retention period (90 days) is ever shown.
- Tolerance alerts use the settings
field.gpsToleranceMeters(150 m) andfield.maxAccuracyMeters(100 m). A check-in whose accuracy exceeds 100 m is flagged « Précision GPS insuffisante » rather than out of tolerance. - Map provider: Google Maps or Mapbox (INT-01, decision pending).
Real-time.
delegate.positionmoves the marker (animated in at most 1 s) and resets its age.tour.startedadds the delegate.tour.closedremoves the marker and marks the row Terminée.visit.checked_inturns the stop Arrivé and may raise a tolerance alert.visit.completedturns the stop Visité or Non réalisée and updates the counters.
States.
- No tour today: « Aucune tournée aujourd'hui », with a link to the planner.
- Delegate without a position yet: listed with « En attente de la première position ».
- Map provider failure: the list and alerts stay usable, with « Carte indisponible ».
- Stream lost: banner, and
admin.live.delegatespolled every 30 s.
Acceptance criteria.
- A delegate who starts a tour appears on the map within 5 s, without a reload.
- Each
delegate.positionevent moves the marker. With no ping for 15 min, the marker turns grey and shows its age. - A check-in 400 m from the point of sale adds an alert « Check-in à 400 m du point de vente ».
- The counters match BO-24 filtered on today.
- After the tour is closed, the delegate's position is no longer displayed.
BO-07 — Stock dashboard
| Field | Value |
|---|---|
| Template | Dashboard |
| Release | V1 |
| Design day | Day 15 — DES-15 |
| Build task | DSH-05 |
| Route | /dashboards/stock |
| Requirements | STK-05 · STK-02 · ANA-03 |
| Permissions required | dashboard.view + stock.view. The Valeur du stock card also needs pricing.view. |
Purpose. Show stock-outs, low stock, reservations and slow movers at a glance (cahier §4.1, §4.3), so the stock manager knows what to reorder and what is not selling.
Who uses it. Stock manager, super admin, direction. Commercial and delivery managers read it.
Entry points. Pilotage › Stock, the sidebar badge, and stock.alert toasts.
Layout.
- Filters: Entrepôt (
warehouse; « Tous » by default), Marque, Catégorie, and Période (period, used for the flow cards only). - KPI cards:
- Ruptures (same definition as BO-04).
- Stock bas.
- Quantité réservée: Σ reserved units, with the number of orders holding reservations.
- Valeur du stock: Σ physical × purchase price HT. Needs
pricing.view. - Entrées: units received over the period (
RECEIPT,RETURN, and positiveADJUSTMENT/INVENTORY). - Sorties: units shipped over the period (
SHIPMENT). - Couverture inférieure à 7 jours: active variants whose available ÷ average daily shipped quantity over the last 30 days is below 7.
- Lists:
- Ruptures à fort impact: out-of-stock variants ranked by quantity sold over the last 30 days.
- Produits à faible rotation: active variants with physical stock above 0 and no
SHIPMENTfor 60 days, sorted by quantity. - Derniers mouvements: the 10 latest.
- Chart: Entrées et sorties par jour (paired bars).
Data.
admin.dashboard.kpis.admin.dashboard.chartswithstock-flowsandstock-cover.admin.stock.levelswithalert=OUToralert=LOW, sorted by sales.admin.stock.movementswithpageSize=10.
Actions.
- Each card and list row opens BO-33 with the matching filter, or the variant's movements in BO-34.
- « Nouvelle réception » opens BO-35 (with
stock.create).
Rules.
- Available = physical − reserved (STK-02).
- Quantities are a back-office-only view (STK-07). Resellers and guests only see
IN_STOCK(En stock) orOUT_OF_STOCK(En rupture), derived from available stock above 0. A low-stock alert here never changes what resellers see. - Cancellations and return receptions put quantities back automatically (STK-03). They show in Entrées as
RESERVATION_RELEASE(not counted) andRETURN(counted) movements. - Thresholds are per variant (and per warehouse when stock is managed per warehouse).
- Slow movers exclude variants created less than 60 days ago.
- Without
pricing.view, the value card is not rendered and the API leaves the value out.
Real-time. stock.alert refreshes the alert cards and lists and shows the toast described in the overview. order.status_changed (reservations and shipments) refreshes the reserved and flow cards, debounced 10 s.
States.
- « Aucune rupture » appears as a positive empty state in the success tone.
- With a single warehouse, the warehouse filter is hidden.
Acceptance criteria.
- A shipment that brings a variant's available stock to 0 increases Ruptures and lists the variant within 15 s.
- Valeur du stock is absent from the API response for a user without
pricing.view. - The Couverture inférieure à 7 jours count matches BO-33 sorted by cover with the same filters.
BO-08 — Finance dashboard
| Field | Value |
|---|---|
| Template | Dashboard |
| Release | V1 |
| Design day | Day 15 — DES-15 |
| Build task | DSH-07 |
| Route | /dashboards/finance |
| Requirements | ANA-01 · FIN-05 · FIN-01 · ACL-04 |
| Permissions required | dashboard.view + finance.view |
Purpose. Show collections, receivables, overdue amounts and credit exposure (cahier §4.1 « Paiements attendus, crédit utilisé et impayés », §4.7).
Who uses it. Finance manager, super admin, direction.
Entry points. Pilotage › Finance, and the finance cards of BO-04.
Layout.
- Filters: Période, Wilaya, Commune, Délégué, Mode de paiement (
paymentMethod, multi-select ofPaymentMethod). - KPI cards (amounts TTC, as invoiced):
- Encaissé: Σ
CONFIRMEDpayments received in the period. - Paiements attendus, Impayés, Crédit utilisé: same definitions as BO-04.
- Déclarations à vérifier: count, amount and oldest age.
- Factures émises: count and Σ TTC over the period.
- Avoirs émis: count and Σ TTC of credit notes issued over the period, from returns and after-sales resolutions (FIN-08).
- Encaissé: Σ
- Charts:
- Encaissements par mode de paiement: stacked bars per bucket, over
PaymentMethod. - Balance âgée:
OVERDUEbalances in the buckets 1–30, 31–60, 61–90 and over 90 days past the due date.
- Encaissements par mode de paiement: stacked bars per bucket, over
- Lists:
- Principaux débiteurs (top 10): reseller, overdue amount, oldest due date, credit used %, delegate.
- Déclarations à vérifier: the 5 oldest, each linking to BO-45.
Data.
admin.dashboard.kpis.admin.dashboard.chartswithpayments-by-methodandreceivables-aging.admin.invoices.listwithstatus=OVERDUE, sorted by balance.admin.payments.listwithstatus=PENDING_VERIFICATION, sorted by date ascending.
Actions. Cards and rows open BO-44, BO-45 or BO-46 with the same filters. A debtor row opens the reseller's 360° on its Finance tab.
Rules.
- Financial data is visible only with
finance.view(ACL-04). The page shows no margins. - Invoice amounts are TTC, while the revenue on BO-04 and BO-05 is HT. Each card says which one it uses.
- An invoice is overdue when the daily job has set
OVERDUE(due date passed with a balance left).
Real-time. No finance event in V1. The page refreshes every 5 minutes and on focus.
States. « Aucun impayé » appears as a positive empty state. In each chart, an empty bucket shows « 0 ».
Acceptance criteria.
- The Balance âgée buckets sum to the Impayés card.
- Confirming a declaration in BO-45 lowers Déclarations à vérifier and raises Encaissé at the next refresh.
- A user without
finance.viewgets « Accès refusé » on/dashboards/finance, and the menu item is hidden.