<?php

/**
 * ---------------------------------------------------------------------
 *
 * GLPI - Gestionnaire Libre de Parc Informatique
 *
 * http://glpi-project.org
 *
 * @copyright 2015-2026 Teclib' and contributors.
 * @copyright 2003-2014 by the INDEPNET Development Team.
 * @licence   https://www.gnu.org/licenses/gpl-3.0.html
 *
 * ---------------------------------------------------------------------
 *
 * LICENSE
 *
 * This file is part of GLPI.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * ---------------------------------------------------------------------
 */

namespace Glpi\Api\HL\Controller;

use Budget;
use Change;
use Contact;
use Entity;
use Glpi\Api\HL\Doc as Doc;
use Glpi\Api\HL\Middleware\ResultFormatterMiddleware;
use Glpi\Api\HL\ResourceAccessor;
use Glpi\Api\HL\Route;
use Glpi\Api\HL\RouteVersion;
use Glpi\Http\Request;
use Glpi\Http\Response;
use Group;
use Item_Project;
use Itil_Project;
use Problem;
use Project;
use ProjectCost;
use ProjectState;
use ProjectTask;
use ProjectTaskTeam;
use ProjectTaskType;
use ProjectTeam;
use ProjectType;
use Session;
use Supplier;
use Ticket;
use User;

