Support & communication
Back-office support and messaging — ticket queue, replies and internal notes, after-sales resolutions, announcements with targeting, the notification log, templates and the event-to-channel rules.
These operations handle reseller requests and claims (cahier §2.1 « demandes SAV et réclamations ») and back-office messaging (cahier §6.1 « messages », §8 notifications): the ticket queue and detail (BO-55, BO-56) including after-sales claims (ticket type AFTER_SALES, V1 since 17 Oct 2026 — SUP-04), announcements (BO-57) and notification templates and rules (BO-58). Ticket statuses: Lifecycles › Ticket; events, channels and copy: notifications matrix; after-sales rules: BR-81, BR-82.
Who uses it. Support agents and the Administrateur commercial handle tickets; resolving after-sales claims needs support.validate; announcements are sent by holders of communication.validate; templates and rules are administered by the Super administrateur.
| Permission | Unlocks on this page |
|---|---|
support.view · support.update | Tickets read · reply, internal notes, assign, status, priority |
support.validate | After-sales resolutions |
communication.view · create · update · delete | Announcements and the notification log · create · edit · delete |
communication.validate | Sending (or scheduling) an announcement |
communication.administer | Templates and rules |
Audit. CREATE on internal notes and announcements, UPDATE/STATUS_CHANGE on tickets, VALIDATE/REJECT on resolutions, UPDATE on templates and rules (before/after). Public replies are not audit rows (they are the conversation).
Conventions: lists return Page<T> = { items, total, page, pageSize } (responses); attachments are uploaded with files.createUpload (purpose TICKET_ATTACHMENT); editable records carry version. 401, 429 and 500 apply everywhere and are not repeated.
| Method | Path | Operation | Access | Apps | Release |
|---|---|---|---|---|---|
| GET | /admin/tickets | admin.tickets.listTickets queue with filters | support.view | BO | V1 |
| GET | /admin/tickets/{ticketId} | admin.tickets.getTicket with public and internal messages | support.view | BO | V1 |
| POST | /admin/tickets/{ticketId}/messages | admin.tickets.replyReply or add an internal note | support.update | BO | V1 |
| PATCH | /admin/tickets/{ticketId} | admin.tickets.updateAssign, change status or priority | support.update | BO | V1 |
| POST | /admin/tickets/{ticketId}/resolution | admin.tickets.resolveResolve an after-sales claim: REPLACEMENT (creates a free order), RETURN (creates a return), CREDIT_NOTE, REPAIR or REJECTED with reason | support.validate | BO | V1 |
| GET · POST · PATCH · DELETE | /admin/announcements[/{announcementId}] | admin.announcements.crudAnnouncements with targeting and schedule | communication.* | BO | V1 |
| POST | /admin/announcements/{announcementId}/send | admin.announcements.sendPublish now (push + inbox) | communication.validate | BO | V1 |
| GET | /admin/notifications/log | admin.notifications.logDelivery log per channel (sent, failed) | communication.view | BO | V1 |
| GET · PUT | /admin/notification-templates[/{eventKey}] | admin.notifications.templatesTemplates per event, channel and locale | communication.administer | BO | V1 |
| GET · PUT | /admin/notification-rules | admin.notifications.rulesEvent → channels → recipients matrix | communication.administer | BO | V1 |
Schemas
Ticket
interface Ticket {
id: Uuid;
number: string; // TKT-2026-000045
type: TicketType; // AFTER_SALES, ORDER_ISSUE, DELIVERY_ISSUE, BILLING, QUESTION, OTHER
status: TicketStatus; // OPEN, IN_PROGRESS, WAITING_RESELLER, RESOLVED, REJECTED, CLOSED
priority: "LOW" | "NORMAL" | "HIGH" | "URGENT"; // TicketPriority (not in enums.json)
subject: string;
reseller: ResellerSummary;
order?: { id: Uuid; number: string; deliveredAt?: Iso };
items?: { orderLineId: Uuid; sku: string; productName: string; quantity: number; issue: AfterSalesIssue }[]; // TicketItem — AFTER_SALES only
assignee?: UserSummary;
resolution?: { kind: AfterSalesResolution; note?: string; reason?: string; by: UserSummary; at: Iso;
orderId?: Uuid; returnId?: Uuid; creditNoteId?: Uuid; repairDate?: string };
createdAt: Iso; lastMessageAt: Iso; firstResponseAt?: Iso; resolvedAt?: Iso; closedAt?: Iso;
unreadForStaff: boolean;
messagesCount: number;
rating?: number; // 1–5 given by the reseller at closing
version: number;
}Tickets
GET /admin/tickets
admin.tickets.list · Permission support.view · Apps BO · Release V1 · Requirements SUP-02
Purpose. The tickets queue with filters; after-sales claims have their own tab.
Request
| Query | Type | Values / default | Notes |
|---|---|---|---|
status | TicketStatus[] | default OPEN, IN_PROGRESS, WAITING_RESELLER | |
type | TicketType[] | repeatable | AFTER_SALES = SAV tab |
priority | string[] | LOW | NORMAL | HIGH | URGENT | |
assigneeId | Uuid[] | me, none allowed | |
resellerId / orderId | Uuid | ||
unread | boolean | Last message from the reseller | |
from / to | string (date) | On createdAt | |
q | string | min 2 chars | Number, subject, store name |
sort / order / page / pageSize | lastMessageAt (default), createdAt, priority · desc · 1 · 25 (max 200) |
Response 200 OK
{
"items": [
{
"id": "01931b30-7c8d-7e9f-8a01-3b4c5d6e7f80", "number": "TKT-2026-000045", "type": "AFTER_SALES", "status": "OPEN", "priority": "NORMAL",
"subject": "Chargeurs qui chauffent", "reseller": { "id": "0192f4b8-61c2-7a3e-9b10-1f2e3d4c5b6a", "number": "REV-000123", "storeName": "Phone Store El Bahia", "ownerName": "Karim Benali", "phone": "+213555123456", "wilayaCode": "31", "communeName": "Bir El Djir", "status": "ACTIVE" },
"order": { "id": "0193016e-4b2a-7c11-9d8e-7f6a5b4c3d2e", "number": "CMD-2026-000158", "deliveredAt": "2026-11-06T11:00:00Z" },
"items": [{ "orderLineId": "0193016e-5a02-7b03-8c04-0d0e0f101113", "sku": "AF-A29-50W-WH", "productName": "Chargeur GaN 50 W 2 ports", "quantity": 2, "issue": "DEFECTIVE" }],
"createdAt": "2026-11-10T09:12:00Z", "lastMessageAt": "2026-11-10T09:12:00Z", "unreadForStaff": true, "messagesCount": 1, "version": 1
}
],
"total": 9, "page": 1, "pageSize": 25,
"counts": { "OPEN": 4, "IN_PROGRESS": 3, "WAITING_RESELLER": 2, "afterSales": 3 }
}Errors
| Status | Code | When |
|---|---|---|
| 403 | PERMISSION_DENIED | Caller lacks support.view |
Rules & side effects
- Read-only; new tickets arrive with
ticket.created.
GET /admin/tickets/{ticketId}
admin.tickets.get · Permission support.view · Apps BO · Release V1 · Requirements SUP-02
Purpose. A ticket with its public and internal messages, claimed items and photos.
Response 200 OK — the Ticket plus messages:
{
"id": "01931b30-7c8d-7e9f-8a01-3b4c5d6e7f80", "number": "TKT-2026-000045", "type": "AFTER_SALES", "status": "IN_PROGRESS",
"messages": [
{ "id": "01931b30-8d9e-7fa0-9b12-4c5d6e7f8091", "author": { "id": "0192f4b8-7001-7a02-8b03-0c0d0e0f1011", "displayName": "Karim Benali", "kind": "RESELLER" }, "body": "Deux chargeurs chauffent fortement après 10 minutes.", "isInternal": false, "attachments": [{ "id": "01931b30-9eaf-7b01-8c23-5d6e7f809102", "url": "https://files.hoco.dz/s/tkt45-1.jpg?sig=…", "mimeType": "image/jpeg", "size": 512330, "purpose": "TICKET_ATTACHMENT" }], "createdAt": "2026-11-10T09:12:00Z" },
{ "id": "01931b31-0a1b-7c2d-9e3f-4a5b6c7d8e9f", "author": { "id": "01929e55-2c3d-7e4f-9a1b-2c3d4e5f6a7b", "displayName": "Nadia Belkacem", "kind": "STAFF" }, "body": "Lot 2610 déjà signalé par un autre client — remplacer.", "isInternal": true, "attachments": [], "createdAt": "2026-11-10T10:02:00Z" }
],
"version": 3
}Errors
| Status | Code | When |
|---|---|---|
| 404 | NOT_FOUND | Unknown ticket |
Rules & side effects
- Reading marks the ticket read for staff (
unreadForStaff: false); not audited.
Used by BO-56
POST /admin/tickets/{ticketId}/messages
admin.tickets.reply · Permission support.update · Apps BO · Release V1 · Requirements SUP-02
Purpose. Replies to the reseller or adds an internal note.
Request
interface TicketReplyBody {
body: string; // 1–4 000
isInternal: boolean; // true = internal note, never shown or notified to the reseller
attachmentFileIds?: Uuid[]; // ≤ 5, TICKET_ATTACHMENT
asQuestion?: boolean; // public reply that waits for the reseller → WAITING_RESELLER
}{ "body": "Bonjour M. Benali, pouvez-vous nous indiquer le numéro de lot inscrit sous le chargeur ?", "isInternal": false, "asQuestion": true }Response 201 Created — the message, plus ticket: { status, firstResponseAt, version }.
Errors
| Status | Code | When |
|---|---|---|
| 400 | VALIDATION_FAILED | Empty body, more than 5 attachments, asQuestion on an internal note |
| 403 | PERMISSION_DENIED | Caller lacks support.update |
| 404 | NOT_FOUND | Unknown ticket |
| 409 | TICKET_TRANSITION_NOT_ALLOWED | Public reply on a CLOSED ticket |
| 422 | FILE_NOT_READY · FILE_PURPOSE_MISMATCH | Attachment not uploaded or wrong purpose |
Rules & side effects
- First public reply on
OPEN→IN_PROGRESSand setsfirstResponseAt;asQuestion→WAITING_RESELLER. Internal notes never change the status. - Notifications: public reply →
ticket.repliedto the reseller (push + in-app; live on the reseller stream). Internal notes notify nobody.
Used by BO-56
PATCH /admin/tickets/{ticketId}
admin.tickets.update · Permission support.update · Apps BO · Release V1 · Requirements SUP-02
Purpose. Assigns, changes the status, priority or type of a ticket.
Request
interface UpdateTicketBody { assigneeId?: Uuid | null; status?: "IN_PROGRESS" | "WAITING_RESELLER" | "RESOLVED" | "CLOSED"; priority?: TicketPriority; type?: TicketType; note?: string; version: number }{ "assigneeId": "01929e55-2c3d-7e4f-9a1b-2c3d4e5f6a7b", "priority": "HIGH", "version": 1 }Response 200 OK — the updated Ticket.
Errors
| Status | Code | When |
|---|---|---|
| 403 | PERMISSION_DENIED | Caller lacks support.update |
| 404 | NOT_FOUND | Unknown ticket or assignee |
| 409 | TICKET_TRANSITION_NOT_ALLOWED | Move not in the table; RESOLVED on an AFTER_SALES ticket (use resolve); changing the type of a claim |
| 409 | VERSION_CONFLICT | Stale version |
Rules & side effects
- Assigning an
OPENticket moves it toIN_PROGRESS. AuditUPDATE/STATUS_CHANGE. - Notifications:
ticket.assigned→ assignee (in-app; proposed event);ticket.status_changed→ reseller forWAITING_RESELLERandRESOLVED.
POST /admin/tickets/{ticketId}/resolution
admin.tickets.resolve · Permission support.validate · Apps BO · Release V1 · Requirements SUP-04 · ORD-13
Purpose. Settles an after-sales claim with exactly one resolution (BR-82).
Request
interface ResolveClaimBody {
resolution: "REPLACEMENT" | "RETURN" | "CREDIT_NOTE" | "REPAIR" | "REJECTED"; // AfterSalesResolution
items?: { orderLineId: Uuid; quantity: number }[]; // default: the claimed items; quantity ≤ claimed
creditAmountTtc?: Money; // CREDIT_NOTE: ≤ claimed lines at snapshot prices (default: that maximum)
repair?: { note: string; expectedOn?: string }; // REPAIR
reason?: string; // REJECTED: required (out of warranty, misuse…), shown to the reseller
message?: string; // public message posted with the resolution
version: number;
}{ "resolution": "REPLACEMENT", "items": [{ "orderLineId": "0193016e-5a02-7b03-8c04-0d0e0f101113", "quantity": 2 }], "message": "Deux chargeurs neufs partent avec votre prochaine livraison.", "version": 3 }Response 200 OK — the Ticket with status RESOLVED (or REJECTED) and resolution, e.g. { "kind": "REPLACEMENT", "orderId": "01931b40-1a2b-7c3d-8e4f-5a6b7c8d9e0f", "by": { "id": "01929e66-3d4e-7f5a-8b6c-7d8e9f0a1b2c", "displayName": "Sofiane Hamdi" }, "at": "2026-11-10T11:00:00Z" } and createdOrder: { "number": "CMD-2026-000171", "status": "SUBMITTED", "totalTtc": 0 }.
| Resolution | Effect |
|---|---|
REPLACEMENT | A free order (channel BACK_OFFICE, lines isFree, price 0, PaymentMethod none required) is created SUBMITTED for the items; it enters the Commandes à confirmer queue and follows the normal flow (reservation, preparation, shipping) |
RETURN | A return request is created APPROVED (origin: AFTER_SALES); its reception re-enters stock and issues the credit note |
CREDIT_NOTE | A CreditNote (AV- number) is issued without physical return, allocated to the order's invoice or kept as an advance (BR-39) |
REPAIR | Repair note and expected date recorded; no stock or money movement |
REJECTED | Ticket REJECTED with the reason |
Errors
| Status | Code | When |
|---|---|---|
| 400 | VALIDATION_FAILED | Item not claimed, quantity above the claimed one, creditAmountTtc above the maximum, repair missing |
| 403 | PERMISSION_DENIED | Caller lacks support.validate |
| 404 | NOT_FOUND | Unknown ticket |
| 409 | TICKET_NOT_AFTER_SALES | Ticket type is not AFTER_SALES |
| 409 | TICKET_TRANSITION_NOT_ALLOWED | Already RESOLVED, REJECTED or CLOSED |
| 409 | VERSION_CONFLICT | Stale version |
| 422 | REASON_REQUIRED | REJECTED without reason |
Rules & side effects
- One transaction: resolution + created order / return / credit note. Audit
VALIDATE(orREJECT) with the resolution, plusCREATEon the created record. - Notifications:
ticket.resolved(templates per resolution, e.g.ticket.resolved.credit_note) orticket.rejected→ reseller (push + in-app, live). The replacement order then produces the order events.
Announcements
interface Announcement {
id: Uuid;
title: string; // ≤ 80
body: string; // ≤ 2 000, plain text with line breaks (French)
bodyAr?: string; // ≤ 2 000, shown to Arabic readers instead of `body` (contract 1.1.0)
image?: FileRef; // ANNOUNCEMENT_IMAGE; write `imageFileId` (null clears it) (contract 1.1.0)
smsText?: string; // ≤ 160, required when SMS is a channel
audience: { roles: ("DELEGATE" | "RESELLER")[]; zoneIds: Uuid[]; wilayaCodes: string[]; segmentIds: Uuid[]; userIds: Uuid[];
customerTypes: CustomerType[] }; // AND between criteria, OR inside; customerTypes (contract 1.1.0): resellers of these types only, needs the RESELLER role
target: { productId?; promotionId?; categoryId?; brandId?; fileId? }; // COMMERCIAL_PUSH / NEW_CATALOGUE
targetNames: { product?: string; promotion?: string; category?: string; brand?: string }; // names of the target ids (contract 1.1.0)
file?: FileRef; // the catalogue PDF of target.fileId, signed (contract 1.1.0)
channels: ("IN_APP" | "PUSH" | "SMS" | "EMAIL")[]; // IN_APP always included
status: "DRAFT" | "SCHEDULED" | "SENT" | "CANCELLED"; // AnnouncementStatus (not in enums.json)
scheduledAt?: Iso;
sentAt?: Iso;
expiresAt?: Iso; // hidden from 'Messages' afterwards
stats?: { recipients: number; read: number; pushDelivered: number; smsSent: number };
createdBy: UserSummary; createdAt: Iso; version: number;
}GET /admin/announcements
admin.announcements.crud (list) · Permission communication.view · Apps BO · Release V1 · Requirements NOT-07
Purpose. Lists announcements. Request — status[], from / to, q, page / pageSize. Response 200 OK — Page<Announcement>, e.g. { "items": [{ "id": "01931d00-1a2b-7c3d-8e4f-5a6b7c8d9e0f", "title": "Nouveaux chargeurs ACEFAST", "body": "La gamme GaN 65 W arrive le 15 novembre…", "audience": { "roles": ["DELEGATE"], "zoneIds": [], "wilayaCodes": [], "segmentIds": [], "userIds": [] }, "channels": ["IN_APP", "PUSH"], "status": "SENT", "sentAt": "2026-11-03T07:30:00Z", "stats": { "recipients": 11, "read": 9, "pushDelivered": 11, "smsSent": 0 }, "createdBy": { "id": "01929e55-2c3d-7e4f-9a1b-2c3d4e5f6a7b", "displayName": "Nadia Belkacem" }, "createdAt": "2026-11-03T07:20:00Z", "version": 2 }], "total": 7, "page": 1, "pageSize": 25 }.
Errors
| Status | Code | When |
|---|---|---|
| 403 | PERMISSION_DENIED | Caller lacks communication.view |
Rules & side effects
- Read-only.
Used by BO-57
GET /admin/announcements/{announcementId}
admin.announcements.crud (get) · Permission communication.view · Apps BO · Release V1 · Requirements NOT-07
Purpose. One announcement with its stats and estimatedRecipients (computed from the audience). Response 200 OK — an Announcement.
Errors
| Status | Code | When |
|---|---|---|
| 404 | NOT_FOUND | Unknown announcement |
Rules & side effects
- Read-only.
Used by BO-57
POST /admin/announcements
admin.announcements.crud (create) · Permission communication.create · Apps BO · Release V1 · Requirements NOT-07
Purpose. Drafts an announcement to delegates and/or resellers.
Request
{ "title": "Fermeture le 1er novembre", "body": "Nos dépôts seront fermés le dimanche 1er novembre. Les commandes reprennent le lundi.", "audience": { "roles": ["RESELLER", "DELEGATE"], "zoneIds": [], "wilayaCodes": ["31", "46"], "segmentIds": [], "userIds": [] }, "channels": ["IN_APP", "PUSH"], "expiresAt": "2026-11-02T23:00:00Z" }Response 201 Created — the Announcement (DRAFT) with estimatedRecipients.
Errors
| Status | Code | When |
|---|---|---|
| 400 | VALIDATION_FAILED | Empty audience, SMS without smsText, body too long; SMS or EMAIL on a COMMERCIAL_PUSH or NEW_CATALOGUE (field code CHANNEL_NOT_AVAILABLE) |
| 403 | PERMISSION_DENIED | Caller lacks communication.create |
| 404 | NOT_FOUND | An audience.userIds id is not a user of the chosen roles |
Rules & side effects
- Audit
CREATE. - SRV-42 — every field is stored:
smsText, theSMSandEMAILchannels (messages only) andaudience.userIds(named users, ANDed with the other criteria). At sending,announcement.publishedgoes through the chosen channels: SMS withsmsTextthrough the SMS gateway (D-13 — simulated until it is configured), e-mail (title and body) through SMTP to recipients with an address; both outside the SMS whitelist and on unless the recipient switched the channel off.byChannelof the send answer counts the recipients with a phone (SMS) or an e-mail (EMAIL);stats.smsSentcounts the SMS sent.
Used by BO-57
PATCH /admin/announcements/{announcementId}
admin.announcements.crud (update) · Permission communication.update · Apps BO · Release V1 · Requirements NOT-07
Purpose. Edits a draft or scheduled announcement (status: "DRAFT" unschedules). Response 200 OK — the Announcement.
Errors
| Status | Code | When |
|---|---|---|
| 409 | ANNOUNCEMENT_NOT_EDITABLE | Already SENT or CANCELLED |
| 409 | VERSION_CONFLICT | Stale version |
Rules & side effects
- Audit
UPDATE.
Used by BO-57
DELETE /admin/announcements/{announcementId}
admin.announcements.crud (delete) · Permission communication.delete · Apps BO · Release V1 · Requirements NOT-07
Purpose. Deletes a draft or cancels a scheduled announcement (SENT ones stay for history). Response 204 No Content.
Errors
| Status | Code | When |
|---|---|---|
| 409 | ANNOUNCEMENT_NOT_EDITABLE | Already SENT |
Rules & side effects
- Audit
DELETE(draft) orSTATUS_CHANGE(→CANCELLED).
Used by BO-57
POST /admin/announcements/{announcementId}/send
admin.announcements.send · Permission communication.validate · Apps BO · Release V1 · Requirements NOT-07
Purpose. Publishes an announcement now, or schedules it.
Request — { "scheduleAt": "2026-10-31T16:00:00Z" } (optional; absent = now).
Response 200 OK — { "id": "…", "status": "SENT", "sentAt": "2026-10-31T16:00:02Z", "recipients": 418, "byChannel": { "IN_APP": 418, "PUSH": 377 } } (or "status": "SCHEDULED").
Errors
| Status | Code | When |
|---|---|---|
| 403 | PERMISSION_DENIED | Caller lacks communication.validate |
| 404 | NOT_FOUND | Unknown announcement |
| 409 | ANNOUNCEMENT_NOT_EDITABLE | Already SENT or CANCELLED |
| 422 | ANNOUNCEMENT_NO_RECIPIENTS | The audience resolves to nobody |
Rules & side effects
- Recipients are resolved at send time; one
Notificationper user (announcement.published, categoryANNOUNCEMENT— respects opt-outs except in-app, and quiet hours for push).AnnouncementReadrows track reads. - Audit
STATUS_CHANGE. AnIdempotency-Keyis recommended (a double click must not send twice).
Used by BO-57
Notifications
GET /admin/notifications/log
admin.notifications.log · Permission communication.view · Apps BO · Release V1 · Requirements NOT-04
Purpose. Delivery log per channel (NotificationDelivery) to diagnose "I did not receive the SMS".
Request — channel[] (NotificationChannel), status[] (QUEUED, SENT, DELIVERED, FAILED, SKIPPED — NotificationDeliveryStatus, not in enums.json), eventKey, userId, from / to, q (masked address suffix), page / pageSize.
Response 200 OK
{
"items": [
{ "id": "01931e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f", "notificationId": "01931dff-0a1b-7c2d-9e3f-4a5b6c7d8e9f", "eventKey": "account.approved", "channel": "SMS", "recipient": { "id": "0192f4c1-7a01-7b02-8c03-0d0e0f101112", "displayName": "Samir Haddad" }, "address": "+213 66• •• •5 67", "status": "FAILED", "provider": "sms-gateway", "providerMessageId": "a81f22", "error": { "code": "UNREACHABLE", "message": "Destinataire injoignable" }, "attempts": 3, "createdAt": "2026-11-04T09:00:00Z", "lastAttemptAt": "2026-11-04T09:12:00Z" }
],
"total": 1286, "page": 1, "pageSize": 25
}Errors
| Status | Code | When |
|---|---|---|
| 403 | PERMISSION_DENIED | Caller lacks communication.view |
Rules & side effects
- Read-only; addresses masked; OTP SMS are never logged with content (notifications matrix).
Used by BO-58
GET /admin/notification-templates
admin.notifications.templates (list) · Permission communication.administer · Apps BO · Release V1 · Requirements NOT-04
Purpose. Lists templates per event × channel × locale with completeness. Response 200 OK — { "items": [{ "eventKey": "order.validated", "channels": { "PUSH": { "fr": true, "ar": false, "en": false }, "IN_APP": { "fr": true, "ar": false, "en": false } }, "updatedAt": "2026-10-30T10:00:00Z" }], "total": 52 }.
Errors
| Status | Code | When |
|---|---|---|
| 403 | PERMISSION_DENIED | Caller lacks communication.administer |
Rules & side effects
- Read-only.
Used by BO-58
GET /admin/notification-templates/{eventKey}
admin.notifications.templates (get) · Permission communication.administer · Apps BO · Release V1 · Requirements NOT-04
Purpose. All templates of one event, with the allowed placeholders.
Response 200 OK
{
"eventKey": "order.validated",
"placeholders": ["orderNumber", "totalTtc", "storeName", "deliveryEta"],
"templates": [
{ "channel": "PUSH", "locale": "fr", "title": "Commande validée", "body": "Votre commande {{orderNumber}} de {{totalTtc}} est validée.", "active": true },
{ "channel": "IN_APP", "locale": "fr", "title": "Commande validée", "body": "La commande {{orderNumber}} a été validée par HOCO. Préparation en cours.", "active": true }
],
"version": 4
}Errors
| Status | Code | When |
|---|---|---|
| 404 | NOT_FOUND | Unknown event key |
Rules & side effects
- Read-only.
Used by BO-58
PUT /admin/notification-templates/{eventKey}
admin.notifications.templates (update) · Permission communication.administer · Apps BO · Release V1 · Requirements NOT-04
Purpose. Replaces the templates of an event ({ templates: [...], version }, same shape as the read).
Response 200 OK — the stored templates.
Errors
| Status | Code | When |
|---|---|---|
| 400 | VALIDATION_FAILED | Missing fr for an active channel, SMS over 160 GSM / 70 Unicode characters per segment × 2, EMAIL without subject |
| 409 | VERSION_CONFLICT | Stale version |
| 422 | TEMPLATE_PLACEHOLDER_UNKNOWN | Placeholder not in the event's list — details: { placeholders } |
Rules & side effects
- Applies to notifications produced from now on;
arandenfall back tofrwhen absent (BR-59). AuditUPDATEwith before/after.
Used by BO-58
GET /admin/notification-rules
admin.notifications.rules (get) · Permission communication.administer · Apps BO · Release V1 · Requirements NOT-04
Purpose. The event → recipients → channels matrix as configured.
Response 200 OK
{
"rules": [
{ "eventKey": "order.validated", "category": "TRANSACTIONAL", "recipient": "RESELLER", "channels": ["IN_APP", "PUSH"], "enabled": true, "locked": true },
{ "eventKey": "tour.started", "category": "FIELD", "recipient": "holders:field.view", "channels": [], "enabled": true, "locked": false }
],
"version": 7
}Errors
| Status | Code | When |
|---|---|---|
| 403 | PERMISSION_DENIED | Caller lacks communication.administer |
Rules & side effects
- Read-only; defaults are the notifications matrix.
Used by BO-58
PUT /admin/notification-rules
admin.notifications.rules (update) · Permission communication.administer · Apps BO · Release V1 · Requirements NOT-04
Purpose. Replaces the rules ({ rules, version }).
Response 200 OK — the stored rules.
Errors
| Status | Code | When |
|---|---|---|
| 400 | VALIDATION_FAILED | Unknown event key, recipient or channel |
| 409 | VERSION_CONFLICT | Stale version |
| 422 | RULE_LOCKED | Disabling or removing IN_APP from a TRANSACTIONAL or SECURITY rule, or editing an OTP rule |
Rules & side effects
- Effective at once for new events. Audit
UPDATEwith before/after.
Used by BO-58
Field operations
Back-office field operations — delegates, zones, objectives, tour planning and live follow-up, visits and their unlocking, visit types, checklist templates and the invitation funnel.
Reports & exports
Back-office report catalogue with the columns and filters of every essential report, report runs with totals, and asynchronous PDF, Excel and CSV exports.