Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
01ed082
Add case delete endpoint and action with tests
eiresendez Jan 9, 2026
b862829
Remove case dependencies on delete and expand factories/tests
eiresendez Jan 12, 2026
642b34f
Document case delete endpoint in Swagger
eiresendez Jan 12, 2026
350548b
Fix delete cascades for inbox rules and process requests
eiresendez Jan 12, 2026
5172685
Merge pull request #8673 from ProcessMaker/story/FOUR-28685
eiresendez Jan 12, 2026
9a11a42
feat(cases): recount saved searches after case delete
eiresendez Jan 13, 2026
07a3894
Create CaseDeleted Event
mcraeteisha Jan 13, 2026
826c502
Listen for CaseDeletedEvent in EventServiceProvider
mcraeteisha Jan 13, 2026
c7b609e
Dispatch CaseDeleted; handle record deletion in trait
mcraeteisha Jan 13, 2026
a080159
Update comments
mcraeteisha Jan 13, 2026
5658b8d
Change case_title to name for priority in modal
mcraeteisha Jan 13, 2026
78dafca
Add testCaseDeleted test to SecurityLogsTest.php
mcraeteisha Jan 13, 2026
26a4700
Resolve caseNumber type mismatch
mcraeteisha Jan 14, 2026
ed4e52f
Add null fallback in getCaseTitle
mcraeteisha Jan 14, 2026
9cc9f51
Update getCaseTitle()
mcraeteisha Jan 14, 2026
0e8b89f
Update testCaseDeleted() in SecurityLogsTest.php
mcraeteisha Jan 14, 2026
82d37ad
Merge branch 'develop' into epic/FOUR-28600
eiresendez Jan 14, 2026
493489f
Merge pull request #8678 from ProcessMaker/story/FOUR-28723
mcraeteisha Jan 14, 2026
b0e70a2
delete notifications tied to case on delete and add test
eiresendez Jan 16, 2026
b5dd0ac
Merge pull request #8685 from ProcessMaker/observation/FOUR-28794
eiresendez Jan 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
282 changes: 282 additions & 0 deletions ProcessMaker/Http/Controllers/Api/Actions/Cases/DeleteCase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
<?php

namespace ProcessMaker\Http\Controllers\Api\Actions\Cases;

use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use ProcessMaker\Models\CaseNumber;
use ProcessMaker\Models\CaseParticipated;
use ProcessMaker\Models\CaseStarted;
use ProcessMaker\Models\Comment;
use ProcessMaker\Models\InboxRule;
use ProcessMaker\Models\InboxRuleLog;
use ProcessMaker\Models\Media;
use ProcessMaker\Models\ProcessAbeRequestToken;
use ProcessMaker\Models\ProcessRequest;
use ProcessMaker\Models\ProcessRequestLock;
use ProcessMaker\Models\ProcessRequestToken;
use ProcessMaker\Models\ScheduledTask;
use ProcessMaker\Models\TaskDraft;