#[Route(path: '/Project', requirements: [
    'project_id' => '\d+',
], tags: ['Project'])]
#[Doc\Route(
    parameters: [
        new Doc\Parameter(
            name: 'project_id',
            schema: new Doc\Schema(type: Doc\Schema::TYPE_STRING),
            description: 'Project ID',
            location: Doc\Parameter::LOCATION_PATH
        ),
    ]
)]
final class ProjectController extends AbstractController
{
    protected static function getRawKnownSchemas(): array
    {
        return [
            'Project' => [
                'x-version-introduced' => '2.0',
                'x-itemtype' => Project::class,
                'type' => Doc\Schema::TYPE_OBJECT,
                'x-rights-conditions' => [ // Object-level extra permissions
                    'read' => static function () {
                        if (!Session::haveRight(Project::$rightname, Project::READALL)) {
                            if (!Session::haveRight(Project::$rightname, Project::READMY)) {
                                return false; // Deny reading
                            }
                            $criteria = [
                                'LEFT JOIN' => [
                                    'glpi_projectteams' => [
                                        'ON' => [
                                            'glpi_projectteams' => 'projects_id',
                                            '_' => 'id',
                                        ],
                                    ],
                                ],
                                'WHERE' => [
                                    'OR' => [
                                        '_.users_id' => Session::getLoginUserID(),
                                        [
                                            "glpi_projectteams.itemtype"   => 'User',
                                            "glpi_projectteams.items_id"   => Session::getLoginUserID(),
                                        ],
                                    ],
                                ],
                            ];
                            if (count($_SESSION['glpigroups'])) {
                                $criteria['WHERE']['OR'][] = [
                                    '_.groups_id' => $_SESSION['glpigroups'],
                                ];
                                $criteria['WHERE']['OR'][] = [
                                    "glpi_projectteams.itemtype"   => 'Group',
                                    "glpi_projectteams.items_id"   => $_SESSION['glpigroups'],
                                ];
                            }
                            return $criteria;
                        }
                        return true; // Allow reading by default. No extra SQL conditions needed.
                    },
                ],
                'properties' => [
                    'id' => [
                        'type' => Doc\Schema::TYPE_INTEGER,
                        'format' => Doc\Schema::FORMAT_INTEGER_INT64,
                        'readOnly' => true,
                    ],
                    'name' => ['type' => Doc\Schema::TYPE_STRING],
                    'comment' => ['type' => Doc\Schema::TYPE_STRING],
                    'content' => ['type' => Doc\Schema::TYPE_STRING, 'format' => Doc\Schema::FORMAT_STRING_HTML],
                    'code' => ['type' => Doc\Schema::TYPE_STRING],
                    'priority' => [
                        'type' => Doc\Schema::TYPE_INTEGER,
                        'enum' => [1, 2, 3, 4, 5, 6],
                        'description' => <<<EOT
                            - 1: Very Low
                            - 2: Low
                            - 3: Medium
                            - 4: High
                            - 5: Very High
                            - 6: Major
                            EOT,
                    ],
                    'entity' => self::getDropdownTypeSchema(class: Entity::class, full_schema: 'Entity'),
                    'tasks' => [
                        'type' => Doc\Schema::TYPE_ARRAY,
                        'items' => [
                            'type' => Doc\Schema::TYPE_OBJECT,
                            'x-full-schema' => 'ProjectTask',
                            'x-join' => [
                                'table' => 'glpi_projecttasks',
                                'fkey' => 'id',
                                'field' => 'projects_id',
                                'primary-property' => 'id',
                            ],
                            'properties' => [
                                'id' => [
                                    'type' => Doc\Schema::TYPE_INTEGER,
                                    'format' => Doc\Schema::FORMAT_INTEGER_INT64,
                                    'readOnly' => true,
                                ],
                                'name' => ['type' => Doc\Schema::TYPE_STRING],
                                'comment' => ['type' => Doc\Schema::TYPE_STRING],
                                'content' => ['type' => Doc\Schema::TYPE_STRING],
                            ],
                        ],
                    ],
                    'costs' => [
                        'x-version-introduced' => '2.3.0',
                        'type' => Doc\Schema::TYPE_ARRAY,
                        'items' => [
                            'type' => Doc\Schema::TYPE_OBJECT,
                            'x-full-schema' => 'ProjectCost',
                            'x-join' => [
                                'table' => ProjectCost::getTable(),
                                'fkey' => 'id',
                                'field' => Project::getForeignKeyField(),
                                'primary-property' => 'id',
                            ],
                            'properties' => [
                                'id' => [
                                    'type' => Doc\Schema::TYPE_INTEGER,
                                    'format' => Doc\Schema::FORMAT_INTEGER_INT64,
                                    'readOnly' => true,
                                ],
                            ],
                        ],
                    ],
                    'status' => self::getDropdownTypeSchema(class: ProjectState::class, full_schema: 'ProjectState') + ['x-version-introduced' => '2.3'],
                    'is_recursive' => ['type' => Doc\Schema::TYPE_BOOLEAN, 'x-version-introduced' => '2.3'],
                    'parent' => self::getDropdownTypeSchema(class: Project::class, full_schema: 'Project') + ['x-version-introduced' => '2.3'],
                    'type' => self::getDropdownTypeSchema(class: ProjectType::class, full_schema: 'ProjectType') + ['x-version-introduced' => '2.3'],
                    'date' => ['type' => Doc\Schema::TYPE_STRING, 'format' => Doc\Schema::FORMAT_STRING_DATE_TIME, 'x-version-introduced' => '2.3'],
                    'date_creation' => ['type' => Doc\Schema::TYPE_STRING, 'format' => Doc\Schema::FORMAT_STRING_DATE_TIME, 'x-version-introduced' => '2.3'],
                    'date_mod' => ['type' => Doc\Schema::TYPE_STRING, 'format' => Doc\Schema::FORMAT_STRING_DATE_TIME, 'x-version-introduced' => '2.3'],
                    'user' => self::getDropdownTypeSchema(class: 'User', full_schema: 'User') + ['x-version-introduced' => '2.3'],
                    'group' => self::getDropdownTypeSchema(class: 'Group', full_schema: 'Group') + ['x-version-introduced' => '2.3'],
                    'plan_start_date' => ['type' => Doc\Schema::TYPE_STRING, 'format' => Doc\Schema::FORMAT_STRING_DATE_TIME, 'x-version-introduced' => '2.3'],
                    'plan_end_date' => ['type' => Doc\Schema::TYPE_STRING, 'format' => Doc\Schema::FORMAT_STRING_DATE_TIME, 'x-version-introduced' => '2.3'],
                    'real_start_date' => ['type' => Doc\Schema::TYPE_STRING, 'format' => Doc\Schema::FORMAT_STRING_DATE_TIME, 'x-version-introduced' => '2.3'],
                    'real_end_date' => ['type' => Doc\Schema::TYPE_STRING, 'format' => Doc\Schema::FORMAT_STRING_DATE_TIME, 'x-version-introduced' => '2.3'],
                    'percent_done' => [
                        'type' => Doc\Schema::TYPE_INTEGER,
                        'x-version-introduced' => '2.3',
                        'minimum' => 0,
                        'maximum' => 100,
                    ],
                    'auto_percent_done' => ['type' => Doc\Schema::TYPE_BOOLEAN, 'x-version-introduced' => '2.3'],
                    'show_on_global_gantt' => ['type' => Doc\Schema::TYPE_BOOLEAN, 'x-version-introduced' => '2.3'],
                    'is_deleted' => ['type' => Doc\Schema::TYPE_BOOLEAN, 'x-version-introduced' => '2.3'],
                    'template_name' => ['type' => Doc\Schema::TYPE_STRING, 'x-version-introduced' => '2.3'],
                    'is_template' => ['type' => Doc\Schema::TYPE_BOOLEAN, 'x-version-introduced' => '2.3'],
                    'tickets' => [
                        'x-version-introduced' => '2.3',
                        'type' => Doc\Schema::TYPE_ARRAY,
                        'items' => [
                            'type' => Doc\Schema::TYPE_OBJECT,
                            'x-full-schema' => 'Ticket',
                            'x-join' => [
                                'table' => Ticket::getTable(),
                                'fkey' => 'items_id',
                                'field' => 'id',
                                'primary-property' => 'id',
                                'ref-join' => [
                                    'table' => Itil_Project::getTable(),
                                    'fkey' => 'id',
                                    'field' => 'projects_id',
                                    'condition' => [
                                        'itemtype' => Ticket::class,
                                    ],
                                ],
                            ],
                            'properties' => [
                                'id' => [
                                    'type' => Doc\Schema::TYPE_INTEGER,
                                    'format' => Doc\Schema::FORMAT_INTEGER_INT64,
                                    'readOnly' => true,
                                ],
                            ],
                        ],
                    ],
                    'changes' => [
                        'x-version-introduced' => '2.3',
                        'type' => Doc\Schema::TYPE_ARRAY,
                        'items' => [
                            'type' => Doc\Schema::TYPE_OBJECT,
                            'x-full-schema' => 'Change',
                            'x-join' => [
                                'table' => Change::getTable(),
                                'fkey' => 'items_id',
                                'field' => 'id',
                                'primary-property' => 'id',
                                'ref-join' => [
                                    'table' => Itil_Project::getTable(),
                                    'fkey' => 'id',
                                    'field' => 'projects_id',
                                    'condition' => [
                                        'itemtype' => Change::class,
                                    ],
                                ],
                            ],
                            'properties' => [
                                'id' => [
                                    'type' => Doc\Schema::TYPE_INTEGER,
                                    'format' => Doc\Schema::FORMAT_INTEGER_INT64,
                                    'readOnly' => true,
                                ],
                            ],
                        ],
                    ],
                    'problems' => [
                        'x-version-introduced' => '2.3',
                        'type' => Doc\Schema::TYPE_ARRAY,
                        'items' => [
                            'type' => Doc\Schema::TYPE_OBJECT,
                            'x-full-schema' => 'Problem',
                            'x-join' => [
                                'table' => Problem::getTable(),
                                'fkey' => 'items_id',
                                'field' => 'id',
                                'primary-property' => 'id',
                                'ref-join' => [
                                    'table' => Itil_Project::getTable(),
                                    'fkey' => 'id',
                                    'field' => 'projects_id',
                                    'condition' => [
                                        'itemtype' => Problem::class,
                                    ],
                                ],
                            ],
                            'properties' => [
                                'id' => [
                                    'type' => Doc\Schema::TYPE_INTEGER,
                                    'format' => Doc\Schema::FORMAT_INTEGER_INT64,
                                    'readOnly' => true,
                                ],
                            ],
                        ],
                    ],
                    'team' => [
                        'x-version-introduced' => '2.3',
                        'type' => Doc\Schema::TYPE_ARRAY,
                        'items' => [
                            'type' => Doc\Schema::TYPE_OBJECT,
                            'x-full-schema' => 'ProjectTeamMember',
                            'x-join' => [
                                'table' => ProjectTeam::getTable(),
                                'fkey' => 'id',
                                'field' => 'projects_id',
                                'primary-property' => 'id',
                            ],
                            'properties' => [
                                'id' => [
                                    'type' => Doc\Schema::TYPE_INTEGER,
                                    'format' => Doc\Schema::FORMAT_INTEGER_INT64,
                                    'readOnly' => true,
                                ],
                                'itemtype' => [
                                    'type' => Doc\Schema::TYPE_STRING,
                                    'enum' => [User::class, Group::class, Supplier::class, Contact::class],
                                    'readOnly' => true,
                                ],
                                'items_id' => [
                                    'type' => Doc\Schema::TYPE_INTEGER,
                                    'format' => Doc\Schema::FORMAT_INTEGER_INT64,
                                    'readOnly' => true,
                                ],
                            ],
                        ],
                    ],
                ],
            ],
            'ProjectTask' => [
                'x-version-introduced' => '2.0',
                'x-itemtype' => ProjectTask::class,
                'type' => Doc\Schema::TYPE_OBJECT,
                'x-rights-conditions' => [ // Object-level extra permissions
                    'read' => static function () {
                        if (!Session::haveRight(Project::$rightname, Project::READALL)) {
                            if (!Session::haveRight(Project::$rightname, Project::READMY)) {
                                return false; // Deny reading
                            }
                            $project_criteria = [
                                'LEFT JOIN' => [
                                    'glpi_projectteams' => [
                                        'ON' => [
                                            'glpi_projectteams' => 'projects_id',
                                            'project' => 'id',
                                        ],
                                    ],
                                ],
                                'WHERE' => [
                                    'OR' => [
                                        '_.users_id' => Session::getLoginUserID(),
                                        [
                                            "glpi_projectteams.itemtype"   => 'User',
                                            "glpi_projectteams.items_id"   => Session::getLoginUserID(),
                                        ],
                                    ],
                                ],
                            ];
                            if (count($_SESSION['glpigroups'])) {
                                $project_criteria['WHERE']['OR'][] = [
                                    'project.groups_id' => $_SESSION['glpigroups'],
                                ];
                                $project_criteria['WHERE']['OR'][] = [
                                    "glpi_projectteams.itemtype"   => 'Group',
                                    "glpi_projectteams.items_id"   => $_SESSION['glpigroups'],
                                ];
                            }

                            $criteria = [
                                'LEFT JOIN' => [
                                    'glpi_projecttaskteams' => [
                                        'ON' => [
                                            'glpi_projecttaskteams' => 'projecttasks_id',
                                            'project' => 'id',
                                        ],
                                    ],
                                ] + $project_criteria['LEFT JOIN'],
                                'WHERE' => [
                                    'OR' => [
                                        '_.users_id' => Session::getLoginUserID(),
                                        $project_criteria['WHERE'],
                                        [
                                            'glpi_projecttaskteams.items_id' => Session::getLoginUserID(),
                                            'glpi_projecttaskteams.itemtype' => 'User',
                                        ],
                                    ],
                                ],
                            ];
                            if (count($_SESSION['glpigroups'])) {
                                $criteria['WHERE']['OR'][] = [
                                    'glpi_projecttaskteams.items_id' => $_SESSION['glpigroups'],
                                    'glpi_projecttaskteams.itemtype' => 'Group',
                                ];
                            }
                            return $criteria;
                        }
                        return true; // Allow reading by default. No extra SQL conditions needed.
                    },
                ],
                'properties' => [
                    'id' => [
                        'type' => Doc\Schema::TYPE_INTEGER,
                        'format' => Doc\Schema::FORMAT_INTEGER_INT64,
                        'readOnly' => true,
                    ],
                    'name' => ['type' => Doc\Schema::TYPE_STRING],
                    'comment' => ['type' => Doc\Schema::TYPE_STRING],
                    'content' => ['type' => Doc\Schema::TYPE_STRING, 'format' => Doc\Schema::FORMAT_STRING_HTML],
                    'project' => self::getDropdownTypeSchema(class: Project::class, full_schema: 'Project'),
                    'parent_task' => self::getDropdownTypeSchema(class: ProjectTask::class, full_schema: 'ProjectTask'),
                    'status' => self::getDropdownTypeSchema(class: ProjectState::class, full_schema: 'ProjectState') + ['x-version-introduced' => '2.3'],
                    'entity' => self::getDropdownTypeSchema(class: Entity::class, full_schema: 'Entity') + ['x-version-introduced' => '2.3'],
                    'is_recursive' => ['type' => Doc\Schema::TYPE_BOOLEAN, 'x-version-introduced' => '2.3'],
                    'is_deleted' => ['type' => Doc\Schema::TYPE_BOOLEAN, 'default' => false, 'x-version-introduced' => '2.3'],
                    'date_creation' => ['type' => Doc\Schema::TYPE_STRING, 'format' => Doc\Schema::FORMAT_STRING_DATE_TIME, 'x-version-introduced' => '2.3'],
                    'date_mod' => ['type' => Doc\Schema::TYPE_STRING, 'format' => Doc\Schema::FORMAT_STRING_DATE_TIME, 'x-version-introduced' => '2.3'],
                    'plan_start_date' => ['type' => Doc\Schema::TYPE_STRING, 'format' => Doc\Schema::FORMAT_STRING_DATE_TIME, 'x-version-introduced' => '2.3'],
                    'plan_end_date' => ['type' => Doc\Schema::TYPE_STRING, 'format' => Doc\Schema::FORMAT_STRING_DATE_TIME, 'x-version-introduced' => '2.3'],
                    'real_start_date' => ['type' => Doc\Schema::TYPE_STRING, 'format' => Doc\Schema::FORMAT_STRING_DATE_TIME, 'x-version-introduced' => '2.3'],
                    'real_end_date' => ['type' => Doc\Schema::TYPE_STRING, 'format' => Doc\Schema::FORMAT_STRING_DATE_TIME, 'x-version-introduced' => '2.3'],
                    'planned_duration' => [
                        'type' => Doc\Schema::TYPE_INTEGER,
                        'format' => Doc\Schema::FORMAT_INTEGER_INT64,
                        'x-version-introduced' => '2.3',
                        'description' => 'Planned duration in seconds',
                    ],
                    'real_duration' => [
                        'type' => Doc\Schema::TYPE_INTEGER,
                        'format' => Doc\Schema::FORMAT_INTEGER_INT64,
                        'x-version-introduced' => '2.3',
                        'x-field' => 'effective_duration',
                        'description' => 'Real duration in seconds',
                    ],
                    'auto_status' => [
                        'type' => Doc\Schema::TYPE_BOOLEAN,
                        'default' => false,
                        'x-version-introduced' => '2.3',
                        'x-field' => 'auto_projectstates',
                        'description' => 'If true, the status of the task will be automatically updated based on the percent done and the global configuration for project task states',
                    ],
                    'type' => self::getDropdownTypeSchema(class: ProjectTaskType::class, full_schema: 'ProjectTaskType') + ['x-version-introduced' => '2.3'],
                    'user' => self::getDropdownTypeSchema(class: 'User', full_schema: 'User') + ['x-version-introduced' => '2.3'],
                    'percent_done' => [
                        'type' => Doc\Schema::TYPE_INTEGER,
                        'x-version-introduced' => '2.3',
                        'minimum' => 0,
                        'maximum' => 100,
                    ],
                    'auto_percent_done' => ['type' => Doc\Schema::TYPE_BOOLEAN, 'default' => false, 'x-version-introduced' => '2.3'],
                    'is_milestone' => ['type' => Doc\Schema::TYPE_BOOLEAN, 'default' => false, 'x-version-introduced' => '2.3'],
                    'team' => [
                        'x-version-introduced' => '2.3',
                        'type' => Doc\Schema::TYPE_ARRAY,
                        'items' => [
                            'type' => Doc\Schema::TYPE_OBJECT,
                            'x-full-schema' => 'ProjectTaskTeamMember',
                            'x-join' => [
                                'table' => ProjectTaskTeam::getTable(),
                                'fkey' => 'id',
                                'field' => 'projecttasks_id',
                                'primary-property' => 'id',
                            ],
                            'properties' => [
                                'id' => [
                                    'type' => Doc\Schema::TYPE_INTEGER,
                                    'format' => Doc\Schema::FORMAT_INTEGER_INT64,
                                    'readOnly' => true,
                                ],
                                'itemtype' => [
                                    'type' => Doc\Schema::TYPE_STRING,
                                    'enum' => [User::class, Group::class, Supplier::class, Contact::class],
                                    'readOnly' => true,
                                ],
                                'items_id' => [
                                    'type' => Doc\Schema::TYPE_INTEGER,
                                    'format' => Doc\Schema::FORMAT_INTEGER_INT64,
                                    'readOnly' => true,
                                ],
                            ],
                        ],
                    ],
                ],
            ],
            'ProjectCost' => [
                'x-version-introduced' => '2.3',
                'x-itemtype' => ProjectCost::class,
                'type' => Doc\Schema::TYPE_OBJECT,
                'properties' => [
                    'id' => [
                        'type' => Doc\Schema::TYPE_INTEGER,
                        'format' => Doc\Schema::FORMAT_INTEGER_INT64,
                        'readOnly' => true,
                    ],
                    'project' => self::getDropdownTypeSchema(class: Project::class, full_schema: 'Project'),
                    'name' => ['type' => Doc\Schema::TYPE_STRING, 'maxLength' => 255],
                    'comment' => ['type' => Doc\Schema::TYPE_STRING],
                    'date_begin' => [
                        'type' => Doc\Schema::TYPE_STRING,
                        'format' => Doc\Schema::FORMAT_STRING_DATE_TIME,
                        'x-field' => 'begin_date',
                    ],
                    'date_end' => [
                        'type' => Doc\Schema::TYPE_STRING,
                        'format' => Doc\Schema::FORMAT_STRING_DATE_TIME,
                        'x-field' => 'end_date',
                    ],
                    'cost' => ['type' => Doc\Schema::TYPE_NUMBER, 'format' => Doc\Schema::FORMAT_NUMBER_FLOAT, 'minimum' => 0],
                    'budget' => self::getDropdownTypeSchema(class: Budget::class, full_schema: 'Budget'),
                    'entity' => self::getDropdownTypeSchema(class: Entity::class, full_schema: 'Entity'),
                    'is_recursive' => ['type' => Doc\Schema::TYPE_BOOLEAN],
                ],
            ],
            'Item_Project' => [
                'x-version-introduced' => '2.3',
                'x-itemtype' => Item_Project::class,
                'type' => Doc\Schema::TYPE_OBJECT,
                'properties' => [
                    'id' => [
                        'type' => Doc\Schema::TYPE_INTEGER,
                        'format' => Doc\Schema::FORMAT_INTEGER_INT64,
                        'readOnly' => true,
                    ],
                    'project' => self::getDropdownTypeSchema(class: Project::class, full_schema: 'Project'),
                    'itemtype' => ['type' => Doc\Schema::TYPE_STRING, 'maxLength' => 100],
                    'items_id' => ['type' => Doc\Schema::TYPE_INTEGER, 'format' => Doc\Schema::FORMAT_INTEGER_INT64],
                ],
            ],
            'ITIL_Project' => [
                'x-version-introduced' => '2.3',
                'x-itemtype' => Itil_Project::class,
                'type' => Doc\Schema::TYPE_OBJECT,
                'properties' => [
                    'id' => [
                        'type' => Doc\Schema::TYPE_INTEGER,
                        'format' => Doc\Schema::FORMAT_INTEGER_INT64,
                        'readOnly' => true,
                    ],
                    'project' => self::getDropdownTypeSchema(class: Project::class, full_schema: 'Project'),
                    'itemtype' => [
                        'type' => Doc\Schema::TYPE_STRING,
                        'enum' => [
                            Ticket::class,
                            Change::class,
                            Problem::class,
                        ],
                    ],
                    'items_id' => ['type' => Doc\Schema::TYPE_INTEGER, 'format' => Doc\Schema::FORMAT_INTEGER_INT64],
                ],
            ],
            'ProjectTeamMember' => [
                'x-version-introduced' => '2.3',
                'x-itemtype' => ProjectTeam::class,
                'type' => Doc\Schema::TYPE_OBJECT,
                'properties' => [
                    'id' => [
                        'type' => Doc\Schema::TYPE_INTEGER,
                        'format' => Doc\Schema::FORMAT_INTEGER_INT64,
                        'readOnly' => true,
                    ],
                    'project' => self::getDropdownTypeSchema(class: Project::class, full_schema: 'Project') + ['required' => true],
                    'itemtype' => [
                        'type' => Doc\Schema::TYPE_STRING,
                        'enum' => [User::class, Group::class, Supplier::class, Contact::class],
                        'required' => true,
                    ],
                    'items_id' => [
                        'type' => Doc\Schema::TYPE_INTEGER,
                        'format' => Doc\Schema::FORMAT_INTEGER_INT64,
                        'required' => true,
                    ],
                ],
            ],
            'ProjectTaskTeamMember' => [
                'x-version-introduced' => '2.3',
                'x-itemtype' => ProjectTaskTeam::class,
                'type' => Doc\Schema::TYPE_OBJECT,
                'properties' => [
                    'id' => [
                        'type' => Doc\Schema::TYPE_INTEGER,
                        'format' => Doc\Schema::FORMAT_INTEGER_INT64,
                        'readOnly' => true,
                    ],
                    'task' => self::getDropdownTypeSchema(class: ProjectTask::class, full_schema: 'ProjectTask') + ['required' => true],
                    'itemtype' => [
                        'type' => Doc\Schema::TYPE_STRING,
                        'enum' => [User::class, Group::class, Supplier::class, Contact::class],
                        'required' => true,
                    ],
                    'items_id' => [
                        'type' => Doc\Schema::TYPE_INTEGER,
                        'format' => Doc\Schema::FORMAT_INTEGER_INT64,
                        'required' => true,
                    ],
                ],
            ],
        ];
    }

