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.
Access
Day and tour
Visits
Prospects and conversion
Profile and system
The delegate's day
The client references show the same journey three times, at three zoom levels:
| Reference | Journey shown |
|---|---|
delegate-02-home | Connexion → Tournée → Prospects → Commandes → Rapport |
delegate-06-reseller-signup, delegate-09-visit-report | Connexion → Tournée → Inscription → Téléchargement app → 1ère commande → Rapport |
delegate-05-visit-checkin | Arrivée sur site → Démarrer la visite → Réaliser la checklist → Ajouter note et photos → Terminer la visite |
| Moment | What the delegate does | Screens | Network needed |
|---|---|---|---|
| Before leaving | Opens 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-05 | Once, at any time before the first stop |
| Start | « Commencer la tournée »: time and position recorded, location sharing starts | DA-05, DA-06 | No |
| Each stop | Navigates, 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 done | DA-08, DA-10, DA-11, DA-12 | No |
| New shop on the way | Creates the prospect with GPS and façade photo, invites the owner to install the reseller app, prepares the first order with them | DA-14, DA-16, DA-17 | Sign-up no; invitation code and order submission yes |
| During the day | Follow-ups due, messages from the supervisor, a pause (« Suspendre ») | DA-19, DA-23, DA-06 | No |
| End of day | Synchronises, gives a reason for every unvisited stop, writes the end-of-day comment and closes; reads the results | DA-27, DA-20, DA-21 | Yes when field.closingRequiresSync is on (default) |
Navigation model
The references disagree on the bottom navigation bar:
| Variant | Tabs | Seen in |
|---|---|---|
| A | Accueil · Tournée · Prospects · Rapports · Compte | delegate-01-login-profile, delegate-04-map-navigation, delegate-07-invitation, delegate-08-first-order |
| B | Planning · Carte · Visite · Prospect · Rapport | delegate-03-tour-program |
Decision — variant A: Accueil · Tournée · Prospects · Rapports · Compte. Reasons:
- 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.
- 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).
- 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.
- Consistency and recognition. A appears on four of the five references that show a bar and matches the journey of
delegate-02-home. - 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.
| Tab | Root screen | Reached from the tab | Tab badge |
|---|---|---|---|
| Accueil | DA-05 Home | Quick access to DA-14, DA-19, DA-18, DA-23; DA-22 through « Voir objectifs » | — |
| Tournée | DA-06 Tour programme | DA-07 (calendar icon), DA-08, DA-10, DA-11, DA-12, DA-20 | Dot while the tour is IN_PROGRESS |
| Prospects | DA-13 Prospects & resellers (list / map) with the segments Points de vente · Relances (DA-19) | DA-09, DA-14, DA-15, DA-16, DA-17 | Number of overdue follow-ups |
| Rapports | DA-21 Reports & performance | Tour and visit report details, DA-22 | — |
| Compte | DA-24 Profile hub | DA-25, DA-22, DA-23, DA-26, DA-27 | Red 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
| Capability | Offline | How | Screens |
|---|---|---|---|
| Open the app with an existing session | Yes | Local data; the version policy is re-checked when the network returns | DA-01 |
| First sign-in, password reset | No | Network required | DA-02, DA-03 |
| Today's tour, its stops and their point-of-sale sheets | Yes | Day snapshot from delegate.tours.today plus deltas | DA-05, DA-06, DA-09 |
| Start, suspend, resume the tour; reorder (recommended order); add an unplanned visit | Yes, queued | Outbox | DA-05, DA-06 |
| Map and next destination | Partly | Markers, order and straight-line distances from local data; map tiles only if cached; ETA estimated; the external navigation app works with its own offline maps | DA-08 |
| Check-in, start, checklist, note, photos, report, end, not done | Yes, queued | Outbox and upload queue; GPS tolerance computed on the phone | DA-10, DA-11, DA-12 |
| Points of sale: list, search, filters, distance sort | Yes | The whole scope is in the local resellers table | DA-13 |
| Create or edit a prospect | Yes, queued | Client-generated UUIDv7; duplicate check against local data only | DA-14, DA-15 |
| Generate an invitation | No (first time) | The code is issued by the server | DA-16 |
| Show and share an existing invitation | Yes | QR rendered on the phone; share intents queue in WhatsApp/SMS | DA-16 |
| Prepare an assisted-order draft | Yes | Local draft, prices from the catalog cache marked « estimé » | DA-17 |
| Price check and sending an assisted order | No | Server pricing and controls | DA-17 |
| Orders of my resellers | Read cached | Last viewed orders | DA-18 |
| Follow-ups: list, complete, reschedule | Yes, queued | Outbox | DA-19 |
| Tour closing | Preview yes (local computation); closing needs the network while field.closingRequiresSync is on | — | DA-20 |
| Reports, performance, objectives | Read cached; today's visit counters stay live | — | DA-21, DA-22 |
| Notifications and messages | Read cached; mark as read queued | — | DA-23 |
| Settings | Yes, except notification preferences | — | DA-26 |
| Sync centre | Yes | — | 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:
- One key per action. The
Idempotency-Keyis created when the action is recorded and reused on every retry, so a lost response never duplicates a check-in. A new attempt after aREJECTEDitem, or an item rebuilt by compaction, gets a new key — the same key with a different body answers409 IDEMPOTENCY_KEY_REUSED. occurredAtis the time of the action, not of the upload. The server refuses values more than 5 minutes in the future or older thansync.maxOfflineHours(72 h) with422 OCCURRED_AT_OUT_OF_RANGE. The app warns from 48 h on (banner and local notification « Synchronisez vos données »).- Compaction of full-state edits.
delegate.visits.checklist,delegate.visits.note,delegate.visits.reportanddelegate.tours.reordersend the whole state: a new edit replaces a still-QUEUEDitem for the same record (new key); an item already in flight is never modified. - Dependencies. Per record, items replay in order. Across records: everything about a client-created reseller waits for its
delegate.resellers.create;delegate.visits.addPhotowaits for its file upload;delegate.visits.endwaits for the visit's photos and report; every visit item waits fordelegate.tours.start;delegate.tours.closeis always last. - 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.
- 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-Keygenerated at the tap and kept until a definitive answer;409 IDEMPOTENCY_IN_PROGRESSis retried after 1 s with the same key. - 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).
| State | Label | Tone | Shown on | Meaning |
|---|---|---|---|---|
| Synced | « Synchronisé » | success | Detail headers (small cloud-check icon) and the sync centre — never on list rows, to keep lists quiet | No outbox item or upload for the record; the server acknowledged every change |
| Pending | « En attente » | info | List rows, detail headers, count in the global indicator | At least one item QUEUED, in flight or waiting for a retry, or a photo still uploading |
| Error | « Erreur » | danger | List rows, detail headers, global indicator, red dot on the Compte tab | At 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.
| Class | Data | Rule |
|---|---|---|
| Live-local | The delegate's own actions: tour state, visits, checklist, notes, photos, reports, prospects created today | Rendered from the local database; the phone is the source of truth until the server acknowledges |
| Delta | Tours, 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 snapshot | Today's tour with the sheets of its points of sale, visit types, checklist templates and report options | delegate.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 open | Home aggregate, objectives, performance, reports, orders, invitations, notifications | Fetched when the screen opens online and older than 5 min; offline the cached copy shows « Mis à jour à 07:42 » |
| Reference | Wilayas, communes of the zone, reference lists, app configuration | ETag revalidation at sign-in and once a day |
| Online only | Assisted-order pricing and sending, invitation creation, server closing preview, phone duplicate check | Never treated as authoritative when cached |
Local store
Tables of the drift database referenced in the specs (the schema itself is owned by offline sync).
| Table | Holds | Filled by |
|---|---|---|
profile | Me, delegate profile, zones, consents | me.get, delegate.profile |
app_config | Version policy, public settings (settings.field.*, settings.sync.*…), serverTime, the day's fieldSettings | appConfig.get, delegate.tours.today |
home_snapshot | Last home aggregate | delegate.home |
objectives | Objectives and achievement per period | delegate.objectives |
tours | Tours from 30 days back to 14 days ahead | delegate.tours.today, delegate.tours.list, sync.delta |
visits | Visits of the cached tours, with local state columns | Same, plus local actions |
visit_checklist_items, visit_photos, visit_reports | Checklist state, photo records with their local file, reports | delegate.visits.get, local actions |
visit_types, reference_items | Visit types with requirements, checklist templates, report options; reference lists | reference.lists, delegate.tours.today |
wilayas, communes | Geography reference | reference.wilayas, reference.communes |
resellers | Points of sale and prospects in scope: summary columns plus the cached sheet | sync.delta, delegate.resellers.get, local creations |
tasks | Follow-ups | delegate.tasks.list, sync.delta |
invitations | Invitations created or viewed | delegate.invitations.create, delegate.invitations.get |
orders | Orders viewed (read-only) | delegate.orders.list, delegate.orders.get |
catalog_brands, catalog_categories, catalog_products | Catalog cache for assisted orders | catalog.brands, catalog.categories, catalog.products.list |
drafts | Unsent forms: new prospect, assisted order, end-of-day comment | Local |
notifications, announcements | Inbox and messages | notifications.list, announcements.list |
outbox | Queued mutations | Local actions |
upload_queue | Local photo files waiting for upload | Camera |
location_pings | Position samples waiting to be sent | Location sampler |
sync_state | Delta cursors per scope, last push and pull, clock offset, snapshot time | Sync 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).
- 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. - 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).
- Sampling only while the tour is
IN_PROGRESS: one sample everyfield.locationIntervalSeconds(300 s) orfield.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 (jobfield.autoSuspendOpenTours, proposal). Never before the tour starts, never after it ends. - Android mechanics. A foreground service of type
locationwith a permanent notification, started from the visible app; only the while in use precise permission (ACCESS_FINE_LOCATION) — noACCESS_BACKGROUND_LOCATION. Samples go tolocation_pingsand are sent in batches throughsync.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. - Retention.
field.locationRetentionDays(90 days) on the server; samples are deleted from the phone once acknowledged. - Mock locations are refused for check-in (
422 GPS_MOCK_LOCATION) and flagged on samples.
| Where | When | What the delegate sees |
|---|---|---|
| Android status bar | Sampling active | Permanent notification « Tournée en cours — position partagée avec HOCO toutes les 5 min »; tap → DA-06 |
| App bar of every tab root | Sampling active | Chip « Position partagée » with a pulsing dot; tap → a sheet explaining what is recorded and until when |
| Home tour card | IN_PROGRESS / SUSPENDED | « Localisation active depuis 08:02 » / « Position non partagée (tournée suspendue) » |
| DA-26 | Always | What 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).
| Topic | Rule |
|---|---|
| Contrast | Primary 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 |
| Status | Every status pill has an icon, a label and a colour — never colour alone (sun glare and colour blindness) |
| Type | Body text 16 sp minimum, secondary 14 sp, KPI numbers 24 sp or more, button labels 16 sp semibold |
| Targets | Every 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 zone | The 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 action | Each state of a screen has exactly one primary button (for example « Je suis arrivé » → « Commencer la visite » → « Terminer la visite ») |
| Text scaling | Layouts hold at 130 % system font size; at 200 % content scrolls and no primary action is clipped |
| Maps | Labels on solid plates; numbered markers at least 32 dp; high-contrast position dot; list alternative always one tap away |
| Feedback | Haptic plus visual confirmation for every recorded action (table below); no information conveyed only by animation |
| Brightness | The QR screen of DA-16 raises the screen brightness to the maximum while visible and restores it on exit |
| Offline clarity | Never an empty screen when cached data exists; a persistent banner says the app is offline and that actions are kept |
| Languages | French in V1; layouts RTL-ready for Arabic (start/end paddings, mirrored directional icons) (NFR-06) |
| Event | Haptic | Visual |
|---|---|---|
| Action recorded (start, check-in, visit end, prospect saved, closing) | HapticFeedback.mediumImpact | Success snackbar with the recorded time |
| Selection (chip, checkbox, quantity stepper) | HapticFeedback.selectionClick | State change |
| Blocked action or validation error | HapticFeedback.heavyImpact | Inline error or dialog that names the fix |
| Entering the GPS tolerance radius on the map | HapticFeedback.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.
| Key | Default | Effect in the app | Screens |
|---|---|---|---|
field.gpsToleranceMeters | 150 | Maximum distance between the fix and the point of sale for a check-in; radius drawn on the map | DA-08, DA-10 |
field.gpsMaxAccuracyMeters | 100 | A fix less precise than this is not used for a check-in or a prospect's position | DA-08, DA-10, DA-14 |
field.allowOutOfToleranceCheckIn | false | true: check-in outside the tolerance allowed with a written justification (10–200 characters), visit flagged; false: refused | DA-10 |
field.maxPhotosPerVisit · note length | 10 · 300 | Photo counter and limit; quick-note counter | DA-10 |
field.locationIntervalSeconds · field.locationDistanceMeters | 300 · 200 | Sampling period and distance while the tour is IN_PROGRESS | Sampler, DA-04, DA-26 |
field.locationRetentionDays | 90 | Shown in the consent notice and in the transparency page | DA-04, DA-26 |
field.closingRequiresSync | true | Closing only with an empty outbox, therefore online | DA-20 |
field.reportRequiredToEndVisit | false | Default of the visit type's report required flag | DA-10, DA-12 |
field.visitUnlockHours · field.reopenMaxDays | 24 · 7 | Correction window after a back-office unlock; reopening limit of a closed tour | DA-10, DA-20 |
sync.maxOfflineHours | 72 | Age limit of queued actions; warning from 48 h | DA-27 |
invitations.validityDays | 30 | « Lien valable 30 jours » | DA-16 |
orders.assistedConfirmationHours · orders.minAmount | 72 · 1000000 | Confirmation countdown; minimum-amount progress in the assisted order | DA-17, DA-18 |
pricing.priceDisplay | TTC | Which 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.
| Code | HTTP | Fallback message | Raised by |
|---|---|---|---|
INVALID_CREDENTIALS | 401 | « Numéro ou mot de passe incorrect. » | auth.login |
WRONG_APP | 403 | « Ce compte n'est pas un compte délégué. Utilisez l'application HOCO Revendeur. » | auth.login |
ACCOUNT_SUSPENDED | 403 | « Votre compte est suspendu. Contactez votre responsable. » (details.status = DISABLED: « Votre compte est désactivé. ») | Any call |
PASSWORD_CHANGE_REQUIRED | 403 | « Choisissez un nouveau mot de passe pour continuer. » | Any call while the password is temporary |
TOO_MANY_ATTEMPTS | 429 | « Trop de tentatives. Réessayez à 10:42. » | auth.login, auth.otp.verify |
UNAUTHENTICATED | 401 | « Votre session a expiré. Reconnectez-vous : vos données en attente sont conservées. » | Any call |
UPGRADE_REQUIRED | 426 | « Mettez l'application à jour pour continuer. » | Any call |
MAINTENANCE | 503 | « Maintenance en cours jusqu'à 14:00. Vous pouvez continuer hors ligne. » | Any call |
TOUR_TRANSITION_NOT_ALLOWED | 409 | « 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_TODAY | 409 | « Cette tournée n'est pas prévue aujourd'hui. » | delegate.tours.start |
TOUR_ALREADY_IN_PROGRESS | 409 | « Clôturez d'abord votre tournée du 21 mai. » | delegate.tours.start |
NOT_ASSIGNED_DELEGATE | 403 | « Cette tournée est affectée à un autre délégué. » | Tour and visit operations |
TOUR_NOT_IN_PROGRESS | 409 | « La tournée n'est pas en cours : démarrez-la ou reprenez-la d'abord. » | Visit operations |
TOUR_ORDER_IMPOSED | 409 | « L'ordre des visites est imposé par votre responsable. » | delegate.tours.reorder |
VISIT_IN_PROGRESS | 409 | « Terminez d'abord la visite en cours chez Electro Plus. » | delegate.visits.arrive, delegate.tours.suspend |
GPS_ACCURACY_TOO_LOW | 422 | « Position trop imprécise (± 180 m). Placez-vous à découvert et réessayez. » | delegate.visits.arrive |
GPS_OUT_OF_TOLERANCE | 422 | « Vous êtes à 420 m du point de vente (tolérance 150 m). » | delegate.visits.arrive |
GPS_MOCK_LOCATION | 422 | « Position simulée détectée : désactivez l'application de localisation fictive. » | delegate.visits.arrive |
VISIT_LOCKED | 409 | « Cette visite est terminée et verrouillée. » | Visit edits |
VISIT_NOT_CHECKED_IN · VISIT_TRANSITION_NOT_ALLOWED | 409 | « Enregistrez d'abord votre arrivée. » · « Action impossible dans l'état actuel de la visite. » | Visit operations |
VISIT_ALREADY_PLANNED | 409 | « Ce point de vente a déjà une visite ouverte dans la tournée. » | delegate.tours.addVisit |
PHOTO_LIMIT_REACHED | 422 | « Nombre maximal de photos atteint (10). » | delegate.visits.addPhoto |
VISIT_REQUIREMENTS_NOT_MET | 422 | details.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_REQUIRED | 422 | « Choisissez un motif. » | delegate.visits.notDone, delegate.tours.suspend |
TOUR_CLOSING_BLOCKED | 409 | « 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_EXISTS | 409 | « Ce numéro est déjà enregistré. » | delegate.resellers.create, delegate.resellers.update |
OUT_OF_SCOPE | 403 | « Ce point de vente ne fait pas partie de votre périmètre. » | Reseller and order operations |
RESELLER_TRANSITION_NOT_ALLOWED | 409 | « Ce changement de statut n'est pas autorisé. » | delegate.resellers.update |
FIELD_NOT_EDITABLE | 422 | details.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_ACCOUNT | 409 | « Ce revendeur a déjà un compte : rien à envoyer. » | delegate.invitations.create |
VERSION_CONFLICT | 409 | « Cette fiche a été modifiée entre-temps. » | Updates (shown as a conflict in DA-27) |
RESELLER_SUSPENDED | 403 | « Ce revendeur est suspendu : commande impossible. » | Assisted orders |
ASSISTED_ORDER_ALREADY_PENDING | 409 | « Une commande attend déjà la confirmation de ce revendeur. » | delegate.assisted.submit |
ORDER_MIN_AMOUNT_NOT_MET | 422 | « Montant minimum : 10 000,00 DA. » | delegate.assisted.submit |
STOCK_INSUFFICIENT · PRODUCT_UNAVAILABLE · QUANTITY_NOT_ALLOWED | 422 | « Quantité non disponible pour cet article. » · « Article indisponible. » · « Quantité non autorisée pour cet article. » — never a stock quantity | Assisted orders |
CREDIT_LIMIT_EXCEEDED | 422 | « Crédit disponible insuffisant : choisissez un autre mode de paiement. » | delegate.assisted.submit |
OCCURRED_AT_OUT_OF_RANGE | 422 | « Action enregistrée il y a plus de 72 h : contactez votre responsable. » | Queued actions |
IDEMPOTENCY_KEY_REUSED · IDEMPOTENCY_IN_PROGRESS | 409 | Never shown: logged to Sentry; in-progress is retried after 1 s | Any mutation |
Deep links and notification targets
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 ».
| Route | Screen |
|---|---|
/home | DA-05 |
/tour | DA-06 (today) |
/tours | DA-07 |
/tours/:tourId | DA-06 for that tour (read-only when not today's) |
/tours/:tourId/map | DA-08 |
/tours/:tourId/closing | DA-20 |
/visits/:visitId · /visits/:visitId/report | DA-10 · DA-12 (DA-11 is a sheet over DA-10) |
/resellers · /resellers/new · /resellers/:resellerId · /resellers/:resellerId/edit | DA-13 · DA-14 · DA-09 · DA-15 |
/resellers/:resellerId/assisted-order | DA-17 (new draft or the open one) |
/invitations/:invitationId | DA-16 |
/orders · /orders/:orderId | DA-18 |
/tasks | DA-19 |
/reports · /objectives | DA-21 · DA-22 |
/messages · /messages/:announcementId | DA-23 |
/account · /account/zone · /account/settings · /sync | DA-24 · DA-25 · DA-26 · DA-27 |
Notifications received by the delegate (from the notifications matrix, which owns the wording):
| Event | Channels | Opens |
|---|---|---|
tour.assigned | Push, 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.reopened | Push, in-app, sync data message | /tours/:tourId/closing |
task.due (08:00 digest) · task.assigned | Push, in-app | /tasks |
invitation.step_completed | In-app for every step; push for ACCOUNT_CREATED and FIRST_ORDER | /invitations/:invitationId |
assisted_order.confirmed · assisted_order.rejected · assisted_order.expired.delegate | Push, in-app | /orders/:orderId |
order.submitted.delegate · order.refused.delegate | Push, in-app | /orders/:orderId |
order.validated.delegate · order.modified.delegate · order.status_changed.delegate · order.cancelled.delegate | In-app | /orders/:orderId |
account.approved.delegate · account.rejected.delegate | Push, in-app | /resellers/:resellerId |
account.suspended.delegate | In-app | /resellers/:resellerId |
promotion.started.delegate | In-app | /messages |
announcement.published | In-app, push when requested | /messages/:announcementId |
| Local reminder: oldest pending item older than 48 h | Local 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
| Reference | Observation | Decision |
|---|---|---|
delegate-03 vs the others | Two bottom bars | Variant 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 status | Visit rows use VisitStatus labels only; « En attente » means a pending sync |
delegate-02-home | Quick 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-profile | Sign-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-program | Tabs À visiter · Visitées · Prospects | Tabs À 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-signup | The 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 Oran | Only 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-invitation | Link app.delegate.com/inviter/AB12CD, code AB12-CD34-EF56, four steps | https://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) |
| All | Dates in 2024, sample names | Placeholder data |
API surface
| Method | Path | Operation | Access | Apps | Release |
|---|---|---|---|---|---|
| POST | /auth/login | auth.loginSign in with phone (or email for staff) and password; returns a session token | public | RA · RW · DA · BO | V1 |
| POST | /auth/logout | auth.logoutRevoke the current session and unregister the device push token | session | RA · RW · DA · BO | V1 |
| GET | /auth/session | auth.sessionCurrent session and its expiry | session | RA · RW · DA · BO | V1 |
| POST | /auth/otp/send | auth.otp.sendSend a one-time code by SMS for sign-up, password reset or phone change | public | RA · RW · DA · BO | V1 |
| POST | /auth/otp/verify | auth.otp.verifyVerify a one-time code; returns a short-lived verification token | public | RA · RW · DA · BO | V1 |
| POST | /auth/password/reset | auth.password.resetSet a new password using a verification token | public | RA · RW · DA · BO | V1 |
| POST | /auth/password/change | auth.password.changeChange password (current password required); other sessions revoked | session | RA · RW · DA · BO | V1 |
| GET | /auth/sessions | auth.sessions.listList my active sessions (device, app, last activity) | session | RA · RW · DA · BO | V1 |
| DELETE | /auth/sessions/{sessionId} | auth.sessions.revokeRevoke one of my sessions | session | RA · RW · DA · BO | V1 |
| GET | /me | me.getMy profile, roles, permissions, scope (reseller or delegate context) and account status | session | RA · RW · DA · BO | V1 |
| PATCH | /me | me.updateUpdate my name, locale or avatar | session | RA · RW · DA · BO | V1 |
| POST | /me/devices | me.devices.registerRegister or refresh this device's FCM token and app version | sessionIdempotency-Key | RA · DA | V1 |
| DELETE | /me/devices/{deviceId} | me.devices.removeUnregister a device | session | RA · DA | V1 |
| GET | /app-config | appConfig.getVersion policy (min/latest/forced), maintenance flag and public business settings for an app/platform | public | RA · RW · DA | V1 |
| GET | /reference/wilayas | reference.wilayasWilayas (code, FR/AR names); cacheable with ETag | public | RA · RW · DA · BO | V1 |
| GET | /reference/wilayas/{wilayaCode}/communes | reference.communesCommunes of a wilaya; cacheable with ETag | public | RA · RW · DA · BO | V1 |
| GET | /reference/lists | reference.listsReference lists (business types, potentials, visit types, not-done reasons, issue tags, ticket types, payment methods); cacheable | session | RA · RW · DA · BO | V1 |
| POST | /files/uploads | files.createUploadRequest a pre-signed upload URL for a photo or document | sessionIdempotency-Key | RA · RW · DA · BO | V1 |
| POST | /files/{fileId}/complete | files.completeUploadConfirm an upload; the server checks size/type and creates thumbnails | sessionIdempotency-Key | RA · RW · DA · BO | V1 |
| GET | /files/{fileId}/url | files.downloadUrlShort-lived signed URL for a private file the caller may access | session | RA · RW · DA · BO | V1 |
| GET | /catalog/brands | catalog.brandsActive brands — guests allowed, without prices | guest or session | RA · RW · DA | V1 |
| GET | /catalog/categories | catalog.categoriesCategory tree with product counts visible to the caller — guests allowed, without prices | guest or session | RA · RW · DA | V1 |
| GET | /catalog/products | catalog.products.listSearch, filter and sort products with the caller's resolved price and availability (cursor pagination) — guests allowed, without prices | guest or session | RA · RW · DA | V1 |
| GET | /catalog/products/suggest | catalog.products.suggestType-ahead suggestions (products, brands, categories) — guests allowed, without prices | guest or session | RA · RW · DA | V1 |
| GET | /catalog/facets | catalog.facetsFilter facets with counts for the current query: categories and sub-categories, brands, price range, attributes, availability, promotion — guests allowed (no price facet) | guest or session | RA · RW · DA | V1 |
| GET | /catalog/products/by-barcode/{barcode} | catalog.products.byBarcodeFind a product variant by barcode — guests allowed, without prices | guest or session | RA · DA | V1 |
| GET | /catalog/products/{productId} | catalog.products.getProduct detail: variants, gallery, specs, prices, availability, quantity limits, active promotion — guests allowed, without prices | guest or session | RA · RW · DA | V1 |
| GET | /promotions | promotions.listPromotions and daily offers available to the caller — guests allowed, without prices | guest or session | RA · RW · DA | V1 |
| GET | /promotions/{promotionId} | promotions.getPromotion detail: conditions, dates, remaining stock cap, eligible products — guests allowed, without prices | guest or session | RA · RW · DA | V1 |
| GET | /notifications | notifications.listMy notification inbox (cursor pagination, unread filter) | session | RA · RW · DA · BO | V1 |
| GET | /notifications/unread-count | notifications.unreadCountUnread badge count | session | RA · RW · DA · BO | V1 |
| POST | /notifications/{notificationId}/read | notifications.markReadMark one notification as read | session | RA · RW · DA · BO | V1 |
| POST | /notifications/read-all | notifications.markAllReadMark all as read | session | RA · RW · DA · BO | V1 |
| GET | /events | events.streamServer-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 | session | RA · RW · DA | V1 |
| GET | /notification-preferences | notifications.preferences.getMy channel preferences per category | session | RA · RW · DA | V1 |
| PUT | /notification-preferences | notifications.preferences.updateUpdate my preferences (transactional ones cannot be disabled) | session | RA · RW · DA | V1 |
| GET | /announcements | announcements.listAnnouncements targeted to me ('Messages') | session | RA · DA | V1 |
| GET | /announcements/{announcementId} | announcements.getOne announcement targeted to me (opened from a notification) | self | RA · RW · DA | V1 |
| POST | /announcements/{announcementId}/read | announcements.markReadAcknowledge an announcement | session | RA · DA | V1 |
| GET | /delegate/home | delegate.homeHome aggregate: zone, today's tour and counters, objectives, conversion, unread counts | delegate | DA | V1 |
| GET | /delegate/profile | delegate.profileMy delegate profile, zones, wilayas and communes covered | delegate | DA | V1 |
| GET | /delegate/zone | delegate.zoneMy zone geometry hints and point-of-sale counts per commune | delegate | DA | V1 |
| GET | /delegate/objectives | delegate.objectivesObjectives and achievement for a period (day, week, month) | delegate | DA | V1 |
| GET | /delegate/performance | delegate.performanceMy performance over a period: visits, sign-ups, installs, first orders, revenue, km, conversion | delegate | DA | V1 |
| GET | /delegate/reports | delegate.reportsHistory of my visit reports and tour reports | delegate | DA | V1 |
| GET | /delegate/tours | delegate.tours.listMy tours in a date range (calendar and history) | delegate | DA | V1 |
| GET | /delegate/tours/today | delegate.tours.todayToday's tour with ordered visits, points of sale, distances and statuses (offline snapshot) | delegate | DA | V1 |
| GET | /delegate/tours/{tourId} | delegate.tours.getA tour with its visits | delegate | DA | V1 |
| POST | /delegate/tours/{tourId}/start | delegate.tours.startStart the tour (time and position recorded) | delegateIdempotency-Key | DA | V1 |
| POST | /delegate/tours/{tourId}/suspend | delegate.tours.suspendSuspend the tour with a reason | delegateIdempotency-Key | DA | V1 |
| POST | /delegate/tours/{tourId}/resume | delegate.tours.resumeResume a suspended tour | delegateIdempotency-Key | DA | V1 |
| PUT | /delegate/tours/{tourId}/visit-order | delegate.tours.reorderReorder remaining visits when the tour's order is 'recommended' (not 'imposed') | delegateIdempotency-Key | DA | V1 |
| POST | /delegate/tours/{tourId}/visits | delegate.tours.addVisitAdd an unplanned visit (client-generated id) | delegateIdempotency-Key | DA | V1 |
| GET | /delegate/tours/{tourId}/closing-preview | delegate.tours.closingPreviewDay summary and blocking items (unvisited stops without reason, unsynced data) | delegate | DA | V1 |
| POST | /delegate/tours/{tourId}/close | delegate.tours.closeClose the tour with an end-of-day comment; returns the final report | delegateIdempotency-Key | DA | V1 |
| GET | /delegate/visits/{visitId} | delegate.visits.getVisit detail: point of sale, checklist template, report config, current state | delegate | DA | V1 |
| POST | /delegate/visits/{visitId}/arrive | delegate.visits.arriveCheck-in: position and time; returns distance and whether the GPS tolerance is met | delegateIdempotency-Key | DA | V1 |
| POST | /delegate/visits/{visitId}/start | delegate.visits.startStart the visit timer | delegateIdempotency-Key | DA | V1 |
| PUT | /delegate/visits/{visitId}/checklist | delegate.visits.checklistSave the checklist state | delegateIdempotency-Key | DA | V1 |
| PUT | /delegate/visits/{visitId}/note | delegate.visits.noteSave the quick note (≤ 300 chars) | delegateIdempotency-Key | DA | V1 |
| POST | /delegate/visits/{visitId}/photos | delegate.visits.addPhotoAttach an uploaded photo with its capture position and time | delegateIdempotency-Key | DA | V1 |
| PUT | /delegate/visits/{visitId}/report | delegate.visits.reportSave the structured visit report; creates or updates the follow-up task | delegateIdempotency-Key | DA | V1 |
| POST | /delegate/visits/{visitId}/end | delegate.visits.endFinish the visit (requires report/photos when the visit type demands them); the visit locks | delegateIdempotency-Key | DA | V1 |
| POST | /delegate/visits/{visitId}/not-done | delegate.visits.notDoneMark the visit as not carried out with a reason | delegateIdempotency-Key | DA | V1 |
| GET | /delegate/resellers | delegate.resellers.listPoints of sale and prospects in my scope: search, status filter, distance sort | delegate | DA | V1 |
| GET | /delegate/resellers/check-phone | delegate.resellers.checkPhoneDuplicate check before creating a prospect | delegate | DA | V1 |
| POST | /delegate/resellers | delegate.resellers.createCreate a prospect/reseller in the field (client-generated id; offline-safe) | delegateIdempotency-Key | DA | V1 |
| GET | /delegate/resellers/{resellerId} | delegate.resellers.getPoint-of-sale sheet: details, status, recent visits, orders summary, open tasks, photos | delegate | DA | V1 |
| PATCH | /delegate/resellers/{resellerId} | delegate.resellers.updateUpdate a prospect/reseller in my scope (status changes per allowed transitions) | delegateIdempotency-Key | DA | V1 |
| POST | /delegate/resellers/{resellerId}/notes | delegate.resellers.addNoteAdd a note or call log to the interaction timeline | delegateIdempotency-Key | DA | V1 |
| POST | /delegate/resellers/{resellerId}/photos | delegate.resellers.addPhotoAttach a façade/interior photo | delegateIdempotency-Key | DA | V1 |
| POST | /delegate/resellers/{resellerId}/invitations | delegate.invitations.createCreate (or return the active) invitation: code, link, QR payload, share texts | delegateIdempotency-Key | DA | V1 |
| GET | /delegate/invitations/{invitationId} | delegate.invitations.getInvitation with its activation steps | delegate | DA | V1 |
| POST | /delegate/invitations/{invitationId}/shared | delegate.invitations.markSharedRecord that the invitation was shared and through which channel | delegateIdempotency-Key | DA | V1 |
| GET | /delegate/tasks | delegate.tasks.listMy follow-ups and tasks (due today, overdue, upcoming) | delegate | DA | V1 |
| PATCH | /delegate/tasks/{taskId} | delegate.tasks.updateReschedule or annotate a task | delegateIdempotency-Key | DA | V1 |
| POST | /delegate/tasks/{taskId}/complete | delegate.tasks.completeComplete a task with an outcome | delegateIdempotency-Key | DA | V1 |
| POST | /delegate/assisted-orders | delegate.assisted.createStart an assisted order for a reseller in my scope (draft, priced) | delegateIdempotency-Key | DA | V1 |
| PATCH | /delegate/assisted-orders/{orderId} | delegate.assisted.updateEdit draft lines, address and note; returns re-priced totals and issues | delegateIdempotency-Key | DA | V1 |
| POST | /delegate/assisted-orders/{orderId}/submit | delegate.assisted.submitSend for confirmation (reseller in app, or administration); same controls as a normal order | delegateIdempotency-Key | DA | V1 |
| DELETE | /delegate/assisted-orders/{orderId} | delegate.assisted.discardDiscard a draft assisted order | delegate | DA | V1 |
| GET | /delegate/orders | delegate.orders.listOrders of resellers in my scope (assisted and direct), read-only | delegate | DA | V1 |
| GET | /delegate/orders/{orderId} | delegate.orders.getOrder detail (read-only, no purchase prices) | delegate | DA | V1 |
| POST | /sync/batch | sync.batchReplay up to 50 queued offline mutations in order; per-item results with idempotent replays | delegate | DA | V1 |
| GET | /sync/delta | sync.deltaChanges since a cursor for the delegate's offline scopes (tours, visits, resellers, tasks, reference lists) | delegate | DA | V1 |
| POST | /delegate/location-pings | sync.locationPingsBatch of position samples during an active tour (policy-driven frequency) | delegateIdempotency-Key | DA | V1 |
Screen index
| ID | Screen | UI title | Section | Release | Design | Build |
|---|---|---|---|---|---|---|
DA-01 | Splash & version check | Démarrage | Access | V1 | Day 11 | MOB-05 |
DA-02 | Sign in | Connexion | Access | V1 | Day 11 | MOB-06 |
DA-03 | Forgot password | Mot de passe oublié | Access | V1 | Day 11 | MOB-06 |
DA-04 | Permissions & location consent | Autorisations et consentement | Access | V1 | Day 11 | MOB-10 |
DA-05 | Home | Accueil délégué | Day | V1 | Day 11 | MDA-01 |
DA-06 | Tour programme | Programme de tournée | Tour | V1 | Day 12 | MDA-02 |
DA-07 | Tours calendar & history | Mes tournées | Tour | V1 | Day 12 | MDA-02 |
DA-08 | Map & navigation | Carte et navigation | Tour | V1 | Day 12 | MDA-03 |
DA-09 | Point-of-sale sheet | Fiche point de vente | Tour | V1 | Day 12 | MDA-04 |
DA-10 | Visit & check-in | Détail de la visite | Visit | V1 | Day 13 | MDA-05 |
DA-11 | Visit not done — reason | Visite non réalisée | Visit | V1 | Day 13 | MDA-05 |
DA-12 | Visit report | Rapport de visite | Visit | V1 | Day 13 | MDA-06 |
DA-13 | Prospects & resellers (list / map) | Prospects | Prospects | V1 | Day 13 | MDA-04 |
DA-14 | New reseller | Nouveau revendeur | Prospects | V1 | Day 14 | MDA-07 |
DA-15 | Edit reseller / prospect | Modifier la fiche | Prospects | V1 | Day 14 | MDA-07 |
DA-16 | Invite a reseller | Inviter un revendeur | Conversion | V1 | Day 14 | MDA-08 |
DA-17 | First-order assistance | Aide à la 1ère commande | Conversion | V1 | Day 14 | MDA-09 |
DA-18 | Assisted orders & orders of my resellers | Commandes | Conversion | V1 | Day 14 | MDA-09 |
DA-19 | Follow-ups & tasks | Relances | Prospects | V1 | Day 13 | MDA-06 |
DA-20 | Tour closing & results | Clôture de tournée | Day | V1 | Day 14 | MDA-10 |
DA-21 | Reports & performance | Rapports | Day | V1 | Day 14 | MDA-10 |
DA-22 | Objectives | Mes objectifs | Profile | V1 | Day 11 | MDA-01 |
DA-23 | Messages & notifications | Messages | Profile | V1 | Day 14 | MDA-11 |
DA-24 | Profile hub | Compte | Profile | V1 | Day 11 | MDA-01 |
DA-25 | My zone | Ma zone | Profile | V1 | Day 11 | MDA-01 |
DA-26 | Settings | Paramètres | Profile | V1 | Day 11 | MDA-11 |
DA-27 | Sync centre | Synchronisation | System | V1 | Day 14 | MDA-11 |
DA-28 | System states (offline, GPS off, out of tolerance, permission denied, forced update) | États système | System | V1 | Day 14 | MOB-10 |
System, returns, guest mode & V2
Screen specifications RA-36 to RA-41 of the reseller app — system states, the V1 return request with its tracking and credit note, guest mode with the price lock, and the V2 packs, loyalty and rewards screens.
Access
Splash and version check, sign-in, password reset and the permissions and location-consent gate of the delegate app (DA-01 to DA-04).