# Moon ERP Business Backbone Survey: Accounting, HRM, Inventory, NPHIES

Scope: the four modules a hospital (HIS) module would reuse, surveyed at `/home/moonui/moon-erp-be/Modules/{Accounting,HRM,Inventory,NPHIES}`. All claims traceable to files read; inferences marked **(inference)**.

Module counts (from directory listings):

| Module | Models | Services | Migrations | Notable |
|---|---|---|---|---|
| Accounting | 44 | 27 | 51 | + 40 Actions, 5 Events, 1 Contract |
| HRM | 40 | 13 | 47 | biometric attendance sub-system |
| Inventory | 14 | 1 (`StockService`) | 15 | FIFO cost layers |
| NPHIES | 3 | 7 | 6 | FHIR client + builders |

---

## 1. Accounting — the central general ledger

### Key entities
- **Chart of accounts**: `accounts` table (`2026_02_10_100003_create_accounts_table.php`), model `Modules/Accounting/app/Models/Account.php`. Hierarchical (`parent_id`, `level`, `has_children`), bilingual (`name`/`name_ar`), `classification`, `nature`, `account_type` (Header/Detail enums), `status`, `is_system`, `zakat_classification`. Header accounts only aggregate; postings allowed on detail accounts only.
- **Journal**: `journal_entries` + `journal_entry_lines` (`2026_02_10_100007/100008`), models `JournalEntry.php`, `JournalEntryLine.php`. Status lifecycle `Draft → Approved → Posted / Cancelled` (helpers `isDraft()/isApproved()/isPosted()/isCancelled()`), balance check `isBalanced()` via `bccomp(total_debit, total_credit, 3)`. Crucial integration fields: `source_type`, `source_id` (added by `2026_02_23_080008_add_source_fields_to_journal_entries.php`), `partner_id` → `Modules\Core\Models\BusinessPartner` (`2026_03_10_080728`), `reversed_entry_id` for reversals, quota tracking via `TracksQuota` (`max_journal_entries`).
- **Fiscal calendar**: `fiscal_years`, `fiscal_periods`; period resolution logic in `Modules/Accounting/app/Services/JournalEntryService.php::resolvePeriod()` — Open period accepts any entry type; SoftClosed accepts adjustments only; Closed accepts nothing.
- **Treasury**: `bank_accounts`, `checks_issued`, `checks_received`, `petty_cash` + `petty_cash_transactions`, `account_transfers`; bank reconciliation (`bank_reconciliations`, `bank_statements`, `reconciliation_matches`, `reconciliation_rules`).
- **Vouchers**: `ReceiptVoucher`/`ReceiptVoucherLine`, `PaymentVoucher`/`PaymentVoucherLine` models with Approve/Cancel actions (`ApproveReceiptVoucher.php`, `ApprovePaymentVoucher.php`, ...).
- **Other**: multi-currency (`currencies`, `exchange_rates`, `CurrencyRevaluationService`), fixed assets + depreciation, budgets, entry templates + recurring entries, tax (`tax_rates`, `withholding_tax_rates`), Zakat (`zakat_calculations`), year-end closing, opening balances.

### Integration contract for HIS
- **Formal interface**: `Modules/Accounting/app/Contracts/AccountingServiceInterface.php`:
  - `createJournalEntry(JournalEntryDTO $dto): JournalEntry`
  - `getAccountBalance(int $companyId, int $accountId, ?string $asOfDate): array`
  - `getTrialBalance(int $companyId, ?string $asOfDate): Collection`
  - Implemented by `Modules/Accounting/app/Services/AccountingService.php` (delegates to `CreateJournalEntry` action and `GeneralLedgerService`).
