fix(db): purge service-item config rows on delete (M20)

Tariff and TenantServiceCoverage reference a service item by a raw int (no FK),
so deleting an item orphaned its tariffs and tenant-coverage config. Delete them
in deleteItem() before removing the item. (The in-use FK guard for invoice/claim
usage is preserved.)

Remaining M20 refs (ClinicStaff/SmsWallet/DoctorAddress.clinicId/Claim.insurance_id
on rare owner deletions) are accepted as harmless unreferenced rows; SmsWallet is
intentionally retained as a financial record. Documented in docs/audit-backlog.md.

Regression: tests/ClinicService/ServiceItemDeleteCleanupTest.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
hamed
2026-06-28 21:17:30 +03:30
co-authored by Claude Opus 4.8
parent 4b80616a17
commit b1aaaf3d55
3 changed files with 57 additions and 1 deletions
@@ -5,6 +5,9 @@ namespace App\ClinicService\Controller;
use App\Auth\Entity\User;
use App\ClinicService\Entity\ServiceItem;
use App\ClinicService\Entity\ServiceSection;
use App\Insurance\Entity\TenantServiceCoverage;
use App\ClinicService\Entity\Tariff;
use Doctrine\ORM\EntityManagerInterface;
use App\ClinicService\Repository\ServiceItemRepository;
use App\ClinicService\Repository\ServiceSectionRepository;
use App\ClinicService\Repository\TariffRepository;
@@ -36,6 +39,7 @@ class ClinicServiceController extends BaseController
private readonly ClinicRepository $clinicRepo,
private readonly TariffRepository $tariffRepo,
private readonly TariffService $tariffService,
private readonly EntityManagerInterface $em,
) {}
// ── Service Sections ─────────────────────────────────────────────────────
@@ -230,6 +234,14 @@ class ClinicServiceController extends BaseController
return $this->error(ErrorCodes::ERR_SERVICE_NOT_FOUND, ErrorCodes::message(ErrorCodes::ERR_SERVICE_NOT_FOUND), 404);
}
// Tariff and tenant-coverage rows reference the item by a raw int (no FK),
// so they would orphan on delete. Remove the item's config rows first.
$itemId = $item->getId();
$this->em->createQuery('DELETE FROM ' . Tariff::class . ' t WHERE t.serviceItemId = :id')
->setParameter('id', $itemId)->execute();
$this->em->createQuery('DELETE FROM ' . TenantServiceCoverage::class . ' c WHERE c.serviceItemId = :id')
->setParameter('id', $itemId)->execute();
try {
$this->itemRepo->remove($item);
} catch (\Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException) {