feat(treatment): open a treatment case with snapshotted areas and its sessions
Opening a case copies what must not move afterwards — the session count and the list of body areas, each with its category name — because a treatment record is a medical document and editing settings tomorrow must not rewrite what was done yesterday. The areas are the leaf categories under the service's own category: "توتال" contains bikini, leg and hand, and treatment happens on those three, not on the grouping node above them. A category with no children is its own single area, so "لیزر دست" gets one area rather than none. Every session in the course is created up front so that "session 5 of 8" has somewhere to live, but none of them is booked: creating eight real appointments would lock eight months of slots for a patient who may not attend session three. CategoryClosureResolver gains leaves(); the graph walk it already does is what tells a leaf from a grouping node, so this belongs next to descendants() rather than in a second traversal elsewhere. TreatmentCase and TreatmentSession carry no money field, and must not: billing lives on PatientSession, which is created when an appointment is confirmed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Treatment\Repository;
|
||||
|
||||
use App\Treatment\Entity\SessionAreaRecord;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<SessionAreaRecord>
|
||||
*/
|
||||
class SessionAreaRecordRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, SessionAreaRecord::class);
|
||||
}
|
||||
|
||||
public function findByUuid(string $uuid): ?SessionAreaRecord
|
||||
{
|
||||
return $this->findOneBy(['uuid' => $uuid]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Treatment\Repository;
|
||||
|
||||
use App\Treatment\Entity\TreatmentCaseArea;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<TreatmentCaseArea>
|
||||
*/
|
||||
class TreatmentCaseAreaRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, TreatmentCaseArea::class);
|
||||
}
|
||||
|
||||
public function findByUuid(string $uuid): ?TreatmentCaseArea
|
||||
{
|
||||
return $this->findOneBy(['uuid' => $uuid]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Treatment\Repository;
|
||||
|
||||
use App\ClinicService\Entity\ServiceItem;
|
||||
use App\Patient\Entity\PatientRecord;
|
||||
use App\Treatment\Entity\TreatmentCase;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<TreatmentCase>
|
||||
*/
|
||||
class TreatmentCaseRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, TreatmentCase::class);
|
||||
}
|
||||
|
||||
public function findByUuid(string $uuid): ?TreatmentCase
|
||||
{
|
||||
return $this->findOneBy(['uuid' => $uuid]);
|
||||
}
|
||||
|
||||
/**
|
||||
* پروندهٔ بازِ همین بیمار برای همین سرویس.
|
||||
*
|
||||
* نقطهٔ تصمیمِ «پروندهٔ دوم نساز»: بیماری که وسط دورهٔ لیزرش نوبت دیگری از همان
|
||||
* سرویس میگیرد، باید جلسهٔ همان دوره را بگیرد، نه یک دورهٔ موازی.
|
||||
*/
|
||||
public function findOpenFor(PatientRecord $record, ServiceItem $service): ?TreatmentCase
|
||||
{
|
||||
return $this->findOneBy([
|
||||
'patientRecord' => $record,
|
||||
'serviceItem' => $service,
|
||||
'status' => TreatmentCase::STATUS_ACTIVE,
|
||||
]);
|
||||
}
|
||||
|
||||
/** @return TreatmentCase[] */
|
||||
public function findForTenant(string $entityType, int $entityId, ?string $status = null): array
|
||||
{
|
||||
$qb = $this->createQueryBuilder('c')
|
||||
->where('c.entityType = :type')
|
||||
->andWhere('c.entityId = :id')
|
||||
->setParameter('type', $entityType)
|
||||
->setParameter('id', $entityId)
|
||||
->orderBy('c.openedAt', 'DESC');
|
||||
|
||||
if ($status !== null) {
|
||||
$qb->andWhere('c.status = :status')->setParameter('status', $status);
|
||||
}
|
||||
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace App\Treatment\Repository;
|
||||
|
||||
use App\Staff\Entity\ClinicStaff;
|
||||
use App\Treatment\Entity\TreatmentCase;
|
||||
use App\Treatment\Entity\TreatmentSession;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<TreatmentSession>
|
||||
*/
|
||||
class TreatmentSessionRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, TreatmentSession::class);
|
||||
}
|
||||
|
||||
public function findByUuid(string $uuid): ?TreatmentSession
|
||||
{
|
||||
return $this->findOneBy(['uuid' => $uuid]);
|
||||
}
|
||||
|
||||
/**
|
||||
* جلسهٔ بعدیِ یک دوره — اولین جلسهای که هنوز به وضعیت نهایی نرسیده.
|
||||
*
|
||||
* `no_show` هم برمیگردد: غیبت جلسه را نمیسوزاند و همان جلسه دوباره
|
||||
* برنامهریزی میشود.
|
||||
*/
|
||||
public function findNextOpen(TreatmentCase $case): ?TreatmentSession
|
||||
{
|
||||
return $this->createQueryBuilder('s')
|
||||
->where('s.treatmentCase = :case')
|
||||
->andWhere('s.status IN (:open)')
|
||||
->setParameter('case', $case)
|
||||
->setParameter('open', [
|
||||
TreatmentSession::STATUS_PLANNED,
|
||||
TreatmentSession::STATUS_BOOKED,
|
||||
TreatmentSession::STATUS_IN_PROGRESS,
|
||||
TreatmentSession::STATUS_NO_SHOW,
|
||||
])
|
||||
->orderBy('s.sessionNumber', 'ASC')
|
||||
->setMaxResults(1)
|
||||
->getQuery()
|
||||
->getOneOrNullResult();
|
||||
}
|
||||
|
||||
/** جلسهٔ انجامشدهٔ قبلی — لنگرِ محاسبهٔ سررسید جلسهٔ بعد. */
|
||||
public function findLastFinishedBefore(TreatmentCase $case, int $sessionNumber): ?TreatmentSession
|
||||
{
|
||||
return $this->createQueryBuilder('s')
|
||||
->where('s.treatmentCase = :case')
|
||||
->andWhere('s.sessionNumber < :number')
|
||||
->andWhere('s.status = :done')
|
||||
->andWhere('s.finishedAt IS NOT NULL')
|
||||
->setParameter('case', $case)
|
||||
->setParameter('number', $sessionNumber)
|
||||
->setParameter('done', TreatmentSession::STATUS_DONE)
|
||||
->orderBy('s.sessionNumber', 'DESC')
|
||||
->setMaxResults(1)
|
||||
->getQuery()
|
||||
->getOneOrNullResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* جلسات امروزِ یک پرسنل — از روی نوبتِ متصل، نه از روی سررسید تخمینی.
|
||||
*
|
||||
* @return TreatmentSession[]
|
||||
*/
|
||||
public function findTodayForStaff(ClinicStaff $staff, int $dayStart, int $dayEnd): array
|
||||
{
|
||||
return $this->createQueryBuilder('s')
|
||||
->join('s.appointment', 'a')
|
||||
->where('a.staff = :staff')
|
||||
->andWhere('a.slotStart >= :from')
|
||||
->andWhere('a.slotStart <= :to')
|
||||
->setParameter('staff', $staff)
|
||||
->setParameter('from', $dayStart)
|
||||
->setParameter('to', $dayEnd)
|
||||
->orderBy('a.slotStart', 'ASC')
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* صفِ «جلسات بدون نوبت» — جلسهای که سررسیدش رسیده و کسی رزروش نکرده.
|
||||
*
|
||||
* @return TreatmentSession[]
|
||||
*/
|
||||
public function findUnbookedDue(string $entityType, int $entityId, int $until): array
|
||||
{
|
||||
return $this->createQueryBuilder('s')
|
||||
->where('s.entityType = :type')
|
||||
->andWhere('s.entityId = :id')
|
||||
->andWhere('s.status IN (:open)')
|
||||
->andWhere('s.dueAt IS NOT NULL')
|
||||
->andWhere('s.dueAt <= :until')
|
||||
->setParameter('type', $entityType)
|
||||
->setParameter('id', $entityId)
|
||||
->setParameter('open', [TreatmentSession::STATUS_PLANNED, TreatmentSession::STATUS_NO_SHOW])
|
||||
->setParameter('until', $until)
|
||||
->orderBy('s.dueAt', 'ASC')
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user