Files
portal/docs/api/openapi.yaml
T

705 lines
21 KiB
YAML
Raw Normal View History

2026-05-17 15:01:19 +03:00
# Стартовый OpenAPI-скелет (smoke A3-интеграции, 2026-05-17).
# Покрывает только группу /api/deals*. Полная спека REST API — отдельная задача вне scope A3.
openapi: 3.1.0
info:
title: Лидерра CRM — Deals API
version: 0.1.0
description: >
Частичная спека REST API Лидерры. Скелет покрывает только группу /api/deals*.
Все эндпоинты требуют аутентификации (Laravel Sanctum) и разрешают доступ
только к сделкам текущего тенанта (RLS + middleware tenant).
servers:
- url: https://app.liderra.ru
description: Production
security:
- sanctumCookie: []
tags:
- name: deals
description: Управление сделками (CRUD + bulk-операции + экспорт)
paths:
/api/deals:
get:
operationId: deals.index
tags: [deals]
summary: Список сделок тенанта
description: >
Возвращает сделки тенанта с пагинацией. Поддерживает два режима пагинации:
keyset (cursor) — O(1) глубины; offset-based — backward-совместимость.
При count_only=true возвращает только {"total": N} без строк.
parameters:
- name: status_in
2026-05-17 15:01:19 +03:00
in: query
description: >
Фильтр по статусам (можно несколько). На проводе сериализуется
Laravel array-binding: status_in[]=NEW&status_in[]=WON. Имя параметра
в спецификации — без скобок: ключи свойств MCP-инструмента обязаны
матчить ^[a-zA-Z0-9_.-]{1,64}$ (скобки запрещены, иначе Anthropic
tools-схема падает с 400).
2026-05-17 15:01:19 +03:00
required: false
schema:
type: array
items:
type: string
style: form
explode: true
- name: project_id
in: query
required: false
schema:
type: integer
- name: manager_id
in: query
required: false
schema:
type: integer
- name: search
in: query
description: ILIKE-поиск по phone / contact_name
required: false
schema:
type: string
- name: limit
in: query
required: false
schema:
type: integer
minimum: 1
maximum: 500
default: 100
- name: offset
in: query
description: Используется только без cursor (OFFSET-режим)
required: false
schema:
type: integer
minimum: 0
default: 0
- name: cursor
in: query
description: base64-encoded keyset cursor от предыдущей страницы
required: false
schema:
type: string
- name: only_deleted
in: query
description: Показывать только soft-deleted (корзина)
required: false
schema:
type: boolean
default: false
- name: count_only
in: query
description: 'Вернуть только {"total": N}, без строк (для бейджа сайдбара)'
required: false
schema:
type: boolean
default: false
responses:
'200':
description: Успех
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/DealsListResponse'
- $ref: '#/components/schemas/DealsCountResponse'
examples:
list:
summary: Обычный список
value:
deals:
- id: 42
tenant_id: 1
project_id: 5
project_name: "B1-Москва"
phone: "+79001234567"
contact_name: "Иван Иванов"
status: "new"
manager_id: 3
manager_name: "Мария К."
manager_initials: "МК"
received_at: "2026-05-17T10:00:00+03:00"
total: 1
offset: 0
limit: 100
next_cursor: null
count_only:
summary: count_only=true
value:
total: 157
'401':
$ref: '#/components/responses/Unauthorized'
post:
operationId: deals.store
tags: [deals]
summary: Создать сделку вручную
description: >
Ручное создание сделки из UI (не webhook). source_crm_id = NULL,
баланс не списывается. Project резолвится или создаётся по project_name.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/DealStoreRequest'
example:
project_name: "B1-Москва"
phone: "+79001234567"
contact_name: "Иван Иванов"
status: "new"
manager_id: 3
comment: "Заинтересован, перезвонить в 15:00"
responses:
'201':
description: Сделка создана
content:
application/json:
schema:
$ref: '#/components/schemas/DealStoreResponse'
'422':
$ref: '#/components/responses/ValidationError'
'401':
$ref: '#/components/responses/Unauthorized'
delete:
operationId: deals.bulkDestroy
tags: [deals]
summary: Bulk soft-delete сделок
description: Мягкое удаление нескольких сделок. Идемпотентно.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/BulkIdsRequest'
example:
ids: [42, 43, 44]
responses:
'200':
description: Результат удаления
content:
application/json:
schema:
$ref: '#/components/schemas/BulkDestroyResponse'
example:
deleted: 3
requested: 3
'422':
$ref: '#/components/responses/ValidationError'
'401':
$ref: '#/components/responses/Unauthorized'
/api/deals/{id}:
get:
operationId: deals.show
tags: [deals]
summary: Детали сделки + лог активности
description: >
Возвращает сделку с relations и до 50 последних событий activity_log.
Используется в DealDetailDrawer.
parameters:
- $ref: '#/components/parameters/DealId'
responses:
'200':
description: Успех
content:
application/json:
schema:
$ref: '#/components/schemas/DealShowResponse'
'404':
$ref: '#/components/responses/NotFound'
'401':
$ref: '#/components/responses/Unauthorized'
patch:
operationId: deals.update
tags: [deals]
summary: Редактировать сделку (частичное обновление)
description: >
Частичное обновление: comment / manager_id / status. Каждое изменение
пишется в activity_log. NO-OP (значение не изменилось) — лог не пишется.
parameters:
- $ref: '#/components/parameters/DealId'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/DealUpdateRequest'
example:
status: "in_progress"
manager_id: 5
comment: "Уточнить условия"
responses:
'200':
description: Сделка обновлена
content:
application/json:
schema:
$ref: '#/components/schemas/DealUpdateResponse'
'404':
$ref: '#/components/responses/NotFound'
'422':
$ref: '#/components/responses/ValidationError'
'401':
$ref: '#/components/responses/Unauthorized'
/api/deals/export:
post:
operationId: deals.export
tags: [deals]
summary: Экспорт сделок в CSV или XLSX
description: >
Streaming-экспорт через OpenSpout (O(1) memory). Формат по умолчанию — csv.
CSV: UTF-8 + BOM, разделитель ;. XLSX: bold-заголовок, sheet «Сделки».
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/DealExportRequest'
example:
ids: [42, 43, 44]
format: csv
responses:
'200':
description: Файл экспорта (streamed)
content:
text/csv:
schema:
type: string
format: binary
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:
schema:
type: string
format: binary
'422':
$ref: '#/components/responses/ValidationError'
'401':
$ref: '#/components/responses/Unauthorized'
/api/deals/transition:
post:
operationId: deals.bulkTransition
tags: [deals]
summary: Bulk смена статуса сделок
description: >
Массовая смена статуса. Bulk-UPDATE + bulk-INSERT в activity_log (2 запроса
вместо N). NO-OP (status уже совпадает) не считается.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/DealTransitionRequest'
example:
ids: [42, 43]
status: "in_progress"
responses:
'200':
description: Результат перехода
content:
application/json:
schema:
$ref: '#/components/schemas/BulkTransitionResponse'
example:
updated: 2
requested: 2
status: "in_progress"
'422':
$ref: '#/components/responses/ValidationError'
'401':
$ref: '#/components/responses/Unauthorized'
/api/deals/restore:
post:
operationId: deals.bulkRestore
tags: [deals]
summary: Bulk восстановление soft-deleted сделок
description: Восстановление из корзины. Идемпотентно (уже живые — NO-OP).
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/BulkIdsRequest'
example:
ids: [42, 43]
responses:
'200':
description: Результат восстановления
content:
application/json:
schema:
$ref: '#/components/schemas/BulkRestoreResponse'
example:
restored: 2
requested: 2
'422':
$ref: '#/components/responses/ValidationError'
'401':
$ref: '#/components/responses/Unauthorized'
components:
securitySchemes:
sanctumCookie:
type: apiKey
in: cookie
name: liderra_session
description: Laravel Sanctum session cookie (SPA auth)
parameters:
DealId:
name: id
in: path
required: true
description: ID сделки (только цифры, regex [0-9]+)
schema:
type: integer
minimum: 1
schemas:
DealSummary:
type: object
description: Краткое представление сделки (для списка)
properties:
id:
type: integer
tenant_id:
type: integer
project_id:
type: integer
project_name:
type: [string, "null"]
phone:
type: string
contact_name:
type: [string, "null"]
status:
type: string
description: Slug из таблицы lead_statuses
manager_id:
type: [integer, "null"]
manager_name:
type: [string, "null"]
manager_initials:
type: [string, "null"]
received_at:
type: [string, "null"]
format: date-time
DealDetail:
type: object
description: Полное представление сделки (для DealDetailDrawer)
properties:
id:
type: integer
tenant_id:
type: integer
project_id:
type: integer
project_name:
type: [string, "null"]
phone:
type: string
contact_name:
type: [string, "null"]
comment:
type: [string, "null"]
status:
type: string
manager_id:
type: [integer, "null"]
manager_name:
type: [string, "null"]
manager_initials:
type: [string, "null"]
received_at:
type: [string, "null"]
format: date-time
assigned_at:
type: [string, "null"]
format: date-time
ActivityEvent:
type: object
properties:
id:
type: integer
event:
type: string
description: >
Тип события: deal.created, deal.status_changed, deal.assigned,
deal.commented, deal.deleted, deal.restored
context:
type: object
description: Произвольный JSON-контекст (from/to/source и т.п.)
additionalProperties: true
created_at:
type: [string, "null"]
format: date-time
actor:
type: [object, "null"]
properties:
id:
type: integer
name:
type: string
initials:
type: string
DealsListResponse:
type: object
properties:
deals:
type: array
items:
$ref: '#/components/schemas/DealSummary'
total:
type: [integer, "null"]
description: Только в OFFSET-режиме (без cursor)
offset:
type: [integer, "null"]
description: Только в OFFSET-режиме
limit:
type: integer
next_cursor:
type: [string, "null"]
description: base64-encoded cursor для следующей страницы
DealsCountResponse:
type: object
description: Ответ при count_only=true
properties:
total:
type: integer
DealShowResponse:
type: object
properties:
deal:
$ref: '#/components/schemas/DealDetail'
events:
type: array
items:
$ref: '#/components/schemas/ActivityEvent'
DealStoreRequest:
type: object
required: [project_name, phone]
properties:
project_name:
type: string
maxLength: 255
phone:
type: string
maxLength: 20
contact_name:
type: [string, "null"]
maxLength: 255
status:
type: [string, "null"]
maxLength: 50
description: Slug из lead_statuses; по умолчанию "new"
manager_id:
type: [integer, "null"]
minimum: 1
comment:
type: [string, "null"]
maxLength: 5000
DealStoreResponse:
type: object
properties:
deal:
type: object
properties:
id:
type: integer
tenant_id:
type: integer
project_id:
type: integer
phone:
type: string
status:
type: string
contact_name:
type: [string, "null"]
manager_id:
type: [integer, "null"]
received_at:
type: string
format: date-time
message:
type: string
example: "Сделка создана."
DealUpdateRequest:
type: object
description: Все поля опциональны; хотя бы одно должно присутствовать
properties:
comment:
type: [string, "null"]
maxLength: 5000
manager_id:
type: [integer, "null"]
minimum: 1
status:
type: [string, "null"]
maxLength: 50
DealUpdateResponse:
type: object
properties:
deal:
type: object
properties:
id:
type: integer
tenant_id:
type: integer
project_id:
type: integer
phone:
type: string
contact_name:
type: [string, "null"]
comment:
type: [string, "null"]
status:
type: string
manager_id:
type: [integer, "null"]
received_at:
type: [string, "null"]
format: date-time
assigned_at:
type: [string, "null"]
format: date-time
DealExportRequest:
type: object
required: [ids]
properties:
ids:
type: array
items:
type: integer
minimum: 1
minItems: 1
maxItems: 10000
format:
type: string
enum: [csv, xlsx]
default: csv
DealTransitionRequest:
type: object
required: [ids, status]
properties:
ids:
type: array
items:
type: integer
minimum: 1
minItems: 1
maxItems: 1000
status:
type: string
maxLength: 50
description: Slug из lead_statuses
BulkIdsRequest:
type: object
required: [ids]
properties:
ids:
type: array
items:
type: integer
minimum: 1
minItems: 1
maxItems: 1000
BulkTransitionResponse:
type: object
properties:
updated:
type: integer
description: Реально изменённых (без NO-OP)
requested:
type: integer
status:
type: string
BulkDestroyResponse:
type: object
properties:
deleted:
type: integer
requested:
type: integer
BulkRestoreResponse:
type: object
properties:
restored:
type: integer
requested:
type: integer
ErrorMessage:
type: object
properties:
message:
type: string
ValidationErrorResponse:
type: object
properties:
message:
type: string
errors:
type: object
additionalProperties:
type: array
items:
type: string
responses:
Unauthorized:
description: Не аутентифицирован
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorMessage'
example:
message: "Unauthenticated."
NotFound:
description: Сделка не найдена
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorMessage'
example:
message: "Сделка не найдена."
ValidationError:
description: Ошибка валидации
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationErrorResponse'
example:
message: "The given data was invalid."
errors:
status:
- "Slug не найден в lead_statuses."