    #[Route(path: '/', methods: ['GET'], middlewares: [ResultFormatterMiddleware::class])]
    #[RouteVersion(introduced: '2.0')]
    #[Doc\SearchRoute(schema_name: 'Project')]
    public function searchProjects(Request $request): Response
    {
        return ResourceAccessor::searchBySchema($this->getKnownSchema('Project', $this->getAPIVersion($request)), $request->getParameters());
    }

    #[Route(path: '/{id}', methods: ['GET'], requirements: ['id' => '\d+'], middlewares: [ResultFormatterMiddleware::class])]
    #[RouteVersion(introduced: '2.0')]
    #[Doc\GetRoute(schema_name: 'Project')]
    public function getProject(Request $request): Response
    {
        return ResourceAccessor::getOneBySchema($this->getKnownSchema('Project', $this->getAPIVersion($request)), $request->getAttributes(), $request->getParameters());
    }

    #[Route(path: '/', methods: ['POST'])]
    #[RouteVersion(introduced: '2.0')]
    #[Doc\CreateRoute(schema_name: 'Project')]
    public function createProject(Request $request): Response
    {
        return ResourceAccessor::createBySchema($this->getKnownSchema('Project', $this->getAPIVersion($request)), $request->getParameters(), [self::class, 'getProject']);
    }

