66use Illuminate \Contracts \Queue \ShouldQueue ;
77use Illuminate \Foundation \Queue \Queueable ;
88use Illuminate \Support \Facades \Log ;
9+ use ProcessMaker \Http \Controllers \Api \Actions \Cases \DeletesCaseRecords ;
910use ProcessMaker \Models \CaseNumber ;
11+ use ProcessMaker \Models \CaseParticipated ;
12+ use ProcessMaker \Models \CaseStarted ;
1013use ProcessMaker \Models \Process ;
1114use ProcessMaker \Models \ProcessRequest ;
15+ use ProcessMaker \Models \ProcessRequestToken ;
16+ use ProcessMaker \Models \TaskDraft ;
1217
1318class EvaluateProcessRetentionJob implements ShouldQueue
1419{
15- use Queueable;
20+ use Queueable, DeletesCaseRecords ;
1621
1722 /**
1823 * Create a new job instance.
@@ -39,14 +44,14 @@ public function handle(): void
3944 return ;
4045 }
4146
42- // Default to 1_year if retention_period is not set
43- $ retentionPeriod = $ process ->properties ['retention_period ' ] ?? '1_year ' ;
47+ // Default to one_year if retention_period is not set
48+ $ retentionPeriod = $ process ->properties ['retention_period ' ] ?? 'one_year ' ;
4449 $ retentionMonths = match ($ retentionPeriod ) {
45- '6_months ' => 6 ,
46- '1_year ' => 12 ,
47- '3_years ' => 36 ,
48- '5_years ' => 60 ,
49- default => 12 , // Default to 1_year
50+ 'six_months ' => 6 ,
51+ 'one_year ' => 12 ,
52+ 'three_years ' => 36 ,
53+ 'five_years ' => 60 ,
54+ default => 12 , // Default to one_year
5055 };
5156
5257 // Default retention_updated_at to now if not set
@@ -78,28 +83,104 @@ public function handle(): void
7883 // Use subquery to get process request IDs
7984 $ processRequestSubquery = ProcessRequest::where ('process_id ' , $ this ->processId )->select ('id ' );
8085
86+ // Collect all ProcessRequest IDs that will be deleted (to delete them after all chunks are processed)
87+ $ processRequestIdsToDelete = [];
88+
8189 CaseNumber::whereIn ('process_request_id ' , $ processRequestSubquery )
82- ->where (function ($ query ) use ($ retentionUpdatedAt , $ oldCasesCutoff , $ newCasesCutoff ) {
83- // Cases created before retention_updated_at: delete if created before (retention_updated_at - retention_period)
84- $ query ->where (function ($ q ) use ($ retentionUpdatedAt , $ oldCasesCutoff ) {
85- $ q ->where ('created_at ' , '< ' , $ retentionUpdatedAt )
86- ->where ('created_at ' , '< ' , $ oldCasesCutoff );
87- })
88- // Cases created after retention_updated_at: delete if created before (now - retention_period)
89- ->orWhere (function ($ q ) use ($ retentionUpdatedAt , $ newCasesCutoff ) {
90- $ q ->where ('created_at ' , '>= ' , $ retentionUpdatedAt )
91- ->where ('created_at ' , '< ' , $ newCasesCutoff );
92- });
93- })
94- ->chunkById (100 , function ($ cases ) {
95- $ caseIds = $ cases ->pluck ('id ' );
96- // Delete the cases
90+ ->where ($ this ->buildRetentionQuery ($ retentionUpdatedAt , $ oldCasesCutoff , $ newCasesCutoff ))
91+ ->chunkById (100 , function ($ cases ) use (&$ processRequestIdsToDelete ) {
92+ $ caseIds = $ cases ->pluck ('id ' )->all ();
93+ $ processRequestIds = $ cases ->pluck ('process_request_id ' )->unique ()->all ();
94+
95+ // Collect ProcessRequest IDs for deletion after all chunks are processed
96+ $ processRequestIdsToDelete = array_merge ($ processRequestIdsToDelete , $ processRequestIds );
97+
98+ $ processRequestTokenIds = ProcessRequestToken::whereIn ('process_request_id ' , $ processRequestIds )->pluck ('id ' )->all ();
99+ $ draftIds = $ this ->getTaskDraftIds ($ processRequestTokenIds );
100+
101+ // uses case_number to delete
102+ $ this ->deleteCasesStarted ($ caseIds );
103+ $ this ->deleteCasesParticipated ($ caseIds );
104+ $ this ->deleteComments ($ caseIds , $ processRequestIds , $ processRequestTokenIds );
105+
106+ // Delete the CaseNumber records that were returned by the query (by their IDs)
97107 CaseNumber::whereIn ('id ' , $ caseIds )->delete ();
98108
109+ $ this ->deleteProcessRequestLocks ($ processRequestIds , $ processRequestTokenIds );
110+ $ this ->deleteInboxRuleLogs ($ processRequestTokenIds );
111+ $ this ->deleteInboxRules ($ processRequestTokenIds );
112+ $ this ->deleteProcessAbeRequestTokens ($ processRequestIds , $ processRequestTokenIds );
113+ $ this ->deleteScheduledTasks ($ processRequestIds , $ processRequestTokenIds );
114+ $ this ->deleteEllucianEthosSyncTasks ($ processRequestTokenIds );
115+
116+ $ this ->deleteTaskDraftMedia ($ draftIds );
117+ $ this ->deleteTaskDrafts ($ processRequestTokenIds );
118+
99119 // TODO: Add logs to track the number of cases deleted
100120 // Get deleted timestamp
101121 // $deletedAt = Carbon::now();
102122 // RetentionPolicyLog::record($process->id, $caseIds, $deletedAt);
103123 });
124+
125+ // Delete ProcessRequests after all chunks are processed
126+ // Only delete ProcessRequests that have no remaining cases
127+ if (!empty ($ processRequestIdsToDelete )) {
128+ $ processRequestIdsToDelete = array_unique ($ processRequestIdsToDelete );
129+
130+ // Filter to only ProcessRequests that have no remaining CaseNumbers
131+ $ processRequestIdsWithNoCases = array_filter ($ processRequestIdsToDelete , function ($ requestId ) {
132+ return !CaseNumber::where ('process_request_id ' , $ requestId )->exists ();
133+ });
134+
135+ if (!empty ($ processRequestIdsWithNoCases )) {
136+ $ this ->deleteProcessRequests ($ processRequestIdsWithNoCases );
137+
138+ // Delete any remaining related records
139+ $ this ->deleteRequestMedia ($ processRequestIdsWithNoCases );
140+ $ this ->deleteNotifications ($ processRequestIdsWithNoCases );
141+
142+ $ this ->dispatchSavedSearchRecount ();
143+ }
144+ }
145+ }
146+
147+ /**
148+ * Build a retention query closure that can be applied to any query builder.
149+ *
150+ * This method encapsulates the retention evaluation logic:
151+ * - Cases created before retention_updated_at: delete if created before (retention_updated_at - retention_period)
152+ * - Cases created after retention_updated_at: delete if created before (now - retention_period)
153+ *
154+ * @param Carbon $retentionUpdatedAt The date when the retention policy was updated
155+ * @param Carbon $oldCasesCutoff The cutoff date for cases created before retention_updated_at
156+ * @param Carbon $newCasesCutoff The cutoff date for cases created after retention_updated_at
157+ * @return \Closure A closure that applies the retention query to a query builder
158+ */
159+ private function buildRetentionQuery (Carbon $ retentionUpdatedAt , Carbon $ oldCasesCutoff , Carbon $ newCasesCutoff ): \Closure
160+ {
161+ return function ($ query ) use ($ retentionUpdatedAt , $ oldCasesCutoff , $ newCasesCutoff ) {
162+ // Cases created before retention_updated_at: delete if created before (retention_updated_at - retention_period)
163+ $ query ->where (function ($ q ) use ($ retentionUpdatedAt , $ oldCasesCutoff ) {
164+ $ q ->where ('created_at ' , '< ' , $ retentionUpdatedAt )
165+ ->where ('created_at ' , '< ' , $ oldCasesCutoff );
166+ })
167+ // Cases created after retention_updated_at: delete if created before (now - retention_period)
168+ ->orWhere (function ($ q ) use ($ retentionUpdatedAt , $ newCasesCutoff ) {
169+ $ q ->where ('created_at ' , '>= ' , $ retentionUpdatedAt )
170+ ->where ('created_at ' , '< ' , $ newCasesCutoff );
171+ });
172+ };
173+ }
174+
175+ private function getTaskDraftIds (array $ tokenIds ): array
176+ {
177+ if ($ tokenIds === []) {
178+ return [];
179+ }
180+
181+ return TaskDraft::query ()
182+ ->whereIn ('task_id ' , $ tokenIds )
183+ ->pluck ('id ' )
184+ ->all ();
104185 }
105186}
0 commit comments