Explorar el Código

Merge branch 'dev' into peroject-linktree-index

# Conflicts:
#	app/Http/Controllers/API/ProjectController.php
#	app/Models/Project.php
kely hace 1 año
padre
commit
c2d72c865c

+ 91 - 1
app/Http/Controllers/API/ProjectController.php

@@ -8,24 +8,34 @@ use App\Http\Requests\API\Project\LinkRequirementByPlanRequest;
 use App\Http\Requests\API\Project\LinkRequirementRequest;
 use App\Http\Requests\API\Project\PostponeRequest;
 use App\Http\Requests\API\Project\UnlinkRequirementRequest;
+use App\Http\Requests\API\Project\UpdateLinkAssetsRequest;
 use App\Http\Resources\API\AssetRequirementGroupTreeResource;
 use App\Http\Resources\API\AssetRequirementResource;
 use App\Http\Resources\API\AssetParentResource;
 use App\Http\Resources\API\ProjectAssetResource;
 use App\Http\Resources\API\ProjectDetailResource;
+use App\Http\Resources\API\ProjectGroupViewTaskResource;
+use App\Http\Resources\API\ProjectKanbanRequirementResource;
+use App\Http\Resources\API\ProjectKanbanTaskResource;
 use App\Http\Resources\API\SimplePlanResource;
 use App\Http\Resources\API\ProjectRequirementResource;
 use App\Http\Resources\API\ProjectResource;
 use App\Models\Enums\ObjectAction;
 use App\Models\Enums\ProjectStatus;
+use App\Models\Enums\TaskStatus;
 use App\Models\Plan;
 use App\Models\Project;
 use App\Models\ProjectAsset;
 use App\Models\ProjectPlan;
 use App\Models\ProjectRequirement;
 use App\Models\Requirement;
+use App\Models\Task;
+use App\Models\User;
 use App\Models\RequirementGroup;
 use App\Repositories\ActionRepository;
+use App\Services\Project\ProjectKanbanService;
+use App\Services\Project\ProjectGanttService;
+use App\Services\Project\ProjectTaskGroupViewService;
 use Illuminate\Http\Request;
 use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\DB;
@@ -306,7 +316,6 @@ class ProjectController extends Controller
         foreach ($plan->requirements as $requirement) {
             ProjectRequirement::query()->firstOrCreate([
                 'project_id' => $project->id, 'requirement_id' => $requirement->id,
-                ''
             ]);
         }
 
@@ -338,4 +347,85 @@ class ProjectController extends Controller
 
         return AssetRequirementResource::collection($requirements);
     }
+
+    public function updateLinkAssets(UpdateLinkAssetsRequest $request,string $project_id)
+    {
+        $project = Project::find($project_id);
+        if (is_null($project)){
+        return   $this->badRequest('project does not exist');
+        }
+        $assetsIds = $request->assets;
+        DB::transaction(function () use ($project_id,$assetsIds) {
+            ProjectAsset::where('project_id', $project_id)->delete();
+            foreach ($assetsIds as $assetId) {
+                ProjectAsset::create([
+                    'project_id' => $project_id,
+                    'asset_id' => $assetId,
+                ]);
+            }
+        });
+        return $this->noContent();
+    }
+
+
+    public function dynamic(Request $request, string $id)
+    {
+        $project = Project::findOrFail($id);
+
+        return $this->success([
+            'data' => ActionRepository::dynamic($project, $request->all())
+        ]);
+    }
+
+    public function kanban(Request $request, ProjectKanbanService $service, string $id)
+    {
+        $project = Project::findOrFail($id);
+
+        return $this->success([
+            'data' => $service->kanban($project, $request->get("group", "requirement_asc"))
+        ]);
+    }
+
+    public function groupView(Request $request, string $id)
+    {
+        $project = Project::findOrFail($id);
+
+        $group = in_array(
+            $request->get("group"),
+            ['requirement_id','status','assign','finished_by','closed_by','task_type']
+        ) ? $request->get("group") : "requirement_id";
+
+
+        return $this->success([
+            'data' => (new ProjectTaskGroupViewService())->groupView($project, $group, $request->all())
+        ]);
+    }
+
+    public function gantt(Request $request, string $id)
+    {
+        $project = Project::findOrFail($id);
+
+        $group = in_array(
+            $request->get("group"),
+            ['requirement_id','assign','task_type']
+        ) ? $request->get("group") : "task_type";
+
+
+        return $this->success([
+            'data' => (new ProjectGanttService())->gantt($project, $group)
+        ]);
+    }
+
+    public function printKanban(string $id)
+    {
+        $project = Project::findOrFail($id);
+
+        return $this->success([
+            'data' => [
+                'requirements' => ProjectKanbanRequirementResource::collection($project->requirements),
+                'tasks' => ProjectKanbanTaskResource::collection($project->tasks),
+                'task_status' => TaskStatus::cases(),
+            ]
+        ]);
+    }
 }

