Skip to content

Feature: Promotion engine with coupons as one mechanism #359

Open

Description

@khaira777

Feature: Single promotion engine (promotions + coupons)

Summary

Build one promotion engine instead of separate coupon logic. Promotions define rules/scopes; coupons are just one activation mechanism for a promotion.

What already exists

  • Manual order discounts already exist as percentage/amount with limits and optional manager approval (discount_max_percentage, discount_max_amount, discount_mode, discount_requires_approval in main/routes/settings.ts ~324-347; PATCH discount endpoints enforcing approval in main/routes/bills.ts ~1984-2013).
  • That manual-discount feature stays as-is.

Proposed model

promotions (id, name, type, value, starts_at, ends_at, priority, stackable, min_spend, is_active)
promotion_scopes (promotion_id, scope_type product|category, scope_id)
coupon_codes (promotion_id, code, usage_limit, per_customer_limit)
promotion_redemptions
order_adjustments (order_id, source_type manual|promotion|coupon, source_id, amount, snapshot_json)
  • Order stores the applied adjustment snapshot so changing a promotion later never rewrites yesterday's bill.
  • Supports: 20% off category, flat discount, happy-hour window, coupon code (e.g. WELCOME10), min spend, stackable/non-stackable, start/end dates.

Acceptance criteria

  • Promotion admin CRUD + scope assignment (product/category).
  • Coupon codes attach to a promotion; redemption with usage limits and per-customer limits.
  • Bill stores the applied snapshot; later promotion edits don't affect historical bills.
  • Manual discount flow (existing, with manager approval) remains unchanged.

Activity

  1. carvalab commented on Sep 3, 2026

    @carvalab
    Contributor

    Hello maintainers. Before anyone implements #632 or #633 against a parallel discount model, could you confirm the promotion-engine boundary and stacking direction proposed for this issue?

    Boundary: the existing manual-discount flow (percentage/amount with limits and manager approval) stays unchanged and remains the only human-initiated path. The engine handles only automatic adjustments: scheduled item offers (#632), customer-type defaults (#633), coupon redemptions (this issue). Owner per-order overrides stay on the manual path.

    Priority (highest first): scheduled offer > customer-type default > coupon > manual (manual applies last, on the remainder, unchanged caps/approval).

    Stacking: offer x customer-type not stackable by default (winner takes all, loser recorded as superseded in the bill snapshot); coupon stacks only when both sides set stackable = true; manual never blocks and is never blocked.

    Evaluation: schedule windows in the store timezone (building on #619); bill stores an order_adjustments + snapshot_json pin so later edits never rewrite history; pricing evaluates fully offline with no network round-trip gating a sale.

    Explicit maintainer confirmation (or redirect) here unblocks #632 and #633 implementation work without re-litigating stacking. Thank you.

  2. added
    type:featureNew capability or enhancement request
    state:needs-designNeeds a design/scope decision before work starts
    on Sep 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:backendBackend (Express/SQLite)area:frontendFrontend (React/Next.js)enhancementNew feature or requeststate:needs-designNeeds a design/scope decision before work startstype:featureNew capability or enhancement request

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions