buildAvailableContexts used ClinicRepository::findByUser(), which is a
findOneBy — so a user who owns two clinics only ever saw the first one.
switchContext validates its input against that same list, so the second
clinic could not be selected at all.
Before tenant isolation this was merely annoying. Since phase 4 it is a
blocker: an environment that cannot be selected is an environment
TenantFilter hides from its own owner. Found by running the suite against
an imported production database, where one account owns two clinics and
its second clinic had become unreachable.
findByUser() stays for the fallbacks that only need "some clinic"; the
context list now uses findAllByUser(). The other 20 findByUser() call sites
are single-clinic fallbacks used when no context is chosen, and keep their
current behaviour — once the owner can switch, UserActiveContext decides.
Removing the fix turns 3 of the 4 new tests red.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Phase 3 of the tenant-marking series. The same concept was written four ways,
and the Doctrine filter arriving in phase 4 keys on the field name — so the
tables using a different spelling would have been skipped silently, which is
exactly the leak this work exists to prevent.
- discount_rules: owner_type/owner_id renamed to entity_type/entity_id. Pure
rename, no data moves.
- doctor_secretaries: owner_type plus a nullable clinic_id replaced by the
shared pair. The environment now comes from the clinic argument alone, so the
inconsistent combination (owner_type='clinic', clinic_id=NULL) can no longer
be constructed, and the redundant constructor parameter is gone.
- user_active_context: added db_type, so resolving an environment is one lookup
instead of "try clinics, then try doctors". Filled from the type already
present in available_contexts.
- entity_type is VARCHAR(10) in all twenty tenant tables; four of them were 20.
Behaviour change, the only one in this series: the doctor_secretaries unique key
went from (doctor_id, secretary_id, owner_type) to (doctor_id, secretary_id,
entity_type, entity_id). With clinic_id outside the key, one secretary could not
be assigned to the same doctor in two clinics — the second row collided on
owner_type='clinic'. The duplicate check in SecretaryController had the same
blind spot and would have rejected the request before the database saw it; both
are fixed together.
Correcting an assumption from the phase-3 plan: mobile_verification_otp.entity_type
really is a tenant pair. NotificationMobileController validates the target against
['doctor','clinic'] and stores that entity's id, so the column was normalised with
the rest rather than treated as unrelated.
TenantOwnedTrait gained assignTenantPair() for callers that resolved the pair as
scalars and hold no entity — building an EntityContext from scalars would produce
one where isClinic() is true but ->clinic is null, breaking consumers silently.
tests/ApiTestCase::createUser now retries on a duplicate mobile. db_test is never
reset and already holds ~38k users, so the 9-digit random draw collided often
enough to fail unrelated tests a few percent of runs.
Tests: 830 passing. PHPStan reports no new errors on the changed files.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Implemented a helper function `displayDoctorName` to prepend "دکتر" to doctor names for consistent display across the application.
- Updated various components (InviteDoctorModal, DashboardPage, DoctorDetailPage, DoctorsPage, etc.) to utilize the new helper for rendering doctor names.
- Modified the DoctorFormPage to automatically add the "دکتر" title in the UI without requiring user input.
- Fixed the EditSpecialtyPicker component to allow multiple specialty selections, resolving a UI bug where only one specialty could be selected at a time.
- Ensured that the backend strips the "دکتر" title from the name during pre-registration and doctor creation processes.
- Added tests for the new functionality, including checks for title handling and specialty selection logic.
- Updated API documentation to reflect changes in name handling and display logic.
- Removed the "دکتر" prefix from doctor names in various components and API responses to ensure consistency and clarity.
- Updated the AppointmentDetailPage, CommentsPage, DashboardPage, RatingsPage, SecretariesPage, and other relevant files to reflect the changes in doctor name formatting.
- Adjusted API documentation to align with the new naming conventions.
- Implemented validation to prevent the creation of clinics without a name and restricted users to a single clinic.
- Added tests to verify that doctor names are stored without titles and that clinic creation adheres to the new validation rules.
- Implement DoctorPermissionsModal for managing doctor permissions in clinics.
- Create usePermissions hook to handle user permissions context.
- Add migration for clinic_doctor_permissions table with default permissions.
- Develop ClinicDoctorPermissionController for handling permissions API.
- Create ClinicDoctorPermission entity to manage permissions data.
- Implement ClinicDoctorPermissionRepository for database interactions.
- Add ClinicDoctorPermissionChecker for permission validation logic.
- Write tests for clinic doctor permissions functionality.
Fill the three previously-placeholder settings sections so every menu item
is now a real page inside the settings shell:
- حساب کاربری: new authenticated POST /api/v1/user/change-password
(verifies current password, ≥8 chars, must differ) + account page with a
profile summary and change-password form.
- برچسبها: new per-tenant TenantTag domain (entity/repo/controller +
migration) with tenant-scoped CRUD at /api/v1/tenant-tag(s), plus a tags
management page (list + color + add/edit/delete).
- مدیریت نوبت دهی: export the existing WeeklyScheduleTab from
DoctorDetailPage and reuse it in a standalone AppointmentSettingsPage
(current doctor's uuid + addresses).
Wire all three menu entries to their routes. Backend covered by PHPUnit
(change-password, tenant-tag CRUD + ownership); FE covered by Vitest.
API docs updated (auth.md, tag.md).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Added AltchaService class for managing ALTCHA captcha challenges and solutions.
- Created CaptchaController to handle API requests for generating challenges.
- Introduced CaptchaGuard for validating captcha solutions on public endpoints.
- Developed unit tests for AltchaService to ensure challenge creation and solution verification functionality.
- Implemented integration tests for the Captcha API endpoint and captcha bypass behavior when disabled.
- Added documentation for the Captcha API in the corresponding markdown file.
M6: send-code rate-limited only per IP, so a victim's number could be
SMS-flooded from rotating IPs. Add a per-mobile bucket (same 5/hour policy)
keyed by the validated mobile.
M7: /oauth/token/refresh reused the presented refresh token verbatim (no
rotation) and never re-checked the user. The rotation infra already existed
(issueTokens mints a fresh refresh token) — the controller just discarded it.
Now revoke the presented token (single-use), issue a fresh pair, and reject a
suspended user (status != 1).
Regressions: tests/Auth/SendCodeMobileRateLimitTest,
tests/Auth/RefreshTokenRotationTest (both fail without the fix).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Updated authStore to include 'representation' role.
- Modified DoctorFormPage and DoctorsPage to handle different endpoints based on user role.
- Created new RepresentationActionController for handling doctor and clinic creation by representatives.
- Added new API endpoints for representatives to manage doctors, clinics, and view appointments.
- Updated documentation to reflect new role and API changes.
- Implemented ClinicFormPage for adding new clinics with validation.
- Created MyFinancialPage to display financial summaries and charts.
- Developed MyPatientsPage for managing patient data with search and pagination.
- Added PreRegistrationsPage for handling pre-registration requests with approval and rejection functionalities.
- Introduced database migration for pre_registrations table.
- Built PreRegistrationController for managing pre-registration logic, including submission, approval, and rejection.
- Created PreRegistration entity and repository for handling pre-registration data.
- 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.