+ 0 - 1
app/Http/Controllers/API/RoleController.php

@@ -5,7 +5,6 @@ namespace App\Http\Controllers\API;
 use App\Http\Controllers\Controller;
 use App\Http\Requests\API\Role\CreateOrUpdateRequest;
 use App\Http\Resources\API\RoleResource;
-use App\Http\Resources\PermissionResource;
 use App\Models\Role;
 use Illuminate\Http\Request;
 

+ 47 - 0
app/Http/Requests/API/Project/UpdateLinkAssetsRequest.php

@@ -0,0 +1,47 @@
+<?php
+
+namespace App\Http\Requests\API\Project;
+
+use App\Models\Asset;
+use App\Models\Enums\AssetStatus;
+use App\Models\Plan;
+use App\Models\User;
+use Illuminate\Foundation\Http\FormRequest;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Validation\Rule;
+use Illuminate\Validation\Rules\Enum;
+
+class UpdateLinkAssetsRequest extends FormRequest
+{
+    /**
+     * Determine if the user is authorized to make this request.
+     */
+    public function authorize(): bool
+    {
+        return true;
+    }
+
+    /**
+     * Get the validation rules that apply to the request.
+     *
+     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
+     */
+    public function rules(): array
+    {
+        return [
+            'project_id' =>[
+                'email'
+            ],
+            'assets' => [
+                'array',
+                function ($attribute, $value, $fail) {
+                    $assetIds = Asset::query()->whereIn('id', $value)->pluck('id')->toArray();
+
+                    if (count(array_diff($value, $assetIds)) > 0) {
+                        $fail('The selected asset is invalid.');
+                    }
+                }
+            ],
+        ];
+    }
+}

+ 5 - 0
app/Http/Resources/API/AssetResource.php

@@ -30,6 +30,11 @@ class AssetResource extends JsonResource
             'latitude' => $this->latitude,
             'longitude' => $this->longitude,
             'parent_id' => $this->parent_id,
+            'parent_asset' => $this->when($this->parent !== null, function () {
+                return[
+                    new AssetParentResource($this->parent)
+                ];
+            }),
             'level' => count(explode(",", trim($this->path, ","))),
             'requirement_total' => $this->total_requirements_count,
             'plan_total' => $this->total_plans_count,

+ 24 - 0
app/Http/Resources/API/KanbanTaskResource.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace App\Http\Resources\API;
+
+use Illuminate\Http\Request;
+use Illuminate\Http\Resources\Json\JsonResource;
+
+class KanbanTaskResource extends JsonResource
+{
+    /**
+     * Transform the resource into an array.
+     *
+     * @return array<string, mixed>
+     */
+    public function toArray(Request $request): array
+    {
+        return [
+            'id' => $this->id,
+            'name' => $this->name,
+            'end' => $this->end,
+            'assign' => new UserProfileResource($this->assignTo),
+        ];
+    }
+}

+ 0 - 1
app/Http/Resources/API/PermissionGroupResource.php

@@ -2,7 +2,6 @@
 
 namespace App\Http\Resources\API;
 
-use App\Http\Resources\PermissionResource;
 use Illuminate\Http\Request;
 use Illuminate\Http\Resources\Json\JsonResource;
 

+ 1 - 1
app/Http/Resources/PermissionResource.php → app/Http/Resources/API/PermissionResource.php

@@ -1,6 +1,6 @@
 <?php
 
