HOCO B2B
DesignScreen specificationsDelegate app

Delegate app

The délégué's day, the navigation model, offline-first, location and outdoor-readability rules, and the index of the 28 delegate-app screens.

The delegate app (dz.hoco.delegate) is the field tool of HOCO's délégués commerciaux (field sales delegates): the day's tournée (tour) of phone-accessory shops, GPS check-in, visit reports, prospect sign-up, invitation to the reseller app and first-order assistance (cahier §6, §6.1–§6.9). Imad builds it in Flutter in hoco-mobile (tasks MDA-01 to MDA-12 on the foundations MOB-05, MOB-09, MOB-10, MOB-11); Akram designs it on days 11 to 14 of the design plan.

The users work outdoors all day, on mid-range Android phones, with weak 3G/4G between shops. The app is therefore offline-first: everything the delegate does on a tour is written to the phone first and synchronised when the network allows (cahier §7.3, SYN-04, SYN-05, SYN-06).

How to read the screen specifications. Every screen has the same blocks: a fields table (French title, release, design day, build task, requirements, client reference), purpose, entry points and exits, layout, data (API source and offline source with its freshness rule), actions (endpoint, Idempotency-Key, occurredAt, what goes to the outbox, optimistic UI, feedback), rules, states, copy and acceptance criteria. Response paths follow the API reference baseline; where an API page differs, the API page — and from SRV-03 on, the OpenAPI document — wins. Local table names refer to the drift store of hoco_offline described in offline sync; location mechanics are in location and maps; components come from the design system.

The delegate's day

The client references show the same journey three times, at three zoom levels:

ReferenceJourney shown
delegate-02-homeConnexion → Tournée → Prospects → Commandes → Rapport
delegate-06-reseller-signup, delegate-09-visit-reportConnexion → Tournée → Inscription → Téléchargement app → 1ère commande → Rapport
delegate-05-visit-checkinArrivée sur site → Démarrer la visite → Réaliser la checklist → Ajouter note et photos → Terminer la visite
MomentWhat the delegate doesScreensNetwork needed
Before leavingOpens the app; today's tour, the sheets of its points of sale, visit types, checklists and reference lists are downloaded — Home shows « Disponible hors ligne »DA-01, DA-05Once, at any time before the first stop
Start« Commencer la tournée »: time and position recorded, location sharing startsDA-05, DA-06No
Each stopNavigates, taps « Je suis arrivé » within the GPS tolerance, starts the visit, ticks the checklist, adds a note and photos, writes the report, ends the visit — or records why the visit was not doneDA-08, DA-10, DA-11, DA-12No
New shop on the wayCreates the prospect with GPS and façade photo, invites the owner to install the reseller app, prepares the first order with themDA-14, DA-16, DA-17Sign-up no; invitation code and order submission yes
During the dayFollow-ups due, messages from the supervisor, a pause (« Suspendre »)DA-19, DA-23, DA-06No
End of daySynchronises, gives a reason for every unvisited stop, writes the end-of-day comment and closes; reads the resultsDA-27, DA-20, DA-21Yes when field.closingRequiresSync is on (default)

The references disagree on the bottom navigation bar:

VariantTabsSeen in
AAccueil · Tournée · Prospects · Rapports · Comptedelegate-01-login-profile, delegate-04-map-navigation, delegate-07-invitation, delegate-08-first-order
BPlanning · Carte · Visite · Prospect · Rapportdelegate-03-tour-program

Decision — variant A: Accueil · Tournée · Prospects · Rapports · Compte. Reasons:

  1. The list and the map are one tour. FLD-06 requires the list and the map to show the same visits in the same order. One Tournée tab with a list/map toggle and a shared filter state guarantees it; B's separate Planning and Carte tabs invite two states that drift apart.
  2. A visit is contextual, not a destination. A visit always belongs to a stop of today's tour. B's Visite tab would be empty most of the day and ambiguous when nothing is in progress. A gets the same one-tap return through the active-visit bar (below).
  3. B has no home and no account. The objectives, counters and the start button (cahier §6.1) need a home; the profile, zone, settings, sync centre and sign-out (cahier §6) need an account tab.
  4. Consistency and recognition. A appears on four of the five references that show a bar and matches the journey of delegate-02-home.
  5. Five tabs is the Material maximum for a bottom bar; A uses it without hiding anything essential.

What happens to B's destinations: Planning → the Tournée tab (list); Carte → the list/map toggle and DA-08; Visite → the active-visit bar and the « Visiter » button of each stop; Prospect → the Prospects tab; Rapport → the Rapports tab.

TabRoot screenReached from the tabTab badge
AccueilDA-05 HomeQuick access to DA-14, DA-19, DA-18, DA-23; DA-22 through « Voir objectifs »—
TournéeDA-06 Tour programmeDA-07 (calendar icon), DA-08, DA-10, DA-11, DA-12, DA-20Dot while the tour is IN_PROGRESS
ProspectsDA-13 Prospects & resellers (list / map) with the segments Points de vente · Relances (DA-19)DA-09, DA-14, DA-15, DA-16, DA-17Number of overdue follow-ups
RapportsDA-21 Reports & performanceTour and visit report details, DA-22—
CompteDA-24 Profile hubDA-25, DA-22, DA-23, DA-26, DA-27Red dot when a sync item is in error

Shared chrome. Every tab root has an app bar with the global sync indicator (cloud icon, see sync badges), the location-sharing chip while positions are being recorded, and the bell with the unread count (→ DA-23). The offline banner sits under the app bar. DA-28 states overlay any screen.

Active-visit bar. While a visit is CHECKED_IN or IN_PROGRESS, a 56 dp bar sits above the bottom navigation on every screen: store name, live duration and « Reprendre » (→ DA-10). Leaving DA-10 never ends the visit.

Back behaviour. Each tab keeps its own stack (go_router StatefulShellRoute). Android back on a tab root returns to Accueil; on Accueil a second back within 2 s leaves the app (« Appuyez encore pour quitter »). Forms with unsaved input (DA-14, DA-15, DA-17) keep a local draft instead of asking to discard.

