Skip to main content
Version: Latest

18. Phase History / Implementation Status

18.1 Status summary

AreaStatus
Survey/question/profile/rule model✅ Complete, seeded (23 questions, 27 rules)
Recommendation engine✅ Complete
Settings patch engine (11 supported groups)✅ Complete — see Chapter 20
Internal apply bridge✅ Complete, secured
JWT authentication on the MultiTenancy host✅ Complete
Tenant-Admin-only restriction on mutating actions✅ Complete
Broader [AllowAnonymous] security review of the host✅ Complete
New Subscriber E2E flow (Register → Verify → Provision → Login → Onboarding → Dashboard → Repeat login)✅ Verified live through the real Register/Verify/Tokens/Create endpoints — see docs/testing/NEW_SUBSCRIBER_E2E_TEST_REPORT.md
Angular client❌ Not started (see Chapter 13)
Real tenant registration/verification API flow✅ Now exercised through the real endpoints (previously used a directly-seeded tenant — see updated §17.3)
Real OTP/email/SMS/WhatsApp delivery⚠️ Still not functional in this dev environment — dispatch failures are now safely swallowed (best-effort, logged) instead of crashing registration, but no real message is ever delivered
Platform-admin permission model (non-onboarding)✅ Working when hosted inside Shumoul.Api (real DB-backed permission service wins) — deny-by-default only for a standalone host, see §17.4
Onboarding survey/summary text localization✅ Complete — sourced from embedded ar.json/en.json via OnboardingLocalization, not hardcoded C# — see Chapter 20, §20.6
Onboarding technical validation message localization✅ Complete — SettingsPatchService/HttpOnboardingTenantSettingsApplier warnings/errors and InternalOnboardingSettingsPatchService/OnboardingInternalController warnings/errors no longer hardcoded
Database-owned onboarding survey content preserved on reseed✅ Complete — seeder fills only blank fields; existing admin/DB content is never overwritten — see Chapter 20, §20.6.1
OnboardingRule stable RuleKey + unique index✅ Complete — RuleKey is now the seeder's match key for recommendation rules, backed by a real unique index (Add_OnboardingRule_RuleKey migration) — see Chapter 20, §20.6.2

18.2 Phase-by-phase

Phase 1.1 — Internal Apply Bridge. Built the internal, service-to-service endpoint on Shumoul.Api (OnboardingInternalController) and the calling code on Shumoul.MultiTenancyApi (HttpOnboardingTenantSettingsApplier), replacing an earlier stub that always returned "not configured." Established the shared-secret (X-Shumoul-Internal-Key) and tenant-header (TenantId) contract between the two hosts.

Phase 1.2 — CostCenterSettings Support & Final Acceptance. Added CostCenterSettings as an 8th supported settings group (previously only 7 were wired), verified runtime configuration actually resolves from the real host's configuration (not just library defaults), and hardened the internal endpoint's tenant-resolution safety checks.

Phase 1.3 — Live E2E Acceptance. First live, end-to-end run against a real dev database and a real seeded test tenant. Found and fixed a real bug: the internal apply bridge never forwarded the TenantId header, so apply always failed with "Tenant could not be resolved." before ever reaching IAppSettingService. Registration/OTP/email/WhatsApp verification were skipped in this phase because ISharedLocationService/IPushNotificationService had no implementation in the MultiTenancy host — a tenant was seeded directly instead. Also discovered (but only worked around temporarily, not fixed) that the host had no working JWT authentication or permission enforcement at all.

Phase 1.4 — Authentication & Permissions Readiness. Wired real, permanent AddJwtAuthentication(), AddIdentityPermissions(), and app.UseCurrentUser() into the MultiTenancy host (none had ever been called). Built OnboardingCurrentUserPermissionsService, a claims-based permission service, since no user/role/permission table exists in the host's central database. Found and fixed two real bugs surfaced by turning authentication on for the first time: OnboardingController's class-level [AllowAnonymous] was silently bypassing every [MustHavePermission] check, and the exception-handling middleware was registered after the authentication middleware, so invalid/expired tokens surfaced as raw 500s instead of 401s.

Phase 1.5 — Onboarding Authorization & Security Hardening. Restricted Answer/Apply/Skip to the tenant Admin role (previously any authenticated tenant user could reach them), using the existing, already- issued Admin role JWT claim — no Shumoul.Api TokenService change was needed. While auditing the host for the same [AllowAnonymous] pattern, found and fixed the most severe issue in this project's history: SharedDbConnectionStringsController.GetActiveListAsync() had no permission check at all and leaked raw shared-database credentials to any unauthenticated caller. Also fixed the same class of bug across 17 other controllers, including action-level (not just class-level) conflicts on TenantsController's tenant create/activate/deactivate actions.

Phase 1.6 — Security Cleanup Before Angular. Closed the two items Phase 1.5 had documented but not yet fixed: gated TestNotificationsController (previously fully anonymous, capable of sending real notifications to arbitrary users/tenants) behind a new, deny-by-default Permissions.Notifications.TestSend permission, and removed the remaining [AllowAnonymous]+[MustHavePermission] conflicts on TaxController's and TaxGroupController's full-listing actions — while explicitly leaving their minimal, permission-less dropdown/lookup actions public, by design. Re-verified every prior fix still holds, including a full Swagger exposure check confirming the internal bridge endpoint remains hidden.

