openapi: 3.0.3 info: title: PM Backend API version: "1.0.0" description: | REST API сервиса **pm-backend** (`planning/pm-backend`) — управление проектами и папками, задачами (иерархия, связи, ресурсы), базовыми планами (состояниями), атрибутами, календарями, настройками КСГ, детализацией задач, системным журналом и экспортом. Сервис написан на Python (**Django 5.1 + Django REST Framework**) и запускается как ASGI-приложение (`config.asgi_root:application`) через gunicorn с воркерами `uvicorn.workers.UvicornWorker` (порт `8000`). Роутинг задан в `config/urls.py` и состоит из трёх групп: - публичный API — префикс `/api/pm/msp/` (`sarex.pm.urls`); - внешний API v2 — префикс `/api/pm/external/v2/` (`sarex.pm.urls_external`), только чтение; - внутренний API — префикс `/internal/pm/` (`sarex.pm.urls_internal`), предназначен для вызовов внутри кластера, аутентификация не требуется. ### Аутентификация Публичные (`/api/pm/msp/*`) и внешние (`/api/pm/external/v2/*`) эндпоинты требуют аутентификации (DRF `IsAuthenticated`). Проверка выполняется по цепочке (`REST_FRAMEWORK.DEFAULT_AUTHENTICATION_CLASSES`): 1. **Zitadel** (`ZitadelJWTAuthentication`) — требуются одновременно заголовки `Authorization: Bearer ` и `Identity: Identity `. При отсутствии любого из них — переход к следующему механизму/ошибка. 2. **sarex-backend** (`JWTAuthentication`) — подпись токена `Authorization: Bearer ` проверяется публичным RSA-ключом (`AUTH_PUBLIC_KEY`, алгоритм `RS512`). При `SERVER_DEBUG=true` аутентификация обходится (возвращается анонимный пользователь). Внутренние эндпоинты (`/internal/pm/*`) имеют `authentication_classes=[]` и `AllowAny` — доступ ограничивается сетевым слоем. Отдельный публичный эндпоинт `/api/pm/msp/external-task-info/` также открыт (`AllowAny`). ### Пагинация По умолчанию используется DRF `LimitOffsetPagination` (`PAGE_SIZE=1000`). Списочные ответы оборачиваются в объект `{ count, next, previous, results }`; размер страницы задаётся query-параметром `limit`, смещение — `offset`. Часть «тяжёлых» списков (задачи, привязки ресурсов, связи задач) использует `ProjectTaskPagination` (`default_limit=30000`). ### Обработка ошибок Ошибки возвращаются в стандартном формате DRF: ошибки валидации — `400` (`{ "": [""] }` либо `{ "detail": "..." }`), нет прав — `403` (`{ "detail": "..." }`), не аутентифицирован — `401`, не найдено — `404`. Объектные права проверяются `PermissionMixin` (raw-SQL), суперпользователь их обходит. ### Замечания (расхождения кода) - Внешние вьюсеты v2 объявляют `http_method_names = ['get']`, поэтому доступны только `list`/`retrieve` (и GET-`@action`), даже если в коде есть методы записи. - Ряд bulk-эндпоинтов принимает JSON-массив (list-сериализатор). - Datetime-поля (`DateTimeWithoutTZFiled`) наивные — таймзона отбрасывается. - Внутренние эндпоинты принимают/возвращают «сырые» словари, без модельных сериализаторов. contact: name: pm-backend url: https://gitlab/planning/pm-backend servers: - url: https://api.sarex.io description: Production (ingress) - url: https://stage-api.sarex.io description: Stage (ingress) - url: http://pm-backend-service.planning.svc.cluster.local:8000 description: Внутрикластерный адрес (ClusterIP, порт 8000) — единственный способ достучаться до /internal/pm - url: http://localhost:8000 description: Локальный запуск (gunicorn/uvicorn, порт 8000) tags: - name: projects description: Проекты и папки — CRUD, копирование, экспорт, права, шаблоны - name: tasks description: Задачи проекта — CRUD, массовые операции, права, описания - name: resources description: Ресурсы и их привязки к задачам и элементам - name: states description: Состояния проекта (базовые планы) - name: relations description: Связи задач и проектов, атрибутивные связи - name: attributes description: Атрибуты проекта и значения атрибутов задач - name: calendars description: Календари - name: settings description: Настройки/состояния КСГ - name: detailing description: Детализация задач, визуальные профили - name: comments description: Комментарии к задачам - name: system-log description: Системный журнал и откаты - name: external description: Внешний API v2 (только чтение) - name: internal description: Внутренние эндпоинты (только внутри кластера, без аутентификации) security: - bearerAuth: [] paths: # ========================================================================== # Projects (/api/pm/msp/projects) # ========================================================================== /api/pm/msp/projects/: get: tags: [projects] summary: Список проектов и папок operationId: listProjects parameters: - $ref: '#/components/parameters/Limit' - $ref: '#/components/parameters/Offset' - { name: search, in: query, schema: { type: string } } - { name: ids, in: query, description: CSV идентификаторов, schema: { type: string } } - { name: resource_id, in: query, schema: { type: string } } - { name: company_id, in: query, schema: { type: integer } } - { name: parent_id, in: query, schema: { type: integer } } - { name: only_children, in: query, schema: { type: boolean } } - { name: templates, in: query, schema: { type: boolean } } - { name: is_folder, in: query, schema: { type: boolean } } - { name: strict, in: query, schema: { type: boolean } } - { name: extend, in: query, schema: { type: boolean } } - { name: schema, in: query, description: "напр. tiny/detail", schema: { type: string } } responses: '200': description: Страница проектов content: application/json: schema: allOf: - $ref: '#/components/schemas/PaginatedResponse' - type: object properties: results: type: array items: { $ref: '#/components/schemas/Project' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } post: tags: [projects] summary: Создать проект/папку operationId: createProject requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/ProjectCreate' } responses: '201': description: Проект создан content: application/json: schema: { $ref: '#/components/schemas/Project' } '400': { $ref: '#/components/responses/BadRequest' } '401': { $ref: '#/components/responses/Unauthorized' } '403': { $ref: '#/components/responses/Forbidden' } /api/pm/msp/projects/{id}/: parameters: - $ref: '#/components/parameters/PathId' get: tags: [projects] summary: Проект по id operationId: retrieveProject parameters: - { name: extend, in: query, schema: { type: boolean } } - { name: schema, in: query, schema: { type: string } } responses: '200': description: Проект content: application/json: schema: { $ref: '#/components/schemas/Project' } '404': { $ref: '#/components/responses/NotFound' } put: tags: [projects] summary: Полное обновление проекта operationId: updateProject requestBody: required: true content: { application/json: { schema: { $ref: '#/components/schemas/ProjectCreate' } } } responses: '200': { description: Обновлено, content: { application/json: { schema: { $ref: '#/components/schemas/Project' } } } } '400': { $ref: '#/components/responses/BadRequest' } '404': { $ref: '#/components/responses/NotFound' } patch: tags: [projects] summary: Частичное обновление проекта operationId: partialUpdateProject requestBody: required: true content: { application/json: { schema: { $ref: '#/components/schemas/ProjectCreate' } } } responses: '200': { description: Обновлено, content: { application/json: { schema: { $ref: '#/components/schemas/Project' } } } } '400': { $ref: '#/components/responses/BadRequest' } '404': { $ref: '#/components/responses/NotFound' } delete: tags: [projects] summary: Удалить проект operationId: deleteProject responses: '204': { description: Удалено } '404': { $ref: '#/components/responses/NotFound' } /api/pm/msp/projects/root_folder/: post: tags: [projects] summary: Создать корневую папку operationId: createRootFolder requestBody: required: true content: { application/json: { schema: { $ref: '#/components/schemas/FolderCreate' } } } responses: '201': { description: Папка создана, content: { application/json: { schema: { $ref: '#/components/schemas/Project' } } } } '400': { $ref: '#/components/responses/BadRequest' } /api/pm/msp/projects/rename/: post: tags: [projects] summary: Переименовать проект operationId: renameProject responses: '200': { description: OK } '400': { $ref: '#/components/responses/BadRequest' } /api/pm/msp/projects/import/: post: tags: [projects] summary: Импорт задач в проект operationId: importProject requestBody: required: true content: application/json: schema: type: object properties: pk: { type: integer } from_type: { type: string, enum: [sarex, ms_project, primavera, template] } update: { type: boolean } template_id: { type: integer, nullable: true } required: [pk, from_type] responses: '201': { description: Импорт запущен, content: { application/json: { schema: { type: object, properties: { uuid: { type: string, format: uuid } } } } } } '404': { $ref: '#/components/responses/NotFound' } /api/pm/msp/projects/import-from-gasprom/: post: tags: [projects] summary: Импорт проекта из Газпром ЦПС operationId: importProjectFromGaspromCps requestBody: required: true content: application/json: schema: type: object properties: project_name: { type: string } company_id: { type: integer } file_url: { type: string } required: [project_name, company_id, file_url] responses: '201': { description: Импорт запущен, content: { application/json: { schema: { type: object, properties: { uuid: { type: string, format: uuid } } } } } } '400': { $ref: '#/components/responses/BadRequest' } /api/pm/msp/projects/{id}/states/: parameters: [ { $ref: '#/components/parameters/PathId' } ] get: tags: [projects] summary: Состояния (базовые планы) проекта operationId: listProjectStates responses: '200': { description: Список состояний, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/ProjectState' } } } } } /api/pm/msp/projects/{id}/permissions/: parameters: [ { $ref: '#/components/parameters/PathId' } ] get: tags: [projects] summary: Права проекта operationId: getProjectPermissions responses: '200': { description: Права, content: { application/json: { schema: { $ref: '#/components/schemas/PermissionsResponse' } } } } post: tags: [projects] summary: Сохранить права проекта operationId: saveProjectPermissions requestBody: { required: true, content: { application/json: { schema: { type: object } } } } responses: '200': { description: Права, content: { application/json: { schema: { $ref: '#/components/schemas/PermissionsResponse' } } } } '400': { $ref: '#/components/responses/BadRequest' } /api/pm/msp/projects/{id}/all_permissions/: parameters: [ { $ref: '#/components/parameters/PathId' } ] get: tags: [projects] summary: Все права проекта operationId: getProjectAllPermissions responses: '200': { description: Права, content: { application/json: { schema: { type: object } } } } /api/pm/msp/projects/{id}/copy_project/: parameters: [ { $ref: '#/components/parameters/PathId' } ] post: tags: [projects] summary: Копировать проект operationId: copyProject requestBody: required: true content: application/json: schema: type: object properties: parent_id: { type: integer, nullable: true } project_name: { type: string } assets_mapping: { type: object } with_resources: { type: boolean } responses: '201': { description: Проект скопирован, content: { application/json: { schema: { $ref: '#/components/schemas/Project' } } } } '400': { $ref: '#/components/responses/BadRequest' } /api/pm/msp/projects/{id}/create_template/: parameters: [ { $ref: '#/components/parameters/PathId' } ] post: tags: [projects] summary: Создать шаблон из проекта operationId: createTemplateFromProject requestBody: required: true content: application/json: schema: type: object properties: project_name: { type: string } description: { type: string, nullable: true } required: [project_name] responses: '201': { description: Шаблон создан, content: { application/json: { schema: { $ref: '#/components/schemas/Project' } } } } /api/pm/msp/projects/{id}/export_project_to_pdf/: parameters: [ { $ref: '#/components/parameters/PathId' } ] post: tags: [projects] summary: Экспорт проекта в PDF operationId: exportProjectToPdf requestBody: required: true content: { application/json: { schema: { $ref: '#/components/schemas/ExportProject' } } } responses: '200': { description: Задача экспорта запущена } '400': { $ref: '#/components/responses/BadRequest' } /api/pm/msp/projects/{id}/project_export/: parameters: [ { $ref: '#/components/parameters/PathId' } ] post: tags: [projects] summary: Экспорт проекта (xlsx/xml) operationId: projectExport responses: '200': { description: Экспорт запущен } /api/pm/msp/projects/{id}/key_milestones/: parameters: [ { $ref: '#/components/parameters/PathId' } ] get: tags: [projects] summary: Ключевые вехи проекта operationId: getKeyMilestones responses: '200': { description: Вехи, content: { application/json: { schema: { type: array, items: { type: object } } } } } /api/pm/msp/projects/{id}/profiles/: parameters: [ { $ref: '#/components/parameters/PathId' } ] get: tags: [projects] summary: Визуальные профили проекта operationId: getProjectProfiles responses: '200': { description: Профили, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/VisualProfile' } } } } } /api/pm/msp/projects/{id}/time_markers_data/: parameters: [ { $ref: '#/components/parameters/PathId' } ] get: tags: [projects] summary: Данные временных маркеров operationId: getTimeMarkersData parameters: - { name: attributes, in: query, description: CSV идентификаторов атрибутов, schema: { type: string } } - { name: with_hierarchy, in: query, schema: { type: boolean } } responses: '200': { description: Данные, content: { application/json: { schema: { type: object } } } } /api/pm/msp/projects/{id}/relations/: parameters: [ { $ref: '#/components/parameters/PathId' } ] get: tags: [projects] summary: Связи/интеграции проекта operationId: getProjectRelations responses: '200': { description: Связи, content: { application/json: { schema: { type: array, items: { type: object } } } } } /api/pm/msp/projects/{id}/bulk_integration/: parameters: [ { $ref: '#/components/parameters/PathId' } ] post: tags: [projects] summary: Массовое создание интеграций operationId: bulkIntegration responses: '200': { description: OK } /api/pm/msp/projects/{id}/issues_data/: parameters: [ { $ref: '#/components/parameters/PathId' } ] get: tags: [projects] summary: Данные проблем проекта operationId: getIssuesData responses: '200': { description: Данные, content: { application/json: { schema: { type: object } } } } /api/pm/msp/projects/{id}/formula/: parameters: [ { $ref: '#/components/parameters/PathId' } ] post: tags: [projects] summary: Пересчёт по формуле operationId: applyFormula responses: '200': { description: OK } '400': { $ref: '#/components/responses/BadRequest' } /api/pm/msp/projects/{id}/rule/: parameters: [ { $ref: '#/components/parameters/PathId' } ] get: tags: [detailing] summary: Правило детализации проекта operationId: getProjectRule responses: '200': { description: Правило, content: { application/json: { schema: { type: object } } } } post: tags: [detailing] summary: Создать правило детализации operationId: createProjectRule requestBody: required: true content: application/json: schema: type: object properties: attributes: { type: array, items: { type: integer } } summary_fields: { type: array, items: { type: string } } mode: { type: string } responses: '200': { description: OK } '400': { $ref: '#/components/responses/BadRequest' } /api/pm/msp/projects/{id}/tasks/: parameters: [ { $ref: '#/components/parameters/PathId' } ] get: tags: [projects] summary: Задачи проекта operationId: getProjectTasks responses: '200': { description: Задачи, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/ProjectTask' } } } } } /api/pm/msp/projects/{id}/create_tasks/: parameters: [ { $ref: '#/components/parameters/PathId' } ] post: tags: [tasks] summary: Массовое создание задач operationId: bulkCreateTasks requestBody: required: true content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/ProjectTaskCreate' } } } } responses: '201': { description: Создано, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/ProjectTask' } } } } } '400': { $ref: '#/components/responses/BadRequest' } /api/pm/msp/projects/{id}/update_tasks/: parameters: [ { $ref: '#/components/parameters/PathId' } ] patch: tags: [tasks] summary: Массовое обновление задач operationId: bulkUpdateTasks requestBody: required: true content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/ProjectTaskUpdate' } } } } responses: '200': { description: Обновлено } '400': { $ref: '#/components/responses/BadRequest' } /api/pm/msp/projects/{id}/delete_tasks/: parameters: [ { $ref: '#/components/parameters/PathId' } ] delete: tags: [tasks] summary: Массовое удаление задач operationId: bulkDeleteTasks requestBody: required: true content: { application/json: { schema: { type: object, properties: { ids: { type: array, items: { type: integer } } } } } } responses: '204': { description: Удалено } /api/pm/msp/projects/{id}/system-logs/: parameters: [ { $ref: '#/components/parameters/PathId' } ] get: tags: [system-log] summary: Системные логи проекта operationId: getSystemLogs parameters: - { name: task_id, in: query, schema: { type: integer } } - $ref: '#/components/parameters/Limit' - $ref: '#/components/parameters/Offset' responses: '200': description: Страница логов content: application/json: schema: allOf: - $ref: '#/components/schemas/PaginatedResponse' - type: object properties: { results: { type: array, items: { $ref: '#/components/schemas/SystemLog' } } } /api/pm/msp/projects/{id}/system-log-detail/: parameters: [ { $ref: '#/components/parameters/PathId' } ] get: tags: [system-log] summary: Деталь записи журнала operationId: getSystemLogDetail parameters: - { name: log_id, in: query, required: true, schema: { type: integer } } responses: '200': { description: Деталь, content: { application/json: { schema: { $ref: '#/components/schemas/SystemLog' } } } } /api/pm/msp/projects/{id}/rollback-to-record/: parameters: [ { $ref: '#/components/parameters/PathId' } ] post: tags: [system-log] summary: Откат к записи журнала operationId: rollbackToRecord requestBody: required: true content: application/json: schema: type: object properties: log_id: { type: integer } read_only: { type: boolean } required: [log_id] responses: '200': { description: OK } '400': { $ref: '#/components/responses/BadRequest' } # ========================================================================== # Tasks (/api/pm/msp/tasks) # ========================================================================== /api/pm/msp/tasks/: get: tags: [tasks] summary: Список задач operationId: listTasks parameters: - $ref: '#/components/parameters/Limit' - $ref: '#/components/parameters/Offset' - { name: project, in: query, schema: { type: integer } } - { name: ids, in: query, schema: { type: string } } - { name: name__icontains, in: query, schema: { type: string } } - { name: time_start, in: query, schema: { type: string, format: date-time } } - { name: time_end, in: query, schema: { type: string, format: date-time } } - { name: status, in: query, description: CSV статусов, schema: { type: string } } - { name: responsible, in: query, schema: { type: string } } - { name: executors, in: query, schema: { type: string } } - { name: with_hierarchy, in: query, schema: { type: boolean, default: true } } responses: '200': description: Страница задач content: application/json: schema: allOf: - $ref: '#/components/schemas/PaginatedResponse' - type: object properties: { results: { type: array, items: { $ref: '#/components/schemas/ProjectTask' } } } post: tags: [tasks] summary: Создать задачу operationId: createTask requestBody: required: true content: { application/json: { schema: { $ref: '#/components/schemas/ProjectTaskCreate' } } } responses: '201': { description: Создано, content: { application/json: { schema: { $ref: '#/components/schemas/ProjectTask' } } } } '400': { $ref: '#/components/responses/BadRequest' } /api/pm/msp/tasks/{id}/: parameters: [ { $ref: '#/components/parameters/PathId' } ] get: tags: [tasks] summary: Задача по id operationId: retrieveTask responses: '200': { description: Задача, content: { application/json: { schema: { $ref: '#/components/schemas/ProjectTask' } } } } '404': { $ref: '#/components/responses/NotFound' } put: tags: [tasks] summary: Полное обновление задачи operationId: updateTask requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/ProjectTaskCreate' } } } } responses: '200': { description: Обновлено, content: { application/json: { schema: { $ref: '#/components/schemas/ProjectTask' } } } } '400': { $ref: '#/components/responses/BadRequest' } patch: tags: [tasks] summary: Частичное обновление задачи operationId: partialUpdateTask requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/ProjectTaskCreate' } } } } responses: '200': { description: Обновлено, content: { application/json: { schema: { $ref: '#/components/schemas/ProjectTask' } } } } delete: tags: [tasks] summary: Удалить задачу operationId: deleteTask responses: '204': { description: Удалено } /api/pm/msp/tasks/task_index/: patch: tags: [tasks] summary: Переиндексация задач operationId: updateTaskIndex responses: '200': { description: OK } /api/pm/msp/tasks/copy/: post: tags: [tasks] summary: Копировать задачи operationId: copyTasks requestBody: required: true content: { application/json: { schema: { type: object, properties: { ids: { type: array, items: { type: integer } } } } } } responses: '200': { description: OK } /api/pm/msp/tasks/{id}/permissions/: parameters: [ { $ref: '#/components/parameters/PathId' } ] get: tags: [tasks] summary: Права задачи operationId: getTaskPermissions responses: '200': { description: Права, content: { application/json: { schema: { $ref: '#/components/schemas/PermissionsResponse' } } } } post: tags: [tasks] summary: Сохранить права задачи operationId: saveTaskPermissions requestBody: { required: true, content: { application/json: { schema: { type: object } } } } responses: '200': { description: Права, content: { application/json: { schema: { $ref: '#/components/schemas/PermissionsResponse' } } } } /api/pm/msp/tasks/{id}/descriptions/: parameters: [ { $ref: '#/components/parameters/PathId' } ] get: tags: [tasks] summary: Описание задачи operationId: getTaskDescription responses: '200': { description: Описание, content: { application/json: { schema: { $ref: '#/components/schemas/Description' } } } } patch: tags: [tasks] summary: Обновить описание задачи operationId: updateTaskDescription requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/Description' } } } } responses: '200': { description: Обновлено, content: { application/json: { schema: { $ref: '#/components/schemas/Description' } } } } # ========================================================================== # Detailed tasks / descriptions # ========================================================================== /api/pm/msp/detailed-tasks/: get: tags: [detailing] summary: Список детализированных задач operationId: listDetailedTasks parameters: - { name: task, in: query, schema: { type: integer } } - { name: project, in: query, schema: { type: integer } } responses: '200': { description: Список, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/DetailedTask' } } } } } post: tags: [detailing] summary: Создать детализированную задачу operationId: createDetailedTask requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/DetailedTask' } } } } responses: '201': { description: Создано, content: { application/json: { schema: { $ref: '#/components/schemas/DetailedTask' } } } } /api/pm/msp/detailed-tasks/{id}/: parameters: [ { $ref: '#/components/parameters/PathId' } ] patch: tags: [detailing] summary: Обновить детализированную задачу operationId: updateDetailedTask requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/DetailedTask' } } } } responses: '200': { description: Обновлено, content: { application/json: { schema: { $ref: '#/components/schemas/DetailedTask' } } } } delete: tags: [detailing] summary: Удалить детализированную задачу operationId: deleteDetailedTask responses: '204': { description: Удалено } /api/pm/msp/descriptions/upload_file/: post: tags: [tasks] summary: Загрузить файл описания operationId: uploadDescriptionFile requestBody: required: true content: multipart/form-data: schema: type: object properties: file: { type: string, format: binary } responses: '200': { description: OK } # ========================================================================== # Values (actual values) # ========================================================================== /api/pm/msp/values/: get: tags: [tasks] summary: Фактические значения задач operationId: listActualValues parameters: - { name: task, in: query, schema: { type: integer } } - $ref: '#/components/parameters/Limit' - $ref: '#/components/parameters/Offset' responses: '200': { description: Значения, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/ActualValue' } } } } } post: tags: [tasks] summary: Создать фактическое значение operationId: createActualValue requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/ActualValueCreate' } } } } responses: '201': { description: Создано, content: { application/json: { schema: { $ref: '#/components/schemas/ActualValue' } } } } /api/pm/msp/values/{id}/: parameters: [ { $ref: '#/components/parameters/PathId' } ] get: tags: [tasks] summary: Значение по id operationId: retrieveActualValue responses: { '200': { description: Значение, content: { application/json: { schema: { $ref: '#/components/schemas/ActualValue' } } } }, '404': { $ref: '#/components/responses/NotFound' } } put: tags: [tasks] summary: Обновить значение operationId: updateActualValue requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/ActualValueCreate' } } } } responses: { '200': { description: Обновлено, content: { application/json: { schema: { $ref: '#/components/schemas/ActualValue' } } } } } delete: tags: [tasks] summary: Удалить значение operationId: deleteActualValue responses: { '204': { description: Удалено } } # ========================================================================== # Project states (base plans) # ========================================================================== /api/pm/msp/project-states/: get: tags: [states] summary: Список состояний operationId: listStates responses: { '200': { description: Список, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/ProjectState' } } } } } } post: tags: [states] summary: Создать состояние (базовый план) operationId: createState requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/ProjectStateCreate' } } } } responses: { '201': { description: Создано, content: { application/json: { schema: { $ref: '#/components/schemas/ProjectState' } } } } } /api/pm/msp/project-states/{id}/: parameters: [ { $ref: '#/components/parameters/PathId' } ] get: tags: [states] summary: Состояние по id operationId: retrieveState responses: { '200': { description: Состояние, content: { application/json: { schema: { $ref: '#/components/schemas/ProjectState' } } } }, '404': { $ref: '#/components/responses/NotFound' } } put: tags: [states] summary: Обновить состояние operationId: updateState requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/ProjectStateCreate' } } } } responses: { '200': { description: Обновлено, content: { application/json: { schema: { $ref: '#/components/schemas/ProjectState' } } } } } delete: tags: [states] summary: Удалить состояние operationId: deleteState responses: { '204': { description: Удалено } } /api/pm/msp/project-states/{id}/data/: parameters: [ { $ref: '#/components/parameters/PathId' } ] get: tags: [states] summary: Данные состояния (базового плана) operationId: getStateData responses: { '200': { description: Данные, content: { application/json: { schema: { type: object } } } } } post: tags: [states] summary: Добавить задачи в базовый план operationId: addTasksToState responses: { '200': { description: OK } } /api/pm/msp/project-states/{id}/edit_state/: parameters: [ { $ref: '#/components/parameters/PathId' } ] patch: tags: [states] summary: Редактировать состояние operationId: editState requestBody: required: true content: application/json: schema: type: object properties: id: { type: integer } time_start: { type: string, format: date-time, nullable: true } time_end: { type: string, format: date-time, nullable: true } progress: { type: number, nullable: true } planned_value: { type: number, nullable: true } unit: { type: string, nullable: true } required: [id] responses: { '200': { description: OK } } # ========================================================================== # Resources # ========================================================================== /api/pm/msp/resources/: get: tags: [resources] summary: Список ресурсов operationId: listResources parameters: - { name: ids, in: query, schema: { type: string } } - { name: projects, in: query, schema: { type: string } } - { name: type, in: query, schema: { type: string } } - { name: parent, in: query, schema: { type: integer } } - { name: company, in: query, schema: { type: integer } } - { name: full, in: query, schema: { type: boolean } } - { name: extend, in: query, schema: { type: boolean } } - $ref: '#/components/parameters/Limit' - $ref: '#/components/parameters/Offset' responses: { '200': { description: Список, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/Resource' } } } } } } post: tags: [resources] summary: Создать ресурс operationId: createResource requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/ResourceCreate' } } } } responses: { '201': { description: Создано, content: { application/json: { schema: { $ref: '#/components/schemas/Resource' } } } } } /api/pm/msp/resources/{id}/: parameters: [ { $ref: '#/components/parameters/PathId' } ] get: { tags: [resources], summary: Ресурс по id, operationId: retrieveResource, responses: { '200': { description: Ресурс, content: { application/json: { schema: { $ref: '#/components/schemas/Resource' } } } }, '404': { $ref: '#/components/responses/NotFound' } } } put: { tags: [resources], summary: Обновить ресурс, operationId: updateResource, requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/ResourceCreate' } } } }, responses: { '200': { description: Обновлено, content: { application/json: { schema: { $ref: '#/components/schemas/Resource' } } } } } } patch: { tags: [resources], summary: Частичное обновление ресурса, operationId: partialUpdateResource, requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/ResourceCreate' } } } }, responses: { '200': { description: Обновлено, content: { application/json: { schema: { $ref: '#/components/schemas/Resource' } } } } } } delete: { tags: [resources], summary: Удалить ресурс, operationId: deleteResource, responses: { '204': { description: Удалено } } } /api/pm/msp/resources/bulk_create/: post: tags: [resources] summary: Массовое создание ресурсов operationId: bulkCreateResources requestBody: { required: true, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/ResourceCreate' } } } } } responses: { '201': { description: Создано } } /api/pm/msp/resources/resource_planning/: get: tags: [resources] summary: Ресурсное планирование operationId: resourcePlanning parameters: - { name: projects, in: query, schema: { type: string } } - { name: resource_type, in: query, schema: { type: string, default: human } } - { name: start, in: query, required: true, schema: { type: string, format: date-time } } - { name: end, in: query, required: true, schema: { type: string, format: date-time } } - { name: scale, in: query, schema: { type: string, default: D } } - { name: group, in: query, schema: { type: string } } responses: { '200': { description: Данные, content: { application/json: { schema: { type: object } } } } } /api/pm/msp/resources/{id}/bind_task/: parameters: [ { $ref: '#/components/parameters/PathId' } ] post: { tags: [resources], summary: Привязать задачу к ресурсу, operationId: bindTaskToResource, requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/ResourceTaskRelation' } } } }, responses: { '201': { description: Создано } } } /api/pm/msp/resources/{id}/bind_elements/: parameters: [ { $ref: '#/components/parameters/PathId' } ] post: { tags: [resources], summary: Привязать элементы к ресурсу, operationId: bindElementsToResource, responses: { '201': { description: Создано } } } # ---- Resource relations ---- /api/pm/msp/resources-tasks/: get: tags: [resources] summary: Список привязок ресурс-задача operationId: listResourceTaskRelations parameters: - { name: projects, in: query, schema: { type: string } } - { name: tasks, in: query, schema: { type: string } } - { name: resources, in: query, schema: { type: string } } responses: { '200': { description: Список, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/ResourceTaskRelation' } } } } } } post: { tags: [resources], summary: Создать привязку ресурс-задача, operationId: createResourceTaskRelation, requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/ResourceTaskRelation' } } } }, responses: { '201': { description: Создано } } } /api/pm/msp/resources-tasks/{id}/: parameters: [ { $ref: '#/components/parameters/PathId' } ] get: { tags: [resources], summary: Привязка по id, operationId: retrieveResourceTaskRelation, responses: { '200': { description: Привязка, content: { application/json: { schema: { $ref: '#/components/schemas/ResourceTaskRelation' } } } } } } patch: { tags: [resources], summary: Обновить привязку, operationId: updateResourceTaskRelation, requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/ResourceTaskRelation' } } } }, responses: { '200': { description: Обновлено } } } delete: { tags: [resources], summary: Удалить привязку, operationId: deleteResourceTaskRelation, responses: { '204': { description: Удалено } } } /api/pm/msp/resources-tasks/assign/: patch: { tags: [resources], summary: Назначение ресурса на задачи, operationId: assignResourceToTasks, responses: { '200': { description: OK } } } /api/pm/msp/resources-tasks/bulk_update/: patch: tags: [resources] summary: Массовое обновление привязок ресурс-задача operationId: bulkUpdateResourceTaskRelations requestBody: { required: true, content: { application/json: { schema: { type: object, properties: { ids: { type: array, items: { type: integer } }, profile: { type: integer } } } } } } responses: { '200': { description: OK } } /api/pm/msp/resources-tasks/bulk_delete/: delete: tags: [resources] summary: Массовое удаление привязок ресурс-задача operationId: bulkDeleteResourceTaskRelations requestBody: { required: true, content: { application/json: { schema: { type: object, properties: { ids: { type: array, items: { type: integer } } } } } } } responses: { '204': { description: Удалено } } /api/pm/msp/resources-elements/: get: tags: [resources] summary: Список привязок ресурс-элемент operationId: listResourceElementRelations parameters: - { name: resources, in: query, schema: { type: string } } - { name: instances, in: query, schema: { type: string } } responses: { '200': { description: Список, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/ResourceElementRelation' } } } } } } post: { tags: [resources], summary: Создать привязку ресурс-элемент, operationId: createResourceElementRelation, requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/ResourceElementRelation' } } } }, responses: { '201': { description: Создано } } } # ========================================================================== # Relations (task/project) # ========================================================================== /api/pm/msp/task-relations/: get: tags: [relations] summary: Список связей задач operationId: listTaskRelations parameters: [ { name: project, in: query, schema: { type: integer } } ] responses: { '200': { description: Список, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/Task2TaskRelation' } } } } } } post: { tags: [relations], summary: Создать связь задач, operationId: createTaskRelation, requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/Task2TaskRelation' } } } }, responses: { '201': { description: Создано } } } /api/pm/msp/task-relations/{id}/: parameters: [ { $ref: '#/components/parameters/PathId' } ] put: { tags: [relations], summary: Обновить связь задач, operationId: updateTaskRelation, requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/Task2TaskRelation' } } } }, responses: { '200': { description: Обновлено } } } delete: { tags: [relations], summary: Удалить связь задач, operationId: deleteTaskRelation, responses: { '204': { description: Удалено } } } /api/pm/msp/task-relations/bulk_delete/: delete: tags: [relations] summary: Массовое удаление связей задач operationId: bulkDeleteTaskRelations requestBody: { required: true, content: { application/json: { schema: { type: object, properties: { ids: { type: array, items: { type: integer } } } } } } } responses: { '204': { description: Удалено } } /api/pm/msp/project-relations/: get: { tags: [relations], summary: Список связей проектов, operationId: listProjectRelations, parameters: [ { name: project, in: query, schema: { type: integer } } ], responses: { '200': { description: Список, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/Project2ProjectRelation' } } } } } } } post: tags: [relations] summary: Создать связь(и) проектов operationId: createProjectRelation requestBody: { required: true, content: { application/json: { schema: { oneOf: [ { $ref: '#/components/schemas/Project2ProjectRelation' }, { type: array, items: { $ref: '#/components/schemas/Project2ProjectRelation' } } ] } } } } responses: { '201': { description: Создано } } /api/pm/msp/project-relations/{id}/: parameters: [ { $ref: '#/components/parameters/PathId' } ] patch: { tags: [relations], summary: Обновить связь проектов, operationId: updateProjectRelation, requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/Project2ProjectRelation' } } } }, responses: { '200': { description: Обновлено } } } delete: { tags: [relations], summary: Удалить связь проектов, operationId: deleteProjectRelation, responses: { '204': { description: Удалено } } } /api/pm/msp/entity-relations/: get: { tags: [relations], summary: Список сущностных связей, operationId: listEntityRelations, parameters: [ { name: project_id, in: query, schema: { type: integer } } ], responses: { '200': { description: Список, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/EntityRelation' } } } } } } } post: { tags: [relations], summary: Создать сущностную связь, operationId: createEntityRelation, requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/EntityRelation' } } } }, responses: { '201': { description: Создано } } } /api/pm/msp/entity-relations/{id}/: parameters: [ { $ref: '#/components/parameters/PathId' } ] patch: { tags: [relations], summary: Обновить сущностную связь, operationId: updateEntityRelation, requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/EntityRelation' } } } }, responses: { '200': { description: Обновлено } } } delete: { tags: [relations], summary: Удалить сущностную связь, operationId: deleteEntityRelation, responses: { '204': { description: Удалено } } } # ========================================================================== # Attributes # ========================================================================== /api/pm/msp/project-attribute/: get: tags: [attributes] summary: Атрибуты проекта operationId: listProjectAttributes parameters: [ { name: project, in: query, schema: { type: integer } } ] responses: { '200': { description: Список, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/ProjectAttributeRelation' } } } } } } post: tags: [attributes] summary: Привязать атрибут(ы) к проекту operationId: createProjectAttribute requestBody: { required: true, content: { application/json: { schema: { oneOf: [ { $ref: '#/components/schemas/ProjectAttributeRelation' }, { type: array, items: { $ref: '#/components/schemas/ProjectAttributeRelation' } } ] } } } } responses: { '201': { description: Создано } } /api/pm/msp/project-attribute/{id}/: parameters: [ { $ref: '#/components/parameters/PathId' } ] put: { tags: [attributes], summary: Обновить атрибут проекта, operationId: updateProjectAttribute, requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/ProjectAttributeRelation' } } } }, responses: { '200': { description: Обновлено } } } delete: { tags: [attributes], summary: Удалить атрибут проекта, operationId: deleteProjectAttribute, responses: { '204': { description: Удалено } } } /api/pm/msp/task-value/: get: tags: [attributes] summary: Значения атрибутов задач operationId: listTaskValues parameters: - { name: tasks, in: query, description: CSV, schema: { type: string } } - { name: attributes, in: query, description: CSV, schema: { type: string } } responses: { '200': { description: Список, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/TaskAttributeValue' } } } } } } post: tags: [attributes] summary: Создать значение(я) атрибутов задач operationId: createTaskValue requestBody: { required: true, content: { application/json: { schema: { oneOf: [ { $ref: '#/components/schemas/TaskAttributeValue' }, { type: array, items: { $ref: '#/components/schemas/TaskAttributeValue' } } ] } } } } responses: { '201': { description: Создано } } # ========================================================================== # Calendars # ========================================================================== /api/pm/msp/calendars/: get: { tags: [calendars], summary: Список календарей, operationId: listCalendars, responses: { '200': { description: Список, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/Calendar' } } } } } } } post: { tags: [calendars], summary: Создать календарь, operationId: createCalendar, requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/Calendar' } } } }, responses: { '201': { description: Создано, content: { application/json: { schema: { $ref: '#/components/schemas/Calendar' } } } } } } /api/pm/msp/calendars/{id}/: parameters: [ { $ref: '#/components/parameters/PathId' } ] get: { tags: [calendars], summary: Календарь по id, operationId: retrieveCalendar, responses: { '200': { description: Календарь, content: { application/json: { schema: { $ref: '#/components/schemas/Calendar' } } } } } } patch: { tags: [calendars], summary: Изменить календарь, operationId: updateCalendar, requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/Calendar' } } } }, responses: { '200': { description: Обновлено } } } delete: { tags: [calendars], summary: Удалить календарь, operationId: deleteCalendar, responses: { '204': { description: Удалено } } } # ========================================================================== # Project settings (KSG) # ========================================================================== /api/pm/msp/project-settings/: get: { tags: [settings], summary: Список настроек КСГ, operationId: listProjectSettings, responses: { '200': { description: Список, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/ProjectSettings' } } } } } } } post: { tags: [settings], summary: Создать настройку КСГ, operationId: createProjectSettings, requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/ProjectSettings' } } } }, responses: { '201': { description: Создано } } } /api/pm/msp/project-settings/{id}/: parameters: [ { $ref: '#/components/parameters/PathId' } ] get: { tags: [settings], summary: Настройка по id, operationId: retrieveProjectSettings, responses: { '200': { description: Настройка, content: { application/json: { schema: { $ref: '#/components/schemas/ProjectSettings' } } } } } } patch: { tags: [settings], summary: Изменить настройку, operationId: updateProjectSettings, requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/ProjectSettings' } } } }, responses: { '200': { description: Обновлено } } } delete: { tags: [settings], summary: Удалить настройку, operationId: deleteProjectSettings, responses: { '204': { description: Удалено } } } /api/pm/msp/project-settings/{id}/apply/: parameters: [ { $ref: '#/components/parameters/PathId' } ] post: tags: [settings] summary: Применить глобальную настройку operationId: applyProjectSettings requestBody: required: true content: application/json: schema: type: object properties: project_id: { type: integer } connect_missing_attributes: { type: boolean } required: [project_id] responses: { '200': { description: OK } } # ========================================================================== # Visual profiles # ========================================================================== /api/pm/msp/visual-profiles/: get: { tags: [detailing], summary: Список визуальных профилей, operationId: listVisualProfiles, responses: { '200': { description: Список, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/VisualProfile' } } } } } } } post: { tags: [detailing], summary: Создать визуальный профиль, operationId: createVisualProfile, requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/VisualProfile' } } } }, responses: { '201': { description: Создано } } } /api/pm/msp/visual-profiles/{id}: parameters: [ { $ref: '#/components/parameters/PathId' } ] patch: { tags: [detailing], summary: Изменить визуальный профиль, operationId: updateVisualProfile, requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/VisualProfile' } } } }, responses: { '200': { description: Обновлено } } } delete: { tags: [detailing], summary: Удалить визуальный профиль, operationId: deleteVisualProfile, responses: { '204': { description: Удалено } } } # ========================================================================== # Comments # ========================================================================== /api/pm/msp/comments/: get: { tags: [comments], summary: Список комментариев, operationId: listComments, responses: { '200': { description: Список, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/Comment' } } } } } } } post: { tags: [comments], summary: Создать комментарий, operationId: createComment, requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/CommentCreate' } } } }, responses: { '201': { description: Создано } } } # ========================================================================== # Descriptions resource # ========================================================================== /api/pm/msp/descriptions/: get: { tags: [tasks], summary: Список описаний, operationId: listDescriptions, parameters: [ { name: task, in: query, schema: { type: integer } } ], responses: { '200': { description: Список, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/Description' } } } } } } } post: { tags: [tasks], summary: Создать описание, operationId: createDescription, requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/Description' } } } }, responses: { '201': { description: Создано } } } # ========================================================================== # Task info (public/service) # ========================================================================== /api/pm/msp/task-info/: get: tags: [tasks] summary: Задачи по профилям (not_started/in_progress/completed) operationId: getTaskInfo parameters: - { name: projects, in: query, required: true, description: CSV идентификаторов, schema: { type: string } } - { name: date, in: query, required: true, schema: { type: string, format: date-time } } - { name: document, in: query, required: true, schema: { type: string } } - { name: base_plane, in: query, schema: { type: string } } responses: { '200': { description: Данные, content: { application/json: { schema: { type: object } } } } } /api/pm/msp/external-task-info/: get: tags: [tasks] summary: Инфо о фоновой задаче через брокер operationId: getExternalTaskInfo security: [] parameters: [ { name: task_id, in: query, schema: { type: string, format: uuid } } ] responses: { '200': { description: Данные, content: { application/json: { schema: { type: object } } } } } # ========================================================================== # External API v2 (read-only) # ========================================================================== /api/pm/external/v2/projects/: get: tags: [external] summary: Список проектов (внешний) operationId: externalListProjects parameters: - $ref: '#/components/parameters/Limit' - $ref: '#/components/parameters/Offset' - { name: search, in: query, schema: { type: string } } - { name: ids, in: query, schema: { type: string } } - { name: parent_id, in: query, schema: { type: integer } } responses: { '200': { description: Список, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/Project' } } } } } } /api/pm/external/v2/projects/{id}/: parameters: [ { $ref: '#/components/parameters/PathId' } ] get: { tags: [external], summary: Проект (внешний), operationId: externalRetrieveProject, responses: { '200': { description: Проект, content: { application/json: { schema: { $ref: '#/components/schemas/Project' } } } }, '404': { $ref: '#/components/responses/NotFound' } } } /api/pm/external/v2/projects/{id}/project_data/: parameters: [ { $ref: '#/components/parameters/PathId' } ] get: { tags: [external], summary: Данные проекта (внешний), operationId: externalProjectData, responses: { '200': { description: Данные, content: { application/json: { schema: { type: object } } } } } } /api/pm/external/v2/tasks/: get: tags: [external] summary: Список задач (внешний) operationId: externalListTasks parameters: - { name: extend, in: query, schema: { type: boolean } } - { name: ids, in: query, schema: { type: string } } - $ref: '#/components/parameters/Limit' - $ref: '#/components/parameters/Offset' responses: { '200': { description: Список, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/ProjectTask' } } } } } } /api/pm/external/v2/tasks/{id}/: parameters: [ { $ref: '#/components/parameters/PathId' } ] get: { tags: [external], summary: Задача (внешний), operationId: externalRetrieveTask, responses: { '200': { description: Задача, content: { application/json: { schema: { $ref: '#/components/schemas/ProjectTask' } } } } } } /api/pm/external/v2/values/: get: { tags: [external], summary: Фактические значения (внешний), operationId: externalListValues, responses: { '200': { description: Список, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/ActualValue' } } } } } } } /api/pm/external/v2/values/{id}/: parameters: [ { $ref: '#/components/parameters/PathId' } ] get: { tags: [external], summary: Значение (внешний), operationId: externalRetrieveValue, responses: { '200': { description: Значение, content: { application/json: { schema: { $ref: '#/components/schemas/ActualValue' } } } } } } /api/pm/external/v2/project-states/: get: tags: [external] summary: Состояния проекта (внешний) operationId: externalListStates parameters: [ { name: project_id, in: query, schema: { type: integer } } ] responses: { '200': { description: Список, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/ProjectState' } } } } } } /api/pm/external/v2/project-states/{id}/: parameters: [ { $ref: '#/components/parameters/PathId' } ] get: { tags: [external], summary: Состояние (внешний), operationId: externalRetrieveState, responses: { '200': { description: Состояние, content: { application/json: { schema: { $ref: '#/components/schemas/ProjectState' } } } } } } /api/pm/external/v2/project-states/{id}/data/: parameters: [ { $ref: '#/components/parameters/PathId' } ] get: { tags: [external], summary: Данные состояния (внешний), operationId: externalStateData, responses: { '200': { description: Данные, content: { application/json: { schema: { type: object } } } } } } # ========================================================================== # Internal API (no auth, cluster-only) # ========================================================================== /internal/pm/auto_scheduling/: post: tags: [internal] summary: Интегрированное автопланирование operationId: internalAutoScheduling security: [] requestBody: { required: true, content: { application/json: { schema: { type: object, properties: { project_id: { type: integer } }, required: [project_id] } } } } responses: { '200': { description: OK } } /internal/pm/integrated_base_plan/: post: tags: [internal] summary: Создать/обновить интегрированные состояния operationId: internalIntegratedBasePlan security: [] requestBody: required: true content: application/json: schema: type: object properties: project_id: { type: integer } user_id: { type: integer } linked_project: { type: integer } name: { type: string } tasks: { type: array, items: { type: object } } task_ids: { type: array, items: { type: integer } } state_id: { type: integer } responses: { '200': { description: OK } } /internal/pm/detailed_tasks/: post: tags: [internal] summary: Обновить значения атрибутов задачи operationId: internalDetailedTasks security: [] requestBody: required: true content: application/json: schema: type: object properties: task_id: { type: integer } result_fields: { type: object } company_id: { type: integer } required: [task_id, company_id] responses: { '200': { description: OK } } /internal/pm/sync_tasks/: post: tags: [internal] summary: Синхронизация задач + автопланирование operationId: internalSyncTasks security: [] requestBody: { required: true, content: { application/json: { schema: { type: object, properties: { project_id: { type: integer } }, required: [project_id] } } } } responses: { '200': { description: OK } } /internal/pm/base_plans/: get: tags: [internal] summary: Получить базовый план operationId: internalGetBasePlan security: [] parameters: [ { name: base_plan_id, in: query, required: true, schema: { type: integer } } ] responses: '200': { description: Данные, content: { application/json: { schema: { type: object } } } } '400': { $ref: '#/components/responses/BadRequest' } /internal/pm/tasks_dates/: get: tags: [internal] summary: Даты и прогресс задач проекта operationId: internalTasksDates security: [] parameters: [ { name: project_id, in: query, required: true, schema: { type: integer } } ] responses: '200': { description: Данные, content: { application/json: { schema: { type: object } } } } '400': { $ref: '#/components/responses/BadRequest' } components: securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: JWT description: | JWT в заголовке `Authorization: Bearer `. В режиме sarex-backend подпись проверяется публичным ключом (`RS512`); в режиме Zitadel дополнительно требуется заголовок `identity`. identityToken: type: apiKey in: header name: identity description: | Заголовок `identity` (`Identity `) для режима Zitadel. При его наличии полезная нагрузка берётся из этого токена. parameters: PathId: name: id in: path required: true schema: { type: integer } Limit: name: limit in: query required: false schema: { type: integer, minimum: 0, default: 1000 } Offset: name: offset in: query required: false schema: { type: integer, minimum: 0, default: 0 } responses: BadRequest: description: Некорректный запрос / ошибка валидации content: application/json: schema: { $ref: '#/components/schemas/ValidationError' } Unauthorized: description: Токен не предоставлен или невалиден content: application/json: schema: { $ref: '#/components/schemas/Detail' } Forbidden: description: Недостаточно прав content: application/json: schema: { $ref: '#/components/schemas/Detail' } NotFound: description: Ресурс не найден content: application/json: schema: { $ref: '#/components/schemas/Detail' } schemas: # --- Общие ------------------------------------------------------------ Detail: type: object properties: detail: { type: string } required: [detail] ValidationError: type: object description: | Ошибка валидации DRF — либо словарь `{ "": ["", ...] }`, либо `{ "detail": "..." }`. additionalProperties: type: array items: { type: string } PaginatedResponse: type: object description: Обёртка DRF LimitOffsetPagination properties: count: { type: integer } next: { type: string, format: uri, nullable: true } previous: { type: string, format: uri, nullable: true } results: { type: array, items: { type: object } } required: [count, results] # --- Проекты ---------------------------------------------------------- Project: type: object properties: id: { type: integer, readOnly: true } name: { type: string } company: { type: integer } parent: { type: integer, nullable: true } is_folder: { type: boolean } resource_id: { type: string, nullable: true } document_id: { type: string, nullable: true } bundle_id: { type: string, nullable: true } description: { type: string, nullable: true } settings: { type: object } target_date: { type: string, format: date-time, nullable: true } project_type: { type: string, nullable: true } calendar: { type: integer, nullable: true } status: { type: string, readOnly: true } creator: { type: integer, readOnly: true } permissions: { type: object, readOnly: true } restrictions: { type: object, readOnly: true } time_start: { type: string, format: date-time, readOnly: true, nullable: true } time_end: { type: string, format: date-time, readOnly: true, nullable: true } total_cost: { type: number, readOnly: true, nullable: true } current_cost: { type: number, readOnly: true, nullable: true } progress: { type: number, readOnly: true, nullable: true } relations: { type: array, items: { type: object }, readOnly: true } rule: { type: object, nullable: true } created_at: { type: string, format: date-time, readOnly: true } updated_at: { type: string, format: date-time, readOnly: true } required: [name, company] ProjectCreate: type: object properties: name: { type: string } company_id: { type: integer } parent: { type: integer, nullable: true } is_folder: { type: boolean } resource_id: { type: string, nullable: true } ms_file: { type: string, nullable: true } description: { type: string, nullable: true } document_id: { type: string, nullable: true } bundle_id: { type: string, nullable: true } settings: { type: object, default: {} } target_date: { type: string, format: date-time, nullable: true } project_type: { type: string, nullable: true } calendar: { type: integer, nullable: true } required: [name, company_id] FolderCreate: type: object properties: company: { type: integer } company_name: { type: string } creator: { type: integer } service_account: { type: string, format: uuid } folder_name: { type: string, nullable: true } required: [company, company_name, creator, service_account] PermissionsResponse: type: object properties: direct_permissions: { type: array, items: { type: object } } inherited_permissions: { type: array, items: { type: object } } # --- Задачи ----------------------------------------------------------- ProjectTask: type: object properties: id: { type: integer, readOnly: true } project: { type: integer } parent: { type: integer, nullable: true } index_prefix: { type: string, nullable: true } task_id: { type: string, nullable: true } name: { type: string } time_start: { type: string, format: date-time, nullable: true } time_end: { type: string, format: date-time, nullable: true } deadline: { type: string, format: date-time, nullable: true } responsible: { type: array, items: { type: integer } } executors: { type: array, items: { type: integer } } task_type: { type: string } unit: { type: string, nullable: true } local_index: { type: integer, nullable: true } constraint_date: { type: string, format: date-time, nullable: true } constraint_type: { type: string, nullable: true } cost: { type: number, nullable: true } progress: { type: number, nullable: true } status: { type: string, nullable: true } duration: { type: number, nullable: true } planned_duration: { type: number, nullable: true } cipher: { type: string, nullable: true } is_active: { type: boolean } current_cost: { type: number, nullable: true } planned_value: { type: number, nullable: true } calendar: { type: integer, nullable: true } distribution: { type: object, nullable: true } actual_value: { type: number, readOnly: true, nullable: true } total_cost: { type: number, readOnly: true, nullable: true } attributes: { type: array, items: { type: object } } permissions: { type: object } ProjectTaskCreate: type: object properties: project: { type: integer } parent: { type: integer, nullable: true } name: { type: string } time_start: { type: string, format: date-time } time_end: { type: string, format: date-time } deadline: { type: string, format: date-time, nullable: true } responsible: { type: array, items: { type: integer } } executors: { type: array, items: { type: integer } } task_type: { type: string, default: fix_value } unit: { type: string, nullable: true } cost: { type: number, nullable: true } progress: { type: number, nullable: true } status: { type: string, nullable: true } is_active: { type: boolean, default: true } calendar: { type: integer, nullable: true } required: [project, name, time_start, time_end] ProjectTaskUpdate: type: object description: Плоский сериализатор для массового обновления properties: id: { type: integer } name: { type: string } time_start: { type: string, format: date-time } time_end: { type: string, format: date-time } responsible: { type: array, items: { type: integer } } executors: { type: array, items: { type: integer } } cost: { type: number } progress: { type: number } status: { type: string } required: [id] ActualValue: type: object properties: id: { type: integer, readOnly: true } value: { type: number } description: { type: string, nullable: true } task: { type: integer } time_start: { type: string, format: date-time } time_end: { type: string, format: date-time } creator: { type: integer, readOnly: true } created_at: { type: string, format: date-time, readOnly: true } updated_at: { type: string, format: date-time, readOnly: true } ActualValueCreate: type: object properties: value: { type: number } description: { type: string, nullable: true } task: { type: integer } time_start: { type: string, format: date-time } time_end: { type: string, format: date-time } required: [value, task, time_start, time_end] DetailedTask: type: object properties: id: { type: integer, readOnly: true } task: { type: integer } weight: { type: number, nullable: true } data: { type: object } Description: type: object properties: id: { type: integer, readOnly: true } task: { type: integer } text: { type: string } required: [task, text] # --- Состояния -------------------------------------------------------- ProjectState: type: object properties: id: { type: integer, readOnly: true } name: { type: string } project: { type: integer } creator: { type: integer, readOnly: true } is_locked: { type: boolean, readOnly: true } ProjectStateCreate: type: object properties: name: { type: string } project: { type: integer } required: [name, project] # --- Ресурсы ---------------------------------------------------------- Resource: type: object properties: id: { type: integer, readOnly: true } name: { type: string } company: { type: integer } calendar: { type: integer, nullable: true } project: { type: integer, nullable: true } resource_type: { type: string } parent: { type: integer, nullable: true } cost: { type: number, nullable: true } path: { type: string, readOnly: true } is_leaf: { type: boolean, readOnly: true } elements: { type: array, items: { type: integer } } projects: { type: array, items: { type: object } } ResourceCreate: type: object properties: name: { type: string } company: { type: integer } calendar: { type: integer, nullable: true } project: { type: integer, nullable: true } resource_type: { type: string } parent: { type: integer, nullable: true } cost: { type: number, nullable: true } required: [name, company] ResourceTaskRelation: type: object properties: id: { type: integer, readOnly: true } resource: { type: integer } task: { type: integer } profile: { type: integer, nullable: true } ResourceElementRelation: type: object properties: id: { type: integer, readOnly: true } resource: { type: integer } instance: { type: string } model_name: { type: string } attributes: { type: object } path: { type: string, nullable: true } # --- Связи ------------------------------------------------------------ Task2TaskRelation: type: object properties: id: { type: integer, readOnly: true } target: { type: integer } source: { type: integer } type: { type: string } lag: { type: number, nullable: true } Project2ProjectRelation: type: object properties: id: { type: integer, readOnly: true } successor: { type: integer } predecessor: { type: integer } mode: { type: string, nullable: true } EntityRelation: type: object properties: id: { type: integer, readOnly: true } project: { type: integer } entity_name: { type: string } attribute_ids: { type: array, items: { type: integer } } settings: { type: object } entity_type: { type: string } # --- Атрибуты --------------------------------------------------------- ProjectAttributeRelation: type: object properties: id: { type: integer, readOnly: true } project: { type: integer } asset_id: { type: string, nullable: true } attribute_id: { type: integer } is_system: { type: boolean } default_value: { nullable: true } mode: { type: string, nullable: true } TaskAttributeValue: type: object properties: id: { type: integer, readOnly: true } task: { type: integer } attribute_id: { type: integer } type: { type: string, description: "Тип значения (вход): integer/float/string/datetime/date" } values: { description: Значение(я) атрибута } integer_value: { type: integer, nullable: true } float_value: { type: number, nullable: true } string_value: { type: string, nullable: true } datetime_value: { type: string, format: date-time, nullable: true } date_value: { type: string, format: date, nullable: true } assets: { type: array, items: { type: string } } # --- Календари -------------------------------------------------------- Calendar: type: object properties: id: { type: integer, readOnly: true } name: { type: string } company: { type: integer } mask: { type: array, items: { type: integer } } work_time_start: { type: string } work_time_end: { type: string } work_day_duration: { type: number, readOnly: true } is_system: { type: boolean } holidays: { type: array, items: { type: string, format: date } } exception_holidays: { type: array, items: { type: string, format: date } } creator: { type: integer, readOnly: true } projects_count: { type: integer, readOnly: true } created_at: { type: string, format: date-time, readOnly: true } updated_at: { type: string, format: date-time, readOnly: true } required: [name, mask, work_time_start, work_time_end] # --- Настройки -------------------------------------------------------- ProjectSettings: type: object properties: id: { type: integer, readOnly: true } name: { type: string } project: { type: integer, nullable: true } state: { type: integer, nullable: true } image: { type: string, nullable: true } is_default: { type: boolean } visibility: { type: string, enum: [global, private, public] } company_id: { type: integer, nullable: true } service_accounts: { type: array, items: { type: string } } creator: { type: integer, readOnly: true } created_at: { type: string, format: date-time, readOnly: true } updated_at: { type: string, format: date-time, readOnly: true } VisualProfile: type: object properties: id: { type: integer, readOnly: true } name: { type: string } color: { type: string, description: "HEX-цвет, напр. #RRGGBB" } project: { type: integer } behavior: { type: object } Comment: type: object properties: id: { type: integer, readOnly: true } text: { type: string } task: { type: integer } creator: { type: integer, readOnly: true } created_at: { type: string, format: date-time, readOnly: true } updated_at: { type: string, format: date-time, readOnly: true } CommentCreate: type: object properties: text: { type: string } task: { type: integer } required: [text, task] # --- Системный журнал ------------------------------------------------- SystemLog: type: object properties: id: { type: integer, readOnly: true } project_id: { type: integer } user_id: { type: integer, nullable: true } created_at: { type: string, format: date-time, readOnly: true } message_data: { type: object } action: { type: string } entity: { type: string } changes: { type: object, description: "Только в detail-варианте" } # --- Экспорт ---------------------------------------------------------- ExportProject: type: object properties: export_fields: { type: array, items: { type: string } } export_attrs: { type: array, items: { type: integer } } fields_order: { type: array, items: { type: string } } data: { type: array, items: { type: object } } page_format: { type: string } page_size: { type: string } left_table_percent_width: { type: number, minimum: 0, maximum: 100 } base_plan_ids: { type: array, items: { type: integer } } margins: { type: array, items: { type: number } } expanded_tasks: { type: array, items: { type: integer } } header_text: { type: string } footer_text: { type: string } display_relations: { type: boolean } date_from: { type: string, format: date } date_to: { type: string, format: date } fields_width: { type: object } project_name: { type: string } show_counter: { type: boolean } font_size: { type: integer } conditional_formatting: { type: array, items: { type: object } } required: [export_fields, data]