From b2e1aba7d711b6459770e5ccac3f5c6e3b5a2aa7 Mon Sep 17 00:00:00 2001 From: Henry Jonas Date: Wed, 23 Apr 2025 12:53:11 -0400 Subject: [PATCH 001/165] FOUR-22485: Open launchpad buttons appears templates edit --- .../components/ProcessCollapseInfo.vue | 62 ++++----- .../home/ArrowButtonGroup/ArrowButton.vue | 48 +++++++ .../ArrowButtonGroup/ArrowButtonGroup.vue | 56 ++++++++ .../home/ButtonGroup/BaseCardButton.vue | 75 ++++++++++ .../home/ButtonGroup/BaseCardButtonGroup.vue | 48 +++++++ .../components/home/Home.vue | 131 ++++++++++++++++++ .../PercentageCardButton.vue | 77 ++++++++++ .../PercentageCardButtonGroup.vue | 45 ++++++ tailwind.config.js | 6 +- 9 files changed, 514 insertions(+), 34 deletions(-) create mode 100644 resources/js/processes-catalogue/components/home/ArrowButtonGroup/ArrowButton.vue create mode 100644 resources/js/processes-catalogue/components/home/ArrowButtonGroup/ArrowButtonGroup.vue create mode 100644 resources/js/processes-catalogue/components/home/ButtonGroup/BaseCardButton.vue create mode 100644 resources/js/processes-catalogue/components/home/ButtonGroup/BaseCardButtonGroup.vue create mode 100644 resources/js/processes-catalogue/components/home/Home.vue create mode 100644 resources/js/processes-catalogue/components/home/PercentageButtonGroup/PercentageCardButton.vue create mode 100644 resources/js/processes-catalogue/components/home/PercentageButtonGroup/PercentageCardButtonGroup.vue diff --git a/resources/js/processes-catalogue/components/ProcessCollapseInfo.vue b/resources/js/processes-catalogue/components/ProcessCollapseInfo.vue index 36954a5443..79f1f8153c 100644 --- a/resources/js/processes-catalogue/components/ProcessCollapseInfo.vue +++ b/resources/js/processes-catalogue/components/ProcessCollapseInfo.vue @@ -1,69 +1,63 @@ @@ -91,6 +84,8 @@ import ProcessesMixin from "./mixins/ProcessesMixin"; import ProcessHeader from "./ProcessHeader.vue"; import ProcessHeaderStart from "./ProcessHeaderStart.vue"; +import Home from "./home/Home.vue"; + export default { components: { CreateTemplateModal, @@ -101,15 +96,23 @@ export default { ProcessesCarousel, ProcessHeader, ProcessHeaderStart, + Home, }, mixins: [ProcessesMixin, ellipsisMenuMixin, processNavigationMixin], props: ["process", "currentUserId", "ellipsisPermission", "myTasksColumns"], + data() { + return { + mobileApp: window.ProcessMaker.mobileApp, + showProcessInfo: false, + collapsed: true, + }; + }, computed: { createdFromWizardTemplate() { return !!this.process?.properties?.wizardTemplateUuid; }, isArchived() { - return this.process?.status === 'ARCHIVED'; + return this.process?.status === "ARCHIVED"; }, wizardTemplateUuid() { return this.process?.properties?.wizardTemplateUuid; @@ -121,13 +124,6 @@ export default { window.location.reload(); }); }, - data() { - return { - mobileApp: window.ProcessMaker.mobileApp, - showProcessInfo: false, - collapsed: true, - }; - }, methods: { /** * Verify if the Description is large @@ -150,7 +146,7 @@ export default { this.showProcessInfo = !this.showProcessInfo; }, updateMyTasksColumns(columns) { - this.$emit('updateMyTasksColumns', columns); + this.$emit("updateMyTasksColumns", columns); }, onProcessInfoCollapsed(collapsed) { this.collapsed = collapsed; diff --git a/resources/js/processes-catalogue/components/home/ArrowButtonGroup/ArrowButton.vue b/resources/js/processes-catalogue/components/home/ArrowButtonGroup/ArrowButton.vue new file mode 100644 index 0000000000..8c608fef40 --- /dev/null +++ b/resources/js/processes-catalogue/components/home/ArrowButtonGroup/ArrowButton.vue @@ -0,0 +1,48 @@ + + + + + diff --git a/resources/js/processes-catalogue/components/home/ArrowButtonGroup/ArrowButtonGroup.vue b/resources/js/processes-catalogue/components/home/ArrowButtonGroup/ArrowButtonGroup.vue new file mode 100644 index 0000000000..d48aa1a8b9 --- /dev/null +++ b/resources/js/processes-catalogue/components/home/ArrowButtonGroup/ArrowButtonGroup.vue @@ -0,0 +1,56 @@ + + + diff --git a/resources/js/processes-catalogue/components/home/ButtonGroup/BaseCardButton.vue b/resources/js/processes-catalogue/components/home/ButtonGroup/BaseCardButton.vue new file mode 100644 index 0000000000..30eddc37f6 --- /dev/null +++ b/resources/js/processes-catalogue/components/home/ButtonGroup/BaseCardButton.vue @@ -0,0 +1,75 @@ + + + diff --git a/resources/js/processes-catalogue/components/home/ButtonGroup/BaseCardButtonGroup.vue b/resources/js/processes-catalogue/components/home/ButtonGroup/BaseCardButtonGroup.vue new file mode 100644 index 0000000000..5a76eaab0d --- /dev/null +++ b/resources/js/processes-catalogue/components/home/ButtonGroup/BaseCardButtonGroup.vue @@ -0,0 +1,48 @@ + + + diff --git a/resources/js/processes-catalogue/components/home/Home.vue b/resources/js/processes-catalogue/components/home/Home.vue new file mode 100644 index 0000000000..dd9b6154a1 --- /dev/null +++ b/resources/js/processes-catalogue/components/home/Home.vue @@ -0,0 +1,131 @@ + + + diff --git a/resources/js/processes-catalogue/components/home/PercentageButtonGroup/PercentageCardButton.vue b/resources/js/processes-catalogue/components/home/PercentageButtonGroup/PercentageCardButton.vue new file mode 100644 index 0000000000..b4ba1597cf --- /dev/null +++ b/resources/js/processes-catalogue/components/home/PercentageButtonGroup/PercentageCardButton.vue @@ -0,0 +1,77 @@ + + + diff --git a/resources/js/processes-catalogue/components/home/PercentageButtonGroup/PercentageCardButtonGroup.vue b/resources/js/processes-catalogue/components/home/PercentageButtonGroup/PercentageCardButtonGroup.vue new file mode 100644 index 0000000000..2970d76227 --- /dev/null +++ b/resources/js/processes-catalogue/components/home/PercentageButtonGroup/PercentageCardButtonGroup.vue @@ -0,0 +1,45 @@ + + + diff --git a/tailwind.config.js b/tailwind.config.js index 1ecc0680bc..7bd5c9a36d 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -15,7 +15,11 @@ module.exports = { }, safelist: [ { - pattern: /(bg|outline|border|text)-(gray|purple|blue|amber|green|gray|emerald|red)-(100|200|300|400|500)/, + pattern: /(outline|border|text)-(gray|purple|blue|amber|green|gray|emerald|red)-(100|200|300|400|500)/, + variants: ["hover"], + }, + { + pattern: /(bg)-([^-]+)-([0-9]+)/, variants: ["hover"], }, ], From d277b1f756c4f04edeb46af49bd20aa28e496794 Mon Sep 17 00:00:00 2001 From: Henry Jonas Date: Wed, 23 Apr 2025 13:13:02 -0400 Subject: [PATCH 002/165] FOUR-23575:S2: Case tab - Implement the component to show a counters --- .../components/home/ArrowButtonGroup/ArrowButton.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/js/processes-catalogue/components/home/ArrowButtonGroup/ArrowButton.vue b/resources/js/processes-catalogue/components/home/ArrowButtonGroup/ArrowButton.vue index 8c608fef40..3d172df238 100644 --- a/resources/js/processes-catalogue/components/home/ArrowButtonGroup/ArrowButton.vue +++ b/resources/js/processes-catalogue/components/home/ArrowButtonGroup/ArrowButton.vue @@ -1,6 +1,6 @@ @@ -16,4 +18,9 @@ const defaultStages = [ { id: 2, label: 'Request Reviewed', selected: true }, { id: 3, label: 'Manager Reviewed', selected: false }, ]; + +const changeStage = (stages) => { + //trigger when stages change. + +}; From b7064373b552788890226fb05e917767ad29be9c Mon Sep 17 00:00:00 2001 From: Paula Quispe Date: Fri, 2 May 2025 20:17:41 -0400 Subject: [PATCH 021/165] FOUR-24219 --- .../Http/Controllers/CasesController.php | 38 ++++++++++--------- .../Http/Resources/V1_1/CaseResource.php | 3 +- ProcessMaker/Models/CaseParticipated.php | 3 +- ProcessMaker/Models/CaseStarted.php | 3 +- .../Repositories/CaseApiRepository.php | 3 +- ProcessMaker/Repositories/CaseRepository.php | 6 ++- ProcessMaker/Repositories/CaseUtils.php | 35 ----------------- ProcessMaker/Repositories/TokenRepository.php | 3 +- ...d_stage_name_to_process_request_tokens.php | 2 +- ...e_id_and_stage_name_to_process_request.php | 8 ++-- ...d_and_stage_name_to_cases_participated.php | 8 ++-- ...age_id_and_stage_name_to_cases_started.php | 8 ++-- tests/Feature/CasesControllerTest.php | 13 ++----- 13 files changed, 53 insertions(+), 80 deletions(-) diff --git a/ProcessMaker/Http/Controllers/CasesController.php b/ProcessMaker/Http/Controllers/CasesController.php index bac3225999..c416731855 100644 --- a/ProcessMaker/Http/Controllers/CasesController.php +++ b/ProcessMaker/Http/Controllers/CasesController.php @@ -84,7 +84,7 @@ public function show($case_number) } else { $request->summary_screen = $request->getSummaryScreen(); } - $currentStages = $this->getCurrentStage($request->stages); + $currentStages = $this->getCurrentStage($request->last_stage_id, $request->last_stage_name); $allStages = $this->getStagesByProcessId($request->process_id); $progressStage = $this->getProgressStage($allStages, $currentStages); // Load the screen configured in "Request Detail Screen" @@ -182,27 +182,25 @@ public function summaryScreenTranslation(ProcessRequest $request): void * This method decodes a JSON string representing stages into an associative array. * If the input is null or not a valid JSON string, it returns an empty array. * - * @param string|null $stages A JSON string representing the stages. - * It can be null or a valid JSON string. + * @param int|null $id The ID of the stage. + * @param string|null $name The name of the stage. * @return array An associative array of stages if the JSON is valid; * otherwise, an empty array. */ - public static function getCurrentStage($stages) + public static function getCurrentStage(?int $id, ?string $name): array { // Initialize currentStages as an empty array $currentStages = []; - // Check if $stages is not null and is a valid JSON string - if (!is_null($stages) && is_string($stages)) { - $decodedStages = json_decode($stages, true); - - // Check if json_decode was successful and returned an array - if (json_last_error() === JSON_ERROR_NONE && is_array($decodedStages)) { - $currentStages = $decodedStages; - } + // Check if $id is not null and $name is a valid string + if (!is_null($id) && is_string($name)) { + $currentStages = [ + 'stage_id' => $id, + 'stage_name' => $name, + ]; } - return $currentStages; // Return the current stages (empty array if none) + return $currentStages; } /** @@ -232,11 +230,15 @@ public static function getProgressStage(array $allStages, array $currentStages): // Count the number of completed stages $completedStages = 0; - - foreach ($currentStages as $currentStage) { - // Check if the current stage is completed - if (in_array($currentStage['id'], array_column($allStages, 'id'))) { - $completedStages++; + // Extract the current stage IDs from the currentStages array + $currentStageId = $currentStages['stage_id']; + + foreach ($allStages as $stage) { + var_dump($currentStageId); + $completedStages++; + // Check if the current stage ID is in the current stages + if ($stage['id'] === $currentStageId) { + break; // Exit the loop once the stage is found } } diff --git a/ProcessMaker/Http/Resources/V1_1/CaseResource.php b/ProcessMaker/Http/Resources/V1_1/CaseResource.php index fb5098bb42..ebc7b32637 100644 --- a/ProcessMaker/Http/Resources/V1_1/CaseResource.php +++ b/ProcessMaker/Http/Resources/V1_1/CaseResource.php @@ -29,7 +29,8 @@ class CaseResource extends ApiResource 'participants', 'initiated_at', 'completed_at', - 'stages', + 'last_stage_id', + 'last_stage_name', ]; public function toArray($request): array diff --git a/ProcessMaker/Models/CaseParticipated.php b/ProcessMaker/Models/CaseParticipated.php index 2de2f74aed..0cd04ba651 100644 --- a/ProcessMaker/Models/CaseParticipated.php +++ b/ProcessMaker/Models/CaseParticipated.php @@ -30,7 +30,8 @@ class CaseParticipated extends ProcessMakerModel 'initiated_at', 'completed_at', 'keywords', - 'stages', + 'last_stage_id', + 'last_stage_name', ]; protected $casts = [ diff --git a/ProcessMaker/Models/CaseStarted.php b/ProcessMaker/Models/CaseStarted.php index 6327abde6f..ff0eccde3e 100644 --- a/ProcessMaker/Models/CaseStarted.php +++ b/ProcessMaker/Models/CaseStarted.php @@ -30,7 +30,8 @@ class CaseStarted extends ProcessMakerModel 'initiated_at', 'completed_at', 'keywords', - 'stages', + 'last_stage_id', + 'last_stage_name', ]; protected $casts = [ diff --git a/ProcessMaker/Repositories/CaseApiRepository.php b/ProcessMaker/Repositories/CaseApiRepository.php index 2b64d95953..e3372d8cf6 100644 --- a/ProcessMaker/Repositories/CaseApiRepository.php +++ b/ProcessMaker/Repositories/CaseApiRepository.php @@ -32,7 +32,8 @@ class CaseApiRepository implements CaseApiRepositoryInterface 'participants', 'initiated_at', 'completed_at', - 'stages', + 'last_stage_id', + 'last_stage_name', ]; protected $sortableFields = [ diff --git a/ProcessMaker/Repositories/CaseRepository.php b/ProcessMaker/Repositories/CaseRepository.php index d36088ad2f..ea9db90c5e 100644 --- a/ProcessMaker/Repositories/CaseRepository.php +++ b/ProcessMaker/Repositories/CaseRepository.php @@ -68,7 +68,8 @@ public function create(ExecutionInstanceInterface $instance): void 'initiated_at' => $instance->initiated_at, 'completed_at' => null, 'keywords' => CaseUtils::getKeywords($dataKeywords), - // TO_DO: save all stages $this->case->stages = CaseUtils::storeStages($this->case->tasks, $taskData); + // TO_DO: save the last stage id $this->case->last_stage_id + // TO_DO: save the last stage id $this->case->last_stage_name ]); } catch (\Exception $e) { Log::error('CaseException: ' . $e->getMessage()); @@ -99,7 +100,8 @@ public function update(ExecutionInstanceInterface $instance, TokenInterface $tok $this->case->request_tokens = CaseUtils::storeRequestTokens($this->case->request_tokens, $token->getKey()); $this->case->tasks = CaseUtils::storeTasks($this->case->tasks, $taskData); $this->case->keywords = CaseUtils::getKeywords($dataKeywords); - // TO_DO: save all stages $this->case->stages = CaseUtils::storeStages($this->case->tasks, $taskData); + // TO_DO: save the last stage id $this->case->last_stage_id + // TO_DO: save the last stage id $this->case->last_stage_name $this->updateParticipants($token); diff --git a/ProcessMaker/Repositories/CaseUtils.php b/ProcessMaker/Repositories/CaseUtils.php index e5ba606dd0..49a48f6ff2 100644 --- a/ProcessMaker/Repositories/CaseUtils.php +++ b/ProcessMaker/Repositories/CaseUtils.php @@ -38,13 +38,6 @@ class CaseUtils 'case_title' => 'case_title', ]; - const STAGE_FIELDS = [ - 'id' => 'stage_id', - 'name' => 'stage_name', - 'process_id' => 'process_id', - 'task_id' => 'task_id', - ]; - /** * Get the case number split into keywords. * @param int $caseNumber @@ -167,34 +160,6 @@ public static function storeTasks(Collection $tasks, ?array $taskData = []): Col return $tasks->unique('id')->values(); } - /** - * Store stages. - * - * @param Collection $stages - * @param array|null $stageData - * An optional array of additional stage data. Each element in the array should have the following structure: - * [ - * 'id' => int, // The unique identifier of the stage - * 'name' => string, // The name of the stage - * 'process_id' => int, // The unique identifier of the process - * 'task_id' => int // The unique identifier of the task - * ] - * @return Collection - */ - public static function storeStages(Collection $stages, ?array $stageData = []): Collection - { - if ( - !empty($stageData) && !array_diff(array_keys(self::STAGE_FIELDS), array_keys($stageData)) - ) { - // Convert the 'id' to string for consistency - $stageData['id'] = (string) $stageData['id']; - // Prepend the stage data to the collection - $stages->prepend($stageData); - } - - return $stages->unique('id')->values(); - } - /** * Store participants. * diff --git a/ProcessMaker/Repositories/TokenRepository.php b/ProcessMaker/Repositories/TokenRepository.php index fc801ac76b..127cb8a07a 100644 --- a/ProcessMaker/Repositories/TokenRepository.php +++ b/ProcessMaker/Repositories/TokenRepository.php @@ -170,7 +170,8 @@ public function persistActivityActivated(ActivityInterface $activity, TokenInter $token->setId($token->getKey()); $request = $token->getInstance(); $request->notifyProcessUpdated('ACTIVITY_ACTIVATED', $token); - // TO_DO: save stages like $request->stages = []; + // TO_DO: save the last stage id $this->request->last_stage_id + // TO_DO: save the last stage id $this->request->last_stage_name CaseUpdate::dispatchSync($request, $token); diff --git a/database/migrations/2025_04_23_183734_add_stage_id_and_stage_name_to_process_request_tokens.php b/database/migrations/2025_04_23_183734_add_stage_id_and_stage_name_to_process_request_tokens.php index 5f7f7e45a2..359332e768 100644 --- a/database/migrations/2025_04_23_183734_add_stage_id_and_stage_name_to_process_request_tokens.php +++ b/database/migrations/2025_04_23_183734_add_stage_id_and_stage_name_to_process_request_tokens.php @@ -13,7 +13,7 @@ public function up(): void Schema::table('process_request_tokens', function (Blueprint $table) { // Add a 'stage_id' column to store the identifier of the current stage // This will allow tracking which stage the token is in within the process. - $table->string('stage_id')->nullable(); + $table->integer('stage_id')->nullable(); // Add a 'stage_name' column to store the name of the current stage // This will facilitate the identification of the stage $table->string('stage_name')->nullable(); diff --git a/database/migrations/2025_04_23_183839_add_stage_id_and_stage_name_to_process_request.php b/database/migrations/2025_04_23_183839_add_stage_id_and_stage_name_to_process_request.php index 40018fe281..18df3781af 100644 --- a/database/migrations/2025_04_23_183839_add_stage_id_and_stage_name_to_process_request.php +++ b/database/migrations/2025_04_23_183839_add_stage_id_and_stage_name_to_process_request.php @@ -11,8 +11,9 @@ public function up(): void { Schema::table('process_requests', function (Blueprint $table) { - // This column will store information about the stages: stage_id, stage_name - $table->json('stages')->nullable(); + // Those columns will store the last stage information related to stage_id, stage_name + $table->integer('last_stage_id')->nullable(); + $table->string('last_stage_name')->nullable(); // This column will be used to display the percentage of advancement of the request through the stages. $table->float('progress')->default(0); }); @@ -24,7 +25,8 @@ public function up(): void public function down(): void { Schema::table('process_requests', function (Blueprint $table) { - $table->dropColumn('stages'); + $table->dropColumn('last_stage_id'); + $table->dropColumn('last_stage_name'); $table->dropColumn('progress'); }); } diff --git a/database/migrations/2025_04_23_184251_add_stage_id_and_stage_name_to_cases_participated.php b/database/migrations/2025_04_23_184251_add_stage_id_and_stage_name_to_cases_participated.php index a817e55c3d..809df0bba5 100644 --- a/database/migrations/2025_04_23_184251_add_stage_id_and_stage_name_to_cases_participated.php +++ b/database/migrations/2025_04_23_184251_add_stage_id_and_stage_name_to_cases_participated.php @@ -11,8 +11,9 @@ public function up(): void { Schema::table('cases_participated', function (Blueprint $table) { - // This column will store information about the stages: stage_id, stage_name - $table->json('stages')->nullable(); + // Those columns will store the last stage information related to stage_id, stage_name + $table->integer('last_stage_id')->nullable(); + $table->string('last_stage_name')->nullable(); // This column will be used to display the percentage of advancement of the case through the stages. $table->float('progress')->default(0); }); @@ -24,7 +25,8 @@ public function up(): void public function down(): void { Schema::table('cases_participated', function (Blueprint $table) { - $table->dropColumn('stages'); + $table->dropColumn('last_stage_id'); + $table->dropColumn('last_stage_name'); $table->dropColumn('progress'); }); } diff --git a/database/migrations/2025_04_23_184327_add_stage_id_and_stage_name_to_cases_started.php b/database/migrations/2025_04_23_184327_add_stage_id_and_stage_name_to_cases_started.php index 22f45818be..3f17aa28ed 100644 --- a/database/migrations/2025_04_23_184327_add_stage_id_and_stage_name_to_cases_started.php +++ b/database/migrations/2025_04_23_184327_add_stage_id_and_stage_name_to_cases_started.php @@ -11,8 +11,9 @@ public function up(): void { Schema::table('cases_started', function (Blueprint $table) { - // This column will store information about the stages: stage_id, stage_name - $table->json('stages')->nullable(); + // Those columns will store the last stage information related to stage_id, stage_name + $table->integer('last_stage_id')->nullable(); + $table->string('last_stage_name')->nullable(); // This column will be used to display the percentage of advancement of the case through the stages. $table->float('progress')->default(0); }); @@ -24,7 +25,8 @@ public function up(): void public function down(): void { Schema::table('cases_started', function (Blueprint $table) { - $table->dropColumn('stages'); + $table->dropColumn('last_stage_id'); + $table->dropColumn('last_stage_name'); $table->dropColumn('progress'); }); } diff --git a/tests/Feature/CasesControllerTest.php b/tests/Feature/CasesControllerTest.php index a53445730f..ec4d2fabac 100644 --- a/tests/Feature/CasesControllerTest.php +++ b/tests/Feature/CasesControllerTest.php @@ -342,19 +342,12 @@ public function testGetProgressStage() ]; // Test case 1: All stages completed - $currentStages1 = [ - ['id' => 1, 'name' => 'Stage 1'], - ['id' => 2, 'name' => 'Stage 2'], - ['id' => 3, 'name' => 'Stage 3'], - ]; + $currentStages1 = CasesController::getCurrentStage(3, 'Stage 3'); $this->assertEquals(100.0, CasesController::getProgressStage($allStages, $currentStages1)); // Test case 2: Some stages completed - $currentStages2 = [ - ['id' => 1, 'name' => 'Stage 1'], - ['id' => 2, 'name' => 'Stage 2'], - ]; - $this->assertEquals(66.67, CasesController::getProgressStage($allStages, $currentStages2), '', 0.01); + $currentStages2 = CasesController::getCurrentStage(2, 'Stage 2'); + $this->assertEquals(66.67, CasesController::getProgressStage($allStages, $currentStages2)); // Test case 3: No stages completed $currentStages3 = []; From d3944bca12f5a44685ae6734dfec0e0d104bcc9f Mon Sep 17 00:00:00 2001 From: Paula Quispe Date: Mon, 5 May 2025 17:36:17 -0400 Subject: [PATCH 022/165] FOUR-23485 --- .../Api/ProcessLaunchpadController.php | 9 +- ProcessMaker/Traits/ProcessTrait.php | 50 +++++++ tests/Feature/Api/ProcessLaunchpadTest.php | 135 +++++++++++++++++- 3 files changed, 183 insertions(+), 11 deletions(-) diff --git a/ProcessMaker/Http/Controllers/Api/ProcessLaunchpadController.php b/ProcessMaker/Http/Controllers/Api/ProcessLaunchpadController.php index a363839b40..0b19741904 100644 --- a/ProcessMaker/Http/Controllers/Api/ProcessLaunchpadController.php +++ b/ProcessMaker/Http/Controllers/Api/ProcessLaunchpadController.php @@ -94,6 +94,7 @@ public function index(Request $request, Process $process) ->get() ->map(function ($process) use ($request) { $process->counts = $process->getCounts(); + $process->stagesSummary = $process->getStagesSummary($process->stages); $process->bookmark_id = Bookmark::getBookmarked(true, $process->id, $request->user()->id); return $process; @@ -200,14 +201,14 @@ public function getProcessesMenu(Request $request) $processes = Process::select('processes.*') ->distinct() - ->whereHas('requests', function($query) use ($userId) { + ->whereHas('requests', function ($query) use ($userId) { $query->where('user_id', $userId) - ->orWhereHas('tokens', function($query) use ($userId) { + ->orWhereHas('tokens', function ($query) use ($userId) { $query->where('user_id', $userId); }); }) - ->where('asset_type', NULL) - ->where('package_key', '=', NULL) + ->where('asset_type', null) + ->where('package_key', '=', null) ->orderBy('processes.name', 'asc') ->get(); diff --git a/ProcessMaker/Traits/ProcessTrait.php b/ProcessMaker/Traits/ProcessTrait.php index fd5cf1f9db..c6cd0d3ec7 100644 --- a/ProcessMaker/Traits/ProcessTrait.php +++ b/ProcessMaker/Traits/ProcessTrait.php @@ -218,4 +218,54 @@ public function getCounts() 'total' => $completed + $in_progress, ]; } + + /** + * Get the count of stages per request. + * + * This method retrieves the count of stages based on the last_stage_id for each request. + * If a JSON string of stages is provided, it decodes it; otherwise, it fetches all stages + * from the database. The method returns an array containing each stage's ID, name, and count. + * + * @param string|null $stages A JSON string representing stages. If provided, it will be decoded; + * if null, all stages will be fetched from the database. + * @return array An array of associative arrays, each containing: + * - 'id': The ID of the stage. + * - 'name': The name of the stage. + * - 'count': The count of occurrences of the stage based on last_stage_id. + */ + public function getStagesSummary($stages = null) + { + if (!empty($stages)) { + $allStages = json_decode($stages, true); + } else { + return []; + } + + // Assuming 'stages' is a relationship defined in the model + $result = $this->requests() + ->selectRaw('last_stage_id, count(*) as count') + ->groupBy('last_stage_id') + ->get(); + + // Prepare an array to hold the counts for each stage + $stageCounts = []; + // Initialize stage counts with zero for all stages + foreach ($allStages as $stage) { + $stageCounts[] = [ + 'id' => $stage['id'], + 'name' => $stage['name'], + 'count' => 0, // Initialize count to 0 for each stage + ]; + } + + foreach ($result as $stage) { + foreach ($stageCounts as $key => &$countData) { + if ($countData['id'] == $stage->last_stage_id) { + $countData['count'] = $stage->count; // Update the count + } + } + } + + return $stageCounts; + } } diff --git a/tests/Feature/Api/ProcessLaunchpadTest.php b/tests/Feature/Api/ProcessLaunchpadTest.php index a02e8729d2..3f07e63b28 100644 --- a/tests/Feature/Api/ProcessLaunchpadTest.php +++ b/tests/Feature/Api/ProcessLaunchpadTest.php @@ -2,23 +2,24 @@ namespace Tests\Feature\Api; +use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Auth; use ProcessMaker\Models\Process; use ProcessMaker\Models\ProcessLaunchpad; -use ProcessMaker\Models\User; +use ProcessMaker\Models\ProcessRequest; use Tests\Feature\Shared\RequestHelper; use Tests\TestCase; class ProcessLaunchpadTest extends TestCase { - use RequestHelper; + use RequestHelper, RefreshDatabase; const API_TEST_URL = '/process_launchpad'; const STRUCTURE = [ 'launchpad', 'media', - 'embed' + 'embed', ]; /** @@ -29,11 +30,11 @@ public function testGetProcessLaunchpad() // Create data $process = Process::factory()->create(); // Call the api GET - $response = $this->apiCall('GET', self::API_TEST_URL .'/' . $process->id); + $response = $this->apiCall('GET', self::API_TEST_URL . '/' . $process->id); // Validate the header status code $response->assertStatus(200); $this->assertNotEmpty($response); - + // Create data related with the auth user $user = Auth::user(); $process = Process::factory()->create(); @@ -42,7 +43,7 @@ public function testGetProcessLaunchpad() 'user_id' => $user->id, ]); // Call the api GET - $response = $this->apiCall('GET', self::API_TEST_URL .'/' . $process->id); + $response = $this->apiCall('GET', self::API_TEST_URL . '/' . $process->id); // Validate the header status code $response->assertStatus(200); $this->assertNotEmpty($response); @@ -59,7 +60,7 @@ public function testStoreProcessLaunchpad() 'process_id' => $process->id, ]); // Call the api PUT - $values = json_encode(["icon" => "fa-user"]); + $values = json_encode(['icon' => 'fa-user']); $response = $this->apiCall('PUT', self::API_TEST_URL . '/' . $process->id, ['properties' => $values]); // Validate the header status code $response->assertStatus(200); @@ -77,4 +78,124 @@ public function testDeleteProcessLaunchpad() // Validate the header status code $response->assertStatus(204); } + + /** + * Test get stages count per process when stages are null. + */ + public function testGetStagesCountPerProcessWhenStagesIsNull() + { + // Create data + $process = Process::factory()->create(); + // Call the api GET + $response = $this->apiCall('GET', self::API_TEST_URL . '/' . $process->id); + // Validate the header status code + $response->assertStatus(200); + $this->assertNotEmpty($response); + // Create data related with the auth user + $user = Auth::user(); + $process = Process::factory()->create([ + 'stages' => null, + ]); + ProcessLaunchpad::factory()->create([ + 'process_id' => $process->id, + 'user_id' => $user->id, + ]); + ProcessRequest::factory()->create(); + // Call the api GET + $response = $this->apiCall('GET', self::API_TEST_URL . '/' . $process->id); + // Validate the header status code + $response->assertStatus(200); + $this->assertNotEmpty($response); + $this->assertEquals(null, $response->json('stagesSummary')); + } + + /** + * Test get stages count per process when there are stages but no ProcessRequests. + */ + public function testGetStagesCountPerProcessWithStagesAndNoRequests() + { + // Create stages for the process + $stages = [ + ['id' => 1, 'name' => 'Stage A'], + ['id' => 2, 'name' => 'Stage B'], + ]; + $user = Auth::user(); + // Create a process with stages + $process = Process::factory()->create([ + 'stages' => json_encode($stages), + ]); + ProcessLaunchpad::factory()->create([ + 'process_id' => $process->id, + ]); + + // No ProcessRequest records are created for this test + + $stageSummary = $process->getStagesSummary(json_encode($stages)); + $expectedStagesSummary = [ + [ + 'id' => 1, + 'name' => 'Stage A', + 'count' => 0, // No requests for this stage + ], + [ + 'id' => 2, + 'name' => 'Stage B', + 'count' => 0, // No requests for this stage + ], + ]; + // Check if the stagesSummary matches the expected output + $this->assertEquals($expectedStagesSummary, $stageSummary); + } + + /** + * Test get stages count per process. + */ + public function testGetStagesCountPerProcess() + { + // Create stages for the process + $stages = [ + ['id' => 1, 'name' => 'Stage 1', 'order' => 1], + ['id' => 2, 'name' => 'Stage 2', 'order' => 3], + ['id' => 3, 'name' => 'Stage 3', 'order' => 2], + ]; + // Create a process + $process = Process::factory()->create([ + 'stages' => json_encode($stages), + ]); + ProcessLaunchpad::factory()->create([ + 'process_id' => $process->id, + ]); + // Create requests associated with the process + ProcessRequest::factory(2)->create([ + 'process_id' => $process->id, + 'last_stage_id' => 1, + 'last_stage_name' => 'Stage 1', + ]); + ProcessRequest::factory(3)->create([ + 'process_id' => $process->id, + 'last_stage_id' => 2, + 'last_stage_name' => 'Stage 2', + ]); + ProcessRequest::factory(5)->create([ + 'process_id' => $process->id, + 'last_stage_id' => 3, + 'last_stage_name' => 'Stage 3', + ]); + + // Call the API GET + $response = $this->apiCall('GET', self::API_TEST_URL . '/' . $process->id); + $stageSummary = $process->getStagesSummary(json_encode($stages)); + // Validate the header status code + $response->assertStatus(200); + $this->assertNotEmpty($response); + + // Validate the stagesSummary in the response + $expectedStagesSummary = [ + ['id' => 1, 'name' => 'Stage 1', 'count' => 2], // 2 requests with last_stage_id 1 + ['id' => 2, 'name' => 'Stage 2', 'count' => 3], // 1 request with last_stage_id 2 + ['id' => 3, 'name' => 'Stage 3', 'count' => 5], // No requests with last_stage_id 3 + ]; + // Check if the stagesSummary matches the expected output + $this->assertEquals($expectedStagesSummary, $stageSummary); + } } From a9a502f2b998f1bb16986e2d5de397dd385d30c0 Mon Sep 17 00:00:00 2001 From: Paula Quispe Date: Mon, 5 May 2025 19:10:52 -0400 Subject: [PATCH 023/165] FOUR-23485 using the stagesSummary in the ProgressBarSection --- ProcessMaker/Traits/ProcessTrait.php | 11 ++++++ .../components/ProcessInfo.vue | 2 +- .../progressBar/ProgressBarSection.vue | 36 +++++++++++-------- .../components/progressBar/ProgressInfo.vue | 2 +- tests/Feature/Api/ProcessLaunchpadTest.php | 18 +++------- 5 files changed, 40 insertions(+), 29 deletions(-) diff --git a/ProcessMaker/Traits/ProcessTrait.php b/ProcessMaker/Traits/ProcessTrait.php index c6cd0d3ec7..6da4908351 100644 --- a/ProcessMaker/Traits/ProcessTrait.php +++ b/ProcessMaker/Traits/ProcessTrait.php @@ -255,6 +255,7 @@ public function getStagesSummary($stages = null) 'id' => $stage['id'], 'name' => $stage['name'], 'count' => 0, // Initialize count to 0 for each stage + 'percentage' => 0, // Initialize percentaje to 0 for each stage ]; } @@ -266,6 +267,16 @@ public function getStagesSummary($stages = null) } } + // Calculate the total count of all stages + $totalCount = array_sum(array_column($stageCounts, 'count')); + + // Calculate the percentage for each stage + foreach ($stageCounts as &$countData) { + if ($totalCount > 0) { + $countData['percentage'] = ($countData['count'] / $totalCount) * 100; + } + } + return $stageCounts; } } diff --git a/resources/js/processes-catalogue/components/ProcessInfo.vue b/resources/js/processes-catalogue/components/ProcessInfo.vue index 944cf0abc4..39674f9651 100644 --- a/resources/js/processes-catalogue/components/ProcessInfo.vue +++ b/resources/js/processes-catalogue/components/ProcessInfo.vue @@ -50,7 +50,7 @@ :process="process" :collapsed="collapsed" /> - + diff --git a/resources/js/processes-catalogue/components/progressBar/ProgressBarSection.vue b/resources/js/processes-catalogue/components/progressBar/ProgressBarSection.vue index 98a0c8df7e..a0945e47da 100644 --- a/resources/js/processes-catalogue/components/progressBar/ProgressBarSection.vue +++ b/resources/js/processes-catalogue/components/progressBar/ProgressBarSection.vue @@ -1,23 +1,31 @@ diff --git a/resources/js/processes-catalogue/components/progressBar/ProgressInfo.vue b/resources/js/processes-catalogue/components/progressBar/ProgressInfo.vue index 3c41978bff..b14f8e18c1 100644 --- a/resources/js/processes-catalogue/components/progressBar/ProgressInfo.vue +++ b/resources/js/processes-catalogue/components/progressBar/ProgressInfo.vue @@ -25,7 +25,7 @@ const props = defineProps({ required: true, }, value: { - type: String, + type: Number, required: true, }, percentage: { diff --git a/tests/Feature/Api/ProcessLaunchpadTest.php b/tests/Feature/Api/ProcessLaunchpadTest.php index 3f07e63b28..001937db6d 100644 --- a/tests/Feature/Api/ProcessLaunchpadTest.php +++ b/tests/Feature/Api/ProcessLaunchpadTest.php @@ -132,16 +132,8 @@ public function testGetStagesCountPerProcessWithStagesAndNoRequests() $stageSummary = $process->getStagesSummary(json_encode($stages)); $expectedStagesSummary = [ - [ - 'id' => 1, - 'name' => 'Stage A', - 'count' => 0, // No requests for this stage - ], - [ - 'id' => 2, - 'name' => 'Stage B', - 'count' => 0, // No requests for this stage - ], + ['id' => 1, 'name' => 'Stage A', 'count' => 0, 'percentage' => 0], + ['id' => 2, 'name' => 'Stage B', 'count' => 0, 'percentage' => 0], ]; // Check if the stagesSummary matches the expected output $this->assertEquals($expectedStagesSummary, $stageSummary); @@ -191,9 +183,9 @@ public function testGetStagesCountPerProcess() // Validate the stagesSummary in the response $expectedStagesSummary = [ - ['id' => 1, 'name' => 'Stage 1', 'count' => 2], // 2 requests with last_stage_id 1 - ['id' => 2, 'name' => 'Stage 2', 'count' => 3], // 1 request with last_stage_id 2 - ['id' => 3, 'name' => 'Stage 3', 'count' => 5], // No requests with last_stage_id 3 + ['id' => 1, 'name' => 'Stage 1', 'count' => 2, 'percentage' => 20], + ['id' => 2, 'name' => 'Stage 2', 'count' => 3, 'percentage' => 30], + ['id' => 3, 'name' => 'Stage 3', 'count' => 5, 'percentage' => 50], ]; // Check if the stagesSummary matches the expected output $this->assertEquals($expectedStagesSummary, $stageSummary); From a802c1aac72fa0a789fcec1c5564115f696ff67a Mon Sep 17 00:00:00 2001 From: Henry Jonas Date: Tue, 6 May 2025 11:30:05 -0400 Subject: [PATCH 024/165] FOUR-24086: S12: Settings - Add a select List to define the view/tce-dashboard and save --- .../shared/LaunchpadSettingsModal.vue | 178 ++++++++++-------- .../components/Process.vue | 69 ++++--- .../components/home/CustomHomeScreen.vue | 12 ++ .../home/CustomHomeTableSection.vue | 11 ++ 4 files changed, 166 insertions(+), 104 deletions(-) create mode 100644 resources/js/processes-catalogue/components/home/CustomHomeScreen.vue create mode 100644 resources/js/processes-catalogue/components/home/CustomHomeTableSection.vue diff --git a/resources/js/components/shared/LaunchpadSettingsModal.vue b/resources/js/components/shared/LaunchpadSettingsModal.vue index 83815151fd..0b61b33500 100755 --- a/resources/js/components/shared/LaunchpadSettingsModal.vue +++ b/resources/js/components/shared/LaunchpadSettingsModal.vue @@ -9,8 +9,7 @@ :set-custom-buttons="true" :custom-buttons="customModalButtons" @saveModal="saveModal" - @closeModal="closeModal" - > + @closeModal="closeModal">