class DeleteCase
{
public function __invoke(string $caseNumber): void
{
$requestIds = $this->getRequestIds($caseNumber);

if ($requestIds === []) {
abort(404);
}

$tokenIds = $this->getRequestTokenIds($requestIds);

DB::transaction(function () use ($caseNumber, $requestIds, $tokenIds) {
$this->deleteInboxRuleLogs($tokenIds);
$this->deleteInboxRules($tokenIds);
$this->deleteProcessRequestLocks($requestIds, $tokenIds);
$this->deleteProcessAbeRequestTokens($requestIds, $tokenIds);
$this->deleteScheduledTasks($requestIds, $tokenIds);
$this->deleteEllucianEthosSyncTasks($tokenIds);
$draftIds = $this->getTaskDraftIds($tokenIds);
$this->deleteTaskDraftMedia($draftIds);
$this->deleteTaskDrafts($tokenIds);
$this->deleteComments($caseNumber, $requestIds, $tokenIds);
$this->deleteRequestMedia($requestIds);
$this->deleteCaseNumbers($requestIds);
$this->deleteCasesStarted($caseNumber);
$this->deleteCasesParticipated($caseNumber);
$this->deleteProcessRequestTokens($requestIds);
$this->deleteProcessRequests($requestIds);
});

$this->dispatchSavedSearchRecount();
}

private function getRequestIds(string $caseNumber): array
{
return ProcessRequest::query()
->where('case_number', $caseNumber)
->pluck('id')
->all();
}

private function getRequestTokenIds(array $requestIds): array
{
if ($requestIds === []) {
return [];
}

return ProcessRequestToken::query()
->whereIn('process_request_id', $requestIds)
->pluck('id')
->all();
}

private function getTaskDraftIds(array $tokenIds): array
{
if ($tokenIds === []) {
return [];
}

return TaskDraft::query()
->whereIn('task_id', $tokenIds)
->pluck('id')
->all();
}

private function deleteCasesStarted(string $caseNumber): void
{
CaseStarted::query()
->where('case_number', $caseNumber)
->delete();
}

private function deleteCasesParticipated(string $caseNumber): void
{
CaseParticipated::query()
->where('case_number', $caseNumber)
->delete();
}

private function deleteCaseNumbers(array $requestIds): void
{
if ($requestIds === []) {
return;
}

CaseNumber::query()
->whereIn('process_request_id', $requestIds)
->delete();
}

private function deleteProcessRequests(array $requestIds): void
{
if ($requestIds === []) {
return;
}

ProcessRequest::query()
->whereIn('id', $requestIds)
->get()
->each
->delete();
}

private function deleteProcessRequestTokens(array $requestIds): void
{
if ($requestIds === []) {
return;
}

ProcessRequestToken::query()
->whereIn('process_request_id', $requestIds)
->delete();
}

private function deleteProcessRequestLocks(array $requestIds, array $tokenIds): void
{
ProcessRequestLock::query()
->whereIn('process_request_id', $requestIds)
->delete();

if ($tokenIds !== []) {
ProcessRequestLock::query()
->whereIn('process_request_token_id', $tokenIds)
->delete();
}
}

private function deleteProcessAbeRequestTokens(array $requestIds, array $tokenIds): void
{
ProcessAbeRequestToken::query()
->whereIn('process_request_id', $requestIds)
->delete();

if ($tokenIds !== []) {
ProcessAbeRequestToken::query()
->whereIn('process_request_token_id', $tokenIds)
->delete();
}
}

private function deleteScheduledTasks(array $requestIds, array $tokenIds): void
{
ScheduledTask::query()
->whereIn('process_request_id', $requestIds)
->delete();

if ($tokenIds !== []) {
ScheduledTask::query()
->whereIn('process_request_token_id', $tokenIds)
->delete();
}
}

private function deleteInboxRules(array $tokenIds): void
{
if ($tokenIds === []) {
return;
}

InboxRule::query()
->whereIn('process_request_token_id', $tokenIds)
->get()
->each
->delete();
}

private function deleteInboxRuleLogs(array $tokenIds): void
{
if ($tokenIds === []) {
return;
}

InboxRuleLog::query()
->whereIn('process_request_token_id', $tokenIds)
->delete();
}

private function deleteEllucianEthosSyncTasks(array $tokenIds): void
{
if ($tokenIds === [] || !Schema::hasTable('ellucian_ethos_sync_global_task_list')) {
return;
}

DB::table('ellucian_ethos_sync_global_task_list')
->whereIn('process_request_token_id', $tokenIds)
->delete();
}

private function deleteTaskDrafts(array $tokenIds): void
{
if ($tokenIds === []) {
return;
}

TaskDraft::query()
->whereIn('task_id', $tokenIds)
->delete();
}

private function deleteTaskDraftMedia(array $draftIds): void
{
if ($draftIds === []) {
return;
}

Media::query()
->where('model_type', TaskDraft::class)
->whereIn('model_id', $draftIds)
->get()
->each
->delete();
}

private function deleteRequestMedia(array $requestIds): void
{
if ($requestIds === []) {
return;
}

Media::query()
->where('model_type', ProcessRequest::class)
->whereIn('model_id', $requestIds)
->get()
->each
->delete();
}

private function deleteComments(string $caseNumber, array $requestIds, array $tokenIds): void
{
Comment::query()
->where('case_number', $caseNumber)
->orWhere(function ($query) use ($requestIds, $tokenIds) {
$query->where('commentable_type', ProcessRequest::class)
->whereIn('commentable_id', $requestIds);

if ($tokenIds !== []) {
$query->orWhere(function ($nestedQuery) use ($tokenIds) {
$nestedQuery->where('commentable_type', ProcessRequestToken::class)
->whereIn('commentable_id', $tokenIds);
});
}
})
->delete();
}

private function dispatchSavedSearchRecount(): void
{
if (!config('savedsearch.count', false)) {
return;
}

$jobClass = 'ProcessMaker\\Package\\SavedSearch\\Jobs\\RecountAllSavedSearches';
if (!class_exists($jobClass)) {
return;
}

DB::afterCommit(static function () use ($jobClass): void {
$jobClass::dispatch(['request', 'task']);
});
}
}
58 changes: 52 additions & 6 deletions ProcessMaker/Http/Controllers/Api/CaseController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace ProcessMaker\Http\Controllers\Api;

