Skip to main content
Version: Latest

7. Package Rules and Limits

7.1 The real schema — no RuleKey/RuleValue

SubscriptionPackageFeatureRule has no RuleKey, RuleValue, or PackageId column. A row is:

public long RowCounter { get; set; } // the limit number itself
public string EntityName { get; set; } // free-text, intended to match a literal tenant-DB table name
public FeatureRuleApplyPeriod ApplyPeriod { get; set; } // WhenSubscribing=0, Daily=1, Monthly=2, Annual=3
public Guid FeatureId { get; set; } // -> SubscriptionPackageFeature.Id (the package+feature JOIN row, never SubscriptionFeature directly)

The task's own suggested rule-key convention (Limits.SalesInvoices.PerMonth, Limits.Users.Total, etc.) does not map to any real column — those names are used in this documentation as human-readable labels only. Real identity/idempotency is the (FeatureId, EntityName, ApplyPeriod) triple.

Because FeatureId points at a package-feature join row, not a package directly, every rule here is attached to the single most semantically relevant feature within Shumoul Starter — this also determines how the rule is grouped on the public pricing page (see Chapter 3 §3.2).

7.2 The five Shumoul Starter limits (live-verified)

EntityNameRowCounterApplyPeriodAttached featureFDescription
Users1WhenSubscribingFeatures.PointofSalesAppUp to 1 user
Branches1WhenSubscribingFeatures.PointofSalesAppUp to 1 branch
Devices1WhenSubscribingFeatures.PointofSalesAppUp to 1 POS device
SalesInvoices10MonthlyFeatures.SalesManagementUp to 10 sales invoices per month
Products5WhenSubscribingFeatures.ProductsManagementUp to 5 products

Confirmed via direct SQL query against the dev database and independently via GET api/Subscription/Packages/pricing (both match exactly) during this task's live verification.

7.3 Queryable AND enforced (as of the follow-up phase below)

Update: a centralized enforcement service now reads and enforces all five limits — see Chapter 13 — Package Limit Enforcement for the full design, implementation, and live verification. This section is kept for history; the table below reflects the current state.

The pre-existing, unrelated mechanism found during the original package-seeding phase — IAppFeatureChecker + [IsFeatureGranted] + FeatureLimitationsCheckEventHandler (BackEnd), wired only to RolesController.CreateAsync via an unrelated AppFeatures.Pos.Roles key, and broken by a hardcoded '@tenant' string literal in its COUNT query — was not reused or fixed. It remains untouched and out of scope; the new enforcement in Chapter 13 is a separate, correct mechanism built specifically for these five limits.

LimitSeededQueryable (pricing API / admin CRUD)Enforced
Users ≤ 1
Branches ≤ 1
POS devices ≤ 1
Sales invoices ≤ 10/month
Products ≤ 5