refactor(pricing): make the service the only price source
Price lists, annual tariffs and per-branch price overrides each answered
"what does this service cost?" differently, so a single date could carry
several answers and nobody could say which one was right. Price now lives
only on ServiceItem.price_rials, edited from the services page.
- drop PriceList/PriceListItem, their repositories and the seven
/api/v1/price-list(s) endpoints; PricingController keeps only quote and
the appointment price snapshot
- drop Tariff, TariffRepository, TariffService and the two
/service-items/{uuid}/tariffs endpoints; creating or repricing a service
no longer upserts a current-year tariff
- drop price_rials from ServiceBranchOverride; the entity stays for its
duration columns, which DurationCalculator and ServiceSelectionValidator
still read
- InvoiceService reads the item price directly
- PricingEngine collapses to a single source; breakdown.sources always
reports service_item, keeping the response contract intact
- remove the price-lists admin page, its route and settings-menu entry, the
tariff modal and the service detail tariffs tab; useAppointmentInvoice
moves to its own hook file
Migration drops price_lists, price_list_items, service_tariffs and the
override price column.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -45,13 +45,6 @@ const mockApi = (item: unknown = ITEM, notFound = false) => {
|
||||
if (url.includes('/service-item/')) {
|
||||
return notFound ? Promise.reject(new Error('یافت نشد')) : Promise.resolve({ success: true, data: item });
|
||||
}
|
||||
if (url.includes('/tariffs')) return Promise.resolve({ success: true, data: {
|
||||
current_year: 1404, default_price_rials: 8_500_000,
|
||||
data: [
|
||||
{ uuid: 't1', year: 1404, price_rials: 8_500_000, is_active: true },
|
||||
{ uuid: 't2', year: 1403, price_rials: 7_000_000, is_active: false },
|
||||
],
|
||||
} });
|
||||
if (url.includes('/tenant-insurances')) return Promise.resolve({ data: { data: [
|
||||
{ uuid: 'ins1', insurance_name: 'بیمه ایران', insurance_kind: 'basic', coverage_percent: 70 },
|
||||
] } });
|
||||
@@ -104,13 +97,12 @@ describe('ServiceDetailPage (جزئیات سرویس)', () => {
|
||||
expect(screen.getByText(/۱۴۰۲/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('تب تعرفهها فهرست سالها را با نشان «سال جاری» میآورد', async () => {
|
||||
/** تعرفهٔ سالانه حذف شد: قیمت فقط روی خودِ سرویس تعریف میشود. */
|
||||
it('دیگر تب تعرفهها ندارد', async () => {
|
||||
render();
|
||||
fireEvent.click(await screen.findByText('تعرفهها'));
|
||||
await screen.findByRole('heading', { name: 'سرم ۵۰۰cc' });
|
||||
|
||||
expect(await screen.findByText('سال جاری')).toBeInTheDocument();
|
||||
expect(screen.getByText('۱۴۰۴')).toBeInTheDocument();
|
||||
expect(screen.getByText('۱۴۰۳')).toBeInTheDocument();
|
||||
expect(screen.queryByText('تعرفهها')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('تب بیمهها قراردادها و درصد پوشش را میآورد', async () => {
|
||||
@@ -225,8 +217,8 @@ describe('ServiceDetailPage (جزئیات سرویس)', () => {
|
||||
|
||||
/** تب در URL مینشیند تا «بازگشت» و رفرش همان نما را بدهند. */
|
||||
it('تب انتخابشده از URL خوانده میشود', async () => {
|
||||
render('/admin/clinic-services/it1?tab=tariffs');
|
||||
render('/admin/clinic-services/it1?tab=insurance');
|
||||
|
||||
expect(await screen.findByText('سال جاری')).toBeInTheDocument();
|
||||
expect(await screen.findByText('بیمه ایران')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user