-namespace App\Http\Resources;
+namespace App\Http\Resources\API;
 
 use Illuminate\Http\Request;
 use Illuminate\Http\Resources\Json\JsonResource;

+ 4 - 1
app/Http/Resources/API/ProjectDetailResource.php

@@ -15,8 +15,10 @@ class ProjectDetailResource extends JsonResource
     public function toArray(Request $request): array
     {
         $assetIds = [];
+        $assets = [];
         foreach ($this->assets as $asset){
-            $assetIds[] = $asset['id'];
+            $assets[] = new AssetResource($asset);
+            $assetIds[] = $asset->id;
         }
         $plansIds = [];
         foreach ($this->plans as $plan){
@@ -35,6 +37,7 @@ class ProjectDetailResource extends JsonResource
             'longitude' => $this->longitude,
             'type' => $this->type,
             'acl' => $this->acl,
+            'assets_detail' =>$assets,
             'assets' =>$assetIds,
             'plans' =>$plansIds,
             //'whitelist' => $this->whitelist,

+ 26 - 0
app/Http/Resources/API/ProjectGanttResource.php

@@ -0,0 +1,26 @@
+<?php
+
+namespace App\Http\Resources\API;
+
+use Illuminate\Http\Request;
+use Illuminate\Http\Resources\Json\JsonResource;
+
+class ProjectGanttResource extends JsonResource
+{
+    /**
+     * Transform the resource into an array.
+     *
+     * @return array<string, mixed>
+     */
+    public function toArray(Request $request): array
+    {
+        return [
+            "id" => $this->id,
+            "name" => $this->name,
+            "status" => $this->status,
+            "assign" => new UserProfileResource($this->assignTo),
+            "begin" => (string)$this->begin,
+            "end" => (string)$this->end,
+        ];
+    }
+}

+ 27 - 0
app/Http/Resources/API/ProjectGroupViewTaskResource.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace App\Http\Resources\API;
+
+use Illuminate\Http\Request;
+use Illuminate\Http\Resources\Json\JsonResource;
+
+class ProjectGroupViewTaskResource extends JsonResource
+{
+    /**
+     * Transform the resource into an array.
+     *
+     * @return array<string, mixed>
+     */
+    public function toArray(Request $request): array
+    {
+        return [
+            "id" => $this->id,
+            "name" => $this->name,
+            "task_type" => $this->task_type,
+            "status" => $this->status,
+            "assign" => new UserProfileResource($this->assignTo),
+            "end" => $this->end,
+            "finished_by" =>  new UserProfileResource($this->finishedBy),
+        ];
+    }
+}

+ 24 - 0
app/Http/Resources/API/ProjectKanbanRequirementResource.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace App\Http\Resources\API;
+
+use Illuminate\Http\Request;
+use Illuminate\Http\Resources\Json\JsonResource;
+
+class ProjectKanbanRequirementResource extends JsonResource
+{
+    /**
+     * Transform the resource into an array.
+     *
+     * @return array<string, mixed>
+     */
+    public function toArray(Request $request): array
+    {
+        return [
+            'id' => $this->id,
+            'title' => $this->title,
+            'note' => $this->note,
+            'description' => $this->description,
+        ];
+    }
+}

+ 25 - 0
app/Http/Resources/API/ProjectKanbanTaskResource.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace App\Http\Resources\API;
+
+use Illuminate\Http\Request;
+use Illuminate\Http\Resources\Json\JsonResource;
+
+class ProjectKanbanTaskResource extends JsonResource
+{
+    /**
+     * Transform the resource into an array.
+     *
+     * @return array<string, mixed>
+     */
+    public function toArray(Request $request): array
+    {
+        return [
+            "id" => $this->id,
+            "name" => $this->name,
+            "requirement_id" => $this->requirement_id,
+            "status" => $this->status,
+            "assign" => new UserProfileResource($this->assignTo),
+        ];
+    }
+}

+ 0 - 1
app/Http/Resources/API/RoleResource.php

@@ -2,7 +2,6 @@
 
 namespace App\Http\Resources\API;
 
-use App\Http\Resources\PermissionResource;
 use Illuminate\Http\Request;
 use Illuminate\Http\Resources\Json\JsonResource;
 

+ 38 - 0
app/ModelFilters/ActionFilter.php

@@ -0,0 +1,38 @@
+<?php
+
+namespace App\ModelFilters;
+
+use Carbon\Carbon;
+use EloquentFilter\ModelFilter;
+
+class ActionFilter extends ModelFilter
+{
+    /**
+    * Related Models that have ModelFilters as well as the method on the ModelFilter
+    * As [relationMethod => [input_key1, input_key2]].
+    *
+    * @var array
+    */
+    public $relations = [];
+
+    public function user($id)
+    {
+        return $this->where("created_by", $id);
+    }
+
+    public function time($time)
+    {
+        return match ($time) {
+            "today" => $this->where("created_at", ">=", Carbon::now()->startOfDay()),
+            "yesterday" => $this->where("created_at", ">=", Carbon::now()->subDay()->startOfDay())
+                ->where("created_at", "<", Carbon::now()->startOfDay()),
+            "this_week" => $this->where("created_at", ">=", Carbon::now()->startOfWeek()),
+            "last_week" => $this->where("created_at", ">=", Carbon::now()->subWeek()->startOfWeek())
+                ->where("created_at", "<", Carbon::now()->startOfWeek()),
+            "this_month" => $this->where("created_at", ">=", Carbon::now()->startOfMonth()),
+            "last_month" => $this->where("created_at", ">=", Carbon::now()->subMonth()->startOfMonth())
+                ->where("created_at", "<", Carbon::now()->startOfMonth()),
+            default => $this,
+        };
+    }
+}

+ 9 - 0
app/ModelFilters/TaskFilter.php

@@ -24,4 +24,13 @@ class TaskFilter extends ModelFilter
     {
         return $my == "yes" ? $this->where("assign", Auth::id()) : $this;
     }
+
+    public function linkRequirement($link): TaskFilter
+    {
+        return match($link) {
+            "yes" => $this->where("requirement_id", ">", 0),
+            "no" => $this->where("requirement_id", "=", 0),
+            default => $this,
+        };
+    }
 }

+ 7 - 1
app/Models/Action.php

@@ -2,16 +2,22 @@
 
 namespace App\Models;
 
+use EloquentFilter\Filterable;
 use Illuminate\Database\Eloquent\Factories\HasFactory;
 use Illuminate\Database\Eloquent\Model;
 
 class Action extends Model
 {
-    use HasFactory;
+    use HasFactory, Filterable;
 
     protected $guarded = ['id'];
 
     protected $casts = [
         'extra_fields' => 'array',
     ];
+
+    public function createdBy(): \Illuminate\Database\Eloquent\Relations\BelongsTo
+    {
+        return $this->belongsTo(User::class, "created_by");
+    }
 }

+ 25 - 0
app/Models/Enums/ActionObjectType.php

@@ -2,6 +2,11 @@
 
 namespace App\Models\Enums;
 
+use App\Models\Asset;
+use App\Models\Plan;
+use App\Models\Project;
+use App\Models\Task;
+
 enum ActionObjectType: string
 {
     case ASSET = "asset";
@@ -9,4 +14,24 @@ enum ActionObjectType: string
     case PROJECT = "project";
 
     case TASK = "task";
+
+    case PLAN = "plan";
+
+    public function modelBuilder(): \Illuminate\Database\Eloquent\Builder
+    {
+        return match ($this) {
+            self::ASSET => Asset::query(),
+            self::PROJECT => Project::query(),
+            self::TASK => Task::query(),
+            self::PLAN => Plan::query(),
+        };
+    }
+
+    public function nameField(): string
+    {
+        return match ($this) {
+            self::ASSET, self::PROJECT, self::TASK  => "name",
+            self::PLAN => "title",
+        };
+    }
 }

+ 28 - 0
app/Models/Enums/ObjectAction.php

@@ -47,4 +47,32 @@ enum ObjectAction: string
     case CANCELED = "canceled";
 
     case ASSIGNED = "assigned";
+
+    case DELETED_FILE = "deletedFile";
+
+    case EDITED_FILE = "editedFile";
+
+    case HIDDEN = "hidden";
+
+    case COMMENTED = "commented";
+
+    case ACTIVATED = "activated";
+
+    case BLOCKED = "blocked";
+
+    case LINKED_TO_PLAN = "linkedToPlan";
+
+    case CHANGED_STATUS = "change status";
+
+    case VERIFIED = "verified";
+
+    case SUSPENDED = "suspended";
+
+    case LINK_CHILDREN_TASK = "linkChildrenTask";
+
+    case UNLINK_CHILDREN_TASK = "unlinkChildrenTask";
+
+    case BATCH_CREATE_TASK = "batchCreateTask";
+
+    case DELETE_CHILDREN_TASK = "deleteChildrenTask";
 }

