# 01 — Moon ERP Core & Modules (Backend Platform Map)

Source tree: `/home/moonui/moon-erp-be` (Laravel 12, `nwidart/laravel-modules`, Angular FE).
All 15 modules enabled in `/home/moonui/moon-erp-be/modules_statuses.json`: Core, Accounting, Inventory, Sales, Purchases, POS, HRM, LIS, WebStore, Production, CRM, CMMS, QMS, NPHIES, EInvoicing.

## 1. Application-level platform (root `app/`)

Files: `/home/moonui/moon-erp-be/app/Models/{BaseModel,Branch,Company,User,CentralSyncQueue}.php`, `app/Traits/`, `app/Services/`.

- **`BaseModel`** (`app/Models/BaseModel.php`) — abstract base used by all module models. Composes:
  - `TenantAware` (`app/Traits/TenantAware.php`): auto-stamps `company_id` from `auth()->user()` on `creating`; `scopeTenant()` filters by current user's company. Multi-company tenancy is column-based (`company_id`), not DB-per-tenant.
  - `Auditable` (`app/Traits/Auditable.php`): full dirty-only activity logging via `spatie/laravel-activitylog` (`logAll()->logOnlyDirty()`).
  - `SoftDeletes`, `HasArabicContent` (`getLocalizedName()`, `name`/`name_ar` bilingual convention used everywhere).
- **`User`** (`app/Models/User.php`) — Sanctum (`HasApiTokens`), `Spatie\Permission\Traits\HasRoles`, activity-logged, quota-tracked (`TracksQuota`, `quotaKey = 'max_users'`). `branches()` belongsToMany with pivot `is_primary`; `primaryBranch()` resolves the stamping branch (explicit primary, else first assigned).
- **`Branch`** (`app/Models/Branch.php`) — per-company branches (`is_main`, geo, bilingual address, signature/stamp images for printouts), quota `max_branches`, `Branch::mainFor($companyId)` fallback used by the B2B portal (no staff user).
- **`Company`** (`app/Models/Company.php`) — tenant root: currency, locale, timezone, `fiscal_year_start_month`, `settings` JSON.
- **SaaS layer** (`app/Services/`): `MoonLicenseClient`, `QuotaTrackingService` (+ `TracksQuota` trait), `CentralSyncService`/`CentralSyncQueue`, `UpdateManager`, `RemoteCommandExecutor`, `HealthReportCollector`, `SystemRequirementsChecker`, Installer. Moon ERP is operated as licensed multi-instance SaaS with quota enforcement.

## 2. Modules/Core deep dive

Path: `/home/moonui/moon-erp-be/Modules/Core/` — 39 migrations, 31 models. App dirs: `Actions, Contracts, Enums, Events, Http, Jobs, Listeners, Models, Notifications, Providers, Services, Support`.

### 2.1 Users / Roles / Permissions
- RBAC = `spatie/laravel-permission`. `Modules\Core\Models\Role` extends `Spatie\Permission\Models\Role` + activity log.
- Role/permission changes are audited via `Modules\Core\Providers\EventServiceProvider` listening to Spatie's `PermissionAttached/Detached`, `RoleAttached/Detached` → `Listeners\LogRolePermissionChange`.
- Roles gained columns by migration:
  - `2026_05_30_100000_add_data_scope_to_roles_table.php` → `roles.data_scope` (nullable string) + `roles.home_page` (role landing page).
  - `2026_06_10_150000_add_company_id_to_roles_table.php` → roles are now company-scoped (tenant-aware RBAC).
- **`PermissionDependencyRegistry`** (`Modules/Core/app/Support/PermissionDependencyRegistry.php`): plugin point where each module registers a permission-dependency contributor by prefix at boot (`register('lis', LisPermissionDependencies::class)` — done in `Modules/LIS/app/Providers/LISServiceProvider.php`). Contributor contract: `expand()` (close permission set over dependency edges on role save), `mapFor()` (auto-select in roles UI), `presets()` (one-click role bundles), `groupOrder()` (workflow-ordered groups). Explicitly built to fix a Core→LIS layering inversion; an HIS/OBGY module would register `his`/`obgy` prefixes the same way.
- `RoleSaveService` in `Core/app/Services` persists roles with dependency expansion.

