An appointment can now carry the insurance it is billed with: the service kind
(outpatient/inpatient) and the basic insurance. Confirming it no longer hands the
whole amount to the patient — the visit is split through BillingCalculator with the
coverage percent of that service kind, and the choice travels to the encounter and
the invoice built from it.
The enabled service kinds are a tenant-wide setting (all of that tenant's
insurances share it), so a tenant covering only one kind is never asked which one:
the panel resolves it the same way the server does.
- add tenant_service_category_settings + TenantServiceCategoryService, exposed on
the existing insurance-pricing endpoint (service_categories,
default_service_category); at least one kind must stay enabled
- add appointments.insurance_service_category / insurance_base_id with
AppointmentInsuranceService validating them against the tenant's own settings
and active contracts (basic only), accepted by PATCH and by confirm
- snapshot the kind on patient_sessions and invoices; the visit's coverage rule is
resolved per kind (services keep using their own ServiceItem.service_category)
- lib/insuranceShares becomes the single client-side mirror of BillingCalculator,
shared by the confirm modal, the appointment edit page and the session form
- surface the selection: confirm modal (with live shares), turns timeline chip,
appointment edit page, patient record service card and invoice summary
- the session form shows the insurance block whenever the tenant has an active
contract and prefills the patient's own insurance, so it can be changed
- fix: the confirm modal showed a zero visit price when the appointment had none —
it now falls back to the tenant's free-visit price like the server
- fix: useServiceCategories read one level too shallow, so Persian labels never
arrived and raw enum keys leaked into the contract summary
- fix: BlogsPage test asserted the public blogs endpoint after the page moved to
the admin one
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
Backend already returned 403 for ungranted secretary actions, but the UI still
showed the add/edit/delete buttons (e.g. clinic-services showed «بخش جدید» to a
secretary without services.create). Sweep every secretary-reachable page so each
create/edit/delete/manage control renders only when the matching
usePermissions().can(resource, action) is true. Owner/doctor/clinic are
unaffected — can() returns true when there is no permission context — so this
restricts only secretaries and mirrors the server checks.
Pages/components gated (resource):
- services: ClinicServicesPage, ServiceDetailPage (+ its tabs)
- inventory: InventoryPage, InventoryItemsTable, InventoryActionsMenu, PackagesView
- tags: TagsSettingsPage · staff: StaffPage · discounts: DiscountTab
- sms: SmsWalletPage · insurances: TenantInsuranceContracts
- clinic_doctors: ClinicDoctorsPage + ClinicDoctorsManager (props, default true)
- patients: PatientsListPage, MyPatientsPage, PatientDetailPage (records/notes/
sessions/attachments/calls/wallet — create/update/delete split)
- appointments: AppointmentsPage (add + empty-slot booking gated by create),
TurnsTable (status dropdown → read-only badge without update_status; actions
menu hidden without manage/cancel)
- appointment_settings: AppointmentSettingsPage + ClinicAppointmentSettingsPage
pass readOnly to ScheduleSection + FreeVisitPrice (new readOnly prop)
Not gated: view/read, search, filter, tabs, navigation, export, and modal
submit buttons reachable only via an already-gated trigger.
tsc clean; full frontend suite 501/501 passes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Updated InsuranceModal to include doctorUuid in the payload for insurance contracts.
- Enhanced TenantInsuranceContracts to allow selection of doctors and pass doctorUuid in API requests.
- Modified InsuranceController to handle doctorUuid for tenant insurance endpoints, ensuring contracts are stored per doctor.
- Updated API documentation to reflect the new optional doctor_uuid parameter for tenant insurance endpoints.
- Added tests to verify the functionality of per-doctor insurance contracts and ensure isolation of contracts between doctors.
Rebuild the /admin/insurance-pricing contracts UI to the Figma "مدیریت بیمه"
design and inject the coverage/franchise/ceiling fields the design omitted.
Backend:
- Add contract-level `kind` column to TenantInsurance (basic|supplementary),
defaulting to the catalog type; migration Version20260715093358.
- POST/PATCH /billing/tenant-insurances now accept effective_from,
effective_to, kind; PATCH also toggles is_active without clobbering the
user-set effective_to (unlike DELETE/deactivate).
- List returns the latest version of every insurance (active + inactive) via
TenantInsuranceRepository::findLatestByTenant, for the فعال/غیرفعال toggle.
Frontend:
- New InsuranceModal (ui/Modal + SearchableSelect + PersianDateInput) with the
seven fields; submit "ثبت بیمه".
- TenantInsuranceContracts rebuilt: header + search box, desktop table
(ردیف/نام/کد/نوع/وضعیت/عملیات) and mobile cards, status toggle -> PATCH.
- utils: isoToUnix/unixToIso helpers for contract dates.
Tests: TenantInsuranceContractApiTest (create/edit/toggle/list, 5 cases),
InsuranceModal + TenantInsuranceContracts vitest suites, docs/api updated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>