- Created new AST JSON file for the doctor service API documentation, detailing endpoints, parameters, and responses.
- Added AST JSON file for the TagController, including methods and their relationships with imported classes.
- Introduced AST JSON file for the SmsLog entity, outlining its methods and dependencies.
- Created migration to set up app_log table for storing application logs.
- Added AppLog entity and repository for ORM handling of logs.
- Developed DbLogger service to persist logs of level WARNING and above to the database while maintaining existing logging behavior.
- Implemented tests for admin log retrieval and DbLogger functionality to ensure proper logging behavior.
- Enhanced logging context sanitization for better error tracking.
- Implement tests for Pagination, StatusBadge, ConfirmDialog, and MobileInput components.
- Add tests for useSubscription, usePaymentConfig, and usePwaInstall hooks.
- Create tests for API requests in the api module, including success and error handling.
- Add utility function tests for formatting and validating Iranian mobile numbers.
- Implement tests for BlogFormPage and BlogsPage to validate form submissions and data fetching.
- Add tests for LoginPage to ensure proper validation and state management.
- Create tests for authStore and uiStore to validate state management and functionality.
- Set up Vitest configuration and testing utilities for consistent testing environment.
GET /billing/claims and GET /settlement now return only the first 50 rows by
default (data.data unchanged, data.meta added). ClaimsPage and
RepresentationSettlementPage read the full array with no pager, so rows beyond 50
were unreachable. Add page state + ?page/limit + the existing <Pagination>
(reading data.meta.totalRecords).
No change needed for: 422 on claim approve/pay (api.ts already surfaces the
backend message via toast; the admin UI sends no amount so it's unreachable),
the owner-only appointment-settings endpoints (admin user bypasses), and refresh
rotation (authStore.refresh already persists the rotated refresh_token).
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>
- Added Dockerfile for multi-stage build including PHP, Node.js, and Nginx.
- Created docker-compose.coolify.yaml for service orchestration with app, workers, MariaDB, and Redis.
- Introduced entrypoint.sh for initialization tasks like JWT key generation and database migrations.
- Configured Nginx with default.conf for handling requests and routing to PHP-FPM.
- Added php.ini with production settings and opcache configuration.
- Set up supervisord.conf to manage PHP-FPM and Nginx processes.
- Created frontend-domains.json for managing allowed frontend domains.
- Added gen-cors-env.php script to generate CORS environment variables from frontend domains.
- Updated framework.yaml to configure trusted proxies and headers.
- Created .dockerignore to exclude unnecessary files from the Docker context.
- Added .env.coolify.example for environment variable configuration.
- Documented deployment steps and troubleshooting in coolify.md.
- Added a new column `national_code_verified` to the `users` table.
- Normalized the `bank_account` field in the `representations` table from a single object to an array of IBANs with a default `verified` status of false.
feat(ApiIrService): implement identity verification client for api.ir
- Created `ApiIrService` to handle identity verification via api.ir.
- Implemented methods for matching national code with mobile and IBAN with national code and birth date.
- Added error handling and logging for external API requests.
- 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.
- Added new configuration keys for appointment and upgrade commissions, tax settings, and SMS panel fee in SiteConfigController and SiteConfigRepository.
- Introduced CommissionService to handle commission calculations for appointments and subscriptions, including tax deductions and SMS fees.
- Created FinancialBreakdown entity and repository to log financial transactions.
- Updated PaymentController to process commissions upon successful payments for appointments and subscriptions.
- Developed FinancialReportPage in the admin panel to display financial breakdowns and summaries.
- Added database migration for the new financial_breakdowns table.
- 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.
Wrap InsurancePricingPage and ClaimsPage in <FeatureGate feature="insurance">
so direct-URL access is blocked without an active subscription that enables
the feature. Add feature: "insurance" to the sidebar links (doctor/clinic/
secretary sections) so the menu items hide when the plan lacks it.
Make AdminSubscriptionPage feature controls dynamic: derive the plan feature
checkboxes from FEATURE_LABELS (now including "insurance") and switch the Zod
schema to z.record, so adding a feature only touches the label map.
No backend/migration change: plan.features is free-form JSON; existing plans
default to insurance:false and admins enable it per plan.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add accent (orange) + pastel stat-card tokens (light/dark), button
accent/outline/lg/block variants, and a pastel StatCard primitive,
aligning the admin design system with the clinic-pro-tauri reference.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GET /api/v1/patient/{uuid} now returns a `profile` object from UserProfile
(demographics, contact, insurance names resolved). Admin record detail shows
a "patient info" section. Empty fields render as "ثبت نشده".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
Extend POST /api/v1/patient to resolve user by uuid or mobile, and
create a new ROLE_USER (name + optional national code, no password)
when no user exists. Admin modal shows a new-patient form on lookup miss.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Introduced a `tag` field in the `SmsLog` entity to categorize SMS messages.
- Updated the `SmsService` to handle the new `tag` parameter during SMS dispatch.
- Implemented a `SmsTextResolver` service to resolve SMS message templates based on tags.
- Created a new `SmsMessageTemplate` entity for editable SMS templates with placeholders.
- Added endpoints for managing SMS message templates in the admin panel.
- Enhanced existing SMS dispatching methods across various controllers to utilize the tagging system.
- Migrated the database to include the new `tag` field and created a seeding command for default SMS templates.
- Updated admin API to filter SMS logs by tag and include tag information in responses.
- 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.
Add GET /api/v1/specialties/doctor-counts?city_id= returning every active
specialty with number_of_doctors (distinct doctors via doctor_specialties,
scoped by doctor_cities when city_id is given). Make /api/v1/specialties GET
public. Powers the /specialties page count. Docs updated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The appointment detail view needs the doctor's specialty and the clinic
address/phone/map, which toArray didn't return. Add doctor.specialties[] and
a top-level `address` (the doctor's first address via DoctorAddress::toArray —
address, telephone, map). Additive only; existing keys unchanged. Docs updated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Birthday was never saved (hydrate forced null) and not returned in a form the
clients use. Now:
- hydrate stores the incoming `birthday` (Unix) into date_of_birth; `birthday`
takes priority over a stray `date_of_birth: null` in the same payload so it
can't be wiped.
- toArray exposes a single `birthday` (Unix) key — drop the duplicate
`date_of_birth` output to avoid overlap.
- admin UserDetailPage reads profile.birthday (formatDate renders Jalali).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When a user completes their profile (POST/PATCH /api/v1/user-profile), the
name was only stored on the profile (label/family) and User.real_name stayed
empty — so users who registered via booking showed with no name in the admin
user list. hydrate() now mirrors the profile's full name (label + family)
onto the owning User.realName, persisted in the same flush.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Unify and harden the payment flow (same API for the main site and all
consumer sites; per-client difference is only frontend_address).
- Payment gains STATUS_CANCELED. Gateways distinguish user-cancel from
failure (Mellat ResCode=17, SEP CanceledByUser, mock cancel=1) via a new
PaymentVerifyResult::canceled flag; callback sets canceled vs failed and
skips the circuit-breaker on cancel.
- Expiry job now cancels the pending payment when a booking lapses
(AppointmentExpiryService + PaymentRepository::findPendingByAppointment).
- frontend_address allowlist is read from the payment_allowed_frontend_hosts
site setting (manageable via PATCH /api/v1/admin/settings), falling back to
the ALLOWED_FRONTEND_HOSTS env var — so a new consumer site needs no code
change.
- .env: broaden CORS_ALLOW_ORIGIN to city subdomains (*.localhost /
*.clinic-pro.ddev.site) and add yazd-nobat.localhost to ALLOWED_FRONTEND_HOSTS.
- Update docs/api/payment.md and docs/api/admin.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rebuild the doctor rating/review system to power the public site's rich
review UI, and restrict who may submit.
Ratings:
- Rate entity holds five 0–100 dimensions (waiting time, diagnosis
accuracy, behaviour, cleanliness, expertise) instead of a single score.
- GET /rate/{uuid} returns aggregate {point, satisfaction, averages[]}.
- POST /rate upserts all five dimensions and returns the new aggregate.
Comments:
- Comment gains parent/replies (threaded) and a rich toArray with author,
like_status (like/dislike counts + current user's vote) and nested
approved replies. POST /comment accepts {comment, parent}.
- Likes are directional (value 1=like, -1=dislike) with toggle/replace;
POST /like/{uuid} returns like_count/dislike_count/current_user_like.
Eligibility:
- Only a user with a confirmed appointment in the last 30 days may rate or
comment (AppointmentRepository::hasRecentConfirmed); otherwise
403 ERR_RATING_NOT_ELIGIBLE. New GET /rate/{uuid}/eligibility for the UI.
- security.yaml: narrow the public rate pattern so /eligibility stays auth'd.
Also updates admin rates listing to the new dimensions and the rating/admin
API docs. Includes migration for the new columns.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>