Commit Graph
100 Commits
Author SHA1 Message Date
hamedandClaude Opus 4.8 3a23aa242e fix(clinic-invitation): provision doctor accounts and repair panel actions
The invitation flow never created an account for the invitee. accept() only
looked up an existing doctor by mobile, so for a brand-new invitee it marked
the invitation accepted and burned the token while leaving doctor_id NULL —
no login, no clinic link, and every doctor-facing endpoint 404ing afterwards.

- invite/accept now provision the users + doctors pair, claim the profile on
  accept, link it to the clinic, and SMS generated credentials when the user
  has no password. Existing passwords are never overwritten.
- accept runs in one transaction so an invitation can no longer be marked
  accepted without its doctor profile and clinic link.
- changeStatus accepts `pending`, refreshing the token and re-sending the SMS
  so reactivating a suspended invitation yields a link that actually works.
  Answered invitations are rejected with 409.
- DELETE returns 200 with the standard envelope instead of a bodyless 204,
  which made the admin panel show a false error toast; api.ts also stops
  calling res.json() on empty responses.
- The clinic-doctors settings page sent the active context uuid as the clinic
  uuid, so users holding both a doctor and a clinic context got 404 on every
  invitation action. It now always resolves the clinic context.
- Adds app:invitations:repair to fix invitations already left orphaned.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 09:14:13 +03:30
hamed 1779e0d6de feat(secretary): implement multi-doctor assignment for clinic secretaries
- Added functionality to assign a single secretary to multiple doctors within a clinic, allowing for scoped access to appointments.
- Introduced `SecretaryService` to handle the logic for assigning and syncing doctors for a secretary.
- Updated `SecretaryController` to support multi-doctor assignment via new endpoints and modified existing ones.
- Enhanced `DoctorSecretary` entity to include secretary UUID in its serialized output.
- Implemented repository methods to facilitate the retrieval and management of doctor-secretary relationships.
- Adjusted appointment filtering in `MyAppointmentsController` to ensure secretaries only see appointments for assigned doctors.
- Created tests to validate the new multi-doctor assignment functionality and appointment access restrictions.
- Updated frontend components to support multi-select for doctors in the secretary management UI.
2026-07-18 08:49:04 +03:30
hamed 6ab7ed38b8 feat: refactor clinic management into a dedicated settings tab
- Removed MyClinicPage and redirected its functionality to a new ClinicDoctorsPage.
- Created ClinicDoctorsManager component for managing doctors and invitations within the settings layout.
- Updated backend permissions to allow clinic owners to detach doctors, alongside admins.
- Adjusted API documentation to reflect new permission structure.
- Updated tests to cover new functionality and permissions.
- Modified sidebar and settings menu to reflect the new structure and role-based visibility.
2026-07-17 21:56:27 +03:30
hamedandClaude Fable 5 385b81fae0 fix(patient): consistent debt/paid status, invoice totals, global service search
- patient_debt_rials now = session remaining (final - discount - paid), so the
  card status and invoice status stay consistent with is_paid even after a
  session is edited post-invoicing (no more '0 remaining but تکمیل پرداخت').
- InvoiceSummaryModal: remaining subtracts discount; the فاکتور status reflects
  actual settlement (تسویه شده / بدهکار), and the payments table shows a
  «مجموع پرداختی‌ها» total row.
- Add GET /api/v1/service-items (all owner services) and make the service
  picker searchable across all services without first choosing a section.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 15:53:28 +03:30
hamedandClaude Fable 5 e9108e7590 feat(admin): show remaining amount in the payment-methods section
Add «مبلغ باقی‌مانده» next to the wallet balance under «انتخاب روش های
پرداخت» on the session pay step, so the operator sees what's still owed while
choosing a payment method.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 15:39:58 +03:30
hamedandClaude Fable 5 2ba841e8fe feat(admin): payment management tab in the session edit page
EditSessionPage now has سرویس‌ها/پرداخت‌ها tabs; the payments tab renders
PaymentStep so payments can be added, edited, and deleted (with audit) while
editing a session, not only from the pay flow.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 15:36:08 +03:30
hamedandClaude Fable 5 142d653c68 fix(patient): derive is_paid from remaining, recompute on edit, audit add-payment
- is_paid now = remaining_rials == 0 (was payment_method != pending), so adding
  a service/package after settlement correctly flips the session back to debtor.
- updateSessionServices recomputes the cached settlement (payment_method/paid_at)
  after totals change.
- addSessionPayment now records a 'create' audit entry (actor, amount, time).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 15:34:41 +03:30
hamedandClaude Fable 5 f2ea2ff262 feat(admin): session edit form, payment edit/delete UI, and audit history modal
- SessionServiceCard menu gains «ویرایش» and «تاریخچه تغییرات» items.
- SessionAuditModal shows the session's change history (field, op badge,
  old->new, actor, time) from /session/{uuid}/audit-log.
- PaymentStep payment rows get edit (modal) + delete (confirm) controls for
  non-wallet payments, calling the new PATCH/DELETE payment endpoints.
