diff --git a/ProcessMaker/Http/Controllers/Api/Actions/Cases/DeleteCase.php b/ProcessMaker/Http/Controllers/Api/Actions/Cases/DeleteCase.php new file mode 100644 index 0000000000..25e0a00079 --- /dev/null +++ b/ProcessMaker/Http/Controllers/Api/Actions/Cases/DeleteCase.php @@ -0,0 +1,46 @@ +where('case_number', $caseNumber) + ->pluck('id') + ->all(); + + if ($requestIds === []) { + abort(404); + } + + DB::transaction(function () use ($caseNumber, $requestIds) { + CaseStarted::query() + ->where('case_number', $caseNumber) + ->delete(); + + CaseParticipated::query() + ->where('case_number', $caseNumber) + ->delete(); + + CaseNumber::query() + ->whereIn('process_request_id', $requestIds) + ->delete(); + + ProcessRequest::query() + ->whereIn('id', $requestIds) + ->delete(); + }); + } +} diff --git a/ProcessMaker/Http/Controllers/Api/CaseController.php b/ProcessMaker/Http/Controllers/Api/CaseController.php index caaacfc14d..574f13bf04 100644 --- a/ProcessMaker/Http/Controllers/Api/CaseController.php +++ b/ProcessMaker/Http/Controllers/Api/CaseController.php @@ -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; @@ -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); @@ -31,12 +33,44 @@ 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=404, ref="#/components/responses/404"), + * ) + */ + 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 @@ -75,7 +109,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 [ [ @@ -100,7 +134,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'; @@ -120,11 +154,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; diff --git a/database/factories/ProcessMaker/Models/ProcessRequestFactory.php b/database/factories/ProcessMaker/Models/ProcessRequestFactory.php index 9c9ab875b3..5a4d2f7464 100644 --- a/database/factories/ProcessMaker/Models/ProcessRequestFactory.php +++ b/database/factories/ProcessMaker/Models/ProcessRequestFactory.php @@ -47,4 +47,20 @@ public function definition() }, ]; } + + public function withCaseNumber(int $caseNumber): self + { + $caseTitle = $this->faker->words(4, true); + + return $this->state([ + 'case_number' => $caseNumber, + 'case_title' => $caseTitle, + 'case_title_formatted' => $caseTitle, + ])->afterCreating(function (ProcessRequest $request) use ($caseNumber, $caseTitle) { + $request->case_number = $caseNumber; + $request->case_title = $caseTitle; + $request->case_title_formatted = $caseTitle; + $request->save(); + }); + } } diff --git a/routes/api.php b/routes/api.php index 2b3ffc38bb..04a8c96f5b 100644 --- a/routes/api.php +++ b/routes/api.php @@ -235,6 +235,7 @@ // Cases Route::get('cases/stages_bar/{case_number?}', [CaseController::class, 'getStagePerCase'])->name('cases.stage'); + Route::delete('cases/{case_number}', [CaseController::class, 'destroy'])->name('cases.destroy'); // TaskDrafts Route::put('drafts/{task}', [TaskDraftController::class, 'update'])->name('taskdraft.update'); diff --git a/tests/Feature/Api/CaseDeleteTest.php b/tests/Feature/Api/CaseDeleteTest.php new file mode 100644 index 0000000000..da4e6689ed --- /dev/null +++ b/tests/Feature/Api/CaseDeleteTest.php @@ -0,0 +1,47 @@ +count(2) + ->withCaseNumber($caseNumber) + ->create(); + + CaseNumber::query()->create(['process_request_id' => $requests->first()->id]); + CaseNumber::query()->create(['process_request_id' => $requests->last()->id]); + CaseStarted::factory()->create(['case_number' => $caseNumber]); + CaseParticipated::factory()->create(['case_number' => $caseNumber]); + + $response = $this->apiCall('DELETE', route('api.cases.destroy', ['case_number' => $caseNumber])); + + $response->assertStatus(204); + $this->assertDatabaseMissing('process_requests', ['case_number' => $caseNumber]); + $this->assertDatabaseMissing('cases_started', ['case_number' => $caseNumber]); + $this->assertDatabaseMissing('cases_participated', ['case_number' => $caseNumber]); + $this->assertDatabaseMissing('case_numbers', ['process_request_id' => $requests->first()->id]); + $this->assertDatabaseMissing('case_numbers', ['process_request_id' => $requests->last()->id]); + } + + public function testDeleteCaseReturnsNotFoundWhenMissing(): void + { + $caseNumber = 99999; + + $response = $this->apiCall('DELETE', route('api.cases.destroy', ['case_number' => $caseNumber])); + + $response->assertStatus(404); + } +}