Delegate home, profile, objectives & reports
The delegate app's read model of the day — home aggregate, profile and zones, objectives, performance and the history of visit and tour reports.
These six read operations feed the Accueil, Compte, Ma zone, Mes objectifs and Rapports screens of the delegate app (application délégué, cahier §6, §6.1, §6.9). They never change data. Every figure they return is computed by the server from the same records the back-office reads, in the business time zone Africa/Algiers, so the delegate and the supervisor always see the same numbers.
Offline behaviour. None of these operations is queued: they are online reads. The app caches the last response of each (drift, hoco_offline) and shows it offline with its age (« Mis à jour à 11:05 »). While offline, the home counters are recomputed locally from the cached today snapshot plus the actions waiting in the outbox, so a visit ended offline moves « Visites réalisées » at once (cahier §6.1 « Les compteurs se mettent à jour après chaque visite »); the next online read replaces the local estimate with the server value.
Scope. Every figure is about the caller only (cahier §3.1 « leurs propres performances », ACL-03). Points of sale are counted only when they are in the caller's delegate scope — the exact rule is on Prospects & points of sale: assigned to the caller, or unassigned prospects of the caller's zones, or points of sale planned in the caller's tours from 7 days back to 30 days ahead (permissions matrix).
| Method | Path | Operation | Access | Apps | Release |
|---|---|---|---|---|---|
| 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 |
Conventions on this page
- Caller. All operations require a delegate session (
Authorization: Bearer …, user typeDELEGATE, accountACTIVE). A staff or reseller session gets403 PERMISSION_DENIED. - Errors common to every operation, not repeated below:
401 UNAUTHENTICATED,403 ACCOUNT_SUSPENDED,403 PERMISSION_DENIED,426 UPGRADE_REQUIRED,429 RATE_LIMITED,500 INTERNAL_ERROR,503 MAINTENANCE(error registry). - Percentages are basis points (
5000= 50 %), as everywhere in the API. Money is integer centimes of DZD. - Periods are inclusive business days in
Africa/Algiers: aWEEKruns Sunday → Saturday (the working week starts on Sunday), aMONTHis the calendar month.
Shared shapes of this page
type ObjectiveMetric = "VISITS" | "SIGNUPS" | "APP_INSTALLS" | "FIRST_ORDERS" | "ORDERS" | "REVENUE";
type ObjectivePeriod = "DAY" | "WEEK" | "MONTH";
interface ObjectiveProgress {
objectiveId: Uuid | null; // null when source = TOUR_PLAN
metric: ObjectiveMetric;
period: ObjectivePeriod;
from: string; // YYYY-MM-DD, first day of the period
to: string; // YYYY-MM-DD, last day (inclusive)
target: number; // a count, or Money (centimes TTC) for REVENUE
achieved: number; // same unit as target
percent: BasisPoints; // achieved / target; may exceed 10000
source: "OBJECTIVE" | "TOUR_PLAN";
}
interface ConversionStat {
from: string; // YYYY-MM-DD
to: string;
prospects: number; // points of sale counted as prospects in the window (rule below)
converted: number; // of those, how many reached FIRST_ORDER in the window
rate: BasisPoints; // converted / prospects; 0 when prospects = 0
}Counting rules (one definition for the home, the objectives, the performance page and the tour closing report — the back-office dashboards use the same, see BR-49 for order attribution, BR-73 for assisted orders and BR-54 for kilometres):
| Metric | Counts, for the caller, in the period |
|---|---|
VISITS | Visits DONE (planned and unplanned) whose endedAt falls in the period. NOT_DONE and CANCELLED never count. |
SIGNUPS | Reseller records created by the caller from the app (source = DELEGATE_APP, createdByDelegateId = caller) in the period, minus those later REJECTED at validation. |
APP_INSTALLS | Invitations of the caller that reached the step APP_INSTALLED in the period (InvitationStep). |
FIRST_ORDERS | Orders flagged isFirstOrder that reached SUBMITTED in the period and are attributed to the caller — the attribution is snapshotted on the order at submission: the source delegate of an assisted order, otherwise the reseller's assigned delegate (BR-49), so reassignments never rewrite history. |
ORDERS · REVENUE | Orders attributed to the caller that reached VALIDATED in the period (the back-office confirmation — no order is confirmed automatically, ORD-14); REVENUE sums their totalTtc. Orders later CANCELLED, REFUSED or RETURNED are subtracted on the day they leave. |
| Conversion | prospects = distinct points of sale that had no first order yet (status before FIRST_ORDER, not SUSPENDED) and that the caller visited (DONE) or created in the window; converted = those among them that reached FIRST_ORDER in the window. |
| Kilometres | Per tour: the leg from the start position, the distances between consecutive accepted location pings (accuracy ≤ 100 m; jumps implying more than 150 km/h ignored) and the leg to the end position (BR-54); summed over the period, rounded to 0.1 km; null when no position exists (location refused). |
Assumption — the cahier gives two different "conversion rate" figures on its reference screens (« 2 / 8 prospects » on the home, « 6/8 visites » on the closing). This API uses the single cohort definition above everywhere; HOCO to confirm (decision register).
GET /delegate/home
delegate.home · Access session, delegate scope (own data) · Apps DA · Idempotency-Key — (read) · Offline-capable no — online read; the app renders its cached copy and local counters offline · Release V1 · Requirements FLD-05, FLD-02
Purpose. One call that draws the whole home screen (reference screen « Accueil délégué »): greeting, sector, the three counters, today's tour with its first stops and the start button state, the objective of the day, the conversion rate and the unread badges.