- **The real workhorse**: `Modules/Accounting/app/Actions/CreateJournalEntry.php::execute(array $data, array $lines)`. Inside one DB transaction it: rejects header-account lines ("every programmatic flow (sales payments, vouchers, payroll, …) funnels through this action" — comment in file), resolves fiscal period, computes totals via bcmath, creates Draft entry + lines, handles multi-currency lines (explicit `exchange_rate` or `ExchangeRateService`, throws if unresolvable).
- **Auto account creation**: `Modules/Accounting/app/Services/AutoAccountService.php::createChildAccount(companyId, parentCode, name, nameAr)` — creates child accounts under a parent code, converting detail→header only when the parent has no journal lines. Listener `Modules/Accounting/app/Listeners/CreatePartnerAccounts.php` is injected directly by LIS (`PostLabInvoice` constructor).
- **Events** (`app/Events/`): `JournalEntryPosted`, `JournalEntryApproved`, `JournalEntryCancelled`, `PeriodClosed`, `FiscalYearClosed`.

### Proven consumers (auto-posting precedent)
Grep for `CreateJournalEntry`/`source_type` consumers outside Accounting found, among others:
- LIS: `Modules/LIS/app/Actions/PostLabInvoice.php` (revenue JE with `entry_type: 'lab_invoice'`, `source_type: 'lab_invoice'`, `source_id: invoice id`; separate COGS JE `entry_type: 'lab_cogs'`; plus `lab_insurance_invoice`, `external_lab_inbound/outbound` entry types), `PostLabPayment.php`, `PostLabInvoiceItem.php`, `CancelLabInvoice.php`, `RecordExternalLabPayment.php`, `DoctorCommissionService.php`.
- Sales: `PostSalesInvoice.php`, `PostSalesPayment.php`, `PostSalesReturn.php`; Purchases: `PostPurchaseBill.php`, `PostPurchasePayment.php`; HRM: `PayrollAccountingService.php`; Core: `CreateDebtChangeJournalEntry.php`.

**HIS implication**: hospital billing posts revenue/COGS/payment JEs through `CreateJournalEntry` with HIS-specific `entry_type`/`source_type` values. No ledger, voucher, treasury, or tax engine should be built in HIS.

---

## 2. HRM — staff, shifts, attendance, payroll

### Key entities
- **Employee**: `employees` table (`2026_03_01_100003_create_employees_table.php`), model `Modules/HRM/app/Models/Employee.php`. Fields: `employee_number` (unique), bilingual names, `user_id`, `department_id`, `position_id`, `branch_id`, `manager_id` (self-FK hierarchy), contract dates/type, `basic_salary`, `payment_method`, `bank_iban`, `national_id`, `passport_number`, plus nursing/special-needs flags (`is_nursing`, `nursing_start_date`, `is_special_needs` — added by `2026_04_11_100002`).
- **The user_id chain**: migration line — `$table->foreignId('user_id')->nullable()->unique()->constrained('users')->nullOnDelete();`. So: `users` (auth identity) → `employees.user_id` (1:1, nullable) → `department/position/branch/manager`. A HIS practitioner profile only needs an `employee_id` (or `user_id`) FK; no new staff tables. **(inference on HIS usage; the FK chain itself is verified)**
- **Org structure**: `departments` (hierarchical: `parent_id`, `manager_id`, `branch_id` — `Department.php`), `positions`.
- **Shifts**: `Modules/HRM/app/Models/Shift.php` — `start_time/end_time`, `break_duration_minutes`, `is_night_shift`, `grace_period_minutes`, `overtime_start_after_minutes`, late/early-leave thresholds and "counts full" rules, `flexibility_minutes`, and hospital-relevant `nursing_extra_minutes`, `special_needs_extra_minutes`, `presence_check_enabled`, `presence_check_interval`. Per-employee assignment via `shift_schedules` (`ShiftSchedule`).
- **Attendance**: `attendances` (`Attendance.php`: `check_in/check_out`, `worked_hours`, `overtime_hours`, `late_minutes`, `early_leave_minutes`, `check_in_source`, `device_name`, `punch_type`), corrections (`attendance_corrections`), presence checks, and a biometric sub-system: `biometric_devices`, `biometric_employee_mappings`, `biometric_sync_logs`, `attendance_punch_types` (migrations `2026_04_09_1000xx`), service `BiometricAttendanceService.php`.
- **Payroll**: `salary_components`, `salary_structures`, `payrolls` → `payroll_items` → `payroll_item_details`, `payroll_adjustments`, `employee_loans` + `loan_installments`, `eos_settlements`. Leave: `leave_types/balances/requests/approvals`. Also recruitment (job openings/candidates/offers), performance (KPIs/reviews), training.

