Files
portal/docs/api/openapi.yaml
T
Дмитрий ff3979d527 @
docs(a3): OpenAPI skeleton for /api/deals — A3 smoke artifact

Стартовый OpenAPI 3.1 скелет для группы /api/deals* (8 эндпоинтов)
как smoke-доказательство api-docs-тулинга. Redocly lint — valid (exit 0,
2 warning о неполноте, ожидаемо для скелета). Не полная спека API.

Task 1 плана A3 integration-tooling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@
2026-05-17 15:19:19 +03:00

700 lines
20 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Стартовый 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[]
in: query
description: Фильтр по статусам (можно несколько)
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."