Offline-first behaviour

What works offline

CapabilityOfflineHowScreens
Open the app with an existing sessionYesLocal data; the version policy is re-checked when the network returnsDA-01
First sign-in, password resetNoNetwork requiredDA-02, DA-03
Today's tour, its stops and their point-of-sale sheetsYesDay snapshot from delegate.tours.today plus deltasDA-05, DA-06, DA-09
Start, suspend, resume the tour; reorder (recommended order); add an unplanned visitYes, queuedOutboxDA-05, DA-06
Map and next destinationPartlyMarkers, order and straight-line distances from local data; map tiles only if cached; ETA estimated; the external navigation app works with its own offline mapsDA-08
Check-in, start, checklist, note, photos, report, end, not doneYes, queuedOutbox and upload queue; GPS tolerance computed on the phoneDA-10, DA-11, DA-12
Points of sale: list, search, filters, distance sortYesThe whole scope is in the local resellers tableDA-13
Create or edit a prospectYes, queuedClient-generated UUIDv7; duplicate check against local data onlyDA-14, DA-15
Generate an invitationNo (first time)The code is issued by the serverDA-16
Show and share an existing invitationYesQR rendered on the phone; share intents queue in WhatsApp/SMSDA-16
Prepare an assisted-order draftYesLocal draft, prices from the catalog cache marked « estimé »DA-17
Price check and sending an assisted orderNoServer pricing and controlsDA-17
Orders of my resellersRead cachedLast viewed ordersDA-18
Follow-ups: list, complete, rescheduleYes, queuedOutboxDA-19
Tour closingPreview yes (local computation); closing needs the network while field.closingRequiresSync is on—DA-20
Reports, performance, objectivesRead cached; today's visit counters stay live—DA-21, DA-22
Notifications and messagesRead cached; mark as read queued—DA-23
SettingsYes, except notification preferences—DA-26
Sync centreYes—DA-27

How an action is recorded

Validate and timestamp. The app checks the screen's rules locally and captures occurredAt = device clock + clock offset. The offset is appConfig.get → serverTime (or the Date header of any response) minus the device time, re-measured on every successful call; above 5 minutes the app warns (DA-28, Horloge incorrecte).

Write once, atomically. In one drift transaction the app updates the local record (optimistic state) and inserts the outbox item: id (UUIDv7, also used as the Idempotency-Key), op (for example delegate.visits.arrive), path parameters, body, occurredAt, the entity it touches, its dependencies, status = QUEUED, attempts = 0.

Show it. Screens render from the database streams, so the change is visible at once, with a haptic and a snackbar, and the record shows the En attente badge.

Send it. When online, the sync engine posts sync.batch within 2 s (debounced): at most 50 items in creation order, skipping items whose dependencies are not applied yet. The batch carries its own Idempotency-Key; each item keeps its own key.

Apply the result. Per item (SyncItemResult): APPLIED or REPLAYED → the item is deleted and the server response merged into the record; RETRY → backoff 5 s, 10 s, 20 s… up to 10 min with ±20 % jitter; CONFLICT or REJECTED → the item turns Erreur, its dependants wait, and the sync centre explains what to do.

Rules that apply to every queued action:

  1. One key per action. The Idempotency-Key is created when the action is recorded and reused on every retry, so a lost response never duplicates a check-in. A new attempt after a REJECTED item, or an item rebuilt by compaction, gets a new key — the same key with a different body answers 409 IDEMPOTENCY_KEY_REUSED.
  2. occurredAt is the time of the action, not of the upload. The server refuses values more than 5 minutes in the future or older than sync.maxOfflineHours (72 h) with 422 OCCURRED_AT_OUT_OF_RANGE. The app warns from 48 h on (banner and local notification « Synchronisez vos données »).
  3. Compaction of full-state edits. delegate.visits.checklist, delegate.visits.note, delegate.visits.report and delegate.tours.reorder send the whole state: a new edit replaces a still-QUEUED item for the same record (new key); an item already in flight is never modified.
  4. Dependencies. Per record, items replay in order. Across records: everything about a client-created reseller waits for its delegate.resellers.create; delegate.visits.addPhoto waits for its file upload; delegate.visits.end waits for the visit's photos and report; every visit item waits for delegate.tours.start; delegate.tours.close is always last.
  5. Server data never overwrites pending work. A delta for a record that still has outbox items is merged only into the fields those items do not touch; the rest is applied once the outbox for that record drains.
  6. Online-only operations (sign-in, invitation creation, assisted-order pricing and sending, closing when sync is required, duplicate check) call the API directly with an Idempotency-Key generated at the tap and kept until a definitive answer; 409 IDEMPOTENCY_IN_PROGRESS is retried after 1 s with the same key.
  7. Nothing is lost silently. Outbox items, uploads and location samples survive app kills, reboots and app updates (drift migrations are additive). Sign-out is refused while items are pending, except through an explicit destructive confirmation (DA-24).

Sync badges

The cahier asks that the user sees « synchronisé, en attente ou en erreur » (cahier §7.3, SYN-06).

StateLabelToneShown onMeaning
Synced« Synchronisé »successDetail headers (small cloud-check icon) and the sync centre — never on list rows, to keep lists quietNo outbox item or upload for the record; the server acknowledged every change
Pending« En attente »infoList rows, detail headers, count in the global indicatorAt least one item QUEUED, in flight or waiting for a retry, or a photo still uploading
Error« Erreur »dangerList rows, detail headers, global indicator, red dot on the Compte tabAt least one item REJECTED or CONFLICT: the delegate must act in DA-27

« En attente » is reserved for synchronisation: no business status of a visit, tour or prospect uses that label in the delegate app (order statuses keep their own label En attente, always inside an order pill).

