- Implemented SidebarStaff component tests to ensure staff users see only their dashboard and services.
- Created StaffMyServicesPage to display assigned services for staff users.
- Added migration to link clinic staff rows to user accounts for ROLE_STAFF access.
- Defined StaffPermissions class for static permissions related to staff role.
- Introduced StaffRouteGuardSubscriber to restrict API access for staff users.
- Developed StaffAccountService for managing staff user accounts and linking them to clinic staff.
- Added comprehensive tests for StaffAccountService to validate user creation, mobile number handling, and account attachment.
- Implemented tests for staff dashboard access to ensure proper permissions and access control.
- Created tests for staff login context to verify correct environment visibility based on user roles.
- Refactor multiple admin pages (BlogsPage, ClinicsPage, DoctorsPage, etc.) to utilize the new useUrlState hook for managing pagination, search, and filter states via URL.
- Ensure that the state persists in the URL, allowing users to return to the same state when navigating back from detail pages.
- Update relevant components to handle state changes appropriately and maintain clean URLs by removing default values.
- Add SlotPicker component for selecting appointment slots based on availability.
- Create tests for useUrlState to validate its functionality and ensure correct behavior when interacting with the URL.
- Update API documentation to reflect changes in appointment creation and slot selection processes.
- Added BackButton component to standardize back navigation across pages.
- Integrated BackButton into various pages, replacing custom back buttons for consistency.
- Updated PageHeader to accept backTo prop for displaying BackButton when navigating from subpages.
- Created useGoBack hook to handle navigation logic, determining whether to go back in history or redirect to a fallback page.
- Added tests for BackButton and its integration with PageHeader to ensure expected behavior.
- Updated CoverageRule and related entities to include franchise_percent instead of franchise_rials.
- Modified Appointment entity to carry supplementary insurance ID alongside base insurance.
- Implemented SessionBillingService to ensure finalized invoices for insured patient sessions.
- Created InvoiceFinalized event to trigger claims creation upon invoice finalization.
- Added BackfillMissingClaimsCommand to generate claims for finalized invoices without existing claims.
- Developed tests to validate the new functionality for supplementary insurance handling in appointments and claims.
- Changed franchise_rials to franchise_percent in tenant_insurances and tenant_service_coverage tables.
- Reset old rial values to 0/NULL as they are not convertible to percentage.
feat(command): add SeedInsuranceScenarioCommand for seeding insurance data
- Implemented a command to seed supplementary insurance contracts, patients, and claims for a specified doctor.
- Includes functionality for purging existing scenario data and generating new entries with predefined contracts and patient scenarios.
Phase 6 of the tenant series. GlobalTables::DEFERRED is now empty and the
coverage test asserts it stays that way.
payments carries the (entity_type, entity_id) pair and belongs to the
receiving side, never the payer: an appointment payment takes the
appointment's environment, a subscription takes the environment its buyer
owns, and an SMS wallet top-up takes the wallet's. The patient never chose
an environment, so TenantFilter stays off for them and they still see their
own payment.
Three corrections to the analysis the phase was planned on, each backed by
the code or the data rather than the plan:
- A third payment type exists. Payment::TYPE_SMS_WALLET is created in
SmsWalletController and already carries its environment in the metadata;
without assigning it the write would fail at flush.
- clinic_subscriptions has no user_id, and its trial rows carry no payment,
so it cannot drive the subscription backfill. The environment is derived
the way handleSubscriptionActivation derives it — and that method now
reads the pair off the payment instead of re-deriving it, so a payment and
the subscription it buys can no longer land on different environments.
- WalletTransaction is not a child of Payment. payment_id is nullable and
none of the four creation sites set it; the wallet is a person's, with a
running balance per user. It and Settlement, which withdraws from that same
wallet, are global with a recorded reason instead.
bank_accounts and pos_devices move from the registering user to the
environment. Their pair is deliberately nullable: nothing in the existing
data says which of a multi-environment owner's cards belongs where, and
guessing would point real money at the wrong account. Ambiguous rows stay
unassigned and the migration reports how many. The cost is that such a row
is invisible in every environment, so the owner reaches it through a
user-scoped lookup that runs outside the filter, and assigns it with
PATCH .../{uuid}/environment. The admin panel marks those rows and offers
the assignment.
Tests: 896 backend (+11), 570 frontend (+4). PHPStan unchanged at its 17
pre-existing errors.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Updated date handling in BlogSeoFields and ScheduleSection to use Tehran timezone utilities for consistency.
- Introduced `toTehranClockTime`, `tehranWallClockToUnix`, and `todayIso` functions for accurate date representation.
- Modified various components to utilize these new utilities, ensuring that date strings are correctly formatted and timestamps are accurately converted.
- Enhanced API documentation to clarify the handling of date fields, emphasizing the importance of server-local midnight.
- Added tests to verify that date overrides and holidays maintain the correct day without shifting due to timezone discrepancies.
- Implemented `adminDetail()` method in `BlogController` to retrieve blog posts of any status for admin editing.
- Introduced `BlogCacheInvalidator` service to handle cache invalidation after blog create/update/delete actions.
- Updated existing methods in `BlogController` and `RepresentationBlogController` to call cache invalidation on blog modifications.
- Enhanced `BlogFormPage` and `RepresentationBlogFormPage` to utilize the new admin endpoint for fetching blog data.
- Added tests for `BlogCacheInvalidator` to ensure proper functionality and error handling.
- Updated documentation to reflect new API endpoint and cache invalidation behavior.
- Refactored role metadata styles in UserDetailPage for consistency with design tokens.
- Replaced hardcoded avatar colors with a utility function to generate gradients based on user ID.
- Improved InfoCard component styles for better hover effects and accessibility.
- Removed deprecated color classes and adjusted background gradients for various components.
- Updated theme token tests to reflect the removal of deferred files and ensure compliance with design standards.
- Added new avatarColors utility file to manage avatar gradient definitions.
- Refactor color palette in `ui-design-spec.md` to utilize CSS variables exclusively, eliminating fixed hex values and Tailwind utility classes.
- Complete dark mode implementation in `uiStore.ts`, ensuring proper theme application via `applyTheme()` and `applyBrand()`.
- Create `admin-theme-dark-light-audit.md` to document the transition process, outlining issues with inline styles and fixed colors.
- Introduce `theme-tokens.test.ts` to enforce rules against fixed hex colors and ensure compliance with the design system.
- Update various components and styles to replace inline styles and fixed colors with CSS variables, ensuring consistent theming across light and dark modes.
- Ensure all changes maintain visual integrity in both light and dark modes, with a focus on accessibility and contrast standards.
- Implemented PATCH endpoints for changing the login mobile number of doctors and clinics.
- Added ChangeLoginMobileModal component for handling mobile number updates in the UI.
- Updated ClinicsPage and DoctorsPage to include buttons for changing mobile numbers.
- Enhanced AdminApiController to manage mobile number changes with validation.
- Created tests to ensure proper functionality and validation for mobile number changes.
- Updated API documentation to reflect new endpoints and their usage.
- Introduced `online_share_enabled` and `online_share_percent` fields in the `doctor_secretaries` table to manage secretary shares from online appointments.
- Added `bank_account` field in the `profiles` table to store user-level IBANs for settlements.
- Created `secretary_earnings` table to track earnings per secretary from online appointments, including a foreign key relationship with `financial_breakdowns`.
- Implemented `SecretaryEarning` entity and repository for managing secretary earnings.
- Developed `SecretaryShareResolver` service to determine which secretaries earn from online payments.
- Added `UserIbanResolver` service to handle user IBAN retrieval and management.
- Created `HasIbansTrait` for entities to manage IBANs in a JSON format.
- Implemented tests for secretary earnings and API endpoints for managing secretary shares and IBANs.
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>
- Added new fields to the Blog entity: sources, review_status, reviewer, reviewed_at, review_note, and topic_slug.
- Created API endpoints for reviewing blog posts: GET /api/v1/admin/blog/review-queue and POST /api/v1/admin/blog/{uuid}/review.
- Updated BlogController to handle review logic, including approval and rejection of posts.
- Introduced BlogReviewPage component for admin interface to manage blog reviews.
- Added migration to update the database schema for new fields.
- Implemented tests for review queue functionality and review decision handling.
The clinic-member-doctor permission system (ClinicDoctorPermission) lagged the
secretary system: only 6 resources, enforced in ~6 places, dead toggles
(services.update never checked), and a sidebar showing just appointments+patients.
Bring it to parity so a clinic owner can control exactly what each member doctor
does — while an independent doctor stays completely unrestricted.
Coverage: add insurances, addresses, inventory, tags, staff, discounts, sms to
ClinicDoctorPermission::DEFAULT_PERMISSIONS + DoctorPermissionsModal
(subscription/clinic_doctors stay owner-only by design).
New App\Clinic\Security\ClinicDoctorAccessChecker (parallel to
SecretaryAccessChecker):
- denyUnlessGranted(user, resource, action): 403 only for a clinic-member doctor
in the clinic context; owner/admin/secretary/independent-doctor pass through.
- memberClinicId(user): resolves the member doctor to the CLINIC's tenant so the
role-based controllers (Inventory/Tag/Staff/Discount/Sms) stop showing them
their personal tenant in clinic context.
Enforcement wired into 10 controllers alongside the existing secretary gates:
ClinicService (services), Insurance (insurances), Patient (patients+payments),
Staff, Discount, Inventory, Tag, SmsWallet, Payment, PaymentMethod.
Frontend: the guest-doctor sidebar branch now exposes every permitted resource
(gated by can()) plus a «تنظیمات» entry; both settings navs (PurchaseSubscription
Sidebar + SETTINGS_MENU) are now permission-filtered for a scope=clinic doctor,
not just secretaries; my-payments route gets the missing payments permission.
CRUD-button gating already applies (usePermissions is role-agnostic).
Tests: ClinicDoctorPermissionEnforcementTest (member denied/allowed +
independent-doctor-unrestricted); guest-doctor sidebar gating. Backend 375 pass,
frontend 503 pass. docs/api/clinic.md updated with the full resource set +
enforcement notes.
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>
Insurance pages redirected to the dashboard: the insurance-pricing/claims
routes never listed `secretary`, so RoleRoute bounced a secretary who had
insurances.view and saw the menu item. Added secretary + permission
['insurances','view'] to both routes; also gated my-financial with
['payments','view'] for consistency.
«خرید اشتراک» was owner-only with no permission toggle, so it could not be
granted. Added a `subscription` secretary resource (view/create) end-to-end:
- entity DEFAULT_PERMISSIONS + SecretaryPermissions type + both secretary forms.
- backend: SubscriptionController::my (view) and trial (create),
PaymentController::initiateSubscription (create). resolveEntity in
SubscriptionController was already secretary-aware.
- frontend: subscription + subscription/success routes accept secretary +
permission; settings navs gate «خرید اشتراک» by ['subscription','view'].
Tests: subscription denied-by-default / allowed-when-granted. docs/api
secretary.md updated (resource list, enforcement map, JSON example).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A clinic-scoped secretary opening «مدیریت نوبت دهی» hit 404s
(/api/v1/doctor/{clinicUuid}, available-locations, weekly-schedule): the
permission-only secretary filter ignored each item's `roles`, so BOTH
appointment-settings variants (doctor → /admin/appointment-settings,
clinic → /admin/settings/appointment-settings) showed. Clicking the doctor
variant landed on the personal page, which has no doctor uuid for a clinic
secretary and fell back to the clinic uuid — not a doctor → 404.
Make the secretary settings filter scope-aware in both navs
(PurchaseSubscriptionSidebar + menuForRole): a role-variant item is kept only
when its `roles` matches the secretary's context scope (clinic→'clinic',
else 'doctor'). The clinic page already threads clinic_uuid through
ScheduleSection, so once routed correctly the flow works end-to-end.
Test: appointment variant resolves to the clinic route under clinic scope.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three reported secretary-access bugs.
1) Settings menu structure. Phase B flat-listed staff/discounts/sms/tags/
appointment_settings/clinic_doctors in the secretary's main sidebar. Mirror
the doctor/clinic layout instead: only inventory + services stay in the main
«مدیریت» nav; the rest live under a single «تنظیمات» entry
(→ /admin/account-settings). Made both settings navs permission-aware for
secretaries: SETTINGS_MENU (menuForRole now takes `can`) and
PurchaseSubscriptionSidebar filter by a per-item `perm`/`alwaysOpen` instead
of role only, so a secretary sees exactly their permitted settings pages and
owner-only items (subscription, secretary-management) stay hidden.
2) Clinic secretary appointment timeline. AppointmentsPage treated a
clinic-scoped secretary as a single-doctor profile: the doctor list was
fetched/shown only for isClinic/isAdmin, so no doctor tabs, timeline, or
booking. Now a clinic-scoped secretary is multi-doctor: fetches the doctor
list, shows tabs, auto-selects the first doctor. The list comes from a new
authenticated endpoint GET /api/v1/my/clinic-doctors returning only the
secretary's ASSIGNED doctors — /clinic/doctor-list is on the public (no-JWT)
firewall and cannot scope by user, so it would have leaked unbookable doctors.
3) Patient record delete. The `patients.delete` toggle was dead: every record
delete (note/medical-record/attachment/call/message) was gated as
`patients.update`. Mapped them to `patients.delete` so the toggle is honored
and delete is controllable separately from edit.
New SecretaryAccessChecker::assignedClinicDoctorIds. Tests: doctor-list scoping,
patients.delete separation (denied/allowed). docs/api secretary.md +
appointment.md updated. Backend 286 + frontend 25 pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extends the secretary permission system to five previously owner-only modules,
so a clinic/doctor can delegate each page to a secretary. All were unreachable
by secretaries before (role-based tenant resolution returned "unknown" → 403).
New permission resources (default-deny, three-place add: entity default,
SecretaryPermissions type, both MySecretariesPage + admin SecretariesPage):
staff, discounts, sms, appointment_settings (view/update only), clinic_doctors
(clinic-only — hidden from independent doctors via `clinicOnly` section filter).
Backend enforcement (SecretaryAccessChecker, three new reusable helpers):
- resolveOwnerEntity(): owner pair from active context — used by StaffController,
DiscountController, SmsWalletController (now secretary-aware resolveEntity).
- canForDoctor(): per-doctor-scoped check (assigned doctor + toggle) — wired into
AppointmentSettingsController::denyDoctorAccess.
- canForClinic(): clinic-scoped check — wired into ClinicController::detachDoctor,
ClinicDoctorPermissionController (view/update), ClinicInvitationController
(create/view/update/delete). clinic_doctors is clinic-context only.
Guards run ahead of any subscription gate; non-secretary roles pass unchanged.
Frontend:
- RoleRoute: staff, discounts, sms-wallet, appointment-settings (doctor+clinic
variants), settings/clinic-doctors routes accept secretary + permission gate.
- Sidebar (secretary branch): five new items gated by can(); appointment_settings
route follows active scope; clinic_doctors only in clinic scope.
Tests: SecretaryResourceEnforcementTest — denied-by-default + allowed-when-granted
for all five (18 total). Sidebar.test — B-resource gating + clinic_doctors scope
rule. docs/api/secretary.md resource list, enforcement map, JSON example updated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Secretaries could reach neither the services module (EntityContextResolver
does not recognise a secretary as clinic owner, so they resolved to
`unknown` → 403) nor had any toggle to grant it. Add `services` as a
first-class secretary permission resource, enforced end-to-end.
Backend
- DoctorSecretary::DEFAULT_PERMISSIONS: new `services` resource (default-deny).
- SecretaryAccessChecker::resolveOwnerEntity(): reusable owner (clinic/doctor)
resolution from the secretary's active context, for controllers whose data
is fetched by [entityType, entityId] and whose generic resolver is not
secretary-aware.
- ClinicServiceController: resolveEntity() is now secretary-aware; every action
(sections, items, tariffs — 13 total) guards with `services` view/create/
update/delete via denyUnlessGranted, ahead of the subscription gate.
Frontend
- SecretaryPermissions type + MySecretariesPage + SecretariesPage: `services`
section so owners can grant it.
- Sidebar (secretary branch): services / inventory / tags menu items gated by
can(resource, 'view').
- RoleRoute: a secretary now needs the page's `permission` to open it (direct
URL entry included); clinic-services, inventory, tags-settings routes accept
secretary + permission gate.
Tests
- SecretaryResourceEnforcementTest: services denied-by-default, allowed-when-
granted, create-denied-while-view-granted.
- Sidebar.test: secretary menu gating for services/inventory/tags.
Docs: secretary.md + clinic-services.md updated with the `services` resource
and the resolveOwnerEntity note.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Added SecretaryAccessChecker to manage resource access for secretaries.
- Integrated permission checks for payments, inventory, and tags in relevant controllers.
- Updated PaymentController and PaymentMethodController to enforce secretary permissions.
- Enhanced TenantTagController to check permissions for tag management actions.
- Introduced tests for secretary resource enforcement, ensuring proper access control.
- Updated DoctorSecretary entity to include inventory and tags permissions.
- Created a comprehensive audit document for secretary permissions coverage and enforcement.
- Fixed potential crashes in SecretaryDashboard when rendering without doctor data.
- Introduced management mode for appointment slots, allowing doctors, admins, and clinic managers to view and book slots regardless of the online booking status.
- Updated SlotCalculatorService to accept a management context parameter, bypassing online booking restrictions.
- Modified appointment-related endpoints to handle management context and ensure proper authorization checks.
- Added tests to verify that management users can access slots even when online booking is disabled, while public users are still restricted.
- Improved documentation for API endpoints to reflect new management parameters and behaviors.
- 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.
- 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.
- Adjusted the calculation of payable amounts in PaymentStep to align with server logic, ensuring overpayments are handled correctly.
- Enhanced DetailsStep to include consumables in the itemized cost breakdown, ensuring consistency with patient share calculations.
- Updated tests for SessionPaymentPage to validate new behavior regarding overpayments and consumable listings.
- Modified PatientController to register SessionPayment correctly when settling sessions via wallet, preventing double charges.
- Refactored WalletService to remove outdated methods and ensure wallet transactions reflect the correct amounts after discounts.
- Improved accessibility in SearchableSelect component by adding aria labels and ensuring proper role attributes for screen readers.
- Updated styles to ensure minimum touch targets meet WCAG guidelines for mobile usability.
- Implemented a smooth curve rendering for the TauriLineChart using a cubic Bezier path.
- Added a forecasting feature to project trends based on recent data points.
- Updated TauriDashboardView to pass the current month for accurate forecasting.
- Refactored TauriLineChart to handle actual and forecasted data points distinctly.
- Introduced gradient strokes and glow effects to align with ApexCharts styling.
- Enhanced user interaction with hover markers and tooltips for forecasted data.
- Added a new test suite for ClinicDetailPage to ensure proper rendering and functionality.
- Added support for 'partial' payment status in MyPaymentsPage and related components.
- Updated API responses to include 'paid_rials' and 'summary' for invoices.
- Introduced InvoicePaymentStatus service to derive payment status based on actual payments.
- Enhanced tests to cover new payment scenarios including partial payments and payment methods.
- Updated documentation to reflect changes in payment status and API responses.
- Implemented a new API endpoint `/api/v1/my/billing/payments/summary` to provide a financial summary of payments with filters for national code, status, and date range.
- Updated the InvoiceRepository to aggregate totals for paid and unsettled invoices.
- Created a new hook `usePaymentsSummary` to fetch summary data in the frontend.
- Redesigned the MyPaymentsPage to align with the ClaimsPage structure, incorporating a design system, summary statistics, and improved filtering options.
- Added tests for the new payments summary endpoint to ensure correct functionality and filtering behavior.
- Introduced `tracking_number` field in the `claims` table to store the insurance tracking number.
- Created `claim_status_logs` table to maintain a history of status changes for claims, including who made the change and when.
- Implemented `ClaimStatusLog` entity and repository for managing status log entries.
- Updated `ClaimService` to log transitions and handle tracking numbers during claim submissions.
- Added new API endpoint for fetching claims by patient, including detailed claim history and status logs.
- Enhanced frontend with a new `ClaimPatientDetailPage` to display claims and their status history.
- Added tests to ensure correct aggregation of claims and proper handling of status transitions.
- Consolidated the calculation of patient and insurance shares into a single method using BillingCalculator.
- Introduced new fields in PatientSession to store breakdown of insurance shares and patient share.
- Updated the API responses to include the new fields for consistency across payment, invoice, and claims dashboard.
- Added migration to backfill existing sessions with appropriate values for the new fields.
- Implemented tests to ensure the correctness of the new logic and verify that the breakdown sums to the gross total.
- Redesigned the claims dashboard to provide a more user-friendly overview of patient claims and their statuses.
- Added support for Jalali calendar in the dashboard, allowing charts to display data based on the current Jalali month and year.
- Updated the API to return `patients_year`, `patients_month`, and `revenue_year` parameters for the dashboard charts.
- Refactored the dashboard controller to handle Jalali date calculations and queries.
- Modified the frontend components to utilize the new Jalali date parameters and reflect changes in the UI.
- Removed the status column from the NewAppointmentsTable as status management is now handled on the appointments page.
- Added tests to ensure the correct functioning of the new Jalali chart period features.
Three related fixes, all rooted in the same flaw: authorization and scoping
decided by the caller's role instead of by the environment the data belongs to.
1. Single-appointment access (clinic operations were entirely broken)
AppointmentController::canView/canManage only knew the patient, the owning
doctor and admin -- appointment.clinic was never consulted. A clinic user could
create an appointment through /my/appointment but got 403 on detail, edit,
move, reserve transfer/replace and status change, so nearly every appointment
operation failed in clinic mode.
AppointmentAccessChecker now decides from appointment.clinic: clinic owner,
member doctor (via ClinicDoctorPermissionChecker) and assigned secretary (via
active context + DoctorSecretary) are recognised. Actions reuse the existing
permission vocabulary, so active=false remains the single source of truth for
"collaboration ended". Cancellation is gated separately and an inline status on
PATCH /appointment/{uuid} cannot bypass that gate. The patient is narrowed to
view + cancel.
Also fixed alongside: listByDoctor now serves a clinic manager but scoped to
that clinic; todayStats gained an admin branch and no longer passes an array of
doctor ids as the clinic parameter; PatientController::appointments filters on
appointment.clinic instead of current membership, so deactivating a doctor no
longer erases clinic appointment history from the case file.
The doctor-only active_slot_key was reviewed and deliberately left alone -- a
doctor is one physical person, so adding clinic to the key would permit
double-booking, not fix a bug. Reasoning recorded on the entity.
2. Appointment registration and confirmation
Panel-created appointments are born pending ("ثبت شده") instead of confirmed.
Confirming is now an explicit act: POST /appointment/{uuid}/confirm transitions
the status, files the case file for the appointment's environment (reusing an
existing record or creating one) and registers full or partial payments on the
resulting visit -- all in one transaction.
AppointmentExpiryService would have expired those pending appointments the
moment their slot time passed; findExpiredPending is now limited to online
gateway holds, which are the only pendings carrying a TTL. A pending
appointment still occupies its slot, so the time stays reserved.
The admin panel gets a "قطعی کردن نوبت" modal showing the visit fee, each
selected service, the total, and paid/remaining/status. It is wired inside
AppointmentStatusDropdown, so picking "confirmed" anywhere (timeline, detail,
reserve list, info modal) goes through it and confirmation can never silently
skip the case file and payment.
3. Clinic case-file access
PatientRecordScopeResolver replaces the single-destination role mapping: the
active context decides, so a doctor invited into a clinic finally sees their
patients' records there. A clinic record is per-patient and shared by design,
so "their own patients" is derived from appointments with that doctor in that
clinic rather than from a new column. Clinic secretaries are limited to their
assigned doctors. Read and write share one rule, and out-of-scope records
report 404 so other environments are never disclosed.
Tests: 29 new cases across the three areas (clinic appointment access, confirm
flow, clinic record access). Full suite 466 tests, 2 pre-existing failures
unchanged. API docs updated for all three.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>