feat(admin): build the last two screens, and pin spacing with a test
The cancellation policy page showed only the tenant policy, so nothing said which services opt out of it. Service policies do not blend with the tenant one — a service that has its own follows it completely — and without the table an operator cannot tell why one service's penalty differs. It lists them with a link to each service. The waitlist had the matches endpoint and no way to reach it. The list answers "who is waiting"; the question asked when capacity frees up is "who is waiting for this slot", so the page now takes a service and a date and answers that. The note says plainly that cancelling notifies them anyway — this is for looking before deciding, not a second notification path. Spacing is enforced at hold time rather than during candidate generation, which costs one slot being shown and then refused, and saves a patient-history query per candidate. That trade had no test; now a booking five days after the last one is refused and one thirty days later goes through. Checklists across all sixteen tasks are final: no pending rows, and the warnings that remain are recorded decisions — one resolver instead of six engines, a closed list instead of a registry, sample size three instead of ten — each with the reason it was taken. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -489,6 +489,62 @@ class PolicyEngineTest extends ApiTestCase
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* ⭐ قانون `spacing` در لحظهٔ **رزرو موقت** اجرا میشود، نه هنگام تولید کاندید.
|
||||
*
|
||||
* هزینهاش یک اسلات است که نمایش داده میشود و بعد رد میشود؛ سودش این است که
|
||||
* جستجوی وقت بهازای هر کاندید یک کوئری تاریخچهٔ بیمار نمیزند. این تست همان مرز را
|
||||
* پین میکند: نوبت نزدیک رد میشود، نوبت دور میگذرد.
|
||||
*/
|
||||
public function testSpacingRejectsABookingTooCloseToTheLastOne(): void
|
||||
{
|
||||
[$user, $section, $address] = $this->clinicWithBranch();
|
||||
$service = $this->service($section, 'لیزر', 20, 1_000_000);
|
||||
|
||||
$this->policy($user, [
|
||||
'category' => 'spacing',
|
||||
'name' => 'حداقل ۲۱ روز بین جلسات',
|
||||
'effects' => [['type' => 'min_days_between', 'value' => 21]],
|
||||
]);
|
||||
|
||||
$doctorUser = $this->createUser(['ROLE_USER', 'ROLE_DOCTOR']);
|
||||
$doctor = new \App\Doctor\Entity\Doctor($doctorUser, 'دکتر فاصله');
|
||||
$this->em->persist($doctor);
|
||||
$this->em->flush();
|
||||
|
||||
$patient = $this->createUser(['ROLE_USER']);
|
||||
$last = time() - 5 * 86400;
|
||||
|
||||
$previous = new \App\Appointment\Entity\Appointment($doctor, $patient, $last, $last + 1200);
|
||||
$previous->assignTenantPair($address->tenantEntityType(), $address->tenantEntityId());
|
||||
$previous->setServiceItem($this->em->getRepository(ServiceItem::class)->find($service->getId()));
|
||||
$previous->setAddressId($address->getId());
|
||||
$previous->setPatientName('بیمار فاصله');
|
||||
$previous->transitionTo(\App\Appointment\Entity\Appointment::STATUS_CONFIRMED);
|
||||
$this->em->persist($previous);
|
||||
$this->em->flush();
|
||||
|
||||
$guard = static::getContainer()->get(\App\Policy\Service\BookingPolicyGuard::class);
|
||||
|
||||
// پنج روز بعد از جلسهٔ قبلی → رد.
|
||||
$rejected = false;
|
||||
try {
|
||||
$guard->assertSpacing($patient, $service, $address, $last + 5 * 86400);
|
||||
} catch (\App\Shared\Exception\AppException $e) {
|
||||
$rejected = true;
|
||||
self::assertStringContainsString('۲۱', str_replace(
|
||||
['0','1','2','3','4','5','6','7','8','9'],
|
||||
['۰','۱','۲','۳','۴','۵','۶','۷','۸','۹'],
|
||||
$e->getMessage(),
|
||||
));
|
||||
}
|
||||
self::assertTrue($rejected, 'فاصلهٔ کمتر از قانون باید رد شود');
|
||||
|
||||
// سی روز بعد → میگذرد.
|
||||
$guard->assertSpacing($patient, $service, $address, $last + 30 * 86400);
|
||||
self::assertTrue(true);
|
||||
}
|
||||
|
||||
// ── جداسازی محیط ────────────────────────────────────────────────────────
|
||||
|
||||
public function testPolicyOfAnotherClinicIsNeitherVisibleNorApplied(): void
|
||||
|
||||
Reference in New Issue
Block a user