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>
110 lines
5.4 KiB
PHP
110 lines
5.4 KiB
PHP
<?php
|
|
|
|
namespace App\Tests\Shared;
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
/**
|
|
* فهرستِ بازبینیشدهٔ جستوجوهای «uuid از درخواست → موجودیت محیطدار».
|
|
*
|
|
* سه نشتی این فاز همگی همین شکل را داشتند و هیچکدام در repository نبودند؛ در
|
|
* کنترلر و سرویس بودند، جایی که uuid از بدنه/کوئری میآید و کسی محیط را نمیسنجد.
|
|
* پس گارد هم باید همانجا باشد، نه روی DQLهای repository.
|
|
*
|
|
* قاعده: عدد هر فایل = تعداد findByUuid روی repositoryهای محیطدار در آن فایل، که
|
|
* همگی بازبینی شدهاند. اگر عدد عوض شود یعنی جستوجوی تازهای اضافه شده و باید
|
|
* ثابت شود محیطش بررسی میشود — با ownsRecord/ownsSection/ownedItem،
|
|
* TenantOwnershipChecker، یا لنگرزدن به ریشهای که خودش بررسی شده.
|
|
*
|
|
* @see \App\Shared\Tenant\TenantOwnershipChecker
|
|
* @see docs/architecture/tenancy.md
|
|
*/
|
|
class TenantLookupInventoryTest extends TestCase
|
|
{
|
|
/** repositoryهایی که موجودیت محیطدار یا فرزند aggregate برمیگردانند. */
|
|
private const TENANT_REPOSITORY_VARIABLES = [
|
|
'itemRepo', 'serviceItemRepo', 'sectionRepo', 'staffRepo',
|
|
'noteRepo', 'attachmentRepo', 'callRepo', 'messageRepo', 'medicalRepo',
|
|
'sessionRepo', 'sessionPaymentRepo', 'recordRepo',
|
|
'tenantInsuranceRepo', 'tenantTagRepo', 'packageRepo', 'ruleRepo',
|
|
];
|
|
|
|
/**
|
|
* فایل => تعداد جستوجوی بازبینیشده.
|
|
*
|
|
* @var array<string, int>
|
|
*/
|
|
private const REVIEWED = [
|
|
// AppointmentController دیگر جستوجوی بازبینینشده ندارد: assertServicesMatchContext
|
|
// حذف شد و مسیر ثبت نوبت هم مثل مسیر اسلات از ServiceBookingCalculator رد میشود،
|
|
// که خودش مالکیت محیط را میسنجد و پایینتر شمرده میشود.
|
|
// TenantOwnershipChecker::belongsToPair() روی هر uuid، پیش از هر بررسی دیگر
|
|
'src/Appointment/Service/ServiceBookingCalculator.php' => 2,
|
|
// TenantOwnershipChecker روی بخش/سرویس/پرسنل و فهرست سرویسها
|
|
'src/Appointment/Controller/MyAppointmentsController.php' => 1,
|
|
// ownsSession / getEntityType روی صورتحساب، پرونده و مطالبه
|
|
'src/Billing/Controller/BillingController.php' => 3,
|
|
// ownsSection ×۹ و مقایسهٔ مستقیم جفت ×۳ (پکیج، کالا، پرسنل)
|
|
'src/ClinicService/Controller/ClinicServiceController.php' => 8,
|
|
'src/Discount/Controller/DiscountController.php' => 1,
|
|
// قرارداد بیمه با جفت، و سرویس با getSection()->getEntityType()
|
|
'src/Insurance/Controller/InsuranceController.php' => 5,
|
|
// ownedItem() / ownedPackage()
|
|
'src/Inventory/Controller/InventoryController.php' => 2,
|
|
'src/Inventory/Service/InventoryService.php' => 1,
|
|
// ownsRecord ×۳۳ و دو پرداختِ لنگرخورده به مراجعهٔ بررسیشده
|
|
'src/Patient/Controller/PatientController.php' => 35,
|
|
// TenantOwnershipChecker در هر سه حلقه (قیمتگذاری، ساخت، ویرایش) + کالا
|
|
'src/Patient/Service/PatientService.php' => 5,
|
|
// ownsStaff()
|
|
'src/Staff/Controller/StaffController.php' => 2,
|
|
];
|
|
|
|
private function projectDir(): string
|
|
{
|
|
return dirname(__DIR__, 2);
|
|
}
|
|
|
|
/** @return array<string, int> */
|
|
private function countLookups(): array
|
|
{
|
|
$pattern = '/->(' . implode('|', self::TENANT_REPOSITORY_VARIABLES) . ')->findByUuid\(/';
|
|
$counts = [];
|
|
|
|
$files = new \RecursiveIteratorIterator(
|
|
new \RecursiveDirectoryIterator($this->projectDir() . '/src', \FilesystemIterator::SKIP_DOTS),
|
|
);
|
|
|
|
foreach ($files as $file) {
|
|
if ($file->getExtension() !== 'php') {
|
|
continue;
|
|
}
|
|
|
|
$found = preg_match_all($pattern, (string) file_get_contents($file->getPathname()));
|
|
if ($found > 0) {
|
|
$relative = str_replace($this->projectDir() . '/', '', $file->getPathname());
|
|
$counts[$relative] = $found;
|
|
}
|
|
}
|
|
|
|
ksort($counts);
|
|
|
|
return $counts;
|
|
}
|
|
|
|
public function testNoUnreviewedTenantLookupExists(): void
|
|
{
|
|
$actual = $this->countLookups();
|
|
$expected = self::REVIEWED;
|
|
ksort($expected);
|
|
|
|
self::assertSame($expected, $actual, <<<'MSG'
|
|
جستوجوی «uuid از درخواست → موجودیت محیطدار» تغییر کرده.
|
|
|
|
برای هر مورد تازه ثابت کن محیطش بررسی میشود (ownsRecord/ownsSection/
|
|
ownedItem، TenantOwnershipChecker، یا لنگر به ریشهٔ بررسیشده) و بعد عدد
|
|
همان فایل را در REVIEWED بهروز کن. اگر عدد کم شده، فقط عدد را کم کن.
|
|
MSG);
|
|
}
|
|
}
|