Skip to main content
Version: Latest

Settings Groups and Entitlements

This page covers the entitlement model that gates every apply flow in this guide, and gives per-property depth on the four settings groups most relevant to starter setup (ProductSettings, CashierAppsSettings, FinancialSettings, CostCenterSettings) that the Business Onboarding chapter documents at the group level but not the individual-property level.

The entitlement model

A SubscriptionFeature (real, admin-CRUD-managed row, key like Features.AccountManagement) links out to four independent entitlement dimensions — MenuClaims, report actions, settings groups, and (future) feature-rule limits. This guide is only concerned with the settings groups link.

ISubscriptionFeatureEntitlementService

Task<List<string>> GetTenantAllowedFeatureKeysAsync(string tenantId, CancellationToken ct = default);
Task<bool> IsFeatureAllowedAsync(string tenantId, string featureKey, CancellationToken ct = default);

Every apply flow in this guide calls IsFeatureAllowedAsync before doing anything else.

ISubscriptionSettingsGroupEntitlementService

Task<List<string>> GetFeatureSettingsGroupsAsync(Guid featureId, CancellationToken ct = default);
Task<List<string>> GetTenantAllowedSettingsGroupsAsync(string tenantId, CancellationToken ct = default);
Task<bool> IsSettingsGroupAllowedAsync(string tenantId, string settingsGroupKey, CancellationToken ct = default);

Resolution chain: ITenantFeatureService.TenantFeaturesIDs(tenantId) → filter SubscriptionFeatureSettingsGroup rows by those feature Ids (IsActive only) → distinct SettingsGroupKey. Returns an empty list (never null) for a tenant with no active subscription.

Confirmed feature keys

The real, admin-CRUD-entered feature keys (queried live from the dev database):

Features.ProductsManagement Features.InventoryManagement Features.PointofSalesApp
Features.SalesManagement Features.PurchaseInvoices Features.PurchaseManagement
Features.PayablesManagement Features.ReceivablesManagement Features.AccountManagement
Features.Tables Features.KitchenDisplayScreen Features.OrdersDisplayScreen
Features.WaitersApp Features.OnlineMenuQR Features.Loyality
Features.StoreMobileApp Features.OnlineStore Features.CustomerService
Features.Bookings Features.DeliveryAggregators Features.ProjectManagement
Features.MarketingManagement

Features.ProjectManagement and Features.MarketingManagement were added later (below) specifically to give ProjectSettings/MarketerSettings real owners. There is no generic Features.Accounting / Features.Projects / Features.Salesmen / Features.Marketers key — always use the exact strings above.

Settings-group → owning-feature ownership table

The 11 settings groups known to the onboarding whitelist (SettingsGroupKeys.Known), and which feature currently owns each:

Settings groupOwning feature(s)
ProductSettingsFeatures.ProductsManagement, Features.InventoryManagement
CashierAppsSettingsFeatures.PointofSalesApp, Features.Tables, Features.WaitersApp, Features.KitchenDisplayScreen, Features.OrdersDisplayScreen
DiscountSettingsFeatures.PointofSalesApp, Features.SalesManagement
TaxSettingsFeatures.PointofSalesApp, Features.SalesManagement
FinancialSettingsFeatures.AccountManagement, Features.PayablesManagement, Features.ReceivablesManagement
CostCenterSettingsFeatures.AccountManagement
LoyaltySettingsFeatures.Loyality
NotificationSettingsFeatures.InventoryManagement, Features.KitchenDisplayScreen, Features.OrdersDisplayScreen, Features.OnlineMenuQR, Features.StoreMobileApp, Features.OnlineStore, Features.CustomerService, Features.Bookings, Features.DeliveryAggregators
ProjectSettingsFeatures.ProjectManagement (added later — see below)
SalesmanSettingsFeatures.SalesManagement (added later — see below)
MarketerSettingsFeatures.MarketingManagement (added later — see below)

Features.PurchaseInvoices/Features.PurchaseManagement deliberately own no settings group.

The ProjectSettings/SalesmanSettings/MarketerSettings gap and its close

When the settings-group entitlement link first shipped, these three groups had no owning feature at all among the 20 then-existing features — meaning they were unreachable for every tenant, regardless of subscription. A follow-up phase resolved this per a formal rule: every group in SettingsGroupKeys.Known must have (a) a real feature owner, (b) a documented "intentionally unavailable" decision, or (c) a newly-seeded feature owner.

