Promotions and loyalty
Page specs BO-49 to BO-54: promotions list, promotion editor with targeting, stacking and preview, banners and daily offers, and the V2 packs, loyalty and rewards pages.
Promotions stimulate orders with targeted offers (cahier §4.6). In V1 they are simple:
- daily offers, scheduled promotions and temporary discounts;
- on products, categories or brands;
- targeted by wilaya, segment and volume;
- with configurable stacking (PRM-01 to PRM-05).
They apply on top of the reseller's customer-type price (Super-gros, Demi-gros or Détail, PRC-02).
Promotions and banners open to everyone are also visible to guests in the reseller app and web, without any price (AUTH-10). Packs and loyalty are V2.
| ID | Screen | Section | Template | Release | Design | Build |
|---|---|---|---|---|---|---|
BO-49 | Promotions list | Promotions | template | V1 | Day 19 | DSH-09 |
BO-50 | Promotion editor | Promotions | bespoke | V1 | Day 19 | DSH-09 |
BO-51 | Banners & daily offers | Promotions | template | V1 | Day 19 | DSH-09 |
BO-52 | Packs | Promotions | template | V2 | DES-30 | DSH-21 |
BO-53 | Loyalty settings & levels | Promotions | template | V2 | DES-30 | DSH-21 |
BO-54 | Rewards & redemptions | Promotions | template | V2 | DES-30 | DSH-21 |
BO-49 — Promotions list
| Field | Value |
|---|---|
| Template | List (with a timeline view) |
| Release | V1 |
| Design day | Day 19 — DES-19 |
| Build task | DSH-09 |
| Route | /promotions (?view=, ?display=list or timeline) |
| Requirements | PRM-01 · PRM-03 |
| Permissions required | promotions.view · promotions.create (« Nouvelle promotion », « Dupliquer ») · promotions.validate (pause, resume, archive) · promotions.export |
Purpose. See every promotion by status and over time, spot overlaps, and pause or archive them.
Who uses it. Commercial admin. Direction, stock and finance read it.
Entry points. Promotions › Promotions, and the « Promo active » badges of BO-27.
Layout.
- Tabs: « Actives », « Programmées », « Brouillons », « En pause », « Terminées », « Archivées » (
PromotionStatus).
| Column | Source field | Sortable | Default visible |
|---|---|---|---|
| Nom | name + « Offre du jour » badge | Yes | Yes |
| Type | type (PromotionType) | Yes | Yes |
| Valeur | « −15 % », « −50,00 DA », « Prix promo », « Livraison offerte » | No | Yes |
| Portée | Summary (« Marque ACEFAST », « 3 produits ») | No | Yes |
| Ciblage | Summary (« Tous · visiteurs inclus », « Oran, Alger · Demi-gros ») | No | Yes |
| Début / Fin | startsAt, endsAt | Yes | Yes |
| Statut | status | Yes | Yes |
| Priorité | priority | Yes | Yes |
| Cumulable | stackable | Yes | No |
| Utilisation | Units sold under the promotion / cap, orders count | Yes | Yes |
| Remise accordée | Σ discount HT on counted orders | Yes | No |
- Filters:
q,type,brand,category,wilaya,segment,customerType,from/to(active during),dailyOffer=true,guestVisible=true. - Timeline view: one bar per promotion on a calendar, grouped by scope (brand or category). Overlapping bars with the same scope and audience show a warning icon.
- Row actions: « Modifier », « Dupliquer », « Mettre en pause » / « Reprendre », « Archiver ».
Data. admin.promotions.crud (GET list), admin.promotions.setStatus.
Actions.
- « Mettre en pause »: « Mettre en pause ACEFAST −15 % ? Elle ne s'appliquera plus aux paniers ; les commandes déjà passées gardent la remise. »
- « Archiver »: confirmation.
- Both are audited
STATUS_CHANGEand not optimistic. - « Dupliquer » opens BO-50 as a draft copy.
Rules.
ENDEDis set automatically at the end date or when the cap is reached. A promotion cannot be resumed past its end date.- « Utilisation » counts only counted (confirmed) orders, with the same basis as BO-04.
Real-time. None. The list refreshes on focus.
States. First use: « Créez votre première promotion ».
Acceptance criteria.
- The timeline shows an overlap warning for two active promotions with the same brand scope and audience.
- Pausing removes the promotion from reseller carts at their next re-validation.
- A promotion whose cap is reached appears under « Terminées ».
BO-50 — Promotion editor
| Field | Value |
|---|---|
| Template | Bespoke (Form skeleton with a live preview) |
| Release | V1 |
| Design day | Day 19 — DES-19 |
| Build task | DSH-09 |
| Route | /promotions/new · /promotions/[promotionId] |
| Requirements | PRM-01 · PRM-02 · PRM-03 · PRM-04 · PRC-02 · AUTH-10 |
| Permissions required | promotions.create (new) · promotions.update (edit) · promotions.validate (schedule, activate, pause, archive) · promotions.view (preview) |
Purpose. Build one promotion end to end (cahier §4.6):
- its mechanics;
- what it applies to (scope);
- who gets it (targeting by wilaya, segment, customer type and volume);
- when, and how much (stock cap);
- how it combines with others (stacking and priority);
- a preview on a real reseller and a sample cart before it goes live.
Who uses it. Commercial admin.
Entry points. « Nouvelle promotion » and « Modifier » / « Dupliquer » on BO-49.
Layout
The page is a two-column form. The left holds six sections. The right holds a sticky panel with three blocks:
- Résumé: a sentence generated from the form, « −15 % sur la marque ACEFAST pour les revendeurs Demi-gros et Détail d'Oran et Alger, du 10/11 au 20/11, dans la limite de 2 000 pièces ».
- Visibilité: guests yes or no.
- Aperçu: the simulation (below).
The status actions sit in the save bar.
1 · Général
| Field | Type | Validation | Default | Help text |
|---|---|---|---|---|
| Nom interne | Text | Required, 3–80 characters | — | Not shown to resellers |
| Libellé revendeur | Text | Required, 60 characters max | Generated from the mechanics | « Affiché dans les applications : ex. −15 % sur ACEFAST » |
| Description | Textarea | 500 characters max | — | Conditions shown on the promotion page |
| Visuel | Image (BANNER_IMAGE, public, 5 MB max) | Optional | — | Used in the promotions list of the apps |
| Offre du jour | Toggle | When on, the duration must be 48 h or less | Off | Shows the promotion in the daily-offer carousel with a countdown (PRM-05) |
| Vente flash | Toggle | When on, the duration must be between 2 h and 48 h (FLASH_SALE_DURATION); exclusive with Offre du jour | Off | Lists the promotion on the Ventes flash tab with a countdown and sends flash_sale.started at activation (PRM-07, change request 03, DSH-14). Quick durations: 2 h, 6 h, 24 h, 48 h |
2 · Mécanique
| Field | Type | Validation | Notes |
|---|---|---|---|
| Type | Radio cards (PromotionType) | Required | — |
Remise en % (PERCENT_OFF) | Percentage | 1–90 % (sent in basis points) | Applies to the reseller's customer-type price |
Remise en montant (AMOUNT_OFF) | Money (DA HT per unit) | Above 0; warning when it is 50 % or more of the lowest price in scope | Never brings a price below 0 |
Prix promotionnel (FIXED_PRICE) | Three money inputs: Super-gros, Demi-gros, Détail (HT) | Each must be below the variant's regular price of that type; scope limited to products or variants | One promotional price per customer type. Assumption — confirm with HOCO. |
Livraison offerte (FREE_DELIVERY) | — | No value | The delivery fee becomes 0 when the conditions are met |
3 · Portée (PromotionScope)
- S'applique à: « Tout le catalogue », « Marques », « Catégories » (sub-categories included), « Produits », « Variantes ». The pickers allow multiple values.
- Exclusions: products or variants.
- A live count of the variants in scope (« 186 variantes concernées ») with « Voir la liste ».
4 · Ciblage (PromotionAudience, PRM-02)
- Destinataires: « Tous les revendeurs (et visiteurs) » or « Restreint ».
- Restreint combines four dimensions:
- Wilayas (multi-select);
- Segments;
- Types de client (
CustomerType; Assumption — customer type as a targeting dimension); - Revendeurs (search, at most 200). Values within one dimension combine with OR; dimensions combine with AND. Niveau de fidélité is shown disabled and labelled « V2 ».
- Condition de volume (optional):
- « Montant minimum du panier » (HT; all items, or only the items in scope);
- « Quantité minimum » (of the items in scope).
- Guest visibility (read-only indicator):
- « Visible par les visiteurs sans compte » is Yes only when Destinataires = « Tous » and there is no condition beyond volume.
- Guests then see the label, the percentage or « Prix promotionnel », and the eligible products, never a price or an amount.
- A restricted promotion is invisible to guests and to resellers outside its target.
5 · Calendrier et plafond
| Field | Type | Validation | Default |
|---|---|---|---|
| Début | Date and time (Africa/Algiers) | Required; for a new promotion, now or later | Now + 1 h |
| Fin | Date and time | Required; after Début | — |
| Stock promotionnel | Integer (units, all resellers) | Optional, 1 or more | — |
| Limite par revendeur | Integer (units per reseller over the period) | Optional, 1 or more | — |
6 · Cumul et priorité (PRM-03)
| Field | Type | Validation | Default | Help text |
|---|---|---|---|---|
| Priorité | Integer | 1–100 (1 = la plus prioritaire) | 50 | « Ordre d'application lorsque plusieurs promotions concernent un même article » |
| Cumulable | Toggle | — | Off | « Si désactivé, aucune autre promotion ne s'applique à un article qui en bénéficie » |
Below the fields, a « Promotions en concurrence » list shows the active and scheduled promotions overlapping this one on scope, audience and dates, with their priority and stackability.
Preview
- Inputs:
- Revendeur: a search, or a quick profile « Type de client + wilaya » without a real reseller, or « Visiteur sans compte ».
- Panier d'exemple: variants and quantities, with « Ajouter 3 articles de la portée ».
- « Simuler » calls
admin.promotions.previewwith the unsaved draft and the sample. - Result per line:
- the regular price for the reseller's customer type;
- the promotions applied, in order, with amounts;
- the final unit price and line total HT.
- Result for the cart: subtotal, discount total, delivery fee, VAT, total TTC (shared
Totals). - Promotions not applied, with their reason:
NOT_TARGETED,OUT_OF_DATES,CAP_REACHED,VOLUME_NOT_MET,NOT_STACKABLE,LOWER_PRIORITY. - « Visiteur sans compte » shows what the apps display: the label and eligible products, with every price replaced by « Créez un compte pour voir les prix ».
Actions
| Action | Endpoint | Confirmation (FR) | Optimistic | Audit |
|---|---|---|---|---|
| « Enregistrer le brouillon » | admin.promotions.crud (POST or PATCH) | — | No | CREATE / UPDATE |
| « Programmer » (start in the future) | admin.promotions.setStatus (SCHEDULED) | « Programmer cette promotion du 10/11 08:00 au 20/11 23:59 ? » | No | STATUS_CHANGE |
| « Activer maintenant » | admin.promotions.setStatus (ACTIVE) | « Activer maintenant ? Elle s'appliquera immédiatement aux paniers des revendeurs ciblés. » | No | STATUS_CHANGE |
| « Mettre en pause » / « Reprendre » | admin.promotions.setStatus | As in BO-49 | No | STATUS_CHANGE |
| « Archiver » | admin.promotions.setStatus (ARCHIVED) | « Archiver la promotion ? » | No | STATUS_CHANGE |
| « Simuler » | admin.promotions.preview | — | — | — |
Activation sends no notification by itself. Announcing a promotion is done with an announcement (BO-57) or a banner (BO-51). The promotional notifications respect the reseller's opt-out (NOT-06).
Rules
- Application (PRM-04):
- A promotion applies only within its dates, its cap and its target.
- The cart engine applies it on the price of the reseller's customer type, and re-validates at checkout.
- An order keeps the discounts it was submitted with, even if the promotion ends before the order is confirmed (BO-39 shows a warning).
- Stacking follows the setting
promotions.stackingMode(BO-64):BEST_ONLY: on each line, only the eligible promotion with the largest discount applies.PRIORITY: eligible promotions apply in priority order on the already-discounted price. A non-stackable one stops further line promotions.- In both modes,
FREE_DELIVERYcombines with line promotions. - A guard caps the combined discount on a line at the setting
promotions.maxLineDiscountPercent(default 50 %). Assumption — to confirm.
- Editing while running:
- In
DRAFTandSCHEDULED, everything is editable. - In
ACTIVEandPAUSED, only the label, description, visual, end date (not in the past) and cap (not below the units already sold) can change. Mechanics, scope and audience are locked: « Dupliquer » to change them.
- In
- Guests: a guest-visible promotion is returned by
promotions.listandpromotions.getto unauthenticated callers, without prices (§1b.6). - Stock: a promotion never shows stock quantities to resellers; eligible products show En stock or En rupture only (STK-07).
Real-time. None.
States.
- Draft banner « Brouillon — non visible ».
- Locked fields with a lock icon while active.
- Empty scope: « Aucune variante dans la portée » blocks activation.
- Preview error: « Simulation indisponible ».
Acceptance criteria
- A −15 % promotion on ACEFAST for Demi-gros resellers of wilaya
31applies only to those resellers' carts, during its dates and until its cap (cahier §12 « La remise s'applique uniquement aux produits, dates et cibles autorisés »). - The preview for a Demi-gros reseller shows the Demi-gros price, the 15 % discount and the final price equal to what the reseller's cart then shows.
- A promotion for « Tous » appears for a guest in the reseller app with its label and eligible products and no price. A promotion restricted to a segment does not appear for guests.
- With
PRIORITYmode, a non-stackable priority-1 promotion prevents a stackable priority-10 promotion on the same line, and the preview reportsNOT_STACKABLE. - Mechanics, scope and audience cannot be changed on an active promotion.
BO-51 — Banners & daily offers
| Field | Value |
|---|---|
| Template | List (with drawer and phone/web preview) |
| Release | V1 |
| Design day | Day 19 — DES-19 |
| Build task | DSH-09 |
| Route | /promotions/banners (?tab=banners or daily) |
| Requirements | PRM-05 · PRM-02 · AUTH-10 |
| Permissions required | promotions.view · promotions.create · promotions.update · promotions.delete |
Purpose. Manage the home banners and the daily-offer carousel with countdown of the reseller app and web (reference screens 1 and 7). Banners open to everyone are also shown to guests.
Who uses it. Commercial admin.
Entry points. Promotions › Bannières et offres du jour.
Layout.
- Tab « Bannières »: a table (thumbnail, Titre, Emplacement, Lien, Ciblage, Visible visiteurs, Début / Fin, Ordre (drag), Actif), and a drawer:
- Titre: internal, required.
- Image mobile: required, 1080 × 480 px recommended.
- Image web: optional, 1600 × 400 px recommended (
BANNER_IMAGE, public). Assumption — the sizes are to fix with Akram's design. - Emplacement: « Accueil — carrousel ».
- Lien: product, category, brand, promotion or none.
- Ciblage: as in BO-50 (« Tous » or restricted). The guest indicator is derived the same way.
- Début, Fin, Actif.
- Tab « Offres du jour »: the ordered list of promotions flagged Offre du jour (from BO-50) and active or scheduled in the next 7 days. Each shows its countdown end and carousel order (drag). « Créer une offre du jour » opens BO-50 with the flag on.
- Preview: a phone frame and a web frame of the reseller home, for a chosen profile (customer type, wilaya, or guest).
Data. admin.banners.crud, admin.promotions.crud (GET with dailyOffer=true), files.createUpload.
Actions. Create, edit and reorder (reorder is optimistic). Delete asks « Supprimer la bannière ? ». All are audited.
Rules.
- The apps show at most 5 banners, in order, filtered by target and dates (
banners.list, guests allowed). - A banner linked to a promotion is hidden automatically when that promotion ends or is paused.
- Guest-visible banners never carry prices in the image. Assumption — this is an editorial rule shown as a hint, and the server cannot check it.
Real-time. None.
States. No banner: the apps show the default HOCO / ACEFAST / BOROFONE banner.
Acceptance criteria.
- A banner targeted to « Tous » shows on the guest home. One targeted to a segment shows only to its members.
- Reordering changes the carousel order on the next home load.
- A daily offer's countdown in the apps ends at the promotion's end time.
BO-52 — Packs
| Field | Value |
|---|---|
| Template | List (with a pack editor) |
| Release | V2 — designed in DES-30 |
| Design day | DES-30 — V2 designs (during the build phase; no V1 design day) |
| Build task | DSH-21 |
| Route | /promotions/packs |
| Requirements | PRM-06 |
| Permissions required | promotions.view · promotions.create · promotions.update · promotions.delete |
V2 — designed in DES-30
Packs ship with V2 (cahier §14.2). The detailed spec will be written with DES-30.
Purpose. Profitable packs: bundles of several references sold at a pack price and added to the cart as a unit (cahier §4.6, §5.5).
Layout (outline).
- A list: pack, items, pack price per customer type, margin (needs
pricing.view), stock (the minimum over items), status. - An editor: items and quantities, the three pack prices, targeting and dates as in BO-50, and a visual.
Data. admin.packs.crud.
Acceptance criteria (draft).
- A pack's availability is En rupture as soon as one item is out of stock.
- A pack added to a cart reserves each item's quantity at confirmation.
BO-53 — Loyalty settings & levels
| Field | Value |
|---|---|
| Template | Settings |
| Release | V2 — designed in DES-30 |
| Design day | DES-30 — V2 designs (during the build phase; no V1 design day) |
| Build task | DSH-21 |
| Route | /loyalty/settings |
| Requirements | LOY-01 · LOY-02 · LOY-03 |
| Permissions required | loyalty.administer · loyalty.update (manual adjustments) |
V2 — designed in DES-30
The loyalty programme is V2. Its thresholds, periods and earning rules must be validated by HOCO (cahier §4.6 « seuils validés par le client »).
Purpose. Configure points earning (per order, credited after delivery or payment), bonuses, expiry, reversal on cancellation or refund, and the levels Bronze, Silver, Gold and VIP with their period and thresholds (cahier §4.4, §4.6).
Layout (outline).
- Groups: Gain de points, Expiration, Régularisations, Niveaux (a table of
LoyaltyLevelwith thresholds and period). - A per-reseller manual adjustment drawer (reason required).
Data. admin.loyalty.settings, admin.loyalty.adjust.
Acceptance criteria (draft).
- Cancelling an order reverses its points automatically (cahier §4.6 « Les points annulés ou remboursés doivent être régularisés »).
- Each manual adjustment appears in the reseller's points history with its reason.
BO-54 — Rewards & redemptions
| Field | Value |
|---|---|
| Template | List |
| Release | V2 — designed in DES-30 |
| Design day | DES-30 — V2 designs (during the build phase; no V1 design day) |
| Build task | DSH-21 |
| Route | /loyalty/rewards (?tab=rewards or redemptions) |
| Requirements | LOY-04 · LOY-05 |
| Permissions required | loyalty.view · loyalty.create · loyalty.update (fulfil redemptions) · loyalty.delete |
V2 — designed in DES-30
Rewards ship with the V2 loyalty programme.
Purpose.
- The rewards catalogue (
RewardType: voucher, free delivery, discount, gift, early access) with points cost, stock and validity. - The redemptions to fulfil (cahier §4.6, §5.5).
Layout (outline).
- Récompenses: list and drawer (type, points, stock, dates, level required).
- Échanges: queue of redemptions (reseller, reward, date, status) with « Marquer comme remis ».
Data. admin.rewards.crud, admin.redemptions.list.
Acceptance criteria (draft).
- A reward out of stock cannot be redeemed (cahier §5.5).
- Every redemption appears in the reseller's points history.
Finance
Page specs BO-44 to BO-48: invoices and credit notes, payments and declarations, credit accounts, and the V2 overdue-reminders and accounting-export pages.
Support and reports
Page specs BO-55 to BO-60: tickets and after-sales claims, ticket detail with SAV resolution, announcements, notification templates and rules, the reports library and the export centre.