Request. No parameters. Optional header If-None-Match with the last ETag: the server answers 304 Not Modified when nothing changed.
Response 200 OK
interface DelegateHome {
serverTime: Iso;
businessDate: string; // YYYY-MM-DD in Africa/Algiers
delegate: { id: Uuid; firstName: string; displayName: string; avatarUrl: string | null };
sector: { zoneId: Uuid; name: string } | null; // primary zone ("Secteur : Oran")
counters: {
visitsPlanned: number; // planned stops of today's tour (not CANCELLED)
visitsDone: number; // DONE today, planned + unplanned
visitsLate: number; // derived: PLANNED and time slot over
newProspects: number; // records created by me today (SIGNUPS rule)
};
tour: HomeTour | null; // null when no tour today
objective: ObjectiveProgress | null; // primary objective of the day (metric VISITS)
conversion: ConversionStat; // month to date
followUps: { dueToday: number; overdue: number };
unread: { notifications: number; messages: number };
}
interface HomeTour {
id: Uuid;
number: string; // TRN-2026-000045
date: string;
status: TourStatus;
orderingMode: "IMPOSED" | "RECOMMENDED";
plannedStartAt: Iso;
startedAt: Iso | null;
primaryAction: "START" | "CONTINUE" | "RESUME" | "CLOSE" | "NONE";
nextVisitId: Uuid | null;
previewStops: Array<{
visitId: Uuid;
sequence: number;
plannedAt: Iso | null;
status: VisitStatus;
isLate: boolean;
priority: "NORMAL" | "HIGH";
reseller: { id: Uuid; storeName: string; addressLine: string; communeName: string };
}>;
}{
"serverTime": "2026-11-03T10:05:12Z",
"businessDate": "2026-11-03",
"delegate": {
"id": "0192f0a1-7c3e-7b10-9a4e-5d2c1b0a9f01",
"firstName": "Yacine",
"displayName": "Yacine Belkacem",
"avatarUrl": null
},
"sector": { "zoneId": "0192e8c4-1f2a-7d3b-8c4d-2e1f0a9b8c01", "name": "Oran" },
"counters": { "visitsPlanned": 4, "visitsDone": 2, "visitsLate": 0, "newProspects": 2 },
"tour": {
"id": "0193a2f0-5b6c-7e8d-9f01-23456789ab01",
"number": "TRN-2026-000045",
"date": "2026-11-03",
"status": "IN_PROGRESS",
"orderingMode": "RECOMMENDED",
"plannedStartAt": "2026-11-03T07:30:00Z",
"startedAt": "2026-11-03T07:41:00Z",
"primaryAction": "CONTINUE",
"nextVisitId": "0193a2f0-5b6c-7e8d-9f01-23456789ab13",
"previewStops": [
{
"visitId": "0193a2f0-5b6c-7e8d-9f01-23456789ab11",
"sequence": 1,
"plannedAt": "2026-11-03T08:00:00Z",
"status": "DONE",
"isLate": false,
"priority": "NORMAL",
"reseller": { "id": "0192d1aa-3b4c-7d5e-8f60-718293a4b123", "storeName": "Magasin El Wafa", "addressLine": "Hai Sabah, cité 300 logements, local n° 4", "communeName": "Bir El Djir" }
},
{
"visitId": "0193a2f0-5b6c-7e8d-9f01-23456789ab12",
"sequence": 2,
"plannedAt": "2026-11-03T09:30:00Z",
"status": "DONE",
"isLate": false,
"priority": "HIGH",
"reseller": { "id": "0192d1aa-3b4c-7d5e-8f60-718293a4b287", "storeName": "Electro Plus", "addressLine": "Cité 200 logements, bloc C", "communeName": "Bir El Djir" }
},
{
"visitId": "0193a2f0-5b6c-7e8d-9f01-23456789ab13",
"sequence": 3,
"plannedAt": "2026-11-03T11:00:00Z",
"status": "PLANNED",
"isLate": false,
"priority": "NORMAL",
"reseller": { "id": "0192d1aa-3b4c-7d5e-8f60-718293a4b301", "storeName": "Bazar du Centre", "addressLine": "Centre-ville, 8 boulevard Emir Abdelkader", "communeName": "Oran" }
},
{
"visitId": "0193a2f0-5b6c-7e8d-9f01-23456789ab14",
"sequence": 4,
"plannedAt": "2026-11-03T13:00:00Z",
"status": "PLANNED",
"isLate": false,
"priority": "NORMAL",
"reseller": { "id": "0192d1aa-3b4c-7d5e-8f60-718293a4b315", "storeName": "Alpha Market", "addressLine": "Route de l'aéroport, cité El Amel", "communeName": "Es Senia" }
}
]
},
"objective": {
"objectiveId": "0193a0b2-44c1-7d20-8e31-5a6b7c8d9e01",
"metric": "VISITS",
"period": "DAY",
"from": "2026-11-03",
"to": "2026-11-03",
"target": 4,
"achieved": 2,
"percent": 5000,
"source": "OBJECTIVE"
},
"conversion": { "from": "2026-11-01", "to": "2026-11-03", "prospects": 8, "converted": 2, "rate": 2500 },
"followUps": { "dueToday": 1, "overdue": 0 },
"unread": { "notifications": 3, "messages": 3 }
}Field notes — reference screen → field
| Reference element | Field |
|---|---|
| « Bonjour, Yacine », avatar | delegate.firstName, delegate.avatarUrl |
| « Secteur : Oran » | sector.name (the primary zone; « Voir objectifs » opens DA-22 with delegate.objectives) |
| « Visites prévues », « Visites réalisées », « Nouveaux prospects » | counters.visitsPlanned, counters.visitsDone, counters.newProspects |
| « Ma tournée du jour » + date | tour.date, tour.previewStops (sequence badge, store name, addressLine + communeName, plannedAt shown in local time, status pill from VisitStatus) |
| « Commencer la tournée » button | tour.primaryAction: START (« Commencer la tournée », tour TO_START), CONTINUE (« Continuer la tournée », IN_PROGRESS), RESUME (« Reprendre la tournée », SUSPENDED), CLOSE (« Clôturer la tournée », IN_PROGRESS with no stop left), NONE (PLANNED, COMPLETED, CANCELLED) |
| « Objectif du jour — 12 visites, 6 / 12 réalisées, 50 % » | objective.target, objective.achieved, objective.percent |
| « Taux de conversion — 25 %, 2 / 8 prospects » | conversion.rate, conversion.converted, conversion.prospects |
| Bell badge and « Messages » badge | unread.notifications (notifications.unreadCount), unread.messages (unread announcements) |
previewStopsholds up to 4 consecutive stops: the window starts one stop before the next unfinished stop and shifts back when fewer stops remain, so a tour of 4 stops or more always shows 4. Before the start it is simply the first 4 stops. The full list comes fromdelegate.tours.today.touris the delegate's current tour: a tour stillIN_PROGRESSorSUSPENDEDfrom a previous day if one exists (it must be closed first), else today's tour, elsenull(the app shows « Aucune tournée planifiée aujourd'hui »).objectiveis theDAYobjective with metricVISITSwhen the back-office defined one (FLD-02); otherwise it is derived from the tour (source = "TOUR_PLAN",target = counters.visitsPlanned,objectiveId = null), andnullwhen there is neither.- The reference screen shows « Commencer la tournée » next to 6 visits already done — an impossible state: once a visit is done the tour is
IN_PROGRESSand the button reads « Continuer la tournée ». Its stop pill « En attente » is not aVisitStatus; unvisited stops show « À visiter » (PLANNED) and a local sync badge when an action on them is still queued (SYN-06).
Errors — only the common errors of this page.
Rules & side effects
- Read-only; no event emitted. Counters are computed at read time from
Visit,Reseller,InvitationandOrderrows (no stored counter can drift). ETagis a hash of the payload withoutserverTime; the app polls on screen focus and after each successfulsync.batch, and refreshes when anotification.createdarrives on the live stream.- A tour edited by the back-office during the day (stop added or removed) changes
counters.visitsPlannedimmediately; the app also receives the change throughsync.delta.
Used by DA-05.
GET /delegate/profile
delegate.profile · Access session, delegate scope (own profile) · Apps DA · Idempotency-Key — · Offline-capable no — cached for offline display · Release V1 · Requirements FLD-01, GEO-02
Purpose. The « Aperçu du profil » block and the Compte hub: identity, account status, zones with the wilayas and communes they cover, today's objectives and the unread count (cahier §6 « Consulter le profil et la zone attribuée », reference screen « Connexion & profil délégué »).

Request. No parameters.
Response 200 OK
interface DelegateProfileView {
id: Uuid;
firstName: string;
lastName: string;
displayName: string;
title: string; // "Délégué commercial"
phone: string; // E.164
email: string | null;
avatarUrl: string | null;
accountStatus: "ACTIVE"; // other statuses never reach this call (403)
supervisor: UserSummary | null;
since: string; // YYYY-MM-DD, delegate profile creation
zones: Array<{
id: Uuid;
code: string;
name: string;
isPrimary: boolean;
areas: Array<{
wilayaCode: string;
wilayaName: string;
coverage: "WHOLE_WILAYA" | "COMMUNES";
communes: Array<{ id: Uuid; name: string }>; // empty when WHOLE_WILAYA
}>;
}>;
wilayas: Array<{ code: string; name: string }>; // distinct, sorted by code — "Wilayas couvertes"
objectivesToday: ObjectiveProgress[];
unreadNotifications: number;
}{
"id": "0192f0a1-7c3e-7b10-9a4e-5d2c1b0a9f01",
"firstName": "Yacine",
"lastName": "Belkacem",
"displayName": "Yacine Belkacem",
"title": "Délégué commercial",
"phone": "+213555123456",
"email": null,
"avatarUrl": null,
"accountStatus": "ACTIVE",
"supervisor": { "id": "0192f0a1-7c3e-7b10-9a4e-5d2c1b0a9e10", "displayName": "Nadia Ferhat" },
"since": "2026-10-25",
"zones": [
{
"id": "0192e8c4-1f2a-7d3b-8c4d-2e1f0a9b8c01",
"code": "ORAN",
"name": "Oran",
"isPrimary": true,
"areas": [
{ "wilayaCode": "29", "wilayaName": "Mascara", "coverage": "WHOLE_WILAYA", "communes": [] },
{ "wilayaCode": "31", "wilayaName": "Oran", "coverage": "WHOLE_WILAYA", "communes": [] },
{ "wilayaCode": "46", "wilayaName": "Aïn Témouchent", "coverage": "WHOLE_WILAYA", "communes": [] }
]
}
],
"wilayas": [
{ "code": "29", "name": "Mascara" },
{ "code": "31", "name": "Oran" },
{ "code": "46", "name": "Aïn Témouchent" }
],
"objectivesToday": [
{
"objectiveId": "0193a0b2-44c1-7d20-8e31-5a6b7c8d9e01",
"metric": "VISITS",
"period": "DAY",
"from": "2026-11-03",
"to": "2026-11-03",
"target": 4,
"achieved": 2,
"percent": 5000,
"source": "OBJECTIVE"
},
{
"objectiveId": "0193a0b2-44c1-7d20-8e31-5a6b7c8d9e02",
"metric": "SIGNUPS",
"period": "DAY",
"from": "2026-11-03",
"to": "2026-11-03",
"target": 3,
"achieved": 2,
"percent": 6667,
"source": "OBJECTIVE"
}
],
"unreadNotifications": 3
}Field notes. « Zone attribuée » = name of the primary zone; « Wilayas couvertes » = wilayas joined with commas; « Objectifs du jour — 12 visites prévues » = the VISITS entry of objectivesToday (its target); the status pill « Actif » = accountStatus. Editing personal information (name, avatar, language) uses me.update; zones are assigned only by the back-office (admin.delegates.crud).
Errors — only the common errors. A suspended delegate never reaches this call: every request answers 403 ACCOUNT_SUSPENDED with the explicit message the app shows on DA-02 (cahier §6 « un compte suspendu est bloqué avec un message explicite »).
Rules & side effects
- Zones come from
DelegateZone→Zone→ZoneArea; an area is either a whole wilaya or a list of communes. A delegate without any zone getszones: []and sees only the resellers assigned to them by name. - Zone changes made in the back-office reach the app on the next call and invalidate the offline cache:
sync.deltaanswersfullResyncRequired: truebecause the set of visible prospects changed.
Used by DA-24, DA-02 (profile preview after sign-in).
GET /delegate/zone
delegate.zone · Access session, delegate scope · Apps DA · Idempotency-Key — · Offline-capable no — cached · Release V1 · Requirements GEO-02
Purpose. Ma zone: where the delegate works and how many points of sale each commune holds, to organise trips (« Organiser vos déplacements »). Also gives the map its initial viewport.
Request. Query zoneId (optional; default: all the caller's zones).
Response 200 OK
interface DelegateZoneView {
zones: Array<{
id: Uuid;
code: string;
name: string;
isPrimary: boolean;
bounds: { north: number; south: number; east: number; west: number }; // WGS84 viewport hint
center: GeoPoint;
totals: PosCounts;
wilayas: Array<{
code: string;
name: string;
totals: PosCounts;
communes: Array<{ id: Uuid; name: string; center: GeoPoint | null; counts: PosCounts }>;
}>;
}>;
}
interface PosCounts {
total: number; // points of sale in my scope located in this area
prospects: number; // no first order yet (NEW_PROSPECT … ACCOUNT_CREATED, NOT_INTERESTED, TO_FOLLOW_UP without order)
resellers: number; // FIRST_ORDER, ACTIVE, TO_FOLLOW_UP with orders, SUSPENDED
active: number; // status ACTIVE
unassigned: number; // prospects of the area not assigned to anyone (visible through my zones)
}{
"zones": [
{
"id": "0192e8c4-1f2a-7d3b-8c4d-2e1f0a9b8c01",
"code": "ORAN",
"name": "Oran",
"isPrimary": true,
"bounds": { "north": 35.95, "south": 34.70, "east": 0.65, "west": -1.65 },
"center": { "lat": 35.6969, "lng": -0.6331 },
"totals": { "total": 179, "prospects": 109, "resellers": 70, "active": 51, "unassigned": 33 },
"wilayas": [
{
"code": "31",
"name": "Oran",
"totals": { "total": 123, "prospects": 70, "resellers": 53, "active": 41, "unassigned": 20 },
"communes": [
{ "id": "0192b7c0-31aa-7a00-8b00-000000310001", "name": "Oran", "center": { "lat": 35.6969, "lng": -0.6331 }, "counts": { "total": 61, "prospects": 34, "resellers": 27, "active": 22, "unassigned": 9 } },
{ "id": "0192b7c0-31aa-7a00-8b00-000000310004", "name": "Bir El Djir", "center": { "lat": 35.7209, "lng": -0.5525 }, "counts": { "total": 38, "prospects": 21, "resellers": 17, "active": 13, "unassigned": 6 } },
{ "id": "0192b7c0-31aa-7a00-8b00-000000310007", "name": "Es Senia", "center": { "lat": 35.6479, "lng": -0.6233 }, "counts": { "total": 24, "prospects": 15, "resellers": 9, "active": 6, "unassigned": 5 } }
]
},
{
"code": "46",
"name": "Aïn Témouchent",
"totals": { "total": 31, "prospects": 22, "resellers": 9, "active": 5, "unassigned": 8 },
"communes": [
{ "id": "0192b7c0-46aa-7a00-8b00-000000460001", "name": "Aïn Témouchent", "center": { "lat": 35.2976, "lng": -1.1401 }, "counts": { "total": 31, "prospects": 22, "resellers": 9, "active": 5, "unassigned": 8 } }
]
},
{
"code": "29",
"name": "Mascara",
"totals": { "total": 25, "prospects": 17, "resellers": 8, "active": 5, "unassigned": 5 },
"communes": [
{ "id": "0192b7c0-29aa-7a00-8b00-000000290001", "name": "Mascara", "center": { "lat": 35.3966, "lng": 0.1403 }, "counts": { "total": 25, "prospects": 17, "resellers": 8, "active": 5, "unassigned": 5 } }
]
}
]
}
]
}Field notes. Only communes holding at least one point of sale in scope are listed, so wilaya totals are the sum of their communes and zone totals the sum of their wilayas. center of a commune is its reference centroid from the geography seed (null when unknown). Counts follow the scope rule, so a commune shared with another delegate shows only the caller's part.
Errors — 404 NOT_FOUND when zoneId is not one of the caller's zones.
Rules & side effects. Read-only. Counts are computed per request (indexed on communeId, assignedDelegateId, status); the response is small enough to be cached for the day.
Used by DA-25, DA-13 (map viewport).
GET /delegate/objectives
delegate.objectives · Access session, delegate scope · Apps DA · Idempotency-Key — · Offline-capable no — cached per period · Release V1 · Requirements FLD-02
Purpose. Mes objectifs: the objectives the back-office set for the caller (admin.objectives.crud) and how far they are reached (cahier §4.5 « Objectifs de visites, inscriptions, installations et commandes »).
Request. Query parameters:
| Name | Type | Default | Rule |
|---|---|---|---|
period | DAY · WEEK · MONTH | DAY | Period granularity |
date | YYYY-MM-DD | today | Any day inside the wanted period; at most 12 months back, never in the future beyond the current period |
Response 200 OK
interface ObjectivesView {
period: ObjectivePeriod;
from: string;
to: string;
daysElapsed: number; // working days elapsed in the period (Sunday–Thursday, holidays excluded)
daysTotal: number; // working days in the period
objectives: ObjectiveProgress[];
}{
"period": "MONTH",
"from": "2026-11-01",
"to": "2026-11-30",
"daysElapsed": 2,
"daysTotal": 21,
"objectives": [
{ "objectiveId": "0193a0b2-44c1-7d20-8e31-5a6b7c8d9e10", "metric": "VISITS", "period": "MONTH", "from": "2026-11-01", "to": "2026-11-30", "target": 90, "achieved": 8, "percent": 889, "source": "OBJECTIVE" },
{ "objectiveId": "0193a0b2-44c1-7d20-8e31-5a6b7c8d9e11", "metric": "SIGNUPS", "period": "MONTH", "from": "2026-11-01", "to": "2026-11-30", "target": 30, "achieved": 3, "percent": 1000, "source": "OBJECTIVE" },
{ "objectiveId": "0193a0b2-44c1-7d20-8e31-5a6b7c8d9e12", "metric": "APP_INSTALLS", "period": "MONTH", "from": "2026-11-01", "to": "2026-11-30", "target": 20, "achieved": 1, "percent": 500, "source": "OBJECTIVE" },
{ "objectiveId": "0193a0b2-44c1-7d20-8e31-5a6b7c8d9e13", "metric": "FIRST_ORDERS", "period": "MONTH", "from": "2026-11-01", "to": "2026-11-30", "target": 8, "achieved": 2, "percent": 2500, "source": "OBJECTIVE" },
{ "objectiveId": "0193a0b2-44c1-7d20-8e31-5a6b7c8d9e14", "metric": "REVENUE", "period": "MONTH", "from": "2026-11-01", "to": "2026-11-30", "target": 300000000, "achieved": 18763400, "percent": 625, "source": "OBJECTIVE" }
]
}Field notes. REVENUE targets and achievements are centimes TTC (300000000 = 3 000 000,00 DA). Objectives are never prorated: a monthly objective appears only in the MONTH view. In the DAY view, when no VISITS objective exists for the day, a TOUR_PLAN entry (target = planned stops of the day) is added so the screen always has a visits gauge. 1 November 2026 is a public holiday, so the month counts 21 working days (Sunday–Thursday) and two of them have elapsed on 3 November.
Errors — 400 VALIDATION_FAILED (date malformed, more than 12 months back, or in a future period).
Rules & side effects. Read-only; achieved follows the counting rules and is recomputed at read time (the dashboards use the same queries, so the delegate and the back-office agree to the unit).
Used by DA-22, DA-05 (« Voir objectifs »).
GET /delegate/performance
delegate.performance · Access session, delegate scope (own performance only) · Apps DA · Idempotency-Key — · Offline-capable no — cached · Release V1 · Requirements FLD-19
Purpose. Rapports & performance: the delegate's results over a period with a daily series for the charts (cahier §6.9 « Consulter l'atteinte des objectifs et le taux de conversion », « Voir les visites, inscriptions, installations, commandes, chiffre d'affaires et kilomètres »).
Request. Query parameters:
| Name | Type | Default | Rule |
|---|---|---|---|
period | DAY · WEEK · MONTH · CUSTOM | MONTH | |
date | YYYY-MM-DD | today | Anchor day for DAY, WEEK, MONTH |
from, to | YYYY-MM-DD | — | Required with CUSTOM; at most 92 days; not in the future |
Response 200 OK
interface PerformanceView {
from: string;
to: string;
visits: { planned: number; done: number; notDone: number; unplannedDone: number; completionRate: BasisPoints };
signups: number;
installs: number;
firstOrders: number;
orders: number;
revenueTtc: Money;
kmTraveled: number | null;
conversionRate: BasisPoints;
conversion: ConversionStat;
tours: { completed: number; averageDurationMinutes: number | null };
objectives: ObjectiveProgress[]; // objectives whose period overlaps [from, to]
daily: Array<{
date: string;
visitsDone: number;
signups: number;
installs: number;
firstOrders: number;
revenueTtc: Money;
kmTraveled: number | null;
}>; // one entry per working day
}{
"from": "2026-11-01",
"to": "2026-11-03",
"visits": { "planned": 9, "done": 8, "notDone": 1, "unplannedDone": 0, "completionRate": 8889 },
"signups": 3,
"installs": 1,
"firstOrders": 2,
"orders": 3,
"revenueTtc": 18763400,
"kmTraveled": 71.4,
"conversionRate": 2500,
"conversion": { "from": "2026-11-01", "to": "2026-11-03", "prospects": 8, "converted": 2, "rate": 2500 },
"tours": { "completed": 2, "averageDurationMinutes": 528 },
"objectives": [
{ "objectiveId": "0193a0b2-44c1-7d20-8e31-5a6b7c8d9e10", "metric": "VISITS", "period": "MONTH", "from": "2026-11-01", "to": "2026-11-30", "target": 90, "achieved": 8, "percent": 889, "source": "OBJECTIVE" }
],
"daily": [
{ "date": "2026-11-02", "visitsDone": 4, "signups": 1, "installs": 0, "firstOrders": 1, "revenueTtc": 6252100, "kmTraveled": 32.8 },
{ "date": "2026-11-03", "visitsDone": 4, "signups": 2, "installs": 1, "firstOrders": 1, "revenueTtc": 12511300, "kmTraveled": 38.6 }
]
}Field notes. completionRate = done ÷ planned (planned stops only; unplanned visits are reported apart so they cannot inflate the rate — cahier §1.4 « Visites réalisées / visites planifiées »). conversionRate duplicates conversion.rate for the widgets. averageDurationMinutes excludes suspensions. Purchase prices and margins are never part of this view (ACL-04).
Errors — 400 VALIDATION_FAILED (CUSTOM without from/to, range over 92 days, to before from, dates in the future).
Rules & side effects. Read-only. Served from the daily aggregates of the analytics module for closed days and computed live for today, so today's figures move during the day.
Used by DA-21, DA-20 (« Mes performances »).
GET /delegate/reports
delegate.reports · Access session, delegate scope (reports authored by the caller) · Apps DA · Idempotency-Key — · Offline-capable no — the last page is cached · Release V1 · Requirements FLD-19
Purpose. The history of the caller's visit reports and tour closing reports (« Historique disponible à tout moment »), newest first.
Request. Query parameters: type (VISIT_REPORT · TOUR_REPORT, repeatable; default both), from, to (YYYY-MM-DD, default the last 30 days, range at most 366 days), resellerId (visit reports of one point of sale), cursor, limit (default 20, max 100).
Response 200 OK — cursor list of ReportHistoryItem.
type ReportHistoryItem =
| {
type: "VISIT_REPORT";
id: Uuid; // VisitReport id
occurredAt: Iso; // last save on the device
visit: { id: Uuid; number: string; status: VisitStatus; endedAt: Iso | null };
tour: { id: Uuid; number: string } | null;
reseller: ResellerSummary;
interestLevel: "LOW" | "MEDIUM" | "HIGH" | null;
nextAction: NextAction;
followUpAt: Iso | null;
commentExcerpt: string | null; // first 140 characters
}
| {
type: "TOUR_REPORT";
id: Uuid; // tour id
occurredAt: Iso; // closedAt
tour: { id: Uuid; number: string; date: string };
summary: TourClosingSummary; // see delegate.tours.close
comment: string | null;
version: number; // > 1 when the tour was reopened and closed again
};{
"items": [
{
"type": "TOUR_REPORT",
"id": "0193a2f0-5b6c-7e8d-9f01-23456789ab01",
"occurredAt": "2026-11-03T16:35:00Z",
"tour": { "id": "0193a2f0-5b6c-7e8d-9f01-23456789ab01", "number": "TRN-2026-000045", "date": "2026-11-03" },
"summary": {
"visitsPlanned": 4,
"visitsDone": 4,
"notDone": 0,
"unplannedDone": 0,
"newResellers": 2,
"appsInstalled": 1,
"firstOrders": 1,
"revenueTtc": 12511300,
"kmTraveled": 38.6,
"objectiveAttainment": [
{ "objectiveId": "0193a0b2-44c1-7d20-8e31-5a6b7c8d9e01", "metric": "VISITS", "period": "DAY", "from": "2026-11-03", "to": "2026-11-03", "target": 4, "achieved": 4, "percent": 10000, "source": "OBJECTIVE" }
],
"conversion": { "from": "2026-11-03", "to": "2026-11-03", "prospects": 5, "converted": 1, "rate": 2000 },
"conversionRate": 2000,
"activeMinutes": 489
},
"comment": "Bonne dynamique sur le secteur Hai Sabah. Intérêt marqué pour l'application et les écouteurs.",
"version": 1
},
{
"type": "VISIT_REPORT",
"id": "0193a31b-0c2d-7e3f-9a40-5b6c7d8e9f11",
"occurredAt": "2026-11-03T08:35:00Z",
"visit": { "id": "0193a2f0-5b6c-7e8d-9f01-23456789ab11", "number": "VIS-2026-001231", "status": "DONE", "endedAt": "2026-11-03T08:41:00Z" },
"tour": { "id": "0193a2f0-5b6c-7e8d-9f01-23456789ab01", "number": "TRN-2026-000045" },
"reseller": {
"id": "0192d1aa-3b4c-7d5e-8f60-718293a4b123",
"number": "REV-000123",
"storeName": "Magasin El Wafa",
"ownerName": "Karim Belkacem",
"phone": "+213555123456",
"wilayaCode": "31",
"communeName": "Bir El Djir",
"status": "ACTIVE",
"location": { "lat": 35.7112, "lng": -0.5724 }
},
"interestLevel": "MEDIUM",
"nextAction": "PHONE_FOLLOW_UP",
"followUpAt": "2026-11-10T08:00:00Z",
"commentExcerpt": "Intéressé par les écouteurs true wireless. Souhaite recevoir le catalogue HOCO."
}
],
"nextCursor": "eyJ0IjoiMjAyNi0xMS0wM1QwODozNTowMFoiLCJpIjoiMDE5M2EzMWIifQ"
}Field notes. Opening an item loads the full record: a visit report through delegate.visits.get, a tour report through delegate.tours.get (its closingReport). A report saved offline appears here only once synced.
Errors — 400 VALIDATION_FAILED (range over 366 days, malformed cursor); 404 NOT_FOUND when resellerId is not in scope.
Rules & side effects. Read-only. Only reports authored by the caller are listed — reports of other delegates on the same point of sale stay invisible even when the reseller is in scope.
Loyalty (V2)
V2 loyalty programme for resellers — points balance and level progress, points history, rewards catalogue with eligibility, redemption and vouchers.
Tours & visits
Every operation of a delegate's day in the field: the offline snapshot of today's tour, start/suspend/resume, reordering, unplanned visits, GPS check-in, checklist, note, photos, structured report, end, not-done and tour closing.