+ 5 - 0
app/Models/Project.php

@@ -41,6 +41,11 @@ class Project extends Model
         return $this->hasMany(TeamMember::class);
     }
 
+    public function tasks(): \Illuminate\Database\Eloquent\Relations\HasMany
+    {
+        return $this->hasMany(Task::class);
+    }
+
     public function requirementsGroup(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
     {
         return $this->belongsToMany(RequirementGroup::class, 'project_requirement');

+ 73 - 0
app/Repositories/ActionRepository.php

@@ -6,6 +6,8 @@ use App\Models\Action;
 use App\Models\Enums\ActionObjectType;
 use App\Models\Enums\ObjectAction;
 use App\Models\Project;
+use Carbon\Carbon;
+use Illuminate\Support\Collection;
 use Illuminate\Support\Facades\Auth;
 
 class ActionRepository
@@ -46,4 +48,75 @@ class ActionRepository
             $extraFields
         );
     }
+
+    public static function dynamic(Project $project, array $filter = [])
+    {
+        $actions = Action::query()->filter($filter)
+            ->where("project_id", $project->id)
+            ->with(['createdBy'])
+            ->selectRaw("*, date_format(created_at, '%Y-%m-%d') as created_date")
+            ->orderBy("created_at")
+            ->get();
+
+        $objectNames = self::objectNamesGroupByType($actions);
+
+        $items = $actions->groupBy("created_date")->toArray();
+        krsort($items);
+
+        $data = [];
+        foreach ($items as $day => $actionItems) {
+            $dayItems = [
+                'date' => $day,
+                'items' => []
+            ];
+
+            foreach ($actionItems as $actionItem) {
+                $labelKey = sprintf("action-labels.label.%s", $actionItem['action']);
+                $objectLabelKey = sprintf("action-labels.object_type.%s", $actionItem['object_type']);
+
+                $dayItems['items'][] = [
+                    'time' => Carbon::parse($actionItem['created_at'])->toTimeString(),
+                    'created_by' => [
+                        'id' => $actionItem['created_by']['id'],
+                        'name' => $actionItem['created_by']['name'],
+                        'username' => $actionItem['created_by']['username'],
+                    ],
+                    'action_label' => app('translator')->has($labelKey) ? __($labelKey) : $actionItem['action'],
+                    'object_label' => app('translator')->has($objectLabelKey) ? __($objectLabelKey) : $actionItem['object_type'],
+                    'object_id' => $actionItem['object_id'],
+                    'object_type' => $actionItem['object_type'],
+                    'object_name' => data_get($objectNames, sprintf("%s.%d", $actionItem['object_type'], $actionItem['object_id'])),
+                ];
+            }
+
+            $data[] = $dayItems;
+        }
+
+        return $data;
+    }
+
+    /**
+     * @param Collection $actions
+     * @return array
+     */
+    protected static function objectNamesGroupByType(Collection $actions): array
+    {
+        $groupActions = $actions->groupBy("object_type");
+
+        $objectNames = [];
+        foreach ($groupActions as $group => $items) {
+            $objectType = ActionObjectType::tryFrom($group);
+
+            $actionObjectBuilder= $objectType?->modelBuilder();
+            if (! $actionObjectBuilder) {
+                continue;
+            }
+
+            $objectNames[$group] = $actionObjectBuilder
+                ->whereIn("id", $items->pluck("object_id")->unique()->toArray())
+                ->pluck($objectType->nameField(), "id");
+        }
+
+        return $objectNames;
+    }
 }

