Dentolize · Dynamic Clinic Seeder Walkthrough
On this pageBusiness viewTechnical view

For Quality / QA

Business view

QA is the primary consumer of this feature. It exists to give you a realistic clinic to test against — and it's also a thing you need to test itself. This page covers both: how to use the seeded data, and where the edges are.

What the seed gives you to test with

A single run produces (at sandbox "small profile" scale):

  • Patients across the billing matrix: cash, insured (various %), VAT-exempt,

and combinations — the descriptive names spell out which is which, so you can pick a test subject without opening the record.

  • Invoices in every payment state: paid in full, partially paid, and unpaid,

paid by cash / card / wallet.

  • Insurance receivables left intentionally open (so there's AR to test claims

against), across multiple insurers and policy classes.

  • Appointments: months of completed history plus today's and the next two

weeks' bookings in open/confirmed/checked-in states.

  • Inventory that has been purchased and consumed, so on-hand amounts and

values are non-trivial.

  • Staff with commissions, salaries, and different permission groups.

High-value manual checks (using the data)

  • Follow the money on one insured patient. Open their invoice: confirm

subtotal − discount + VAT = total, and that the insurance portion + patient portion reconcile. See The money model for the exact formula.

  • Filter the patients list by insurance company / policy class / tags — the

seed populates all of these.

  • Check the treasury balances are positive and plausible (capital was injected

before expenses/purchases, so nothing goes negative).

  • Open the calendar on "today" — there should be a few appointments in mixed

states, and more spread across the next fortnight.

Testing the seeder itself — the edges

  1. Determinism. Same SEED + same params ⇒ identical clinic. Two runs into

two fresh local DBs should match record-for-record.

  1. Idempotency. Re-running with the same OWNER_EMAIL must not create a

second company — it should refresh passwords and reprint the summary.

  1. Safety guard. Point DATABASE_URL at a non-local host with no --confirm

→ it must refuse. With --confirm → it proceeds.

  1. Cleanup. --cleanup <companyId> should remove the company and its children;

it must reject a non-UUID id.

  1. Country presets. SA (15% VAT, SAR), AE (5%, AED), EG (14%, EGP) —

VAT and currency must follow the preset, never Faker.

  1. Parameter clamping. Silly inputs (e.g. BRANCHES=0, PATIENTS=-5) are

floored to sane minimums rather than crashing.

  1. Generic-mode skips. On a base-schema DB (no accounting module), the HR (S-5b)

and coverage (S-6) steps must skip cleanly and reconciliation must report OK with nothing to reconcile — not error.

Known edges / things to watch

  • Insurer AR is left open on purpose — unpaid insurance balances are expected,

not a bug. They exist so the claims flow (accounting branch) has receivables.

  • Quotations can silently fail. Quotation and upcoming-appointment creation are

wrapped in .catch(() => null), so a schema mismatch there is swallowed. If quotations/appointments look sparse, that swallow is the first place to look.

  • allowNegative inventory. Items are created allowing negative stock; if

consumption ever exceeds purchases the amount can go negative by design.

  • Sandbox scale ≠ default scale. The PR text says "small profile" (1 branch,

2 doctors, 12 patients, 3 months); the code defaults are 2/4/80/12mo. The sandbox passes overrides. Test both scales.

Technical view

  • Determinism: single Faker instance seeded once; every draw goes through

rng (rng.js:15-20, rng.js:48). No direct Math.random.

  • Idempotency: owner-exists early return + updateMany password refresh

(index.js:71-91).

called on both the seed and cleanup paths (index.js:58,64).

  • Cleanup: UUID regex validation, FK triggers disabled within one

transaction, child-then-parent deletes, tables absent from the schema skipped (report.js:76-97).

  • Country/VAT source of truth: COUNTRIES preset, never Faker

(countries.js:7); used for tax, currency, splits (generate.js:73).

  • Parameter clamping: Math.max(1, …) / Math.max(0, …) on counts

(config.js:64-69).

  • Generic-mode skips: default hooks no-op; runHr/runCoverage return null

and the caller logs "skipped"; reconcile returns { ok: true } (hooks.js:140-150, index.js:117-129).

  • Swallowed failures to watch: makeQuotation and scheduleUpcoming

.catch(() => null) (generate.js:242, generate.js:348); per-procedure commission override .catch(() => null) (setupPeople.js:118).

  • Financial correctness: rounding via r2, insurance capped at remaining class

limit, VAT split patient/insurer (generate.js:80-97). Detailed in The money model.

  • Reconciliation gate: on the accounting branch a failed reconcile sets a

non-zero exit code (index.js:159-162); on this branch it always passes.