The seed pipeline (S-1 → S-6)
Business view
The seeder builds a clinic the way a real one grows: first the company and its setup, then its branches and cash drawers, then the people, then the equipment and stock and insurers, and finally a year of patients and their visits — with appointments, invoices, payments, and expenses. Two extra steps (HR and accounting coverage) only run when the accounting module is present.
Each step is labeled S-1, S-2, … in the console output, so when you watch a seed run you can see exactly where it is.
Technical view
The sequence is driven by main() in index.js:51. Steps in bold run on every branch; the last two are hook-gated.
Before anything: guardrails
- Config is parsed from env vars / CLI flags (config.js:57).
- If
--cleanup <id>is present, it tears down that company and exits
- The banner prints the target DB + params, then
assertSafeTargetblocks
non-local DBs unless confirmed (index.js:63-64).
- Idempotency check: if
OWNER_EMAILalready exists, refresh all users'
passwords, reprint the summary, and return — no second company (index.js:71-91).
S-1 — Provision the company · provisionBase.js:45
Creates the Company with the same nested seed arrays the real register resolver uses: permission groups, procedure / condition / medical / clinical / note-template groups, labs, referral sources, patient tags, payment types, feedback questions. It's a deliberate hand-kept replication of authMutations.js register (~127–286) — provisionBase.js:10-12 says so and tells you to mirror changes.
Notable choices: the company is created as tier: 2, isBeta: true (provisionBase.js:67-70) so feature-flagged modules (claims, ZATCA e-invoicing, custom forms) are unlocked for the demo; maxBranches/maxUsers are sized to the params. A unique companyLoginName is guaranteed by appending a uuid fragment on collision (provisionBase.js:55-57). Then hooks.provisionCompanyModules runs (no-op here; Category tree + CoA on accounting) (provisionBase.js:101).
S-1b — Feature flags · index.js:98-99
hooks.enableFeatureFlags — a no-op on this branch; enables FEATURE_ACCOUNTING_MODULE on the accounting branch.
S-2 — Branches, treasuries, price list · setupBranches.js:70
For each branch: a Branch (localized address, three rooms, 09:00–21:00 hours built in the tenant timezone — see setupBranches.js:60-68 for the tz subtlety), three treasuries (cash / card / bank, setupBranches.js:18), and the BranchTreasury default-treasury mappings the payment UI reads (setupBranches.js:25). Once, company-wide, a Standard Price List with a Price per procedure (setupBranches.js:34). Distinct cities keep branch names unique.
S-3 — People · setupPeople.js:37
- Owner:
isOwner+isDoctorat 100%, in the Owners group, on all
branches (setupPeople.js:51-68). The owner is createdById for everything that follows.
- Doctors (
DOCTORS): each with a commission % from a fixed set, an optional
base salary (some are commission-only), a home branch, the Doctors group, and a few per-procedure commission overrides (setupPeople.js:77-124).
- Support staff (
STAFF): cycled through role archetypes — Receptionist,
Accountant, HRManager, Nurse, Manager, InventoryClerk, CallAgent, Marketer — each with a matching permission group and salary band (setupPeople.js:19-28). Note HRManager uses the Accountant ++ group, which carries HR/payroll permissions.
hooks.createSalaryProfile runs per user (no-op here; Salary-Hub profile on accounting) (setupPeople.js:121,160).
S-4 — Suppliers, inventory, assets, insurance, capital · setupAssets.js:14
- Owner capital is injected into each branch's bank treasury first
(250k–400k) so the clinic is funded before it spends (setupAssets.js:18-21).
- 3 suppliers, a master storage per branch, and the **15 catalog inventory
items (catalog.js:8), stocked via two purchase cycles per branch** (setupAssets.js:56-64).
- Fixed assets per branch from the equipment catalog
(catalog.js:27, setupAssets.js:66-81).
- Insurance: the first three insurers of the country preset, each with a
policy and 2–4 policy-class tiers (Class A–D) (setupAssets.js:83-115, catalog.js:36).
S-5 — Patients, visits, invoices, payments, expenses · generate.js:245
The core. For each of PATIENTS patients: demographics, optional insurance, optional wallet prepay, then a randomized series of visits. Each visit may create an appointment, operations, a fully-mathed invoice, a doctor commission, inventory consumption, and a payment. ~15% of patients also get a quotation. Then monthly operating expenses across every branch and month, and a batch of upcoming appointments. The arithmetic is documented in The money model.
After S-5, the seeder mirrors net on-hand stock into the denormalized masterAmount/masterValue columns the inventory list reads (index.js:121-124).
S-5b — HR · index.js:115-118
hooks.runHr — returns null here (logged as "skipped — HR module not present"). On accounting: leave types, holidays, comp config, schedules, attendance, leave.
S-6 — Accounting coverage · index.js:126-129
hooks.runCoverage — null here ("skipped — accounting module not present"). On accounting: doctor payouts, payroll runs, claims, depreciation, transfers.
Wrap-up
Running counters (patient / invoice / transaction reference numbers) are persisted onto the company so the app continues numbering where the seed left off (index.js:131-135). Then hooks.reconcile runs (OK here), the summary + credential blocks print (report.js:13), and — on accounting only — a failed reconcile sets a non-zero exit code (index.js:159-162).