+ 63 - 0
app/Services/Project/ProjectGanttService.php

@@ -0,0 +1,63 @@
+<?php
+
+namespace App\Services\Project;
+
+use App\Http\Resources\API\ProjectGanttResource;
+use App\Http\Resources\API\ProjectGroupViewTaskResource;
+use App\Models\Enums\ProjectStatus;
+use App\Models\Project;
+use App\Models\Requirement;
+use App\Models\Task;
+use App\Models\User;
+use Illuminate\Support\Collection;
+
+class ProjectGanttService
+{
+    public function gantt(Project $project, string $group): array
+    {
+        $groupTasks = $this->getGroupTask($project, $group);
+
+        $groups = $groupTasks->keys()->filter();
+
+        $groupNamesKeyBy = $this->getGroupNamesKeyBy($groups, $group);
+
+        $items = [];
+        foreach(["", ...$groups] as $groupKey) {
+            if (! isset($groupTasks[$groupKey])) {
+                continue;
+            }
+
+            $items[] = [
+                'group' => $group,
+                'group_label' => $groupKey == "" ? ['id' => "", "name" => ""] : $groupNamesKeyBy[$groupKey],
+                'tasks' => ProjectGanttResource::collection($groupTasks[$groupKey]),
+            ];
+        }
+
+        return $items;
+    }
+
+    protected function getGroupNamesKeyBy(Collection $groups, string $group): array
+    {
+        $groupsFormat = $groups->map(fn($group) => ['id' => $group, 'name' => $group]);
+        $groupNames = match ($group) {
+            "requirement_id" => Requirement::query()->whereIn("id", $groups)->selectRaw("id,title as name")->get(),
+            "task_type" => $groupsFormat,
+            "assign" => User::query()->whereIn("id", $groups)->get(['id', 'name']),
+        };
+
+        return $groupNames->keyBy("id")->toArray();
+    }
+
+    protected function getGroupTask(
+        Project $project,
+        string $group,
+    ): \Illuminate\Database\Eloquent\Collection|\Illuminate\Support\Collection|array
+    {
+        return Task::query()
+            ->with(['assignTo', 'finishedBy'])
+            ->where("project_id", $project->id)
+            ->get()
+            ->groupBy($group);
+    }
+}

