Marketing · Activity editor (loyalty mechanics, rules & targeting)

The largest form in the product (1,100+ lines of controller). Defines a marketing activity: type, multi-language content, legal bases, date range, reference period, target stores and segment, and — for loyalty activities — the full earning mechanic: raffle tickets, loyalty cards and stamps, rewards or Fideliza gift vouchers, each with their own earning rules, QR options and limits.

AreaMarketing
Routesmarketing.campaigns.new · marketing.campaigns.edit
Centre modulesfideliza · lottery · loyalty-cards · marketing · rewards · tickets
Staff levelsroot · admin · manager · scmanager · storeManager · crmmanager · weboperator
Modelscampaign · campaignrule · commerce · gift-voucher · language · segment · segmented-campaign-rule · supplier · ticket
UI labelcampaign.new / campaign.edit
Coverage7 use cases · 3 happy · 19 failure

Known issues (4)

Missing translations

These keys are used on this screen but do not exist in translations.json, so users see the raw key:

  • reference-campaign
  • reference-date

How it works

Structure: tabs for sections with per-tab validation attribute lists, and steps generated one-per-language (the default language marked with *), so translations are a wizard dimension rather than a separate screen. The loyalty mechanic is a tree: loyalty ∈ none | raffle | loyalty-card | reward | gift-voucher, each gated by its own module (enums/loyalty-types carries the module per option). Raffle and loyalty-card then branch again on how entitlement is earned — by uploaded tickets, by amount, or by store QR. Rules come in three kinds (base, receipts, stores) submitted separately via submitGeneralRule / submitReceiptsRule, and segmented-campaign-rule records let a segment earn extra stamps with unique or multiple allocation. Several toggles have cascading side effects: checkGenerateQrTickets clears withoutRemains, isGenericReward, balanceNotVisibleInApp, the general rule amount and receiptsRedemptionDays; onOneTicketPerCommerceChange(false) forces maxLoyaltyCardsPerUser to 1. Changing the poll max-votes opens a confirmation modal that reverts the value on cancel (__initialPollMaxVotes). afterSaved opens the QR download modal when a loyalty-card campaign is not auto-validating. Error handling is centralised in _setAdapterError, which maps API errors through mapErrors and picks user-error for 4xx vs api-error for everything else — the clearest error-classification in the codebase. downloadCommercesPDF posts the selected store ids to /api/v4/qr_download and downloads a printable QR sheet.

Use cases

Why someone opens this (7)

  • Running a receipt-based raffle: customers upload receipts, earn entries per spend threshold, prizes drawn at the end.
  • Running a stamp card: ten stamps earned by store QR scan, redeemable for a reward.
  • Issuing Fideliza gift vouchers to a segment with an amount, a budget and a validity window.
  • Letting a specific segment earn additional stamps (segmented rules) as a VIP perk.
  • Restricting earning to a subset of stores, or to one ticket per store.
  • Attaching legal bases either as rich text or as a URL, as required by raffle regulation.
  • Printing the QR sheet for all participating stores before an activity launches.

Path map

flowchart LR
    F(["Activity editor loyalty mechanics, rules and ta…"]):::feat
    F --> H1["Receipt-based raffle created"]:::ok
    F --> H2["Loyalty-card activity with store QR"]:::ok
    F --> H3["Gift-voucher activity"]:::ok
    F -.-> X1["Core fields missing"]:::bad
    F -.-> X2["End date not after start date"]:::bad
    F -.-> X3["Loyalty type left as 'none' on a loyalty activity"]:::bad
    F -.-> X4["Reward validity not specified"]:::bad
    F -.-> X5["Earning amount not set"]:::bad
    F -.-> X6["No stores selected"]:::bad
    F -.-> X7["No audience"]:::bad
    F -.-> X8["Stamps quota missing"]:::bad
    F -.-> X9["Negative tickets-per-receipt cap"]:::bad
    F -.-> X10["Images pasted into rich text"]:::bad
    F -.-> X11["Reference period incomplete"]:::bad
    F -.-> X12["Rating missing on closure"]:::bad
    F -.-> X13["Rule invalid"]:::bad
    F -.-> X14["Segmented rule invalid"]:::bad
    F -.-> X15["Gift voucher incomplete"]:::bad
    F -.-> X16["Raffle structure locked"]:::bad
    F -.-> X17["API rejection"]:::bad
    F -.-> X18["Store QR sheet download fails"]:::bad
    F -.-> X19["Raw translation key on screen"]:::bad
    classDef feat fill:#b0432c,stroke:#b0432c,color:#ffffff,font-weight:bold
    classDef ok fill:#e3f1ea,stroke:#0d6b42,color:#0d4a2e
    classDef bad fill:#f7e6e2,stroke:#9a2d1c,color:#6e1f13

Happy paths

Receipt-based raffle created

  1. User sets name, type promotion, start and end dates and the reference period.
  2. Loyalty is set to raffle and the raffle type to ‘by amount’.
  3. A base rule defines the spend amount per entry; maxTicketsPerReceipt caps abuse.
  4. Participating stores and the target segment (or ‘all users’) are chosen.
  5. Legal bases are supplied as text or URL; content is translated for each language step.
  6. saveCampaign validates with the active rule set, patchCampaign saves, and the activity appears as planned or active.

Loyalty-card activity with store QR

  1. Loyalty is set to loyalty-card, card type ‘by store QR’, stamps quota entered.
  2. checkGenerateQrTickets clears the conflicting flags and the rule amount.
  3. Stores are selected; one-ticket-per-store is enabled.
  4. On save, because the card does not auto-validate, the QR download modal opens so the user can print the store QRs.

Gift-voucher activity

  1. Loyalty is set to gift-voucher (requires the Fideliza module).
  2. Vouchers are added with name, amount, segment, budget and either a day count or a validUntil date.
  3. checkGiftVoucherForm and handleValidityGiftVoucher validate the validity choice.
  4. The total amount is recalculated (calculateTotalAmount) against the budget and the activity is saved.

Failure paths

QA checklist

Tick these off while regression-testing this feature. The boxes are plain Markdown, so progress is visible in git diffs.

  • ✅ Receipt-based raffle created
  • ✅ Loyalty-card activity with store QR
  • ✅ Gift-voucher activity
  • ⛔ Core fields missing
  • ⛔ End date not after start date
  • ⛔ Loyalty type left as ‘none’ on a loyalty activity
  • ⛔ Reward validity not specified
  • ⛔ Earning amount not set
  • ⛔ No stores selected
  • ⛔ No audience
  • ⛔ Stamps quota missing
  • ⛔ Negative tickets-per-receipt cap
  • ⛔ Images pasted into rich text
  • ⛔ Reference period incomplete
  • ⛔ Rating missing on closure
  • ⛔ Rule invalid
  • ⛔ Segmented rule invalid
  • ⛔ Gift voucher incomplete
  • ⛔ Raffle structure locked
  • ⛔ API rejection
  • ⛔ Store QR sheet download fails
  • ⛔ Raw translation key on screen

Implementation


Generated from doc/feature-inventory.json · id marketing-campaign-editor · commit 276788ca6