New Subscriber E2E Verification (2026-07-11). First full live run of the entire subscriber journey through the real API surface: Register → Verify → Tenant DB provisioning → Login → First-login onboarding survey → Activity/settings selection → Apply settings → ApplyLog → Dashboard access → Repeat login. Found and fixed 5 real bugs blocking this path (registration/verification 500s caused by unguarded notification dispatch; the onboarding apply bridge's config section name mismatch; a missing TenantId header on the bridge's outbound call; a duplicate JWT authentication scheme registration crash) — full detail in docs/testing/NEW_SUBSCRIBER_E2E_TEST_REPORT.md. Confirmed repeat login does not re-force onboarding. Also corrected §17.3 and §17.4, which had described limitations later found to be already fixed or only partially accurate (see Chapter 17).

Latest Update — Phase 8. Completed onboarding recommendation + apply support for the three settings groups still missing after the New Subscriber E2E run: FinancialSettings, LoyaltySettings, NotificationSettings. All three had the same two gaps (missing from SettingsPatchService's whitelist and missing a case in InternalOnboardingSettingsPatchService's apply switch); recommendation rules were updated to produce all three using only existing survey questions/answers; a seeder idempotency bug and an apply bridge timeout (found during this phase's own E2E verification) were also fixed. Settings groups supported by onboarding: 8 → 11. Full technical detail, exact fixes, and E2E verification steps are in docs/testing/NEW_SUBSCRIBER_E2E_TEST_REPORT.md's "Phase 8" section; the survey-facing summary is in Chapter 20, §20.7.

Onboarding Text Localization (2026-07-12). Moved every hardcoded Arabic/English string out of OnboardingSurveySeeder.cs, OnboardingService.cs's step titles, and OnboardingRecommendationService.cs's summary/warning builders into two new embedded resource files, Onboarding/Localization/ar.json/en.json, read through a new internal reader, OnboardingLocalization. No question key, answer-option key, recommendation rule, settings-patch behavior, or apply behavior changed — the database contract (TitleAr/TitleEn, LabelAr/LabelEn, NameAr/NameEn, OnboardingSummaryLineDto.Ar/En) and the API response shape are unchanged, only the source of those values changed. This phase's seeder also began refreshing an existing question/profile row's text from the resources on every reseed — a decision corrected the next day, see the entry below. Full detail in Chapter 20, §20.6.

Onboarding Technical Validation Message Localization (2026-07-12, continuation). Moved the remaining hardcoded strings — this time in the settings-patch validation path rather than survey/summary content — into localization resources: SettingsPatchService.Validate's whitelist/property warnings, HttpOnboardingTenantSettingsApplier's bridge-disabled/apply-failed messages (both via OnboardingLocalization, MultiTenancyApi), and InternalOnboardingSettingsPatchService.ApplyAsync's errors/ warnings plus OnboardingInternalController's validation message (both via the app-wide IStringLocalizer<T> already used by OnboardingService, added to Shumoul.Api/Localization/ar.json/ en.json). All messages use {0}/{1} placeholders substituted via string.Format, never manual string concatenation. No settings-group whitelist entry, apply behavior, or API contract (errors/warnings remain plain string[]) changed. Full detail in Chapter 20, §20.6.

Preserve Database-Owned Onboarding Survey Content on Reseed (2026-07-13). Corrected the prior day's "always refresh from resources" seeder behavior, which would have silently discarded a future admin's content edit on the next restart. The seeder now only fills a currently-blank TitleAr/TitleEn/option-label/profile-name/survey-name field from the localization default — an existing non-empty value (an admin edit, or a previously seeded default) is left untouched even if the resource value has since changed. Matching is still exclusively by stable, non-text keys (OnboardingQuestion.Key, option Value within OptionsJson, OnboardingProfile.Code, survey Code+Version) — verified that the required unique indexes for all three already exist in the EF model. OnboardingRule has no unique index on its own (ProfileCode, ConditionJson) match key (ConditionJson is nvarchar(max), un-indexable without a migration) — flagged as a follow-up, no migration created in this pass. OnboardingRule.SettingsPatchJson/ Priority continue to sync from source on every run, unchanged — they are system logic, not admin-editable survey content. No question key, option key, recommendation rule, settings-patch behavior, apply behavior, or API response shape changed. Full detail in Chapter 20, §20.6.1.

Add Stable RuleKey for Onboarding Recommendation Rules (2026-07-13, same-day follow-up). Closed the OnboardingRule unique-index gap the entry above flagged. Added OnboardingRule.RuleKey — a stable, human-readable, non-text identity (Profile.RestaurantCafe, HasInventory.True, etc.) — via migration Add_OnboardingRule_RuleKey (nullable nvarchar(450) column + a filtered unique index, additive only, no data touched). SeedRulesAsync now matches existing rules by RuleKey first; a legacy row seeded before this column existed (RuleKey == null) is matched once by the old (ProfileCode, ConditionJson) identity and backfilled, purely for migration compatibility — every subsequent run matches it by RuleKey only. All 27 existing rules in the real dev database were backfilled cleanly onto their existing row IDs (verified: 27 rows, 27 distinct RuleKey values, no duplicates, unchanged after two more reseeds). ProfileCode/ ConditionJson/SettingsPatchJson/Priority continue to be rule logic, synced from source on every run exactly as before — only the match identity changed. No question key, option key, recommendation logic, settings-patch behavior, apply behavior, or API response shape changed (RuleKey is never returned in any API response). Full detail in Chapter 20, §20.6.2.

18.3 What "done" means today

The backend is feature-complete, authorization-hardened, and live-tested end-to-end against a real dev database — including the real Register → Verify → Login API surface, not a directly-seeded tenant — for every permission tier described in this guide. All 11 supported settings groups (see Chapter 11, §11.2 and Chapter 20) recommend and apply correctly. Angular integration has not started — see Chapter 13 to begin it.