Dentolize · Dynamic Clinic Seeder Walkthrough
On this pageBusiness viewTechnical view

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

(index.js:54-61).

  • The banner prints the target DB + params, then assertSafeTarget blocks

non-local DBs unless confirmed (index.js:63-64).

  • Idempotency check: if OWNER_EMAIL already 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 + isDoctor at 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.runCoveragenull 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).