fix(db): purge insurance config when a doctor/clinic is deleted (H5)
tenant_insurances, entity_insurance_pricing and tenant_service_coverages reference their owner through a polymorphic (entity_type, entity_id) pair, so no database FK can cascade their cleanup. Hard-deleting a doctor (DoctorController) or clinic (AdminApiController) left these rows orphaned. Add TenantInsuranceCleanupService::purgeForEntity() and call it from both delete paths — removes coverage (via owning tenant_insurance ids), then tenant insurances, then pricing. Residual (separate, lower-freq paths): deleting an insurance category or a service_item still orphans rows that reference them by id — tracked under the medium-tier soft-ref findings. Regression: tests/Insurance/TenantInsuranceCleanupTest. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -34,6 +34,7 @@ class AdminApiController extends BaseController
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $em,
|
||||
private readonly \App\Appointment\Service\SlotCalculatorService $slotCalculator,
|
||||
private readonly \App\Insurance\Service\TenantInsuranceCleanupService $insuranceCleanup,
|
||||
) {}
|
||||
|
||||
// ── Users ─────────────────────────────────────────────────────────────────
|
||||
@@ -553,6 +554,7 @@ class AdminApiController extends BaseController
|
||||
$clinic = $this->em->getRepository(Clinic::class)->findOneBy(['uuid' => $uuid]);
|
||||
if (!$clinic) return $this->error('CLINIC_NOT_FOUND', 'کلینیک یافت نشد', 404);
|
||||
|
||||
$this->insuranceCleanup->purgeForEntity(\App\Insurance\Entity\TenantInsurance::TYPE_CLINIC, $clinic->getId());
|
||||
$this->em->remove($clinic);
|
||||
$this->em->flush();
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ namespace App\Doctor\Controller;
|
||||
|
||||
use App\Appointment\Repository\WeeklyScheduleRepository;
|
||||
use App\Auth\Entity\User;
|
||||
use App\Insurance\Entity\TenantInsurance;
|
||||
use App\Insurance\Service\TenantInsuranceCleanupService;
|
||||
use App\Auth\Repository\UserRepository;
|
||||
use App\Clinic\Entity\Clinic;
|
||||
use App\Clinic\Repository\ClinicRepository;
|
||||
@@ -39,6 +41,7 @@ class DoctorController extends BaseController
|
||||
private readonly UserRepository $userRepo,
|
||||
private readonly FileValidatorService $fileValidator,
|
||||
private readonly WeeklyScheduleRepository $scheduleRepo,
|
||||
private readonly TenantInsuranceCleanupService $insuranceCleanup,
|
||||
private readonly string $projectDir,
|
||||
) {}
|
||||
|
||||
@@ -369,6 +372,7 @@ class DoctorController extends BaseController
|
||||
return $this->error(ErrorCodes::ERR_VALIDATION_002, 'دکتر یافت نشد', 404);
|
||||
}
|
||||
|
||||
$this->insuranceCleanup->purgeForEntity(TenantInsurance::TYPE_DOCTOR, $doctor->getId());
|
||||
$this->doctorRepo->remove($doctor);
|
||||
return $this->success(['message' => 'دکتر با موفقیت حذف شد']);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Insurance\Service;
|
||||
|
||||
use App\Insurance\Entity\EntityInsurancePricing;
|
||||
use App\Insurance\Entity\TenantInsurance;
|
||||
use App\Insurance\Entity\TenantServiceCoverage;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
/**
|
||||
* Removes the insurance configuration a doctor/clinic owns when that entity is
|
||||
* deleted. The owning columns (entity_type, entity_id) are polymorphic
|
||||
* (doctor|clinic), so no database FK can cascade this — the cleanup is enforced
|
||||
* here in the application. Coverage rows are removed via their owning
|
||||
* tenant_insurance ids.
|
||||
*/
|
||||
final class TenantInsuranceCleanupService
|
||||
{
|
||||
public function __construct(private readonly EntityManagerInterface $em) {}
|
||||
|
||||
public function purgeForEntity(string $entityType, int $entityId): void
|
||||
{
|
||||
$tenantInsuranceIds = $this->em->createQuery(
|
||||
'SELECT t.id FROM ' . TenantInsurance::class . ' t
|
||||
WHERE t.entityType = :type AND t.entityId = :id'
|
||||
)->setParameter('type', $entityType)
|
||||
->setParameter('id', $entityId)
|
||||
->getSingleColumnResult();
|
||||
|
||||
if ($tenantInsuranceIds !== []) {
|
||||
$this->em->createQuery(
|
||||
'DELETE ' . TenantServiceCoverage::class . ' c
|
||||
WHERE c.tenantInsuranceId IN (:ids)'
|
||||
)->setParameter('ids', $tenantInsuranceIds)->execute();
|
||||
}
|
||||
|
||||
$this->em->createQuery(
|
||||
'DELETE ' . TenantInsurance::class . ' t
|
||||
WHERE t.entityType = :type AND t.entityId = :id'
|
||||
)->setParameter('type', $entityType)
|
||||
->setParameter('id', $entityId)
|
||||
->execute();
|
||||
|
||||
$this->em->createQuery(
|
||||
'DELETE ' . EntityInsurancePricing::class . ' p
|
||||
WHERE p.entityType = :type AND p.entityId = :id'
|
||||
)->setParameter('type', $entityType)
|
||||
->setParameter('id', $entityId)
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user