Skip to main content
Version: Latest

Product Catalog Prerequisites

Phase 6A.1 (2026-07-15/16). Product Catalog Templates shipped categories only, deliberately deferring items because the real Product/ProductCategory model depends on tenant master data — BusinessType, ProductDepartment, UnitOfMeasure — that no seed/apply flow created for a new tenant. Live DB verification during Phase 6A confirmed this wasn't hypothetical: the designated E2E test tenant genuinely had zero ProductDepartment rows. This phase closes that gap.

The tenant DB hierarchy this unblocks

UnitOfMeasure must exist before ProductUnits; BusinessType must exist before ProductDepartment/ Product; ProductDepartment must exist before ProductCategory; ProductCategory must exist before Product; Product must exist before its ProductUnits.

Schema (SaaS DB)

TablePurpose
Saas.ProductCatalogPrerequisiteTemplatesSame shape as ProductCatalogTemplateCode, Name/FName, BusinessActivityId FK, Version, IsDefault
Saas.ProductCatalogPrerequisiteTemplateBusinessTypesTemplateId FK, Code (template-internal identity only — the real BusinessType table has no Code column), Name/FName, IsDefault (exactly one per template)
Saas.ProductCatalogPrerequisiteTemplateDepartmentsTemplateId FK, Code (template-internal identity — the real ProductDepartment table has no Code column either), Name/FName, Description/FDescription
Saas.ProductCatalogPrerequisiteTemplateUnitsTemplateId FK, Code (maps directly to the real UnitOfMeasure.UnitCode column — the one prerequisite where Code-based matching is genuinely possible), Name/FName, ConversionRate, IsDefault (marks the base "Piece" unit)
Saas.TenantProductCatalogPrerequisiteTemplateApplyLogsCentral apply log

Design simplification (deliberate): normally exactly one BusinessType row per template — modeled as a child table for schema consistency with siblings and future extensibility, not because multiple business types per template is a common real case. Departments don't carry their own BusinessType reference — every department in a template is applied under that template's single default BusinessType row.

Why UnitOfMeasure was included even though a fallback "Piece" seeder already exists

ApplicationDbSeeder.SeedUnitOfMeasuresAsync already creates one default unit (Name="وحدة", FName="Piece", UnitCode="Pc", Conversation_Rate=1) per tenant. Per explicit business direction, that seeder is treated as a minimum tenant fallback, not the source of truth — Phase 6B needed a richer, business-activity-aware unit set (Box, Carton, Kilogram, Liter, Hour, Service) the generic single-unit seeder was never meant to provide. Every template's base unit is deliberately coded "Pc" so apply-time matching (by UnitCode, the real column) recognizes and skips the tenant's already-seeded row instead of duplicating it. Live DB verification found the designated test tenant had zero UnitOfMeasure rows at all at the time — a real-world confirmation that Units genuinely needed to be in scope, not deferred as "already covered."

Apply order and idempotency

BusinessTypes first (the template's IsDefault item becomes the one every Department attaches to), then Departments, then Units (fully independent of the other two — a BusinessType/Department failure never blocks Units).

KindMatched byReason
BusinessTypeName (case-insensitive)Real table has no Code column
ProductDepartmentName (case-insensitive)Real table has no Code column
UnitOfMeasureUnitCodeReal table's own column genuinely exists

Force=false (default): an existing row is never touched. Force=true: fills only a currently-blank FName — never the match key, never any structural field. Never deletes, never deactivates, never resurrects a soft-deleted row (reports Failed). An ambiguous existing match (more than one row sharing the same Name/UnitCode) blocks the whole apply rather than guessing. ResolvedBusinessTypeId and ResolvedProductDepartmentId are exposed on the apply result specifically so a caller that just ran this apply can immediately follow with a category apply targeting the department it just created — see Product Catalog — Starter Kit Integration.

API

api/Saas/ProductCatalogPrerequisiteTemplates (Swagger group MultiTenancy):

RoutePermission
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 BusinessTypes/*, Departments/*, Units/*.Edit
POST {id}/ApplyToTenant/Preview / POST {id}/ApplyToTenant.Apply

POST api/internal/product-catalog-prerequisite-templates/apply (Shumoul.Api) — same internal-key bridge mechanism as every other apply flow in this initiative, no new secret.

Entitlement

Features.ProductsManagement — the same feature key used by categories and items apply. No new feature key was introduced.

Seed data

5 templates (one per activity), each IsDefault=true, one default BusinessType each, one Department each, 18 units total across all 5 templates — every template's "Piece" unit coded Pc.

What was deliberately not done

No Product Items yet (closed by Phase 6B). No stock quantities, no opening stock, no inventory transactions, no accounting postings. Not auto-triggered by onboarding or first login on its own — only reachable via an explicit admin apply, or (Phase 6C) as the first step of the Tenant Starter Kit product chain.