Files
clinicpro/assets/admin/hooks/usePaymentMethods.ts
T
hamedandClaude Opus 4.8 b459d082a4 feat: port payment management tab from tauri to admin dashboard
Add per-clinic payment methods (bank accounts + POS/card-reader devices)
under the "مدیریت پرداخت" settings tab at /admin/my-financial, ported from
clinic-pro-tauri's mock-only PaymentManagement tab into a real persisted
feature. These records are referenceable (by uuid) from patient invoices to
record which method a service payment was made with.

Backend (new src/PaymentMethod domain):
- BankAccount + Pos entities, repositories, PaymentMethodService (validation,
  ownership scoping, create/update/toggle logic).
- Thin PaymentMethodController exposing /api/v1/my/payment-methods/{bank-accounts,pos}
  (GET/POST/PUT + PATCH .../status), guarded to clinic/doctor/secretary/admin.
- Migration for bank_accounts + pos_devices tables.
- Functional tests (success + validation/404/403 + empty boundaries).
- docs/api/payment-method.md.

Frontend:
- Replace MyFinancialPage content with the payment-management UI (two tabs,
  tables, add/edit modals, status toggle) using the admin design system.
- usePaymentMethods hook (TanStack Query) + presentational components.
- Update page test to cover tabs, data, empty state and the add modal.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 11:39:32 +03:30

113 lines
3.9 KiB
TypeScript

import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { api } from '../lib/api';
import type { ApiResponse } from '../lib/api';
/**
* روش‌های پرداختِ کلینیک (حساب بانکی + کارت‌خوان) برای صفحهٔ «مدیریت پرداخت».
* منبع: /api/v1/my/payment-methods/... — این رکوردها بعداً از فاکتور مراجعه‌کننده
* برای ثبت روش پرداختِ یک سرویس ارجاع داده می‌شوند.
*/
export interface BankAccount {
uuid: string;
bank_name: string;
card_number: string | null;
account_number: string;
shaba_number: string | null;
is_active: boolean;
created_at: number;
}
export interface Pos {
uuid: string;
bank_name: string;
serial_number: string | null;
terminal_number: string;
account_number: string | null;
is_active: boolean;
created_at: number;
}
export interface BankAccountInput {
bank_name: string;
account_number: string;
card_number?: string;
shaba_number?: string;
}
export interface PosInput {
bank_name: string;
terminal_number: string;
serial_number?: string;
account_number?: string;
}
const BANK_KEY = ['payment-methods', 'bank-accounts'] as const;
const POS_KEY = ['payment-methods', 'pos'] as const;
// ── Bank accounts ────────────────────────────────────────────────────────────
export function useBankAccounts() {
return useQuery<ApiResponse<BankAccount[]>>({
queryKey: BANK_KEY,
queryFn: () => api.get('/api/v1/my/payment-methods/bank-accounts'),
});
}
export function useCreateBankAccount() {
const qc = useQueryClient();
return useMutation<ApiResponse<BankAccount>, unknown, BankAccountInput>({
mutationFn: (body) => api.post('/api/v1/my/payment-methods/bank-accounts', body),
onSuccess: () => qc.invalidateQueries({ queryKey: BANK_KEY }),
});
}
export function useUpdateBankAccount() {
const qc = useQueryClient();
return useMutation<ApiResponse<BankAccount>, unknown, { uuid: string; body: BankAccountInput }>({
mutationFn: ({ uuid, body }) => api.put(`/api/v1/my/payment-methods/bank-accounts/${uuid}`, body),
onSuccess: () => qc.invalidateQueries({ queryKey: BANK_KEY }),
});
}
export function useToggleBankAccountStatus() {
const qc = useQueryClient();
return useMutation<ApiResponse<BankAccount>, unknown, string>({
mutationFn: (uuid) => api.patch(`/api/v1/my/payment-methods/bank-accounts/${uuid}/status`, {}),
onSuccess: () => qc.invalidateQueries({ queryKey: BANK_KEY }),
});
}
// ── POS devices ──────────────────────────────────────────────────────────────
export function usePosDevices() {
return useQuery<ApiResponse<Pos[]>>({
queryKey: POS_KEY,
queryFn: () => api.get('/api/v1/my/payment-methods/pos'),
});
}
export function useCreatePos() {
const qc = useQueryClient();
return useMutation<ApiResponse<Pos>, unknown, PosInput>({
mutationFn: (body) => api.post('/api/v1/my/payment-methods/pos', body),
onSuccess: () => qc.invalidateQueries({ queryKey: POS_KEY }),
});
}
export function useUpdatePos() {
const qc = useQueryClient();
return useMutation<ApiResponse<Pos>, unknown, { uuid: string; body: PosInput }>({
mutationFn: ({ uuid, body }) => api.put(`/api/v1/my/payment-methods/pos/${uuid}`, body),
onSuccess: () => qc.invalidateQueries({ queryKey: POS_KEY }),
});
}
export function useTogglePosStatus() {
const qc = useQueryClient();
return useMutation<ApiResponse<Pos>, unknown, string>({
mutationFn: (uuid) => api.patch(`/api/v1/my/payment-methods/pos/${uuid}/status`, {}),
onSuccess: () => qc.invalidateQueries({ queryKey: POS_KEY }),
});
}