+ 70 - 0
app/Services/Project/ProjectKanbanService.php

@@ -0,0 +1,70 @@
+<?php
+
+namespace App\Services\Project;
+
+use App\Http\Resources\API\KanbanTaskResource;
+use App\Http\Resources\API\UserProfileResource;
+use App\Models\Enums\ProjectStatus;
+use App\Models\Project;
+use App\Models\Requirement;
+use App\Models\User;
+
+class ProjectKanbanService
+{
+    public function kanban(Project $project, $group = "requirement_asc")
+    {
+        $groupKey = match ($group) {
+            "assign" => "assign",
+            "finished_by" => "finished_by",
+            default => "requirement_id"
+        };
+
+        $groupTasks = $project->tasks()->with(['assignTo'])->get()->groupBy($groupKey);
+
+        $statusItems = array_column(ProjectStatus::cases(), 'value');
+
+        $items = [];
+        $groupIds = [];
+        foreach ($groupTasks as $groupId => $tasks) {
+            $groupItems = [];
+
+            foreach ($statusItems as $status) {
+                $groupItems[$status] = [];
+            }
+
+            foreach ($tasks as $task) {
+                $groupItems[$task->status][] = new KanbanTaskResource($task);
+            }
+
+            $items[$groupId ?: "empty"] = $groupItems;
+            $groupIds[] = $groupId;
+        }
+
+        return [
+            'group_data' => $this->getKanbanGroupData($groupIds, $group),
+            'group' => $group,
+            'tasks' => $items,
+            'status_items' => $statusItems,
+        ];
+    }
+
+    protected function getKanbanGroupData(array $ids, string $group)
+    {
+        $orderBy = match ($group) {
+            "requirement_asc" => ['id', 'asc'],
+            "requirement_desc" => ['id', 'desc'],
+            "requirement_priority_asc" => ['priority', 'asc'],
+            "requirement_priority_desc" => ['priority', 'desc'],
+            default => null
+        };
+
+        return match ($group) {
+            "assign", "finished_by" => UserProfileResource::collection(User::query()->whereIn("id", $ids)->get()),
+            default => Requirement::query()->whereIn("id", $ids)
+                ->when($orderBy, fn($query) => $query->orderBy(...$orderBy))
+                ->get([
+                    'id', 'title', 'priority', 'status'
+                ]),
+        };
+    }
+}