Global indicator (app bar, tap → DA-27): « Synchronisé · il y a 2 min » · « 3 en attente » · « 1 erreur » (red) · « Hors ligne » (with the pending count) · « Synchronisation… » (animated while a batch is in flight).

Freshness classes

Every data block in the screen specs names one of these classes.

ClassDataRule
Live-localThe delegate's own actions: tour state, visits, checklist, notes, photos, reports, prospects created todayRendered from the local database; the phone is the source of truth until the server acknowledges
DeltaTours, visits, resellers, tasks, reference lists (sync.delta scopes)Pulled at launch, on return to the foreground after 5 min, after every successful push, on pull-to-refresh, on a push data message (tour.updated, tour.reopened…) and every 15 min in the background while a tour is IN_PROGRESS
Day snapshotToday's tour with the sheets of its points of sale, visit types, checklist templates and report optionsdelegate.tours.today at the first launch of the day and whenever a delta touches today's tour; Home shows « Disponible hors ligne · 07:42 »
On openHome aggregate, objectives, performance, reports, orders, invitations, notificationsFetched when the screen opens online and older than 5 min; offline the cached copy shows « Mis à jour à 07:42 »
ReferenceWilayas, communes of the zone, reference lists, app configurationETag revalidation at sign-in and once a day
Online onlyAssisted-order pricing and sending, invitation creation, server closing preview, phone duplicate checkNever treated as authoritative when cached

Local store

Tables of the drift database referenced in the specs (the schema itself is owned by offline sync).

TableHoldsFilled by
profileMe, delegate profile, zones, consentsme.get, delegate.profile
app_configVersion policy, public settings (settings.field.*, settings.sync.*…), serverTime, the day's fieldSettingsappConfig.get, delegate.tours.today
home_snapshotLast home aggregatedelegate.home
objectivesObjectives and achievement per perioddelegate.objectives
toursTours from 30 days back to 14 days aheaddelegate.tours.today, delegate.tours.list, sync.delta
visitsVisits of the cached tours, with local state columnsSame, plus local actions
visit_checklist_items, visit_photos, visit_reportsChecklist state, photo records with their local file, reportsdelegate.visits.get, local actions
visit_types, reference_itemsVisit types with requirements, checklist templates, report options; reference listsreference.lists, delegate.tours.today
wilayas, communesGeography referencereference.wilayas, reference.communes
resellersPoints of sale and prospects in scope: summary columns plus the cached sheetsync.delta, delegate.resellers.get, local creations
tasksFollow-upsdelegate.tasks.list, sync.delta
invitationsInvitations created or vieweddelegate.invitations.create, delegate.invitations.get
ordersOrders viewed (read-only)delegate.orders.list, delegate.orders.get
catalog_brands, catalog_categories, catalog_productsCatalog cache for assisted orderscatalog.brands, catalog.categories, catalog.products.list
draftsUnsent forms: new prospect, assisted order, end-of-day commentLocal
notifications, announcementsInbox and messagesnotifications.list, announcements.list
outboxQueued mutationsLocal actions
upload_queueLocal photo files waiting for uploadCamera
location_pingsPosition samples waiting to be sentLocation sampler
sync_stateDelta cursors per scope, last push and pull, clock offset, snapshot timeSync engine

Location and consent

The position of a delegate is personal data: the cahier limits its use to « les règles validées par le client » (§6.3) and asks for consent and transparency (§10; FLD-15, SEC-07). Frequency, tolerance and retention are settings still to be validated with HOCO (cahier §8, §15).

  1. Consent first. No position is read before the delegate accepts the location notice (DA-04). The acceptance is stored with its version through me.update (consents.locationTracking). Without consent the delegate can consult everything but cannot start a tour; withdrawing consent (DA-26) stops sampling at once.
  2. One-shot reads happen only on an explicit action: tour start, « Je suis arrivé », visit end, « Visite non réalisée » (when available), GPS capture of a new prospect, and the blue dot on the map (shown, never stored).
  3. Sampling only while the tour is IN_PROGRESS: one sample every field.locationIntervalSeconds (300 s) or field.locationDistanceMeters (200 m), whichever comes first. It starts at « Commencer » or « Reprendre » and stops at « Suspendre », at closing, at sign-out, on consent withdrawal, and when the server suspends a forgotten tour at 21:00 (job field.autoSuspendOpenTours, proposal). Never before the tour starts, never after it ends.
  4. Android mechanics. A foreground service of type location with a permanent notification, started from the visible app; only the while in use precise permission (ACCESS_FINE_LOCATION) — no ACCESS_BACKGROUND_LOCATION. Samples go to location_pings and are sent in batches through sync.locationPings (every 10 min or 20 samples, and at each visit event). One-shot reads use high accuracy; sampling uses balanced accuracy to save battery.
  5. Retention. field.locationRetentionDays (90 days) on the server; samples are deleted from the phone once acknowledged.
  6. Mock locations are refused for check-in (422 GPS_MOCK_LOCATION) and flagged on samples.
WhereWhenWhat the delegate sees
Android status barSampling activePermanent notification « Tournée en cours — position partagée avec HOCO toutes les 5 min »; tap → DA-06
App bar of every tab rootSampling activeChip « Position partagée » with a pulsing dot; tap → a sheet explaining what is recorded and until when
Home tour cardIN_PROGRESS / SUSPENDED« Localisation active depuis 08:02 » / « Position non partagée (tournée suspendue) »
DA-26AlwaysWhat is recorded, when, how often, how long it is kept, consent date and version

Outdoor readability and one-hand use

Delegates read the screen in direct sun, often with one hand while holding a bag or a phone box. These rules complete the design system for this app; Akram checks them on a real phone outdoors in DQA-03 and Imad in the accessibility pass MOB-17 (NFR-07).

