Bestseller by Size
Bestseller by Size groups sales by product and then by size variant, ranking which sizes sell fastest (or slowest) within each style — a quick way to see, for example, that a shirt's Medium and Large sell out while Small and XXL barely move, informing your next reorder.
Overview
Sales in the chosen date range are grouped first by product, then by size — size is resolved by matching each sale item's product SKU to a ProductVariant record and reading a "size" (or "Size") key out of that variant's attributes JSON; if no matching variant or size attribute is found, the size shows as "—".
A Bestsellers / Slow Movers toggle re-sorts the flat list of product+size rows — Bestsellers shows highest units-sold first, Slow Movers shows lowest units-sold first — then the sorted rows are grouped back into per-product cards for display.
Each product card lists its sizes as rows with Units Sold, Revenue, and a proportional bar under a "Rank" column.
The default date range is January 1st of the current year through today, and only completed sales (payment status COMPLETED, type SALE) are counted, same as Style Contribution.
Before You Start
- Requires the "reports" plan feature to be enabled.
- Size detection depends on your ProductVariant records actually having a "size"/"Size" key in their attributes JSON and a SKU that matches what was recorded on the sale item — products without size-tagged variants will show "—" instead of a real size.
Step-by-Step Guide
1 Find your fastest and slowest selling sizes
- Set the From/To dates (defaults to Jan 1st this year through today) and click Apply.
- Use the Bestsellers / Slow Movers toggle in the top-right of the filter row to switch ranking direction.
- Scan each product card — sizes are listed in rank order for that toggle, with a units-sold figure, revenue, and a proportional bar.
2 Export the size breakdown
- Click Export CSV (disabled until there is at least one row of data) to download Product, Size, Units Sold and Revenue as a CSV, in whichever sort order (Bestsellers/Slow Movers) is currently selected.
Every Field & Button, Explained
| Field / Button | What it does |
|---|---|
From / To + Apply | Date range for the sales data; defaults to Jan 1st of the current year through today. |
Bestsellers / Slow Movers toggle | Re-sorts all product+size rows by units sold descending (Bestsellers) or ascending (Slow Movers) before regrouping them by product. |
Export CSV | Downloads Product, Size, Units Sold, Revenue for every row in the current sort order; disabled until at least one row exists. |
Product group header | Groups all size rows for one product under its name. |
Size | Resolved from the matching ProductVariant's attributes.size/Size; shows "—" if no matching variant or size attribute is found for that sale item's SKU. |
Units Sold / Revenue | Total quantity and revenue for that specific product+size combination in the selected period. |
Rank (bar) | A proportional bar scaled against the current view's reference row — see the known bar-scaling quirk in Troubleshooting for Slow Movers view. |
Medal icons | 🥇/🥈/🥉 shown on the top 3 rows within a product's size list — see the workflow note above for a display quirk affecting the 2nd/3rd medals in Slow Movers view. |
Tips & Best Practices
- Use Bestsellers view before placing a reorder — it tells you exactly which sizes within a style are actually selling so you don't over-order sizes that sit on the shelf.
- Cross-check Slow Movers against Age of Inventory before writing off a size as unpopular — a size might rank low here simply because very few units were stocked in the period, not because it doesn't sell.
- Export the CSV when reviewing with a supplier or buyer so the exact numbers (not just the visual bars) are on record.
Troubleshooting & FAQ
A size shows as "—" instead of S/M/L/XL.
In Slow Movers view, the 2nd and 3rd rows under a product show medal emojis even though they aren't the true worst sellers.
In Slow Movers view, most of the progress bars look maxed out or clipped.
A product is missing from the report entirely.
🧑💻 Developer Notes
Source component(s):
frontend-app/pos-app-garment/src/pages/garment/BestsellerBySizePage.jsx
Backend endpoints used:
GET /garment/analytics/bestseller-by-size?from=&to=
Related tables (db-core repositories):
SaleSaleItemProductProductVariant
Redux slices:
None — local component state only
Feature flag key: reports
Backed by packages/pos-core/src/garment.js getBestsellerBySize (~line 696): joins Sale/SaleItem/Product for COMPLETED SALE-type transactions in range, then separately loads ProductVariant rows (filtered by tenantId when available) and builds a sku -> size lookup by JSON-parsing each variant's attributes column for an "size"/"Size" key. Rows are keyed by `${productId}_${size}` so the same product with an unresolved size ("—") is aggregated into one bucket. Two client-side bugs found in BestsellerBySizePage.jsx worth fixing: (1) the medal ternary `idx === 0 && view === 'bestseller' ? '🥇' : idx === 1 ? '🥈' : idx === 2 ? '🥉' : null` only gates the gold medal on view, so silver/bronze still render in Slow Movers mode; (2) `maxQty = sorted.length > 0 ? sorted[0].qtySold : 1` is taken from the head of the already mode-sorted array, which is the minimum (not the maximum) qtySold when view === 'slowmover', so bar-width percentages exceed 100% and clip visually for most rows in that view.