Skip to content

feat: console screens for promotions, promo codes, segments and campaigns - #106

Merged
roncodes merged 11 commits into
release/v0.4.22from
feat/promotions-console
Sep 28, 2026
Merged

roncodes merged 11 commits into
release/v0.4.22from
feat/promotions-console

Conversation

@roncodes

@roncodes roncodes commented Sep 27, 2026 •

Copy link
Copy Markdown
Member

Summary

Console screens for the promotions work. The Storefront Promotions section (sidebar and tabs) now has Promotions, Campaigns, Segments and the existing Push Notifications.

Stacked PR, part of release #107 (release/v0.4.22). This builds on #105 (campaigns), which builds on #102, #103 and #104. Review only commit c7a10fb "feat: add console screens…". The diff shrinks to it once the stack merges into the release branch.

Promotions

  • List: name, type (with value), trigger (automatic or promo code), status, run dates, redemptions, and discount given.
  • Row actions: edit, manage codes, announce, activate / pause / end, and delete.
  • Form sections:
    • Basics: name, description, type (percentage, amount off, free delivery, buy X get Y with quantities and discount %), trigger, status.
    • Schedule: start and end date/time.
    • Conditions: minimum spend, minimum items, first order only.
    • Targeting: products, categories, excluded products.
    • Limits: total uses, uses per customer, total budget, maximum discount per order.
    • Stacking: stackable, priority, show in the app's deals.
  • Promo codes modal: generate a batch (count, prefix, length, uses per code) or one specific code; list codes with usage; disable or enable each code.
  • Announce modal: audience segment, notification title and message, optional send time. It creates a campaign through POST promotions/{id}/announce.

Campaigns

  • List: status, audience, title, send time, and customers reached. Row actions: edit, send now, cancel, delete.
  • Form: name, notification title and message, audience (segment, or all customers), channels (push and/or inbox), linked promotion or a URL, and when to send: save as draft, send now, or schedule. Existing campaigns show their current audience size.

Segments

  • List and a rule builder (order counts, ordered or lapsed within N days, joined within N days, minimum spend, reachable by push) that shows a live count of matching customers.

Plumbing

  • Ember Data models, adapters and app re-exports for promotion, promotion-code, customer-segment and campaign.
  • Three small table cell components for the promotions list.
  • English translations under storefront.promotions.*. Other locales fall back and still need translating.

Related Issue

Part of the promotions / notifications work. No tracking issue.

Type of Change

  • Bug fix
  • Feature
  • Refactor
  • Documentation
  • Test
  • Chore

Implementation Notes

  • The screens follow the existing Storefront patterns: Layout::Section with Table, row dropdown actions, and modalsManager forms. Monetary fields use MoneyInput (minor units, same as the API) and dates use DateTimeInput.
  • The promotion form stores targeting as ids in applies_to and reloads the selected products and categories when it opens.
  • The weekly schedule windows supported by the API (e.g. happy hours) aren't editable in the form yet. That's a follow-up.

Validation

  • Tests (no new Ember tests)
  • Lint
  • Build
  • Manual validation (see below)
eslint addon app           -> clean
ember-template-lint addon  -> clean for new files (2 errors that already exist on main: widget/customers.hbs:35, widget/orders.hbs:49)
fleetbase-intl-lint        -> all translation keys found
pnpm build (production)    -> succeeds
composer test:unit         -> 582 tests, 0 failures (backend of the stack)

Not clicked through in a browser. The local console runs the published engine package, not this checkout. I did run the API behind these screens against a local MySQL database:

  • the promotion engine, including stacking and codes
  • reservation under a row lock
  • every segment rule
  • campaign audience counts
  • the inbox queries

Temporary rows were cleaned up afterwards. A manual pass in the console is still needed before merging.

Documentation Impact

  • No documentation changes needed
  • Documentation updated in fleetbase/fleetbase.io
  • Documentation needed but not included

API Reference Impact

  • No API reference changes needed
  • Updated fleetbase/postman
  • API reference updates required but not included

Documentation Notes

fleetbase.io, Storefront → Promotions: user guide for promotions, promo codes, segments and campaigns.

Risk

UI only. The promotions route's index is now the promotions list, and Push Notifications moved to promotions/push-notifications, so existing sidebar and tab links are updated.

Screenshots / Recordings

Not captured (see Validation).

Android pushes never worked with store-level FCM credentials and some iOS
devices were rejected, because of several stacked defects:

- configureFcm put the service account JSON into credentials.private_key,
  so no valid Firebase client was ever built from a store channel
