Requirements
Documentation for Modules
Product Catalog Module - Requirements Specification
Module ID:
product-catalogLibrary:@libs-modules/product-catalogLocation:libs/modules/product-catalog/Base Path:/catalogPermission Namespace:catalog:*
1. Overview
Generic product catalog module that serves as the foundational item registry for the GoGrab platform. Supports physical products, digital goods, and services through a unified entity model. Designed to be consumed by downstream modules (inventory, POS/sales, e-commerce, work orders) via domain events, public actions, and embeddable components.
This module focuses exclusively on products and variants. Categories, brands, and tax configuration are provided by shared core libraries that other modules can also consume independently.
1.1 Architecture Context
libs/core/ (shared platform infrastructure)
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ taxonomy │ │ brand- │ │ fiscal │
│ │ │ registry │ │ │
│ - Categories │ │ - Brands │ │ - TaxClass │
│ - Tags │ │ - Logos │ │ - TaxRate │
│ (generic, │ │ (shared │ │ - TaxService │
│ any module) │ │ registry) │ │ (minimal) │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
└────────┬────────┴────────┬────────┘
│ │
┌──────┴─────────────────┴──────┐
│ product-catalog │
│ (libs/modules/) │
│ │
│ - Products (physical/ │
│ digital/service) │
│ - Product Variants │
│ - Product Attributes │
└───────────────────────────────┘
2. Functional Requirements
2.1 Product Entity (Core)
A single unified entity Product that supports three item types:
| Field | Type | Required | Description |
|---|---|---|---|
id |
string |
Yes | Unique identifier (UUID) |
tenantId |
string |
Yes | Multi-tenant isolation |
name |
string |
Yes | Product display name |
slug |
string |
Yes | URL-friendly identifier (auto-generated, editable) |
description |
string |
No | Full product description |
shortDescription |
string |
No | Brief description for listings |
sku |
string |
Yes | Stock Keeping Unit (unique per tenant) |
barcode |
string |
No | UPC/EAN barcode |
type |
ProductType |
Yes | 'physical' | 'digital' | 'service' |
status |
ProductStatus |
Yes | 'draft' | 'active' | 'discontinued' |
categoryId |
string |
No | Reference to @libs-core/taxonomy category |
brandId |
string |
No | Reference to @libs-core/brand-registry brand |
tags |
string[] |
No | References to @libs-core/taxonomy tags |
unit |
UnitOfMeasure |
Yes | 'piece' | 'kg' | 'g' | 'liter' | 'ml' | 'meter' | 'hour' | 'session' | 'service' | 'unit' |
taxClassId |
string |
Yes | Reference to @libs-core/fiscal tax class |
hasVariants |
boolean |
Yes | Whether product uses variant system |
basePrice |
number |
Yes* | Base selling price (*required if hasVariants === false) |
costPrice |
number |
No | Cost/purchase price |
compareAtPrice |
number |
No | Original price for showing discounts |
currency |
string |
Yes | ISO 4217 currency code (default from tenant config) |
weight |
number |
No | Weight value (for physical products) |
weightUnit |
WeightUnit |
No | 'kg' | 'g' | 'lb' | 'oz' |
dimensions |
ProductDimensions |
No | { length, width, height, unit } |
isOnlineAvailable |
boolean |
Yes | Available for e-commerce (default: true) |
isFeatured |
boolean |
Yes | Featured/highlighted product (default: false) |
sortOrder |
number |
No | Manual sort position |
metaTitle |
string |
No | SEO meta title |
metaDescription |
string |
No | SEO meta description |
metaKeywords |
string[] |
No | SEO keywords |
notes |
string |
No | Internal notes (not public) |
createdAt |
Date |
Yes | Creation timestamp |
updatedAt |
Date |
Yes | Last update timestamp |
createdBy |
string |
Yes | Creator user ID |
updatedBy |
string |
Yes | Last modifier user ID |
2.2 Product Variants
When hasVariants === true, the product acts as a parent with child variant entities:
Product Attribute (defines variation axes):
| Field | Type | Required | Description |
|---|---|---|---|
id |
string |
Yes | Unique identifier |
productId |
string |
Yes | Parent product reference |
name |
string |
Yes | Attribute name (e.g., "Size", "Color") |
values |
string[] |
Yes | Possible values (e.g., ["S", "M", "L", "XL"]) |
sortOrder |
number |
No | Display order |
Product Variant (concrete SKU per attribute combination):
| Field | Type | Required | Description |
|---|---|---|---|
id |
string |
Yes | Unique identifier |
productId |
string |
Yes | Parent product reference |
sku |
string |
Yes | Variant-specific SKU (unique per tenant) |
barcode |
string |
No | Variant-specific barcode |
attributeValues |
Record<string, string> |
Yes | Attribute selections (e.g., { "Size": "M", "Color": "Red" }) |
price |
number |
Yes | Variant selling price |
costPrice |
number |
No | Variant cost price |
compareAtPrice |
number |
No | Variant original price |
weight |
number |
No | Variant-specific weight |
isActive |
boolean |
Yes | Whether this variant is available |
sortOrder |
number |
No | Display order |
imageId |
string |
No | Variant-specific image reference |
Variant Rules:
- When
hasVariants === true,basePriceon the parent product is optional (variants define prices) - Variants are auto-generated from attribute combinations or manually created
- Each variant has its own SKU, price, and optional physical attributes
- Deleting a parent product cascades to all variants
- At least one active variant required for an active product with variants
2.3 Media Integration
- Full gallery support per product via
@libs-core/mediaintegration - Multiple images per product with ordering and primary image designation
- Variant-specific images (optional, linked by
imageId) - Uses
GgsEntityMediaSectionComponentfor upload/gallery in product form
3. Product Lifecycle (Workflow)
3.1 Status Transitions
┌──────────┐
│ Draft │ (initial state)
└────┬─────┘
│ publish
┌────┴─────┐
│ Active │◀────┐
└────┬─────┘ │ reactivate
│ │
discontinue│ ┌─────┴──────────┐
┌────┴─────┴────────────────┘
│ Discontinued │
└────────────────┘
| Transition | From | To | Permission | Description |
|---|---|---|---|---|
publish |
draft |
active |
catalog:publish |
Make product available |
discontinue |
active |
discontinued |
catalog:manage |
Remove from active catalog |
reactivate |
discontinued |
active |
catalog:manage |
Return to active catalog |
3.2 Transition Guards
- publish: Product must have at least name, SKU, and price (or active variants with prices)
- discontinue: Warn if product has active inventory (event to inventory module)
- reactivate: No special guards
4. Integration Points
4.1 Domain Events (Product-Catalog Only)
| Event | Payload | Trigger |
|---|---|---|
catalog.product.created |
{ product, createdBy, tenantId } |
Product created |
catalog.product.updated |
{ product, changes, updatedBy, tenantId } |
Product updated |
catalog.product.deleted |
{ productId, tenantId } |
Product deleted |
catalog.product.status-changed |
{ productId, fromStatus, toStatus, tenantId } |
Status transition |
catalog.variant.created |
{ variant, productId, tenantId } |
Variant created |
catalog.variant.updated |
{ variant, productId, changes, tenantId } |
Variant updated |
catalog.variant.deleted |
{ variantId, productId, tenantId } |
Variant deleted |
Category, brand, and tax events are published by their respective core libraries (
taxonomy,brand-registry,fiscal).
4.2 Public Actions
| Action ID | Description | Parameters | Permission |
|---|---|---|---|
catalog:get-product |
Retrieve product by ID | { productId } |
catalog:read |
catalog:search-products |
Search/filter products | { query, filters, pagination } |
catalog:read |
catalog:get-variants |
Get variants for product | { productId } |
catalog:read |
catalog:create-product |
Create new product | { product data } |
catalog:create |
Category/brand lookup actions are provided by
@libs-core/taxonomyand@libs-core/brand-registry.
4.3 Embeddable Components
| Component ID | Description | Inputs |
|---|---|---|
catalog:product-lookup |
Product search and selection widget | limit, typeFilter, categoryFilter, multiSelect, showPrice, showVariants |
catalog:product-card |
Single product display card | productId, showPrice, showImage, compact |
Category tree and brand selector embeddables are provided by their respective core libraries.
4.4 Downstream Module Integration Patterns
Inventory Module (future):
- Listens to
catalog.product.createdto initialize stock records - Listens to
catalog.variant.createdto create variant stock entries - Listens to
catalog.product.status-changedto update availability - Uses
catalog:product-lookupfor stock adjustment forms
Sales/POS Module (future):
- Uses
catalog:product-lookupfor item selection in transactions - Uses
catalog:search-productsaction for barcode scanning - Reads product price and tax class (via
@libs-core/fiscal) for invoice calculation
E-commerce Module (future):
- Reads full product data (images, SEO, variants) for storefront
- Filters by
isOnlineAvailable === true - Uses
taxonomy:category-treeembeddable for storefront navigation
Work Orders Module (future):
- Uses
catalog:product-lookupfiltered bytype: 'service' - Creates service line items from catalog entries
5. Permissions (Product-Catalog Only)
| Permission | Description |
|---|---|
catalog:read |
View products and variants |
catalog:create |
Create new products |
catalog:update |
Edit existing products |
catalog:delete |
Delete products |
catalog:publish |
Publish draft products to active |
catalog:manage |
Full management (discontinue, reactivate) |
catalog:variants:manage |
Create/edit/delete product variants |
Category, brand, and tax permissions are managed by their respective core libraries.
6. User Stories
Products
- US-01: As a catalog manager, I can create a new product (physical/digital/service) with all required fields so that it appears in the catalog.
- US-02: As a catalog manager, I can edit product details (name, description, price, images, etc.) to keep the catalog up to date.
- US-03: As a catalog manager, I can publish a draft product to make it active and available to other modules.
- US-04: As a catalog manager, I can discontinue a product so it's no longer available but preserved in historical records.
- US-05: As a catalog manager, I can reactivate a discontinued product to make it available again.
- US-06: As a catalog manager, I can delete a draft product that was never published.
- US-07: As a catalog manager, I can search and filter the product list by name, SKU, type, status, category, brand, and tags.
- US-08: As a catalog manager, I can view product details including all fields, images, variants, and status history.
- US-09: As a catalog manager, I can upload multiple images for a product and designate a primary image.
- US-10: As a catalog manager, I can assign a category (from taxonomy) and brand (from brand-registry) to a product.
Variants
- US-11: As a catalog manager, I can define product attributes (e.g., Size, Color) with their possible values.
- US-12: As a catalog manager, I can generate variants from attribute combinations automatically.
- US-13: As a catalog manager, I can set individual prices, SKUs, and active status per variant.
- US-14: As a catalog manager, I can deactivate specific variants without affecting the parent product.
Integration
- US-15: As a developer of another module, I can embed a product lookup widget to let users select products.
- US-16: As a developer of another module, I can listen to catalog domain events to react to product changes.
- US-17: As a developer of another module, I can invoke public actions to query or create products programmatically.
7. Non-Functional Requirements
- NF-01: All components use standalone Angular 20 with OnPush change detection.
- NF-02: State management follows Signals (primary) → RxJS (async) hierarchy.
- NF-03: Multi-tenant isolation on all entities and operations.
- NF-04: Full mock API for development without backend.
- NF-05: Minimum 90% test coverage on business logic.
- NF-06: Integration with workflow-engine for status transitions.
- NF-07: Integration with media library for image galleries.
- NF-08: All API operations use RxJS Observables (no Promises).
- NF-09: SKU uniqueness enforced per tenant.
- NF-10: Slug auto-generation from product name with uniqueness check.
8. Open Questions
- Currency handling: Should the module support multi-currency per tenant, or single currency per tenant with a global setting?
- Product duplication: Should there be a "clone product" feature for quickly creating similar products?
- Bulk operations: What bulk operations are needed beyond delete? (bulk status change, bulk price update, bulk category assign?)
- Import/Export: Is CSV/Excel import/export needed for initial data loading?
- Product relations: Should products support "related products" or "frequently bought together" associations?
- Audit trail: Beyond createdBy/updatedBy, do we need a full change history log for products?
9. Module Dependencies
@libs-modules/product-catalog
├── @libs-core/taxonomy (categories, tags) ← NEW
├── @libs-core/brand-registry (brands) ← NEW
├── @libs-core/fiscal (tax classes, rates) ← NEW
├── @libs-core/module-base (contracts, CRUD composables, base components)
├── @libs-core/event-system (domain events)
├── @libs-core/security (permissions, guards, tenant context)
├── @libs-core/log-system (logging)
├── @libs-core/utilities (string utils, validation, date utils)
├── @libs-core/shared-models (BaseEntity, pagination, API types)
├── @libs-core/data-access (repositories, base repository)
├── @libs-core/ui-framework (UI components, forms, dialogs)
├── @libs-core/workflow-engine (product status transitions)
├── @libs-core/media (product images, gallery)
├── @libs-core/action-registry (public actions)
└── @libs-core/mock-api (development mock data)