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>
This commit is contained in:
hamed
2026-07-16 13:32:06 +03:30
co-authored by Claude Opus 4.8
parent 73ae4baa66
commit 27d088c6dd
18 changed files with 1425 additions and 55 deletions
@@ -2,6 +2,14 @@ import type { CSSProperties } from 'react';
import { formatDate, formatRial } from '../lib/utils';
import { FilesServiceSuccess, FilesServiceMore } from './icons/FilesServiceIcons';
export interface SessionPaymentEntry {
uuid: string;
method: string;
amount_rials: number;
paid_at?: number;
created_by_name?: string | null;
}
export interface SessionCardData {
uuid: string;
services?: Array<{ service_name?: string; name?: string }>;
@@ -13,6 +21,13 @@ export interface SessionCardData {
invoice_uuid?: string | null;
notes?: string | null;
created_at?: number;
// تسویه چندتکه + تخفیف (backend session settlement fields)
discount_type?: 'percent' | 'fixed' | null;
discount_value?: number;
discount_rials?: number;
paid_total_rials?: number;
paid_at?: number | null;
payments?: SessionPaymentEntry[];
}
/** label:value row inside the service card (mirrors tauri ServiceInfoRow). */