### 2.2 Branch DataScope pattern
`Modules/Core/app/Support/DataScope.php` (docblock says "Multi-branch / role data-visibility scoping for LIS list endpoints"):
- `effectiveScope($user)`: broadest `data_scope` across the user's roles — `all > branch > own`; null/absent = `all` (backward compatible; restricted user must hold exactly one restricted role).
- `apply(Builder, $user, Request, $opts)`:
  - `own` → `where created_by = user.id`;
  - `branch` → `whereIn branch_id (user branches) OR branch_id IS NULL` (legacy-row leak kept deliberately until backfill), with optional narrowing via `?branch_id=` or `X-Branch-Id` header (Angular FE branch selector sends header globally);
  - `all` → unrestricted, honours explicit branch filter.
- `operatingBranchId($user)`: new records stamped with the user's single primary branch regardless of viewing filter.
- Consumed today by LIS controllers/services: `LabRequestController`, `LabSampleController`, `LabResultController`, `LabInvoiceController`, `LabPaymentController`, `CreateLabRequest`, `LabSampleService`, etc. Directly reusable for HIS/OBGY list endpoints.

### 2.3 Master data: BusinessPartner (no core Patient)
- **`BusinessPartner`** (`Modules/Core/app/Models/BusinessPartner.php`, table `business_partners`): the single party master. `company_id`, `branch_id`, `code`, bilingual name, enums `PartnerType/PartnerClassification/PartnerStatus`, flags `is_customer`/`is_supplier`, tax/CR numbers, credit limit, payment terms. Children: `bp_contacts`, `bp_addresses`, `debt_changes` (linked to accounting journal entries via `add_journal_entry_id_to_debt_changes_table`).
- **Extension-table pattern** over the partner master:
  - Accounting: `AccBpExt` (`accExt()` hasOne, `partner_id`) — partner GL accounts.
  - CRM: `crm_customer_ext` (`Modules/CRM/app/Models/CrmCustomerExt.php`, `partner_id`) — customer_type, industry, source, lifecycle_stage, tags.
- **There is NO Patient entity in Core.** The only patient model in the whole ERP is `Modules\LIS\Models\LabPatient` (table `lab_patients`): `mrn`, bilingual names, DOB/gender/blood group, national ID/passport/nationality, `insurance_info` JSON, `medical_history`, `partner_id` → BusinessPartner, `insurance_contract_id`, `external_lab_id`, patient-portal tokens (`portal_link_token` auto-generated `Str::random(64)` on create for QR/WhatsApp links).
- Other clinical actors also resolve to BusinessPartner via `partner_id`: `LabDoctor`, `LabInsuranceContract`, `LabMonthlyInsuranceInvoice`, `LabExternalLab`.
- Product master also lives in Core: `Product`, `ProductVariant`, `ProductUnit`, `ProductPrice`, `ProductPricingTier`, `ProductSerial`, `Brand`, `ProductCategory`, `UnitGroup/Unit` — reusable for drug/consumable catalogs.

### 2.4 Shared platform services (Core/app/Services + Models)
- `SequenceService` — document numbering per company/module/entity, optional `per_branch` counters, fiscal-year/month reset (`sequences`, `sequence_counters`).
- `SettingsService` (singleton) + `SettingDefinition` — scoped settings, `SettingScope` enum: `global | company | branch | user`. LIS seeds its own definitions (`LabSettingDefinitionSeeder`).
- `ApprovalWorkflowService` — multi-level approvals (`approval_workflows`, `approval_workflow_levels`, `approval_logs`); **`ApprovalModule` enum currently only `Sales` and `Purchases`** — needs extension for clinical document types.
- `Attachment` model (`attachments` table, `morphTo`) + root trait `HasAttachments` (`morphMany`) — polymorphic file attachments for any entity.
- AI layer: `AiChatService`, `AiSettingsService`, `AiUsageService` with tables `ai_settings`, `ai_user_preferences`, `ai_usage_logs`, `ai_response_cache`; HRM already builds on it (`HrAiAnalysis`).
- `NotificationService`, `DashboardService`, `HijriDateService` (Hijri calendar — relevant for KSA/clinical contexts), `VariantGeneratorService`, `DatabaseBackup` model + migrations.
- Geo lookups: `Country`, `Governorate` (WebStore adds `City`).
- Contracts/DI: `BusinessPartnerServiceInterface` and `ProductServiceInterface` bound in `CoreServiceProvider::register()` — other modules consume partner/product behavior through interfaces.

