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 ProductCatalogTemplate — Code, 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.