Architecture
Al-Mohaseb is a set of Orchard Core modules hosted by one ASP.NET Core application. Each business is its own tenant with its own database; the books underneath every screen are a double-entry general ledger; and heavy work runs as durable background jobs.
Project layers
Each area of the product is split into up to three projects. Namespaces follow folders, and each file holds one public type named after the file.
| Layer | Project | Holds | Depends on Orchard UI |
|---|---|---|---|
| Abstractions | CrestApps.Almohaseb.<Area>.Abstractions | Contracts implemented by separate provider projects, and plain models | No |
| Core | CrestApps.Almohaseb.<Area>.Core | Models, YesSql indexes, stores, services and static permission classes. Testable without the Orchard UI. | No |
| Module | CrestApps.Almohaseb.<Area> | Orchard wiring only: Manifest.cs, startups, display drivers, controllers, views, migrations, recipes | Yes |
The foundation area has no <Area> suffix: the module is CrestApps.Almohaseb and its library is CrestApps.Almohaseb.Core. Primitives with no Orchard dependency, such as Money, FiscalCalendar and EmployerIdentificationNumber, live in CrestApps.Almohaseb.Abstractions under the root namespace CrestApps.Almohaseb. See Foundation.
The host, CrestApps.Almohaseb.Web, references every module: Al-Mohaseb's, CrestApps' and Orchard Core's.
builder.Services
.AddOrchardCms(orchardCoreBuilder => orchardCoreBuilder
// Installs the tenant hierarchy guards in every tenant. They do nothing in a tenant that is neither a firm
// (parent) nor a firm-managed client (child).
.AddTenantHierarchy());
Features
- A feature is declared only when a person should choose it. Accounting is one; the foundation, the admin UI and jobs are not.
- Shared services live in features marked
EnabledByDependencyOnly = true. They turn on when a feature that needs them does. - Integrations between features are
[RequireFeatures(...)]startups, so a behaviour switches on only when both sides are enabled. - Tax years, states and POS brands are never features. They are data and behaviours inside a feature.
- A recipe cannot enable an
EnabledByDependencyOnlyfeature directly; it enables a managed feature that depends on it.
The Feature ID Reference lists every feature.
Tenancy
| Tenant | Who | Set up with |
|---|---|---|
| Default | The platform: tenant management and the tenant hierarchy that turns tenants into firms | almohaseb-platform |
| Firm | An accounting firm. A parent tenant in the CrestApps tenant hierarchy. | (planned) |
| Firm-managed business | A business served by a firm. A child tenant with its own database. | A business recipe |
| Standalone business | A business on its own. A plain Orchard Core tenant with no hierarchy feature. | A business recipe |
Every business tenant runs the same bookkeeping modules, whether or not it belongs to a firm. The host calls AddTenantHierarchy() so the hierarchy guards are installed everywhere; they do nothing in a tenant that is neither a parent nor a child.
Persistence
- YesSql only. No Entity Framework and no NHibernate.
- High-volume and regulated records are YesSql documents in a dedicated collection per bounded context (for example
Jobs), stored through CrestAppsDocumentCatalog<T, TIndex>stores. - Content items are used only for master data that editors should be able to extend with parts and fields.
- Index migrations use literal column names (never
DocumentId) and.WithLength(26)for ids. - Queries must translate to SQL. No unbounded
ListAsync(): lists page in the database.
Money, dates and time
- Money is
decimal, wrapped inMoneywith an explicitRoundingPolicy.doubleis never used for money. - The current time comes from
IClock, neverDateTime.UtcNow. - Business dates come from
IBusinessCalendar, which applies the tenant's time zone and the business day's start time. A convenience store whose business day starts at 04:00 books a 02:30 sale on the previous day. See Foundation.
Permissions
- Each area has a static
public static class <Area>Permissionsin its Core project, and aninternal sealed class PermissionProviderin its module. - Default stereotypes give every permission to the Administrator role only (
OrchardCoreConstants.Roles.Administrator). - Every controller action and endpoint calls
IAuthorizationService.AuthorizeAsyncand returnsForbid()when it fails. Menus only hide items; they are never the security boundary.
The User Manual's Roles and permissions page lists every permission by its display name.
Admin UI
- The product UI is the Orchard Core admin (TheAdmin). There is no single-page app.
- HTML screens are MVC
[Admin]controllers; JSON is served by Minimal APIs. - Every list renders through
AdminListwithAdminListContext<TEntry>, which gives the List and Grid layouts, bulk actions and the page-size pager. - Every list shares one search and filter pattern. See Admin UI.
- Editors are display drivers with
ocat-*form classes. Menus useAdminNavigationProviderunder the existing groups. - User-facing strings go through
S[...]andH[...]so they can be localized.
Background processing
Work that touches more than about 500 documents or takes more than about 2 seconds runs as a durable job in bounded batches, with one session per batch, rather than in a web request. Orchard Core background tasks are used only as schedulers and sweepers. See Jobs.
Security
- No secrets in code, recipes or tests.
- Tenant secrets and sensitive identifiers (such as the EIN) are protected at rest with ASP.NET Core Data Protection and shown masked. Host secrets belong in a key vault.
- Deployment plans carry protected values with the source tenant's key ring, so a target tenant cannot read them; re-enter the EIN on the target tenant after an import. See Foundation.