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.
| Area | Marketing |
| Routes | marketing.campaigns.new · marketing.campaigns.edit |
| Centre modules | fideliza · lottery · loyalty-cards · marketing · rewards · tickets |
| Staff levels | root · admin · manager · scmanager · storeManager · crmmanager · weboperator |
| Models | campaign · campaignrule · commerce · gift-voucher · language · segment · segmented-campaign-rule · supplier · ticket |
| UI label | campaign.new / campaign.edit |
| Coverage | 7 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-campaignreference-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
- User sets name, type
promotion, start and end dates and the reference period.- Loyalty is set to
raffleand the raffle type to ‘by amount’.- A base rule defines the spend amount per entry;
maxTicketsPerReceiptcaps abuse.- Participating stores and the target segment (or ‘all users’) are chosen.
- Legal bases are supplied as text or URL; content is translated for each language step.
saveCampaignvalidates with the active rule set,patchCampaignsaves, and the activity appears as planned or active.
Loyalty-card activity with store QR
- Loyalty is set to
loyalty-card, card type ‘by store QR’, stamps quota entered.checkGenerateQrTicketsclears the conflicting flags and the rule amount.- Stores are selected; one-ticket-per-store is enabled.
- 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
- Loyalty is set to
gift-voucher(requires the Fideliza module).- Vouchers are added with name, amount, segment, budget and either a day count or a
validUntildate.checkGiftVoucherFormandhandleValidityGiftVouchervalidate the validity choice.- The total amount is recalculated (
calculateTotalAmount) against the budget and the activity is saved.
Failure paths
Core fields missing
Cause — Name, type, start date or end date blank.
What happens — Presence rules fire with the generic
requiredmessage plus the attribute label.
End date not after start date
Cause —
endDate <= startDate.What happens —
campaign.end-date-after-start-date.
Loyalty type left as 'none' on a loyalty activity
Cause —
campaignType === 'loyalty'butloyalty === 'none'.What happens — Custom validation fails with
required+ the loyalty label.
Reward validity not specified
Cause — Reward loyalty with neither
validDaysnorvalidUntil.What happens — Mutually exclusive presence rules:
campaign.form.valid-days.required/campaign.form.valid-until.required— whichever is missing when the other is also absent.
Earning amount not set
Cause — Loyalty-card ‘by amount’ or raffle ‘by amount’ with a base rule amount of 0.
What happens — Custom validation inspects the last
baserule and fails withrequired+ amount. Note it readsgeneralRule.lastObject.amountwithout a guard, so a campaign with no base rule throws instead of showing the error.
No stores selected
Cause — Loyalty activity without
commerces.What happens — Presence rule fails with
required+ commerces.
No audience
Cause — Loyalty activity (not gift-voucher) with neither
allUsersnor a segment.What happens — Custom validation fails with
required+ segment.
Stamps quota missing
Cause —
isLoyaltyCardwithoutstamps.What happens — Presence rule fails with
required+ stamps.
Negative tickets-per-receipt cap
Cause —
maxTicketsPerReceiptbelow 0.What happens —
marketing.campaign.raffle.max-tickets-per-receipt.error.
Images pasted into rich text
Cause —
descriptionorlegalTextcontains an<img>.What happens —
error.proposal.description.image-not-suported/error.campaign.legal-text.image-not-suported.
Reference period incomplete
Cause —
referenceTypeisdatewithout both reference dates, orcampaignwithout a reference campaign.What happens —
required+ reference-date / reference-campaign.
Rating missing on closure
Cause — Activity marked
closedwithout a rating.What happens —
required+ rating — closure demands an assessment.
Rule invalid
Cause — A general or receipts rule fails
validate()(missing label, zero amount, orstorestype without stores).What happens —
submitGeneralRule/submitReceiptsRulereject with the rule’s own errors (required+ label / amount / commerces).
Segmented rule invalid
Cause — Missing campaign, segment, or stamps not greater than 0.
What happens — The segmented-campaign-rule model’s presence and numericality rules fire.
Gift voucher incomplete
Cause — Missing segment, name or amount; neither days nor valid-until; vouchers limit ≤ 0.
What happens —
gift-voucher.days-or-valid-until-required,gift-voucher.vouchers-limit-must-be-greater-than-zeroand the relevantrequiredmessages.
Raffle structure locked
Cause —
canEditRaffleis false because a rule already has issued tickets.What happens — Structural raffle edits are blocked to protect entries already awarded.
API rejection
Cause — Server-side validation or failure.
What happens —
_setAdapterErrormaps each error throughmapErrorsintoerrorDetailsand showsuser-errorfor 4xx orapi-errorfor 5xx. Note it readserrors[0].status.charAt(), so an error payload without astatusstring throws inside the error handler.
Store QR sheet download fails
Cause —
/api/v4/qr_downloaderrors.What happens — No
.catch()on the fetch chain — the download silently never starts.
Raw translation key on screen
Cause — The code references
reference-campaign,reference-datebut the key does not exist in translations.json.What happens — ember-intl renders the missing key (or its missing-message placeholder) instead of text — visible to every user of this screen until the key is added.
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
app/controllers/marketing/campaigns/new.jsapp/routes/marketing/campaigns/new.jsapp/models/campaign.jsapp/models/campaignrule.jsapp/models/segmented-campaign-rule.jsapp/models/gift-voucher.jsapp/enums/loyalty-types.jsapp/enums/raffle-types.jsapp/enums/loyalty-card-types.jsapp/enums/campaign-rule-types.js
Translation keys (28)
Key English campaign.newNew activity campaign.editEdit activity users.general_dataGeneral data campaign.date-rangeDate range campaign-rule.labelLabel marketing.campaigns.loyaltyLoyalty raffleRaffle loyalty-cardLoyalty Card gift-voucherGift voucher generate-ballots.by-uploaded-ticketsGenerate ballots by uploaded tickets generate-ballots.by-amountGenerate ballots by amount generate-stamps.by-uploaded-ticketsGenerate stamps by number of tickets uploaded generate-stamps.by-amountGenerate stamps by ticket amount generate-stamps.by-qr-ticketsGenerate stamps by scanning store QR segmented-campaign-rule.aditional-stampsAditional stamps segmented-campaign-rule.unique-allocationUnique allocation segmented-campaign-rule.multiple-allocationMultiple allocation gift-voucher.amountAmount gift-voucher.budgetBudget gift-voucher.daysValid days gift-voucher.valid-untilValid until campaign.end-date-after-start-dateEnd date is set before start date campaign.form.valid-days.requiredOne of both “valid days” or “valid until” is required campaign.form.valid-until.requiredOne of both “valid days” or “valid until” is required marketing.campaign.raffle.max-tickets-per-receipt.errorMaximum tickets must be equal to or greater than 0 error.campaign.legal-text.image-not-suportedThe images in the text field of the legal bases are not supported user-errorSomething is wrong. Please check the following errors: api-errorSomething is wrong and it’s not your fault. Please, contact support with the following information:
Related
- Same area — Activity list & duplication · Activity report & PDF · Activity campaigns (programme grouping) · Email campaign list, duplication & stop · Email campaign builder (wizard, A-B test, scheduling) · Digital games (lottery) · Fidcard · Present cards (Fideliza gift cards) · Book appointment
- Shares data with — Proposal editor (content, coupon, push, AI images) · Customer receipts tab & ticket-stamp generation · Store create & edit · Customer loyalty — raffle tickets · Customer loyalty — cards & stamps · Receipt verification queue · Push notification editor (scheduling, geofencing, deep links) · App button manager (MOFs)
- Behind the same module — Store create & edit · Customer loyalty — cards & stamps · Customer loyalty — raffle tickets · Customer loyalty — rewards · Customer receipts tab & ticket-stamp generation · Receipt verification queue · Digital games (lottery) · Present cards (Fideliza gift cards)
Generated from doc/feature-inventory.json · id marketing-campaign-editor · commit 276788ca6