- FcmChannel required the platform-wide Firebase project to be configured
- credentials were always resolved from the store, never the network app
- one APNs environment per channel rejected sandbox/dev build tokens
- registerDevice never reassigned a token to the latest customer and did
  not normalize platform casing
- mail/database errors ran before push and stopped it; failures and dead
  tokens were never handled or logged

Introduce Push\StorefrontPushChannel with isolated Firebase/APNs clients,
network-first credential resolution with wrong-app and wrong-environment
retries, dead token pruning, and high priority payloads. Order
notifications share a StorefrontOrderNotification base class. Add a
customers/unregister-device endpoint and an admin test push action.
Codecov requires full patch coverage. Cover the FCM and APNs transport
send paths, PushMessage setters, APNs environment short-circuit, explicit
push routes, and pruning/logging failure handling. Read device
attributes with data_get so devices returned by a custom push route do
not need to be Eloquent models, and drop two unreachable branches.
Storefront order notifications were already stored in the core
notifications table, but customers had no way to read them. Add
storefront/v1/notifications endpoints for the authenticated customer to
list (filter by unread/type, limit/offset), count unread, read, mark as
read, mark all as read and delete their notifications, scoped to the
storefront app (store, or network and its member stores).

Add customer notification preferences (order update pushes, promotions)
stored on the contact meta and honored by the notifications, and
broadcast new notifications in realtime on contact.{uuid} through a
broadcast channel that never fails the send.
…asts

Also drop null values from inbox item data, and read notification data
through the model's array cast.
…ration

Add promotions owned by a store or network: automatic or code based
percentage, fixed amount, free delivery and buy X get Y discounts, with
product/category/store targeting, minimum subtotal and item conditions,
first order only, date ranges and weekly schedule windows, usage limits
(total, per customer, per code), budgets, maximum discounts, stacking and
priorities.

Checkout prices the cart's promotions when a checkout is created, stores
them on the checkout options and reserves each use under a row lock, so
the amount charged (cash, Stripe, QPay) always matches the discount
shown. Capture records a discount transaction item and order meta and
redeems the reservations; multi-store orders split the discount per
store. Stale reservations are released by a scheduled command.

Cart endpoints apply, remove and preview codes; the public API lists the
storefront's live deals; the console API manages promotions and
generates codes.
…campaigns

# Conflicts:
#	server/src/routes.php
Customer segments are rule based audiences of a store's or network's
customers (order count bounds, ordered or lapsed within N days, joined
within N days, minimum spend, reachable by push, explicit customers).

Campaigns send a title/body with an optional image and deep link to a
segment (or explicit recipients, or everyone) by push and/or inbox, now
or at a scheduled time. storefront:dispatch-campaigns claims due
campaigns atomically and queues delivery in batches; customers who
turned promotions off are skipped. Promotions can be announced with a
campaign that goes out when the promotion starts, and announcements for
promotions that stopped running are canceled.

The console API manages segments (with live audience previews) and
campaigns (send now, cancel, audience count). The existing promotional
push action now sends a tracked campaign.
…ampaigns

Promotions now has Promotions, Campaigns, Segments and Push Notifications
tabs (and sidebar entries). Promotions: list with type, trigger, status,
schedule, redemptions and discount given; a form for discounts,
conditions, targeting, limits and stacking; activate/pause/end; promo
code generation and management; and announcing a promotion to a segment.
Segments: rule builder with a live count of matching customers.
Campaigns: message, audience, channels and deep link, saved as a draft,
sent now or scheduled, with send and cancel actions.
after:starts_at failed validation whenever starts_at was empty.
@roncodes roncodes added needs-docs Requires documentation updates type:feature Feature or enhancement labels Sep 27, 2026
@codecov

codecov Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (490f9e5) to head (b8de7df).
⚠️ Report is 21 commits behind head on release/v0.4.22.

Additional details and impacted files
@@                  Coverage Diff                  @@
##             release/v0.4.22      #106     +/-   ##
=====================================================
  Coverage             100.00%   100.00%             
- Complexity              1772      2281    +509     
=====================================================
  Files                    135       182     +47     
  Lines                   7778      9070   +1292     
=====================================================
+ Hits                    7778      9070   +1292     
Flag Coverage Δ
backend 100.00% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@roncodes roncodes mentioned this pull request Sep 28, 2026
7 tasks
@roncodes
roncodes changed the base branch from main to release/v0.4.22 September 28, 2026 03:22
@roncodes
roncodes marked this pull request as ready for review September 28, 2026 04:12
@roncodes
roncodes merged commit e33ef95 into release/v0.4.22 Sep 28, 2026
11 checks passed
@roncodes
roncodes deleted the feat/promotions-console branch September 28, 2026 04:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-docs Requires documentation updates type:feature Feature or enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant