Home / Help Center / pos-app-garment / garment / Markdown Rules
🏷️ garment

Markdown Rules

Markdown Rules automatically identifies aging seasonal stock (e.g. SS25 stock unsold after 30 days) and calculates a tiered discount for it — you review the exact products and new prices in a Preview before ever touching a live price, then Apply the rule to write those new prices to your catalog in one click.

📍 Menu path: /garment/markdown-rules
👤 Who uses it: Owner,Manager

Overview

A rule is built from up to three discount tiers — Tier 1, Tier 2 and Tier 3 — each pairing a "days old" threshold with a discount percentage (e.g. 30 days → 10% off, 60 days → 20% off, 90 days → 30% off). Only Tier 1 is required; Tiers 2 and 3 are optional for a steeper multi-stage clearance.

A rule can optionally be scoped to a Season (e.g. "SS25") and/or a comma-separated list of Categories (e.g. "Shirts, Trousers, Jackets"); leaving both blank means the rule considers every active product in the catalog.

The "days old" a product is judged against is calculated from that product record's creation date in the system (Product.createdAt) — not a separate "date put on the floor" or "last restocked" date. A product record created long ago will keep aging even if you have since replenished its physical stock.

Every rule carries an Action label — "Suggest only" or "Priority — apply first when reviewing" — but this is a self-documenting label only: nothing in the backend runs on a schedule to apply rules automatically. Both action types require a staff member to open the rule and click Apply; the label just helps a team decide which rules to review first.

Only the single highest threshold a product has crossed applies — tiers do not stack. A product that is 95 days old on a rule with Tier 1 = 30 days/10% and Tier 3 = 90 days/30% gets the 30% discount only, never 10% + 30%.

Before You Start

Step-by-Step Guide

1 Create a markdown rule

  1. Click + New Rule.
  2. Enter a Rule Name * (e.g. "SS25 End-of-Season Clearance").
  3. Optionally set Season (e.g. "SS25") and Categories (comma-separated; leave blank to match every category).
  4. Set Tier 1 (First threshold) — Days old and Discount % — and optionally Tier 2 and Tier 3 for steeper multi-stage discounts as stock ages further.
  5. Choose an Action label: "Suggest only" or "Priority — apply first when reviewing" (see the note below — both require you to manually click Apply).
  6. Add any Notes and click Create Rule.
💡 Neither Action option triggers anything automatically — treat "Priority — apply first when reviewing" purely as a label for your own team's workflow (e.g. "we always approve this one without discussion") rather than an actual automated job.

2 Preview a rule before touching any prices

  1. Click Preview on any rule card.
  2. A panel opens below the rule list showing "Preview — N products affected" and a table of every matching product: Product, SKU, Days Old, Current Price, New Price and the Discount % badge.
  3. Preview makes no changes whatsoever — it is a read-only simulation you can open and close (✕) as many times as you like without side effects.

3 Apply a rule (permanent price change)

  1. Click Apply on the rule card (or from an open Preview).
  2. Confirm the prompt "Apply this rule? Product prices will be updated permanently." — this is your only safety check.
  3. On success, a toast reports "Applied to N products" and the rule's "Last applied" date updates; every matching product's price is now overwritten with the discounted price shown in Preview.
💡 Products with no price set (null or 0) are silently skipped from the price update — they still count toward the rule's matched products in Preview, but are excluded from the "applied" count on Apply.

4 Manage existing rules

  1. Click Disable/Enable on a rule to toggle it without deleting it — disabled rules are shown dimmed in the list, but they still work if you press Preview or Apply on them (disabling does not currently block those actions, it is purely a visual/organisational flag).
  2. Click Edit to change any field on an existing rule.
  3. Click Delete and confirm to permanently remove a rule.

Every Field & Button, Explained