TopicRule
ContrastPrimary information (store names, times, counters, button labels) at least 7:1 against its background; nothing below 4.5:1; no light-grey secondary text on white
StatusEvery status pill has an icon, a label and a colour — never colour alone (sun glare and colour blindness)
TypeBody text 16 sp minimum, secondary 14 sp, KPI numbers 24 sp or more, button labels 16 sp semibold
TargetsEvery touch target at least 48 × 48 dp with 8 dp between targets; primary actions 56 dp high and full width; list rows at least 64 dp
Thumb zoneThe screen's primary action lives in a sticky bottom action bar; nothing essential is only reachable in the app bar; destructive actions are never next to the primary action
One primary actionEach state of a screen has exactly one primary button (for example « Je suis arrivé » → « Commencer la visite » → « Terminer la visite »)
Text scalingLayouts hold at 130 % system font size; at 200 % content scrolls and no primary action is clipped
MapsLabels on solid plates; numbered markers at least 32 dp; high-contrast position dot; list alternative always one tap away
FeedbackHaptic plus visual confirmation for every recorded action (table below); no information conveyed only by animation
BrightnessThe QR screen of DA-16 raises the screen brightness to the maximum while visible and restores it on exit
Offline clarityNever an empty screen when cached data exists; a persistent banner says the app is offline and that actions are kept
LanguagesFrench in V1; layouts RTL-ready for Arabic (start/end paddings, mirrored directional icons) (NFR-06)
EventHapticVisual
Action recorded (start, check-in, visit end, prospect saved, closing)HapticFeedback.mediumImpactSuccess snackbar with the recorded time
Selection (chip, checkbox, quantity stepper)HapticFeedback.selectionClickState change
Blocked action or validation errorHapticFeedback.heavyImpactInline error or dialog that names the fix
Entering the GPS tolerance radius on the mapHapticFeedback.lightImpact, once per stop« Je suis arrivé » becomes the primary button

Settings the app reads

Business settings are edited on BO-64 and reach the app two ways, both stored in app_config: the public subset through appConfig.get (settings.*), and the field settings of the day through delegate.tours.today (fieldSettings: tolerance, accuracy, out-of-tolerance switch, report rule, closing rule, note length, photo limit, location policy) so that they apply offline. Defaults below; the settings catalogue is authoritative.

KeyDefaultEffect in the appScreens
field.gpsToleranceMeters150Maximum distance between the fix and the point of sale for a check-in; radius drawn on the mapDA-08, DA-10
field.gpsMaxAccuracyMeters100A fix less precise than this is not used for a check-in or a prospect's positionDA-08, DA-10, DA-14
field.allowOutOfToleranceCheckInfalsetrue: check-in outside the tolerance allowed with a written justification (10–200 characters), visit flagged; false: refusedDA-10
field.maxPhotosPerVisit · note length10 · 300Photo counter and limit; quick-note counterDA-10
field.locationIntervalSeconds · field.locationDistanceMeters300 · 200Sampling period and distance while the tour is IN_PROGRESSSampler, DA-04, DA-26
field.locationRetentionDays90Shown in the consent notice and in the transparency pageDA-04, DA-26
field.closingRequiresSynctrueClosing only with an empty outbox, therefore onlineDA-20
field.reportRequiredToEndVisitfalseDefault of the visit type's report required flagDA-10, DA-12
field.visitUnlockHours · field.reopenMaxDays24 · 7Correction window after a back-office unlock; reopening limit of a closed tourDA-10, DA-20
sync.maxOfflineHours72Age limit of queued actions; warning from 48 hDA-27
invitations.validityDays30« Lien valable 30 jours »DA-16
orders.assistedConfirmationHours · orders.minAmount72 · 1000000Confirmation countdown; minimum-amount progress in the assisted orderDA-17, DA-18
pricing.priceDisplayTTCWhich price value is shown large (decision to confirm)DA-17

To align — the sampling distance is named field.locationDistanceMeters in appConfig.get and field.locationMinDistanceMeters on the tours API page (fieldSettings.location.minDistanceMeters); the settings catalogue should keep one key. field.visitUnlockHours and field.reopenMaxDays are not sent to the app: it reads their effect from unlockedUntil and the reopening notification.

Error codes shown by the app

The app shows the server's localised error.message; the French strings below are its fallbacks when the message is missing (offline evaluation, old cache). The error registry is authoritative.