### 2.5 Module registration
`CoreServiceProvider` (pattern repeated in every module): boots translations, recursive config merge, views, `loadMigrationsFrom(module_path(..., 'database/migrations'))`; registers module `EventServiceProvider` + `RouteServiceProvider`. Enable/disable per module via `modules_statuses.json`.

### 2.6 Events bus
- Core domain events: `BusinessPartnerCreated/Updated/Frozen`, `ProductCreated/Updated` (dispatched from `Core/app/Actions/CreateBusinessPartner`, `UpdateBusinessPartner`, `CreateProduct`).
- Cross-module subscription example: `Modules/Accounting/app/Providers/EventServiceProvider.php` maps `BusinessPartnerCreated/Updated` → `CreatePartnerAccounts` (auto-creates partner GL accounts).
- LIS events (10): `LabSampleCollected/Received`, `AllSamplesCollected`, `LabResultEntered/Released/Corrected/Retracted`, `LabRequestCompleted/Invalidated`, `CriticalResultDetected`; 11 LIS listeners chain the workflow (e.g. `GenerateResultsOnSampleReceived`, `PostInvoiceItemOnResultRelease`, `ConsumeReagentOnResultEntry`, `CreateCriticalAlert`).
- Other modules with events: Accounting (`JournalEntryPosted/Approved/Cancelled`, `FiscalYearClosed`, `PeriodClosed`), EInvoicing (`InvoicePosted`, `EInvoiceStatusChanged`, ...), Sales/Purchases/WebStore (`StoreOrderPlaced`).
- **Financial integration is NOT events-only**: `Modules/LIS/app/Actions/PostLabInvoice.php` directly invokes `Modules\Accounting\Actions\CreateJournalEntry` (standard JE + insurance-invoice JE variants) — a sanctioned synchronous Action-call pattern for GL posting.

## 3. Per-module map (migrations / models counted from `database/migrations` and `app/Models`)

