Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
6e2df1d
Add checkmark icon
mcraeteisha Feb 3, 2026
74a91e7
Add Cases Retention to Process Configurations
mcraeteisha Feb 3, 2026
5c9a8c3
Add case_retention_policy_enabled config
mcraeteisha Feb 3, 2026
bd0b9ca
Add warning icon
mcraeteisha Feb 4, 2026
816505b
Configure new kernal command for evaluating retention
sanjacornelius Feb 5, 2026
4a87002
Implement new job to run and delete cases
sanjacornelius Feb 5, 2026
5875f22
Implement EvaludateCasesRetention command
sanjacornelius Feb 5, 2026
1ee9291
Add modal on tier/period change
mcraeteisha Feb 5, 2026
2755edb
Merge pull request #8717 from ProcessMaker/task/FOUR-29105
sanjacornelius Feb 5, 2026
98ab8d8
Implement unit tests
sanjacornelius Feb 6, 2026
9db1e9a
Create caseNumber factory
sanjacornelius Feb 6, 2026
b1e3c39
Handle retention policy update deletions
sanjacornelius Feb 6, 2026
082163c
Remove todo
sanjacornelius Feb 6, 2026
eca7b57
Disable job if feature flag is not enabled
sanjacornelius Feb 6, 2026
f3d2573
Default to 6_month retention period for processes that do not have re…
sanjacornelius Feb 6, 2026
31d1720
Add warning and success modal
mcraeteisha Feb 6, 2026
dbc2536
set default retention period to 1 year
sanjacornelius Feb 10, 2026
958cbe5
remove unused import
sanjacornelius Feb 10, 2026
fb58fc3
Disable multiselect options by tier
mcraeteisha Feb 10, 2026
fdad4af
Add retention tiers to config
mcraeteisha Feb 10, 2026
dca5de8
Update test default retention period
sanjacornelius Feb 10, 2026
23c5f1a
Update EvaluateProcessRetentionJob.php
sanjacornelius Feb 10, 2026
09df9d8
Check if case retention policy is enabled before running job
sanjacornelius Feb 10, 2026
da5863a
fix truthy statement
sanjacornelius Feb 10, 2026
a24399f
fix issue with cached config
sanjacornelius Feb 10, 2026
dfa1502
Resolve failing tests: Cases not being deleted due to improper retent…
sanjacornelius Feb 11, 2026
888d7a9
CusorBot Fix: use subquery instead of loading all IDs into memory
sanjacornelius Feb 11, 2026
ebe3c70
Set default retention period
mcraeteisha Feb 12, 2026
72daa9a
Merge pull request #8721 from ProcessMaker/task/FOUR-29110
sanjacornelius Feb 12, 2026
3c269cc
Resolve modal showing 'confirm' screen on close
mcraeteisha Feb 12, 2026
a95f47f
Update README Documentation; add Case Retention Info
mcraeteisha Feb 12, 2026
9435866
Merge branch 'develop' into epic/FOUR-29101
sanjacornelius Feb 13, 2026
204d1a3
Merge branch 'epic/FOUR-29101' into task/FOUR-29107
mcraeteisha Feb 13, 2026
2e61155
Merge pull request #8727 from ProcessMaker/task/FOUR-29107
sanjacornelius Feb 17, 2026
2573e60
Merge pull request #8735 from ProcessMaker/task/FOUR-29106
sanjacornelius Feb 17, 2026
d7225c0
store retention_updated_by property
sanjacornelius Feb 18, 2026
b3397b7
Display updated_by details
sanjacornelius Feb 19, 2026
db48c03
Format the last modified date to the users datetime settings
sanjacornelius Feb 20, 2026
de2fd0b
Merge pull request #8736 from ProcessMaker/task/FOUR-29108
sanjacornelius Feb 20, 2026
a2e353d
add details logs of EvaluateProcessRetentionJob
sanjacornelius Feb 20, 2026
a1223b0
Update log messages for better clarity
sanjacornelius Feb 20, 2026
dc5ca0d
Prevent job from running on system & template processs
sanjacornelius Feb 20, 2026
1ff22c9
Resolve ambiguous id field
sanjacornelius Feb 20, 2026
e5525b1
Add tests to ensure job does not run on templates and system processes
sanjacornelius Feb 20, 2026
4148e83
CursorBot Fix: Use exists() instead of count() for better performance
sanjacornelius Feb 24, 2026
343b1f0
Cursor Bot Fix: Fix ineffective system categories/templates test
sanjacornelius Feb 24, 2026
13baad2
Delete cases_started and cases_participated
sanjacornelius Feb 25, 2026
af66e75
Update tests to assert deletion of cases_started and cases_participated
sanjacornelius Feb 25, 2026
4a0c48c
Update tests to ensure ALL assets are deleted during retention job ex…
sanjacornelius Mar 4, 2026
41f5474
Add array support for $caseNumbers
sanjacornelius Mar 4, 2026
67d3524
Add support for deleting all related assets; Utilize DeleteCasesRecor…
sanjacornelius Mar 4, 2026
09464d7
Update retention period formatting
mcraeteisha Mar 4, 2026
6a8acae
Move dispatchSavedSearchRecount() for accessibility
mcraeteisha Mar 4, 2026
0f870bf
Recount saved searches after process request deletion
mcraeteisha Mar 4, 2026
b976ed0
Merge pull request #8744 from ProcessMaker/task/FOUR-29112-B
sanjacornelius Mar 4, 2026
c08dc75
Merge branch 'epic/FOUR-29101' into task/FOUR-29113
sanjacornelius Mar 5, 2026
03695a0
PHPCS FIXER: Remove Space
sanjacornelius Mar 5, 2026
9579d01
Remove duplicate comments
sanjacornelius Mar 5, 2026
c8c44e1
Merge pull request #8740 from ProcessMaker/task/FOUR-29113
sanjacornelius Mar 5, 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
74 changes: 74 additions & 0 deletions ProcessMaker/Console/Commands/EvaluateCaseRetention.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

