Product Catalog Categories
Phase 6 (2026-07-15), categories only. The first Product Catalog phase — a SaaS-managed
ProductCatalogTemplate catalog scoped by BusinessActivity, plus an explicit preview/apply flow that
copies a template's categories into a tenant's real dbo.ProductCategories. Items were deliberately
deferred to Phase 6B once
Phase 6A.1's prerequisites existed.
Schema (SaaS DB)
| Table | Purpose |
|---|---|
Saas.ProductCatalogTemplates | Code (unique), Name/FName, Description/FDescription, BusinessActivityId FK, Version, IsDefault — same 2-column (BusinessActivityId, IsDefault) unique-index shape as CostCenterTemplate |
Saas.ProductCatalogTemplateCategories | TemplateId FK, Code (template-internal identity only — the real ProductCategory table has no Code column), Name/FName, Description/FDescription |
Saas.TenantProductCatalogTemplateApplyLogs | Central apply log |
This same ProductCatalogTemplate table is later extended by
Phase 6B to also own item templates — items are children of
this same template, not a second template family.
Apply behavior
Matched by Name (case-insensitive, scoped to the resolved ProductDepartment) — the real
ProductCategory table has no Code column at all, so Name is the only viable identifying field.
Force=false (default): an existing Name is skipped. Force=true: fills only a currently-blank
FName. A soft-deleted existing Name is reported as a per-category failure, never silently
resurrected. An ambiguous existing match blocks the whole apply.
Every category requires a resolvable tenant ProductDepartment — Phase 6 never auto-creates one; a
tenant with no active department gets every category reported MissingDependency and the whole apply
blocked, never a silent partial success.
TargetProductDepartmentId (added by Phase 6A.1): an optional override on the apply request. When
provided, the specific active ProductDepartment is used instead of "the tenant's first active
department" — letting an orchestrated "prerequisites, then categories" flow (Prerequisites' own
ResolvedProductDepartmentId) target the department it just created, rather than relying on ambiguous
ordering once a tenant has more than one department. Omitted, behavior is unchanged from Phase 6 — falls
back to "first active department." Category apply was not changed to auto-create a
ProductDepartment — only the resolution mechanism gained an optional override.
API
ProductCatalogTemplatesController, route api/Saas/ProductCatalogTemplates (Swagger group
MultiTenancy):
| Route | Permission |
|---|---|
GET Get / GetActive / GetByBusinessActivity/{code} / GetDefaultByBusinessActivity/{code} | .ViewAll |
GET {id}/Detail | .View |
POST Create / Copy | .Create |
PUT Update/{id} | .Edit |
DELETE Delete/{id} | .Delete |
POST Active/{id} / Inactive/{id} | .Activate / .Deactivate |
POST/PUT/DELETE Categories/* | .Edit |
POST {id}/ApplyToTenant/Preview / POST {id}/ApplyToTenant | .Apply |
POST api/internal/product-catalog-templates/apply (Shumoul.Api) — same internal-key bridge mechanism.
Entitlement
Features.ProductsManagement.
Seed data
5 templates, one per activity, all default. Each has between 5 and 20 categories.
What was deliberately not done this phase
No product items, no ProductUnits. No stock quantities, no opening balances, no inventory
transactions, no accounting postings. Not auto-triggered by onboarding or first login on its own.