| Module | Purpose (one line) | Key entities | Mig/Models |
|---|---|---|---|
| Core | Master data + platform services (partners, products, settings, sequences, approvals, AI, RBAC support) | BusinessPartner, Product, Role, Sequence, Setting, ApprovalWorkflow, Attachment | 39/31 |
| Accounting | Full GL: chart, journals, fiscal calendar, banks, checks, assets, budgets, cost centers | Account, JournalEntry(+Line), FiscalYear/Period, BankAccount/Reconciliation, FixedAsset, Budget, PaymentVoucher, ReceiptVoucher, Currency/ExchangeRate, TaxRate | 51/44 |
| LIS | Production lab system: full request→sample→result→invoice lifecycle, QC, machine interfacing, insurance, B2B referral, commissions, patient portal | LabPatient, LabRequest, LabSample, LabResult, LabInvestigation(+NormalRange/Panel), LabInvoice/Payment, LabMachine(+TestMapping/Result), LabQcLot/Result, LabInsuranceContract, LabExternalLab(+Referral), LabDoctorCommission, LisCashierSession | 137/66 |
| HRM | HR: org, biometric attendance, leave, payroll, loans, recruitment, training, performance | Employee, Department, Position, Attendance, BiometricDevice, LeaveRequest/Balance, Payroll(+Items), SalaryStructure, Candidate/JobOpening, PerformanceReview, TrainingProgram | 47/40 |
| Inventory | Warehousing: receipts/issues/transfers/adjustments/counts, movements, cost layers | Warehouse, StockBalance, InventoryMovement, InventoryCostLayer, InventoryReceipt/Issue/Transfer/Adjustment/Count | 15/14 |
| Sales | Quote→order→delivery→invoice→payment, returns, schedules, commissions | SalesQuotation, SalesOrder, SalesDeliveryNote, SalesInvoice, SalesPayment(+Schedule), SalesReturn, SalesCommission(+Rule) | 16/14 |
| Purchases | PR→PO→GRN→bill→payment, returns, supplier price lists | PurchaseRequest, PurchaseOrder, PurchaseGrn, PurchaseBill, PurchasePayment(+Schedule), PurchaseReturn, SupplierPriceList | 14/13 |
| POS | POS terminals, cash sessions, coupons, held orders | POSTerminal, POSSession(+Payment), POSCoupon, POSHeldOrder | 7/5 |
| WebStore | Full e-commerce storefront incl. prescriptions upload | StoreCustomer(+Otp/Address), StoreCart, StoreOrder(+Status flow), StoreCoupon, StorePrescription, ProductImage/Rating/Tag | 25/29 |
| CRM | Customer-relationship extension over BusinessPartner: tickets, interactions, SLA | CrmCustomerExt, CrmTicket(+Comment), CrmCustomerInteraction, CrmSlaPolicy, CrmCustomerTag | 1/6 |
| CMMS | Maintenance: assets, work orders (labor/parts), PM schedules — relevant for medical equipment | CmmsAsset(+Category), CmmsWorkOrder(+Labor/Part), CmmsPmSchedule | 1/6 |
| Production | Manufacturing: BOMs, routing operations, production orders | BillOfMaterials, BomComponent/Operation, ProductionOrder(+Material/Operation), ProductionCenter | 3/7 |
| QMS | Quality: inspection plans/results, non-conformance, CAPA, supplier evaluation | InspectionPlan, Inspection(+Criteria/ResultRecord), NonConformance, CapaAction, SupplierEvaluation | 1/7 |
| NPHIES | Saudi NPHIES health-insurance platform integration (pre-auth, claims transactions) | NphiesConfig, NphiesPreauth, NphiesTransaction | 6/3 |
| EInvoicing | Regulatory e-invoicing documents and submissions | EInvoiceConfig, EInvoiceDocument, EInvoiceSubmission | 3/3 |

## 4. What HIS / OBGY inherit for free (answer to the study question)

1. Multi-company tenancy (`TenantAware`/`company_id`) and full audit trail (`Auditable`) by simply extending `BaseModel`.
2. Branches + role `data_scope` (`all/branch/own`) via `DataScope::apply()` — already battle-tested on LIS clinical endpoints.
3. Spatie RBAC with company-scoped roles, audit of role/permission changes, and the `PermissionDependencyRegistry` contributor pattern for module-owned permission graphs and role presets.
4. Unified party master (`BusinessPartner` + extension tables) — doctors, insurers, referral facilities already modeled this way in LIS; OBGY referring doctors/insurers map directly.
5. Document numbering (`SequenceService`), scoped settings (`SettingsService`), polymorphic attachments, notifications, AI services, Hijri dates, DB backups.
6. Financial backbone: direct GL posting pattern (`PostLabInvoice` → `CreateJournalEntry`), receivables (`debt_changes`), insurance monthly invoicing (LIS), NPHIES claims module, EInvoicing.
7. Adjacent operational modules: Inventory (drug/consumable stock), CMMS (medical equipment maintenance), HRM (clinical staff, shifts), QMS (clinical quality/CAPA), WebStore/portal patterns (patient portal tokens already in `LabPatient`).
8. Eventing conventions (domain events + listeners per module) for clinical workflow chaining, proven by 10 LIS events / 11 listeners.

### Gaps a HIS build must close (inference, marked)
- No core `Patient` entity — `lab_patients` is LIS-private; HIS needs a promoted shared patient master (or a Patients core module) with LIS migration/linking strategy. *(inference)*
- `ApprovalModule` enum covers only Sales/Purchases — clinical approvals need enum/route extension. *(inference)*
- DataScope's "broadest role wins" rule requires role-assignment discipline for clinical confidentiality. *(documented caveat in the class docblock)*
- No appointment/encounter/ADT concepts anywhere in the codebase today (`LabVisit` is lab-local). *(inference)*
