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:
- Deserialize
settingsPatchJsonintoDictionary<string, Dictionary<string, JsonElement>>(group name → property name → value). - For each group name,
switchto 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. - 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 theJsonElementto the property's real CLR type (bool/int/long/byte/decimal/double/string/enum, nullable-aware), and only then set it. - 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 viaIAppSettingService.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. - 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 Group | Purpose | Applied by Onboarding | Notes |
|---|---|---|---|
CashierAppsSettings | POS/cashier behavior — tables, kitchen routing, cancel/return rules, rounding | Yes | Present in every base profile rule |
ProductSettings | Product-level toggles — modifiers, expiry, batch, serial, scale, multi-unit, images | Yes | Present in every base profile rule |
DiscountSettings | Discount/promotion/coupon toggles and percentage limits | Yes | Present in most base profile rules |
TaxSettings | Sales/purchase tax enablement and price-inclusive-of-tax behavior | Yes | Present in every base profile rule + several global rules |
CostCenterSettings | Document-level cost-center enable/mandatory flags (sales/purchase invoice, journal entry) | Yes | Added in Phase 1.2 — see Chapter 18 |
ProjectSettings | Project linkage on sales/purchase invoices and payment/receipt vouchers | Yes | Driven by needsProjects |
SalesmanSettings | Salesman-on-invoice + commission auto-calculation | Yes | Driven by hasSalesmen |
MarketerSettings | Marketer-on-invoice + commission auto-calculation | Yes | Driven by hasMarketers |
FinancialSettings | Voucher/journal-level cost-center mandatory flags + system-level financial toggles | Yes | Added in Phase 8 — curated 15-property subset of ~80 total; see Chapter 20 |
LoyaltySettings | Customer loyalty/points program configuration | Yes | Added in Phase 8 — all 12 properties whitelisted; applies to RestaurantCafe/RetailStore base rules |
NotificationSettings | Stock-related admin notifications (low/high quantity, negative stock, purchasing/transfer activity) | Yes | Added 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, andCostCenterSettings(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 callsGetAppSetting/SetAppSettingfor it. -
PrinterSettings— persisted through a separate, per-device mechanism (PrinterSettingService/PrinterSettingentity), not throughAppSettingsat 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 type | Accepted JSON |
|---|---|
bool | JSON true/false |
int, long, byte | JSON number, exact fit |
decimal, double | JSON number |
string | JSON string |
| enum | JSON 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
CacheSettingsentirely (not whitelisted). - Ignores
WarehouseId(Guid). - Applies
Enable_Change_Price(a plainbool) toCashierAppsSettings. - Returns
succeeded: true,appliedGroups: ["CashierAppsSettings"],ignoredGroups: ["CacheSettings"], and two warnings explaining exactly what was skipped and why.
