Catalogue & product data
Catalog Price Rules (Scheduled)
Generated from docs/plan/core/catalog-price-rules/PLAN.md in the AstroBaaS repository. Nothing described below is implemented — it is the written plan for it.
Scheduled, attribute-based pricing rules applied to products without manual editing each SKU. A rule like ‘category: Sunglasses, 20% off, Friday–Sunday’ automatically reduces prices for all 400 sunglasses on Friday, expires Sunday. Core promotion engine (like cart price rules) but applied at catalog index time, not cart time. Essential for seasonal and bulk promotions without manual bulk edits.
The problem
I run weekend sales on sunglasses — I need to reduce 400 product prices by 20% Friday–Sunday without editing each one. I used to bulk-edit prices manually, but now I forget to remove the discount on Monday. I need a rule I set once and forget; prices auto-apply and auto-revert.
What it does
- Catalog price rule: condition (e.g., category=‘Sunglasses’), discount (20% off or fixed €50 off), start/end dates and times
- Rule application: applied at product index time (when products are fetched for search/listing), not stored on product
- Discount types: percentage off, fixed amount off, fixed price (sell at €X regardless of cost)
- Condition types: category, attribute values (brand=Nike), price range (€100-500), product IDs, tags, or combinations (AND/OR logic)
- Multiple rules per product: if two rules match, highest discount wins (or stack, merchant chooses)
- Rule scheduling: recurring (every weekend), one-time (Black Friday 2024-11-29), or always-on (clearance)
- Rule UI in admin: visual rule builder (if category=‘Sunglasses’ then 20% off from Fri 5PM to Sun 11:59PM)
- Rule preview: show affected product count (‘150 products will be discounted’) and sample products before saving
- Rule conflict warnings: if two rules overlap and both discount, show potential stacking behavior
- Rule reporting: show which products are currently under a rule, revenue impact per rule
What it deliberately does NOT do
Each boundary carries its reason. A boundary without a reason gets crossed by the next person who reads this.
- Cart-level price rules (buy-X-get-Y, quantity discounts) — those are cart price rules, a separate feature.
- Customer segment-based pricing (VIP gets different prices) — that is customer segments pricing module.
- Dynamic pricing (AI adjusts prices per demand) — that is pricing intelligence module.
- Time-zone aware scheduling — that is complex scheduling; core uses UTC only.
- Tiered discounts per product (buy 1 at €10, buy 5 at €8) — that is tiered pricing, separate feature.
Data model
New CATALOG_PRICE_RULE table (rule_id, name, conditions JSON, discount_type (percent|fixed|fixed_price), discount_value, start_at, end_at, recurrence (if recurring), priority, enabled). Migration required: none (new table).
API
- POST /admin/catalog-price-rules (create rule)
- GET /admin/catalog-price-rules (list rules with current status)
- PATCH /admin/catalog-price-rules/:id (update rule conditions/discount/dates)
- DELETE /admin/catalog-price-rules/:id (delete rule)
- POST /admin/catalog-price-rules/:id/preview (return affected product count + sample products)
- GET /products (prices returned include catalog rule discount; GET /products/:id shows discount_rule_ids array)
Every route added here must also appear in src/pages/openapi.json.ts — a test
fails the build if it does not.
Admin
Catalog Price Rules page with table of active/scheduled/expired rules. Create rule button. Rule builder UI: condition picker (if category=‘Sunglasses’ and price > €100), discount type/value, date/time range, recurrence (if recurring). Preview button shows matching product count and 5 sample products. Rule status column shows ‘Active’, ‘Scheduled’, or ‘Expired’.
The seam — why this is core
Core owns catalog price rule engine, condition matching, and price application at index time. Core does not own cart-level rules (that is cart price rules) or segment-based pricing (that is customer segments). Core owns the interface; fulfillment/reporting consume discounted prices.
Real gap. Without this, bulk price edits are manual. Built on same price-rule engine as cart rules. Enables time-bound catalogue pricing.
Dependencies
- flexible-product-attributes (for condition matching on attributes)
- Product indexing/search (for applying rules at query time)
Acceptance checks
Each of these must be able to fail. Before claiming this is done, break the code deliberately and watch each one go red.
- Rule ‘Weekend Sunglasses 20% Off’ created: condition (category=‘Sunglasses’), discount (20%), schedule (Fri 5PM-Sun 11:59PM, recurring weekly)
- Rule is active Friday 5PM; all Sunglasses products returned by GET /products have discounted price (original_price, discount_price, discount_rule_id shown)
- Rule expires Sunday 11:59PM; Monday morning, Sunglasses prices revert to original (no manual action required)
- Preview before save shows ‘150 products will be discounted’ (count of Sunglasses), sample of 5 products
- Two rules overlap (Weekend 20% off Sunglasses, Summer 15% off all): highest discount (20%) is applied; both rule IDs shown in product response
- Rule conflict warning shown: ‘Rule A (25% off) and Rule B (€50 off) both apply to 10 products; Rule A wins (25% > €50 off)’
- Rule reporting shows: ‘Weekend Sunglasses 20% Off active for 150 products, estimated revenue impact +€5000 this weekend’
Risks
If rules are not applied atomically, product prices flicker between discounted and full price. Mitigate: batch rule application on a schedule, cache results. If rule end time passes and discount doesn’t auto-revert, prices stay discounted. Mitigate: schedule automatic rule deactivation at end_at timestamp. If rule condition matching is slow (>1s per product), search is sluggish. Mitigate: pre-compute rule matches at index time, not query time. If multiple rules stack incorrectly, customer pays wrong price. Mitigate: define stacking order (first match wins or highest discount wins) and log applied rules in order.
Commercial context
| Suggested price | Free; core promotion |
| Rival anchor | Magento Open Source Catalog Price Rules; free |
The anchor is what the nearest equivalent charges on Shopify or Magento today. It is context for a pricing decision, not the decision.
Generated from the commerce plan. See docs/COMMERCE-PLAN.md for the full
catalogue and ../../AI-GUIDE.md for how to work on this repository.