Business Activities
Phase 4 (2026-07-15). Saas.BusinessActivities is the stable master-data key that every starter
template family in this guide (accounting, inventory account category, cost center, product catalog)
scopes itself by. Purely additive — zero tenant-side effects, no migration on any tenant database.
Purpose
Before this phase, "what type of business is this tenant" only existed as the onboarding survey's
activityType answer value (a free-form string inside OptionsJson). That's fine for driving the
survey UI, but starter templates need a real, FK-able, queryable table to key off — not a JSON blob.
BusinessActivity.Code is designed to be exactly the same value space as the onboarding
activityType answer options, so the two concepts stay in lockstep without onboarding's own API
contract changing at all.
BusinessActivityis master data, not an entitlement. No entitlement service references it; a tenant's set of allowed features/settings groups is governed entirely by subscription feature entitlements, completely independent of whichBusinessActivitythe tenant picked.
Current codes
Exactly 5 exist, seeded and active:
RestaurantCafe
Supermarket
RetailStore
Services
WholesaleDistribution
(A sixth, "Manufacturing," appears only in old illustrative planning notes — it was never actually seeded and is not a real code.)
Schema
Saas.BusinessActivities:
| Column | Type | Notes |
|---|---|---|
Id | Guid PK | |
Code | nvarchar(128), unique | Stable identity — must exactly match an onboarding activityType answer-option value. Never display text. |
Name | nvarchar(256), nullable | Arabic display name |
FName | nvarchar(256), nullable | English display name |
Description | nvarchar(1000), nullable | Arabic |
FDescription | nvarchar(1000), nullable | English |
IsActive, DisplayOrder | via BaseEntity |
Indexes: unique IX_BusinessActivities_Code, plus IsActive/DisplayOrder.
Deliberately no soft delete — BusinessActivity has no ISoftDelete, matching the precedent set
by OnboardingProfile/SubscriptionFeature/SubscriptionFeatureSettingsGroup. Deletion (rare, admin
CRUD only) is a hard RemoveAsync.
Naming convention correction
BusinessActivity originally shipped with NameAr/NameEn/DescriptionAr/DescriptionEn columns.
A dedicated cleanup phase (SCHEMA_CONVENTION_CLEANUP, 2026-07-16, migration
20260716195141_Update_SaasTemplates_AlignNameFNameConvention) renamed these — and the equivalent
columns on AccountingChartTemplate/AccountingChartTemplateAccount — to the platform-wide
Name (Arabic) / FName (English) / Description (Arabic) / FDescription (English) convention.
Every table introduced in this initiative after that cleanup (InventoryAccountCategoryTemplate,
CostCenterTemplate, ProductCatalogTemplate and children) already used Name/FName from
inception — the cleanup was a one-time correction of the two earliest tables, not a recurring pattern.
Platform-wide rule going forward: do not use
NameAr/NameEn/DescriptionAr/DescriptionEn/NotesAr/NotesEnin any new database table without explicit approval.
The migration was a pure RenameColumn operation (20 calls, no data movement, no dropped indexes) —
but its own history is a documented cautionary tale: dotnet ef migrations add's auto-scaffolded
rename-pairing heuristic got the pairing backwards for 2 of the 3 renamed tables (it would have
silently swapped every row's Arabic/English text). This was caught by independent review before ever
applying to any database, and the migration was hand-corrected. Lesson carried forward: never trust
dotnet ef migrations add's auto-generated rename pairing when 2+ properties change on the same entity
at once — always hand-verify each RenameColumn pair.
The Excel/JSON import parsers for AccountingChartTemplate (not a table, a wire/DTO shape) kept the
old NameAr/NameEn field names as accepted legacy aliases — official Name/FName always wins
if both are supplied; a legacy-only input is folded into the official field automatically.
API
BusinessActivitiesController, route api/Saas/BusinessActivities:
| Endpoint | Verb | Permission |
|---|---|---|
Get | GET | Permissions.BusinessActivities.ViewAll |
GetActive | GET | [AllowAnonymous] — the one public endpoint, needed so the onboarding survey UI (unauthenticated, pre-tenant-login) can list activities |
GetDetails/{id} | GET | .View |
GetByCode/{code} | GET | .View |
Create | POST | .Create |
Update/{id} | PUT | .Edit |
Delete/{id} | DELETE | .Delete |
Active/{id} | POST | .Activate |
Inactive/{id} | POST | .Deactivate |
Seed behavior
Insert-missing, fill-blank-only, matched by Code alone. An admin-edited, non-blank Name/FName/
Description/FDescription is never overwritten on reseed; IsActive/DisplayOrder are only set on
first insert.
Where BusinessActivity is used downstream
Every one of these template families carries a required BusinessActivityId FK and enforces "at most
one active default template per BusinessActivity" via a unique filtered index:
- Accounting Chart Templates
- Inventory Account Category Templates (a 3-column
unique index —
BusinessActivityId+AccountingChartTemplateId+IsDefault, since one activity can pair with more than one chart template over time) - Cost Center Templates
- Product Catalog Prerequisite Templates
- Product Catalog Templates (categories + items)
The Tenant Starter Kit resolves one BusinessActivity per
orchestration call and uses it to pick every family's default template unless an explicit template Id
is supplied.