CodeHTTPFallback messageRaised by
INVALID_CREDENTIALS401« Numéro ou mot de passe incorrect. »auth.login
WRONG_APP403« Ce compte n'est pas un compte délégué. Utilisez l'application HOCO Revendeur. »auth.login
ACCOUNT_SUSPENDED403« Votre compte est suspendu. Contactez votre responsable. » (details.status = DISABLED: « Votre compte est désactivé. »)Any call
PASSWORD_CHANGE_REQUIRED403« Choisissez un nouveau mot de passe pour continuer. »Any call while the password is temporary
TOO_MANY_ATTEMPTS429« Trop de tentatives. Réessayez à 10:42. »auth.login, auth.otp.verify
UNAUTHENTICATED401« Votre session a expiré. Reconnectez-vous : vos données en attente sont conservées. »Any call
UPGRADE_REQUIRED426« Mettez l'application à jour pour continuer. »Any call
MAINTENANCE503« Maintenance en cours jusqu'à 14:00. Vous pouvez continuer hors ligne. »Any call
TOUR_TRANSITION_NOT_ALLOWED409« Cette tournée ne peut pas être démarrée. » (not TO_START: planned for another day, completed or cancelled); on suspend, resume, reorder, add-visit and close: « Action impossible : la tournée est terminée ou annulée. »Tour operations
TOUR_NOT_TODAY409« Cette tournée n'est pas prévue aujourd'hui. »delegate.tours.start
TOUR_ALREADY_IN_PROGRESS409« Clôturez d'abord votre tournée du 21 mai. »delegate.tours.start
NOT_ASSIGNED_DELEGATE403« Cette tournée est affectée à un autre délégué. »Tour and visit operations
TOUR_NOT_IN_PROGRESS409« La tournée n'est pas en cours : démarrez-la ou reprenez-la d'abord. »Visit operations
TOUR_ORDER_IMPOSED409« L'ordre des visites est imposé par votre responsable. »delegate.tours.reorder
VISIT_IN_PROGRESS409« Terminez d'abord la visite en cours chez Electro Plus. »delegate.visits.arrive, delegate.tours.suspend
GPS_ACCURACY_TOO_LOW422« Position trop imprécise (± 180 m). Placez-vous à découvert et réessayez. »delegate.visits.arrive
GPS_OUT_OF_TOLERANCE422« Vous êtes à 420 m du point de vente (tolérance 150 m). »delegate.visits.arrive
GPS_MOCK_LOCATION422« Position simulée détectée : désactivez l'application de localisation fictive. »delegate.visits.arrive
VISIT_LOCKED409« Cette visite est terminée et verrouillée. »Visit edits
VISIT_NOT_CHECKED_IN · VISIT_TRANSITION_NOT_ALLOWED409« Enregistrez d'abord votre arrivée. » · « Action impossible dans l'état actuel de la visite. »Visit operations
VISIT_ALREADY_PLANNED409« Ce point de vente a déjà une visite ouverte dans la tournée. »delegate.tours.addVisit
PHOTO_LIMIT_REACHED422« Nombre maximal de photos atteint (10). »delegate.visits.addPhoto
VISIT_REQUIREMENTS_NOT_MET422details.missing: REPORT_REQUIRED « Le rapport de visite est obligatoire. » · PHOTO_REQUIRED « Ajoutez au moins 1 photo. » · CHECKLIST_INCOMPLETE « Cochez les points obligatoires de la checklist. »delegate.visits.end
REASON_REQUIRED422« Choisissez un motif. »delegate.visits.notDone, delegate.tours.suspend
TOUR_CLOSING_BLOCKED409« Des points restent à régler avant la clôture. » (details.blockingItems, including UNSYNCED_DATA: « Des données ne sont pas encore synchronisées. »)delegate.tours.close
PHONE_ALREADY_EXISTS409« Ce numéro est déjà enregistré. »delegate.resellers.create, delegate.resellers.update
OUT_OF_SCOPE403« Ce point de vente ne fait pas partie de votre périmètre. »Reseller and order operations
RESELLER_TRANSITION_NOT_ALLOWED409« Ce changement de statut n'est pas autorisé. »delegate.resellers.update
FIELD_NOT_EDITABLE422details.fields[].reason: HAS_ACCOUNT « Le revendeur a un compte : il modifie son numéro dans son application. » · CONFIRMED_AT_VALIDATION « Le type de client est confirmé par HOCO. » · VALIDATED_LOCATION « Position validée : signalez un déménagement par une note. »delegate.resellers.update
RESELLER_HAS_ACCOUNT409« Ce revendeur a déjà un compte : rien à envoyer. »delegate.invitations.create
VERSION_CONFLICT409« Cette fiche a été modifiée entre-temps. »Updates (shown as a conflict in DA-27)
RESELLER_SUSPENDED403« Ce revendeur est suspendu : commande impossible. »Assisted orders
ASSISTED_ORDER_ALREADY_PENDING409« Une commande attend déjà la confirmation de ce revendeur. »delegate.assisted.submit
ORDER_MIN_AMOUNT_NOT_MET422« Montant minimum : 10 000,00 DA. »delegate.assisted.submit
STOCK_INSUFFICIENT · PRODUCT_UNAVAILABLE · QUANTITY_NOT_ALLOWED422« Quantité non disponible pour cet article. » · « Article indisponible. » · « Quantité non autorisée pour cet article. » — never a stock quantityAssisted orders
CREDIT_LIMIT_EXCEEDED422« Crédit disponible insuffisant : choisissez un autre mode de paiement. »delegate.assisted.submit
OCCURRED_AT_OUT_OF_RANGE422« Action enregistrée il y a plus de 72 h : contactez votre responsable. »Queued actions
IDEMPOTENCY_KEY_REUSED · IDEMPOTENCY_IN_PROGRESS409Never shown: logged to Sentry; in-progress is retried after 1 sAny mutation

Routes of the app's router (MOB-05). Push payloads carry a route that is opened after the splash; a target outside the delegate's scope or deleted falls back to Accueil with « Élément introuvable ou retiré de votre périmètre ».

