Categories Module
Overview
The categories module manages product and service categories within FlowPOS. Categories are used to organize the catalog and support filtering, reporting, and merchandising.
Categories are business-scoped: every category belongs to a specific business and is isolated from other businesses.
Architecture
The module follows Hexagonal Architecture with strict layer boundaries.
categories/
├── categories.module.ts # NestJS module wiring
├── domain/
│ └── categories-repository.domain.ts # ICategoriesRepository port
├── application/
│ └── categories.service.ts # Use cases (CRUD + delete-with-replacement)
├── infrastructure/
│ └── categories.repository.ts # Kysely DB adapter (implements port)
└── interfaces/
├── categories.controller.ts # HTTP adapter
├── dtos/
│ ├── create-category.dto.ts
│ ├── update-category.dto.ts
│ └── delete-category.dto.ts
└── query/
└── paginate-categories.query.ts
Layer responsibilities
| Layer | Responsibility |
|---|---|
domain/ | Repository port (ICategoriesRepository). |
application/ | CategoriesService — orchestrates CRUD and the delete-with-replacement use case (transactional reassignment of products/services). |
infrastructure/ | CategoriesRepository — Kysely queries against the category table. Implements ICategoriesRepository. |
interfaces/ | CategoriesController — maps HTTP requests to service calls. Thin layer with no business logic. |
Dependency rule
interfaces → application → domain ← infrastructure
Domain Concepts
Category
A named grouping used to classify products and services.
| Field | Type | Description |
|---|---|---|
id | UUID | Primary key (auto-generated) |
name | string | Display name (required) |
businessId | UUID | FK to business |
categoryCode | string | Optional external code for integrations/imports |
isActive | boolean | Defaults to true |
createdBy | UUID | FK to user |
updatedBy | UUID | FK to user (nullable) |
createdAt | timestamptz | Auto-set on creation |
updatedAt | timestamptz | Set on update |
Delete with Replacement
When deleting a category that has products or services assigned to it, you can provide a replaceCategoryId. This triggers a transactional flow:
- Validate the replacement category exists
- Reassign all products with the old category to the replacement
- Reassign all services with the old category to the replacement
- Delete the original category
All steps execute within a single database transaction.
API Endpoints
All endpoints require Bearer token authentication and are scoped to PolicyResource.Category for RBAC.
POST /categories
Create a new category.
Body:
{
"name": "Beverages",
"businessId": "<uuid>",
"isActive": true,
"createdBy": "<uuid>"
}
GET /categories
List categories with pagination, search, and sorting.
Query parameters:
| Param | Required | Description |
|---|---|---|
businessId | Yes | UUID of the business |
page | No | Page number (default: 1) |
size | No | Page size (default: 10) |
search | No | Case-insensitive name search |
orderBy | No | Sort field (name) |
order | No | asc or desc |
GET /categories/:id
Get a single category by UUID.
Query parameters: businessId (required)
PATCH /categories/:id
Partially update a category.
Body:
{
"name": "Updated Name",
"businessId": "<uuid>",
"updatedBy": "<uuid>"
}
DELETE /categories/:id
Delete a category. Optionally reassign products/services to another category.
Query parameters: businessId (required)
Body (optional):
{
"replaceCategoryId": "<uuid>"
}
Data Import
Categories can be bulk-imported via the data-import module. The CategoryImportHandler supports:
- Required field:
name - Optional field:
code(maps tocategoryCode) - Upsert behavior: matches by
namewithin the business; updatescategoryCodeif the category exists, creates a new one otherwise.
Related Modules
- Products — products reference
categoryIdas a FK - Services — services reference
categoryIdas a FK - Markdown —
MarkdownScopeType.Categoryenables category-level price markdowns - Collections — merchandising collections are a separate grouping mechanism