Field / ButtonWhat it does
+ New RuleOpens a blank markdown rule form.
Rule Name *Required label for the rule, e.g. "SS25 End-of-Season Clearance".
SeasonOptional — restricts the rule to products whose Season field matches exactly; leave blank to ignore season.
CategoriesOptional comma-separated list (e.g. "Shirts, Trousers"); stored as a JSON array; leave blank to match every category.
Action"Suggest only" or "Priority — apply first when reviewing" — a label only; neither runs automatically, both require clicking Apply manually.
Tier 1/2/3 — Days oldThe minimum age (in days since the product record was created) a product must reach for that tier's discount to apply. Tier 1 is required; Tier 2/3 are optional.
Tier 1/2/3 — Discount %The percentage price cut applied once a product reaches that tier's day threshold. Only the single highest threshold crossed applies — tiers never stack.
NotesFree-text notes on the rule.
PreviewRead-only simulation — lists every currently-matching product with its current price, calculated new price, discount % and days old, with zero effect on live data.
ApplyPermanently writes the discounted price to every matching product's price field and stamps the rule's "Last applied" date; requires a confirmation click.
Disable / EnableToggles the rule's isActive flag; dims the card in the list, but does not currently block Preview or Apply from still being run on a disabled rule.
Last appliedShows the date Apply was last successfully run on this rule, or "Never".
Edit / DeleteEdit reopens the form pre-filled; Delete permanently removes the rule after a confirmation prompt.

Tips & Best Practices

Troubleshooting & FAQ

I set the Action to "Priority — apply first when reviewing" but prices never changed on their own.
This label is intentionally manual — there is no background job or scheduler in BazaarPOS that applies markdown rules automatically. Use it to flag which rules your team should review and Apply first, then still open the rule and click Apply (or Preview first) whenever you want the price change to actually happen.
Preview shows 0 products affected.
Check that Season and Categories (if set) actually match values on your products exactly, and remember "days old" is measured from the product's creation date in the system, not a restock date — a product may be older in the shop than its record suggests, or vice versa.
I applied a rule but the "applied" count was lower than the "affected" count in Preview.
Products with no price set (null or zero) are matched and shown in Preview, but are skipped during Apply since there is no base price to discount from — they count toward the difference between "affected" and "applied".
Can I undo an Apply?
No — there is no built-in rollback. Applying overwrites the product price directly; if you need to reverse it, you will need to manually re-enter the original prices (Preview's "Current Price" column, checked beforehand, is your best record of what they were).
A product got a smaller discount than I expected given how old it is.
Only the single highest day-threshold tier that has been crossed applies — tiers are not cumulative. Check which of Tier 1/2/3 the product's age actually falls into.

🧑‍💻 Developer Notes

Source component(s):

  • frontend-app/pos-app-garment/src/pages/garment/MarkdownRulesPage.jsx

Backend endpoints used:

  • GET /garment/markdown-rules
  • POST /garment/markdown-rules
  • PUT /garment/markdown-rules/:id
  • DELETE /garment/markdown-rules/:id
  • GET /garment/markdown-rules/:id/preview
  • POST /garment/markdown-rules/:id/apply

Related tables (db-core repositories):

  • GarmentMarkdownRule
  • Product

Redux slices:

  • None — local component state only (useState/useCallback)

Feature flag key: promotions

Backed by packages/pos-core/src/garment.js (previewMarkdownRule / applyMarkdownRule, ~line 375+). previewMarkdownRule computes daysOld from Product.createdAt and picks the applicable tier by iterating tiers highest-threshold-first, stopping at the first one daysOld satisfies (no stacking). applyMarkdownRule re-runs previewMarkdownRule internally then writes new prices in a simple loop with no transaction wrapper, skipping any item whose newPrice is null. previewMarkdownRule's product query now also filters by tenantId and by the rule's own storeId (matching the pattern already used by getStyleContribution/getBestsellerBySize) — a prior version queried every active product with no tenant/store scoping at all, which could preview/apply pricing changes against another tenant's products in a shared database; this has been fixed. The "Priority — apply first when reviewing" action value is stored and displayed but never read by any scheduler/cron in the codebase — by design it is a review-order label only, not automation, and the UI now says so explicitly.