Services are referenced by appointments, sessions, invoices and payment history,
so deleting one orphans/corrupts those records (deleting a section cascaded to
its services too). Make deletion impossible:
- Backend: DELETE /service-item/{uuid} and DELETE /service-section/{uuid} now
always return 409 (ERR_SERVICE_ITEM_IN_USE) with a message pointing to
deactivate; no rows are touched. Deactivate stays via PATCH active=false.
- Frontend: removed the section delete button, its confirm dialog, the delete
mutation, and the now-unused delete state/flag/icon from ClinicServicesPage.
Section and item deactivate toggles are unchanged.
Tests: ServiceItemDeleteCleanupTest rewritten — delete of item and section both
rejected (409) and the row survives. docs/api/clinic-services.md updated.
Co-Authored-By: Claude Opus 4.8 (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>
- Implement PriceInput component tests to validate Persian and Arabic numeral handling, input formatting, and controlled behavior.
- Create ServiceDetailPage component with detailed service information, including pricing, insurance coverage, and editing capabilities.
- Add API tests for service item detail retrieval and coverage synchronization with insurance contracts.
- Ensure proper error handling and user feedback for service item retrieval and coverage management.
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>
- Introduced a new booking mode in WeeklySchedule to support service-based appointments.
- Updated SlotCalculatorService to calculate available start times based on selected service durations and buffer times.
- Enhanced AppointmentController to handle service items during booking, calculating slot_end on the server side.
- Implemented validation to ensure at least one bookable service exists for doctors in service mode.
- Added new API endpoint to retrieve available appointment slots based on selected services.
- Updated MyAppointmentsController to accept service items during appointment creation.
- Modified ServiceItem entity to include a bookable flag, allowing services to be marked for scheduling.
- Created migration to add bookable column to service_items table.
- Added tests for service-based slot calculations and validation logic.
- revert the previous merge of insurance coverage into the service edit
modal (per user): 'پوشش بیمه' is again a ⋮ menu action opening its own
ServiceInsuranceModal, kept within the services page
- delete the inline ServiceInsuranceSection; restore ServiceInsuranceModal
- fix ⋮ dropdown position: left:8 → insetInlineStart:8 so the menu anchors
under the ⋮ button (which sits at inline-start/right in RTL) instead of
the opposite side
- keep the --primary-subtle → --primary-soft token fix in the modal
- test: assert the ⋮ menu exposes ویرایش / تعرفههای سالانه / پوشش بیمه
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- extract ContractCard + contracts list from ServiceInsuranceModal into a
new inline ServiceInsuranceSection (no dialog wrapper; buttons type=button
so they don't submit the parent service form)
- ClinicServicesPage: drop the separate 'پوشش بیمه' menu action and its
standalone modal; render coverage inline under the service fields in the
edit modal (edit only — needs the item uuid; create shows a hint)
- delete now-unused ServiceInsuranceModal.tsx
- fix undefined --primary-subtle token → --primary-soft
- test: coverage renders inline inside the service edit modal
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Render the clinic services page (sections → services) inside the settings
sub-navigation shell (SettingsLayout, "خدمات" active) to match the Figma
settings design. Restyle section cards to show the service count and a
status toggle with edit/delete actions, and service cards with labelled
price/duration and personnel chips.
A service can now have multiple personnel: add an additive many-to-many
ServiceItem↔ClinicStaff (staffMembers, EAGER) while keeping the legacy
single `staff` column mirrored for backward compatibility. Endpoints accept
`staff_uuids[]` (falling back to the legacy single `staff_uuid`) and return
`staff_members[]`; the section list now reports `items_count`.
Backfill-safe: pre-migration rows fall back to the single staff in toArray.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Restructure clinic-services from two-pane to Figma two-view flow:
sections card grid (name, edit, activate/deactivate, 'بخش جدید' modal) →
click a section → its services card grid with breadcrumb back. Service
card matches Figma: overflow menu, active badge, base price, average
time badge, staff chip. Add duration_minutes to ServiceItem (migration
+ create/update + form field). Update clinic-services.md.
(Version20260713050014 syncs pre-existing entity/schema drift.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replicate the Figma patient-service card chrome for catalog items:
overflow menu top-start, status square (active/inactive), name + section
subtitle, label:value rows, price, full-width primary action. Adapts the
frame's fields to catalog data (staff, insurance, price).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Convert clinic service items from rows to a Figma-style card grid using
cp-card/tokens; drop hardcoded oklch tint. Functionality unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Updated NewSessionPage to calculate patient share based on insurance coverage rules.
- Refactored billing calculations to utilize new patientShareOf function for service items.
- Enhanced API documentation to reflect changes in service coverage structure.
- Implemented ServiceInsuranceModal for managing insurance coverage per service.
- Added UI components for displaying and editing insurance coverage details.
- Removed obsolete toggle switch styles and adjusted CSS for new components.
- Ensured backend endpoints support both service_item_id and service_item_uuid for flexibility.
- Fix national code handling in staff creation and updates to support Persian digits.
- Update ClinicStaff entity to allow longer national codes (up to 15 characters).
- Implement support for clinic secretaries in SecretaryController, allowing creation without a doctor UUID.
- Add a new endpoint to retrieve doctors associated with a clinic for secretary management.
- Improve appointment management by ensuring doctors are selectable even when no appointments exist.
- Extend PatientController to allow secretaries to create patient records if they have the appropriate permissions.
- Introduce a PriceInput component for better price formatting in forms, supporting Persian digits.
- Add a MockGateway for testing payment processes without real transactions.
- Enhance SMS settings management with an approval flow for post-visit text messages, including new fields for pending text and status.
- Update migrations to reflect changes in database schema for national codes and SMS settings.