Skip to main content
Version: Latest

11. Settings Patch Engine & Supported/Unsupported Groups

11.1 What the patch engine does

InternalOnboardingSettingsPatchService (Shumoul.Infrastructure/Services/) is the only code path that turns a generated JSON patch into an actual write through IAppSettingService. It is intentionally a whitelist, not a generic "set any property on any settings class" mechanism:

  1. Deserialize settingsPatchJson into Dictionary<string, Dictionary<string, JsonElement>> (group name → property name → value).
  2. For each group name, switch to one of a fixed, compile-time list of supported settings types — anything not in the switch is ignored and reported as a warning, never applied and never an error by itself.
  3. For each property inside a supported group: reflect the real settings class, skip and warn on unknown property names, skip and warn on Guid/Guid? properties, convert the JsonElement to the property's real CLR type (bool/int/long/byte/decimal/double/string/enum, nullable-aware), and only then set it.
  4. If at least one property in a group converted successfully, read the group's current value via IAppSettingService.GetAppSetting<T>(refreshCache: true), apply the changes, and write it back via IAppSettingService.SetAppSetting<T>() — the same call every other part of the ERP uses, including its existing automatic cache invalidation. No new persistence or caching mechanism was introduced.
  5. Before/after snapshots of the whole settings object are captured for the audit log (TenantOnboardingApplyLogs), per touched group.

11.2 Supported settings groups

These 11 groups are wired into AppSettingsController's existing GetAppSetting/SetAppSetting calls and are the only ones the patch engine can ever write to:

Settings GroupPurposeApplied by OnboardingNotes
CashierAppsSettingsPOS/cashier behavior — tables, kitchen routing, cancel/return rules, roundingYesPresent in every base profile rule
ProductSettingsProduct-level toggles — modifiers, expiry, batch, serial, scale, multi-unit, imagesYesPresent in every base profile rule
DiscountSettingsDiscount/promotion/coupon toggles and percentage limitsYesPresent in most base profile rules
TaxSettingsSales/purchase tax enablement and price-inclusive-of-tax behaviorYesPresent in every base profile rule + several global rules
CostCenterSettingsDocument-level cost-center enable/mandatory flags (sales/purchase invoice, journal entry)YesAdded in Phase 1.2 — see Chapter 18
ProjectSettingsProject linkage on sales/purchase invoices and payment/receipt vouchersYesDriven by needsProjects
SalesmanSettingsSalesman-on-invoice + commission auto-calculationYesDriven by hasSalesmen
MarketerSettingsMarketer-on-invoice + commission auto-calculationYesDriven by hasMarketers
FinancialSettingsVoucher/journal-level cost-center mandatory flags + system-level financial togglesYesAdded in Phase 8 — curated 15-property subset of ~80 total; see Chapter 20
LoyaltySettingsCustomer loyalty/points program configurationYesAdded in Phase 8 — all 12 properties whitelisted; applies to RestaurantCafe/RetailStore base rules
NotificationSettingsStock-related admin notifications (low/high quantity, negative stock, purchasing/transfer activity)YesAdded in Phase 8 — all 15 properties whitelisted; driven by hasInventory = true

See Chapter 20 for the full question-by-question and rule-by-rule breakdown of what drives each of these groups.

For exact, real, per-property detail on ProductSettings, CashierAppsSettings, FinancialSettings, and CostCenterSettings (confirmed property names, which are and are not onboarding-reachable, and how each relates to a subscription feature and to the Tenant Starter Kit's starter-data templates), see Settings Groups and Entitlements in the Tenant Starter Kit & Product Catalog guide. That chapter also documents the subscription feature → settings-group entitlement filter this engine's whitelist is independently re-checked against at recommendation and apply time.

11.3 Unsupported settings groups

Two categories of "unsupported," reported with different warning text:

Known but not wired — the group name is recognized, but no persistence path exists for it yet:

  • SalesInvoiceTemplateSettings — still defined as a settings class, but no controller action ever calls GetAppSetting/SetAppSetting for it.

  • PrinterSettings — persisted through a separate, per-device mechanism (PrinterSettingService / PrinterSetting entity), not through AppSettings at all.

    "Settings group {X} exists but is not supported by the current AppSettings persistence service."

Not part of the whitelist at all — anything else, most importantly CacheSettings, JwtSettings, and any connection-string-bearing settings — these are never even considered, regardless of what a patch contains:

"Settings group {X} is not part of the onboarding whitelist and was ignored."

Either way, the group is added to ignoredGroups in the response and the request still succeeds as long as at least one other group had at least one property applied.

11.4 Guid properties are always stripped

Any property whose underlying type is Guid/Guid? (e.g. WarehouseId, CashAccountId, SalesInvoiceTemplateId) is skipped with:

"Property {Group}.{Property} was ignored because Guid reference assignment is outside Phase 1."

This is deliberate — assigning a real entity reference (a warehouse, a cash account) requires that entity to already exist and be chosen correctly, which is out of scope for an automatic first-run recommendation. This rule is never weakened — the Tenant Operational Defaults phase resolves the same four Guid properties through a separate, purpose-built path instead (creating the missing record, then setting the Id directly via IAppSettingService, never through this JSON patch engine — see Tenant Operational Defaults, Chapter 7). These show up as nextActions like CreateDefaultWarehouse in the recommendation response instead (see §8.4).

The onboarding apply flow also makes a second, separate best-effort call right after Tenant Operational Defaults — Cash/Bank GL-Currency Linking — which links the newly-created default CashAccount/BankAccount to a real GL account and currency when an accounting chart template has already been applied for the tenant. Like the operational-defaults call, a failure or "nothing to do yet" outcome here never blocks the settings-patch apply.

11.5 Type conversion rules

Target typeAccepted JSON
boolJSON true/false
int, long, byteJSON number, exact fit
decimal, doubleJSON number
stringJSON string
enumJSON number (numeric enum value) or JSON string (parsed case-insensitively by name)
anything else (including Guid)never converted

A property whose value can't be converted for its target type is skipped with a warning ("Property {Group}.{Property} has an incompatible value and was ignored.") — it never throws and never fails the whole request.

11.6 Worked example

Given this patch:

{
"CacheSettings": { "DefaultCacheDurationInMinutes": 999 },
"CashierAppsSettings": { "WarehouseId": "11111111-1111-1111-1111-111111111111", "Enable_Change_Price": true }
}

The engine:

  • Ignores CacheSettings entirely (not whitelisted).
  • Ignores WarehouseId (Guid).
  • Applies Enable_Change_Price (a plain bool) to CashierAppsSettings.
  • Returns succeeded: true, appliedGroups: ["CashierAppsSettings"], ignoredGroups: ["CacheSettings"], and two warnings explaining exactly what was skipped and why.