Saltar al contenido principal

Collections

Collections are fundamental in apparel because products are not only grouped by category — they follow a seasonal and merchandising lifecycle. Collections connect merchandising, markdown strategy, replenishment, promotions, and analytics. If variants are the operational core, collections are the merchandising core.

What "Collection" Means (Business Meaning)

A collection groups products that belong to a merchandising concept. Examples: Summer 2025, Back to School, Denim Essentials, Winter Jackets, Holiday Drop, Brand Capsule.

Collections are not categories.

  • Category = product type (jeans, shirts) — structural taxonomy
  • Collection = merchandising context (season, campaign) — lifecycle grouping

Collections are not tags.

  • Tags = flexible labeling, help filtering
  • Collections = strategic grouping, drive decisions

Both distinctions are important. Do not merge them.


Implementation Status

Status: Fully Implemented (MVP) Feature Branch: 022-merchandising-collections


Database Schema

Migration: packages/backend/database/src/migrations/2026-03-21t02-00-00-collection-management.mjs

Enums

collection_season: spring, summer, fall, winter, holiday, capsule, year_round, custom

collection_status: planning, active, ended, archived

TypeScript enums: packages/global/enums/collection.enums.tsCollectionSeason, CollectionStatus

collection Table

ColumnTypeNotes
iduuid PKgen_random_uuid()
business_iduuid FKCASCADE delete, multi-tenant scope
namevarchar(255)unique per business (uq_collection_name_business)
descriptiontextoptional
seasoncollection_seasonenum
custom_season_namevarchar(100)required when season = custom
yearintegere.g. 2026
statuscollection_statusdefault planning
launch_attimestamptzoptional, auto-set on activation if null
end_attimestamptzoptional
buyer_notestextoptional merchandising notes
priorityintegerschema-only, no UI yet
planned_markdown_startdateschema-only, no UI yet
target_sell_through_pctnumeric(5,2)schema-only, no UI yet
created_byuuid FKuser who created
updated_byuuid FKuser who last updated
created_attimestamptzdefault now
updated_attimestamptzdefault now

Indexes: idx_collection_business (business_id), idx_collection_status (status), idx_collection_season_year (season, year)

product_collection Join Table

ColumnTypeNotes
iduuid PKgen_random_uuid()
product_iduuid FKCASCADE delete
collection_iduuid FKCASCADE delete
created_byuuid FKuser who assigned
created_attimestamptzdefault now

Unique constraint: uq_product_collection (product_id, collection_id) — duplicate assignments silently ignored via ON CONFLICT DO NOTHING


Collection Lifecycle (State Machine)

Planning  -->  Active  -->  Ended  -->  Archived
| ^
+------------> Ended ----------------+

Valid transitions:

  • planning -> active (launch)
  • planning -> ended (skip activation)
  • active -> ended (close)
  • ended -> archived (archive — returns stock warning if products have remaining inventory)

Constraints:

  • Archived collections are read-only (update returns 400)
  • Cannot assign products to archived collections
  • launch_at is auto-set when activating if not already set

Backend Architecture

Module: apps/backend/src/collections/ — hexagonal architecture

collections/
collections.module.ts
application/
collections.service.ts # Business logic, state machine, validation
domain/
collections-repository.domain.ts # Repository interface (port)
infrastructure/
collections.repository.ts # Kysely implementation (adapter)
interfaces/
collections.controller.ts # HTTP endpoints
dtos/
create-collection.dto.ts
update-collection.dto.ts
update-collection-status.dto.ts
assign-products.dto.ts
query/
paginate-collections.query.ts

API Endpoints

All endpoints require authentication. Guarded with @PermissionResource(PolicyResource.Collection).

MethodPathDescriptionPermission
POST/collectionsCreate collectionCreate
GET/collectionsList collections (paginated, filterable)Read
GET/collections/:idGet collection by IDRead
PATCH/collections/:idUpdate collectionUpdate
PATCH/collections/:id/statusTransition statusUpdate
POST/collections/:id/productsAssign products (bulk)Update
DELETE/collections/:id/products/:productIdRemove productUpdate
GET/collections/:id/productsList collection productsRead
GET/products/:id/collectionsList product's collectionsRead

