Everything that differs between specialties as data is already stored as data. What is left is behaviour — when a case opens, what happens once a session ends — so it becomes a TreatmentWorkflow resolved through a tagged-service registry. The booking path calls one collaborator and never names a specialty; adding dentistry is a new class, not an edit to confirmation. A clinic that has chosen no practice domain still gets working multi-session courses: DefaultTreatmentWorkflow answers for null and for any code without a dedicated implementation, keeping "unset means behave as today, not error". LaserTreatmentWorkflow is deliberately empty beyond claiming `beauty` — it is the seam where laser-specific behaviour will land without disturbing anyone else. Session due dates are anchored to the previous session's actual finish, so a patient who comes twenty days late shifts the rest of their course instead of getting the next session while it can still do nothing. Only the next session is recomputed; later ones keep their estimate because they are anchored to nothing yet. Attachment targets the first session without an appointment rather than the first open one: a patient booking again mid-course was otherwise matched to the session that already had a booking, and the second appointment went nowhere. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
70 lines
2.5 KiB
PHP
70 lines
2.5 KiB
PHP
<?php
|
|
|
|
namespace App\Tests\Treatment;
|
|
|
|
use App\PracticeDomain\Entity\PracticeDomain;
|
|
use App\Tests\ApiTestCase;
|
|
use App\Treatment\Workflow\DefaultTreatmentWorkflow;
|
|
use App\Treatment\Workflow\LaserTreatmentWorkflow;
|
|
use App\Treatment\Workflow\TreatmentWorkflowRegistry;
|
|
|
|
class TreatmentWorkflowTest extends ApiTestCase
|
|
{
|
|
private TreatmentWorkflowRegistry $registry;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
$this->registry = static::getContainer()->get(TreatmentWorkflowRegistry::class);
|
|
}
|
|
|
|
public function testBeautyResolvesToTheLaserWorkflow(): void
|
|
{
|
|
self::assertInstanceOf(LaserTreatmentWorkflow::class, $this->registry->for('beauty'));
|
|
}
|
|
|
|
/** حوزهای که هنوز پیادهسازی ندارد باید رفتار پیشفرض بگیرد، نه خطا. */
|
|
public function testUnknownDomainFallsBackToTheDefaultWorkflow(): void
|
|
{
|
|
self::assertSame(
|
|
DefaultTreatmentWorkflow::class,
|
|
$this->registry->for('physiotherapy')::class,
|
|
);
|
|
}
|
|
|
|
/** نال یعنی «حوزه تنظیم نشده» و همان رفتار امروز، نه خطا. */
|
|
public function testNullDomainFallsBackToTheDefaultWorkflow(): void
|
|
{
|
|
self::assertSame(DefaultTreatmentWorkflow::class, $this->registry->for(null)::class);
|
|
}
|
|
|
|
public function testHasDedicatedWorkflowDistinguishesTheTwo(): void
|
|
{
|
|
self::assertTrue($this->registry->hasDedicatedWorkflow('beauty'));
|
|
self::assertFalse($this->registry->hasDedicatedWorkflow('physiotherapy'));
|
|
self::assertFalse($this->registry->hasDedicatedWorkflow(null));
|
|
}
|
|
|
|
/** پنل ادمین باید ببیند کدام حوزه هنوز workflow ندارد. */
|
|
public function testListExposesHasWorkflow(): void
|
|
{
|
|
$suffix = bin2hex(random_bytes(4));
|
|
|
|
foreach ([['beauty_' . $suffix, false], ['beauty', true]] as [$code, $expected]) {
|
|
if ($this->em->getRepository(PracticeDomain::class)->findOneBy(['code' => $code]) === null) {
|
|
$this->em->persist(new PracticeDomain($code, 'حوزهٔ آزمون'));
|
|
}
|
|
}
|
|
$this->em->flush();
|
|
|
|
$admin = $this->createUser(['ROLE_USER', 'ROLE_ADMIN']);
|
|
$body = $this->authJson('GET', '/api/v1/practice-domains', $admin);
|
|
|
|
$byCode = array_column($body['data'], 'has_workflow', 'code');
|
|
|
|
self::assertTrue($byCode['beauty']);
|
|
self::assertFalse($byCode['beauty_' . $suffix]);
|
|
}
|
|
}
|