CRM
Page specs BO-09 to BO-15: resellers and prospects list and map, reseller 360°, create and edit form, validation queue, follow-ups and segments.
The CRM pages cover the commercial life of a point of sale, from prospect (prospect) to active reseller (revendeur actif) (cahier §4.2). Its ten lifecycle statuses are in ResellerStatus. Every record also carries a customer type (CustomerType: Super-gros, Demi-gros, Détail). The reseller declares it at sign-up, the back-office confirms it at validation, and only the back-office changes it afterwards, with an audit trail (AUTH-11). The customer type selects which of the three variant prices the reseller pays (PRC-02).
| ID | Screen | Section | Template | Release | Design | Build |
|---|---|---|---|---|---|---|
BO-09 | Resellers & prospects list | CRM | bespoke | V1 | Day 16 | DSH-03 |
BO-10 | Resellers map | CRM | bespoke | V1 | Day 16 | DSH-03 |
BO-11 | Reseller 360° | CRM | bespoke | V1 | Day 16 | DSH-03 |
BO-12 | Reseller create / edit | CRM | template | V1 | Day 16 | DSH-03 |
BO-13 | Account validation queue | CRM | bespoke | V1 | Day 16 | DSH-03 |
BO-14 | Follow-ups & tasks | CRM | template | V1 | Day 16 | DSH-03 |
BO-15 | Segments | CRM | template | V1 | Day 16 | DSH-03 |
BO-09 — Resellers & prospects list
| Field | Value |
|---|---|
| Template | Bespoke (List skeleton, with a map toggle) |
| Release | V1 |
| Design day | Day 16 — DES-16 |
| Build task | DSH-03 |
| Route | /crm/resellers |
| Requirements | CRM-11 · CRM-06 · CRM-05 · ANA-05 |
| Permissions required | crm.view (page) · crm.create (« Nouveau revendeur ») · crm.update (bulk actions, status, assignment) · crm.export (export) · orders.view (revenue column) · field.view (last-visit column) |
Purpose. Find any point of sale, prospect or reseller, filter the network by lifecycle, geography, delegate, segment, potential and activity, and act on it in bulk (cahier §4.2, §4.1 filters).
Who uses it. Commercial admin daily. Super admin and direction read it. The finance manager reads it to find a reseller.
Entry points.
- CRM › Revendeurs.
- Global search « Voir tous les résultats ».
- Clicks from the reseller cards of BO-04.
- The delegate detail (BO-17) with
delegate=set.
Layout.
- Header: « Revendeurs et prospects » with the count. The primary action is « Nouveau revendeur » (BO-12). « Exporter » sits next to it, and a Liste / Carte switch leads to BO-10 with the same filters.
- View tabs (
view):- « Tous »;
- « Prospects » (
NEW_PROSPECT,TO_CONTACT,INTERESTED); - « En activation » (
APP_INSTALLED,ACCOUNT_CREATED,FIRST_ORDER); - « Actifs » (
ACTIVE); - « À relancer » (
TO_FOLLOW_UP); - « Non intéressés » (
NOT_INTERESTED); - « Suspendus » (
SUSPENDED).
- Table:
| Column | Source field | Sortable | Default visible |
|---|---|---|---|
| N° | number (REV-000123, mono) | Yes | Yes |
| Magasin | storeName + façade thumbnail | Yes | Yes |
| Responsable | ownerName | Yes | Yes |
| Téléphone | phone (national display, click to call) | No | Yes |
| Type de client | customerType (pill) | Yes | Yes |
| Wilaya | wilayaName | Yes | Yes |
| Commune | communeName | Yes | Yes |
| Statut | status (ResellerStatus pill) | Yes | Yes |
| Compte | accountStatus (AccountStatus pill; « — » for a prospect without an account) | Yes | Yes |
| Délégué | assignedDelegate.displayName | Yes | Yes |
| Dernière commande | lastOrderAt | Yes | Yes |
| Zone | zone.name | Yes | No |
| Segments | segments[].name (chips, at most 2 + « +1 ») | No | No |
| Potentiel | potential | Yes | No |
| Type de commerce | businessType (BUSINESS_TYPE label) | Yes | No |
| Source | source (ResellerSource) | Yes | No |
| CA 90 jours | revenue90d (net HT, same basis as BO-04; needs orders.view) | Yes | No |
| Dernière visite | lastVisitAt (needs field.view) | Yes | No |
| Tâches ouvertes | openTasksCount | Yes | No |
| Créé le | createdAt | Yes | No |
| Filter | Control | URL parameter |
|---|---|---|
| Search (name, number, phone, owner) | Text | q |
| Statut | Multi-select (preset by the tab) | status |
| Compte | Multi-select of AccountStatus | accountStatus |
| Type de client | Multi-select of CustomerType | customerType |
| Wilaya, Commune | Searchable selects (commune depends on wilaya) | wilaya, commune |
| Zone | Select | zone |
| Délégué | Select, including « Non assigné » | delegate (none for unassigned) |
| Segment | Multi-select | segment |
| Potentiel | Multi-select of Potential | potential |
| Type de commerce | Multi-select | businessType |
| Source | Multi-select | source |
| Activité | Select: « Actif (60 j) », « Inactif », « Jamais commandé » | activity = active / inactive / never |
| Validation | Select of ValidationStatus | validation |
| Créé entre / Validé entre | Date ranges | createdFrom, createdTo, approvedFrom, approvedTo |
| Position GPS | « Avec » / « Sans » | hasGps |
- Default sort:
createdAtdescending. The « Actifs » tab sorts bylastOrderAtdescending instead. - Bulk actions:
| Bulk action | Endpoint | Input |
|---|---|---|
| « Assigner un délégué » | admin.resellers.bulk (action: ASSIGN) | Delegate and zone (the zone defaults to the commune's zone) |
| « Changer le statut » | admin.resellers.bulk (action: STATUS) | Target status (manual statuses only) and a reason |
| « Ajouter à un segment » / « Retirer du segment » | admin.resellers.bulk (action: TAG) | Segment |
| « Planifier une visite » | — | Opens BO-22 with the selection preselected as stops (?stops=…, at most 40) |
| « Exporter la sélection » | admin.resellers.export | Format |
- Row actions: « Ouvrir la fiche », « Modifier », « Appeler », « WhatsApp », « Créer une tâche », « Changer le statut ».
- Export: XLSX or CSV of the current filters and visible columns.
Data.
admin.resellers.listwithpage,pageSize,sort,orderand the filters above.admin.resellers.bulkandadmin.resellers.export.- Option lists:
reference.wilayas,reference.communes,reference.lists(BUSINESS_TYPE),admin.delegates.crud,admin.zones.crud,admin.segments.crud(GET).
Actions. Bulk assignment and tagging run through one call each, report per-record results and are not optimistic. Their confirmation reads « Assigner 24 revendeurs à Yacine M. (zone Oran Est) ? Les tournées déjà planifiées ne sont pas modifiées. ». A bulk status change requires a reason and writes one STATUS_CHANGE audit entry per reseller. A reassignment notifies the new delegate (push « 24 points de vente vous ont été attribués »).
Rules.
- Statuses in bulk: only the manual statuses (
TO_CONTACT,INTERESTED,NOT_INTERESTED,TO_FOLLOW_UP,SUSPENDED) can be set.APP_INSTALLED,ACCOUNT_CREATED,FIRST_ORDERandACTIVEare set by events only (CRM-04). - Assignment keeps its history:
ResellerAssignmentrows are visible in the 360° (CRM-05). - Exports: every export is audited (
EXPORT) with its filters, because it contains personal data (AUD-01).
Real-time. account.pending shows the banner « 1 nouveau compte en attente de validation » with a link to BO-13. The list itself refreshes only on demand, so rows never jump under the cursor.
States.
- First use: « Aucun revendeur pour l'instant », with « Nouveau revendeur » and « Importer » (BO-67).
- No results: reset link.
- Hidden columns: without
orders.vieworfield.view, the revenue and last-visit columns are absent from the column menu.
Acceptance criteria.
- Filtering on wilaya
31, the « Prospects » tab and « Non assigné » returns exactly the unassigned prospects of Oran, and the URL reproduces the view when shared. - Searching
0555123456finds the reseller whose phone is+213555123456. - Bulk-assigning 24 records produces 24
ResellerAssignmentrows and one notification to the delegate. - The status menu offers no automatic status.
- An export of 3 000 rows completes in the export centre, and the activity log shows an
EXPORTentry with the filters.
BO-10 — Resellers map
| Field | Value |
|---|---|
| Template | Bespoke |
| Release | V1 |
| Design day | Day 16 — DES-16 |
| Build task | DSH-03 |
| Route | /crm/resellers/map (same filter parameters as BO-09, plus bbox and zoom) |
| Requirements | CRM-11 · INT-01 · GEO-01 |
| Permissions required | crm.view · crm.update (bulk actions on a lasso selection) |
Purpose. See the network on a map: density by commune, uncovered areas, prospects near active resellers. Select points of sale geographically for assignment or a tour.
Who uses it. Commercial admin (zoning, tour preparation), direction.
Entry points. The Carte switch on BO-09, and « Voir sur la carte » in the 360° (BO-11).
Layout.
- Map: full width, with the BO-09 filter bar on top.
- Pins and clusters:
- Pins are coloured by
ResellerStatustone and shaped by customer type (circle Détail, square Demi-gros, diamond Super-gros). - Clusters show counts above the server threshold.
- Pins are coloured by
- Legend at the bottom left, with the count per status in the current view.
- Missing GPS: the chip « 37 sans position GPS » links to BO-09
?hasGps=false. - Pin popover: store name, number, status and type pills, delegate, last order, last visit. Actions: « Ouvrir la fiche », « Ajouter à une tournée », « Appeler ».
- Lasso tool (polygon select): its sticky bar offers « 18 sélectionnés : Assigner · Ajouter au segment · Planifier une visite ».
- Layer toggle « Zones »: highlights the pins of the selected zone and greys the others.
Data. admin.resellers.map with bbox, zoom and the BO-09 filters. It returns pins, or clusters { lat, lng, count } above the threshold, and is refetched on pan and zoom (debounced 300 ms). Bulk actions call admin.resellers.bulk.
Actions. Same bulk actions and confirmations as BO-09. « Ajouter à une tournée » asks for the delegate and date, then opens BO-22 with the stop added.
Rules.
- Coordinates come from the record's GPS: captured by the delegate, set in BO-12, or geocoded when the maps provider allows it (GEO-04).
- The lasso selects at most 200 pins. Beyond that: « Sélection limitée à 200 points — zoomez ».
Real-time. None.
States. Map provider failure shows « Carte indisponible — utilisez la liste » with the switch back. A filter with no geolocated result shows « Aucun point géolocalisé pour ces filtres ».
Acceptance criteria.
- The status legend counts match BO-09 with the same filters and
hasGps=true. - Zooming in splits clusters into pins without losing any record.
- A lasso of 18 pins followed by « Assigner » assigns exactly those 18.
BO-11 — Reseller 360°
| Field | Value |
|---|---|
| Template | Bespoke (Detail skeleton) |
| Release | V1 |
| Design day | Day 16 — DES-16 |
| Build task | DSH-03 |
| Route | /crm/resellers/[resellerId] (?tab=) |
| Requirements | CRM-12 · CRM-07 · CRM-04 · CRM-05 · CRM-10 · AUTH-11 |
| Permissions required | crm.view (page) · crm.update (edit, status, customer type, assignment, notes, documents) · crm.validate (validation shortcut) · orders.view (Commandes tab, KPIs) · orders.create (« Nouvelle commande ») · finance.view (Finance tab, balance and credit) · field.view (Visites tab) · support.view (Tickets et SAV tab) |
Purpose. One page that tells everything about a point of sale (cahier §4.2): identity and contacts, location, lifecycle and customer type, assigned delegate, orders, invoices, payments, credit, visits and reports, tasks, interactions, tickets and after-sales claims, returns, documents and the invitation funnel.
Who uses it. The commercial admin before a call or a decision. The finance manager for credit questions. Direction to understand a key account.
Entry points.
- BO-09 and BO-10 rows and pins.
- Global search.
- Links from orders (BO-39), visits (BO-25), tickets (BO-56), the validation queue (BO-13) and the finance pages.
Header
- Identity line: façade photo (64 px), store name,
REV-000123(copy on click).- Pills:
ResellerStatus,AccountStatuswhen an account exists,ValidationStatuswhile pending, and customer type (CustomerType, brand tone).
- Pills:
- Facts row:
- Owner name and contact role (
PERSON_ROLE). - Phone (click to call) and WhatsApp (
wa.me). - Wilaya and commune. The support region from BO-70 shows in a tooltip.
- Assigned delegate (link to BO-17) and zone.
- Source and creating delegate.
- Segments and potential.
- « Client depuis » (the approval date).
- Owner name and contact role (
- KPI strip (each card needs its permission):
- CA 12 mois (net HT).
- Commandes (12 months).
- Panier moyen.
- Dernière commande (date, plus days since).
- Solde dû (TTC;
finance.view). - Crédit disponible (limit − used;
finance.view). - Dernière visite (
field.view).
- Primary action, chosen by state:
- « Valider le compte », when validation is
PENDINGand the user hascrm.validate: opens the BO-13 decision panel for this record. - Otherwise « Nouvelle commande » (BO-40, with
orders.create). It is disabled with a tooltip when the account is not active or the reseller isSUSPENDED.
- « Valider le compte », when validation is
- « Plus » menu: « Modifier » (BO-12), « Changer le statut », « Changer le type de client », « Assigner », « Ajouter une note / un appel », « Créer une tâche », « Planifier une visite », « Voir sur la carte », « Suspendre » or « Réactiver ».
Tabs
Tab (tab) | Content | Data | Needs |
|---|---|---|---|
Aperçu (overview, default) | Left: contacts, delivery addresses (default marked), mini-map with the pin and GPS accuracy, photos strip, business type, potential, notes. Right: the five latest interactions, open tasks (with due dates, overdue in red), the latest visit with its interest level and next action. | admin.resellers.get | crm.view |
Commandes (orders) | Orders table (number, date, channel, status, total TTC, first-order badge), filters by status and period. A row opens BO-39. | admin.orders.list ?reseller= | orders.view |
Finance (finance) | Credit card (limit, used, available, blocked flag, limit history), invoices (number, date, due date, TTC, balance, status), payments and declarations (number, method, amount, status, allocation), credit notes. | admin.credit.get, admin.invoices.list, admin.payments.list ?reseller= | finance.view |
Visites (visits) | Visits table (number, date, delegate, type, status, check-in distance, duration, interest level, next action). A row opens BO-25. Visit reports are summarised inline: person met, products and promotions presented, issues. | admin.visits.list ?reseller= | field.view |
Tâches (tasks) | Follow-ups for this reseller: open first, then done; « Créer une tâche ». | admin.tasks.list ?reseller= | crm.view |
Historique (timeline) | Unified timeline (CRM-07): calls, visits, notes, WhatsApp, SMS, email, orders, payments, claims, status changes, customer-type changes, assignments, newest first. Filter chips per InteractionType. Each entry shows the actor, the time and a link to its record. « Ajouter une note » sits on top. | admin.resellers.timeline | crm.view |
Tickets et SAV (support) | Tickets, after-sales claims first (type, related order, status, resolution), and return requests (RET-…, status, credit note). | admin.tickets.list ?reseller=, admin.returns.list ?reseller= | support.view; returns need orders.view |
Documents (documents) | Documents (DocumentType: RC, NIF, NIS, AI, ID, contract) and photos (PhotoKind) with upload date, uploader and expiry when relevant. « Ajouter ». Files open through signed URLs. | admin.resellers.documents, admin.resellers.documents.add, files.downloadUrl | crm.view; adding needs crm.update |
Activation (activation) | The invitation funnel (InvitationStep, each step dated): sent → app installed → account created → phone verified → catalog viewed → first order, with the inviting delegate and channel. | admin.invitations.list ?reseller= | field.view |
Tab labels carry the counters returned by admin.resellers.get (« Commandes 18 », « Tâches 2 »). A tab whose permission is missing is not rendered.
Data.
admin.resellers.get: header, KPIs, counters andversion.- The tab operations above.
admin.resellers.setStatus,admin.resellers.update(customer type, other fields),admin.resellers.assign,admin.resellers.notes.add,admin.tasks.create.
Actions
| Action | Endpoint | Confirmation (FR) | Reason | Optimistic | Audit | Notifies |
|---|---|---|---|---|---|---|
| « Changer le statut » | admin.resellers.setStatus | « Passer Boutique Nour de Intéressé à Non intéressé ? » | Required, free text | No | STATUS_CHANGE (from → to, reason) | Assigned delegate (in-app) |
| « Suspendre » | admin.resellers.setStatus (SUSPENDED) | « Suspendre Boutique Nour ? Le revendeur ne pourra plus commander ; ses commandes en cours ne sont pas annulées. » | Required | No | STATUS_CHANGE | Reseller (SMS + push « Votre compte est suspendu, contactez HOCO ») and delegate |
| « Changer le type de client » | admin.resellers.update (customerType) | « Passer Boutique Nour de Détail à Demi-gros ? Les prix Demi-gros s'appliqueront aux prochains paniers ; les commandes déjà passées ne changent pas. » | Required | No | UPDATE (customerType before → after, reason) | Reseller (push + in-app « Vos conditions tarifaires ont été mises à jour ») |
| « Assigner » | admin.resellers.assign | « Assigner à Yacine M. (zone Oran Est) ? » | Optional | No | UPDATE + ResellerAssignment row | New and previous delegate |
| « Ajouter une note / un appel » | admin.resellers.notes.add | — | — | Yes: appears at once in Historique, rolled back on error | CREATE (interaction) | — |
| « Créer une tâche » | admin.tasks.create | — (drawer, see BO-14) | — | No | CREATE | Assignee |
| « Planifier une visite » | — | Asks for delegate and date, then opens BO-22 with the stop | — | — | — | — |
| « Ajouter un document » | files.createUpload → files.completeUpload → admin.resellers.documents.add | — | — | No | CREATE | — |
Rules
- Customer type (AUTH-11, PRC-02):
- It drives the price: the reseller pays the variant's
priceSuperGros,priceDemiGrosorpriceDetail, then promotions. - A change applies to carts re-priced after the change. Submitted orders keep their prices.
- Changing it is a back-office action only, always audited with a reason.
- It drives the price: the reseller pays the variant's
- Statuses: the status menu offers only the manual transitions of the reseller lifecycle (CRM-04). Automatic statuses come from events: install, account creation, first order, activity window.
- Suspension blocks new orders (checkout answers
403) without touching orders already submitted. - Purchase prices and margins never appear on this page (ACL-04). Finance figures need
finance.view. - Documents and photos are private files read through 5-minute signed URLs (SEC-04).
- Concurrency: header edits send
version. A409 VERSION_CONFLICTopens the standard reload dialog.
Real-time.
order.createdandorder.status_changedfor this reseller refresh the KPI strip and the Commandes tab, debounced 10 s.visit.completedfor this reseller refreshes Visites and Aperçu.
States.
- Prospect without an account: no Finance tab, the KPIs show « — », and the primary action is « Planifier une visite ».
- Rejected validation: a banner shows the rejection reason and date.
- Restricted tabs: a tab whose permission is missing is not rendered; deep links to it fall back to Aperçu.
- Archived or merged record (V2 merge, CRM-13): read-only banner.
- Not found or out of scope: the « Introuvable » page.
Acceptance criteria
- The page shows orders, invoices, payments, visits, reports, tasks and interactions of the reseller in one place (CRM-12). Each tab's count matches its list.
- Changing the customer type from Détail to Demi-gros writes an
UPDATEaudit entry with both values and the reason. The reseller's next cart shows Demi-gros prices; an order already submitted keeps its prices. - A finance manager sees the Finance tab. A commercial admin without
finance.viewsees neither the tab nor the balance and credit cards, and the API response contains no finance fields. - Suspending the reseller makes the next checkout from the reseller app fail with the suspension message. The timeline shows the status change with its reason.
- A note added here appears in the delegate app's point-of-sale sheet after its next sync.
- The Activation tab shows each invitation step with its date, matching the delegate app (IVT-03).
BO-12 — Reseller create / edit
| Field | Value |
|---|---|
| Template | Form |
| Release | V1 |
| Design day | Day 16 — DES-16 |
| Build task | DSH-03 |
| Route | /crm/resellers/new · /crm/resellers/[resellerId]/edit |
| Requirements | CRM-01 · CRM-02 · CRM-03 · AUTH-11 · CRM-10 · GEO-01 |
| Permissions required | crm.create (new) · crm.update (edit) |
Purpose. Create a prospect or reseller from the back-office, for example after a phone call (source BACK_OFFICE), or correct a record. The minimum data set of cahier §15.5 is enforced, and the phone is unique.
Who uses it. Commercial admin, super admin.
Entry points. « Nouveau revendeur » on BO-09, and « Modifier » on BO-11.
Layout. A page form with four sections and the sticky save bar. Duplicate hints show inline under the phone field.
| Section | Field | Type | Validation | Default | Help text / permission |
|---|---|---|---|---|---|
| Magasin | Nom du magasin | Text | Required, 2–120 characters | — | — |
| Type de commerce | Select (BUSINESS_TYPE) | Required | — | — | |
| Type de client | Three radio cards (CustomerType) with one line each: « Gros volumes, prix le plus bas », « Volumes intermédiaires », « Boutique de détail » | Required | None. The user must choose. | « Détermine lequel des trois prix s'applique au revendeur. » In edit mode, see the rule below. | |
| Potentiel | Segmented control (Potential) | Optional | MEDIUM | — | |
| Contact | Responsable | Text | Required, 2–80 characters | — | — |
| Fonction | Select (PERSON_ROLE) | Optional | « Gérant » | — | |
| Téléphone principal | Phone (national or E.164) | Required, valid Algerian mobile or landline, unique after normalisation | — | Live duplicate check (see Rules). Read-only once the reseller has an account. | |
| Phone + « Identique au téléphone » checkbox | Optional, valid | Checkbox ticked | — | ||
| Optional, valid | — | — | |||
| Adresse | Wilaya | Searchable select | Required | — | — |
| Commune | Searchable select, filtered by wilaya | Required | — | — | |
| Adresse | Text | Required, 5–200 characters | — | — | |
| Repère | Text | Optional, 120 characters max | — | « Ex. : en face de la poste » | |
| Position GPS | Map picker (drag the pin) + « Géolocaliser l'adresse » | Optional; lat/lng inside Algeria | Geocoded from the commune centroid | « Indispensable pour les tournées » | |
| Affectation | Délégué | Select of delegates whose zones contain the commune | Optional | — | Warning when the delegate's zones do not contain the commune |
| Zone | Select | Optional | The commune's zone | — | |
| Segments | Multi-select | Optional | — | — | |
| Statut initial | Select: NEW_PROSPECT, TO_CONTACT, INTERESTED (create only) | Required | NEW_PROSPECT | — | |
| Pièces | Documents | Uploader per DocumentType (PDF/JPEG/PNG, 10 MB max) | Optional | — | Private files |
| Photos | Uploader per PhotoKind (JPEG/PNG/WebP, 5 MB max) | Optional | — | The façade photo is recommended |
Data.
admin.resellers.checkPhone: debounced 400 ms once the number is valid.admin.resellers.create/admin.resellers.update(withversion).files.createUploadandfiles.completeUpload, thenadmin.resellers.documents.add.- Option lists:
reference.wilayas,reference.communes,reference.lists.
Actions.
| Action | Endpoint | Behaviour | Audit |
|---|---|---|---|
| « Créer le revendeur » | admin.resellers.create | Source BACK_OFFICE, REV- number assigned by the server, then BO-11 opens | CREATE |
| « Enregistrer » (edit) | admin.resellers.update | Back to BO-11 with the toast « Fiche mise à jour » | UPDATE with the changed fields |
| Change of customer type in edit mode | admin.resellers.update | Confirmation « Passer de Détail à Demi-gros ? Les prix Demi-gros s'appliqueront aux prochains paniers. » and a required reason | UPDATE (customerType before → after); reseller notified |
Rules.
- Duplicates (CRM-03):
- When the phone already exists, an inline card shows « Ce numéro appartient déjà à REV-000123 — Boutique Nour (Oran, Revendeur actif) » with « Ouvrir la fiche ». Creation is blocked.
- The server answers
409 PHONE_ALREADY_EXISTSif the check is bypassed.
- Phone of an account holder: read-only, with the hint « Le revendeur modifie son numéro depuis l'application (vérification par SMS) » (
OtpPurpose.PHONE_CHANGE). - Customer type (AUTH-11):
- It is required at creation.
- For a reseller whose account is not yet validated, the type stays editable without a reason; validation confirms it (BO-13).
- After validation, any change requires a reason and is audited.
- Status is not edited here after creation; « Changer le statut » on BO-11 does it.
- Unsaved changes are guarded. A
409 VERSION_CONFLICTshows the reload dialog.
Real-time. None.
States.
- The duplicate check shows a spinner beside the field while running.
- Upload progress and errors are shown per file.
- With a delegate outside the commune's zones: the warning « Ce délégué ne couvre pas cette commune » (saving stays possible).
Acceptance criteria.
- Entering an existing phone, in any format, shows the duplicate card and disables « Créer ».
- Saving without a customer type is refused with « Choisissez un type de client ».
- Editing the customer type of a validated reseller asks for a reason and adds an
UPDATEaudit entry. - A record created here appears in BO-09 with source Back-office, and in the delegate app of the assigned delegate after sync.
BO-13 — Account validation queue
| Field | Value |
|---|---|
| Template | Bespoke (List skeleton, master-detail) |
| Release | V1 |
| Design day | Day 16 — DES-16 |
| Build task | DSH-03 |
| Route | /crm/validation (?id= for the selected record) |
| Requirements | CRM-09 · FLD-14 · AUTH-11 · CRM-03 · AUTH-05 |
| Permissions required | crm.validate (page and decisions) · crm.update (edits made from the panel) |
Purpose. Approve or reject new reseller accounts (self sign-up, claims of field prospects) and new points of sale created by delegates (cahier §4.2 « La validation du compte est réalisée par un utilisateur autorisé », §4.5 « Validation des nouveaux points de vente »). Before an account is approved, the reviewer confirms or changes the declared customer type and checks the duplicate hints.
Who uses it. Commercial admin, several times a day. The super admin.
Entry points.
- CRM › Comptes à valider (badge).
- The
account.pendingtoast. - The « À traiter » panel on BO-04.
- « Valider le compte » on BO-11.
Layout
- Tabs:
- « Tous ».
- « Comptes revendeurs »: self sign-ups and claims,
AccountStatus = PENDING_VALIDATION. - « Nouveaux points de vente »: field creations by delegates,
ValidationStatus = PENDINGwithout an account. - « Doublons probables »: items with at least one strong hint.
- Left column, the queue (oldest first, 400 px):
- Each item shows the store name, commune and wilaya, the source (
ResellerSourceicon), the creating delegate or « Inscription revendeur », and the waiting time. - Waiting time turns amber after 24 h and red after 48 h. Assumption — the SLA is to confirm with HOCO.
- Badges: declared customer type, duplicate-hint count (danger tone when strong), and completeness (« 4/6 pièces »).
- Each item shows the store name, commune and wilaya, the source (
- Right panel, the decision for the selected item:
- Identity: store, owner, phone (with « vérifié par SMS » when the OTP was passed), WhatsApp, email, business type, potential, address, map with the pin and accuracy.
- Customer type to confirm:
- The declared value is highlighted (« Déclaré à l'inscription : Demi-gros »).
- Three radio cards preselect the declared value, each showing an example price of a reference product (« Câble X14 : 245,00 DA HT »).
- Hints to help decide: business type, store photos, the delegate's note, and the potential set by the delegate.
- Duplicate hints (see below), each with a side-by-side comparison.
- Documents and photos: thumbnails that open in a viewer (zoom, rotate). A missing recommended piece is listed (« Photo de façade manquante »).
- Origin: source, creating delegate and visit (link to BO-25), invitation code used, sign-up time.
- Assignment at approval: delegate and zone, preset from the commune and the creating delegate.
- Decision bar (sticky): « Valider le compte » (primary) and « Refuser » (danger, outline).
- Keyboard:
- J / K: next or previous item.
- 1 / 2 / 3: customer type Super-gros / Demi-gros / Détail.
- A: approve (opens the confirmation).
- R: refuse.
- D: open the first duplicate.
Duplicate hints
Returned by admin.resellers.validation.list for each item, strongest first:
| Hint | Rule | Strength |
|---|---|---|
| Same main phone | Impossible by construction: a phone match triggers the claim flow (AUTH-05). Listed for completeness. | — |
| WhatsApp or secondary phone = another record's main phone | Exact match after normalisation | Strong |
| Same GPS spot | Another point of sale within 50 m | Strong when the store name is also similar, else medium |
| Similar store name in the same commune | Trigram similarity of at least 0.6 | Medium |
| Same RC or NIF number | From uploaded documents, when captured | Strong |
| Same email | Case-insensitive | Medium |
Each hint card shows the other record (number, name, status, delegate, last order) with its matched fields highlighted. It offers « Comparer » (two-column diff), « Ce n'est pas un doublon » (dismisses the hint for this pair), and « Refuser comme doublon de REV-000123 » (prefills the rejection reason). Merging records is V2 (CRM-13).
Data.
admin.resellers.validation.list: queue withdeclaredCustomerType,duplicateHints[], completeness and origin.admin.resellers.getandadmin.resellers.documentsfor the panel.admin.resellers.validatewith{ decision: "APPROVE" | "REJECT", customerType, reason, assignment }.admin.resellers.updatefor a quick fix of a field before approval.- Example prices of the reference product come from
admin.products.get(a configured product).
Actions
| Action | Endpoint | Confirmation (FR) | Reason | Optimistic | Audit | Notifies |
|---|---|---|---|---|---|---|
| « Valider le compte » | admin.resellers.validate (APPROVE, customerType) | « Valider Boutique Nour comme client Demi-gros ? Le revendeur pourra commander aux prix Demi-gros. » When the type differs from the declared one: « Le type déclaré était Détail » plus a required reason. | Required only when the type is changed | No; the item leaves the queue after the response | VALIDATE; plus UPDATE of customerType when changed | Reseller: SMS + push « Votre compte HOCO est validé ». Creating delegate: in-app. |
| « Refuser » | admin.resellers.validate (REJECT) | « Refuser ce compte ? Le motif sera communiqué au revendeur. » | Required, free text of at least 10 characters (chips: « Doublon de REV-… », « Informations incomplètes », « Hors zone de distribution ») | No | REJECT | Reseller (SMS + push with the reason); delegate (in-app) |
| « Ce n'est pas un doublon » | admin.resellers.update (dismissed hint) | — | — | Yes | UPDATE | — |
| Quick fix of a field (address, business type) | admin.resellers.update | — | — | No | UPDATE | — |
Rules
- Who decides: only holders of
crm.validate(CRM-09). - Customer type (AUTH-11):
- Approval always records a confirmed customer type. The request carries
customerTypeeven when unchanged. - Approval with a type different from the declared one needs a reason. Both values stay in the history.
- Approval always records a confirmed customer type. The request carries
- What approval does:
- It sets
ValidationStatus = APPROVEDand, for an account,AccountStatusPENDING_VALIDATION → ACTIVE, so the reseller can now order. - A rejected account stays
PENDING_VALIDATIONwithValidationStatus = REJECTED. The reseller app shows the reason (RA-06). - A rejected field point of sale is kept with
REJECTEDand hidden from BO-09's default views.
- It sets
- Customer type before validation: after sign-up, a reseller sees the prices of their declared type before validation, but cannot order until approved (AUTH-10; decision to confirm in open decisions).
- Concurrency: two reviewers cannot both decide. The second gets
409 VERSION_CONFLICTwith « Déjà traité par Karim B. à 10:42 », and the item disappears.
Real-time. account.pending prepends the new item to the queue, without moving the current selection, and increments the badge.
States.
- Empty queue: « Aucun compte en attente. Tout est à jour. » (success tone).
- Selected item already decided elsewhere: the panel shows « Ce compte a été traité par … », then moves to the next item.
- Documents still uploading from the delegate's offline queue: « 2 photos en cours de synchronisation ».
Acceptance criteria
- A self sign-up appears in the queue within 5 s of account creation, with its declared customer type (cahier §12 « Une inscription valide apparaît dans le back-office et peut être approuvée »).
- Approving with the declared type sets the account
ACTIVE, recordscustomerType, and sends the validation SMS and push. - Approving with another type requires a reason and writes both
VALIDATEandUPDATEaudit entries. - Rejecting requires a reason of at least 10 characters. The reseller app then shows that reason.
- A record whose WhatsApp number equals an existing reseller's phone shows a strong duplicate hint naming that reseller.
- Keyboard-only processing (J, 2, A, Enter) approves an item as Demi-gros and selects the next one.
BO-14 — Follow-ups & tasks
| Field | Value |
|---|---|
| Template | List |
| Release | V1 |
| Design day | Day 16 — DES-16 |
| Build task | DSH-03 |
| Route | /crm/tasks |
| Requirements | CRM-08 · FLD-10 |
| Permissions required | crm.view (page) · crm.update (create, reassign, reschedule, complete, cancel) |
Purpose. Follow every follow-up (relance), task and next action across the team, whether it came from a visit report, an inactivity rule or a manual entry, with due dates (cahier §4.2 « Relances, tâches, prochaines actions et notifications »).
Who uses it. Commercial admin. Each staff user sees « Mes tâches ».
Entry points. CRM › Relances et tâches (badge = my overdue tasks), the Tâches tab of BO-11, and task notifications.
Layout.
- Tabs:
- « Mes tâches ».
- « En retard »: derived,
OPENwithdueAtin the past. - « Aujourd'hui ».
- « À venir ».
- « Terminées ».
- « Toutes ».
- Columns (all default visible except the last two):
| Column | Source field | Sortable |
|---|---|---|
| Échéance | dueAt (red when overdue) | Yes (default ascending) |
| Type | type (TaskType) | Yes |
| Revendeur | reseller.storeName + number | Yes |
| Assignée à | assignee.displayName (delegate or staff) | Yes |
| Origine | origin (« Rapport de visite VIS-… », « Inactivité », « Manuel ») | No |
| Statut | status (TaskStatus) or « En retard » | Yes |
| Note | note (first line) | No |
| Résultat | outcome (done tasks) | No |
| Créée le | createdAt | Yes |
- Filters:
assignee,type,status,dueFrom/dueTo,reseller,zone,origin. - Drawer « Nouvelle tâche »:
- Revendeur (search, required).
- Type (required).
- Échéance (date + optional time, required, not in the past).
- Assignée à (staff or delegate, required; defaults to the reseller's delegate).
- Note (500 characters max).
- Bulk actions: « Réassigner », « Replanifier ».
- Row actions: « Terminer » (outcome, required, 300 characters max), « Replanifier », « Réassigner », « Annuler » (reason).
Data. admin.tasks.list, admin.tasks.create, admin.tasks.update.
Actions.
- Create, reassign and reschedule are not optimistic. « Terminer » is optimistic (the row moves to « Terminées »).
- Each action writes an audit entry (
CREATE/UPDATE). - The assignee is notified on creation and reassignment: push for delegates, in-app for staff.
Rules.
- « En retard » is derived and never stored (
TaskStatusnote). - A visit report with a next action other than
NONEcreates a task automatically, with origin « Rapport de visite » (FLD-10). - Tasks assigned to a delegate sync to the delegate app (DA-19).
- Completing a task adds an entry to the reseller's timeline.
Real-time. None. The list refreshes on focus.
States. « Aucune tâche en retard » is a positive empty state. A reseller that is out of scope or deleted shows « Revendeur introuvable ».
Acceptance criteria.
- A visit report with « Relance téléphonique » on 12/11 creates a task due on 12/11, assigned to the visiting delegate, visible here and in the delegate app.
- At midnight in Algiers, an open task due yesterday appears under « En retard ».
- Reassigning notifies the new assignee and writes an
UPDATEaudit entry.
BO-15 — Segments
| Field | Value |
|---|---|
| Template | List (with drawer) |
| Release | V1 |
| Design day | Day 16 — DES-16 |
| Build task | DSH-03 |
| Route | /crm/segments (?drawer=) |
| Requirements | CRM-06 · CAT-05 · PRM-02 |
| Permissions required | crm.administer |
Purpose. Maintain the reseller segments used for product visibility (CAT-05), promotion and banner targeting (PRM-02) and announcements. Examples: « Grands comptes Oran », « Boutiques téléphonie centre-ville ». Segmentation by potential, business type, brands sold and activity (cahier §4.2) is done with list filters, and the result is saved as a segment. Customer type is its own field, not a segment.
Who uses it. Commercial admin.
Entry points. CRM › Segments, and « Ajouter à un segment » in BO-09 and BO-10.
Layout.
- Table: Nom, Description, Couleur, Revendeurs (count), Utilisé par (promotions, banners, visibility rules, announcements as chips with counts), Modifié le.
- Drawer:
- Nom: required, unique, 3–60 characters.
- Description: 200 characters max.
- Couleur: one of 8 swatches.
- Membres: count, plus « Voir les membres », which opens BO-09
?segment=. - « Ajouter par critères »: a one-shot add of all resellers matching potential, business type, wilaya or activity, with a preview count.
Data. admin.segments.crud. Members are added through admin.resellers.bulk (TAG).
Actions.
- Create, rename: no confirmation; audited
CREATE/UPDATE. - Delete: « Supprimer le segment Grands comptes Oran ? 42 revendeurs en sont membres. » When the segment is used by an active promotion, banner or visibility rule, deletion is refused (
409) and lists the usages. - « Ajouter par critères »: « Ajouter 57 revendeurs au segment ? »
Rules.
- Segments are static: membership changes only by explicit actions. « Ajouter par critères » is a one-time add, not a dynamic rule. Assumption — dynamic segments are not in V1.
- A reseller can belong to several segments.
Real-time. None.
States. First use: « Créez un segment pour cibler vos promotions et votre catalogue. »
Acceptance criteria.
- A segment used by an active promotion cannot be deleted, and the error names the promotion.
- « Ajouter par critères » with Potentiel = Élevé and wilaya
16adds exactly the resellers BO-09 returns for those filters. - Members added here show the segment chip in BO-09 and BO-11.
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.
Field operations
Page specs BO-16 to BO-26: delegates, zones, objectives, the tour planner, tour editor and live tour detail, visits and visit configuration.