fix(services): forbid deleting service items/sections — deactivate only
Services are referenced by appointments, sessions, invoices and payment history,
so deleting one orphans/corrupts those records (deleting a section cascaded to
its services too). Make deletion impossible:
- Backend: DELETE /service-item/{uuid} and DELETE /service-section/{uuid} now
always return 409 (ERR_SERVICE_ITEM_IN_USE) with a message pointing to
deactivate; no rows are touched. Deactivate stays via PATCH active=false.
- Frontend: removed the section delete button, its confirm dialog, the delete
mutation, and the now-unused delete state/flag/icon from ClinicServicesPage.
Section and item deactivate toggles are unchanged.
Tests: ServiceItemDeleteCleanupTest rewritten — delete of item and section both
rejected (409) and the row survives. docs/api/clinic-services.md updated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -212,18 +212,14 @@ class ClinicServiceController extends BaseController
|
||||
#[Route('/api/v1/service-section/{uuid}', methods: ['DELETE'])]
|
||||
public function deleteSection(string $uuid, #[CurrentUser] User $user): JsonResponse
|
||||
{
|
||||
$this->denyServices($user, 'delete');
|
||||
[$entityType, $entityId] = $this->resolveEntity($user);
|
||||
$this->assertServicesGate($entityType, $entityId);
|
||||
|
||||
$section = $this->sectionRepo->findByUuid($uuid);
|
||||
if ($section === null || !$this->ownsSection($section, $entityType, $entityId)) {
|
||||
return $this->error(ErrorCodes::ERR_SERVICE_NOT_FOUND, ErrorCodes::message(ErrorCodes::ERR_SERVICE_NOT_FOUND), 404);
|
||||
}
|
||||
|
||||
$this->sectionRepo->remove($section);
|
||||
|
||||
return $this->success(['message' => 'بخش حذف شد']);
|
||||
// حذف بخش مجاز نیست: حذفِ آن سرویسهای زیرمجموعه را هم پاک میکرد و سوابق
|
||||
// پرداخت/فاکتور به همان سرویسها ارجاع دارند. فقط غیرفعالکردن مجاز است
|
||||
// (PATCH active=false).
|
||||
return $this->error(
|
||||
ErrorCodes::ERR_SERVICE_ITEM_IN_USE,
|
||||
'حذف بخش ممکن نیست؛ برای حفظ سوابق پرداخت فقط میتوانید آن را غیرفعال کنید.',
|
||||
409
|
||||
);
|
||||
}
|
||||
|
||||
// ── Service Items ────────────────────────────────────────────────────────
|
||||
@@ -407,29 +403,15 @@ class ClinicServiceController extends BaseController
|
||||
#[Route('/api/v1/service-item/{uuid}', methods: ['DELETE'])]
|
||||
public function deleteItem(string $uuid, #[CurrentUser] User $user): JsonResponse
|
||||
{
|
||||
$this->denyServices($user, 'delete');
|
||||
[$entityType, $entityId] = $this->resolveEntity($user);
|
||||
|
||||
$item = $this->itemRepo->findByUuid($uuid);
|
||||
if ($item === null || !$this->ownsSection($item->getSection(), $entityType, $entityId)) {
|
||||
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) {
|
||||
return $this->error(ErrorCodes::ERR_SERVICE_ITEM_IN_USE, ErrorCodes::message(ErrorCodes::ERR_SERVICE_ITEM_IN_USE), 409);
|
||||
}
|
||||
|
||||
return $this->success(['message' => 'سرویس حذف شد']);
|
||||
// حذف سرویس مجاز نیست: نوبتها، جلسات، فاکتورها و سوابق پرداخت به سرویس
|
||||
// ارجاع دارند و حذف آنها را یتیم/ناسازگار میکرد. فقط غیرفعالکردن مجاز است
|
||||
// (PATCH active=false) — سرویسِ غیرفعال در پذیرش جدید نمایش داده نمیشود ولی
|
||||
// سوابق حفظ میشوند.
|
||||
return $this->error(
|
||||
ErrorCodes::ERR_SERVICE_ITEM_IN_USE,
|
||||
'حذف سرویس ممکن نیست؛ برای حفظ سوابق پرداخت فقط میتوانید آن را غیرفعال کنید.',
|
||||
409
|
||||
);
|
||||
}
|
||||
|
||||
// ── Tariffs (تعرفهی نسخهدار سالانه) ──────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user