Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Appendix F: Promotion Rules Reference

Competitive Intelligence Applied

This appendix documents the complete promotions system, inspired by Retail Pro’s sophisticated promotions engine - their most powerful feature after 35 years of retail software development.


Promotion Types

1. Percentage Discount

Reduces price by a percentage.

{
  "type": "percentage",
  "value": 25,
  "appliesTo": "category",
  "targetIds": ["cat_apparel"]
}

Examples:

  • 25% off all apparel
  • 10% off entire purchase
  • 15% off clearance items

2. Fixed Amount Discount

Reduces price by a fixed dollar amount.

{
  "type": "fixed_amount",
  "value": 10.00,
  "appliesTo": "item",
  "targetIds": ["item_shirt_001"]
}

Examples:

  • $10 off any shirt
  • $5 off purchase over $50
  • $25 off seasonal items

3. Buy X Get Y (BXGY)

Buy a quantity, get additional items free or discounted.

{
  "type": "buy_x_get_y",
  "buyQuantity": 2,
  "getQuantity": 1,
  "getDiscountPercent": 100,
  "appliesTo": "category",
  "targetIds": ["cat_socks"]
}

Examples:

  • Buy 2 get 1 free
  • Buy 3 get 1 50% off
  • Buy 1 get 2nd 25% off

4. Bundle Pricing

Fixed price for a combination of items.

{
  "type": "bundle",
  "bundlePrice": 99.00,
  "requiredItems": [
    { "categoryId": "cat_shirts", "quantity": 3 }
  ]
}

Examples:

  • 3 shirts for $99
  • Outfit bundle: shirt + pants + belt for $149
  • Mix & match: any 5 items for $75

5. Threshold Discount

Spend X amount, save Y amount.

{
  "type": "threshold",
  "thresholds": [
    { "spend": 50, "save": 10 },
    { "spend": 100, "save": 25 },
    { "spend": 200, "save": 60 }
  ]
}

Examples:

  • Spend $50 save $10
  • Spend $100 save $25
  • Tiered: Spend more, save more

6. BOGO (Buy One Get One)

Special case of BXGY, commonly used.

{
  "type": "bogo",
  "getDiscountPercent": 50,
  "appliesTo": "all"
}

Examples:

  • BOGO 50% off
  • BOGO Free (getDiscountPercent: 100)
  • BOGO $10 off

Condition Rules

Minimum Purchase Amount

{
  "conditions": {
    "minPurchaseAmount": 75.00
  }
}

Only applies if cart subtotal >= $75.00


Minimum Quantity

{
  "conditions": {
    "minQuantity": 3
  }
}

Only applies if qualifying items quantity >= 3


Customer Type Requirement

{
  "conditions": {
    "customerTypes": ["gold", "platinum", "employee"]
  }
}

Only applies to customers in specified loyalty tiers.

Customer Types:

TypeDescription
guestNo customer attached
basicStandard customer
silverSilver loyalty tier
goldGold loyalty tier
platinumPlatinum loyalty tier
employeeStaff discount
vipVIP customers

Excluded Items

{
  "conditions": {
    "excludedItems": ["item_giftcard", "item_clearance_001"],
    "excludedCategories": ["cat_gift_cards", "cat_already_on_sale"]
  }
}

Items in these lists are never eligible for this promotion.


First Purchase Only

{
  "conditions": {
    "firstPurchaseOnly": true
  }
}

Only applies to customers with zero purchase history.


Scheduling

Date Range

{
  "schedule": {
    "startAt": "2025-12-20T00:00:00Z",
    "endAt": "2025-12-26T23:59:59Z"
  }
}

Promotion only active during this window.


Active Days

{
  "schedule": {
    "activeDays": ["friday", "saturday", "sunday"]
  }
}

Only active on specified days of week.


Active Hours (Retail Pro Feature)

{
  "schedule": {
    "activeHours": {
      "start": "10:00",
      "end": "14:00"
    }
  }
}

Only active during specified hours (lunch special, happy hour, etc.)


Recurring Schedule

{
  "schedule": {
    "recurring": true,
    "recurrence": {
      "type": "weekly",
      "days": ["tuesday"],
      "startTime": "17:00",
      "endTime": "20:00"
    }
  }
}

Repeats every Tuesday 5-8 PM (e.g., “Taco Tuesday” equivalent).


Usage Limits

Total Uses

{
  "limits": {
    "maxUsesTotal": 1000
  }
}

Promotion ends after 1000 total uses across all customers.


Per Customer

{
  "limits": {
    "maxUsesPerCustomer": 3
  }
}

Each customer can use this promotion max 3 times.


Per Day

{
  "limits": {
    "maxUsesPerDay": 100
  }
}

Max 100 uses per day (flash sale protection).


Per Transaction

{
  "limits": {
    "maxUsesPerTransaction": 1
  }
}

Can only be applied once per sale (e.g., one coupon per purchase).


Stacking Rules

Non-Stackable (Exclusive)

{
  "stacking": {
    "stackable": false,
    "priority": 10
  }
}

