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>
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
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 }),
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user