Base insurance is a percentage-only rule: patient share is now total minus the
base share, and the contract franchise no longer inflates it (franchise stays
meaningful for supplementary contracts only).
Coverage percentages are managed centrally by admin per service category
(outpatient/inpatient, extensible via the ServiceCategory enum). A tenant
contract may override a category, otherwise it follows the admin default live —
changing the central value immediately applies to every contract that did not
override it.
- add ServiceCategory enum + GET /api/v1/service-categories as the single source
of the category list for every client
- add insurance_coverage_defaults (+ GET/PUT admin coverage-defaults endpoints)
and expose coverage_defaults on the insurance list and insurance-pricing
- add tenant_insurance_category_coverage; tenant-insurances accepts optional
category_coverages (needs insurances.update) and returns the effective
percentages with their source
- add service_items.service_category; visits always resolve as outpatient
- drop the reverse-engineered percent from patient_share_rials in MyPatientsPage
and align the client-side BillingCalculator mirror in CreateStep
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Users typing on a Persian keyboard produced two distinct failures. Fields with
type="number" silently returned an empty string — the browser rejects Persian
digits, so the value was lost and saved as empty or zero. Text fields passed the
Persian characters straight through to the database, where a mobile stored as
۰۹۱۲… never matches 09… again. The secretary form hit the second case with no
validation at all.
Frontend:
- Adds digitsOnly() and the national-code schemas to lib/utils, plus lib/forms
with numericField()/latinDigitsField() wrappers for React Hook Form fields.
- Converts every type="number" input to type="text" inputMode="numeric" with
digit normalization; none remain. Fields that legitimately carry non-digits
(sheba, landline) only get the digits translated, keeping IR and separators.
- Points the patient national-code and mobile schemas at the shared normalizing
schemas, which accept Persian input instead of rejecting it.
- Drops two duplicate local digit converters in favour of the shared helper.
Backend:
- Adds NumericFieldNormalizerSubscriber, translating digits in whitelisted
numeric keys of JSON request bodies under /api/v1/ before controllers run, so
nobat724_front and clinic-pro-tauri are covered too. Translation only — no
characters are stripped, non-string values and other keys are untouched.
Three component tests asserted on role="spinbutton" and numeric input values;
both are properties of type="number", so they were updated to match the new
text inputs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>