Files
clinicpro/assets/admin/pages/SmsWalletPage.tax.test.tsx
T
hamed 7716b40f6a feat: implement tax calculations for subscription and SMS wallet payments
- Updated SubscriptionPeriod interface to include tax-related fields: tax_percent, tax_rials, and payable_rials.
- Modified payment API documentation to reflect changes in tax handling for subscriptions and SMS wallet charges.
- Adjusted PaymentController to calculate payment amounts based on subscription period details instead of client input.
- Enhanced PaymentManager to handle net amounts for SMS wallet charges, ensuring tax is not credited to the wallet.
- Created PaymentTaxCalculator and SubscriptionTaxCalculator services to manage tax calculations consistently across payment types.
- Added tests for tax calculations in both subscription and SMS wallet contexts, ensuring correct behavior with and without tax enabled.
- Updated frontend components to display tax information appropriately during payment processes.
2026-08-09 16:51:22 +03:30

84 lines
3.7 KiB
TypeScript

import { describe, it, expect, beforeEach, vi } from 'vitest';
import { screen, fireEvent } from '@testing-library/react';
import { renderWithProviders } from '../test/utils';
import { formatRial, tomanToRial } from '../lib/utils';
vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn() } }));
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 { useAuthStore } from '../stores/authStore';
import SmsWalletPage from './SmsWalletPage';
const get = api.get as ReturnType<typeof vi.fn>;
/**
* مبلغِ واردشده اعتبارِ کیف پول است — خالص. مالیات رویش اضافه می‌شود، پس عددِ
* دکمهٔ پرداخت باید بزرگ‌تر از مبلغ واردشده باشد وگرنه کاربر سرِ درگاه غافلگیر
* می‌شود.
*/
function mockApi(taxPercent: number) {
get.mockImplementation((url: string) => {
if (url.includes('/sms/wallet/balance')) {
return Promise.resolve({ success: true, data: { balance_rials: 0, sms_price_rials: 5000 } });
}
if (url.includes('/sms/wallet/logs')) {
return Promise.resolve({ success: true, data: [], meta: { totalRecords: 0, totalPages: 0, currentPage: 1 } });
}
if (url.includes('/sms/settings')) return Promise.resolve({ success: true, data: null });
// FeatureGate صفحه را پشت قابلیت پنل پیامک نگه می‌دارد.
if (url.includes('/subscription/my')) {
return Promise.resolve({
success: true,
data: { subscription: null, used_trial: false, effective_plan: { features: { sms_panel: true } } },
});
}
if (url.includes('/payment/config')) {
return Promise.resolve({
success: true,
data: { test_mode: false, appointment_fee_rials: 0, tax_percent: taxPercent, gateways: [{ name: 'mellat', label: 'بانک ملت' }] },
});
}
return Promise.resolve({ success: true, data: null });
});
}
async function openChargeModalWith(amountToman: string) {
renderWithProviders(<SmsWalletPage />, { route: '/admin/sms-wallet' });
fireEvent.click(await screen.findByText('شارژ کیف پول'));
const input = await screen.findByLabelText('مبلغ شارژ (تومان)');
fireEvent.change(input, { target: { value: amountToman } });
}
describe('SmsWalletPage — tax', () => {
beforeEach(() => {
get.mockReset();
// useSubscription فقط برای این نقش‌ها کوئری می‌زند؛ بدونش FeatureGate صفحه را می‌بندد.
useAuthStore.setState({ primaryRole: 'doctor', context: null } as any);
});
it('adds tax on top of the credit and shows the split', async () => {
mockApi(10);
await openChargeModalWith('100000');
const net = tomanToRial(100000);
expect(await screen.findByText('اعتباری که به کیف پول اضافه می‌شود')).toBeInTheDocument();
expect(screen.getByText('مالیات بر ارزش افزوده ۱۰٪')).toBeInTheDocument();
expect(screen.getByText(formatRial(net * 0.1))).toBeInTheDocument();
expect(screen.getAllByText(formatRial(net * 1.1)).length).toBeGreaterThan(0);
});
it('shows no tax row and charges the plain amount when tax is off', async () => {
mockApi(0);
await openChargeModalWith('100000');
expect(await screen.findByText(/پرداخت .* از طریق بانک ملت/)).toBeInTheDocument();
expect(screen.queryByText('اعتباری که به کیف پول اضافه می‌شود')).not.toBeInTheDocument();
expect(screen.getByText(new RegExp(formatRial(tomanToRial(100000))))).toBeInTheDocument();
});
});