+ 95 - 0
app/Services/Project/ProjectTaskGroupViewService.php

@@ -0,0 +1,95 @@
+<?php
+
+namespace App\Services\Project;
+
+use App\Http\Resources\API\ProjectGroupViewTaskResource;
+use App\Models\Enums\ProjectStatus;
+use App\Models\Project;
+use App\Models\Requirement;
+use App\Models\Task;
+use App\Models\User;
+use Illuminate\Support\Collection;
+
+class ProjectTaskGroupViewService
+{
+    public function groupView(Project $project, string $group, array $filter = []): array
+    {
+        $groupTasks = $this->getGroupTask($project, $group, $filter);
+
+        $countItems = $this->countItems($project, $group, $filter);
+
+        $groups = $groupTasks->keys()->filter();
+
+        $groupNamesKeyBy = $this->getGroupNamesKeyBy($groups, $group);
+
+        $items = [];
+        foreach(["", ...$groups] as $groupKey) {
+            if (! isset($groupTasks[$groupKey])) {
+                continue;
+            }
+
+            $items[] = [
+                'group' => $group,
+                'group_label' => $groupKey == "" ? ['id' => "", "name" => ""] : $groupNamesKeyBy[$groupKey],
+                'count' => $countItems[$groupKey],
+                'tasks' => ProjectGroupViewTaskResource::collection($groupTasks[$groupKey]),
+            ];
+        }
+
+        return [
+            'total' => $countItems->sum("all_count"),
+            'items' => $items,
+        ];
+    }
+
+    protected function getGroupNamesKeyBy(Collection $groups, string $group): array
+    {
+        $groupsFormat = $groups->map(fn($group) => ['id' => $group, 'name' => $group]);
+        $groupNames = match ($group) {
+            "requirement_id" => Requirement::query()->whereIn("id", $groups)->selectRaw("id,title as name")->get(),
+            "status", "task_type" => $groupsFormat,
+            "assign", "finished_by", "closed_by" => User::query()->whereIn("id", $groups)->get(['id', 'name']),
+        };
+
+        return $groupNames->keyBy("id")->toArray();
+    }
+
+    protected function countItems(
+        Project $project,
+        string $group,
+        array $filter
+    ): \Illuminate\Database\Eloquent\Collection|\Illuminate\Support\Collection|array
+    {
+        return Task::query()
+            ->where("project_id", $project->id)
+            ->filter($filter)
+            ->selectRaw(
+                sprintf("
+                    %s,
+                    count(*) as all_count,
+                    count(case when status = '%s' then 1 else null end) as wait_count,
+                    count(case when status = '%s' then 1 else null end) as doing_count",
+                    $group,
+                    ProjectStatus::WAIT->value,
+                    ProjectStatus::DOING->value
+                )
+            )
+            ->groupBy($group)
+            ->get()
+            ->keyBy($group);
+    }
+
+    protected function getGroupTask(
+        Project $project,
+        string $group,
+        array $filter
+    ): \Illuminate\Database\Eloquent\Collection|\Illuminate\Support\Collection|array
+    {
+        return Task::query()
+            ->filter($filter)
+            ->with(['assignTo', 'finishedBy'])
+            ->where("project_id", $project->id)
+            ->get()
+            ->groupBy($group);
+    }
+}

+ 1 - 1
config/database.php

