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)
| EntityName | RowCounter | ApplyPeriod | Attached feature | FDescription |
|---|---|---|---|---|
Users | 1 | WhenSubscribing | Features.PointofSalesApp | Up to 1 user |
Branches | 1 | WhenSubscribing | Features.PointofSalesApp | Up to 1 branch |
Devices | 1 | WhenSubscribing | Features.PointofSalesApp | Up to 1 POS device |
SalesInvoices | 10 | Monthly | Features.SalesManagement | Up to 10 sales invoices per month |
Products | 5 | WhenSubscribing | Features.ProductsManagement | Up 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.
| Limit | Seeded | Queryable (pricing API / admin CRUD) | Enforced |
|---|---|---|---|
| Users ≤ 1 | ✅ | ✅ | ✅ |
| Branches ≤ 1 | ✅ | ✅ | ✅ |
| POS devices ≤ 1 | ✅ | ✅ | ✅ |
| Sales invoices ≤ 10/month | ✅ | ✅ | ✅ |
| Products ≤ 5 | ✅ | ✅ | ✅ |
