HOCO B2B
DesignScreen specificationsDelegate app

Visits

Point-of-sale sheet, visit and GPS check-in, visit not done, structured visit report and follow-ups of the delegate app (DA-09 to DA-12, DA-19).

A visit is one stop of a tournée (tour): the délégué arrives within the GPS tolerance, runs the visit (checklist, quick note, photos), writes the structured report that plans the follow-up, and ends it — or records why it could not happen (cahier §6.4, §6.8). Everything here works offline and is replayed in order (see recording actions).

Visit lifecycle seen from the app

ValueUI label (fr)Meaning
PLANNEDÀ visiter
CHECKED_INArrivéArrival recorded (position + time).
IN_PROGRESSEn cours
DONEVisitéFinished; locked unless unlocked by permission.
NOT_DONENon réaliséeNot carried out — a reason is mandatory.
CANCELLEDAnnulée

'Late' (En retard) is derived: PLANNED and its time slot has passed.

A DONE visit is locked. The back-office can unlock it for correction (admin.visits.unlock, BO-25) for field.visitUnlockHours (24 h); the next « Terminer la visite » locks it again. Only one visit of a tour can be CHECKED_IN or IN_PROGRESS at a time.

DA-09 — Point-of-sale sheet

FieldValue
French title« Fiche point de vente »
ReleaseV1
Design dayDay 12 — DES-12
Build taskMDA-04 — point-of-sale sheet & prospects list
RequirementsCRM-01 · CRM-07 · CRM-08
Reference screenNone — the store header of delegate-05-visit-checkin gives the visual language

Purpose. Everything the delegate needs about a store before and during a visit: identity, contacts, status in the commercial lifecycle, customer type, position, photos, recent visits, orders summary, open follow-ups and the interaction history — plus the actions that move the store forward (CRM-01, CRM-07, CRM-08).

