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>
65 lines
1.9 KiB
TypeScript
65 lines
1.9 KiB
TypeScript
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
import { api } from '../lib/api';
|
|
import type { ApiResponse } from '../lib/api';
|
|
|
|
export interface WalletTxn {
|
|
uuid: string;
|
|
amount_rials: number;
|
|
type: 'credit' | 'debit' | string;
|
|
description?: string | null;
|
|
balance_after: number;
|
|
created_by_name?: string | null;
|
|
payment_method?: string | null;
|
|
reference?: string | null;
|
|
status?: string;
|
|
created_at: number;
|
|
}
|
|
|
|
interface WalletData {
|
|
balance_rials: number;
|
|
recent_transactions: WalletTxn[];
|
|
}
|
|
|
|
export interface WalletTxnInput {
|
|
amount_rials: number;
|
|
description?: string;
|
|
payment_method?: string;
|
|
reference?: string;
|
|
}
|
|
|
|
/**
|
|
* کیف پول بیمار: موجودی + تراکنشهای اخیر، و mutationهای شارژ (credit) و برداشت (debit).
|
|
* منبع: GET /api/v1/patient/{uuid}/wallet و POST .../wallet/{charge|withdraw}.
|
|
* کلید کوئری: ['patient-wallet', uuid]؛ پس از هر mutation موفق invalidate میشود.
|
|
*/
|
|
export function usePatientWallet(uuid: string | undefined) {
|
|
const qc = useQueryClient();
|
|
const queryKey = ['patient-wallet', uuid];
|
|
|
|
const query = useQuery<ApiResponse<WalletData>>({
|
|
queryKey,
|
|
queryFn: () => api.get(`/api/v1/patient/${uuid}/wallet`),
|
|
enabled: !!uuid,
|
|
});
|
|
|
|
const invalidate = () => qc.invalidateQueries({ queryKey });
|
|
|
|
const charge = useMutation<ApiResponse<unknown>, unknown, WalletTxnInput>({
|
|
mutationFn: (body) => api.post(`/api/v1/patient/${uuid}/wallet/charge`, body),
|
|
onSuccess: invalidate,
|
|
});
|
|
|
|
const withdraw = useMutation<ApiResponse<unknown>, unknown, WalletTxnInput>({
|
|
mutationFn: (body) => api.post(`/api/v1/patient/${uuid}/wallet/withdraw`, body),
|
|
onSuccess: invalidate,
|
|
});
|
|
|
|
return {
|
|
balanceRials: query.data?.data?.balance_rials ?? 0,
|
|
transactions: query.data?.data?.recent_transactions ?? [],
|
|
isLoading: query.isLoading,
|
|
charge,
|
|
withdraw,
|
|
};
|
|
}
|