    #[Route(path: '/{id}', methods: ['PATCH'], requirements: ['id' => '\d+'])]
    #[RouteVersion(introduced: '2.0')]
    #[Doc\UpdateRoute(schema_name: 'Project')]
    public function updateProject(Request $request): Response
    {
        return ResourceAccessor::updateBySchema($this->getKnownSchema('Project', $this->getAPIVersion($request)), $request->getAttributes(), $request->getParameters());
    }

    #[Route(path: '/{id}', methods: ['DELETE'], requirements: ['id' => '\d+'])]
    #[RouteVersion(introduced: '2.0')]
    #[Doc\DeleteRoute(schema_name: 'Project')]
    public function deleteProject(Request $request): Response
    {
        return ResourceAccessor::deleteBySchema($this->getKnownSchema('Project', $this->getAPIVersion($request)), $request->getAttributes(), $request->getParameters());
    }

    #[Route(path: '/Task', methods: ['GET'], middlewares: [ResultFormatterMiddleware::class])]
    #[RouteVersion(introduced: '2.0')]
    #[Doc\SearchRoute(
        schema_name: 'ProjectTask',
        description: 'List or search project tasks'
    )]
    public function searchTasks(Request $request): Response
    {
        return ResourceAccessor::searchBySchema($this->getKnownSchema('ProjectTask', $this->getAPIVersion($request)), $request->getParameters());
    }

    #[Route(path: '/Task/{id}', methods: ['GET'], requirements: ['id' => '\d+'], middlewares: [ResultFormatterMiddleware::class])]
    #[RouteVersion(introduced: '2.0')]
    #[Doc\GetRoute(
        schema_name: 'ProjectTask',
        description: 'Get a project task by ID'
    )]
    public function getTask(Request $request): Response
    {
        return ResourceAccessor::getOneBySchema($this->getKnownSchema('ProjectTask', $this->getAPIVersion($request)), $request->getAttributes(), $request->getParameters());
    }

    #[Route(path: '/Task', methods: ['POST'])]
    #[RouteVersion(introduced: '2.0')]
    #[Doc\CreateRoute(
        schema_name: 'ProjectTask',
        description: 'Create a new project task'
    )]
    public function createTask(Request $request): Response
    {
        return ResourceAccessor::createBySchema($this->getKnownSchema('ProjectTask', $this->getAPIVersion($request)), $request->getParameters(), [self::class, 'getTask']);
    }

    #[Route(path: '/Task/{id}', methods: ['PATCH'], requirements: ['id' => '\d+'])]
    #[RouteVersion(introduced: '2.0')]
    #[Doc\UpdateRoute(
        schema_name: 'ProjectTask',
        description: 'Update an existing project task'
    )]
    public function updateTask(Request $request): Response
    {
        return ResourceAccessor::updateBySchema($this->getKnownSchema('ProjectTask', $this->getAPIVersion($request)), $request->getAttributes(), $request->getParameters());
    }

    #[Route(path: '/Task/{id}', methods: ['DELETE'], requirements: ['id' => '\d+'])]
    #[RouteVersion(introduced: '2.0')]
    #[Doc\DeleteRoute(
        schema_name: 'ProjectTask',
        description: 'Delete a project task'
    )]
    public function deleteTask(Request $request): Response
    {
        return ResourceAccessor::deleteBySchema($this->getKnownSchema('ProjectTask', $this->getAPIVersion($request)), $request->getAttributes(), $request->getParameters());
    }

    #[Route(path: '/{project_id}/Task', methods: ['GET'], middlewares: [ResultFormatterMiddleware::class])]
    #[RouteVersion(introduced: '2.0')]
    #[Doc\SearchRoute(
        schema_name: 'ProjectTask',
        description: 'List or search project tasks'
    )]
    public function searchLinkedTasks(Request $request): Response
    {

        $filter = $request->hasParameter('filter') ? $request->getParameter('filter') : '';
        $filter .= ';project.id==' . $request->getAttributes()['project_id'];
        $request->setParameter('filter', $filter);
        return ResourceAccessor::searchBySchema($this->getKnownSchema('ProjectTask', $this->getAPIVersion($request)), $request->getParameters());
    }

    #[Route(path: '/{project_id}/Task', methods: ['POST'])]
    #[RouteVersion(introduced: '2.0')]
    #[Doc\CreateRoute(
        schema_name: 'ProjectTask',
        description: 'Create a new project task'
    )]
    public function createLinkedTask(Request $request): Response
    {
        $params = $request->getParameters();
        $params['project'] = $request->getAttributes()['project_id'];
        return ResourceAccessor::createBySchema($this->getKnownSchema('ProjectTask', $this->getAPIVersion($request)), $params, [self::class, 'getTask']);
    }

    #[Route(path: '/Project/{id}/Cost', methods: ['GET'], middlewares: [ResultFormatterMiddleware::class])]
    #[RouteVersion(introduced: '2.3')]
    #[Doc\SearchRoute(schema_name: 'ProjectCost', description: 'Get the costs for a specific Project')]
    public function searchCosts(Request $request): Response
    {
        $schema = $this->getKnownSchema('ProjectCost', $this->getAPIVersion($request));
        $parameters = $request->getParameters();
        $filters = $parameters['filter'] ?? '';
        $filters .= 'project.id==' . $request->getAttribute('id');
        $request->setParameter('filter', $filters);
        return ResourceAccessor::searchBySchema($schema, $request->getParameters());
    }

    #[Route(path: '/Project/{id}/Cost', methods: ['POST'])]
    #[RouteVersion(introduced: '2.3')]
    #[Doc\CreateRoute(schema_name: 'ProjectCost', description: 'Create a new cost for a specific Project')]
    public function createCost(Request $request): Response
    {
        $parameters = $request->getParameters();
        $parameters['project'] = $request->getAttribute('id');
        $schema = $this->getKnownSchema('ProjectCost', $this->getAPIVersion($request));
        return ResourceAccessor::createBySchema($schema, $parameters, [self::class, 'getCost'], [
            'mapped' => [
                'id' => $request->getAttribute('id'),
            ],
            'id' => 'cost_id',
        ]);
    }

    #[Route(path: '/Project/{id}/Cost/{cost_id}', methods: ['GET'], requirements: [
        'cost_id' => '\d+',
    ], middlewares: [ResultFormatterMiddleware::class])]
    #[RouteVersion(introduced: '2.3')]
    #[Doc\GetRoute(schema_name: 'ProjectCost', description: 'Get a specific cost by ID for a specific Project')]
    public function getCost(Request $request): Response
    {
        $schema = $this->getKnownSchema('ProjectCost', $this->getAPIVersion($request));
        return ResourceAccessor::getOneBySchema($schema, ['id' => $request->getAttribute('cost_id')], $request->getParameters());
    }

    #[Route(path: '/Project/{id}/Cost/{cost_id}', methods: ['PATCH'], requirements: [
        'cost_id' => '\d+',
    ])]
    #[RouteVersion(introduced: '2.3')]
    #[Doc\UpdateRoute(schema_name: 'ProjectCost', description: 'Update a specific cost by ID for a specific Project')]
    public function updateCost(Request $request): Response
    {
        $schema = $this->getKnownSchema('ProjectCost', $this->getAPIVersion($request));
        return ResourceAccessor::updateBySchema($schema, ['id' => $request->getAttribute('cost_id')], $request->getParameters());
    }

    #[Route(path: '/Project/{id}/Cost/{cost_id}', methods: ['DELETE'], requirements: [
        'cost_id' => '\d+',
    ])]
    #[RouteVersion(introduced: '2.3')]
    #[Doc\DeleteRoute(schema_name: 'ProjectCost', description: 'Delete a specific cost by ID for a specific Project')]
    public function deleteCost(Request $request): Response
    {
        $schema = $this->getKnownSchema('ProjectCost', $this->getAPIVersion($request));
        return ResourceAccessor::deleteBySchema($schema, ['id' => $request->getAttribute('cost_id')], $request->getParameters());
    }

    #[Route(path: '/Project/{items_id}/KBArticle', methods: ['POST'], requirements: [
        'items_id' => '\d+',
    ])]
    #[RouteVersion(introduced: '2.3')]
    #[Doc\CreateRoute(
        schema_name: 'KBArticle_Item',
        description: 'Assign a KB article to an item'
    )]
    public function createKBArticleItemLink(Request $request): Response
    {
        $request->setParameter('itemtype', 'Project');
        $request->setParameter('items_id', $request->getAttribute('items_id'));
        return ResourceAccessor::createBySchema(
            (new KnowbaseController())->getKnownSchema('KBArticle_Item', $this->getAPIVersion($request)),
            $request->getParameters(),
            [self::class, 'getKBArticleItemLink'],
            [
                'mapped' => [
                    'items_id' => $request->getAttribute('items_id'),
                ],
            ],
        );
    }

    #[Route(path: '/Project/{items_id}/KBArticle', methods: ['GET'], requirements: [
        'items_id' => '\d+',
    ], middlewares: [ResultFormatterMiddleware::class])]
    #[RouteVersion(introduced: '2.3')]
    #[Doc\SearchRoute(
        schema_name: 'KBArticle_Item',
        description: 'List or search KB article links'
    )]
    public function searchKBArticleItemLinks(Request $request): Response
    {
        $filters = $request->hasParameter('filter') ? $request->getParameter('filter') : '';
        $filters .= ';itemtype==Project;items_id==' . $request->getAttribute('items_id');
        $request->setParameter('filter', $filters);
        return ResourceAccessor::searchBySchema((new KnowbaseController())->getKnownSchema('KBArticle_Item', $this->getAPIVersion($request)), $request->getParameters());
    }

    #[Route(path: '/Project/{items_id}/KBArticle/{id}', methods: ['GET'], requirements: [
        'items_id' => '\d+',
        'id' => '\d+',
    ], middlewares: [ResultFormatterMiddleware::class])]
    #[RouteVersion(introduced: '2.3')]
    #[Doc\GetRoute(
        schema_name: 'KBArticle_Item',
        description: 'Get a specific KB article link'
    )]
    public function getKBArticleItemLink(Request $request): Response
    {
        $filters = $request->hasParameter('filter') ? $request->getParameter('filter') : '';
        $filters .= ';itemtype==Project;items_id==' . $request->getAttribute('items_id');
        $request->setParameter('filter', $filters);
        return ResourceAccessor::getOneBySchema((new KnowbaseController())->getKnownSchema('KBArticle_Item', $this->getAPIVersion($request)), $request->getAttributes(), $request->getParameters());
    }

    #[Route(path: '/Project/{items_id}/KBArticle/{id}', methods: ['PATCH'], requirements: [
        'items_id' => '\d+',
        'id' => '\d+',
    ])]
    #[RouteVersion(introduced: '2.3')]
    #[Doc\UpdateRoute(
        schema_name: 'KBArticle_Item',
        description: 'Update a specific KB article link'
    )]
    public function updateKBArticleItemLink(Request $request): Response
    {
        return ResourceAccessor::updateBySchema((new KnowbaseController())->getKnownSchema('KBArticle_Item', $this->getAPIVersion($request)), $request->getAttributes(), $request->getParameters());
    }

    #[Route(path: '/Project/{items_id}/KBArticle/{id}', methods: ['DELETE'], requirements: [
        'id' => '\d+',
    ])]
    #[RouteVersion(introduced: '2.3')]
    #[Doc\DeleteRoute(
        schema_name: 'KBArticle_Item',
        description: 'Delete a specific KB article link'
    )]
    public function deleteKBArticleItemLink(Request $request): Response
    {
        return ResourceAccessor::deleteBySchema((new KnowbaseController())->getKnownSchema('KBArticle_Item', $this->getAPIVersion($request)), $request->getAttributes(), $request->getParameters());
    }

    #[Route(path: '/Project/{items_id}/Contract', methods: ['POST'], requirements: [
        'items_id' => '\d+',
    ])]
    #[RouteVersion(introduced: '2.3')]
    #[Doc\CreateRoute(
        schema_name: 'Contract_Item',
        description: 'Assign a contract to an item'
    )]
    public function createContractItemLink(Request $request): Response
    {
        $request->setParameter('itemtype', 'Project');
        $request->setParameter('items_id', $request->getAttribute('items_id'));
        return ResourceAccessor::createBySchema(
            (new ManagementController())->getKnownSchema('Contract_Item', $this->getAPIVersion($request)),
            $request->getParameters(),
            [self::class, 'getContractItemLink'],
            [
                'mapped' => [
                    'items_id' => $request->getAttribute('items_id'),
                ],
            ],
        );
    }

    #[Route(path: '/Project/{items_id}/Contract', methods: ['GET'], requirements: [
        'items_id' => '\d+',
    ], middlewares: [ResultFormatterMiddleware::class])]
    #[RouteVersion(introduced: '2.3')]
    #[Doc\SearchRoute(
        schema_name: 'Contract_Item',
        description: 'List or search contract links'
    )]
    public function searchContractItemLinks(Request $request): Response
    {
        $filters = $request->hasParameter('filter') ? $request->getParameter('filter') : '';
        $filters .= ';itemtype==Project;items_id==' . $request->getAttribute('items_id');
        $request->setParameter('filter', $filters);
        return ResourceAccessor::searchBySchema((new ManagementController())->getKnownSchema('Contract_Item', $this->getAPIVersion($request)), $request->getParameters());
    }

    #[Route(path: '/Project/{items_id}/Contract/{id}', methods: ['GET'], requirements: [
        'items_id' => '\d+',
        'id' => '\d+',
    ], middlewares: [ResultFormatterMiddleware::class])]
    #[RouteVersion(introduced: '2.3')]
    #[Doc\GetRoute(
        schema_name: 'Contract_Item',
        description: 'Get a specific contract link'
    )]
    public function getContractItemLink(Request $request): Response
    {
        $filters = $request->hasParameter('filter') ? $request->getParameter('filter') : '';
        $filters .= ';itemtype==Project;items_id==' . $request->getAttribute('items_id');
        $request->setParameter('filter', $filters);
        return ResourceAccessor::getOneBySchema((new ManagementController())->getKnownSchema('Contract_Item', $this->getAPIVersion($request)), $request->getAttributes(), $request->getParameters());
    }

    #[Route(path: '/Project/{items_id}/Contract/{id}', methods: ['PATCH'], requirements: [
        'items_id' => '\d+',
        'id' => '\d+',
    ])]
    #[RouteVersion(introduced: '2.3')]
    #[Doc\UpdateRoute(
        schema_name: 'Contract_Item',
        description: 'Update a specific contract link'
    )]
    public function updateContractItemLink(Request $request): Response
    {
        return ResourceAccessor::updateBySchema((new ManagementController())->getKnownSchema('Contract_Item', $this->getAPIVersion($request)), $request->getAttributes(), $request->getParameters());
    }

    #[Route(path: '/Project/{items_id}/Contract/{id}', methods: ['DELETE'], requirements: [
        'items_id' => '\d+',
        'id' => '\d+',
    ])]
    #[RouteVersion(introduced: '2.3')]
    #[Doc\DeleteRoute(
        schema_name: 'Contract_Item',
        description: 'Delete a specific contract link'
    )]
    public function deleteContractItemLink(Request $request): Response
    {
        return ResourceAccessor::deleteBySchema((new ManagementController())->getKnownSchema('Contract_Item', $this->getAPIVersion($request)), $request->getAttributes(), $request->getParameters());
    }

    #[Route(path: '/{project_id}/Ticket', methods: ['GET'], middlewares: [ResultFormatterMiddleware::class])]
    #[RouteVersion(introduced: '2.3')]
    #[Doc\SearchRoute(
        schema_name: 'ITIL_Project',
        description: 'List or search tickets linked to the project'
    )]
    public function getLinkedTickets(Request $request): Response
    {
        $filter = $request->hasParameter('filter') ? $request->getParameter('filter') : '';
        $filter .= ';project.id==' . $request->getAttributes()['project_id'] . ';itemtype==Ticket';
        $request->setParameter('filter', $filter);
        return ResourceAccessor::searchBySchema($this->getKnownSchema('ITIL_Project', $this->getAPIVersion($request)), $request->getParameters());
    }

    #[Route(path: '/{project_id}/Ticket', methods: ['POST'])]
    #[RouteVersion(introduced: '2.3')]
    #[Doc\CreateRoute(
        schema_name: 'ITIL_Project',
        description: 'Link a ticket to the project'
    )]
    public function addLinkedTicket(Request $request): Response
    {
        $params = $request->getParameters();
        $params['project'] = $request->getAttributes()['project_id'];
        $params['itemtype'] = Ticket::class;
        return ResourceAccessor::createBySchema($this->getKnownSchema('ITIL_Project', $this->getAPIVersion($request)), $params, [self::class, 'getLinkedTickets']);
    }

    #[Route(path: '/{project_id}/Ticket/{ticket_id}', methods: ['DELETE'], requirements: ['ticket_id' => '\d+'])]
    #[RouteVersion(introduced: '2.3')]
    #[Doc\DeleteRoute(
        schema_name: 'ITIL_Project',
        description: 'Unlink a ticket from the project'
    )]
    public function deleteLinkedTicket(Request $request): Response
    {
        global $DB;

        // find the link ID
        $it = $DB->request([
            'SELECT' => 'id',
            'FROM' => Itil_Project::getTable(),
            'WHERE' => [
                'projects_id' => $request->getAttributes()['project_id'],
                'itemtype' => Ticket::class,
                'items_id' => $request->getAttributes()['ticket_id'],
            ],
        ]);

        if (!$it->count()) {
            return self::getNotFoundErrorResponse();
        }

        return ResourceAccessor::deleteBySchema(
            $this->getKnownSchema('ITIL_Project', $this->getAPIVersion($request)),
            ['id' => $it->current()['id']],
            $request->getParameters()
        );
    }

    #[Route(path: '/{project_id}/Change', methods: ['GET'], middlewares: [ResultFormatterMiddleware::class])]
    #[RouteVersion(introduced: '2.3')]
    #[Doc\SearchRoute(
        schema_name: 'ITIL_Project',
        description: 'List or search changes linked to the project'
    )]
    public function getLinkedChanges(Request $request): Response
    {
        $filter = $request->hasParameter('filter') ? $request->getParameter('filter') : '';
        $filter .= ';project.id==' . $request->getAttributes()['project_id'] . ';itemtype==Change';
        $request->setParameter('filter', $filter);
        return ResourceAccessor::searchBySchema($this->getKnownSchema('ITIL_Project', $this->getAPIVersion($request)), $request->getParameters());
    }

    #[Route(path: '/{project_id}/Change', methods: ['POST'])]
    #[RouteVersion(introduced: '2.3')]
    #[Doc\CreateRoute(
        schema_name: 'ITIL_Project',
        description: 'Link a change to the project'
    )]
    public function addLinkedChange(Request $request): Response
    {
        $params = $request->getParameters();
        $params['project'] = $request->getAttributes()['project_id'];
        $params['itemtype'] = Change::class;
        return ResourceAccessor::createBySchema($this->getKnownSchema('ITIL_Project', $this->getAPIVersion($request)), $params, [self::class, 'getLinkedChanges']);
    }

    #[Route(path: '/{project_id}/Change/{change_id}', methods: ['DELETE'], requirements: ['change_id' => '\d+'])]
    #[RouteVersion(introduced: '2.3')]
    #[Doc\DeleteRoute(
        schema_name: 'ITIL_Project',
        description: 'Unlink a change from the project'
    )]
    public function deleteLinkedChange(Request $request): Response
    {
        global $DB;

        // find the link ID
        $it = $DB->request([
            'SELECT' => 'id',
            'FROM' => Itil_Project::getTable(),
            'WHERE' => [
                'projects_id' => $request->getAttributes()['project_id'],
                'itemtype' => Change::class,
                'items_id' => $request->getAttributes()['change_id'],
            ],
        ]);

        if (!$it->count()) {
            return self::getNotFoundErrorResponse();
        }

        return ResourceAccessor::deleteBySchema(
            $this->getKnownSchema('ITIL_Project', $this->getAPIVersion($request)),
            ['id' => $it->current()['id']],
            $request->getParameters()
        );
    }

    #[Route(path: '/{project_id}/Problem', methods: ['GET'], middlewares: [ResultFormatterMiddleware::class])]
    #[RouteVersion(introduced: '2.3')]
    #[Doc\SearchRoute(
        schema_name: 'ITIL_Project',
        description: 'List or search problems linked to the project'
    )]
    public function getLinkedProblems(Request $request): Response
    {
        $filter = $request->hasParameter('filter') ? $request->getParameter('filter') : '';
        $filter .= ';project.id==' . $request->getAttributes()['project_id'] . ';itemtype==Problem';
        $request->setParameter('filter', $filter);
        return ResourceAccessor::searchBySchema($this->getKnownSchema('ITIL_Project', $this->getAPIVersion($request)), $request->getParameters());
    }

    #[Route(path: '/{project_id}/Problem', methods: ['POST'])]
    #[RouteVersion(introduced: '2.3')]
    #[Doc\CreateRoute(
        schema_name: 'ITIL_Project',
        description: 'Link a problem to the project'
    )]
    public function addLinkedProblem(Request $request): Response
    {
        $params = $request->getParameters();
        $params['project'] = $request->getAttributes()['project_id'];
        $params['itemtype'] = Problem::class;
        return ResourceAccessor::createBySchema($this->getKnownSchema('ITIL_Project', $this->getAPIVersion($request)), $params, [self::class, 'getLinkedProblems']);
    }

    #[Route(path: '/{project_id}/Problem/{problem_id}', methods: ['DELETE'], requirements: ['problem_id' => '\d+'])]
    #[RouteVersion(introduced: '2.3')]
    #[Doc\DeleteRoute(
        schema_name: 'ITIL_Project',
        description: 'Unlink a problem from the project'
    )]
    public function deleteLinkedProblem(Request $request): Response
    {
        global $DB;

        // find the link ID
        $it = $DB->request([
            'SELECT' => 'id',
            'FROM' => Itil_Project::getTable(),
            'WHERE' => [
                'projects_id' => $request->getAttributes()['project_id'],
                'itemtype' => Problem::class,
                'items_id' => $request->getAttributes()['problem_id'],
            ],
        ]);

        if (!$it->count()) {
            return self::getNotFoundErrorResponse();
        }

        return ResourceAccessor::deleteBySchema(
            $this->getKnownSchema('ITIL_Project', $this->getAPIVersion($request)),
            ['id' => $it->current()['id']],
            $request->getParameters()
        );
    }

    #[Route(path: '/{project_id}/TeamMember', methods: ['GET'], middlewares: [ResultFormatterMiddleware::class])]
    #[RouteVersion(introduced: '2.3')]
    #[Doc\SearchRoute(
        schema_name: 'ProjectTeamMember',
        description: 'List or search team members linked to the project'
    )]
    public function getProjectTeamMembers(Request $request): Response
    {
        $filter = $request->hasParameter('filter') ? $request->getParameter('filter') : '';
        $filter .= ';project.id==' . $request->getAttributes()['project_id'];
        $request->setParameter('filter', $filter);
        return ResourceAccessor::searchBySchema($this->getKnownSchema('ProjectTeamMember', $this->getAPIVersion($request)), $request->getParameters());
    }

    #[Route(path: '/{project_id}/TeamMember/{teammember_id}', methods: ['DELETE'], requirements: ['teammember_id' => '\d+'])]
    #[RouteVersion(introduced: '2.3')]
    #[Doc\DeleteRoute(
        schema_name: 'ProjectTeamMember',
        description: 'Remove a team member from the project'
    )]
    public function deleteProjectTeamMember(Request $request): Response
    {
        return ResourceAccessor::deleteBySchema(
            $this->getKnownSchema('ProjectTeamMember', $this->getAPIVersion($request)),
            ['id' => $request->getAttribute('teammember_id')],
            $request->getParameters()
        );
    }

    #[Route(path: '/{project_id}/TeamMember', methods: ['POST'])]
    #[RouteVersion(introduced: '2.3')]
    #[Doc\CreateRoute(
        schema_name: 'ProjectTeamMember',
        description: 'Add a team member to the project'
    )]
    public function addProjectTeamMember(Request $request): Response
    {
        $params = $request->getParameters();
        $params['project'] = $request->getAttributes()['project_id'];
        return ResourceAccessor::createBySchema(
            $this->getKnownSchema('ProjectTeamMember', $this->getAPIVersion($request)),
            $params,
            [self::class, 'getProjectTeamMembers']
        );
    }

    #[Route(path: '/Task/{task_id}/TeamMember', methods: ['GET'], middlewares: [ResultFormatterMiddleware::class])]
    #[RouteVersion(introduced: '2.3')]
    #[Doc\SearchRoute(
        schema_name: 'ProjectTaskTeamMember',
        description: 'List or search team members linked to a task'
    )]
    public function getProjectTaskTeamMembers(Request $request): Response
    {
        $filter = $request->hasParameter('filter') ? $request->getParameter('filter') : '';
        $filter .= ';task.id==' . $request->getAttributes()['task_id'];
        $request->setParameter('filter', $filter);
        return ResourceAccessor::searchBySchema(
            $this->getKnownSchema('ProjectTaskTeamMember', $this->getAPIVersion($request)),
            $request->getParameters()
        );
    }

    #[Route(path: '/Task/{task_id}/TeamMember/{teammember_id}', methods: ['DELETE'], requirements: [
        'task_id' => '\d+',
        'teammember_id' => '\d+',
    ])]
    #[RouteVersion(introduced: '2.3')]
    #[Doc\DeleteRoute(
        schema_name: 'ProjectTaskTeamMember',
        description: 'Remove a team member from a task'
    )]
    public function deleteProjectTaskTeamMember(Request $request): Response
    {
        return ResourceAccessor::deleteBySchema(
            $this->getKnownSchema('ProjectTaskTeamMember', $this->getAPIVersion($request)),
            ['id' => $request->getAttribute('teammember_id')],
            $request->getParameters()
        );
    }

    #[Route(path: '/Task/{task_id}/TeamMember', methods: ['POST'])]
    #[RouteVersion(introduced: '2.3')]
    #[Doc\CreateRoute(
        schema_name: 'ProjectTaskTeamMember',
        description: 'Add a team member to a task'
    )]
    public function addProjectTaskTeamMember(Request $request): Response
    {
        $params = $request->getParameters();
        $params['task'] = $request->getAttributes()['task_id'];
        return ResourceAccessor::createBySchema(
            $this->getKnownSchema('ProjectTaskTeamMember', $this->getAPIVersion($request)),
            $params,
            [self::class, 'getProjectTaskTeamMembers']
        );
    }
}