### Integration contract for HIS
- `Modules/HRM/app/Services/PayrollAccountingService.php::postPayroll()` posts payroll to GL via `CreateJournalEntry` (`entry_type: 'payroll'`, `source_type: 'payroll'`), using settings `hrm.salary_expense_account_id` / `hrm.salary_payable_account_id`; `createPaymentEntry()` clears per-employee payable sub-accounts against a bank account.
- `Modules/HRM/app/Services/EmployeeAccountService.php::getOrCreateSubAccount()` auto-creates per-employee GL sub-accounts with code `{parent_code}-{employee_number}` — the pattern HIS can reuse for doctor commission/payable accounts. **(inference on reuse; mechanism verified)**

**HIS implication**: doctors/nurses/technicians are `Employee` rows linked to `users`; duty rosters = `shifts` + `shift_schedules` (nursing fields already exist); payroll and attendance must not be rebuilt.

---

## 3. Inventory — pharmacy & consumables backbone

### Key entities
- **Warehouse**: `warehouses` (`Warehouse.php`): `branch_id`, `code`, `type` (WarehouseType enum), `parent_warehouse_id` (hierarchy), `manager_id`, `allow_negative_stock`, `account_id` (GL link).
- **StockBalance**: `inventory_stock_balances` — per `product_id`/`product_variant_id`/`warehouse_id`: `quantity`, `average_cost`, `total_value`.
- **InventoryMovement**: `inventory_movements` — full audit trail per movement: `movement_type` (enum `Modules/Inventory/app/Enums/MovementType.php`: `receipt, issue, transfer_in, transfer_out, adjustment, opening, return`), polymorphic `reference_type`/`reference_id`, `quantity_in/out`, `unit_cost`, `total_cost`, `balance_after`, `cost_after`.
- **InventoryCostLayer**: `inventory_cost_layers` — FIFO layers (`original_quantity`, `remaining_quantity`, `unit_cost`), always created on receipt.
- **Documents**: receipts, issues, transfers, counts, adjustments (header + items models each).
- **Item master lives in Core**: `Modules/Core/app/Models/Product.php`, `ProductVariant`, `ProductUnit`, `ProductCategory`, `ProductSerial`, etc. Inventory tracks quantities/costs only.

### Integration contract for HIS
`Modules/Inventory/app/Services/StockService.php` (the single service, 324 lines):
- `increaseStock(array)` / `decreaseStock(array)` — both take `company_id, product_id, product_variant_id, warehouse_id, quantity, unit_cost, movement_type, reference_type, reference_id, date, notes`; update balance, write movement, manage FIFO layers.
- `getIssueCost()`, `getProductCost()`, `getValuationMethod()` — valuation per company setting `inventory.valuation_method` (`fifo` | `weighted_avg`).
- Consumers found outside Inventory: Sales (`ConfirmDeliveryNote`, `PostSalesInvoice`, returns), Purchases (`PostPurchaseBill`, returns).
- **Medical precedent**: `Modules/LIS/app/Models/LabReagent.php` links `product_id` and reads average cost directly from `Modules\Inventory\Models\StockBalance` when `cost_source === 'product'`; LIS also has `LabInvestigationConsumable`.

**HIS implication**: a hospital pharmacy/sub-store = `Warehouse` rows (optionally a new WarehouseType); drug dispensing = `decreaseStock()` with `movement_type: issue` and `reference_type` pointing at the prescription/encounter document. No stock engine in HIS. **(reference_type usage for prescriptions is inference; the open polymorphic mechanism is verified)**

---

## 4. NPHIES — Saudi insurance eligibility, preauth, claims