- CreateStep accepts an editSession prop (prefill + PATCH); EditSessionPage
  reuses it at /patients/:recordUuid/session/:sessionUuid/edit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 15:23:02 +03:30
hamed 48579dc06f feat(session): implement auto-fill for PatientSession on appointment confirmation 2026-07-17 15:15:14 +03:30
hamedandClaude Fable 5 7da7d968b9 feat(patient): session edit + payment PATCH/DELETE + audit-log endpoints
updateSession now accepts services/consumables/visit_price/insurance and calls
updateSessionServices; discount paths pass the actor for audit. Add
PATCH/DELETE /session/{uuid}/payments/{paymentUuid} and GET
/session/{uuid}/audit-log (owner-scoped). Inject the payment + audit repos.
Verified end-to-end (edit visit price, payment edit-exceeds guard, delete +
recompute, audit trail). Docs updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 15:14:49 +03:30
hamedandClaude Fable 5 cd1c9529cb feat(patient): session edit + payment edit/delete service logic with audit
Add updateSessionServices (replace services/consumables/visit-price/insurance,
recompute totals), updatePayment and deletePayment (wallet payments blocked;
recompute cached payment_method/paid_at via recomputeSettlement), and thread
audit logging (logSessionChange) through service edits and the discount path.
Add remove()/findByUuid to payment repo, remove() to service/consumable repos,
and method/amount/paid_at setters to SessionPayment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 15:11:07 +03:30
hamedandClaude Fable 5 d8c8ba0df7 feat(patient): add SessionAuditLog entity for financial/service change trail
New SessionAuditLog (mirrors AppointmentEvent): session FK, field, operation
(create/update/delete), old_value/new_value, actor id+name, note, created_at.
Repository saves and lists a session's history (newest first, array hydration).
Migration creates the table.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 15:06:25 +03:30
hamedandClaude Fable 5 0d943d8fc5 feat(admin): session action menu (view invoice + archive) and archive filter
SessionServiceCard's '...' icon becomes a real dropdown with «مشاهده فاکتور»
and «آرشیو»/«خروج از آرشیو» (click-outside close), plus an «آرشیو» badge on
archived cards. PatientDetailPage adds a فعال‌ها/همه/آرشیو segmented filter on
the services tab (default active), threads the filter into the sessions query,
and wires the archive action to PATCH /session/{uuid} { archived }.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 14:48:08 +03:30
hamedandClaude Fable 5 666833fc69 feat(patient): soft-archive sessions with active/all/archived filter
Add archived + archived_at columns to PatientSession (setArchived stamps the
time). findByRecord/countByRecord accept an archived filter (default all to
keep existing callers, incl. the discount visit-count, unchanged); the
sessions GET reads ?filter=active|all|archived, defaulting to active so
archived visits are hidden. updateSession accepts { archived }. Records are
kept, only hidden. Verified end-to-end; docs updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 14:44:23 +03:30
hamedandClaude Fable 5 53bd87e807 feat(admin): show payment date/time and recorder on session payments
Each recorded payment row (PaymentStep + DetailsStep) now shows the payment
date+time (paid_at, Jalali via formatDateTime, falling back to created_at) and
the user who recorded it (created_by_name) beneath the method/amount line. Both
fields were already in the API payload. Add created_at to SessionPaymentEntry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 14:40:00 +03:30
hamedandClaude Fable 5 476e219165 fix(admin): convert toman to rial when recording session payment/discount
PaymentStep sent the toman amount straight through as amount_rials (and the
fixed discount value as discount_value), so a 500,000 toman payment was stored
as 5,000,000... no — as 500,000 rial (10x too small). Apply tomanToRial before
sending the payment amount and the fixed-discount value; percent discount and
rule-based discount are unaffected. Verified stored value is now correct rial.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 14:38:10 +03:30
hamedandClaude Fable 5 ee69ac96be feat(admin): comma-grouped Latin amounts on the session payment step
Add an opt-in latin prop to PriceInput (en-US grouping, English digits) and
use it for the discount-value and payment-amount fields on PaymentStep, which
were raw number inputs. Amounts now show 3-digit comma grouping and Persian
digits typed are converted to English (via PriceInput's toEnglishDigits).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 12:45:45 +03:30
hamedandClaude Fable 5 204c7debe1 fix(discount): correct edit-form data + calendar clipping + input styling
- List endpoint returned camelCase keys (getArrayResult) so the edit modal
  read empty discount_type/target_*; return toArray() (snake_case) instead.
- Portal the Persian date-picker calendar to body (fixed) so it no longer
  renders under the modal's overflow-scroll body.
- Style the PriceInput/number fields with cp-input in the rule modal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 12:37:43 +03:30
hamedandClaude Fable 5 809ad11680 feat(discount): add «مدیریت تخفیف‌ها» to the desktop settings sidebar
The desktop settings sidebar (PurchaseSubscriptionSidebar) uses its own
NAV_ITEMS list, separate from SETTINGS_MENU; add the discounts entry there so
it shows in the settings menu on desktop too.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 12:29:52 +03:30
hamedandClaude Fable 5 8d741767ba feat(discount): dedicated «مدیریت تخفیف‌ها» item in the settings menu
Add a standalone settings sidebar entry + route (/admin/discounts) rendering
DiscountsPage (DiscountTab inside SettingsLayout, doctor/clinic only), and
revert the temporary tab inside the subscription page. Cleaner, discoverable
placement.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 12:24:32 +03:30
hamedandClaude Fable 5 07c9600991 fix(discount): move Discount Management tab to the /admin/subscription page
The discount tab was wired into AdminSubscriptionPage (route
/admin/admin-subscription, admin-only) but /admin/subscription renders the
doctor/clinic SubscriptionPage. Since discount rules are owner-scoped, add
the segmented «مدیریت تخفیف‌ها» tab there and revert AdminSubscriptionPage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 12:14:48 +03:30
hamedandClaude Fable 5 2691751e70 feat(discount): apply rule-based discounts on the session payment step
PaymentStep now loads /session/{uuid}/discount-suggestions and lets the
operator apply a suggested rule (PATCH discount_rule_uuid) or remove it,
alongside the existing manual discount. Adds an amount summary (before /
discount + source rule label / final payable). Verified end-to-end.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 12:04:56 +03:30
hamedandClaude Fable 5 18d9cc9812 feat(discount): admin Discount Management tab + uuid-based rule targets
Switch rule target references from int ids to uuids (frontend-friendly; the
engine compares uuids directly) via a migration. Add a Discount Management
tab to the subscription page with a full CRUD UI (DiscountTab): table + modal
form with per-type dynamic target fields (tenant tag / service cascade /
amount / visit count / specific patient / occasion + validity window),
priority and combinable/active toggles. Verified TSC + build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 12:02:03 +03:30
hamedandClaude Fable 5 b3b1dad832 feat(discount): apply a discount rule to a session with audit
Extend PatientService::applyDiscount to carry the source rule id/label and
add applyDiscountRule, which computes the rial amount via the engine
(DiscountEngine::computeForRule now public) and records the rule for audit.
updateSession accepts discount_rule_uuid (owner-scoped, takes precedence over
the manual discount; ''/null clears). Verified end-to-end.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 11:55:16 +03:30
hamedandClaude Fable 5 6cc42c941e feat(discount): admin CRUD + session discount-suggestions endpoints
DiscountController exposes owner-scoped CRUD for discount rules
(/api/v1/admin/discount-rules) and GET /api/v1/session/{uuid}/discount-
suggestions which runs the engine for a session. Owner resolved from the
doctor/clinic user. Verified end-to-end (create, list, suggestions). Adds
docs/api/discount.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 11:52:03 +03:30
hamedandClaude Fable 5 cf69369231 feat(discount): add DiscountEngine to evaluate rules for a session
Evaluates all active owner rules against a session across the six types
(patient tag, invoice amount, specific patient, occasion incl. birthday,
service, visit count), honoring validity windows and capping each computed
discount at the session's unpaid remainder. Returns priority-ordered
suggestions with computed rials.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 11:49:08 +03:30
hamedandClaude Fable 5 f0e1f43d51 feat(discount): add DiscountRule entity and session discount-rule audit columns
New Discount domain: DiscountRule (generic per-tenant rule with 6 types,
priority, combinable, validity window, and per-type target fields) plus its
repository. PatientSession gains applied_discount_rule_id/label audit columns
and setDiscount() now records the source rule. Migration creates the table
and columns.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 11:47:33 +03:30
hamedandClaude Fable 5 db7b0aca1a feat(admin): show visit datetime and itemized cost breakdown in session details
DetailsStep now renders the appointment's visit date/time first (from
session_at, falling back to created_at) and an itemized cost table (visit +
each service line with its line total) plus the grand total. Extend
SessionCardData with session_at, services_total_rials, and per-line price
fields already returned by the session API.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 11:40:42 +03:30
hamedandClaude Fable 5 8c39d720b5 fix(patient): populate auto-created session from the confirmed appointment
When an appointment is confirmed, the auto-created PatientSession was saved
empty: session_at null, visit_price_rials 0, no service line items, totals 0.
Now it copies the visit datetime from the appointment slot, the visit price
(falling back to the tenant free-visit price), a SessionService line per
appointment service (price snapshot from the service), and computes
services_total_rials / final_price_rials. Verified end-to-end.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 11:38:13 +03:30
hamedandClaude Fable 5 49b2ca60d7 feat(appointment): log cancellation events and show them in a Timeline
Introduce the first per-appointment event system. On cancel (via the status
or general update endpoints) an AppointmentEvent (type=cancelled, «نوبت لغو
شد») is recorded with the actor, cancel time, and an optional cancel_reason,
plus a warning-level app_log entry. New GET /appointment/{uuid}/events
returns the ordered event list. The admin appointment detail page renders a
Timeline section and the cancel dialog now collects an optional reason.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 11:13:23 +03:30
hamedandClaude Fable 5 36d7fe0303 fix(admin): send visit_price_rials from appointment create modal & drawer
NewAppointmentModal and NewAppointmentDrawer never read the require-visit-
price setting nor sent visit_price_rials, so booking failed with 422
"هزینه ویزیت الزامی است" when the flag was on. Add a visit-price field
(prefilled from the free-visit price), require it when the flag is set, and
include visit_price_rials in the POST payload, matching AppointmentCreatePage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 11:02:23 +03:30
hamedandClaude Fable 5 15f1c8d1eb feat(admin): force Latin digits in numeric fields globally
- Add a numeric prop to the base Input component that sets inputMode,
  dir=ltr, lang=en and normalizes Persian/Arabic digits to Latin via the
  shared toEnglishDigits on every change.