Settings groupResolutionReasoning
SalesmanSettingsOwned by the existing Features.SalesManagementThe Salesman menu claim already lives inside the Sales menu structure in legacy config, available at every historical tier — not a separately-sold add-on
ProjectSettingsNew feature Features.ProjectManagementA real, fully independent module (own MenuClaims system, own sub-entities), no legacy tiering overlap with Sales
MarketerSettingsNew feature Features.MarketingManagementLegacy config excluded Marketer from the cheapest tier historically — a real, commercially-distinguished capability, not bundled with base sales

No SubscriptionPackageFeature row was created for either new feature — whether/which package(s) include them is left as a deliberate, separate admin/commercial decision through the existing admin UI. Until an admin makes that assignment, ProjectSettings/MarketerSettings remain unreachable for every existing tenant (a correctable admin decision now, not a structural code gap).

The onboarding settings-patch whitelist — three independent layers, never trusting the layer above

Onboarding apply is never a free patch of arbitrary AppSettings — three independent layers narrow every incoming patch, and none of them trusts the layer above it to have already done its job correctly:

  1. Static whitelist (SettingsPatchService.Validate, MultiTenancyApi side) — a manually-maintained snapshot of the 11 known groups and their supported properties (MultiTenancyApi has no compile-time visibility into BackEnd's real settings classes, so this dictionary can drift and must be kept in sync by hand). An unknown group → warning, dropped. A Guid-typed FK property (e.g. WarehouseId, CashAccountId, CostCenterId) → warning, dropped — entity references are never settable through onboarding. A property not in the whitelist → warning, dropped. A JSON value of the wrong kind for the declared type → warning, dropped.
  2. Recommendation-time entitlement filter (Phase 3) — after the whitelist produces a clean patch, every group not in GetTenantAllowedSettingsGroupsAsync(...) is removed.
  3. Apply-time entitlement re-check (Phase 3, deliberately independent) — re-checked fresh at apply time, since a tenant's subscription can change between recommendation generation and apply. If nothing remains after filtering, the request fails before ever calling the HTTP applier.
  4. BackEnd's own third, independent guard (InternalOnboardingSettingsPatchService) — re-validates everything again on its own side: an unknown group name → IgnoredGroups with a warning; an unknown property name on a known group → reflection lookup fails, skipped, warned; a Guid-typed property → independently blocked and warned (GuidPropertyIgnored); a wrong-type value → warned, skipped; and — its own subscription-eligibility guard — a group name that is one of the 11 known groups but is not in the tenant's currently-allowed set is rejected before it's ever applied (SettingsGroupNotAllowedBySubscription). A genuinely-unrecognized group name is never mislabeled as a subscription problem — it falls through to the ordinary "not part of the whitelist" path instead.

Net behavior: an unsupported/unentitled property or group is silently dropped from the applied patch (never throws) — each drop is recorded as a warning string in the response, but nothing fails the whole request unless zero groups end up with anything applied, in which case the response reports NoValidSettingsToApply.

ProductSettings

Item-catalog behavior configuration — not the same thing as Product Catalog Templates (see the distinction box below). Confirmed properties (real class, Shumoul.Application/Settings/ProductSettings.cs): item-attribute toggles (ExpireDate, BatchNumber, SerialNumbers, ScaleSettings, BranchSettings, MultiImages, MandatoryMainImages, Description, FreeQuantity, Booking, Map, Tags, PromotionName, SubName, AttachFiles, Properties, Ingredients, AdditionalData); unit-related flags (Modifiers, MandatoryPriceForMainUnit, MultiUnits, MultiPricelListForUnits, MandatoryCostForMainUnit, ImagesForUnit, MultiImagesForUnit, PackagingInfo, ColorForUnit, ShapForUnit); kitchen flags (KitchenPreparationTime, EnableCalories); promo flags (EnablePromotions, EnableTimeEvents, EnableDiscount); coding/sequence settings per level (Business/Department/Category/Product: *SequenceType, *CodeLength, *CodeStartNo, SegmentSeparator, AddZeroToRightOfCodeStartNo); scale settings (ScaleBarcodeDigits, ScaleProductCodeDigits, ScaleIntDigits, ScaleDecimalDigits, ScaleProductCodeFirstInBracode). Owning features: Features.ProductsManagement, Features.InventoryManagement.

ProductSettings is configuration. Product Catalog Templates are starter/master data. They are related — both concern "products" — but distinct: ProductSettings controls how the product module behaves (coding scheme, mandatory fields, multi-unit support); Product Catalog Templates create the actual Products/Categories/Units rows. Neither reads or writes the other.

CashierAppsSettings

POS/cashier terminal behavior. 57 real properties on Shumoul.Application/Settings/CashierAppsSettings.cs. Confirmed present (against a commonly-cited property list): Enable_Cash_Sales, Enable_CreditCard_Sales, Enable_Customer_Sales, Enable_Free_Quantity, Enable_Edit_ItemName, Enable_Change_Price, Enable_Product_Packages, Enable_Tables_System, Table_GuestCount_Required, Table_Required, Active_Tips, Return_Require_Customer_Info, Require_Cancel_Reason — all real.

Two commonly-assumed property names do not exist on this classEnable_Sales_Discount (the real discount toggles live on DiscountSettings, not here) and Discount_Require_Customer_Info (the real, similarly-named property is Coupon_Require_Customer_Info). Always verify a property name against the real class before referencing it in a patch or a client integration.

Other real properties include default-linkage Guid fields (SalesInvoiceTemplateId, GroupId, PriceListId, WarehouseId, CashAccountId, BankAccountId, LocalCardAccountId, InternationalCardAccountId, CostCenterId — all blocked from onboarding patching as entity references, see the whitelist rules above), rounding (Rounding_Type, Round_Digits), kitchen/print sorting, session limits, and inventory/return-policy flags. Owning features: Features.PointofSalesApp, Features.Tables, Features.WaitersApp, Features.KitchenDisplayScreen, Features.OrdersDisplayScreen.

FinancialSettings

Accounting/financial behavior configuration — distinct from the starter data layers below it. ~90 real properties, grouped:

  • 9 intermediate-account Guid pointers (CurrencyDifferencesAccountId, LostItemsAccountId, ExcessItemsAccountId, PayableNotesAccountId, ReceivableNotesAccountId, CreditDifferencesAccountId, RoundingDifferencesAccountId, CostDifferencesAccountId, IntermediateAccountsCostCenterId) — not a generic Cash/Bank/AR/AP account map; those live on InventoryAccountCategory instead (see Accounting Chart Templates).
  • System-level bool flags (AllowModifyDocumentDate, RoundingDecimals (default 2), DailyTransferCurrencyDifferences, AllowSaveZeroDocument, budget/limit check flags per document category).
  • Account-tree-shape settings (AccountTreeLevels default 5, ParentAccountCodeLength, SubAccountCodeLength, MandatoryParentAccountCodeInSubAccountCode, several Enable*ForAccounts flags).
  • 18 MandatoryCostCenterIn* flags, one per document type (PaymentVoucher, ReceiptVoucher, JournalEntry, CreditMemo, DebitMemo, DiscountInvoice, SalesInvoice, and 11 purchase/inventory document types), plus matching MultiCostCenterIn* flags. This is the exact field group the onboarding needsCostCenters answer patches (see Tenant Starter Kit Orchestration): Mandatory sets these true for the curated onboarding-reachable subset (sales invoice, payment voucher, receipt voucher, journal entry); No/Simple set them false.

Owning features: Features.AccountManagement, Features.PayablesManagement, Features.ReceivablesManagement.

Layering, spelled out: FinancialSettings is configuration (how vouchers behave). Accounting Chart Templates create the chart of accounts (starter data). Inventory Account Category Templates map specific inventory-related GL accounts. All three are different layers that happen to all concern "accounting" — none of them writes to either of the others.

CostCenterSettings

The real class (Shumoul.Application/Settings/CostCenterSettings.cs) is much broader than what onboarding can touch: 3 code-format properties (CostCenterTreeLevels, ParentCostCenterCodeLength, SubCostCenterCodeLength, MandatoryParentCostCenterCodeInSubCostCenterCode) plus, for every document type across Vouchers/Sales/Purchasing/Inventory, a trio of EnableCostCenterIn{Doc}, MultiCostCenterIn{Doc}, MandatoryCostCenterIn{Doc} flags — roughly 63 properties total.

The onboarding whitelist exposes only a curated 6-property subset:

EnableCostCenterInSalesInvoice MandatoryCostCenterInSalesInvoice
EnableCostCenterInPurchaseInvoice MandatoryCostCenterInPurchaseInvoice
EnableCostCenterInJournalEntry MandatoryCostCenterInJournalEntry

Everything else on the real class — every Multi* flag, and Enable/Mandatory for every other document type (returns, credit/debit memos, all the purchasing/inventory document types) — is unreachable through onboarding entirely; it can only be set through direct admin configuration of CostCenterSettings itself. Owning feature: Features.AccountManagement.

CostCenterSettings is configuration (which documents require/allow a cost center). Cost Center Templates are separate starter data — the actual dbo.CostCenters rows, created only through an explicit apply. needsCostCenters (an onboarding question) drives some CostCenterSettings and FinancialSettings fields directly, and separately drives whether the Tenant Starter Kit includes the CostCenters step by default — two different mechanisms reading the same one answer.