Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
100.00% |
73 / 73 |
|
100.00% |
2 / 2 |
CRAP | |
100.00% |
1 / 1 |
| ProjectGanttDataTransformer | |
100.00% |
72 / 72 |
|
100.00% |
2 / 2 |
15 | |
100.00% |
1 / 1 |
| transform | |
100.00% |
64 / 64 |
|
100.00% |
1 / 1 |
8 | |||
| resolveStatusColor | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
7 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | /** @license For full copyright and license information, please see the LICENSE.md file. */ |
| 6 | |
| 7 | namespace App\Modules\Projects\Application\Service; |
| 8 | |
| 9 | defined('AMMONLY_APP') || exit('Direct script access is forbidden.'); |
| 10 | |
| 11 | use App\Modules\Projects\Domain\Model\Project; |
| 12 | use App\Modules\Projects\Domain\Model\ProjectStage; |
| 13 | use App\Modules\Projects\Domain\Model\ProjectTask; |
| 14 | |
| 15 | /** |
| 16 | * Transforms Project, Stages, and Tasks into standard hierarchical Gantt chart dataset. |
| 17 | * |
| 18 | * @package App\Modules\Projects\Application\Service |
| 19 | */ |
| 20 | final class ProjectGanttDataTransformer |
| 21 | { |
| 22 | private const TYPE_STAGE = 'stage'; |
| 23 | private const TYPE_MILESTONE = 'milestone'; |
| 24 | private const TYPE_TASK = 'task'; |
| 25 | |
| 26 | /** |
| 27 | * Transforms project hierarchy into interactive Gantt chart payload. |
| 28 | * |
| 29 | * @param Project $project Project domain aggregate. |
| 30 | * @param list<ProjectStage> $stages List of project stages. |
| 31 | * @param list<ProjectTask> $tasks List of project tasks. |
| 32 | * @return array{ |
| 33 | * project: array<string, mixed>, |
| 34 | * items: list<array<string, mixed>>, |
| 35 | * links: list<array<string, mixed>> |
| 36 | * } |
| 37 | */ |
| 38 | public function transform(Project $project, array $stages, array $tasks): array |
| 39 | { |
| 40 | $items = []; |
| 41 | $links = []; |
| 42 | |
| 43 | foreach ($stages as $stage) { |
| 44 | $stageId = 'stage_' . $stage->id; |
| 45 | $isMilestone = $stage->isMilestone; |
| 46 | $items[] = [ |
| 47 | 'id' => $stageId, |
| 48 | 'raw_id' => $stage->id, |
| 49 | 'type' => $isMilestone ? self::TYPE_MILESTONE : self::TYPE_STAGE, |
| 50 | 'text' => $stage->name, |
| 51 | 'start_date' => $stage->startDate ?? $project->startDate ?? date('Y-m-d'), |
| 52 | 'end_date' => $stage->endDate, |
| 53 | 'progress' => $stage->progress, |
| 54 | 'parent' => $stage->parentId !== null ? 'stage_' . $stage->parentId : '0', |
| 55 | 'open' => true, |
| 56 | 'is_milestone' => $isMilestone, |
| 57 | 'status' => $stage->status, |
| 58 | 'color' => $this->resolveStatusColor($stage->status), |
| 59 | ]; |
| 60 | } |
| 61 | |
| 62 | foreach ($tasks as $task) { |
| 63 | $taskId = 'task_' . $task->id; |
| 64 | $parentStage = 'stage_' . $task->stageId; |
| 65 | $parent = $task->parentTaskId !== null ? 'task_' . $task->parentTaskId : $parentStage; |
| 66 | |
| 67 | $items[] = [ |
| 68 | 'id' => $taskId, |
| 69 | 'raw_id' => $task->id, |
| 70 | 'type' => self::TYPE_TASK, |
| 71 | 'text' => $task->name, |
| 72 | 'start_date' => $task->startDate, |
| 73 | 'end_date' => $task->endDate, |
| 74 | 'actual_end_date' => $task->actualEndDate, |
| 75 | 'progress' => (float) $task->progress, |
| 76 | 'parent' => $parent, |
| 77 | 'open' => false, |
| 78 | 'status' => $task->status, |
| 79 | 'priority' => $task->priority, |
| 80 | 'estimated_hours' => $task->estimatedHours, |
| 81 | 'actual_hours' => $task->actualHours, |
| 82 | 'owner' => $task->owner, |
| 83 | 'color' => $this->resolveStatusColor($task->status), |
| 84 | ]; |
| 85 | |
| 86 | if ($task->dependsOnTaskId !== null && $task->dependsOnTaskId > 0) { |
| 87 | $links[] = [ |
| 88 | 'id' => 'link_' . $task->dependsOnTaskId . '_' . $task->id, |
| 89 | 'source' => 'task_' . $task->dependsOnTaskId, |
| 90 | 'target' => $taskId, |
| 91 | 'type' => '0', // Finish-to-Start (FS) |
| 92 | ]; |
| 93 | } |
| 94 | } |
| 95 | |
| 96 | return [ |
| 97 | 'project' => [ |
| 98 | 'id' => $project->id, |
| 99 | 'name' => $project->name, |
| 100 | 'status' => $project->status, |
| 101 | 'start_date' => $project->startDate, |
| 102 | 'target_end_date' => $project->targetEndDate, |
| 103 | 'actual_end_date' => $project->actualEndDate, |
| 104 | 'progress' => $project->progress, |
| 105 | 'estimated_hours' => $project->estimatedHours, |
| 106 | 'actual_hours' => $project->actualHours, |
| 107 | 'color' => $project->color, |
| 108 | ], |
| 109 | 'items' => $items, |
| 110 | 'links' => $links, |
| 111 | ]; |
| 112 | } |
| 113 | |
| 114 | /** |
| 115 | * Resolves status badge or bar hex color for Gantt items. |
| 116 | * |
| 117 | * @param string $status Entity status string. |
| 118 | * @return string Hex color code. |
| 119 | */ |
| 120 | private function resolveStatusColor(string $status): string |
| 121 | { |
| 122 | return match ($status) { |
| 123 | 'completed' => '#2fb344', |
| 124 | 'in_progress' => '#206bc4', |
| 125 | 'in_acceptance' => '#ae3ec9', |
| 126 | 'on_hold' => '#f59f00', |
| 127 | 'cancelled' => '#6c757d', |
| 128 | default => '#4299e1', |
| 129 | }; |
| 130 | } |
| 131 | } |