Resellers & prospects (CRM)
Reseller and prospect records, creation paths, unique normalised phone, the ten-status lifecycle, assignment, segments, timeline, follow-ups, validation, documents, list, map and the 360° page.
The CRM centralises the commercial life of every point of sale, from the first prospect record to the active revendeur (reseller) (cahier §4.1, §4.2, §6.5, §6.8, §9, §10, §12, §15.1, §15.5). Records are created in the back-office (BO), in the delegate app (DA, offline-capable), by the reseller's own sign-up in the reseller app or web (RA, RW) or by import; they are worked by delegates in the field and by the commercial team in the back-office. The server's crm module owns Reseller, ResellerAddress, ResellerDocument, ResellerPhoto, ResellerStatusChange, ResellerAssignment, Interaction, CrmTask, Segment and ResellerSegment; files go through the files module. The back-office operations are specified on API › CRM, the delegate ones on API › Delegate resellers, and the statuses on Lifecycles › Reseller.
Every reseller also carries a customer type (Super-gros, Demi-gros, Détail) that selects its prices; how it is declared, reviewed and changed is AUTH-11.
| ID | Requirement | Release | Priority | Source | Apps |
|---|---|---|---|---|---|
CRM-01 | Reseller/prospect record with the minimum data set (store, owner, phone, WhatsApp, email, wilaya, commune, address, GPS, type, potential, delegate, photos, documents, status) | V1 | MUST | §4.2, §15.5 | API · BO · DA |
CRM-02 | Creation from the back-office, the delegate app, reseller self sign-up or import, with the source recorded | V1 | MUST | §4.2, §6.5 | API · BO · DA · RA |
CRM-03 | Duplicate prevention: the main phone number is unique (normalised); duplicate check on every creation path | V1 | MUST | §4.2, §6.5, §12 | API · BO · DA |
CRM-04 | Reseller lifecycle (10 statuses) with automatic and manual transitions and full history | V1 | MUST | §4.2, §15.1 | API · BO · DA |
CRM-05 | Assignment to a delegate and a zone; link to the creating delegate; reassignment history | V1 | MUST | §4.2, §6.5 | API · BO |
CRM-06 | Segmentation by potential, business type, brands sold, level and activity | V1 | SHOULD | §4.2 | API · BO |
CRM-07 | Unified interaction history: calls, visits, notes, orders, payments, claims | V1 | MUST | §4.2 | API · BO · DA |
CRM-08 | Follow-ups (relances), tasks and next actions with due dates and notifications | V1 | MUST | §4.2, §6.8 | API · BO · DA |
CRM-09 | Account validation by an authorised user (approve / reject with reason) | V1 | MUST | §4.2, §6.5, §12 | API · BO |
CRM-10 | Store photos and documents (façade, interior, RC, NIF, ID) with protected access | V1 | MUST | §4.2, §10, §15.5 | API · BO · DA · RA |
CRM-11 | CRM list and map views with filters (status, zone, wilaya, commune, delegate, segment, potential, activity) and export | V1 | MUST | §4.1, §4.2, §9 | BO |
CRM-12 | Reseller 360° page: orders, invoices, payments, visits, reports, tasks, interactions | V1 | MUST | §4.2 | BO |
CRM-13 | Merge tool for historical duplicates | V2 | COULD | §4.2 | BO |
Settings used in this area
| Setting | Default | Meaning |
|---|---|---|
crm.inactivityDays | 60 | Activity window: a reseller with no order confirmed in this window is inactive and gets a follow-up (CRM-04, CRM-06). |
crm.duplicateRadiusMeters | 50 | Radius within which a near-identical store name is reported as a possible duplicate (CRM-03). |
crm.validationReminderHours | 24 | A record waiting longer than this in the validation queue triggers one reminder to the validators (CRM-09). |
Requirements
CRM-01 — Reseller and prospect record with the minimum data set
| Release | Priority | Source | Apps | Owner tasks |
|---|---|---|---|---|
| V1 | MUST | cahier §4.2, §15.5 | API · BO · DA | SRV-14 · DSH-03 · WEB-05 · MRA-07 · MDA-04 |
Statement — The system shall keep one record per point of sale — prospect or reseller — holding at least the data set of cahier §15.5: store, owner, phone, WhatsApp, email, wilaya, commune, address, GPS position, business type, potential, delegate, photos, documents and status.
Rules
-
Fields and constraints:
Field Constraint numberREV-000123, allocated byNumberSequenceat creation, never reusedstoreName·ownerName2–120 characters each phone(main)Algerian mobile, E.164, unique (CRM-03); it is the owner's sign-in identifier once the account exists whatsapp·emailOptional; WhatsApp follows the phone rules (often equal to the main phone); email RFC-valid businessTypeCodeItem of the BUSINESS_TYPEreference list (ADM-05)customerTypeSUPER_GROS·DEMI_GROS·DETAIL(AUTH-11); may be empty for a field prospect until validationpotentialLOW·MEDIUM·HIGHStore address wilaya, commune (must belong to the wilaya — GEO-01), address line 5–200, landmark; it is also the default delivery address ( ResellerAddress)locationGPS position per GEO-03 with its source (field capture, geocoded, manual pin) Legal identifiers RC, NIF, NIS, AI (optional text; required later for invoices to professionals per D-21) brandsSoldBrands the store declares selling (segmentation, CRM-06) Delegate, zone CRM-05 Photos, documents CRM-10 status·validationStatus·sourceResellerStatus(CRM-04),ValidationStatus(CRM-09),ResellerSource(CRM-02) -
Required at creation depends on the path (CRM-02); every record has at least store name, main phone, wilaya and commune. A field-created point of sale also requires GPS position and a façade photo before it can be approved (CRM-09).
-
Who edits what: the back-office (
crm.update) edits every field; a delegate edits records in scope except the main phone of a record that has an account, the assignment, segments, customer type and validation; the reseller edits, throughprofile.update, owner name, WhatsApp, email, landmark and delivery addresses — the store name, store address, business type and customer type go through support or the back-office (422 FIELD_NOT_EDITABLEfor the type). -
Main-phone change: by the back-office with the uniqueness check of CRM-03; by the reseller through an OTP of purpose
PHONE_CHANGEon the new number. Either way the old number stays in the history. -
Every change is historised (AUD-02) with actor, time and before/after values; records are never deleted — a closed shop is
SUSPENDEDorNOT_INTERESTED.
Acceptance criteria
- Creating a record without
storeName,phone,wilayaCodeorcommuneIdreturns400 VALIDATION_FAILEDnaming the missing fields. - A new record gets the next
REV-number and a default delivery address equal to its store address. - A delegate updating
customerTypeor the assignment of a record in scope gets403 PERMISSION_DENIED; updating the potential succeeds and appears in the history with the delegate as actor. - A reseller calling
profile.updatewith a new email succeeds; withcustomerTypeit gets422 FIELD_NOT_EDITABLE. - BO-12, DA-15 and RW-12 show the same values for the same record.
Data — Reseller, ResellerAddress, ResellerDocument, ResellerPhoto, NumberSequence, AuditLog.
Interfaces
- Screens: RA-05 Sign-up — store details, customer type & password · RA-28 Store profile & documents · DA-09 Point-of-sale sheet · DA-15 Edit reseller / prospect · BO-12 Reseller create / edit · RW-12 Profile, store, addresses & settings
- API:
profile.getGET /reseller/profile·profile.updatePATCH /reseller/profile·delegate.resellers.getGET /delegate/resellers/{resellerId}·delegate.resellers.updatePATCH /delegate/resellers/{resellerId}·admin.resellers.updatePATCH /admin/resellers/{resellerId}
CRM-02 — Creation paths with the source recorded
| Release | Priority | Source | Apps | Owner tasks |
|---|---|---|---|---|
| V1 | MUST | cahier §4.2, §6.5 | API · BO · DA · RA | SRV-14 |
Statement — The system shall create reseller and prospect records from the back-office, the delegate app, the reseller's self sign-up and imports, recording the source of each record permanently.
Rules
-
Paths and their defaults:
Path sourceInitial status validationStatusAssignment Operation Back-office form (BO-12), crm.createBACK_OFFICENEW_PROSPECT,TO_CONTACTorINTERESTED(defaultNEW_PROSPECT)APPROVEDChosen; proposed: the delegate covering the commune when there is exactly one admin.resellers.createDelegate app (DA-14), offline-capable DELEGATE_APPNEW_PROSPECTorINTERESTEDPENDING(new point of sale to validate — FLD-14)The creating delegate delegate.resellers.createSelf sign-up (RA-05, RW-01) SELF_SIGNUPACCOUNT_CREATEDPENDINGThe inviting delegate, else none auth.signup.resellerImport (ADM-06), settings.administerIMPORTPer row; existing customers ACTIVEAPPROVEDPer row admin.imports.create -
sourceand the creating user (createdBy, andcreatedByDelegateIdfor delegate creations) are set once and never change — a later claim, assignment or merge keeps them. -
Delegate creations carry a client-generated UUIDv7
idand anIdempotency-Key: a replay from the offline queue returns the original result and never creates a second record (SYN-05). The record shows the delegate's capture time (occurredAt) and the server's reception time. -
A back-office creation does not create a login: the owner claims the record by signing up with the same phone (AUTH-05).
-
Every path runs the duplicate checks of CRM-03 and the field checks of CRM-01 before writing.
-
Side effects:
ResellerStatusChange(— →initial status),Interactionon the timeline,AuditLogCREATE; delegate creations and sign-ups raiseaccount.pendingto validators (live event); a back-office creation with a delegate notifies that delegate (reseller.assigned). -
The new record appears in the CRM list and map with its source, delegate and position within 5 seconds of reception (cahier §6.5 acceptance).
Acceptance criteria
- A prospect created offline by delegate Karim and synced later appears in BO-09 with source Application délégué, Karim as creator and assigned delegate, its GPS position, and validation En attente.
- Replaying the same creation (same
idandIdempotency-Key) returnsREPLAYEDand leaves one record. - A back-office creation is
APPROVEDat once and has no login account. - An imported existing customer is
ACTIVE,APPROVED, sourceIMPORT, with the customer type of its row. sourcecannot be changed byadmin.resellers.update(the field is ignored or refused).
Data — Reseller, ResellerAddress, ResellerStatusChange, ResellerAssignment, Interaction, ImportJob, IdempotencyRecord, AuditLog, OutboxEvent.
Interfaces
- Screens: BO-12 Reseller create / edit
- API:
auth.signup.resellerPOST /auth/signup/reseller·delegate.resellers.createPOST /delegate/resellers·admin.resellers.createPOST /admin/resellers
CRM-03 — Unique normalised main phone and duplicate checks
| Release | Priority | Source | Apps | Owner tasks |
|---|---|---|---|---|
| V1 | MUST | cahier §4.2, §6.5, §12 | API · BO · DA | SRV-14 · DSH-03 · MDA-07 |
Statement — The system shall normalise every phone number to E.164, keep the main phone of a point of sale unique across all records and logins, and run a duplicate check on every creation path with a defined answer for each.
Rules
-
Normalisation (one implementation in
@hoco/shared, mirrored inhoco_corewith the same test vectors): remove spaces, dots, dashes and parentheses;00213…→+213…;213followed by 9 digits →+213…;0followed by 9 digits →+213+ the 9 digits; 9 digits starting with 5, 6 or 7 →+213…. Vectors:0555 12 34 56,05-55-12-34-56,555123456,00213555123456and+213 555 12 34 56all give+213555123456. -
Validity. A main phone must be an Algerian mobile —
+213then 9 digits starting with 5, 6 or 7 — because it receives the sign-up code by SMS; anything else is field codeINVALID_PHONE. WhatsApp numbers follow the same rule. -
Uniqueness. The normalised main phone is unique among all
Resellerrecords whatever their status, and must not be the login of a staff or delegate user. The database enforces it with a unique index, so two concurrent creations cannot both succeed; the loser gets409 PHONE_ALREADY_EXISTS. -
Hints (non-blocking). A creation also looks for possible duplicates: the same WhatsApp number on another record (
WHATSAPP), or a store name with trigram similarity of at least 0.80 withincrm.duplicateRadiusMeters(50 m) of another record's position (NAME_NEARBY). Names are compared lower-cased, without accents and without the words boutique, magasin, store, shop. -
Answers per path:
Path Pre-check On creation with a used phone Hints Back-office (BO-12) admin.resellers.checkPhonewhile typing:available, the matching record (RESELLER_PHONE, blocking — with link « Ouvrir la fiche existante ») orUSER_LOGIN(blocking, only the user type is revealed); an invalid number answersvalid: false409 PHONE_ALREADY_EXISTSwithdetails.existing409 DUPLICATE_SUSPECTEDwithdetails.hints; the user re-sends withignoreDuplicateHints: trueto confirmDelegate app (DA-14) delegate.resellers.checkPhonewhen online: for a record in scope, its name and a link to its sheet; outside the scope only « Ce numéro est déjà enregistré — contactez l'administration », no personal dataOnline: 409 PHONE_ALREADY_EXISTS. Offline: the queued creation comes back fromsync.batchasREJECTEDwithPHONE_ALREADY_EXISTS; the sync centre (DA-27) keeps the local record and offers to correct the phone, open the existing record (if in scope) or discard; dependent queued items (photos, invitation) waitRecorded on the new record and shown to the validator — never blocks a field creation Self sign-up (RA, RW) — auth.otp.sendanswers409 PHONE_ALREADY_EXISTS(canSignIn: true) when the phone has a login; a record without login is claimed, not duplicated (AUTH-05)Listed in the validation queue Import Dry run lists each row's normalised phone and status Row error PHONE_ALREADY_EXISTS(or, withonDuplicate: UPDATE, the existing record is updated)Row warnings -
Changing a main phone (back-office edit,
PHONE_CHANGE) runs the same uniqueness rule (409 PHONE_ALREADY_EXISTS). -
Check operations are read-only and limited to 60 calls per minute per user.
Acceptance criteria
- The five formats of rule 1 are normalised to
+213555123456by the server and by the Flutter helper. 021 23 45 67(fixed line) as main phone returns field codeINVALID_PHONE.- Creating a second record with
0555 12 34 56in the back-office returns409 PHONE_ALREADY_EXISTSwith the existingREV-number; two simultaneous creations with that phone produce one record and one409. - A back-office creation named Boutique El Wafa 30 m from El Wafa returns
409 DUPLICATE_SUSPECTED(NAME_NEARBY); re-sent withignoreDuplicateHints: trueit succeeds. - A delegate checking a phone of a record in another zone sees only the generic message, with no store name.
- A prospect created offline with a used phone comes back
REJECTED/PHONE_ALREADY_EXISTSand stays in the sync centre with the correction options. auth.otp.send(SIGNUP) with the phone of a reseller who has a login returns409 PHONE_ALREADY_EXISTS.
Data — Reseller (unique normalised phone), User (login phone), ImportJob; pg_trgm index on normalised store names.
Interfaces
- Screens: DA-14 New reseller · BO-12 Reseller create / edit
- API:
auth.signup.resellerPOST /auth/signup/reseller·delegate.resellers.checkPhoneGET /delegate/resellers/check-phone·delegate.resellers.createPOST /delegate/resellers·admin.resellers.createPOST /admin/resellers·admin.resellers.checkPhoneGET /admin/resellers/check-phone
CRM-04 — Reseller lifecycle with automatic and manual transitions
Statement — The system shall move every point of sale through the ten statuses of cahier §15.1 — automatically from business events and manually by authorised people — and keep the full history of every change.
Rules
- The ten statuses form three families (Lifecycles › Reseller): qualification (
NEW_PROSPECT,TO_CONTACT,INTERESTED,NOT_INTERESTED,TO_FOLLOW_UP) moved by people and two system rules; activation funnel (APP_INSTALLED,ACCOUNT_CREATED,FIRST_ORDER,ACTIVE) moved only by the system; commercial block (SUSPENDED) moved only by the back-office. - Automatic transitions (table): planning a visit or a call task on a
NEW_PROSPECT→TO_CONTACT; an invitation install →APP_INSTALLED; account created or claimed →ACCOUNT_CREATED; first order submitted →FIRST_ORDER; an order delivered →ACTIVE; no order confirmed duringcrm.inactivityDays→TO_FOLLOW_UP(daily scan at 02:00Africa/Algiers, with aCALLtask « Relance inactivité »); a new order confirmed for a reseller who already had a delivery → back toACTIVE. - Automatic events only move a reseller forward in the funnel and lift qualification statuses; they never lift
SUSPENDED(the event is recorded on the timeline and the status stays). The only backward automatic moves are the first-order revert (the first order ends refused or cancelled) and the inactivity rule. Replayed events are idempotent. - Manual transitions (table): among qualification statuses, by a delegate on a record in scope (DA-15, visit report) or by the back-office (
crm.update, also in bulk);NOT_INTERESTEDrequires a reason (3–300 characters) and cancels the record's open tasks;TO_FOLLOW_UPrequires a next action and a date and creates the follow-up task. A delegate may only move a record whose current status is a qualification status. - Setting a funnel status by hand answers
409 RESELLER_TRANSITION_NOT_ALLOWEDwithdetails.reason = "AUTOMATIC_STATUS"; a target equal to the current status answers the same code. SUSPENDED(back-office, reason required: unpaid, fraud, closure…) blocks checkout, assisted orders and return requests (403 RESELLER_SUSPENDED) but not the owner's login; open orders are flagged, not cancelled.restorereturns to the status before the suspension, then automation is re-evaluated.- Every change writes a
ResellerStatusChange(from, to, time, actor or system, trigger, reason), a timelineInteractionof typeSTATUS_CHANGEand, for manual changes,AuditLogSTATUS_CHANGE. - A delegate's offline status change replayed after an automatic move (the prospect installed the app meanwhile) is answered
CONFLICTbysync.batchand shown in the sync centre.
| Value | UI label (fr) | Meaning |
|---|---|---|
NEW_PROSPECT | Nouveau prospect | Just created; no qualified contact yet. |
TO_CONTACT | À contacter | Planned for a call or a visit. |
INTERESTED | Intéressé | Interest confirmed during a visit or call. |
APP_INSTALLED | Application installée | Invitation attributed an install (automatic). |
ACCOUNT_CREATED | Compte créé | Reseller account created and phone verified (automatic). |
FIRST_ORDER | Première commande | First order submitted (automatic). |
ACTIVE | Revendeur actif | First order delivered, or a validated order within the activity window (automatic). |
NOT_INTERESTED | Non intéressé | Declined; kept for later campaigns. |
TO_FOLLOW_UP | À relancer | A follow-up is due (from a visit report or inactivity). |
SUSPENDED | Suspendu | Commercially suspended (unpaid, fraud, closure); ordering blocked. |
Acceptance criteria
- Adding prospect
REV-000123(NEW_PROSPECT) as a stop of a tour sets it toTO_CONTACTwith actor Système. - A
NOT_INTERESTEDprospect whose invitation records an install becomesAPP_INSTALLED; aSUSPENDEDone staysSUSPENDEDand the install appears on its timeline. admin.resellers.setStatustoACTIVEreturns409 RESELLER_TRANSITION_NOT_ALLOWEDwithAUTOMATIC_STATUS.- Setting
NOT_INTERESTEDwithout a reason returns422 REASON_REQUIRED; with a reason, the record's open tasks becomeCANCELLED. - When the reseller's first order is refused and no other order is open, the status returns to its previous value and
isFirstOrdermoves to the next submitted order. - 61 days without a confirmed order turn an
ACTIVEreseller intoTO_FOLLOW_UPwith aCALLtask for its delegate; confirming a new order closes the task and restoresACTIVE. - A suspended store's owner can open invoices;
cart.checkoutreturns403 RESELLER_SUSPENDED.
Data — Reseller (status), ResellerStatusChange, Interaction, CrmTask, AuditLog, OutboxEvent.
Interfaces
- Screens: DA-13 Prospects & resellers (list / map) · DA-15 Edit reseller / prospect · BO-11 Reseller 360°
- API:
delegate.resellers.listGET /delegate/resellers·delegate.resellers.updatePATCH /delegate/resellers/{resellerId}·admin.resellers.setStatusPOST /admin/resellers/{resellerId}/status
CRM-05 — Assignment to a delegate and a zone
Statement — The system shall assign each point of sale to at most one delegate and one zone at a time, keep the link to the delegate who created it, and keep the history of every reassignment.
Rules
- A record has at most one current assignment (
ResellerAssignment: delegate, zone, from, to, actor, reason); the zone defaults to the zone containing the store commune (GEO-02).createdByDelegateIdis separate and immutable (attribution in reports, cahier §4.2 « lié au délégué qui l'a créé »). - Defaults: field creation → the creating delegate; self sign-up → the attributed invitation's delegate, else unassigned (the validator assigns it); back-office creation → the delegate chosen, proposed when exactly one covers the commune; import → the row's delegate.
admin.resellers.assign(crm.update) sets or clears the delegate. The delegate must beACTIVE(422 DELEGATE_INACTIVE) and cover the commune (422 DELEGATE_ZONE_MISMATCH), unlessallowOutOfZone: truewith a reason — recorded as anOVERRIDEin the activity log.- Effects of a reassignment: the new delegate gains the record in their scope at the next
sync.delta; the previous one loses it unless the commune is still in their zones (ACL-03); open tasks of the previous delegate on that record move to the new one; planned tours are not changed (the back-office edits them). - Bulk assignment (
admin.resellers.bulk, actionASSIGN): 1–500 ids or a list filter matching at most 5 000 records (422 BULK_TOO_LARGE); each record follows the single-record guards, failures are skipped and reported; notifications are grouped per delegate (« 22 points de vente vous ont été attribués »). - When a delegate is disabled, their records keep the assignment until reassigned and appear under the filter Délégué inactif.
- Notifications:
reseller.assignedto the new delegate (push,FIELDcategory). AuditUPDATEper record.
Acceptance criteria
- Assigning a reseller of Es Sénia to a delegate covering only Alger returns
422 DELEGATE_ZONE_MISMATCH; withallowOutOfZone: trueand a reason it succeeds and the activity log shows anOVERRIDE. - After reassignment from Karim to Samir, Karim's open call task on that record now belongs to Samir; the 360° history shows both assignments with dates and actors.
createdByDelegateIdstays Karim after the reassignment, and Karim keeps the first-order credit in reports.- A bulk assignment of a filter matching 6 000 records returns
422 BULK_TOO_LARGE; one of 300 records with 3 failing guards updates 297 and reports the 3.
Data — ResellerAssignment, Reseller (createdByDelegateId, zone), DelegateZone, CrmTask, AuditLog, Notification.
Interfaces
- Screens: none dedicated
- API:
admin.resellers.assignPOST /admin/resellers/{resellerId}/assignment·admin.resellers.bulkPOST /admin/resellers/bulk
CRM-06 — Segmentation
Statement — The system should let the back-office group resellers into segments by potential, business type, brands sold, customer type, level and activity, and reuse those segments for visibility rules, promotion targeting and reporting.
Rules
- A
SegmentisSTATIC(members added by hand or by the bulk actionADD_SEGMENTS) orDYNAMIC(members computed from criteria). Dynamic criteria: potential, business type, brands sold, customer type, wilaya or zone, status, activity and — in V2 — loyalty level. - Activity is derived:
ACTIVE= at least one order confirmed (not cancelled) during the lastcrm.inactivityDaysdays;INACTIVE= has ordered before, none in the window;NEVER_ORDERED= no confirmed order ever. - Dynamic membership (
ResellerSegmentrows) is materialised at creation, refreshed nightly at 01:30 and on reseller, order and status events, so the price, visibility and promotion engines read a plain table. - Segments are used by product visibility rules (CAT-05), promotion targeting (PRM-02), CRM filters and reports. In V1 they do not set prices — prices come from the customer type (PRC-02).
- Creating, editing and deleting segments needs
crm.administer; reading themcrm.view. A segment referenced by an active visibility rule or promotion cannot be deleted (409 SEGMENT_IN_USE); dynamic segments cannot receive manual members (400 VALIDATION_FAILED).
Acceptance criteria
- A dynamic segment Demi-gros actifs Oran (customer type
DEMI_GROS, wilaya 31, activityACTIVE) lists exactly the matching resellers, and a reseller confirmed for an order today enters it without waiting for the night. - Adding a reseller by hand to a dynamic segment returns
400 VALIDATION_FAILED. - Deleting a segment used by an active promotion returns
409 SEGMENT_IN_USE. - BO-09 filters by segment and shows the segment chips on each row.
Data — Segment, ResellerSegment, Reseller, Order (activity).
Interfaces
- Screens: BO-09 Resellers & prospects list · BO-15 Segments
- API:
admin.resellers.bulkPOST /admin/resellers/bulk·admin.segments.crudGET · POST · PATCH · DELETE /admin/segments[/{segmentId}]
Notes / open points — Loyalty levels are V2 (LOY-03); the criterion is hidden until then.
CRM-07 — Unified interaction timeline
Statement — The system shall show, for every point of sale, one timeline of its calls, visits, notes, messages, orders, payments, claims and status changes.
Rules
- Entry types (
InteractionType):CALL,VISIT,NOTE,WHATSAPP,SMS,EMAIL,ORDER,PAYMENT,CLAIM,STATUS_CHANGE. Each entry has its time (occurredAt), actor (or system), client app, a French one-line summary built by the server, an optional body and a link to the source record (order, payment, visit, ticket, return, task). - Written explicitly: notes and call logs by staff (
admin.resellers.notes.add) and delegates (delegate.resellers.addNote, offline-capable with a client id). Written automatically from events: visit completed, order submitted and each order status change, payment confirmed, after-sales claim and ticket opened, return requested, lifecycle change, task completed. - A call started from the point-of-sale sheet (DA-09) logs a
CALLentry with the start time; the delegate may add the outcome. Notes are immutable; a correction is a new note. - Visibility: staff with
crm.view; delegates for records in scope, wherePAYMENTentries show no amount (ACL-04); resellers never see the timeline. - Sorted newest first; filter by type; cursor pagination in the delegate app, page pagination in the back-office.
Acceptance criteria
- After a visit, an order and a payment, BO-11's timeline lists the three entries with links to the visit, the order
CMD-…and the paymentPAY-…. - A note written offline by a delegate appears once after sync, with the delegate's capture time.
- A delegate opening the timeline sees the payment entry without its amount.
- Filtering on
CALLshows only call logs.
Data — Interaction, with references to Visit, Order, Payment, Ticket, ReturnRequest, CrmTask.
Interfaces
- Screens: DA-09 Point-of-sale sheet · BO-11 Reseller 360°
- API:
delegate.resellers.getGET /delegate/resellers/{resellerId}·delegate.resellers.addNotePOST /delegate/resellers/{resellerId}/notes·admin.resellers.timelineGET /admin/resellers/{resellerId}/timeline·admin.resellers.notes.addPOST /admin/resellers/{resellerId}/notes
CRM-08 — Follow-ups, tasks and next actions
| Release | Priority | Source | Apps | Owner tasks |
|---|---|---|---|---|
| V1 | MUST | cahier §4.2, §6.8 | API · BO · DA | SRV-14 · DSH-03 · MDA-06 · DES-13 |
Statement — The system shall manage follow-ups (relances) as tasks with a type, an assignee, a due date and an outcome, create them from visit reports, inactivity and manual planning, and remind assignees when they are due.
Rules
- A
CrmTaskhas a type (TaskType:CALL,VISIT,WHATSAPP,SEND_CATALOG,OTHER), a reseller, an assignee, a due date and time, a status (OPEN,DONE,CANCELLED), a source (MANUAL,VISIT_REPORT,INACTIVITY,SYSTEM) and an outcome. Overdue is derived (OPENand due date passed), never stored. - A visit report's next action creates or updates one task for that visit (FLD-10):
PHONE_FOLLOW_UP→CALL,NEW_VISIT→VISIT,SEND_CATALOG→SEND_CATALOG,WHATSAPP_MESSAGE→WHATSAPP,NONE→ no task; the follow-up date is required when the action is notNONE. - The inactivity rule (CRM-04) creates a
CALLtask for the assigned delegate (commercial administrators when unassigned), due the next working day. - Staff create tasks for any user about a reseller (
admin.tasks.create); a delegate assignee must have the record in scope, otherwise the record is added to their scope for this task. ACALLorVISITtask on aNEW_PROSPECTmoves it toTO_CONTACT. - Transitions:
OPEN → DONEwith an outcome (adds a timeline entry of the task's type);OPEN → CANCELLED;DONE → OPENwith a reason; rescheduling and reassignment keep the status and notify the new assignee. - Reminders:
task.assignedon creation or reassignment (push for delegates, in-app for staff);task.dueat 08:00Africa/Algierson the due date; overdue tasks are highlighted in DA-19 and BO-14. - Delegates work their tasks offline (complete, reschedule) through the queue.
Acceptance criteria
- A visit report with next action Relance téléphonique on 12 Nov creates a
CALLtask due 12 Nov for the delegate; editing the report to 14 Nov moves the same task instead of creating a second one. - On 12 Nov at 08:00 the delegate receives the
task.duepush. - Completing a task without an outcome returns
400 VALIDATION_FAILED; with an outcome a timeline entry appears. - A task due yesterday and still
OPENis listed as overdue in DA-19 and BO-14.
Data — CrmTask, VisitReport, Interaction, Notification.
Interfaces
- Screens: DA-09 Point-of-sale sheet · DA-12 Visit report · DA-19 Follow-ups & tasks · BO-14 Follow-ups & tasks
- API:
delegate.visits.reportPUT /delegate/visits/{visitId}/report·delegate.tasks.listGET /delegate/tasks·delegate.tasks.updatePATCH /delegate/tasks/{taskId}·delegate.tasks.completePOST /delegate/tasks/{taskId}/complete·admin.tasks.listGET /admin/tasks·admin.tasks.createPOST /admin/tasks·admin.tasks.updatePATCH /admin/tasks/{taskId}
CRM-09 — Account validation by an authorised user
Statement — The system shall put every new self sign-up, claim and field-created point of sale into a validation queue where a user holding crm.validate approves it — confirming its customer type — or rejects it with a reason.
Rules
- The queue (BO-13,
admin.resellers.validation.list) holds records with validationPENDING, of kindSELF_SIGNUP,FIELD_PROSPECTorCLAIM, oldest first, with what is missing (location,facadePhoto,customerType,businessType,commune), the duplicate hints of CRM-03, the declared and delegate-recorded customer types and any previous rejection. New entries arrive live (account.pending); a record waiting longer thancrm.validationReminderHourstriggers one reminder. - Approve (
admin.resellers.validate): validationAPPROVED; accountPENDING_VALIDATION → ACTIVEwhen a login exists (checkout unlocked); the customer type is confirmed or changed (AUTH-11); orders on hold forRESELLER_NOT_VALIDATEDare re-checked and return to the confirmation queue (ORD-14). - Approval guards: a field-created point of sale needs a GPS position (
422 LOCATION_REQUIRED); the minimum data of §15.5 must be present (422 RESELLER_INCOMPLETEwithdetails.missing); a customer type is needed (422 CUSTOMER_TYPE_REQUIRED). - Reject: reason required (10–500 characters, shown to the reseller on RA-06); the commercial status is unchanged;
disableAccount: true(duplicate or fraud) disables the login and revokes sessions;duplicateOfIdlinks the record it duplicates. A rejected owner who corrects the data (profile, documents) sends the record back toPENDING, and the queue shows the previous rejection. - A record not
PENDINGanswers409 VALIDATION_NOT_PENDING; an approved record is never re-validated. - Notifications: approval →
account.approvedto the reseller (SMS and push, « Votre compte est validé », with the confirmed customer type) andprospect.approvedto the creating delegate; rejection →account.rejected(push and in-app with the reason) andprospect.rejected. AuditVALIDATEorREJECTwith the type before and after.
Acceptance criteria
- A self sign-up appears in BO-13 within 2 seconds of sign-up with kind
SELF_SIGNUPand its declared type. - Approving it unlocks checkout at once:
cart.checkoutfrom that reseller is accepted afterwards. - Approving a field prospect without GPS returns
422 LOCATION_REQUIRED. - Rejecting with a 5-character reason returns
400 VALIDATION_FAILED; with a proper reason, RA-06 shows it and the delegate receivesprospect.rejected. - After the owner uploads the missing RC document, the record is back in the queue with the previous rejection visible.
- Validating an already approved record returns
409 VALIDATION_NOT_PENDING.
Data — Reseller (validationStatus, customer type, validation decision), User (status), Order (pending reasons), AuditLog, Notification.
Interfaces
- Screens: RA-06 Account pending validation / rejected · BO-13 Account validation queue
- API:
admin.resellers.validation.listGET /admin/resellers/validation-queue·admin.resellers.validatePOST /admin/resellers/{resellerId}/validation
Notes / open points — Who validates and the target delay are D-30 (default: crm.validate holders, one working day).
CRM-10 — Store photos and documents with protected access
| Release | Priority | Source | Apps | Owner tasks |
|---|---|---|---|---|
| V1 | MUST | cahier §4.2, §10, §15.5 | API · BO · DA · RA | SRV-14 · DSH-03 · MOB-11 · MRA-07 · MDA-07 |
Statement — The system shall attach photos (façade, interior, shelf) and documents (RC, NIF, NIS, AI, identity card, contract) to a point of sale, store them privately and serve them only to authorised users through short-lived links.
Rules
- Photos (
ResellerPhoto,PhotoKind:FACADE,INTERIOR,SHELF,OTHER) and documents (ResellerDocument,DocumentType:COMMERCE_REGISTER,TAX_ID,STAT_ID,ARTICLE_ID,ID_CARD,CONTRACT,OTHER) reference aFileuploaded through a pre-signed URL (SEC-04): photos areSTORE_PHOTO(at most 5 MB), documentsSTORE_DOCUMENT(at most 10 MB, PDF, JPEG or PNG); a wrong purpose answers422 FILE_PURPOSE_MISMATCH, an incomplete upload422 FILE_NOT_READY. - Who adds: delegates add photos to records in scope (offline queue, capture position and time kept — MOB-11); resellers add their own documents (
profile.documents.add); staff withcrm.updateadd both, and may replace a document (the previous version stays in the history). - Files are private and served through signed URLs valid 5 minutes, issued only to: staff with
crm.view, the delegate for records in scope (photos and documents they can see), and the reseller for their own files. The URL of an identity card or a legal document is issued only to staff withcrm.view, and each issue is written to the activity log (AUD-01). - EXIF metadata is removed from images at upload; the capture position is kept as structured data on the photo instead.
- Files are never hard-deleted by users: removing one hides it and keeps it for the retention period (D-11).
Acceptance criteria
- A façade photo taken offline appears on the record after sync with its capture position and time, and without EXIF data in the stored file.
- A delegate outside the record's scope requesting a document URL gets
404 NOT_FOUND; a reseller gets URLs for their own documents only. - A signed URL older than 5 minutes is refused by storage.
- Opening an ID card from BO-11 adds an entry to the activity log naming the document and the user.
- Uploading a 12 MB PDF as a document is refused with the file-size error.
Data — ResellerPhoto, ResellerDocument, File, AuditLog.
Interfaces
- Screens: RA-28 Store profile & documents · DA-14 New reseller
- API:
profile.documents.listGET /reseller/documents·profile.documents.addPOST /reseller/documents·delegate.visits.addPhotoPOST /delegate/visits/{visitId}/photos·delegate.resellers.addPhotoPOST /delegate/resellers/{resellerId}/photos·admin.resellers.documentsGET /admin/resellers/{resellerId}/documents·admin.resellers.documents.addPOST /admin/resellers/{resellerId}/documents
CRM-11 — CRM list and map with filters and export
Statement — The system shall list and map resellers and prospects in the back-office with combinable filters, search, sorting and export of the filtered list.
Rules
- Filters (BO-09, BO-10): status, validation status, account status (including sans compte), customer type (including none), source, zone, wilaya, commune, assigned delegate (including non attribué and délégué inactif), segment, potential, business type, activity, has GPS position, creation date range. Values of one filter are OR-ed, different filters AND-ed.
- Search
q(at least 2 characters) on store name, owner name,REV-number and phone (normalised, partial digits accepted). - Sort by store name, creation date, last order date, revenue over 12 months or status; page pagination (default 25, at most 200).
- Each row carries everything BO-09 shows without a second call (number, store, owner, phone, customer type, status pills, zone, delegate, segments, activity, last order, revenue, next task);
balanceDueonly withfinance.view. - The map shows the same filtered set within the visible bounding box; pins are coloured by status and clustered server-side above a threshold; clicking a pin opens the record's summary card.
- Export (
admin.resellers.export,crm.export): an asynchronous job over the current filters in CSV or XLSX through the export centre (ANA-05); financial columns only withfinance.view; journaled asEXPORT. - Performance: the first page answers in under 1 second for 20 000 records.
Acceptance criteria
- Filtering wilaya 31 + statut Intéressé + délégué Karim returns the matching records with the right total, and the map shows the same records.
- Searching
555 12 34finds the reseller whose phone is+213555123456. - A commercial administrator without
finance.viewsees no balance column and gets no balance column in the export. - The export of a filtered list of 1 200 records appears in the export centre and in the activity log.
Data — Reseller, ResellerAssignment, ResellerSegment, ExportJob.
Interfaces
- Screens: BO-09 Resellers & prospects list · BO-10 Resellers map
- API:
admin.resellers.listGET /admin/resellers·admin.resellers.mapGET /admin/resellers/map
CRM-12 — Reseller 360° page
Statement — The system shall give the back-office one page per point of sale that gathers its identity, status, assignment, customer type and every related record: orders, invoices, payments, visits, reports, tasks, interactions, documents, tickets and returns.
Rules
- Header (
admin.resellers.get): number, store, owner, phone and WhatsApp with call/WhatsApp actions, status, validation and account status, customer type (declared and confirmed, with its history), source, creating and assigned delegates, zone, segments, potential, legal identifiers, map position, creation date, KPIs (first order date, orders and revenue over 12 months, average basket) and — withfinance.view— balance and credit. - Tabs, each loaded from the module's own list filtered by the reseller and shown only with the right permission: orders and returns (
orders.view), invoices and payments (finance.view), visits and reports (field.view), tickets and after-sales claims (support.view), tasks, timeline, documents and photos, addresses, history (status, assignment, type changes). Each tab label carries its counter. allowedActionsdrives the buttons: edit, set status, assign, validate, add note, add document, create task, create an order on behalf (ORD-11).- The header and counters load in under 1 second; tab contents load on demand.
Acceptance criteria
- A commercial administrator without
finance.viewsees no invoice or payment tab and no balance; a finance manager sees them. - The orders tab counter equals the number of the reseller's non-draft orders.
- « Créer une commande » opens BO-40 with the reseller pre-selected.
Data — Reseller and every model linked to it; counters computed per permission.
Interfaces
- Screens: BO-11 Reseller 360°
- API:
admin.resellers.getGET /admin/resellers/{resellerId}
CRM-13 — Merge tool for historical duplicates
| Release | Priority | Source | Apps | Owner tasks |
|---|---|---|---|---|
| V2 | COULD | cahier §4.2 | BO | — (not scheduled in the plan) |
Statement — The system could, in V2, merge two records of the same point of sale into one, moving every related record to the survivor.
Rules
- The user (
crm.administer) chooses a survivor and a duplicate; the survivor keeps itsREV-number, main phone, customer type and login; the duplicate's orders, invoices, payments, visits, interactions, tasks, documents, photos, invitations and returns move to the survivor. - A merge is refused when both records have a login with orders (manual review), or when one is
SUSPENDEDfor fraud. - The duplicate is closed with a link to the survivor and keeps its history; the merge is journaled with both ids and is not reversible.
Acceptance criteria
- Merging
REV-000245intoREV-000123moves its 2 orders and 3 visits; searchingREV-000245opens a page pointing toREV-000123. - Merging two records that both have a login with orders is refused.
Data — Reseller and every linked model; AuditLog.
Interfaces
- Screens: none dedicated
- API: no dedicated operation
Notes / open points — V2. In V1 duplicates are prevented (CRM-03) and handled at validation (reject as duplicate with duplicateOfId).
Error codes
| Code | HTTP | Raised when |
|---|---|---|
PHONE_ALREADY_EXISTS | 409 | The main phone belongs to another record or a login |
DUPLICATE_SUSPECTED | 409 | WhatsApp or near-identical name nearby, not confirmed with ignoreDuplicateHints |
RESELLER_TRANSITION_NOT_ALLOWED | 409 | Funnel status set by hand, or a move outside the lifecycle tables |
VALIDATION_NOT_PENDING | 409 | Validating a record that is not PENDING |
SEGMENT_IN_USE | 409 | Deleting a segment used by a visibility rule or promotion |
REASON_REQUIRED | 422 | NOT_INTERESTED, suspension, out-of-zone assignment or type change without reason |
DELEGATE_INACTIVE · DELEGATE_ZONE_MISMATCH | 422 | Assignment to a non-active delegate or outside their zones |
LOCATION_REQUIRED · RESELLER_INCOMPLETE · CUSTOMER_TYPE_REQUIRED | 422 | Approval guards |
BULK_TOO_LARGE | 422 | A bulk filter matches more than 5 000 records |
FILE_NOT_READY · FILE_PURPOSE_MISMATCH | 422 | Attaching an incomplete upload or a file of the wrong purpose |
RESELLER_SUSPENDED | 403 | Checkout, assisted order or return request for a commercially suspended store |
Field codes in 400 VALIDATION_FAILED | 400 | INVALID_PHONE, COMMUNE_NOT_IN_WILAYA, import row codes PHONE_ALREADY_EXISTS, UNKNOWN_COMMUNE |
Geography, zones & GPS
The official wilaya and commune reference, sales zones assigned to delegates, the GPS capture standard with distance computation, and optional geocoding.
Catalog
Brands, the category tree with sub-categories, products and variants with filterable attributes, lifecycle, visibility, import/export, search with advanced filters, product detail, favorites, barcode scan and home merchandising.