Entry points & exits. Entry: a row of DA-13, « Voir la fiche » on DA-10 and DA-12, a follow-up of DA-19, DA-14 after saving, notifications account.approved.delegate, account.rejected.delegate, account.suspended.delegate (/resellers/:resellerId). Exits: DA-15 (« Modifier »), DA-16 (« Inviter »), DA-17 (« Commande assistée »), DA-10 (« Démarrer une visite » / today's visit), DA-18 (orders), DA-08 (Itinéraire), dialer and WhatsApp.

Layout (top to bottom).

  1. Header — façade photo (16:9, tap → full screen), store name, number REV-000123 (or « N° attribué après synchronisation »), ResellerStatus pill, validation pill when not approved (« En attente de validation » / « Refusé »), customer-type chip (« Demi-gros », with « proposé » while not confirmed), sync badge.
  2. Contact rows — Responsable (owner name), Téléphone with call button, WhatsApp with WhatsApp button, Adresse with landmark (→ external map), Commune, wilaya.
  3. Action bar (horizontal, 4 large buttons) — Visite · Inviter · Commande · Note. Buttons the caller may not use are hidden, not disabled (rules).
  4. Infos commerciales — type of business, potential, assigned delegate, created by and source, position status (« Position validée », « Position proposée, en attente de validation », « Position inconnue »).
  5. Relances — open follow-ups with due date and « Fait ».
  6. Dernières visites — last 5 with date, delegate, interest level, next action (→ report read-only).
  7. Commandes — count, last order date, total of the last 90 days (TTC); « Voir les commandes » (→ DA-18 filtered).
  8. Invitation — current funnel step and code (→ DA-16).
  9. Historique — the interaction timeline (calls, visits, notes, orders, payments, claims, status changes), 10 latest, « Voir plus ».
  10. Photos — grid with kind labels; « Ajouter une photo ».

Data.

FieldAPI sourceOffline sourceFreshness
Identity and contactsdelegate.resellers.get → storeName, number, ownerName, phone, whatsapp, email, addressLine, landmark, communeName, wilayaNameresellers (card columns + cached sheet)Delta for the card; sheet on open, cached for today's stops by delegate.tours.today → pointsOfSale[]
Statusesstatus, validationStatus, accountStatus, hasAccountresellersDelta
Customer typecustomerType, customerTypeConfirmedresellersDelta
Positionlocation, locationSource, locationCandidateresellersDelta
Façade and photosfacadePhoto, photos[] (signed URLs, 5 min)Image files cached by file id; local photos from upload_queueOn open (downloaded at once — the URLs expire)
Commercial infosbusinessTypeCode (label from BUSINESS_TYPE), potential, assignedDelegate, createdBy, source, zoneresellers, reference_itemsDelta
Follow-upsopenTasks[] (type, title, dueAt, isOverdue)tasksDelta
Last visitslastVisits[]resellers sheet cache; local visits of todayOn open
Orders summaryordersSummary.count, .lastOrderAt, .totalTtc90dSheet cacheOn open
Invitationinvitation.displayCode, .currentStep, .activeinvitationsOn open
TimelinerecentInteractions[] (type, body, occurredAt, author, ref)Sheet cache + local notes not yet sentOn open + live-local
What the caller may doallowedStatusTransitions[], editableFields[], scopeClauseSheet cacheOn open

Actions.

  • Appeler → Android dialer (tel:), no call permission needed. On return, a snackbar proposes « Noter l'appel » → note of type CALL with optional duration.
  • WhatsApp → https://wa.me/<number without +>.
  • Note → sheet (type Note, Appel, WhatsApp, SMS; text 1–2000) → delegate.resellers.addNote queued with a client UUIDv7 id and occurredAt; shown at once in the timeline with the pending badge.
  • Ajouter une photo → camera (gallery allowed for interior and shelf, not for the façade) → kind → upload queue → delegate.resellers.addPhoto queued after the upload (id, fileId, kind, takenAt, location, occurredAt).
  • Visite → if the store is a stop of today's tour: its DA-10; otherwise « Ajouter à ma tournée du jour » (delegate.tours.addVisit, as on DA-06) when a tour is IN_PROGRESS.
  • Inviter → DA-16. Commande → DA-17. Modifier (app-bar icon) → DA-15.
  • Fait on a follow-up → completion sheet of DA-19.

Rules.

  • Scope (ACL-03): stores assigned to the delegate, unassigned prospects of the delegate's zones, and stops of the delegate's open tours. A store reached only as a tour stop (scopeClause = TOUR_STOP) is read-only except visit, notes and photos — Modifier, Inviter and Commande are hidden.
  • Inviter is hidden when the store already has an account (hasAccount) or is SUSPENDED; Commande is hidden for SUSPENDED stores and shows « Commande assistée » (not 1ère commande) when the store already ordered.
  • Never shown: purchase prices, margins, credit limits, stock quantities (ACL-04, §1b).
  • The status pill uses ResellerStatus labels; « En attente » is never used for a status (reserved for sync).

States.

StateUI
Not cached and offline« Fiche non disponible hors ligne » with the card data (name, phone, address) when known
Created on this phone, not syncedHeader badge « En attente »; number line « N° attribué après synchronisation »; Inviter and Commande show « Disponible après synchronisation »
Reassigned to another delegate403 OUT_OF_SCOPE → « Ce point de vente ne fait plus partie de votre périmètre. », record removed from the list
Unknown (or never in scope)404 NOT_FOUND → « Point de vente introuvable »
Validation rejectedDanger banner with the reason from the account.rejected.delegate notification
Suspended storeDanger banner « Revendeur suspendu : commandes bloquées »

Copy. « Fiche point de vente » · « Responsable » · « Téléphone » · « Adresse » · « Infos commerciales » · « Relances » · « Dernières visites » · « Commandes » · « Historique » · « Photos » · « Ajouter une photo » · « Noter l'appel » · « Position validée » · « Position proposée, en attente de validation » · « N° attribué après synchronisation ».

Acceptance criteria.

  1. The sheet of every stop of today's tour opens in airplane mode with its contacts, last visits and open follow-ups.
  2. A note added offline appears at once in the timeline and in the back-office reseller timeline after sync.
  3. A store reached only as a tour stop shows no Modifier, Inviter or Commande.
  4. No screen of the sheet shows a stock quantity, a purchase price or a credit figure.

DA-10 — Visit & check-in

FieldValue
French title« Détail de la visite »
ReleaseV1
Design dayDay 13 — DES-13
Build taskMDA-05 — visit flow
RequirementsFLD-08 · GEO-03
Reference screendelegate-05-visit-checkin — shown below
Client reference — store header, GPS validation, arrival time, running duration, checklist, quick note and photos
Client reference — store header, GPS validation, arrival time, running duration, checklist, quick note and photos — client reference (cahier des charges)

Purpose. Record the arrival (time and position, within the GPS tolerance), the duration, the commercial checklist, a quick note and photo proofs of the visit, and end it — locked from then on (cahier §6.4, FLD-08, GEO-03).

Entry points & exits. Entry: « Visiter » / « Voir » and rows of DA-06, stops of DA-05, « Arrivé » on DA-08, the active-visit bar, DA-01 when resuming, DA-20 (visit not ended), notification of an unlock. Exits: DA-12 (report), DA-11 (not done), DA-09 (« Voir la fiche »), DA-17 (checklist item Aider à la 1ère commande), DA-08 (Itinéraire), back to DA-06 after « Terminer la visite ».

Layout (top to bottom).

  1. App bar — back, « Détail de la visite », bell, overflow: Visite non réalisée, Itinéraire, Voir la fiche.
  2. Store header — façade thumbnail, store name, commune, ResellerStatus pill, visit number and type chip (« Prospection »), requirement chips (« Photo obligatoire », « Rapport obligatoire »), sync badge.
  3. Contact rows — Responsable, Téléphone (call icon), Adresse (chevron → external map).
  4. GPS validation card — icon, « Validation GPS » and one state: « Position confirmée · à 38 m » (success), « Hors tolérance · justifiée » (warning), « Position du magasin inconnue — proposée à HOCO » (info), « Recherche d'une position précise… ± 140 m » (progress), « Localisation désactivée » (danger, « Activer »), « Non vérifiée » before arrival.
  5. Time cards ×2 — « Arrivée sur site » (« 09:12 », date) and « Durée de la visite » (live « 00:23 », « En cours » / « Terminée »).
  6. Checklist « Checklist de la visite » with the counter « 2/5 »; one 56 dp row per item with an icon and a checkbox; required items marked « obligatoire ».
  7. Note rapide (optional), counter « 0/300 ».
  8. Photos de la visite — label from the visit type: « (obligatoire — 1 minimum) » or « (optionnel) »; thumbnails with upload state (spinner, check, error); tile « Ajouter une photo »; counter « 1/10 ».
  9. Rapport de visite row → DA-12 with its state: « À remplir — obligatoire », « À remplir », « Enregistré à 09:40 ».
  10. Sticky action bar — one primary button by state: PLANNED → « Je suis arrivé » (secondary text button « Visite non réalisée »); CHECKED_IN → « Commencer la visite »; IN_PROGRESS → « Terminer la visite » (disabled with the missing items listed under it, e.g. « Ajoutez 1 photo et le rapport pour terminer »); DONE → no button, line « Visite terminée à 09:47 — verrouillée ». The reference shows Commencer and Terminer side by side; one state-driven button prevents ending a visit that never started.

Data.

FieldAPI sourceOffline sourceFreshness
Store header and contactsdelegate.visits.get → reseller.storeName, .communeName, .status, .ownerName, .phone, .addressLine, .facadeThumbnailUrlresellers + cached façade imageDay snapshot
Visit identitynumber, sequence, status, isUnplanned, tour.statusvisits, toursLive-local
Visit type and requirementsvisitType.name, .requiresPhoto, .minPhotos, .requiresReport, .checklistTemplateId, .reportFields[]visit_typesDay snapshot
GPS settingsdelegate.tours.today → fieldSettings.gpsToleranceMeters, .gpsMaxAccuracyMeters, .allowOutOfToleranceCheckIn, .maxPhotosPerVisit, .noteMaxLengthapp_configDay snapshot
Check-incheckIn.checkedInAt, .distanceMeters, .accuracy, .gpsCheck, .gpsValidated, .justification, .posLocationCandidatevisits (local verdict until the server's arrives)Live-local
DurationstartedAt, endedAt, durationSecondsvisits; timer computed from startedAtLive-local
Checklistchecklist.items[] (key, label, required, checked), checkedCount, totalvisit_checklist_items (template from the snapshot)Live-local
Notenote.textvisits.noteLive-local
Photosphotos[] (thumbnailUrl, takenAt, kind)visit_photos + upload_queueLive-local
Report statereport (null or saved)visit_reportsLive-local
Locklocked, lockedAt, unlockedUntil, allowedActions[], missingToEnd[]visitsLive-local + delta (unlock)

While the outbox holds items for the visit, the local row is authoritative; server reads (delegate.visits.get on open when online) only fill fields with no pending item.

Actions.

  • Je suis arrivé → check-in algorithm → delegate.visits.arrive queued. Body { occurredAt, location: { lat, lng, accuracy, capturedAt }, isMockLocation, justification }. One transaction: visit CHECKED_IN, checked_in_at = occurredAt, position, distance, local verdict; outbox item (Idempotency-Key = item id — a double tap or a lost answer never creates two check-ins). Feedback: mediumImpact, snackbar « Arrivée enregistrée à 09:12 », the active-visit bar appears. If the tour is TO_START, the sheet « Démarrer la tournée et enregistrer l'arrivée ? » writes the start and the check-in as two ordered items.
  • Commencer la visite → delegate.visits.start queued, { occurredAt }; the timer starts at occurredAt.
  • Checklist tick → local state; delegate.visits.checklist queued with the full state { items: [{ key, checked, checkedAt }], occurredAt }; compacted (a still-queued item is replaced, with a new key). selectionClick haptic.
  • Note rapide → saved 1 s after typing stops; delegate.visits.note queued, compacted; editing on a CHECKED_IN visit starts it implicitly (server rule).
  • Ajouter une photo → camera only (gallery disabled: a visit photo is a proof); the image is resized to 1600 px on the long side, JPEG quality 80, EXIF stripped, kept in app storage; position and time of the capture recorded. A visit_photos row (client UUIDv7) and an upload_queue entry are written; the upload runs through files.createUpload → PUT to the pre-signed URL → files.completeUpload, then delegate.visits.addPhoto { id, fileId, kind, takenAt, location, caption, occurredAt } is released from the outbox. Deleting a photo is possible only before its upload starts.
  • Rapport → DA-12.
  • Terminer la visite → client check of missingToEnd (report if required, minPhotos photos, required checklist items) → visit DONE and locked locally; delegate.visits.end queued { occurredAt, location }, held until the visit's photos and report items are applied. Feedback: mediumImpact, snackbar « Visite terminée — 35 min », return to DA-06 with the next stop highlighted; counters move at once.
  • Visite non réalisée → DA-11. Appeler → dialer. Itinéraire → external navigation.

Check-in tolerance algorithm

The app runs the same steps as the server (delegate.visits.arrive) so that the verdict is known offline; the server's verdict is authoritative when the item is replayed.

  1. Guards. Tour IN_PROGRESS (TO_START → offer to start it; SUSPENDED → offer to resume); no other visit of the tour CHECKED_IN or IN_PROGRESS (« Terminez d'abord la visite chez Electro Plus », server 409 VISIT_IN_PROGRESS); visit PLANNED or NOT_DONE; in IMPOSED order, the first remaining stop.
  2. Fix acquisition. High-accuracy position stream for at most 30 s; keep the fix with the smallest accuracy; stop early as soon as a fix is at 30 m or better. A last known position is reused only if it is 30 s old or less and passes the gates. The card shows the live accuracy (« ± 140 m »).
  3. Mock gate. A fix flagged as mock (isMocked) is refused: « Position simulée détectée : désactivez l'application de localisation fictive. » (422 GPS_MOCK_LOCATION).
  4. Accuracy gate. accuracy ≤ field.gpsMaxAccuracyMeters (100 m).
  5. Reference point. The store's stored location. When it has none, the check-in is accepted on the accuracy gate alone (gpsCheck = NO_REFERENCE_LOCATION) and the position is proposed to the back-office as the store's position (validated on BO-25) — the delegate never sets the reference used by later check-ins.
  6. Distance. Haversine, Earth radius 6 371 000 m, rounded to the metre. Shown as « à 38 m » below 1 km, « à 1,2 km » above.
  7. Tolerance. distance ≤ field.gpsToleranceMeters (150 m) → « Position confirmée ». Otherwise the out-of-tolerance dialog, by field.allowOutOfToleranceCheckIn (default false):
SettingDialogButtonsResult
falseTitle « Arrivée impossible » · « Vous êtes à 420 m du point de vente (tolérance 150 m). Rapprochez-vous de l'entrée du magasin. » (accuracy failures: « Position trop imprécise (± 180 m). Placez-vous à découvert et réessayez. »)« Réessayer » (primary) · « Itinéraire » · « Visite non réalisée »Nothing recorded on the visit; the attempt is audited by the server only if sent
trueTitle « Vous êtes hors de la zone du magasin » · same distance line · required field « Justification » (10–200 characters, placeholder « Ex. : magasin déplacé dans la galerie, 200 m plus loin »)« Réessayer » · « Confirmer l'arrivée » (enabled once the justification is valid) · « Annuler »Check-in with justification; gpsCheck = OUT_OF_TOLERANCE_JUSTIFIED; card « Hors tolérance · justifiée »; the visit is flagged in the back-office list

A queued check-in the server refuses later (the store's position changed in between, setting switched to false) becomes an Erreur item in DA-27; its dependants (start, checklist, photos, report, end) stay on the phone, nothing is deleted, and the message tells the delegate to call the supervisor, who settles the visit in BO-25.

Rules.

  • « L'heure et la position sont enregistrées au check-in » (cahier §6.4): checkedInAt = occurredAt of the tap; the server keeps receivedAt too.
  • Photos may be mandatory per visit type (cahier §6.4): visitType.requiresPhoto and minPhotos; at most field.maxPhotosPerVisit (10) photos (422 PHOTO_LIMIT_REACHED).
  • The report may be mandatory before ending (cahier §6.8): visitType.requiresReport (default field.reportRequiredToEndVisit). Required checklist items must be ticked. The server answers 422 VISIT_REQUIREMENTS_NOT_MET with details.missing (REPORT_REQUIRED, PHOTO_REQUIRED, CHECKLIST_INCOMPLETE) if the app's check was bypassed.
  • A finished visit cannot be modified without permission (cahier §6.4): after « Terminer la visite » every field is read-only and any queued edit answers 409 VISIT_LOCKED. When the back-office unlocks it (« Visite déverrouillée pour correction jusqu'à 10:00 demain »), checklist, note, photos and report become editable until unlockedUntil; « Terminer la correction » re-sends delegate.visits.end and locks it again.
  • The visit appears in real time in the back-office (cahier §6.4): when online, the check-in reaches BO-23 through the outbox within seconds.
  • A visit belongs to its tour's business day even if it ends after midnight.

States.

StateUI
LoadingNone from local data; skeleton only for a visit of another day not cached
OfflineBanner; every action works; GPS card « Position confirmée · vérification serveur à la synchronisation »
GPS off / permission deniedGPS card danger state with « Activer »; « Je suis arrivé » opens the system dialog or DA-04
Accuracy too lowProgress state for up to 30 s, then the out-of-tolerance dialog (accuracy wording)
Camera permission denied« Autorisez l'appareil photo pour ajouter une preuve de visite » with « Ouvrir les réglages »
Storage full« Espace de stockage insuffisant : libérez de la place pour prendre la photo »
Upload failingThumbnail with an error badge and « Réessayer »; the visit can still end (the end waits for the upload in the outbox)
LockedRead-only; banner « Visite terminée et verrouillée »
Visit cancelled by the back-officeBanner « Cette visite a été annulée par votre responsable »; local actions shown as conflicts in DA-27
Tour reassigned403 NOT_ASSIGNED_DELEGATE on replay → « Cette tournée est affectée à un autre délégué. »
App killed during the visitReopens here; the timer continues from startedAt

Copy. « Détail de la visite » · « Validation GPS » · « Position confirmée » · « Hors tolérance · justifiée » · « Arrivée sur site » · « Durée de la visite » · « En cours » · « Checklist de la visite » · « Note rapide (optionnel) » · « Ajouter une note… » · « Photo(s) de la visite » · « Ajouter une photo » · « Je suis arrivé » · « Commencer la visite » · « Terminer la visite » · « Arrivée enregistrée à 09:12 » · « Visite terminée — 35 min » · « Arrivée impossible » · « Justification » · « Confirmer l'arrivée ».

Acceptance criteria.

  1. At 38 m with an accuracy of 20 m, « Je suis arrivé » records the check-in with time and position — also in airplane mode — and the back-office shows it with the original time once synced (cahier §12 GPS).
  2. At 420 m with field.allowOutOfToleranceCheckIn = false the check-in is refused with the distance shown; with true it requires a 10-character justification and the visit is flagged in BO-24.
  3. A visit whose type requires a photo cannot be ended without one; the button explains what is missing.
  4. After « Terminer la visite » nothing can be edited; a replayed edit answers VISIT_LOCKED and is shown in the sync centre.
  5. Two taps on « Je suis arrivé » produce exactly one check-in on the server.
  6. Photos taken offline upload automatically when the network returns and appear in BO-25 with capture time and position.
  7. Killing the app during the visit and reopening it shows the same visit with the right elapsed time.

DA-11 — Visit not done — reason

FieldValue
French title« Visite non réalisée »
ReleaseV1
Design dayDay 13 — DES-13
Build taskMDA-05 — visit flow
RequirementsFLD-09
Reference screenNone — bottom sheet over DA-10, DA-06 or DA-20

Purpose. Record why a planned visit did not happen, with a reason from the reference list (cahier §4.5, §6.9, FLD-09).

Entry points & exits. Entry: overflow and secondary button of DA-10, row menu of DA-06, blocking items of DA-20 (one or several visits). Exit: back to the calling screen.

Layout (bottom sheet).

  1. Title « Visite non réalisée » and the store name (or « 3 visites » in bulk mode).
  2. Radio list of reasons (VISIT_NOT_DONE_REASON, e.g. Magasin fermé, Responsable absent, Visite refusée, Adresse introuvable, Reportée à la demande du client, Autre (préciser)), 56 dp rows.
  3. Comment (≤ 300), required when the reason requires it (Autre).
  4. Optional switch « Planifier une nouvelle visite » with a date: it adds a VISIT follow-up for the store (delegate.resellers.update with status: TO_FOLLOW_UP and followUp), offered only when the store is in the delegate's scope beyond the tour stop.
  5. Bulk mode only: « Même motif pour toutes » switch, or one reason per store.
  6. Primary « Enregistrer ».

Data.

FieldAPI sourceOffline sourceFreshness
Reasonsreference.lists → VISIT_NOT_DONE_REASON (code, label, meta.commentRequired)reference_itemsReference
Visitstatus, reseller.storeNamevisitsLive-local

Actions. Enregistrer → per visit, delegate.visits.notDone queued { reasonCode, comment, occurredAt, location } (location = a one-shot fix when available — proof of presence, e.g. shop closed; never blocking). Visit NOT_DONE locally, mediumImpact, snackbar « Visite marquée non réalisée ». One Idempotency-Key per visit.

Rules. Allowed from PLANNED, CHECKED_IN and IN_PROGRESS, tour TO_START, IN_PROGRESS or SUSPENDED. A missing reason, or Autre without comment, is blocked locally (422 REASON_REQUIRED on the server). A visit NOT_DONE can still be checked in later the same tour if the shop reopens. Back-office supervisors are notified at once for HIGH priority stops.

States. Offline (normal); visit already DONE or CANCELLED (409 VISIT_LOCKED) → « Cette visite est déjà terminée »; reasons list empty (never synced) → built-in fallback list of the seed codes.

Copy. « Visite non réalisée » · « Pourquoi cette visite n'a pas eu lieu ? » · « Commentaire » · « Même motif pour toutes » · « Enregistrer » · « Visite marquée non réalisée ».

Acceptance criteria.

  1. « Enregistrer » stays disabled until a reason is chosen, and until a comment is typed for Autre.
  2. Three visits marked in bulk from DA-20 create three items with the same reason and remove the blocking item.
  3. A visit marked not done in the morning can be checked in in the afternoon of the same tour.

DA-12 — Visit report

FieldValue
French title« Rapport de visite »
ReleaseV1
Design dayDay 13 — DES-13
Build taskMDA-06 — visit report & follow-ups
RequirementsFLD-10 · CRM-08
Reference screendelegate-09-visit-report — shown below
Client reference — person met, interest level, products and promotions presented, issues, next action, follow-up date, comments, attachments
Client reference — person met, interest level, products and promotions presented, issues, next action, follow-up date, comments, attachments — client reference (cahier des charges)

Purpose. Standardise the visit report and plan the follow-up: who was met, how interested they are, what was presented, what problems and needs came up, what happens next and when (cahier §6.8, FLD-10, CRM-08).

Entry points & exits. Entry: Rapport de visite row of DA-10, DA-21 and DA-09 (read-only). Exits: back to DA-10 (« Enregistrer »), DA-06 (« Terminer la visite »), DA-09 (« Voir la fiche »).

Layout (top to bottom; each block is shown only when the visit type lists the field as visible — cahier §6.8 « Les champs affichés peuvent dépendre du type de visite »; required ones carry an asterisk).

  1. App bar — back, « Rapport de visite », sync badge.
  2. Header card — store name and commune, « Voir la fiche », date and check-in time.
  3. Personne rencontrée — Nom et prénom (prefilled with the owner name, editable) and Fonction (select from PERSON_ROLE: Propriétaire, Gérant, Employé, Autre).
  4. Niveau d'intérêt — segmented control Faible · Moyen · Élevé (InterestLevel), 48 dp high.
  5. Produits présentés — removable chips (« Chargeurs rapides HOCO », « Câbles Type-C HOCO »); picker sheet grouped by brand then category, with a search for a specific product; 20 at most.
  6. Promotions présentées — switch; when on, chips of the active promotions (10 at most).
  7. Problématiques signalées — chips from VISIT_ISSUE_TAG (Prix trop élevé, Rupture de stock, Retard de livraison, Produit défectueux, Concurrence, Conditions de paiement, Besoin de présentoir, Autre), 10 at most.
  8. Besoins — free text (≤ 1000), e.g. « Demande écouteurs Bluetooth ».
  9. Prochaine action — select NextAction (Relance téléphonique, Nouvelle visite, Envoyer le catalogue, Message WhatsApp, Aucune).
  10. Date de relance — date and time picker, default today + 7 days at 10:00; shown and required when the next action is not Aucune.
  11. Commentaires — multiline (≤ 2000).
  12. Pièces jointes — up to 5 photos (camera or gallery) or PDF documents.
  13. Sticky action bar — « Enregistrer » (secondary) and « Terminer la visite » (primary, when the visit is IN_PROGRESS).

Data.

FieldAPI sourceOffline sourceFreshness
Visible / required fieldsdelegate.visits.get → visitType.reportFields[] (field, visible, required)visit_typesDay snapshot
Person roles, issue tags, next actionsreference.lists → PERSON_ROLE, VISIT_ISSUE_TAG, NEXT_ACTION, INTEREST_LEVELreference_itemsReference
Brands, categories, promotions to presentdelegate.tours.today → reportCatalog.brands[], .categories[], .promotions[]; product search through catalog.products.list (resellerId = the store)catalog_brands, catalog_categories, catalog_productsDay snapshot; product search online, cached products offline
Saved reportreport (VisitReportView: personMetName, personMetRole, interestLevel, productsPresented[], promotionsPresented[], issueTags[], needs, nextAction, followUpAt, comment, attachments[])visit_reportsLive-local
Follow-up createddelegate.visits.report → followUpTask, taskActiontasks (local placeholder until the delta brings the server task)On sync
Locklocked, unlockedUntilvisitsLive-local

Actions.

  • Enregistrer → local visit_reports row (client UUIDv7 id created at the first save and kept for every later save); delegate.visits.report queued with the full report and occurredAt; compacted like other full-state edits. A follow-up placeholder appears in DA-19 with the pending badge; when the server answers (taskAction = CREATED or UPDATED), the delta replaces it with the real task. Autosave every 10 s while typing, so nothing is lost if the app is killed. mediumImpact, snackbar « Rapport enregistré ».
  • Terminer la visite → save, then the end action of DA-10 (two ordered items).
  • Attachments → camera/gallery/file picker → upload queue (purpose VISIT_PHOTO for images, STORE_DOCUMENT for PDFs); the report item waits for the uploads.

Rules.

  • The report can be mandatory before ending the visit (cahier §6.8), per visit type.
  • followUpAt is required when the next action is not Aucune, must be in the future and at most 90 days ahead (400 VALIDATION_FAILED otherwise); with Aucune the date is cleared and an existing follow-up created by this report is cancelled (taskAction = CANCELLED).
  • The next action maps to the follow-up type: Relance téléphonique → CALL, Nouvelle visite → VISIT, Envoyer le catalogue → SEND_CATALOG, Message WhatsApp → WHATSAPP; the task is assigned to the delegate and due at followUpAt (cahier §6.8 « déclenche la relance planifiée »).
  • Only promotions active at occurredAt can be selected.
  • Editable while the visit is CHECKED_IN or IN_PROGRESS, or unlocked; read-only once DONE (409 VISIT_LOCKED). Saving on a visit not checked in answers 409 VISIT_NOT_CHECKED_IN.

States. Offline (normal, product search limited to cached products: « Recherche limitée aux produits enregistrés sur le téléphone »); read-only (locked, all controls replaced by values); validation errors inline per field; attachment upload failing (badge, retry).

Copy. « Rapport de visite » · « Voir la fiche » · « Personne rencontrée » · « Nom et prénom » · « Fonction » · « Niveau d'intérêt » · « Faible » · « Moyen » · « Élevé » · « Produits présentés » · « Promotions présentées » · « Problématiques signalées » · « Besoins » · « Prochaine action » · « Date de relance » · « Commentaires » · « Pièces jointes » · « Ajouter une pièce jointe » · « Enregistrer » · « Terminer la visite » · « Rapport enregistré ».

Acceptance criteria.

  1. A report saved offline with Relance téléphonique on 11 November creates, after sync, an open CALL task due that day in DA-19 and in the back-office follow-ups (cahier §6.8 acceptance criterion).
  2. The report is readable from the store sheet (DA-09) and the back-office reseller timeline.
  3. For a visit type that hides Promotions présentées, the block is not shown and the server accepts the report without it.
  4. « Terminer la visite » is refused while a required field is empty, naming the field.
  5. Killing the app while typing loses at most 10 s of input.

DA-19 — Follow-ups & tasks

FieldValue
French title« Relances »
ReleaseV1
Design dayDay 13 — DES-13
Build taskMDA-06 — visit report & follow-ups
RequirementsCRM-08
Reference screenNone — list pattern of DA-13

Purpose. Show the delegate's follow-ups (relances) and tasks — overdue, today, upcoming — and let them do, complete or reschedule each one (cahier §4.2 « Relances, tâches, prochaines actions et notifications », CRM-08).

Entry points & exits. Entry: segment Relances of the Prospects tab, quick access Relances on DA-05, notifications task.due and task.assigned (/tasks), follow-ups block of DA-09. Exits: DA-09, dialer, WhatsApp, DA-16 (Envoyer le catalogue for a store without account), DA-06 (task turned into a visit).

Layout.

  1. Segments En retard (3) · Aujourd'hui (5) · À venir.
  2. Rows (64 dp): type icon and label (TaskType: Relance téléphonique, Nouvelle visite, Message WhatsApp, Envoyer le catalogue, Autre), store name and commune, due date and time (red when overdue), origin (« Rapport de visite du 4 nov. », « Back-office », « Inactivité »), sync badge.
  3. Row actions: primary quick action by type (Appeler, WhatsApp, Ajouter à ma tournée, Partager), and « Fait »; swipe left → Reprogrammer (button alternative in the row menu).
  4. Completion sheet: outcome (3–500 characters, required), optional « Planifier une nouvelle relance » (type, date, note).
  5. Reschedule sheet: date and time, note.

Data.

FieldAPI sourceOffline sourceFreshness
Tasksdelegate.tasks.list → items[] (type, status, title, dueAt, isOverdue, reseller, sourceVisitId, note, createdBy)tasks (all open tasks of the delegate) + report placeholdersDelta
Store contactsreseller.phone, whatsappresellersDelta

Actions.

  • Fait → delegate.tasks.complete queued { outcome, nextTask, occurredAt }; the row leaves the list with an undo snackbar (5 s, before the item is released to the outbox).
  • Reprogrammer → delegate.tasks.update queued (dueAt, note).
  • Appeler / WhatsApp → dialer or wa.me; on return, « Marquer comme faite ? ».
  • Ajouter à ma tournée (type Nouvelle visite, tour IN_PROGRESS, store in scope) → delegate.tours.addVisit; the task completes automatically when that visit ends.
  • Envoyer le catalogue → share sheet of DA-16 (store without account) or a WhatsApp message with the reseller-app link.

Rules. Tasks come from visit reports, the back-office and the inactivity scan; a task stays with its assignee even if the store is reassigned (store details then reduced to the summary). Completing a task writes the matching interaction on the store timeline. A Nouvelle visite task is also proposed to the planner (BO-21) — the delegate does not plan future tours. Overdue = open and due in the past (Africa/Algiers). A morning digest at 08:00 lists the day's follow-ups.

States. Empty « Aucune relance prévue » · offline (normal) · conflict (task cancelled by the back-office meanwhile → item in DA-27, row removed) · placeholder of an unsynced report (« Relance en cours de création »).

Copy. « Relances » · « En retard » · « Aujourd'hui » · « À venir » · « Fait » · « Reprogrammer » · « Résultat » · « Planifier une nouvelle relance » · « Marquer comme faite ? » · « Aucune relance prévue ».

Acceptance criteria.

  1. A follow-up planned in a visit report appears under its due date, offline immediately as a placeholder, and as the server task after sync.
  2. Completing a call follow-up offline records the outcome on the store timeline after sync.
  3. Rescheduling moves the 08:00 reminder to the new date.
  4. Overdue tasks are counted in the Prospects tab badge.

On this page