Query Parameters (GET /collections)

ParamTypeDescription
businessIduuidRequired, multi-tenant scope
pagenumberDefault 1
sizenumberDefault 20
searchstringilike on name, description
statusenumFilter by collection status
seasonenumFilter by season
yearnumberFilter by year
orderBystringname, status, launchAt, createdAt, year
orderasc/descSort direction

CASL Permissions

RolePermission
AdministratorAll (CRUD)
StoreManagerCreate, Read, Update
CashierRead only

Frontend (PWA) Implementation

New Files

FilePurpose
types/collection.tsTypeScript interfaces
services/collectionService.tsAPI service (CRUD, assignment, queries)
hooks/useCollections.tsTanStack Query hooks with cache invalidation
components/forms/collection/CollectionForm.tsxCreate/edit dialog (React Hook Form + Zod)
components/forms/collection/CollectionList.tsxTable with sort, filter, search, pagination
components/forms/collection/CollectionsPage.tsxContainer page composing list + form + status dialogs
components/forms/collection/CollectionDetail.tsxDetail view with product list management
components/forms/collection/ProductMultiPicker.tsxSearchable multi-select for bulk product assignment
components/forms/collection/ProductCollectionsTab.tsxTab in product form showing collection memberships

Modified Files

FileChange
pages/MainPage.tsxAdded routes: /forms/CollectionsPage, /forms/CollectionDetailPage
components/forms/product/ProductForm.tsxAdded "Collections" tab
components/discovery/DiscoveryCatalogContent.tsxAdded collection filter dropdown
services/discoveryService.tsAdded collectionId query param
i18n/locales/en.jsonCollection translation keys
i18n/locales/es.jsonCollection translation keys (Spanish)

The Collections page is accessed via sidebar navigation. The sidebar is driven by Firebase Remote Config (pwaMenu), so a new entry must be added there pointing to /forms/CollectionsPage.

User Flows

Collections Management — Create, edit, filter/search collections. Advance lifecycle status with confirmation dialogs. Archive shows stock warning.

Product Assignment — From collection detail, use "Add Products" to open the multi-picker. Search and select multiple products, confirm to bulk-assign. Remove individual products with the trash button.

Product Detail — The "Collections" tab on any product shows which collections it belongs to, with remove buttons.

Catalog Filtering — In the Discovery/Catalog view, a "Collections" dropdown appears alongside the category filter. Selecting a collection filters products to only those in that collection. Combinable with category, search, and stock filters. Backend uses a subquery through product_collection to filter.


Edge Cases Handled

ScenarioBehavior
Duplicate collection name (same business)409 Conflict — validated in service before insert
Duplicate product assignmentSilently ignored (ON CONFLICT DO NOTHING), returns assigned/skipped counts
Edit archived collection400 Bad Request — COLLECTION_ARCHIVED_READ_ONLY
Assign to archived collection400 Bad Request
Planning -> Ended (skip Active)Allowed — valid transition in state machine
Archive with remaining stockReturns warning with product count and total stock units
Deactivated products in collectionShown with isActive: false badge in product list
Products in multiple collectionsFully supported — many-to-many relationship
Delete collection with productsCASCADE delete removes product_collection rows

Phase 2 (Future Enhancements)

These fields exist in the schema but have no UI or business logic yet:

  • priority — collection priority ranking
  • planned_markdown_start — planned date for markdown waves
  • target_sell_through_pct — target sell-through percentage

Future capabilities:

  • Collection lifecycle tracking and timeline visualization
  • Assortment planning per collection
  • Size curve analytics per collection
  • Auto markdown by collection
  • Replenishment strategy per collection
  • Cross-location allocation
  • Campaign linkage
  • Collection-level analytics in Metabase (revenue, sell-through rate, margin, aging inventory, markdown impact)