Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 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
ead6e29
fix(cases): guard missing case requests before rendering detail
eiresendez Jan 20, 2026
d6d9947
Merge pull request #8693 from ProcessMaker/observation/FOUR-28838
eiresendez Jan 21, 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
65 changes: 65 additions & 0 deletions ProcessMaker/Events/CaseDeleted.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

namespace ProcessMaker\Events;

use Carbon\Carbon;
use Illuminate\Foundation\Events\Dispatchable;
use ProcessMaker\Contracts\SecurityLogEventInterface;
use ProcessMaker\Traits\FormatSecurityLogChanges;

class CaseDeleted implements SecurityLogEventInterface
{
use Dispatchable;
use FormatSecurityLogChanges;

private string $caseNumber;

private string $caseTitle;

/**
* Create a new event instance.
*
* @return void
*/
public function __construct(string $caseNumber, string $caseTitle)
{
$this->caseNumber = $caseNumber;
$this->caseTitle = $caseTitle;
}

/**
* Get specific data related to the event
*
* @return array
*/
public function getData(): array
{
return [
'name' => $this->caseTitle,
'case_number' => $this->caseNumber,
'deleted_at' => Carbon::now(),
];
}

/**
* Get specific data related to the event
*
* @return array
*/
public function getChanges(): array
{
return [
'case_number' => $this->caseNumber,
];
}

/**
* Get the Event name
*
* @return string
*/
public function getEventName(): string
{
return 'CaseDeleted';
}
}
118 changes: 118 additions & 0 deletions ProcessMaker/Http/Controllers/Api/Actions/Cases/DeleteCase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<?php

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

use Illuminate\Support\Facades\DB;
use ProcessMaker\Events\CaseDeleted;
use ProcessMaker\Models\CaseStarted;
use ProcessMaker\Models\ProcessRequest;
use ProcessMaker\Models\ProcessRequestToken;
use ProcessMaker\Models\TaskDraft;

class DeleteCase
{
use DeletesCaseRecords;

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

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

$caseTitle = $this->getCaseTitle($caseNumber);
$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->deleteNotifications($requestIds);
$this->deleteRequestMedia($requestIds);
$this->deleteCaseNumbers($requestIds);
$this->deleteCasesStarted($caseNumber);
$this->deleteCasesParticipated($caseNumber);
$this->deleteProcessRequestTokens($requestIds);
$this->deleteProcessRequests($requestIds);
});

CaseDeleted::dispatch($caseNumber, $caseTitle);

$this->dispatchSavedSearchRecount();
}

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

private function getCaseTitle(string $caseNumber): string
{
$caseStarted = CaseStarted::query()
->where('case_number', $caseNumber)
->first();

if ($caseStarted) {
return $caseStarted->case_title ?? "Case #{$caseNumber}";
} else {
// If CaseStarted doesn't exist, get case title from the first ProcessRequest
$firstRequest = ProcessRequest::query()
->where('case_number', $caseNumber)
->whereNull('parent_request_id')
->first();

return $firstRequest?->case_title ?? "Case #{$caseNumber}";
}
}

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 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']);
});
}
}
Loading
Loading