### Key entities (3 models)
- `NphiesConfig` (`nphies_config`): `provider_license`, `facility_nphies_id`, `sandbox_mode`, `certificate_path`, `private_key_path`, `sender_id`, `receiver_id`, `is_active`.
- `NphiesTransaction` (`nphies_transactions`): audit log of every FHIR exchange — `type` (enum incl. Eligibility/Claim), `bundle_id`, full `request_json`/`response_json`, `status`, `http_status_code`, `response_time_ms`, and links `patient_id`, `lab_request_id`, `lab_invoice_id`.
- `NphiesPreauth` (`nphies_preauths`): `preauth_ref`, `insurer_code`, `status`, `valid_from/valid_to`, `approved_amount`, `approved_items` (json), `diagnosis_code`, `isValid()` guard.

### Services (7)
- `NphiesFhirClient` — bundle building (`buildMessageBundle`) + HTTP send with transaction logging.
- `FhirPatientBuilder`, `FhirServiceItemBuilder` — FHIR resource builders.
- `NphiesEligibilityService::check(LabPatient $patient, string $insurerCode, ?string $serviceDate)` — builds `eligibility-request` bundle (Patient + Coverage + provider/payer Organizations + CoverageEligibilityRequest), returns `{eligible, disposition, benefits, transaction_id, raw, error}`.
- `NphiesPreauthService` — pre-authorization requests.
- `NphiesClaimService::submit(LabPatient, LabInvoice, insurerCode, diagnosisCode, ?preauthRef)` — validates preauth, builds claim items from `invoice->items` (via each item's `investigation` and `net_price`), submits `claim-request` bundle, returns `{outcome, total_submitted, total_covered, total_copay, payment_date, items, transaction_id, error}`, and writes back to the invoice.
- `NphiesPollingService` — async response polling.

### Critical coupling finding
NPHIES is **hard-coupled to LIS models today**: services type-hint `Modules\LIS\Models\LabPatient` and `Modules\LIS\Models\LabInvoice`; its own migrations modify LIS tables:
- `2026_04_02_200001_add_saudi_id_fields_to_lab_patients.php` → `lab_patients.national_id_type`, `passport_country`
- `2026_04_02_200002_add_sbscs_code_to_lab_investigations.php` → SBS billing codes on `lab_investigations`
- `2026_04_03_000001_add_nphies_claim_fields_to_lab_invoices.php` → `lab_invoices.nphies_claim_status`, `nphies_preauth_ref`, `nphies_covered_amount`, `nphies_copay_amount`

**HIS implication**: the FHIR client, transaction log, preauth store, and config are reusable as-is; the eligibility/claim services need their inputs generalized (patient + billable-item abstractions, or HIS-specific builders alongside `FhirPatientBuilder`/`FhirServiceItemBuilder`) before HIS encounters/invoices can flow through. This is a generalization gap, not a build-from-scratch gap. **(inference from the coupling evidence above)**

---

## 5. Conclusions for the HIS architecture decision

1. **Billing, payroll, pharmacy stock, and insurance connectivity already exist** in production modules with formal contracts (`AccountingServiceInterface`, `CreateJournalEntry`, `StockService`, NPHIES services). HIS must consume, not rebuild.
2. **The platform's specialty-module pattern is proven by LIS**: LIS owns clinical documents (`lab_invoices`, `lab_patients`, …) and delegates GL posting (`PostLabInvoice` → `CreateJournalEntry`), costing (`LabReagent` → `StockBalance`), and insurance (NPHIES services) to backbone modules. This is the ready-made template for HIS — and for OBGY functionality.
3. **`journal_entries.source_type/source_id` + free-form `entry_type` strings** give HIS an immediate posting slot (e.g. `his_invoice`, `his_pharmacy_issue`) with zero Accounting changes.
4. **HRM already contains hospital-grade workforce features** (nursing shift extras, biometric attendance, per-employee GL sub-accounts) — practitioner management in HIS reduces to a clinical profile referencing `employees`.
5. **The only backbone investment HIS requires is NPHIES generalization** away from `LabPatient`/`LabInvoice` type-hints toward a patient/invoice abstraction shared by LIS and HIS.
