feat: port wallet charge/withdraw modal from tauri to patient admin page
Add manual wallet withdrawal (debit) endpoint mirroring the offline app's
balance guard, and rebuild the patient کیف پول tab around a single
charge/withdraw toggle modal (quick amounts, تومان→ریال conversion,
transaction filters).
Backend:
- POST /api/v1/patient/{uuid}/wallet/withdraw — creates a debit
WalletTransaction; 422 ERR_WALLET_INSUFFICIENT when amount exceeds balance.
- ErrorCodes: ERR_WALLET_INSUFFICIENT ('موجودی کیف پول کافی نیست').
- docs/api/patient.md updated.
Frontend:
- usePatientWallet hook (balance + charge/withdraw mutations).
- WalletTransactionModal (toggle, quick amounts, UI-only payment fields).
- WalletTab: charge button, همه/واریزی/برداشت filters.
Tests: backend withdraw success/insufficient/non-positive/ownership;
frontend modal + wallet tab interactions.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -158,12 +158,42 @@ describe('PatientDetailPage (پرونده تبدار)', () => {
|
||||
expect(screen.getByText('هیچ پرداختی ثبت نشده است.')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows wallet balance on the wallet tab', async () => {
|
||||
it('shows wallet balance, charge button and transaction filters on the wallet tab', async () => {
|
||||
renderDetail();
|
||||
await loaded();
|
||||
fireEvent.click(screen.getByText('کیف پول'));
|
||||
expect(await screen.findByText('موجودی کیف پول')).toBeInTheDocument();
|
||||
expect(await screen.findByText('شارژ')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /شارژ کیف پول/ })).toBeInTheDocument();
|
||||
// فیلترهای تراکنش
|
||||
expect(screen.getByRole('button', { name: 'همه' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'واریزی' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'برداشت' })).toBeInTheDocument();
|
||||
// تراکنش credit اولیه دیده میشود
|
||||
expect(screen.getByText('شارژ')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('filters out the credit transaction when the برداشت filter is selected', async () => {
|
||||
renderDetail();
|
||||
await loaded();
|
||||
fireEvent.click(screen.getByText('کیف پول'));
|
||||
await screen.findByText('موجودی کیف پول');
|
||||
fireEvent.click(screen.getByRole('button', { name: 'برداشت' }));
|
||||
expect(screen.getByText('تراکنشی ثبت نشده است')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('opens the charge/withdraw modal and posts a charge', async () => {
|
||||
const post = api.post as ReturnType<typeof vi.fn>;
|
||||
post.mockResolvedValue({ success: true, data: {} });
|
||||
renderDetail();
|
||||
await loaded();
|
||||
fireEvent.click(screen.getByText('کیف پول'));
|
||||
await screen.findByText('موجودی کیف پول');
|
||||
fireEvent.click(screen.getByRole('button', { name: /شارژ کیف پول/ }));
|
||||
// مودال باز شد → تب برداشت هم دیده میشود
|
||||
expect(await screen.findByRole('button', { name: 'برداشت از کیف پول' })).toBeInTheDocument();
|
||||
fireEvent.change(screen.getByPlaceholderText('مبلغ دلخواه (تومان)'), { target: { value: '50000' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: 'ثبت تراکنش' }));
|
||||
await waitFor(() => expect(post).toHaveBeenCalledWith('/api/v1/patient/r1/wallet/charge', { amount_rials: 500000 }));
|
||||
});
|
||||
|
||||
it('renders the messages tab with a send box', async () => {
|
||||
|
||||
Reference in New Issue
Block a user