- Dedupe digit conversion: PriceInput now uses toEnglishDigits instead of
  its local map; AppointmentsPage mobile handler uses sanitizeMobileInput.
- Fix raw national-code / mobile inputs in NewAppointmentModal and
  NewAppointmentDrawer that stripped Persian digits without converting.
- Cover the numeric Input behaviour with unit tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 10:58:55 +03:30
hamedandClaude Fable 5 2638b70162 feat(admin): move visit-price save button to end of card
Relocate the ذخیره button out of the price-input row to the bottom of the
FreeVisitPrice card, right-aligned, so it sits after the require-visit-price
toggle in a logical end-of-form position.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 10:55:00 +03:30
hamedandClaude Fable 5 3e5d61e280 feat(admin): move work schedule from profile to appointment settings
Export ScheduleSection from DoctorDetailPage and render the full
weekly/overrides/holidays editor in AppointmentSettingsPage. Gate the
profile's schedule render on !isOwnProfile so it no longer appears in the
doctor's own profile while the admin doctor-detail view keeps it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 10:53:51 +03:30
hamed fcd7a0596d feat: convert deposit amounts from toman to rials in appointment handling 2026-07-17 10:21:52 +03:30
hamed a0ddb4c0d1 feat: add visit price requirement feature
- Introduced a new boolean flag `require_visit_price` in the `EntityInsurancePricing` to enforce visit price for appointments.
- Updated the appointment creation endpoints to validate `visit_price_rials` based on the new flag.
- Added `visit_price_rials` field to the `Appointment` entity to store the visit price.
- Enhanced the `PatientService` to validate visit price during session creation.
- Updated API documentation to reflect changes in appointment and insurance pricing.
- Implemented a new service `VisitPriceRequirementResolver` to determine if a visit price is required for a doctor based on their pricing settings.
- Added migrations to update the database schema for the new fields.
2026-07-16 19:44:30 +03:30
hamed 880c4c06cb chore: update graphify graph 2026-07-16 15:44:45 +03:30
hamedandClaude Opus 4.8 d567ea5553 fix: RTL stepper connectors and duplicate clock icon in session wizard
SessionStepper copied MUI's LTR connector offsets (left: -50%), which in
the RTL admin drew each connector away from its previous step — a stray
line ran off the card edge past the last step and the first two steps
had no line between them. Swap the offsets so connectors extend toward
the previous step on the right, matching tauri's MUI-flipped rendering.

