Synchronisation & offline
Requirements SYN-01 to SYN-07: data flows between the back-office and the apps, the delegate app's offline queue, idempotent replay, conflicts, visible sync state and real-time streams.
This area covers how data moves between the back-office and the apps (cahier §7.1, §7.2), how the delegate app works under weak or no network (§7.3), and how changes reach open screens live (§5.3, §6.4 and the decision of 17 Oct 2026 on live order status — AUTHORING §1b.3). Apps: delegate app (offline-first), reseller app and web (online, live order status), back-office (live operations stream). Server module: sync (idempotency records, sync.batch, sync.delta), with the outbox dispatcher and notifications for the SSE streams. The technical design is in offline sync design; idempotency rules are in architecture.
| ID | Requirement | Release | Priority | Source | Apps |
|---|---|---|---|---|---|
SYN-01 | Back-office → apps flows: accounts, roles, zones, tours, objectives, tasks, catalog, prices, stock, promotions, order statuses, invoices, notifications, support replies | V1 | MUST | §7.1 | API |
SYN-02 | Apps → back-office flows: sign-ups, profiles, orders, addresses, payments, claims, positions, visits, photos, checklists, reports, installs, conversions | V1 | MUST | §7.2 | API |
SYN-03 | Critical data sent immediately when a connection is available | V1 | MUST | §7.3 | DA · RA |
SYN-04 | Delegate app keeps the tour, visits, notes and photos locally under weak network | V1 | MUST | §7.3 | DA |
SYN-05 | Sync queue handling retries, duplicates (idempotency), errors and conflicts | V1 | MUST | §7.3 | DA · API |
SYN-06 | Visible sync state: synced, pending, error | V1 | MUST | §7.3 | DA |
SYN-07 | Real-time events: to the back-office (orders, visits, tours, positions) and to the reseller apps (their own order status changes) | V1 | MUST | §5.3, §6.4 | API · BO |
SYN-01 — Back-office to apps flows
| Release | Priority | Source | Apps | Owner tasks |
|---|---|---|---|---|
| V1 | MUST | cahier §7.1 | API | SRV-23 |
Statement — The system shall deliver to each app, within its permissions and scope, every back-office change it needs: accounts, roles, zones, tours, objectives, tasks, catalog, prices, availability, promotions, order statuses, invoices, notifications and support replies.
Rules
-
Delivery mechanism per data:
Data Delegate app Reseller app and web Freshness target Account status, roles, permissions me.getat start and foreground; a suspended account gets403 ACCOUNT_SUSPENDEDon the next callsame next call Zones, tours, visits, points of sale in scope, tasks, visit types, checklists, reference lists, field settings sync.delta(offline scopes) + data push{"type":"sync","scope":"tours"}on tour changes— 1 min online Objectives, performance delegate.objectives,delegate.performanceonline, cached— 5 min Catalog, prices of the caller's customer type, promotions online reads (assisted orders) online reads, HTTP caching with ETagnext request Stock availability only: IN_STOCK/OUT_OF_STOCKavailability only next request Order status changes, assisted orders, returns, ticket replies delegate.orders.*read-onlyevents.streamlive + push + inbox (SYN-07)5 s p95 while open Invoices, payments — online reads next request Settings, app versions appConfig.get,sync.deltaappConfig.getapp start / foreground -
Stock quantities never leave the back-office: no reseller- or delegate-facing response carries a quantity (AUTHORING §1b.1, STK-07).
-
sync.delta?cursor=returns, per scope, upserts and removals (op,id,version,data) since an opaque signed cursor, 500 changes per page withnextCursor. Scopes: tours from 7 days back to 7 days ahead and their visits, points of sale in scope, open tasks, reference lists, visit types and checklist templates, field settings. Records leaving the scope (reassignment, cancelled stop) are sent as removals (ACL-03). -
A cursor older than 30 days answers
410 CURSOR_EXPIRED; the app then re-downloads its offline scopes from scratch without touching its queue. -
The delegate app pulls a delta at start, at foreground, after every successful
sync.batch, on a data push and every 15 minutes while open.
Acceptance criteria
- Given the back-office adds a stop to today's tour, an online delegate app shows it within 1 minute without user action.
- Given a point of sale reassigned to another delegate, the next delta removes it from the former delegate's phone.
- No reseller or delegate API response contains a stock quantity (contract test on every reseller/delegate schema).
- A 40-day-old cursor →
410 CURSOR_EXPIRED, followed by a full re-download; pending queue items are kept.
Data — Tour, Visit, Reseller, CrmTask, ReferenceItem, VisitType, ChecklistTemplate, Setting, OutboxEvent.
Interfaces
- Endpoints:
sync.delta
SYN-02 — Apps to back-office flows
| Release | Priority | Source | Apps | Owner tasks |
|---|---|---|---|---|
| V1 | MUST | cahier §7.2 | API | SRV-23 |
Statement — The system shall record in the central database, visible in the back-office, every item sent by the apps: sign-ups, profiles, orders, addresses, payments, claims, positions, visits, photos, checklists, reports, installs and conversions.
Rules
-
Flows:
Flow Operation(s) Offline-capable Back-office surface Reseller sign-up auth.signup.resellerno validation queue BO-13, account.pendingeventField sign-up delegate.resellers.createyes BO-09, BO-13 Profile changes profile.update,delegate.resellers.updatedelegate: yes reseller 360° BO-11 Orders cart.checkout,delegate.assisted.submit,orders.confirmAssistedno orders to confirm BO-41, order.createdAddresses addresses.*no BO-11 Payment declarations payments.declareno BO-45 Claims and returns tickets.create(incl.AFTER_SALES),returns.createno BO-55, BO-69 Positions sync.locationPingsyes (buffered) BO-06, BO-23 Visits, checklists, notes, photos, reports delegate.visits.*,files.*yes BO-24, BO-25 Installs invitations.attribute— invitation funnel Conversions automatic status changes from events — CRM, reports -
Every record keeps the client app (
ClientApp), the author,occurredAt(device time of the action) andreceivedAt. -
Orders are never confirmed automatically: every order arrives
SUBMITTED(orPENDING) and waits for the back-office (ORD-14).
Acceptance criteria
- A visit finished offline at 10:12 and received at 14:30 shows both times on BO-25 and the delegate app as source.
- A reseller checkout appears in the orders-to-confirm queue within 5 seconds with channel
RESELLER_APPorRESELLER_WEB.
Data — Reseller, Order, Payment, Ticket, ReturnRequest, Visit, VisitPhoto, VisitReport, LocationPing, InvitationEvent, AuditLog.
Interfaces — none specific (see the operations above).
SYN-03 — Critical data sent immediately
Statement — The system shall send critical data as soon as a connection is available, without waiting for a manual action.
Rules
- Critical in the delegate app: tour start, suspend, resume and close; check-in, start, end and not-done; reports; field sign-ups; notes and call logs; task completion; assisted-order submission.
- The outbox flushes immediately on enqueue when online; on network regain within 10 seconds; at app foreground; then with exponential backoff (5 s, 15 s, 45 s, 2 min, 5 min cap, jitter). Photos upload in queue order before the actions that need them; position pings travel on their own batched channel and never delay actions.
- Reseller app and web keep no offline order queue: checkout needs network (prices and availability are re-validated); if the response is lost, the app retries with the same
Idempotency-Keyand gets the original order (Idempotent-Replayed: true). - Target: an online delegate action reaches the server within 5 seconds (p95), photo upload time excluded.
Acceptance criteria
- Airplane mode off → queued check-ins and reports are sent within 10 seconds without user action.
- A checkout whose response is lost and retried creates exactly one order.
Data — IdempotencyRecord, outbox on the device.
Interfaces
- Endpoints:
sync.batch
SYN-04 — Local storage of the tour under weak network
Statement — The delegate app shall keep locally the tours, visits, points of sale, notes and photos needed to work a full day without network.
Rules
- Stored locally (drift): the offline scopes of SYN-01, the outbox, and captured photos (app-private storage until upload is confirmed).
- Works offline: tour start, suspend, resume; programme, map pins (tiles when cached); check-in with on-device tolerance check; visit flow, checklist, note, photos, report; not-done; field sign-up; notes and call logs; task completion and rescheduling; showing and sharing an existing invitation.
- Needs network: tour closing (FLD-12), assisted orders (server pricing), first creation of an invitation, refreshed performance figures, ETA.
- Capacity: a full day fully offline — 30 visits, 60 photos, 5 sign-ups, 150 pings — then fully synchronised within 5 minutes on a 3G-class connection (≥ 1 Mbps up).
- Sign-out is refused while the queue is not empty (« 3 éléments non synchronisés »); sign-out wipes the local database. App updates migrate the queue without loss.
- Assumption — the local database is encrypted at rest with a key in secure storage.
Acceptance criteria
- A delegate completes 5 visits with photos in airplane mode, kills and restarts the app: everything is still there and syncs on reconnection.
- Sign-out with 2 pending items is blocked with the count.
Data — device database; Tour, Visit, Reseller, VisitPhoto.
Interfaces
- Screens: DA-14 New reseller
- Endpoints:
delegate.tours.today·sync.delta
SYN-05 — Sync queue: retries, duplicates, errors, conflicts
| Release | Priority | Source | Apps | Owner tasks |
|---|---|---|---|---|
| V1 | MUST | cahier §7.3 | DA · API | SRV-03, SRV-04, SRV-23, MOB-02, MOB-09, MOB-13 |
Statement — The system shall replay queued offline actions in order, exactly once, and report per item whether it was applied, already applied, in conflict, rejected or to retry.
Rules
-
Each queued item has: client id, operation id, path, body, an
Idempotency-Keyfixed at enqueue time and reused on every retry,occurredAt, an entity key (tour, visit or point-of-sale id) and its status. -
sync.batch(headerIdempotency-Keyrequired) carries up to 50 items in queue order; the server applies them sequentially, each in its own transaction, and returns one result per item (SyncItemResult). -
Idempotency: same key and same body →
REPLAYEDwith the original result; same key, different body →REJECTEDIDEMPOTENCY_KEY_REUSED; key in progress →RETRY. Refusals are stored too, so a replay returns the same refusal; a corrected action is a new item with a new key. Records kept 30 days. -
Ordering: within one entity key, items are strictly ordered. After a
CONFLICTorREJECTED, the server does not attempt later items of the same entity key in that batch and returns them asCONFLICTwithPREDECESSOR_FAILED(details.blockedBy); the app holds them until the user resolves the first item. Items of other entities continue. -
occurredAtis required; values more than 5 minutes ahead or 72 hours behindreceivedAt→REJECTEDOCCURRED_AT_OUT_OF_RANGE; timestamps of one visit must be ordered (OCCURRED_AT_INCONSISTENT). The app stamps actions with device time corrected by the last offset measured from the server'sDateheader, and warns when an item is pending for more than 48 hours. -
Conflicts and rejections:
Situation while offline Result Code What the delegate can do Back-office cancelled the tour actions dated before the cancellation APPLIED, after itCONFLICTTOUR_CANCELLEDdiscard, or contact the back-office Tour already closed (other device) CONFLICTTOUR_CLOSEDretry after an authorised reopening Stop removed by the back-office CONFLICTVISIT_CANCELLEDdiscard Visit already finished REJECTEDVISIT_LOCKEDask for an unlock Point of sale reassigned — edits, notes, photos on the record REJECTEDOUT_OF_SCOPEdiscard (visits of the delegate's own tour stay APPLIED)Record edited meanwhile CONFLICTVERSION_CONFLICT(withdetails.current)review server values, re-apply as a new item Status moved automatically (prospect installed the app) CONFLICTRESELLER_TRANSITION_NOT_ALLOWEDaccept server state Phone registered meanwhile (field sign-up) REJECTEDPHONE_ALREADY_EXISTScorrect the phone or discard Server error, rate limit, timeout RETRY— automatic backoff -
RETRYitems are retried automatically;CONFLICTandREJECTEDnever are.426 UPGRADE_REQUIREDpauses the queue until the app is updated;503 MAINTENANCEpauses it until maintenance ends — offline work continues. -
Batch request and result shape:
{
"items": [
{ "clientId": "0192f5a3-0001-7000-8000-000000000001", "op": "delegate.visits.arrive", "idempotencyKey": "6f1c…", "entityKey": "visit:0192…", "occurredAt": "2026-11-03T08:12:00Z", "body": { "position": { "lat": 35.6969, "lng": -0.6331, "accuracy": 18, "capturedAt": "2026-11-03T08:11:52Z" } } }
]
}{
"results": [
{ "clientId": "0192f5a3-0001-7000-8000-000000000001", "result": "APPLIED", "status": 200, "body": { "status": "CHECKED_IN", "distanceMeters": 42 } },
{ "clientId": "0192f5a3-0001-7000-8000-000000000002", "result": "CONFLICT", "status": 409, "error": { "code": "TOUR_CANCELLED" } }
]
}Acceptance criteria
- The same batch replayed three times creates each record once; items 2 and 3 answer
REPLAYED(business rules). - A batch of 51 items →
400 VALIDATION_FAILED. - A not-done item dated 80 hours ago →
REJECTED/OCCURRED_AT_OUT_OF_RANGE. - Given a rejected check-in, the start and end of the same visit in that batch come back
CONFLICT/PREDECESSOR_FAILED, while another visit's items areAPPLIED. - A 50-item batch (photos excluded) completes server-side in under 3 seconds p95 (cross-cutting NFR-1.9).
Data — IdempotencyRecord, OutboxEvent, all offline-creatable entities.
Interfaces
- Screens: DA-27 Sync centre
- Endpoints:
sync.batch
SYN-06 — Visible sync state
Statement — The delegate app shall always show whether its data is synchronised, pending or in error, and let the delegate act on items in error.
Rules
- Global indicator in the app bar: Synchronisé (queue empty, last delta under 15 minutes), En attente (n), Erreur (n) (items
CONFLICTorREJECTED), Hors ligne. - Sync centre (DA-27): items grouped by tour, visit or point of sale with status, attempts, localised error message, photo upload progress (n / m, MB), last successful sync time; actions Réessayer (after resolution), Voir (open the record), Ignorer (discard after confirmation, only for
CONFLICT/REJECTED), Exporter le journal for support. - Records with pending changes show a clock icon; records in error a red badge.
- The closing screen (DA-20) shows the pending count and blocks closing until it is 0.
- Messages never show raw codes alone: « Tournée annulée par l'administration — 3 actions non appliquées ».
Acceptance criteria
- With 3 items pending offline the bar shows En attente (3); after sync, Synchronisé.
- A
TOUR_CANCELLEDconflict appears in the sync centre with Ignorer and the record link.
Data — device outbox.
Interfaces
SYN-07 — Real-time events to the back-office and the reseller apps
| Release | Priority | Source | Apps | Owner tasks |
|---|---|---|---|---|
| V1 | MUST | cahier §5.3, §6.4; decision 17 Oct 2026 | API · BO | SRV-26 |
Statement — The system shall push live events to the back-office (orders, visits, tours, positions) and to each open reseller app or web page (their own order status changes), so screens update without reload.
Rules
- Two SSE streams fed by the outbox (integrations):
admin.events.stream(/v1/admin/events, back-office):order.created,order.status_changed,visit.checked_in,visit.completed,tour.started,tour.closed,delegate.position(max one per delegate per 60 s),stock.alert,account.pending; each delivered only to sessions holding the module's view permission.events.stream(/v1/events, RA, RW, DA while open): only the caller's ownorder.status_changed,order.modified,assisted_order.awaiting_confirmation,ticket.replied,return.status_changed,notification.created.
- Every order status change reaches the reseller live and as push plus inbox notification (ORD-15).
- Latency: commit to screen refresh within 5 seconds p95.
- Resume with
Last-Event-IDwithin a 5-minute replay window; beyond it the client reloads the screen. Heartbeat every 25 seconds. Mobile apps open the stream in the foreground only; reconnection backoff 1 to 30 seconds. - Payloads carry identifiers and the new state only; clients refetch details through the API with their own session.
Acceptance criteria
- When the back-office confirms an order, the reseller's open order detail shows Validée within 5 seconds and a push arrives.
- Reseller A's stream never carries reseller B's events; a stock manager's back-office stream carries
stock.alertbut nodelegate.position. - After a 2-minute network cut, the stream resumes from
Last-Event-IDwithout losing events.
Data — OutboxEvent, Notification.
Interfaces
- Screens: BO-06 Field dashboard & live map
- Endpoints:
events.stream·admin.events.stream
Administration & settings
Requirements ADM-01 to ADM-08: user management, roles editor, business settings, business numbers, reference lists, initial imports, mobile app versions and support regions.
External integrations
Requirements INT-01 to INT-07: maps, Firebase Cloud Messaging, SMS gateway, transactional email, S3-compatible storage, ERP/accounting (V2) and the providers' costs and delays.