@@ -56,7 +56,7 @@ return [
             'collation' => 'utf8mb4_unicode_ci',
             'prefix' => '',
             'prefix_indexes' => true,
-            'strict' => true,
+            'strict' => false,
             'engine' => null,
             'options' => extension_loaded('pdo_mysql') ? array_filter([
                 PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),

+ 44 - 0
lang/en/action-labels.php

@@ -0,0 +1,44 @@
+<?php
+use \App\Models\Enums\ObjectAction;
+use \App\Models\Enums\ActionObjectType;
+
+return[
+    'object_type'=>[
+        ActionObjectType::PROJECT->value => "Project",
+        ActionObjectType::PLAN->value => "Plan",
+        ActionObjectType::TASK->value =>"Task",
+        ActionObjectType::ASSET->value =>"Asset",
+    ],
+    'label'=>[
+        ObjectAction::CREATED->value => "created",
+        ObjectAction::OPENED->value => "opened",
+        ObjectAction::CHANGED->value => "changed",
+        ObjectAction::EDITED->value => "edited",
+        ObjectAction::ASSIGNED->value => "assigned",
+        ObjectAction::CLOSED->value => "closed",
+        ObjectAction::DELETED->value => "deleted",
+        ObjectAction::DELETED_FILE->value => "deleted",
+        ObjectAction::EDITED_FILE->value => "edited",
+        ObjectAction::UNDELETED->value => "restored",
+        ObjectAction::HIDDEN->value => "restored",
+        ObjectAction::COMMENTED->value => "commented",
+        ObjectAction::ACTIVATED->value => "activated",
+        ObjectAction::BLOCKED->value => "blocked",
+        ObjectAction::REVIEWED->value => "reviewed",
+        ObjectAction::MOVED->value => "moved",
+        ObjectAction::LINKED_TO_PLAN->value => "linked to Plan",
+        ObjectAction::LINKED_TO_PLAN->value => "unlinked from",
+        ObjectAction::STARTED->value => "started",
+        ObjectAction::RESTARTED->value => "restarted",
+        ObjectAction::CHANGED_STATUS->value => "change status",
+        ObjectAction::CANCELED->value => "canceled",
+        ObjectAction::PAUSED->value => "paused",
+        ObjectAction::VERIFIED->value => "verified",
+        ObjectAction::DELAY->value => "delayed",
+        ObjectAction::SUSPENDED->value => "suspended",
+        ObjectAction::LINK_CHILDREN_TASK->value => "linked a child task",
+        ObjectAction::UNLINK_CHILDREN_TASK->value => "unlinked a child task",
+        ObjectAction::BATCH_CREATE_TASK->value => "batch created tasks",
+        ObjectAction::DELETE_CHILDREN_TASK->value => "delete children task",
+    ]
+];

+ 7 - 0
routes/api.php

@@ -44,6 +44,11 @@ Route::middleware(['auth:sanctum'])->group(function () {
         Route::patch("requirement/{plan_id}/plan", [API\RequirementController::class, "linkPlan"])->name("requirement.linkPlan");
         Route::patch("requirement/plan/unlink", [API\RequirementController::class, "unlinkPlan"])->name("requirement.unlinkPlan");
 
+        Route::get("project/{project}/dynamic", [API\ProjectController::class, "dynamic"])->name("project.dynamic");
+        Route::get("project/{project}/kanban", [API\ProjectController::class, "kanban"])->name("project.kanban");
+        Route::get("project/{project}/group-view", [API\ProjectController::class, "groupView"])->name("project.group-view");
+        Route::get("project/{project}/gantt", [API\ProjectController::class, "gantt"])->name("project.gantt");
+        Route::get("project/{project}/print-kanban", [API\ProjectController::class, "printKanban"])->name("project.print-kanban");
         Route::patch("project/{project}/closed", [API\ProjectController::class, "closed"])->name("project.closed");
         Route::patch("project/{project}/start", [API\ProjectController::class, "start"])->name("project.start");
         Route::patch("project/{project}/pause", [API\ProjectController::class, "pause"])->name("project.pause");
@@ -55,6 +60,8 @@ Route::middleware(['auth:sanctum'])->group(function () {
             ->name("project.unlink-requirement");
         Route::patch("project/{project}/link-requirement-by-plan", [API\ProjectController::class, "linkRequirementByPlan"])
             ->name("project.link-requirement-by-plan");
+        Route::patch("project/{project_id}/update-link-assets", [API\ProjectController::class, "updateLinkAssets"])
+            ->name("project.update-link-assets");
 
         Route::get("project/{project}/plan", [API\ProjectController::class, "plan"])
             ->name("project.plan"); //项目关联计划