Commit Graph
629 Commits
Author SHA1 Message Date
hamed e4ddd38f0c feat: add per-doctor permissions management in clinics
- Implement DoctorPermissionsModal for managing doctor permissions in clinics.
- Create usePermissions hook to handle user permissions context.
- Add migration for clinic_doctor_permissions table with default permissions.
- Develop ClinicDoctorPermissionController for handling permissions API.
- Create ClinicDoctorPermission entity to manage permissions data.
- Implement ClinicDoctorPermissionRepository for database interactions.
- Add ClinicDoctorPermissionChecker for permission validation logic.
- Write tests for clinic doctor permissions functionality.
2026-07-18 09:44:13 +03:30
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