RouteScreen
/homeDA-05
/tourDA-06 (today)
/toursDA-07
/tours/:tourIdDA-06 for that tour (read-only when not today's)
/tours/:tourId/mapDA-08
/tours/:tourId/closingDA-20
/visits/:visitId · /visits/:visitId/reportDA-10 · DA-12 (DA-11 is a sheet over DA-10)
/resellers · /resellers/new · /resellers/:resellerId · /resellers/:resellerId/editDA-13 · DA-14 · DA-09 · DA-15
/resellers/:resellerId/assisted-orderDA-17 (new draft or the open one)
/invitations/:invitationIdDA-16
/orders · /orders/:orderIdDA-18
/tasksDA-19
/reports · /objectivesDA-21 · DA-22
/messages · /messages/:announcementIdDA-23
/account · /account/zone · /account/settings · /syncDA-24 · DA-25 · DA-26 · DA-27

Notifications received by the delegate (from the notifications matrix, which owns the wording):

EventChannelsOpens
tour.assignedPush, in-app/tours/:tourId (DA-07 context)
tour.updated (stops changed, tour cancelled)Push, in-app, data message that triggers sync.delta/tours/:tourId
tour.reopenedPush, in-app, sync data message/tours/:tourId/closing
task.due (08:00 digest) · task.assignedPush, in-app/tasks
invitation.step_completedIn-app for every step; push for ACCOUNT_CREATED and FIRST_ORDER/invitations/:invitationId
assisted_order.confirmed · assisted_order.rejected · assisted_order.expired.delegatePush, in-app/orders/:orderId
order.submitted.delegate · order.refused.delegatePush, in-app/orders/:orderId
order.validated.delegate · order.modified.delegate · order.status_changed.delegate · order.cancelled.delegateIn-app/orders/:orderId
account.approved.delegate · account.rejected.delegatePush, in-app/resellers/:resellerId
account.suspended.delegateIn-app/resellers/:resellerId
promotion.started.delegateIn-app/messages
announcement.publishedIn-app, push when requested/messages/:announcementId
Local reminder: oldest pending item older than 48 hLocal notification/sync

While the app is open and online it also listens to events.stream (notification.created) to refresh the bell and the open order without waiting for a push.

Reference screens: decisions on inconsistencies

ReferenceObservationDecision
delegate-03 vs the othersTwo bottom barsVariant A (see navigation)
delegate-02-home« Commencer la tournée » while stop 1 is already « Visité »The home button follows TourStatus; no visit can be done before the tour starts
delegate-02-home« En attente » used as a visit statusVisit rows use VisitStatus labels only; « En attente » means a pending sync
delegate-02-homeQuick access repeats the bottom-bar tabs (Tournée, Prospects, Rapports)Quick access shows what the bar does not: Nouveau revendeur, Relances, Commandes, Messages
delegate-01-login-profileSign-in form and profile preview on one screen; « Se souvenir de moi »Two screens (DA-02, DA-24); no checkbox — mobile sessions last 30 days and the phone is prefilled
delegate-03-tour-programTabs À visiter · Visitées · ProspectsTabs À visiter · Réalisées · Non réalisées; Prospects becomes a filter chip
delegate-05-visit-checkin« Commencer » and « Terminer » side by side; photos always « (optionnel) »One state-driven primary button; the photo label follows the visit type
delegate-06-reseller-signupThe status stepper lets the delegate pick Compte créé or Revendeur actif; no customer type; no asterisk on the façade photo; « 0.6331° E » for OranOnly manual statuses can be chosen; customer type added (proposed by the delegate, confirmed by HOCO — §1b); façade photo required (FLD-11); hemisphere from the sign (Oran is 0,6331° O)
delegate-07-invitationLink app.delegate.com/inviter/AB12CD, code AB12-CD34-EF56, four stepshttps://app.hoco.dz/i/AB12CD34, 8-character code shown AB12-CD34, the six InvitationStep values
delegate-08-first-order« Passer la 1ère commande »; HOCO products only; « 2.350,00 DA »; « En stock » only« Envoyer pour confirmation » (the reseller or the administration confirms, then the back-office confirms — cahier §6.7, §1b); the three brands with filters; French format 2 350,00 DA; availability only, never a quantity
delegate-10-tour-closing« Terminer la tournée » above the performances; conversion « 6 / 8 visites » while home says « 2 / 8 prospects »Sticky bottom action; one conversion definition everywhere (DA-21)
AllDates in 2024, sample namesPlaceholder data

API surface

MethodPathOperationAccessAppsRelease
POST/auth/loginauth.login
Sign in with phone (or email for staff) and password; returns a session token
publicRA · RW · DA · BOV1
POST/auth/logoutauth.logout
Revoke the current session and unregister the device push token
sessionRA · RW · DA · BOV1
GET/auth/sessionauth.session
Current session and its expiry
sessionRA · RW · DA · BOV1
POST/auth/otp/sendauth.otp.send
Send a one-time code by SMS for sign-up, password reset or phone change
publicRA · RW · DA · BOV1
POST/auth/otp/verifyauth.otp.verify
Verify a one-time code; returns a short-lived verification token
publicRA · RW · DA · BOV1
POST/auth/password/resetauth.password.reset
Set a new password using a verification token
publicRA · RW · DA · BOV1
POST/auth/password/changeauth.password.change
Change password (current password required); other sessions revoked
sessionRA · RW · DA · BOV1
GET/auth/sessionsauth.sessions.list
List my active sessions (device, app, last activity)
sessionRA · RW · DA · BOV1
DELETE/auth/sessions/{sessionId}auth.sessions.revoke
Revoke one of my sessions
sessionRA · RW · DA · BOV1
GET/meme.get
My profile, roles, permissions, scope (reseller or delegate context) and account status
sessionRA · RW · DA · BOV1
PATCH/meme.update
Update my name, locale or avatar
sessionRA · RW · DA · BOV1
POST/me/devicesme.devices.register
Register or refresh this device's FCM token and app version
session
Idempotency-Key
RA · DAV1
DELETE/me/devices/{deviceId}me.devices.remove
Unregister a device
sessionRA · DAV1
GET/app-configappConfig.get
Version policy (min/latest/forced), maintenance flag and public business settings for an app/platform
publicRA · RW · DAV1
GET/reference/wilayasreference.wilayas
Wilayas (code, FR/AR names); cacheable with ETag
publicRA · RW · DA · BOV1
GET/reference/wilayas/{wilayaCode}/communesreference.communes
Communes of a wilaya; cacheable with ETag
publicRA · RW · DA · BOV1
GET/reference/listsreference.lists
Reference lists (business types, potentials, visit types, not-done reasons, issue tags, ticket types, payment methods); cacheable
sessionRA · RW · DA · BOV1
POST/files/uploadsfiles.createUpload
Request a pre-signed upload URL for a photo or document
session
Idempotency-Key
RA · RW · DA · BOV1
POST/files/{fileId}/completefiles.completeUpload
Confirm an upload; the server checks size/type and creates thumbnails
session
Idempotency-Key
RA · RW · DA · BOV1
GET/files/{fileId}/urlfiles.downloadUrl
Short-lived signed URL for a private file the caller may access
sessionRA · RW · DA · BOV1
GET/catalog/brandscatalog.brands
Active brands — guests allowed, without prices
guest or sessionRA · RW · DAV1
GET/catalog/categoriescatalog.categories
Category tree with product counts visible to the caller — guests allowed, without prices
guest or sessionRA · RW · DAV1
GET/catalog/productscatalog.products.list
Search, filter and sort products with the caller's resolved price and availability (cursor pagination) — guests allowed, without prices
guest or sessionRA · RW · DAV1
GET/catalog/products/suggestcatalog.products.suggest
Type-ahead suggestions (products, brands, categories) — guests allowed, without prices
guest or sessionRA · RW · DAV1
GET/catalog/facetscatalog.facets
Filter facets with counts for the current query: categories and sub-categories, brands, price range, attributes, availability, promotion — guests allowed (no price facet)
guest or sessionRA · RW · DAV1
GET/catalog/products/by-barcode/{barcode}catalog.products.byBarcode
Find a product variant by barcode — guests allowed, without prices
guest or sessionRA · DAV1
GET/catalog/products/{productId}catalog.products.get
Product detail: variants, gallery, specs, prices, availability, quantity limits, active promotion — guests allowed, without prices
guest or sessionRA · RW · DAV1
GET/promotionspromotions.list
Promotions and daily offers available to the caller — guests allowed, without prices
guest or sessionRA · RW · DAV1
GET/promotions/{promotionId}promotions.get
Promotion detail: conditions, dates, remaining stock cap, eligible products — guests allowed, without prices
guest or sessionRA · RW · DAV1
GET/notificationsnotifications.list
My notification inbox (cursor pagination, unread filter)
sessionRA · RW · DA · BOV1
GET/notifications/unread-countnotifications.unreadCount
Unread badge count
sessionRA · RW · DA · BOV1
POST/notifications/{notificationId}/readnotifications.markRead
Mark one notification as read
sessionRA · RW · DA · BOV1
POST/notifications/read-allnotifications.markAllRead
Mark all as read
sessionRA · RW · DA · BOV1
GET/eventsevents.stream
Server-Sent Events stream of the caller's own live events (order.status_changed, order.modified, assisted_order.awaiting_confirmation, ticket.replied, return.status_changed, notification.created) while the app or page is open; push notifications cover the rest
sessionRA · RW · DAV1
GET/notification-preferencesnotifications.preferences.get
My channel preferences per category
sessionRA · RW · DAV1
PUT/notification-preferencesnotifications.preferences.update
Update my preferences (transactional ones cannot be disabled)
sessionRA · RW · DAV1
GET/announcementsannouncements.list
Announcements targeted to me ('Messages')
sessionRA · DAV1
GET/announcements/{announcementId}announcements.get
One announcement targeted to me (opened from a notification)
selfRA · RW · DAV1
POST/announcements/{announcementId}/readannouncements.markRead
Acknowledge an announcement
sessionRA · DAV1
GET/delegate/homedelegate.home
Home aggregate: zone, today's tour and counters, objectives, conversion, unread counts
delegateDAV1
GET/delegate/profiledelegate.profile
My delegate profile, zones, wilayas and communes covered
delegateDAV1
GET/delegate/zonedelegate.zone
My zone geometry hints and point-of-sale counts per commune
delegateDAV1
GET/delegate/objectivesdelegate.objectives
Objectives and achievement for a period (day, week, month)
delegateDAV1
GET/delegate/performancedelegate.performance
My performance over a period: visits, sign-ups, installs, first orders, revenue, km, conversion
delegateDAV1
GET/delegate/reportsdelegate.reports
History of my visit reports and tour reports
delegateDAV1
GET/delegate/toursdelegate.tours.list
My tours in a date range (calendar and history)
delegateDAV1
GET/delegate/tours/todaydelegate.tours.today
Today's tour with ordered visits, points of sale, distances and statuses (offline snapshot)
delegateDAV1
GET/delegate/tours/{tourId}delegate.tours.get
A tour with its visits
delegateDAV1
POST/delegate/tours/{tourId}/startdelegate.tours.start
Start the tour (time and position recorded)
delegate
Idempotency-Key
DAV1
POST/delegate/tours/{tourId}/suspenddelegate.tours.suspend
Suspend the tour with a reason
delegate
Idempotency-Key
DAV1
POST/delegate/tours/{tourId}/resumedelegate.tours.resume
Resume a suspended tour
delegate
Idempotency-Key
DAV1
PUT/delegate/tours/{tourId}/visit-orderdelegate.tours.reorder
Reorder remaining visits when the tour's order is 'recommended' (not 'imposed')
delegate
Idempotency-Key
DAV1
POST/delegate/tours/{tourId}/visitsdelegate.tours.addVisit
Add an unplanned visit (client-generated id)
delegate
Idempotency-Key
DAV1
GET/delegate/tours/{tourId}/closing-previewdelegate.tours.closingPreview
Day summary and blocking items (unvisited stops without reason, unsynced data)
delegateDAV1
POST/delegate/tours/{tourId}/closedelegate.tours.close
Close the tour with an end-of-day comment; returns the final report
delegate
Idempotency-Key
DAV1
GET/delegate/visits/{visitId}delegate.visits.get
Visit detail: point of sale, checklist template, report config, current state
delegateDAV1
POST/delegate/visits/{visitId}/arrivedelegate.visits.arrive
Check-in: position and time; returns distance and whether the GPS tolerance is met
delegate
Idempotency-Key
DAV1
POST/delegate/visits/{visitId}/startdelegate.visits.start
Start the visit timer
delegate
Idempotency-Key
DAV1
PUT/delegate/visits/{visitId}/checklistdelegate.visits.checklist
Save the checklist state
delegate
Idempotency-Key
DAV1
PUT/delegate/visits/{visitId}/notedelegate.visits.note
Save the quick note (≤ 300 chars)
delegate
Idempotency-Key
DAV1
POST/delegate/visits/{visitId}/photosdelegate.visits.addPhoto
Attach an uploaded photo with its capture position and time
delegate
Idempotency-Key
DAV1
PUT/delegate/visits/{visitId}/reportdelegate.visits.report
Save the structured visit report; creates or updates the follow-up task
delegate
Idempotency-Key
DAV1
POST/delegate/visits/{visitId}/enddelegate.visits.end
Finish the visit (requires report/photos when the visit type demands them); the visit locks
delegate
Idempotency-Key
DAV1
POST/delegate/visits/{visitId}/not-donedelegate.visits.notDone
Mark the visit as not carried out with a reason
delegate
Idempotency-Key
DAV1
GET/delegate/resellersdelegate.resellers.list
Points of sale and prospects in my scope: search, status filter, distance sort
delegateDAV1
GET/delegate/resellers/check-phonedelegate.resellers.checkPhone
Duplicate check before creating a prospect
delegateDAV1
POST/delegate/resellersdelegate.resellers.create
Create a prospect/reseller in the field (client-generated id; offline-safe)
delegate
Idempotency-Key
DAV1
GET/delegate/resellers/{resellerId}delegate.resellers.get
Point-of-sale sheet: details, status, recent visits, orders summary, open tasks, photos
delegateDAV1
PATCH/delegate/resellers/{resellerId}delegate.resellers.update
Update a prospect/reseller in my scope (status changes per allowed transitions)
delegate
Idempotency-Key
DAV1
POST/delegate/resellers/{resellerId}/notesdelegate.resellers.addNote
Add a note or call log to the interaction timeline
delegate
Idempotency-Key
DAV1
POST/delegate/resellers/{resellerId}/photosdelegate.resellers.addPhoto
Attach a façade/interior photo
delegate
Idempotency-Key
DAV1
POST/delegate/resellers/{resellerId}/invitationsdelegate.invitations.create
Create (or return the active) invitation: code, link, QR payload, share texts
delegate
Idempotency-Key
DAV1
GET/delegate/invitations/{invitationId}delegate.invitations.get
Invitation with its activation steps
delegateDAV1
POST/delegate/invitations/{invitationId}/shareddelegate.invitations.markShared
Record that the invitation was shared and through which channel
delegate
Idempotency-Key
DAV1
GET/delegate/tasksdelegate.tasks.list
My follow-ups and tasks (due today, overdue, upcoming)
delegateDAV1
PATCH/delegate/tasks/{taskId}delegate.tasks.update
Reschedule or annotate a task
delegate
Idempotency-Key
DAV1
POST/delegate/tasks/{taskId}/completedelegate.tasks.complete
Complete a task with an outcome
delegate
Idempotency-Key
DAV1
POST/delegate/assisted-ordersdelegate.assisted.create
Start an assisted order for a reseller in my scope (draft, priced)
delegate
Idempotency-Key
DAV1
PATCH/delegate/assisted-orders/{orderId}delegate.assisted.update
Edit draft lines, address and note; returns re-priced totals and issues
delegate
Idempotency-Key
DAV1
POST/delegate/assisted-orders/{orderId}/submitdelegate.assisted.submit
Send for confirmation (reseller in app, or administration); same controls as a normal order
delegate
Idempotency-Key
DAV1
DELETE/delegate/assisted-orders/{orderId}delegate.assisted.discard
Discard a draft assisted order
delegateDAV1
GET/delegate/ordersdelegate.orders.list
Orders of resellers in my scope (assisted and direct), read-only
delegateDAV1
GET/delegate/orders/{orderId}delegate.orders.get
Order detail (read-only, no purchase prices)
delegateDAV1
POST/sync/batchsync.batch
Replay up to 50 queued offline mutations in order; per-item results with idempotent replays
delegateDAV1
GET/sync/deltasync.delta
Changes since a cursor for the delegate's offline scopes (tours, visits, resellers, tasks, reference lists)
delegateDAV1
POST/delegate/location-pingssync.locationPings
Batch of position samples during an active tour (policy-driven frequency)
delegate
Idempotency-Key
DAV1

Screen index

IDScreenUI titleSectionReleaseDesignBuild
DA-01Splash & version checkDémarrageAccessV1Day 11MOB-05
DA-02Sign inConnexionAccessV1Day 11MOB-06
DA-03Forgot passwordMot de passe oubliéAccessV1Day 11MOB-06
DA-04Permissions & location consentAutorisations et consentementAccessV1Day 11MOB-10
DA-05HomeAccueil déléguéDayV1Day 11MDA-01
DA-06Tour programmeProgramme de tournéeTourV1Day 12MDA-02
DA-07Tours calendar & historyMes tournéesTourV1Day 12MDA-02
DA-08Map & navigationCarte et navigationTourV1Day 12MDA-03
DA-09Point-of-sale sheetFiche point de venteTourV1Day 12MDA-04
DA-10Visit & check-inDétail de la visiteVisitV1Day 13MDA-05
DA-11Visit not done — reasonVisite non réaliséeVisitV1Day 13MDA-05
DA-12Visit reportRapport de visiteVisitV1Day 13MDA-06
DA-13Prospects & resellers (list / map)ProspectsProspectsV1Day 13MDA-04
DA-14New resellerNouveau revendeurProspectsV1Day 14MDA-07
DA-15Edit reseller / prospectModifier la ficheProspectsV1Day 14MDA-07
DA-16Invite a resellerInviter un revendeurConversionV1Day 14MDA-08
DA-17First-order assistanceAide à la 1ère commandeConversionV1Day 14MDA-09
DA-18Assisted orders & orders of my resellersCommandesConversionV1Day 14MDA-09
DA-19Follow-ups & tasksRelancesProspectsV1Day 13MDA-06
DA-20Tour closing & resultsClôture de tournéeDayV1Day 14MDA-10
DA-21Reports & performanceRapportsDayV1Day 14MDA-10
DA-22ObjectivesMes objectifsProfileV1Day 11MDA-01
DA-23Messages & notificationsMessagesProfileV1Day 14MDA-11
DA-24Profile hubCompteProfileV1Day 11MDA-01
DA-25My zoneMa zoneProfileV1Day 11MDA-01
DA-26SettingsParamètresProfileV1Day 11MDA-11
DA-27Sync centreSynchronisationSystemV1Day 14MDA-11
DA-28System states (offline, GPS off, out of tolerance, permission denied, forced update)États systèmeSystemV1Day 14MOB-10

On this page