diff --git a/ProcessMaker/Http/Controllers/Api/V1_1/CaseController.php b/ProcessMaker/Http/Controllers/Api/V1_1/CaseController.php new file mode 100644 index 0000000000..301457724e --- /dev/null +++ b/ProcessMaker/Http/Controllers/Api/V1_1/CaseController.php @@ -0,0 +1,204 @@ +get('pageSize', 15); + + $query = CaseStarted::select($this->defaultFields); + + $this->filters($request, $query); + + $pagination = CaseResource::collection($query->paginate($pageSize)); + + return [ + 'data' => $pagination->items(), + 'meta' => [ + 'total' => $pagination->total(), + 'perPage' => $pagination->perPage(), + 'currentPage' => $pagination->currentPage(), + 'lastPage' => $pagination->lastPage(), + ], + ]; + } + + /** + * Apply filters to the query. + * + * @param Request $request + * @param Builder $query + * + * @return void + */ + private function filters(Request $request, Builder $query): void + { + if ($request->has('userId')) { + $query->where('user_id', $request->get('userId')); + } + + if ($request->has('status')) { + $query->where('case_status', $request->get('status')); + } + + $this->search($request, $query); + $this->filterBy($request, $query); + $this->sortBy($request, $query); + } + + /** + * Sort the query. + * + * @param Request $request: Query parameter format: sortBy=field:asc,field2:desc,... + * @param Builder $query + * + * @return void + */ + private function sortBy(Request $request, Builder $query): void + { + $sort = explode(',', $request->get('sortBy')); + + foreach ($sort as $value) { + if (!preg_match('/^[a-zA-Z_]+:(asc|desc)$/', $value)) { + continue; + } + + $sort = explode(':', $value); + $field = $sort[0]; + $order = $sort[1] ?? self::DEFAULT_SORT_DIRECTION; + + if (in_array($field, $this->sortableFields)) { + $query->orderBy($field, $order); + } + } + } + + /** + * Filter the query. + * + * @param Request $request: Query parameter format: filterBy[field]=value&filterBy[field2]=value2&... + * @param Builder $query + * @param array $dateFields List of date fields in current model + * + * @return void + */ + private function filterBy(Request $request, Builder $query): void + { + if ($request->has('filterBy')) { + $filterByValue = $request->get('filterBy'); + + foreach ($filterByValue as $key => $value) { + if (!in_array($key, $this->filterableFields)) { + continue; + } + + if (in_array($key, $this->dateFields)) { + $query->whereDate($key, $value); + continue; + } + + $query->where($key, $value); + } + } + } + + /** + * Search by case number or case title. + + * @param Request $request: Query parameter format: search=keyword + * @param Builder $query + * + * @return void + */ + private function search(Request $request, Builder $query): void + { + if ($request->has('search')) { + $search = $request->get('search'); + + $query->where(function ($q) use ($search) { + foreach ($this->searchableFields as $field) { + if ($field === 'case_number') { + $q->orWhere($field, $search); + } else { + $q->orWhereFullText($field, $search . '*', ['mode' => 'boolean']); + } + } + }); + } + } +} diff --git a/ProcessMaker/Http/Requests/GetAllCasesRequest.php b/ProcessMaker/Http/Requests/GetAllCasesRequest.php new file mode 100644 index 0000000000..500cb15bf7 --- /dev/null +++ b/ProcessMaker/Http/Requests/GetAllCasesRequest.php @@ -0,0 +1,35 @@ +|string> + */ + public function rules(): array + { + return [ + 'userId' => 'sometimes|integer', + 'status' => 'sometimes|in:IN_PROGRESS,COMPLETED', + 'sortBy' => ['sometimes', 'string', new SortBy], + 'filterBy' => 'sometimes|array', + 'search' => 'sometimes|string', + 'pageSize' => 'sometimes|integer|min:1', + 'page' => 'sometimes|integer|min:1', + ]; + } +} diff --git a/ProcessMaker/Http/Resources/V1_1/CaseResource.php b/ProcessMaker/Http/Resources/V1_1/CaseResource.php new file mode 100644 index 0000000000..c483ecda30 --- /dev/null +++ b/ProcessMaker/Http/Resources/V1_1/CaseResource.php @@ -0,0 +1,37 @@ +$field; + } + + return $data; + } +} diff --git a/ProcessMaker/Models/CaseStarted.php b/ProcessMaker/Models/CaseStarted.php new file mode 100644 index 0000000000..41019b5c6b --- /dev/null +++ b/ProcessMaker/Models/CaseStarted.php @@ -0,0 +1,55 @@ + AsArrayObject::class, + 'requests' => AsArrayObject::class, + 'request_tokens' => AsArrayObject::class, + 'tasks' => AsArrayObject::class, + 'participants' => AsArrayObject::class, + 'initiated_at' => 'datetime', + 'completed_at' => 'datetime', + ]; + + protected static function newFactory(): Factory + { + return CaseStartedFactory::new(); + } + + /** + * Get the user that owns the case. + */ + public function user() + { + return $this->belongsTo(User::class); + } +} diff --git a/ProcessMaker/Rules/SortBy.php b/ProcessMaker/Rules/SortBy.php new file mode 100644 index 0000000000..2fb3398141 --- /dev/null +++ b/ProcessMaker/Rules/SortBy.php @@ -0,0 +1,25 @@ + + */ +class CaseStartedFactory extends Factory +{ + protected $model = CaseStarted::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'case_number' => fake()->unique()->randomNumber(), + 'user_id' => fake()->randomElement([1, 3]), + 'case_title' => fake()->words(3, true), + 'case_title_formatted' => fake()->words(3, true), + 'case_status' => fake()->randomElement(['IN_PROGRESS', 'COMPLETED']), + 'processes' => array_map(function() { + return [ + 'id' => fake()->randomNumber(), + 'name' => fake()->words(2, true), + ]; + }, range(1, 3)), + 'requests' => [ + [ + 'id' => fake()->randomNumber(), + 'name' => fake()->words(2, true), + 'parent_request' => fake()->randomNumber(), + ], + [ + 'id' => fake()->randomNumber(), + 'name' => fake()->words(3, true), + 'parent_request' => fake()->randomNumber(), + ], + ], + 'request_tokens' => fake()->randomElement([fake()->randomNumber(), fake()->randomNumber(), fake()->randomNumber()]), + 'tasks' => [ + [ + 'id' => fake()->numerify('node_####'), + 'name' => fake()->words(4, true), + ], + [ + 'id' => fake()->numerify('node_####'), + 'name' => fake()->words(3, true), + ], + [ + 'id' => fake()->numerify('node_####'), + 'name' => fake()->words(2, true), + ], + ], + 'participants' => [ + [ + 'id' => fake()->randomNumber(), + 'name' => fake()->name(), + ], + [ + 'id' => fake()->randomNumber(), + 'name' => fake()->name(), + ], + [ + 'id' => fake()->randomNumber(), + 'name' => fake()->name(), + ], + ], + 'initiated_at' => fake()->dateTime(), + 'completed_at' => fake()->dateTime(), + 'keywords' => '', + ]; + } +} diff --git a/database/migrations/2024_09_09_181717_create_cases_started_table.php b/database/migrations/2024_09_09_181717_create_cases_started_table.php new file mode 100644 index 0000000000..2f9d32efe5 --- /dev/null +++ b/database/migrations/2024_09_09_181717_create_cases_started_table.php @@ -0,0 +1,47 @@ +unsignedInteger('case_number')->primary(); + $table->unsignedInteger('user_id'); + $table->string('case_title', 255); + $table->text('case_title_formatted'); + $table->string('case_status', 20); + $table->json('processes'); + $table->json('requests'); + $table->json('request_tokens'); + $table->json('tasks'); + $table->json('participants'); + $table->timestamp('initiated_at')->nullable(); + $table->timestamp('completed_at')->nullable(); + $table->timestamps(); + $table->text('keywords'); + + $table->foreign('user_id')->references('id')->on('users'); + + $table->index(['user_id', 'case_status', 'created_at']); + $table->index(['user_id', 'case_status', 'updated_at']); + + $table->fullText('case_title'); + $table->fullText('keywords'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('cases_started'); + } +}; diff --git a/routes/v1_1/api.php b/routes/v1_1/api.php index fe1b6a01c7..f15b2d8f77 100644 --- a/routes/v1_1/api.php +++ b/routes/v1_1/api.php @@ -1,6 +1,7 @@ name('show.interstitial'); }); + + // Cases Endpoints + Route::name('cases.')->prefix('cases')->group(function () { + // Route to list all cases + Route::get('get_all_cases', [CaseController::class, 'getAllCases']) + ->name('cases.all_cases'); + }); });