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>
113 lines
3.9 KiB
TypeScript
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 }),
|
|
});
|
|
}
|