namespace ProcessMaker\Console\Commands;

use Illuminate\Console\Command;
use ProcessMaker\Jobs\EvaluateProcessRetentionJob;
use ProcessMaker\Models\Process;
use ProcessMaker\Models\ProcessCategory;

class EvaluateCaseRetention extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'cases:retention:evaluate';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Evaluate and delete cases past their retention period';

/**
* Execute the console command.
*/
public function handle()
{
// Only run if case retention policy is enabled
$enabled = config('app.case_retention_policy_enabled', false);
if (!$enabled) {
$this->info('Case retention policy is disabled');
$this->error('Skipping case retention evaluation');

return;
}

$this->info('Case retention policy is enabled');
$this->info('Dispatching retention evaluation jobs for all processes');

// Get system category IDs to exclude
$systemCategoryIds = ProcessCategory::where('is_system', true)->pluck('id');

// Exclude processes that are templates or in system categories
$jobCount = 0;
$query = Process::where('is_template', '!=', 1);

// Exclude processes in system categories
if ($systemCategoryIds->isNotEmpty()) {
$query->where(function ($q) use ($systemCategoryIds) {
$q->where(function ($subQuery) use ($systemCategoryIds) {
$subQuery->whereNotIn('process_category_id', $systemCategoryIds)
->orWhereNull('process_category_id');
});
})
->whereDoesntHave('categories', function ($q) use ($systemCategoryIds) {
// Exclude processes with any category assignment to system categories
$q->whereIn('process_categories.id', $systemCategoryIds);
});
}

$query->chunkById(100, function ($processes) use (&$jobCount) {
foreach ($processes as $process) {
dispatch(new EvaluateProcessRetentionJob($process->id));
$jobCount++;
}
});

$this->info("Dispatched {$jobCount} retention evaluation job(s) to the queue");
$this->info('Jobs will be processed asynchronously by queue workers');
}
}
7 changes: 7 additions & 0 deletions ProcessMaker/Console/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@ protected function schedule(Schedule $schedule)
break;
}

// evaluate cases retention policy
$schedule->command('cases:retention:evaluate')
->daily()
->onOneServer()
->withoutOverlapping()
->runInBackground();

// 5 minutes is recommended in https://laravel.com/docs/12.x/horizon#metrics
$schedule->command('horizon:snapshot')->everyFiveMinutes();
}
Expand Down
16 changes: 0 additions & 16 deletions ProcessMaker/Http/Controllers/Api/Actions/Cases/DeleteCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,20 +99,4 @@ private function getTaskDraftIds(array $tokenIds): array
->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']);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,30 @@

trait DeletesCaseRecords
{
private function deleteCasesStarted(string $caseNumber): void
private function deleteCasesStarted(string | array $caseNumbers): void
{
CaseStarted::query()
->where('case_number', $caseNumber)
->delete();
if (is_array($caseNumbers) && $caseNumbers !== []) {
CaseStarted::query()
->whereIn('case_number', $caseNumbers)
->delete();
} else {
CaseStarted::query()
->where('case_number', $caseNumbers)
->delete();
}
}

private function deleteCasesParticipated(string $caseNumber): void
private function deleteCasesParticipated(string | array $caseNumbers): void
{
CaseParticipated::query()
->where('case_number', $caseNumber)
->delete();
if (is_array($caseNumbers)) {
CaseParticipated::query()
->whereIn('case_number', $caseNumbers)
->delete();
} else {
CaseParticipated::query()
->where('case_number', $caseNumbers)
->delete();
}
}

private function deleteCaseNumbers(array $requestIds): void
Expand Down Expand Up @@ -183,11 +195,18 @@ private function deleteRequestMedia(array $requestIds): void
->delete();
}

private function deleteComments(string $caseNumber, array $requestIds, array $tokenIds): void
private function deleteComments(string | array $caseNumbers, array $requestIds, array $tokenIds): void
{
Comment::query()
->where('case_number', $caseNumber)
->orWhere(function ($query) use ($requestIds, $tokenIds) {
if (is_array($caseNumbers) && $caseNumbers !== []) {
$query = Comment::query()
->whereIn('case_number', $caseNumbers);
} else {
$query = Comment::query()
->where('case_number', $caseNumbers);
}

if ($requestIds !== [] || $tokenIds !== []) {
$query->orWhere(function ($query) use ($requestIds, $tokenIds) {
$query->where('commentable_type', ProcessRequest::class)
->whereIn('commentable_id', $requestIds);

Expand All @@ -197,8 +216,10 @@ private function deleteComments(string $caseNumber, array $requestIds, array $to
->whereIn('commentable_id', $tokenIds);
});
}
})
->delete();
});
}

$query->delete();
}

private function deleteNotifications(array $requestIds): void
Expand All @@ -220,4 +241,20 @@ private function deleteNotifications(array $requestIds): void
->whereIn('data->type', $notificationTypes)
->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']);
});
}
}
Loading
Loading