use Illuminate\Http\JsonResponse;
use ProcessMaker\Http\Controllers\Api\Actions\Cases\DeleteCase;
use ProcessMaker\Http\Controllers\Controller;
use ProcessMaker\Models\Process;
use ProcessMaker\Models\ProcessRequest;
Expand All @@ -12,7 +14,7 @@ class CaseController extends Controller
/**
* Get stage information for cases
*/
public function getStagePerCase($case_number = null)
public function getStagePerCase(?string $case_number = null): JsonResponse
{
if (!empty($case_number)) {
$responseData = $this->getSpecificCaseStages($case_number);
Expand All @@ -31,12 +33,56 @@ public function getStagePerCase($case_number = null)
return response()->json($responseData);
}

/**
* Delete a case and its related requests.
*
* @param string $case_number
* @return JsonResponse
*
* @OA\Delete(
* path="/cases/{case_number}",
* summary="Delete a case and its related requests",
* operationId="deleteCase",
* tags={"Cases"},
* @OA\Parameter(
* description="Case number to delete",
* in="path",
* name="case_number",
* required=true,
* @OA\Schema(type="string")
* ),
* @OA\Response(
* response=204,
* description="success"
* ),
* @OA\Response(
* response=401,
* description="Unauthorized"
* ),
* @OA\Response(response=404, ref="#/components/responses/404"),
* @OA\Response(
* response=409,
* description="Conflict"
* ),
* @OA\Response(
* response=500,
* description="Internal Server Error"
* ),
* )
*/
public function destroy(string $case_number): JsonResponse
{
(new DeleteCase)($case_number);

return response()->json([], 204);
}

/**
* Get specific case stages information
* @param string $caseNumber The unique identifier of the case to retrieve stages for
* @return array
*/
private function getSpecificCaseStages($caseNumber)
private function getSpecificCaseStages(string $caseNumber): array
{
$allRequests = ProcessRequest::where('case_number', $caseNumber)->get();
// Check if any requests were found
Expand Down Expand Up @@ -75,7 +121,7 @@ private function getSpecificCaseStages($caseNumber)
* @param string|null $status The status to set for the stages
* @return array
*/
private function getDefaultCaseStages($status = null)
private function getDefaultCaseStages(?string $status = null): array
{
return [
[
Expand All @@ -100,7 +146,7 @@ private function getDefaultCaseStages($status = null)
* @param string $stageName The name of the stage ('In Progress' or 'Completed')
* @return string The mapped status
*/
private function mapStatus($status, $stageName)
private function mapStatus(?string $status, string $stageName): string
{
if ($status === 'COMPLETED') {
return 'Done';
Expand All @@ -120,11 +166,11 @@ private function mapStatus($status, $stageName)
/**
* Get the stages summary based on the provided request.
*
* @param $requestId
* @param ProcessRequest $request
* @return array An array of stage results, each containing the stage ID, name, status,
* and completion date.
*/
private function getStagesSummary(ProcessRequest $request)
private function getStagesSummary(ProcessRequest $request): array
{
$requestId = $request->id;
$processId = $request->process_id;
Expand Down
Loading
Loading