Cannot combine with other promotions. Higher priority wins.


Stackable with Restrictions

{
  "stacking": {
    "stackable": true,
    "stackableWith": ["promo_loyalty", "promo_birthday"],
    "excludeWithCodes": ["CLEARANCE", "EMPLOYEE"]
  }
}

Can stack with specific promotions, excludes others.


Priority System

When multiple exclusive promotions apply:

PriorityWins
1Lowest - applies last if stackable
10Higher - wins over lower
100Highest - always wins
{
  "stacking": {
    "priority": 50
  }
}

Location Restrictions

Specific Locations

{
  "locationIds": ["loc_gm", "loc_hm"]
}

Only valid at Greenbrier Mall and Hampton locations.


Exclude Locations

{
  "excludeLocationIds": ["loc_outlet"]
}

Valid everywhere except outlet store.


All Locations

{
  "locationIds": null
}

null means valid at all locations.


Promotion Evaluation Algorithm

┌─────────────────────────────────────────────────────────────────────────┐
│                      PROMOTION EVALUATION FLOW                           │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  1. GATHER CONTEXT                                                       │
│     ├── Cart items (itemId, categoryId, quantity, price)                 │
│     ├── Customer info (customerId, loyaltyTier, purchaseHistory)         │
│     ├── Location (locationId)                                            │
│     └── Current time (for schedule checks)                               │
│                                                                          │
│  2. FILTER ACTIVE PROMOTIONS                                             │
│     ├── Is within startAt - endAt range?                                 │
│     ├── Is active today (activeDays)?                                    │
│     ├── Is active now (activeHours)?                                     │
│     ├── Is location valid (locationIds)?                                 │
│     └── Has uses remaining (limits)?                                     │
│                                                                          │
│  3. CHECK ELIGIBILITY PER PROMOTION                                      │
│     ├── Does cart meet minPurchaseAmount?                                │
│     ├── Does cart meet minQuantity?                                      │
│     ├── Is customer type eligible?                                       │
│     ├── Are there non-excluded items that qualify?                       │
│     └── Is customer eligible (maxUsesPerCustomer)?                       │
│                                                                          │
│  4. CALCULATE DISCOUNTS                                                  │
│     ├── For each eligible promotion:                                     │
│     │   └── Calculate discount amount                                    │
│     │                                                                    │
│  5. RESOLVE STACKING                                                     │
│     ├── Group by stackable vs non-stackable                              │
│     ├── For non-stackable: keep highest priority                         │
│     ├── For stackable: apply all that stackableWith allows               │
│     └── Exclude any excludeWithCodes conflicts                           │
│                                                                          │
│  6. RETURN RESULT                                                        │
│     ├── applicablePromotions[] (will be applied)                         │
│     ├── autoAppliedPromotions[] (loyalty, etc.)                          │
│     ├── ineligiblePromotions[] (and reasons)                             │
│     └── totalDiscount, newSubtotal                                       │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

Database Schema

-- Promotions table
CREATE TABLE promotions (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id       UUID NOT NULL REFERENCES shared.tenants(id),
    name            VARCHAR(255) NOT NULL,
    code            VARCHAR(50),                    -- Promo code (optional)
    description     TEXT,
    type            VARCHAR(50) NOT NULL,           -- percentage, fixed_amount, etc.
    value           DECIMAL(10,2),                  -- Discount value
    applies_to      VARCHAR(50) NOT NULL,           -- all, category, item, vendor
    target_ids      TEXT[],                         -- Array of category/item IDs
    conditions      JSONB DEFAULT '{}',             -- Complex conditions
    schedule        JSONB DEFAULT '{}',             -- Scheduling rules
    limits          JSONB DEFAULT '{}',             -- Usage limits
    stacking        JSONB DEFAULT '{}',             -- Stacking rules
    location_ids    UUID[],                         -- NULL = all locations
    status          VARCHAR(20) DEFAULT 'active',   -- active, paused, expired
    created_at      TIMESTAMPTZ DEFAULT NOW(),
    updated_at      TIMESTAMPTZ DEFAULT NOW(),
    created_by      UUID REFERENCES employees(id)
);

-- Promotion usage tracking
CREATE TABLE promotion_usage (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    promotion_id    UUID NOT NULL REFERENCES promotions(id),
    sale_id         UUID NOT NULL REFERENCES sales(id),
    customer_id     UUID REFERENCES customers(id),
    location_id     UUID NOT NULL,
    discount_amount DECIMAL(10,2) NOT NULL,
    used_at         TIMESTAMPTZ DEFAULT NOW()
);

-- Indexes for performance
CREATE INDEX idx_promotions_tenant_status ON promotions(tenant_id, status);
CREATE INDEX idx_promotions_code ON promotions(code) WHERE code IS NOT NULL;
CREATE INDEX idx_promotion_usage_promo ON promotion_usage(promotion_id);
CREATE INDEX idx_promotion_usage_customer ON promotion_usage(customer_id);
CREATE INDEX idx_promotion_usage_date ON promotion_usage(used_at);

