- AppointmentEditPage (edit.pdf): full-page edit of بخش/سرویس/پرسنل, Jalali
date + start/end time, deposit, status and notes; hydrates from
GET /appointment/{uuid} and saves through the general PATCH with the
optimistic-lock version. Routed at /admin/appointments/:uuid/edit (the
actions-menu ویرایش target).
- AppointmentFiltersModal (filter-desktop.pdf): name/national-code search,
بخش/سرویس selects, six status checkboxes (لغو شده covers both cancel
reasons), gender radios, حذف همه reset. Filtering is client-side over the
loaded day via the pure applyAppointmentFilters; toolbar gains the filter
button with an active indicator.
- /my/appointments rows now include patient_national_code and patient_gender
so the filters have data to match on.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Extend Appointment for the clinic-facing appointments area:
- New nullable relations service_section/service_item/staff (بخش/سرویس/پرسنل)
plus deposit_required/deposit_amount_rials (بیعانه) and is_reserve.
- New statuses following_up (در حال پیگیری) and salon (سالن) with day-of
transition rules; reserve entries never occupy a slot (several reserves may
share one day), enforced in refreshActiveSlotKey.
- rescheduleTo(slotStart, slotEnd, isReserve) keeps active_slot_key consistent
for جا به جایی and reserve transfers.
- New PATCH /api/v1/appointment/{uuid}: partial update covering edit, slot
move (409 on taken slot, race backstop on the unique key), reserve toggle,
patient swap (جایگزینی) and optional status transition; optimistic lock via
version like the status endpoint.
- POST /my/appointment now accepts the workflow fields and is_reserve
(day-level entry: no past-slot rule, no atomic slot booking); GET
/my/appointments gains reserve=1 and returns the new fields per row.
Migration Version20260713195434 (+ mirrored on db_test). Docs updated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Created migration to add representation_cities table and domain, is_global fields to representations.
- Implemented SiteContextController to resolve domain to site context (city | representation | unknown).
- Developed DomainContext and DomainContextResolver services for domain mapping.
- Added tests for DomainContextResolver and commission logic based on domain ownership.
~27 ad-hoc error codes (SLOT_TAKEN, USER_NOT_FOUND, VALIDATION, …) were raw
strings, so ErrorCodes::message() returned the "unknown" fallback for them.
Register all 14 distinct codes as constants with their messages and replace the
raw usages across AdminApiController, MyAppointmentsController, CategoryController,
PreRegistrationController and ClinicInvitationController.
Wire values are kept identical (verified no consumer — admin SPA, nobat724_front,
tauri — switches on these strings), so this is backward compatible.
Regression: tests/Shared/ErrorCodesTest (wire values preserved + message resolves).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- M2 GET /insurance/{id}: was unguarded; now owner-or-admin (403 otherwise) —
stops reading another doctor's negotiated price by id enumeration.
- M3 GET /clinic-pro/doctor-address/{id}: add the same owner/admin check the
sibling PATCH/DELETE already had.
- M4 POST/PATCH /service-item: staff_uuid must belong to the caller's tenant
(entity_type/entity_id) → 422; stops binding another tenant's staff.
- M5 appointment-settings list endpoints (date-override/holidays/
available-locations): add the per-doctor ownership check the sibling
single-record endpoints already enforce.
Regressions (6 negative cases fail without the fixes):
DoctorInsuranceOwnershipTest, DoctorAddressOwnershipTest,
ServiceItemStaffOwnershipTest, AppointmentSettingsListOwnershipTest.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A non-unique index on (doctor_id, slot_start) plus a count-then-insert check
left a TOCTOU race: two concurrent requests could both pass isSlotTaken and
both insert. wrapInTransaction alone doesn't stop the phantom under InnoDB
REPEATABLE-READ.
Add a nullable, unique active_slot_key on Appointment = "doctorId:slotStart"
while the booking occupies the slot (pending/confirmed — in lockstep with
isSlotTaken); NULL once expired/completed/no_show/cancelled (NULLs don't collide
in a MySQL unique index, so released slots rebook freely). bookAtomically now:
catches the unique violation -> SlotTakenException, and expires lapsed pendings
in-transaction so the ~1-min window before the expiry cron doesn't wrongly block
rebooking. All three booking paths (online / my / admin) routed through it.
Migration backfills one row per (doctor, slot) — the latest id — so the index
builds even on dirty historical data without destructively cancelling bookings.
(Backfill surfaced a real pre-existing double-booked slot in dev data.)
Regression: tests/Appointment/SlotUniquenessTest. Adjusted the expiry-service
test fixture to use distinct slots (one live booking per slot is now enforced).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
createAppointment only checked the caller held an allowed role, then booked
onto whatever doctor_uuid the request named — a doctor could book onto any
other doctor's calendar, a clinic onto doctors outside it, a secretary outside
their scope. Add canBookForDoctor(): doctor→own only, clinic→member doctors,
secretary→active scope + appointments.create permission, admin→any.
Regression: tests/Appointment/BookingScopeTest.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
getOverride leaked any doctor's override to any authenticated user; add the
owner-or-admin check (matching the update/delete endpoints) + regression test.
getSchedule returned any doctor's schedule to any authenticated user — the
mutation endpoints (update/delete) already checked owner-or-admin but this GET
did not. Add the same check + regression test (fails without the fix).
- Added domain guard in CommissionService to ensure commission is calculated only when the appointment is booked under the same representation as the doctor.
- Updated RepresentationController to filter statistics by representation, ensuring accurate data is shown for each representative.
- Introduced new endpoints for the representation dashboard to provide summary statistics, doctor performance, and financial reports.
- Created new pages for RepresentationFinance and RepresentationSettlement to display financial data and allow for settlement requests.
- Added migration to include booking_representation_id in appointments for tracking the representative under which the appointment was booked.
Appointments now persist address_id resolved from the weekly-schedule
session (location_id) across all booking paths (online, secretary, admin).
On confirm, the patient is added to the clinic owning that address, or to
the doctor's single clinic as fallback. Weekly-schedule create/update now
requires location_id on every active session. PatientSession exposes
doctor_uuid/doctor_name so clinic records show which doctor each visit is for.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
book() now accepts for_self plus patient_* fields: for_self fills the
patient from the paying user's profile, otherwise patient_name/mobile are
required (422 if missing) and the rest are stored. Every booking starts
pending with a 15-minute expires_at. Persisting goes through
bookAtomically (re-check inside a transaction) so two concurrent requests
for the same slot can't both win — the loser gets 409.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add GET /api/v1/appointment-settings/month-availability/{doctorUuid}
?year=&month= (Gregorian) returning disabled_dates / enabled_dates for
the month plus the doctor's online_booking flag and window. Lives in
AppointmentController (per-method guards) so it is genuinely public —
the class-level IsGranted on AppointmentSettingsController would have
forced auth. Whitelisted in security.yaml (firewall + access_control).
Uses SlotCalculator::hasAnyAvailability per day, so holidays, closed
overrides, non-working days and out-of-window dates all come back
disabled.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a reserved meta key inside WeeklySchedule.setting (no migration) holding
online_booking_enabled and a booking_window value+unit (week|month), with
sane defaults. Expose meta separately in toArray() and keep it out of the
day schedule map. setSetting now preserves meta when the day schedule is
replaced; the weekly-schedule POST/PATCH endpoints accept an optional meta.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Added `owner_type` and `clinic_id` fields to `DoctorSecretary` entity to distinguish between clinic and personal practice relationships.
- Updated repository methods to be scope-aware, allowing for specific queries based on the context of the secretary's relationship (clinic or doctor).
- Modified `SecretaryController` to handle secretary creation with appropriate scope based on the current user's role.
- Enhanced `AuthController` to build contexts that reflect the scope of the secretary's access.
- Updated `DashboardController` and `PatientController` to respect the new scope logic when retrieving data.
- Created migration to update the database schema accordingly, dropping the old unique constraint and adding the new fields and constraints.
- Added StaffController for managing clinic staff, including listing, creating, updating, and toggling staff status.
- Created ClinicStaff entity and repository for staff data handling.
- Developed SubscriptionController to manage subscription plans and periods, including trial subscriptions.
- Introduced SubscriptionPlan, SubscriptionPeriod, and ClinicSubscription entities for subscription management.
- Implemented SubscriptionService for handling subscription logic, including trial activation and subscription creation from payments.
- Added necessary repositories for subscription entities to facilitate data access and manipulation.
- Added `clinic_id` and `type` fields to `DoctorAddress` entity to differentiate between personal and clinic addresses.
- Updated constructor to support creation of addresses for both doctors and clinics.
- Modified repository methods to handle new address types and added methods for counting and finding addresses by clinic.
- Implemented migration to update the database schema accordingly.
- Removed deprecated endpoint for creating addresses from clinics and updated related controller methods.
- Added new endpoints for managing clinic addresses, including CRUD operations.
- Updated frontend components to handle new address types and display accordingly.
- Fixed calendar crash due to invalid array length in PersianCalendar.tsx by changing locale to 'en-u-ca-persian'.
- Added Persian weekday display in DateNavigator with appropriate styling and logic.
- Updated empty slot message to indicate when a day is off.
- Made patient name a required field in appointment creation and implemented find-or-create logic for patients in both admin and user endpoints.
- Corrected mobile number display to show the patient's number instead of the doctor's in appointment listings.
- Ensured booked appointments are displayed correctly in the schedule view.
- Removed unnecessary operations column from the appointments table view.
- Implemented InviteDoctorModal component for inviting doctors to clinics.
- Updated ClinicDashboard to include a button for inviting doctors and handle modal state.
- Added createAppointment API endpoint in AdminApiController for scheduling appointments.
- Enhanced ClinicInvitationController to check user access when inviting doctors.
- Updated MyAppointmentsController to ensure unique appointment records.
- Added seed_test_data.php for populating test data including doctors, clinics, and appointments.
- Refactored styles to include new appointment status badges and updated font imports.
- Implemented GET /api/v1/dashboard/clinic to return clinic stats and today's schedule for clinic owners.
- Implemented GET /api/v1/dashboard/doctor to return doctor's stats and today's schedule for doctors.
- Implemented GET /api/v1/dashboard/secretary to return stats and conditional appointments for secretaries.
feat(migrations): create user_active_context and mobile_verification_otp tables
- Added migration to create user_active_context table for tracking active user sessions.
- Added migration to create mobile_verification_otp table for handling mobile number verification.
feat(migrations): create site_config table for application settings
- Added migration to create site_config table to store various site configuration settings.
feat(appointments): create MyAppointmentsController for user-specific appointments
- Added MyAppointmentsController to handle fetching user-specific appointments with pagination and filtering.
feat(auth): implement NotificationMobileController for mobile number verification
- Added NotificationMobileController to handle OTP requests and verification for mobile number changes.
feat(auth): create MobileVerificationOtp entity for OTP management
- Created MobileVerificationOtp entity to manage OTP records for mobile verification.
feat(auth): create UserActiveContext entity for user session management
- Created UserActiveContext entity to manage user active sessions.
feat(config): implement SiteConfigController for managing site settings
- Added SiteConfigController to handle fetching and updating site configuration settings.
feat(config): create SiteConfig entity and repository for configuration management
- Created SiteConfig entity and repository to manage site configuration data.
- Added a new endpoint to list holidays for a specific doctor.
- Added a new endpoint to delete a holiday, with access control for doctors and admins.
- Implemented a repository method to retrieve all holidays for a doctor.
- Added jalaali-js library to handle date conversions.
- Introduced CLAUDE.md for internal guidance on project structure, commands, and architecture.
- Created README.md with detailed project overview, technology stack, directory structure, setup instructions, API endpoints, authentication flow, and external services.
- Add SendSmsMessage class for encapsulating SMS message data.
- Create KavehNegarProvider and RanginehProvider classes implementing SmsProviderInterface for sending SMS.
- Implement SmsLogRepository and SmsTemplateRepository for managing SMS logs and templates.
- Develop SendSmsHandler for handling SMS sending messages.
- Create SmsService to manage SMS dispatching and logging.
- Add UserProfileController for managing user profiles with CRUD operations.
- Implement UserProfile entity and repository for user profile data management.
- Update symfony.lock and bootstrap.php for project dependencies and environment setup.