The acceptance-time field showed two clock icons (custom ClockP + the
browser's native picker indicator); hide the native one like the tauri
source, which has a single ClockField adornment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 15:44:37 +03:30
hamed 2e69af1f74 chore: update graphify graph 2026-07-16 15:39:11 +03:30
hamedandClaude Opus 4.8 76bfa5d36f fix: issue invoice on demand so view-invoice buttons actually work
The admin SPA never called POST /billing/invoices, so every session had
invoice_uuid = null and the view-invoice buttons on the patient services
tab (and MyPatients visit modal) were permanently disabled. Add a shared
useIssueInvoice hook (idempotent create + finalize when draft) and wire
it into:
- DetailsStep: the wizard's final step now really issues the invoice
- SessionServiceCard / PatientDetailPage: clicking view-invoice on a
  session without an invoice issues it first, then opens the summary
- MyPatientsPage visit modal: same, replacing the dead disabled button

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 15:39:02 +03:30
hamed 0ba4bf383b chore: update graphify graph 2026-07-16 14:39:00 +03:30
hamedandClaude Opus 4.8 0e0e4ebe71 feat: enrich invoice summary with session payments, consumables and discount
Port the remaining gap of tauri /files/invoice-summary into the admin
invoice summary: GET /api/v1/billing/invoices/{uuid} now includes a
'session' key (full PatientSession payload) so InvoiceSummaryModal can
render the consumables table, the itemized payments table (method,
amount, date-time, recorder) and real discount/paid/remaining figures
instead of heuristics. Invoices without a source session keep the
previous behavior (session: null).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 14:38:38 +03:30
hamed 3b2b22f7d6 chore: update graphify graph 2026-07-16 14:21:02 +03:30
hamedandClaude Opus 4.8 07222826c3 feat: port tauri create-service page as 3-step new-session wizard
Backend:
- Add session_at and inventory_package_id to patient_sessions, new
  session_consumables table (migration Version20260716102537)
- New SessionConsumable entity/repository mirroring SessionService;
  price snapshot, quantity >= 1, tenant-scoped silent skip
- PatientService::createSession accepts session_at, consumables[] and
  inventory_package_uuid; consumables are fully patient-paid (no
  insurance coverage) and added to final_price_rials
- Functional tests: success, foreign-tenant/unknown skip, empty and
  zero-quantity edges (tests/Patient/SessionConsumableTest.php)
- docs/api/patient.md updated for the new Create Session fields

Frontend (admin):
- NewSessionPage rewritten as the tauri /files/create-service 3-step
  wizard (ایجاد سرویس ← پرداخت ← جزییات) using SessionStepper
- New CreateStep: acceptance date/time (Jalali), section/service/staff,
  consumables with counters, package select, conditional insurance
  block (insured service or insured patient profile), price summary
- PaymentStep/DetailsStep extracted from SessionPaymentPage and shared
  between both pages (behavior unchanged, tests still green)
- UserTick and FilesServiceAddCard icons ported verbatim from tauri
- Vitest coverage for the wizard incl. empty-data states

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 14:20:32 +03:30
hamed d7b06108f0 chore: update graphify graph 2026-07-16 13:32:39 +03:30
hamedandClaude Opus 4.8 27d088c6dd feat: port tauri create-service payment flow to admin session settlement
Port the tauri /files/create-service page (payment mode) to the admin SPA
and back it with real multi-part session settlement:

Backend:
- New SessionPayment entity (session_payments table): partial payments
  per session with method (wallet/pos/cash/card), amount, paid_at, actor
- PatientSession: settlement discount (percent/fixed), discount_rials,
  paid_at, payments relation; remaining debt derived from
  final - discount - paid total
- POST /api/v1/session/{uuid}/payments: register a partial payment;
  wallet method debits the patient wallet; zero remaining marks paid
- PATCH /api/v1/session/{uuid}: accepts discount_type/discount_value
  (null removes) and paid_at, backward compatible
- New error codes: ERR_SESSION_PAYMENT_INVALID/_EXCEEDS,
  ERR_SESSION_DISCOUNT_INVALID
- Migration + 14 functional tests (partial/full/wallet/exceed/discount)

Frontend (admin):
- SessionPaymentPage: two-step stepper (پرداخت ← جزییات) ported from
  tauri AddService payment mode — service cost, settlement discount
  input, Jalali payment date, wallet balance, 4-method payment accordion,
  paid-list box, details summary
- SessionStepper + stepper/payment icons ported verbatim from tauri SVGs
- «تکمیل پرداخت» on SessionServiceCard now navigates to the payment page
  (replaces the small settle modal on PatientDetailPage)
- Routes for patients/ and my-patients/ variants; vitest coverage
- docs/api/patient.md updated

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 13:32:06 +03:30
hamed 73ae4baa66 chore: update graphify graph 2026-07-16 12:51:54 +03:30
hamedandClaude Opus 4.8 15c92903e1 feat: replace patient messages tab with pinnable notes
Port the tauri AddNoteModal notes feature into the admin patient case-file,
replacing the mislabeled «پیام‌ها» (SMS log) tab with «یادداشت‌ها».

Backend (src/Patient):
- PatientNote entity + repository (record-scoped, pinned-first ordering)
- CRUD endpoints on PatientController: GET /notes, POST /note,
  PATCH /note/{uuid} (edit body + toggle pin), DELETE /note/{uuid}
- author display name captured server-side from the current user
- migration for patient_notes; docs/api/patient.md updated

Frontend (assets/admin):
- NotesTab: compose box, newest/oldest sort, pinned-first list with
  accent rail + pin/edit/delete, edit modal, confirm-delete, empty state
- tab key/label/icon messages -> notes; onAddNote deep-links the notes tab

Tests: PatientNoteTest (create/list-order/edit/pin/delete/validation/ownership),
PatientDetailPage notes cases (render/empty/pin/create).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 12:51:33 +03:30
hamed 65083854cb feat: set default date and time in CallCenterTab, update input styles 2026-07-16 12:38:22 +03:30
hamed d4fd6c0020 chore: update graphify graph 2026-07-16 12:22:55 +03:30
hamedandClaude Opus 4.8 ef97b2b249 feat: unify wallet with real payment methods, full transaction transparency, pay-session-from-wallet
Address wallet feedback: use the clinic's real payment infrastructure,
redesign the tab to match the admin panel, and make every wallet movement
fully auditable.

Backend:
- WalletTransaction: add createdBy (acting user) + createdByName, payment_method,
  reference, status; toArray exposes them (migration Version20260716083939).
- WalletService (Settlement): balance/charge/withdraw + settleSessionFromWallet,
  records actor/method/reason; insufficient balance throws ERR_WALLET_INSUFFICIENT.
- PatientController: charge/withdraw delegate to WalletService and accept
  payment_method/reference; PATCH /session/{uuid} with payment_method=wallet
  debits the patient's final share from the wallet (reference=session:{uuid}).
- docs/api/patient.md updated.

Frontend:
- Wallet modal redesigned to panel style (no gradient); payment method now uses
  the clinic's real bank accounts + POS devices (usePaymentMethods) plus cash.
- Wallet tab: panel balance card + DataTable ledger with columns مبلغ/نوع/روش/
  دلیل/ثبت‌کننده/تاریخ/ساعت/وضعیت + همه/واریزی/برداشت filters.
- Session card «تکمیل پرداخت» opens a payment-method chooser incl. کیف پول.

Tests: backend transparency + session-from-wallet (success/insufficient/cash);
frontend modal (real methods, toman→rials) + wallet tab + settle chooser.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 12:22:37 +03:30
hamed 22474653b5 chore: update graphify graph 2026-07-16 11:57:00 +03:30
hamedandClaude Opus 4.8 f028d6841a feat: port wallet charge/withdraw modal from tauri to patient admin page
Add manual wallet withdrawal (debit) endpoint mirroring the offline app's
balance guard, and rebuild the patient کیف پول tab around a single
charge/withdraw toggle modal (quick amounts, تومان→ریال conversion,
transaction filters).

Backend:
- POST /api/v1/patient/{uuid}/wallet/withdraw — creates a debit
  WalletTransaction; 422 ERR_WALLET_INSUFFICIENT when amount exceeds balance.
- ErrorCodes: ERR_WALLET_INSUFFICIENT ('موجودی کیف پول کافی نیست').
- docs/api/patient.md updated.

Frontend:
- usePatientWallet hook (balance + charge/withdraw mutations).
- WalletTransactionModal (toggle, quick amounts, UI-only payment fields).
- WalletTab: charge button, همه/واریزی/برداشت filters.

Tests: backend withdraw success/insufficient/non-positive/ownership;
frontend modal + wallet tab interactions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 11:56:39 +03:30
hamed 4a70c87a8b chore: update graphify graph 2026-07-16 11:41:05 +03:30
hamedandClaude Opus 4.8 069189863c feat: port پرداخت‌ها (payments) tab from tauri to patient detail page
Replace the flat gateway-payment list on the patient detail «پرداخت‌ها» tab with
the session-grouped accordion design ported from clinic-pro-tauri PaymentsSection:

- New SessionPaymentAccordion mirrors the tauri accordion: header (service, date,
  final price, پرداخت شده/تسویه نشده badge) + a settlement line (date, amount,
  method, personnel=doctor) or the «هیچ پرداختی ثبت نشده است.» empty message.
- New PaymentsTab reuses the already-fetched sessions query (real model = one
  payment_method per session) — no extra request, no backend change, no new API.
- Add FilesServicePaymentsCheck icon (verbatim from tauri).
- Remove the now-unused paymentsQ (gateway list), PAYMENT_STATUS map and the dead
  TabList helper (both its callers replaced by the ported card/accordion tabs).
- Tests: SessionPaymentAccordion (paid/unpaid/collapsed/toggle) + updated the page
  payments-tab test to assert session accordions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 11:40:51 +03:30
hamed b82ab4db9c chore: update graphify graph 2026-07-16 11:30:35 +03:30
hamedandClaude Opus 4.8 1a783971c9 feat: port نوبت‌ها (appointments) tab from tauri to patient detail page
Replace the placeholder row list on the patient detail «نوبت‌ها» tab with the
card-grid design ported pixel-for-pixel from clinic-pro-tauri TurnsSection:

- New AppointmentTurnCard mirrors tauri TurnsCard (success icon, title, date/time
  chips, personnel, status). Status uses the live AppointmentStatusDropdown
  instead of the tauri mock.
- New AppointmentsTab in PatientDetailPage: sort/filter toolbar (client-side) +
  reserve/new buttons linking to existing /admin/appointments pages + card grid.
- Add CalendarD/ClockP/UserD/StatusGlobe icons (verbatim from tauri).
- Backend: expose version on GET /patient/{uuid}/appointments so the status
  dropdown can optimistic-lock. No new endpoint.
- Tests: PatientAppointmentsTest (shape/version/order/empty/ownership) +
  AppointmentTurnCard + tab data/empty cases. docs/api/patient.md updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 11:30:12 +03:30
hamed 7b87fda8f9 chore: update graphify graph 2026-07-16 11:16:40 +03:30
hamedandClaude Opus 4.8 f5b0ddc69e fix: SearchableSelect matches value across string/number types
Insurance option values are strings (String(insurance_id)) while the patient
profile prefill is a number, so strict === matching left بیمه پایه/تکمیلی
showing the placeholder on load — the saved insurance was invisible and looked
unsaved even though PATCH persisted it. Match with String(o.value) ===
String(value) so numeric prefills bind to string-valued options (and vice
versa); null/'' still selects nothing. Verified live: selects now display the
saved insurance after reload.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 11:16:22 +03:30
hamed 2e2b63f72d chore: update graphify graph 2026-07-16 10:59:43 +03:30
hamedandClaude Opus 4.8 46ee24d101 feat: add supplementary insurance select to patient record info form
The «اطلاعات پرونده» form only exposed basic insurance («نوع بیمه»). Add a
«بیمه تکمیلی» select next to it, wired to the supplementary insurances from
insurance-pricing. Backend PATCH /api/v1/patient/{uuid} already accepts
supplementary_insurance_id (UserProfile entity + controller) — frontend-only:
schema field, form select, profileToFormValues/formValuesToPayload mapping,
PatientProfileUpdate type, and the supplementary option passed from both the
case-file info tab (PatientDetailPage) and MyPatientsPage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 10:59:23 +03:30
hamed 8feaf0f22a chore: update graphify graph 2026-07-16 10:53:05 +03:30
hamedandClaude Opus 4.8 9638f363af feat: wire editable «اطلاعات پرونده» form into patient case-file info tab
Port tauri /files-services FileInfoSection into PatientDetailPage's info
tab. The tab previously rendered a stripped read-only InfoRow list; it now
renders the inline editable PatientRecordInfoForm (18 fields + ثبت اطلاعات),
matching the tauri source. Reuses the existing form component, patientForm
helpers, and the already-present patient/provinces/cities/insurance-pricing
endpoints (no new API). PATCH /api/v1/patient/{uuid} on submit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 10:52:31 +03:30
hamed d12259be15 chore: update graphify graph 2026-07-16 10:43:44 +03:30
hamedandClaude Opus 4.8 04cc36c33a fix: clean up selected-service chip layout in ServiceSlotPicker
Replace the .field-wrapped duration input (whose min-width + inner label
overflowed the fixed 92px box) with a self-contained inline duration editor;
drop flex-wrap and let the section->service name truncate with ellipsis so
each chip stays on one tidy row.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 10:43:39 +03:30
hamed 78396d3275 chore: update graphify graph 2026-07-16 10:41:24 +03:30
hamedandClaude Opus 4.8 e00fe997f9 feat: section-based service picker + per-appointment duration override (service mode)
Service-booking mode now selects services by section like slot mode:
appointment-booking-services returns service_section per item; ServiceSlotPicker
groups by section (SearchableSelect), accumulates picks across sections into a
removable 'section -> service' chip list.

Secretaries can override a service's duration for a single appointment without
changing the service default: appointment-service-slots accepts durations[uuid]
and both create endpoints accept service_durations; the override drives total
duration and slot_end. Online (patient) booking is unaffected — it never sends
overrides. Backend + frontend tests and docs updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 10:41:15 +03:30
hamed af6da197dc chore: update graphify graph 2026-07-16 10:18:16 +03:30
hamedandClaude Opus 4.8 029b5f632e feat: section-then-service picker with cross-section accumulation
Rework slot-mode service selection: pick a section, its services show as a
checkbox list, and chosen services collect into a removable 'selected'
chip list that persists when switching to another section (services from
multiple sections accumulate). Changing the section no longer clears the
selection. Add a test covering multi-section accumulation and chip removal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 10:18:10 +03:30
hamed 08aa3a78b8 chore: update graphify graph 2026-07-16 10:10:56 +03:30
hamedandClaude Opus 4.8 4642506c0f feat: support multiple services per appointment (checkbox selection)
Appointments could only reference a single service (ManyToOne). Add an
appointment_service_items join table (ManyToMany) so an appointment can
carry several services; the first stays the primary service_item for
backward compatibility, and toArray now also returns service_items[].

Both create endpoints (my/appointment, admin/appointment) accept
service_item_uuids[] and attach all of them. A new duration_from_services
flag gates the slot_end recompute: service-booking mode sends it true
(slot_end = start + Σ durations); slot mode omits it so the manual end
time is preserved. The admin endpoint previously ignored services entirely.

Frontend: in slot mode the single service dropdown becomes a checkbox list
filtered by the selected section (multi-select); service mode sends the
duration flag. Migration + backend/entity tests + docs updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 10:10:47 +03:30
hamed d76fd73529 chore: update graphify graph 2026-07-16 09:57:11 +03:30
hamedandClaude Opus 4.8 3e8126c520 fix: return patient national code from profile in patients list
National code is stored on the profiles table, not users, so the patients
search returned null user_national_code for patients that actually have one
— which blocked selecting an existing patient on the appointment create
form. Backfill user_national_code from the profile (batch query) in the
list endpoint. On the create page, show a picked patient's stored national
code read-only and only prompt for input when the record genuinely lacks
one. Add backend tests and update docs/api/patient.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 09:57:03 +03:30
hamed 110c210cf4 chore: update graphify graph 2026-07-16 09:45:13 +03:30
hamedandClaude Opus 4.8 e1fb63eb0f feat: add DigitInput component; force English digits for phone & national code
New reusable DigitInput normalizes Persian/Arabic digits to English on
input, strips non-digits, enforces maxDigits, and is always LTR + numeric
keyboard. Use it for the phone and national-code fields on the appointment
create page (phone previously kept raw Persian digits; national code lost
Persian digits to the \D strip).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 09:45:04 +03:30
hamed b2347f00e7 chore: update graphify graph 2026-07-16 09:39:40 +03:30
hamedandClaude Opus 4.8 bc4030251d fix: allow submit when picking an existing patient
Backend requires a valid national code for every appointment, but a picked
patient record may have none stored. Show a selected-patient card with an
editable national-code field (prefilled from the record) so submit can
enable. Add happy-path and boundary tests for the picked-patient flow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 09:39:33 +03:30
hamed 10adfa3b15 chore: update graphify graph 2026-07-16 09:33:24 +03:30
hamedandClaude Opus 4.8 a40fb7e598 feat: toggle new-patient form on appointment create page
Show name/phone/national-code fields only after clicking 'مراجعه کننده جدید';
hide the existing-patient search while in new-patient mode, with a
'انتخاب از لیست موجود' link to switch back. Update tests to click the
toggle before filling the fields.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 09:33:16 +03:30
hamed 6e040402e1 chore: update graphify graph 2026-07-16 09:28:25 +03:30
hamedandClaude Opus 4.8 54bdc73f37 fix: correct deposit section layout on appointment create page
Move switch to its own row; render amount field (wrapped in .field for a
proper bordered box) and wallet-charge button on a following aligned row.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 09:28:19 +03:30
hamed a6c11a20bd chore: update graphify graph 2026-07-16 09:18:53 +03:30
hamedandClaude Opus 4.8 170cd5f37e style: restyle appointment create page to match tauri CreateTurn mockup
Widen card to 1080px, 18px section headings, multi-column grids
(patient 3-col, service 3-col, slot-mode time 4-col), search field +
outlined 'new patient' button row, toggle switch for deposit, orange
wallet charge button, right-aligned submit. Service/slot booking logic,
endpoints, validation, and placeholders unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 09:18:29 +03:30
hamed b7730f545a fix(appointment): update EmptyCard to display 'افزودن نوبت سریع' for better clarity 2026-07-16 09:05:26 +03:30
hamed b1b99903ec Replace all native <select> elements in the admin panel with SearchableSelect for a consistent UI experience. This change enhances accessibility, supports RTL and dark mode, and improves the overall design by utilizing a common component. The updates include adjustments to state management and event handling to ensure seamless integration with existing functionality across various pages and components. 2026-07-16 08:56:59 +03:30
hamed 7b8a5c2775 feat(date-input): refactor PersianDateInput to use PersianDatePicker for Jalali calendar support 2026-07-16 08:26:39 +03:30
hamed 2f060bd5be feat(timezone): implement Tehran timezone handling across the application 2026-07-16 00:25:21 +03:30
hamed 8cd1253b81 feat(appointment): enhance service mode to display available booking slots between appointments 2026-07-16 00:16:37 +03:30
hamed 4715649fd8 feat(appointment): implement service mode functionality with service slot selection and validation 2026-07-16 00:02:36 +03:30
hamed ac4a430564 feat(appointment): add booking services endpoint and update security configuration for public access 2026-07-15 23:48:38 +03:30
hamed 2df16c6d29 feat(appointment): implement immutable booking mode after first save and update API documentation 2026-07-15 23:33:00 +03:30
hamed ad94c165e8 feat(appointment): add service mode functionality and update API documentation for service-based booking 2026-07-15 23:22:57 +03:30
hamed 5937f7e176 feat: add service-based booking mode to appointment scheduling
- 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.
2026-07-15 23:15:45 +03:30
hamed 6904361e32 feat(appointment): update patient name handling to prioritize existing profile name for new appointments 2026-07-15 22:35:33 +03:30
hamed 13bf9453e5 feat(appointment): ensure existing profile name is used over modal input for patient name 2026-07-15 20:33:15 +03:30
hamed d91867bd63 fix(appointment): resolve issue with reserved slots incorrectly shown as available 2026-07-15 19:13:27 +03:30
hamed de6fe1bd56 feat(appointment): update patient identification to use national code from profile and enhance appointment creation logic 2026-07-15 18:46:56 +03:30
hamed 0dc1ab949a feat(appointment): add Enter key functionality for mobile search in appointment modal 2026-07-15 18:29:59 +03:30
hamed 5d5089244b feat: add mobile-based patient lookup for appointment booking
- Implemented a new endpoint `/api/v1/my/appointment/patient-lookup` to search for patients by mobile number before booking an appointment.
- Updated the `NewAppointmentModal` component to utilize the new patient lookup feature, allowing for direct booking if the patient is found with a national code.
- Enhanced the appointment booking form to handle mobile input normalization and display relevant fields based on the search results.
- Added tests for the new patient lookup functionality, ensuring proper behavior for found and not found cases, as well as validation for mobile input.
- Updated sidebar tests to reflect changes in the sidebar component structure and functionality.
2026-07-15 18:27:29 +03:30
hamed f378bf58e6 Add AST JSON representation for BookingScopeTest.php with nodes and edges 2026-07-15 18:14:59 +03:30