Example Promotions

1. Holiday Weekend Sale

{
  "name": "Holiday Weekend Sale",
  "code": "HOLIDAY25",
  "type": "percentage",
  "value": 25,
  "appliesTo": "category",
  "targetIds": ["cat_apparel", "cat_accessories"],
  "conditions": {
    "minPurchaseAmount": 50.00,
    "excludedCategories": ["cat_gift_cards", "cat_clearance"]
  },
  "schedule": {
    "startAt": "2025-12-20T00:00:00Z",
    "endAt": "2025-12-26T23:59:59Z"
  },
  "limits": {
    "maxUsesPerCustomer": 2
  },
  "stacking": {
    "stackable": false,
    "priority": 50
  }
}

2. Gold Member Auto-Discount

{
  "name": "Gold Member Discount",
  "code": null,
  "type": "percentage",
  "value": 10,
  "appliesTo": "all",
  "conditions": {
    "customerTypes": ["gold", "platinum"]
  },
  "schedule": {},
  "limits": {},
  "stacking": {
    "stackable": true,
    "priority": 5
  }
}

Automatically applied, no code needed, stacks with other promos.


3. Buy 2 Get 1 Free Socks

{
  "name": "Sock Sale",
  "type": "buy_x_get_y",
  "buyQuantity": 2,
  "getQuantity": 1,
  "getDiscountPercent": 100,
  "appliesTo": "category",
  "targetIds": ["cat_socks"],
  "schedule": {
    "activeDays": ["saturday", "sunday"]
  }
}

Weekend only, auto-applies when 3+ socks in cart.


4. Employee Discount

{
  "name": "Employee Discount",
  "code": "STAFF",
  "type": "percentage",
  "value": 40,
  "appliesTo": "all",
  "conditions": {
    "customerTypes": ["employee"],
    "excludedCategories": ["cat_gift_cards"]
  },
  "stacking": {
    "stackable": false,
    "priority": 100
  }
}

40% off for employees, highest priority, never stacks.


5. Flash Sale (Limited Quantity)

{
  "name": "Flash Sale - First 50",
  "type": "percentage",
  "value": 50,
  "appliesTo": "item",
  "targetIds": ["item_bomber_jacket"],
  "schedule": {
    "startAt": "2025-01-15T12:00:00Z",
    "endAt": "2025-01-15T14:00:00Z"
  },
  "limits": {
    "maxUsesTotal": 50,
    "maxUsesPerCustomer": 1
  }
}

2-hour flash sale, first 50 customers only.


API Usage Examples

Create a Promotion

POST /api/v1/promotions
Authorization: Bearer {token}

{
  "name": "Summer Sale",
  "code": "SUMMER30",
  "type": "percentage",
  "value": 30,
  "appliesTo": "category",
  "targetIds": ["cat_swimwear", "cat_sandals"],
  "schedule": {
    "startAt": "2025-06-01T00:00:00Z",
    "endAt": "2025-08-31T23:59:59Z"
  }
}

Evaluate Cart for Promotions

POST /api/v1/promotions/evaluate
Authorization: Bearer {token}

{
  "locationId": "loc_gm",
  "customerId": "cust_jane",
  "lineItems": [
    { "itemId": "item_001", "categoryId": "cat_swimwear", "quantity": 2, "unitPrice": 45.00 }
  ],
  "subtotal": 90.00,
  "appliedCodes": ["SUMMER30"]
}

Check Promotion Usage

GET /api/v1/promotions/{id}/usage?from=2025-06-01&to=2025-06-30
Authorization: Bearer {token}

Response:

{
  "promotionId": "promo_summer30",
  "period": { "from": "2025-06-01", "to": "2025-06-30" },
  "totalUses": 342,
  "totalDiscountGiven": 4567.89,
  "uniqueCustomers": 287,
  "byLocation": [
    { "locationId": "loc_gm", "uses": 145, "discount": 1890.50 },
    { "locationId": "loc_hm", "uses": 112, "discount": 1456.78 }
  ],
  "byDay": [
    { "date": "2025-06-01", "uses": 23 },
    { "date": "2025-06-02", "uses": 31 }
  ]
}

Implementation Checklist

  • Create promotions table with JSONB columns
  • Create promotion_usage tracking table
  • Implement PromotionService with evaluation logic
  • Add /api/v1/promotions endpoints
  • Build promotion editor UI in Admin Portal
  • Add promotion display to POS cart
  • Implement auto-apply for loyalty promotions
  • Add promotion analytics to Reports
  • Test stacking logic thoroughly
  • Load test evaluation endpoint

Competitive Advantage

FeatureOur SystemRetail ProLightspeed
Minute-level scheduling
Customer type targeting⚠️ Limited
Complex stacking rules
Per-location restrictions
Usage limits (total/customer/day)⚠️ Limited
Auto-apply (no code needed)⚠️ Limited
Real-time evaluation API

We match Retail Pro’s 35-year sophistication in a modern architecture.


Appendix F - Version 1.0 Based on competitive analysis of Retail Pro Prism promotions engine