feat: Add online share functionality for secretaries
- Introduced `online_share_enabled` and `online_share_percent` fields in the `doctor_secretaries` table to manage secretary shares from online appointments. - Added `bank_account` field in the `profiles` table to store user-level IBANs for settlements. - Created `secretary_earnings` table to track earnings per secretary from online appointments, including a foreign key relationship with `financial_breakdowns`. - Implemented `SecretaryEarning` entity and repository for managing secretary earnings. - Developed `SecretaryShareResolver` service to determine which secretaries earn from online payments. - Added `UserIbanResolver` service to handle user IBAN retrieval and management. - Created `HasIbansTrait` for entities to manage IBANs in a JSON format. - Implemented tests for secretary earnings and API endpoints for managing secretary shares and IBANs.
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import { Routes, Route } from 'react-router-dom';
|
||||
import { renderWithProviders } from '../test/utils';
|
||||
|
||||
vi.mock('../lib/api', () => ({
|
||||
api: { get: vi.fn(), post: vi.fn(), patch: vi.fn(), put: vi.fn(), delete: vi.fn() },
|
||||
ApiError: class extends Error {},
|
||||
}));
|
||||
|
||||
import { api } from '../lib/api';
|
||||
import SecretaryDetailPage from './SecretaryDetailPage';
|
||||
|
||||
const get = api.get as ReturnType<typeof vi.fn>;
|
||||
const put = api.put as ReturnType<typeof vi.fn>;
|
||||
|
||||
function mockSecretary(over: Record<string, unknown> = {}) {
|
||||
get.mockResolvedValue({
|
||||
success: true,
|
||||
data: { data: {
|
||||
uuid: 'rel-1', user_name: 'زهرا رضایی', mobile_number: '09120000000',
|
||||
doctor_name: 'دکتر تست', doctor_uuid: 'doc-1', is_active: true,
|
||||
permissions: {}, created_at: 1_700_000_000,
|
||||
online_share_enabled: false, online_share_percent: 0,
|
||||
earnings: { total_rials: 1_500_000, this_month_rials: 500_000, appointments_count: 3 },
|
||||
...over,
|
||||
} },
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
get.mockReset();
|
||||
put.mockReset();
|
||||
put.mockResolvedValue({ success: true, data: { data: {} } });
|
||||
});
|
||||
|
||||
function renderDetail() {
|
||||
return renderWithProviders(
|
||||
<Routes><Route path="/admin/secretaries/:uuid" element={<SecretaryDetailPage />} /></Routes>,
|
||||
{ route: '/admin/secretaries/rel-1' },
|
||||
);
|
||||
}
|
||||
|
||||
describe('SecretaryDetailPage', () => {
|
||||
it('مشخصات منشی و خلاصهٔ درآمد را نشان میدهد', async () => {
|
||||
mockSecretary();
|
||||
renderDetail();
|
||||
|
||||
expect(await screen.findByText('اطلاعات منشی')).toBeInTheDocument();
|
||||
expect(screen.getByText('دکتر تست')).toBeInTheDocument();
|
||||
expect(screen.getByText('سهم درآمد نوبتهای آنلاین')).toBeInTheDocument();
|
||||
expect(screen.getByText('خلاصه درآمد')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('سوییچ و درصد را از سرور پیشپر میکند و ذخیره میفرستد', async () => {
|
||||
mockSecretary({ online_share_enabled: true, online_share_percent: 5 });
|
||||
renderDetail();
|
||||
|
||||
await waitFor(() => expect(screen.getByLabelText('درصد سهم منشی')).toHaveValue('5'));
|
||||
expect(screen.getByLabelText('محاسبه درآمد منشی از نوبتهای آنلاین')).toBeChecked();
|
||||
|
||||
fireEvent.change(screen.getByLabelText('درصد سهم منشی'), { target: { value: '7' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: 'ذخیره' }));
|
||||
|
||||
await waitFor(() => expect(put).toHaveBeenCalledWith('/api/v1/admin/secretary/rel-1/online-share', {
|
||||
enabled: true,
|
||||
percent: 7,
|
||||
}));
|
||||
});
|
||||
|
||||
it('درصد بیشتر از ۱۰۰ خطا میدهد و ذخیره غیرفعال میشود', async () => {
|
||||
mockSecretary({ online_share_enabled: true, online_share_percent: 5 });
|
||||
renderDetail();
|
||||
await waitFor(() => expect(screen.getByLabelText('درصد سهم منشی')).toHaveValue('5'));
|
||||
|
||||
fireEvent.change(screen.getByLabelText('درصد سهم منشی'), { target: { value: '101' } });
|
||||
|
||||
expect(screen.getByText('درصد نمیتواند بیشتر از ۱۰۰ باشد')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'ذخیره' })).toBeDisabled();
|
||||
expect(put).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('فعالسازی با درصد صفر مجاز نیست', async () => {
|
||||
mockSecretary();
|
||||
renderDetail();
|
||||
await screen.findByText('اطلاعات منشی');
|
||||
|
||||
fireEvent.click(screen.getByLabelText('محاسبه درآمد منشی از نوبتهای آنلاین'));
|
||||
|
||||
expect(screen.getByText('برای فعالسازی، درصد باید بیشتر از صفر باشد')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'ذخیره' })).toBeDisabled();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user