openapi: 3.0.3
info:
  title: BuildCore API
  version: 1.0.0
  description: |
    API строительного маркетплейса BuildCore.

    Платформа объединяет заказчиков, подрядчиков, поставщиков, исполнителей услуг
    и технадзор. Все ключевые операции (тендеры, заказы, эскроу, приёмка этапов,
    AI-подбор) доступны через REST-эндпоинты и server actions.

    Аутентификация — сессионная cookie (`buildcore-session`), для отдельных
    интеграций — webhook-секреты в заголовке `X-Signature`.
  contact:
    name: BuildCore Support
    email: hello@buildcore.io
    url: https://buildcore.app/help
  license:
    name: Proprietary
servers:
  - url: https://api.buildcore.app/v1
    description: Production
  - url: https://staging-api.buildcore.app/v1
    description: Staging
  - url: http://localhost:3000/api
    description: Local development

tags:
  - name: auth
    description: Аутентификация и сессии
  - name: users
    description: Пользователи и организации
  - name: catalog
    description: Категории, товары, услуги
  - name: projects
    description: Проекты заказчика
  - name: requests
    description: Заявки и тендеры
  - name: proposals
    description: Предложения подрядчиков
  - name: orders
    description: Заказы и этапы
  - name: payments
    description: Эскроу и платежи
  - name: stages
    description: Этапы работ и приёмка
  - name: disputes
    description: Споры и арбитраж
  - name: reviews
    description: Отзывы и рейтинги
  - name: files
    description: Загрузка файлов и фотоотчёт
  - name: chat
    description: Чаты и сообщения
  - name: ai
    description: AI-подбор и рекомендации
  - name: subscriptions
    description: Подписки PRO
  - name: bonuses
    description: Бонусная программа 3%
  - name: technadzor
    description: Технический надзор
  - name: documents
    description: Документы, PDF, CSV-выгрузки
  - name: webhooks
    description: Webhook-эндпоинты

paths:
  # ----------------------------------------------------------------------
  # AUTH
  # ----------------------------------------------------------------------
  /auth/demo:
    get:
      tags: [auth]
      summary: Demo-login для разработки и презентаций
      description: Авторизует пользователя под выбранной ролью без пароля, выставляет сессионную cookie и делает 302-редирект в соответствующий личный кабинет.
      parameters:
        - name: as
          in: query
          required: true
          schema:
            type: string
            enum: [customer, contractor, supplier, sp, technadzor, admin]
      responses:
        '302':
          description: Редирект в кабинет роли
          headers:
            Set-Cookie:
              schema:
                type: string
                example: buildcore-session=...; Path=/; HttpOnly; Secure; SameSite=Lax
            Location:
              schema:
                type: string
                example: /customer
        '400':
          $ref: '#/components/responses/BadRequest'

  /auth/sign-in:
    post:
      tags: [auth]
      summary: Логин по email и паролю
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, password]
              properties:
                email: { type: string, format: email }
                password: { type: string, minLength: 8 }
                totp: { type: string, description: "TOTP-код 2FA, если включён" }
      responses:
        '200':
          description: Успешная авторизация
          content:
            application/json:
              schema:
                type: object
                properties:
                  user: { $ref: '#/components/schemas/User' }
        '401':
          $ref: '#/components/responses/Unauthorized'

  /auth/sign-out:
    post:
      tags: [auth]
      summary: Выход (стирает сессию)
      responses:
        '204':
          description: Сессия завершена

  /auth/2fa/init:
    post:
      tags: [auth]
      summary: Инициализация 2FA — выдаёт TOTP secret и QR-код
      responses:
        '200':
          description: Секрет создан
          content:
            application/json:
              schema:
                type: object
                properties:
                  secret: { type: string }
                  qrDataUrl: { type: string, description: "data:image/png;base64,..." }

  /auth/2fa/confirm:
    post:
      tags: [auth]
      summary: Подтверждение 2FA кодом — активирует двухфакторку
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [code]
              properties:
                code: { type: string, minLength: 6, maxLength: 6 }
      responses:
        '200':
          description: 2FA включена
        '400':
          $ref: '#/components/responses/BadRequest'

  # ----------------------------------------------------------------------
  # USERS / ORGANIZATIONS
  # ----------------------------------------------------------------------
  /me:
    get:
      tags: [users]
      summary: Текущий пользователь
      security:
        - sessionCookie: []
      responses:
        '200':
          description: Профиль
          content:
            application/json:
              schema: { $ref: '#/components/schemas/User' }
        '401':
          $ref: '#/components/responses/Unauthorized'

  /organizations/{id}:
    get:
      tags: [users]
      summary: Карточка организации
      parameters:
        - $ref: '#/components/parameters/IdPath'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Organization' }
        '404':
          $ref: '#/components/responses/NotFound'

  /organizations/{id}/verify:
    post:
      tags: [users]
      summary: Отправить организацию на верификацию модератором
      parameters:
        - $ref: '#/components/parameters/IdPath'
      responses:
        '202':
          description: Заявка принята, статус PENDING_REVIEW

  # ----------------------------------------------------------------------
  # CATALOG
  # ----------------------------------------------------------------------
  /catalog/categories:
    get:
      tags: [catalog]
      summary: Дерево категорий
      parameters:
        - name: kind
          in: query
          schema: { type: string, enum: [PRODUCT, SERVICE] }
      responses:
        '200':
          description: Список категорий
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/Category' }

  /catalog/products:
    get:
      tags: [catalog]
      summary: Поиск товаров
      parameters:
        - name: q
          in: query
          schema: { type: string }
        - name: categoryId
          in: query
          schema: { type: string }
        - name: supplierId
          in: query
          schema: { type: string }
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PageSize'
      responses:
        '200':
          description: Постраничный список товаров
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/PageMeta'
                  - type: object
                    properties:
                      items:
                        type: array
                        items: { $ref: '#/components/schemas/Product' }

  /catalog/products/{id}:
    get:
      tags: [catalog]
      summary: Карточка товара
      parameters:
        - $ref: '#/components/parameters/IdPath'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Product' }
        '404':
          $ref: '#/components/responses/NotFound'

  /catalog/products/import:
    post:
      tags: [catalog]
      summary: Импорт прайса поставщика (CSV/XLSX)
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file: { type: string, format: binary }
                mode: { type: string, enum: [replace, append, merge] }
      responses:
        '202':
          description: Импорт принят в обработку
          content:
            application/json:
              schema:
                type: object
                properties:
                  jobId: { type: string }
                  estimatedRows: { type: integer }

  # ----------------------------------------------------------------------
  # CONTRACTORS / SUPPLIERS / SERVICE PROVIDERS
  # ----------------------------------------------------------------------
  /contractors:
    get:
      tags: [users]
      summary: Каталог подрядчиков
      parameters:
        - name: region
          in: query
          schema: { type: string }
        - name: specialization
          in: query
          schema: { type: string }
        - name: minRating
          in: query
          schema: { type: number, minimum: 0, maximum: 5 }
      responses:
        '200':
          description: Список подрядчиков
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/Contractor' }

  /contractors/{id}:
    get:
      tags: [users]
      summary: Карточка подрядчика
      parameters:
        - $ref: '#/components/parameters/IdPath'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Contractor' }

  /suppliers:
    get:
      tags: [users]
      summary: Каталог поставщиков
      responses:
        '200':
          description: Список
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/Supplier' }

  /service-providers:
    get:
      tags: [users]
      summary: Каталог исполнителей услуг (электрики, сантехники, дизайнеры)
      parameters:
        - name: category
          in: query
          schema: { type: string }
        - name: city
          in: query
          schema: { type: string }
        - name: radiusKm
          in: query
          schema: { type: integer }
      responses:
        '200':
          description: Список
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/ServiceProvider' }

  /service-providers/register:
    post:
      tags: [users]
      summary: Регистрация исполнителя услуг
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                orgName: { type: string }
                legalType: { type: string, enum: [SELF_EMPLOYED, IP, OOO] }
                categories: { type: array, items: { type: string } }
                regions: { type: array, items: { type: string } }
                hourlyRate: { type: integer }
                callOutFee: { type: integer }
      responses:
        '201':
          description: Профиль создан, отправлен на модерацию

  # ----------------------------------------------------------------------
  # PROJECTS
  # ----------------------------------------------------------------------
  /projects:
    get:
      tags: [projects]
      summary: Проекты текущего пользователя
      security:
        - sessionCookie: []
      responses:
        '200':
          description: Список проектов
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/Project' }
    post:
      tags: [projects]
      summary: Создать проект
      security:
        - sessionCookie: []
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ProjectInput' }
      responses:
        '201':
          description: Проект создан
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Project' }

  /projects/{id}:
    get:
      tags: [projects]
      summary: Карточка проекта
      parameters:
        - $ref: '#/components/parameters/IdPath'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Project' }
    patch:
      tags: [projects]
      summary: Обновить проект
      parameters:
        - $ref: '#/components/parameters/IdPath'
      requestBody:
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ProjectInput' }
      responses:
        '200':
          description: Обновлён

  /projects/{id}/budgets:
    get:
      tags: [projects]
      summary: Сметы проекта (версии)
      parameters:
        - $ref: '#/components/parameters/IdPath'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/Budget' }
    post:
      tags: [projects]
      summary: Добавить смету (или новую версию)
      parameters:
        - $ref: '#/components/parameters/IdPath'
      requestBody:
        content:
          application/json:
            schema: { $ref: '#/components/schemas/BudgetInput' }
      responses:
        '201':
          description: Смета создана

  /projects/{id}/budgets/ai-generate:
    post:
      tags: [ai, projects]
      summary: AI-генерация сметы по описанию задачи (нормы ГЭСН-2024)
      parameters:
        - $ref: '#/components/parameters/IdPath'
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                brief: { type: string }
                area: { type: number }
                region: { type: string }
      responses:
        '200':
          description: Черновик сметы
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Budget' }

  # ----------------------------------------------------------------------
  # REQUESTS / TENDERS / PROPOSALS
  # ----------------------------------------------------------------------
  /requests:
    get:
      tags: [requests]
      summary: Список заявок (тендеров)
      parameters:
        - name: status
          in: query
          schema: { type: string }
        - name: type
          in: query
          schema: { type: string, enum: [MATERIALS, WORK, TURNKEY] }
        - name: region
          in: query
          schema: { type: string }
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/Request' }
    post:
      tags: [requests]
      summary: Создать заявку
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/RequestInput' }
      responses:
        '201':
          description: Заявка создана

  /requests/{id}:
    get:
      tags: [requests]
      summary: Карточка заявки
      parameters:
        - $ref: '#/components/parameters/IdPath'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Request' }

  /requests/{id}/publish:
    post:
      tags: [requests]
      summary: Опубликовать заявку (статус → RECEIVING_PROPOSALS)
      parameters:
        - $ref: '#/components/parameters/IdPath'
      responses:
        '200':
          description: Опубликована

  /requests/{id}/proposals:
    get:
      tags: [proposals]
      summary: Предложения по заявке
      parameters:
        - $ref: '#/components/parameters/IdPath'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/Proposal' }
    post:
      tags: [proposals]
      summary: Отправить предложение
      parameters:
        - $ref: '#/components/parameters/IdPath'
      requestBody:
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ProposalInput' }
      responses:
        '201':
          description: Предложение отправлено

  /proposals/{id}/accept:
    post:
      tags: [proposals]
      summary: Принять предложение (создаёт Order)
      parameters:
        - $ref: '#/components/parameters/IdPath'
      responses:
        '201':
          description: Создан заказ
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Order' }

  /proposals/{id}/reject:
    post:
      tags: [proposals]
      summary: Отклонить предложение
      parameters:
        - $ref: '#/components/parameters/IdPath'
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                reason: { type: string }
      responses:
        '200':
          description: Отклонено

  # ----------------------------------------------------------------------
  # ORDERS / STAGES
  # ----------------------------------------------------------------------
  /orders:
    get:
      tags: [orders]
      summary: Список заказов
      parameters:
        - name: status
          in: query
          schema: { type: string }
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/Order' }

  /orders/{id}:
    get:
      tags: [orders]
      summary: Карточка заказа
      parameters:
        - $ref: '#/components/parameters/IdPath'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Order' }

  /orders/{id}/bom.csv:
    get:
      tags: [orders, documents]
      summary: Экспорт спецификации (BOM) в CSV
      parameters:
        - $ref: '#/components/parameters/IdPath'
      responses:
        '200':
          description: CSV-файл
          content:
            text/csv:
              schema: { type: string }

  /orders/{id}/stages:
    get:
      tags: [stages]
      summary: Этапы заказа
      parameters:
        - $ref: '#/components/parameters/IdPath'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/OrderStage' }

  /stages/{id}/start:
    post:
      tags: [stages]
      summary: Начать этап (статус → IN_PROGRESS)
      parameters:
        - $ref: '#/components/parameters/IdPath'
      responses:
        '200':
          description: Этап стартован

  /stages/{id}/submit:
    post:
      tags: [stages]
      summary: Сдать этап на приёмку (статус → SUBMITTED_FOR_REVIEW)
      parameters:
        - $ref: '#/components/parameters/IdPath'
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                comment: { type: string }
                photoIds: { type: array, items: { type: string } }
      responses:
        '200':
          description: Этап на приёмке

  /stages/{id}/accept:
    post:
      tags: [stages, payments]
      summary: Принять этап и выпустить эскроу-выплату
      parameters:
        - $ref: '#/components/parameters/IdPath'
      responses:
        '200':
          description: Этап принят, платёж RELEASED
          content:
            application/json:
              schema:
                type: object
                properties:
                  stage: { $ref: '#/components/schemas/OrderStage' }
                  payment: { $ref: '#/components/schemas/Payment' }

  /stages/{id}/reject:
    post:
      tags: [stages]
      summary: Вернуть этап на доработку
      parameters:
        - $ref: '#/components/parameters/IdPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [reason]
              properties:
                reason: { type: string }
      responses:
        '200':
          description: Этап возвращён на доработку

  /stages/{id}/inspect:
    post:
      tags: [stages, technadzor]
      summary: Аудит этапа технадзором
      parameters:
        - $ref: '#/components/parameters/IdPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [verdict]
              properties:
                verdict: { type: string, enum: [APPROVED, REJECTED, NEEDS_REVISION] }
                notes: { type: string }
                photoIds: { type: array, items: { type: string } }
      responses:
        '200':
          description: Инспекция записана

  # ----------------------------------------------------------------------
  # PAYMENTS / ESCROW
  # ----------------------------------------------------------------------
  /payments:
    get:
      tags: [payments]
      summary: История платежей пользователя
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/Payment' }

  /payments/{id}:
    get:
      tags: [payments]
      summary: Карточка платежа
      parameters:
        - $ref: '#/components/parameters/IdPath'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Payment' }

  /payments/escrow/fund:
    post:
      tags: [payments]
      summary: Завести деньги на эскроу под заказ
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [orderId, amount]
              properties:
                orderId: { type: string }
                amount: { type: number }
                provider: { type: string, enum: [stripe, tinkoff, sberpay, paypal] }
      responses:
        '200':
          description: Создана платёжная сессия
          content:
            application/json:
              schema:
                type: object
                properties:
                  paymentUrl: { type: string }
                  sessionId: { type: string }

  /payments/escrow/release:
    post:
      tags: [payments]
      summary: Релиз эскроу-средств подрядчику (после приёмки)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [stageId]
              properties:
                stageId: { type: string }
      responses:
        '200':
          description: Средства выпущены

  /payments/escrow/refund:
    post:
      tags: [payments]
      summary: Возврат эскроу заказчику (по решению спора)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                paymentId: { type: string }
                amount: { type: number }
                reason: { type: string }
      responses:
        '200':
          description: Возврат инициирован

  # ----------------------------------------------------------------------
  # DISPUTES
  # ----------------------------------------------------------------------
  /disputes:
    get:
      tags: [disputes]
      summary: Список споров
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/Dispute' }
    post:
      tags: [disputes]
      summary: Открыть спор
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [orderId, reason]
              properties:
                orderId: { type: string }
                reason: { type: string }
                description: { type: string }
                amount: { type: number }
      responses:
        '201':
          description: Спор открыт

  /disputes/{id}/resolve:
    post:
      tags: [disputes]
      summary: Резолюция спора модератором
      parameters:
        - $ref: '#/components/parameters/IdPath'
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                resolution: { type: string }
                refundAmount: { type: number }
                releaseAmount: { type: number }
      responses:
        '200':
          description: Спор закрыт

  # ----------------------------------------------------------------------
  # REVIEWS
  # ----------------------------------------------------------------------
  /reviews:
    post:
      tags: [reviews]
      summary: Оставить отзыв (7-факторный рейтинг)
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ReviewInput' }
      responses:
        '201':
          description: Отзыв создан

  /contractors/{id}/reviews:
    get:
      tags: [reviews]
      summary: Отзывы о подрядчике
      parameters:
        - $ref: '#/components/parameters/IdPath'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/Review' }

  # ----------------------------------------------------------------------
  # FILES / PHOTOS
  # ----------------------------------------------------------------------
  /upload:
    post:
      tags: [files]
      summary: Загрузка файла (документ / фото / портфолио)
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file, kind]
              properties:
                file:
                  type: string
                  format: binary
                kind:
                  type: string
                  enum: [doc, photo, portfolio, certificate, avatar]
                projectId: { type: string }
                stageId: { type: string }
      responses:
        '200':
          description: Файл загружен; для фото — EXIF + анти-подмена
          content:
            application/json:
              schema:
                type: object
                properties:
                  id: { type: string }
                  url: { type: string }
                  hash: { type: string, description: "sha256 хеш контента" }
                  size: { type: integer }
                  mime: { type: string }
                  exif:
                    type: object
                    properties:
                      takenAt: { type: string, format: date-time }
                      lat: { type: number }
                      lng: { type: number }
                      tampered: { type: boolean }
                      tamperReasons:
                        type: array
                        items: { type: string }
        '413':
          description: Файл слишком большой
        '415':
          description: Неподдерживаемый тип файла

  # ----------------------------------------------------------------------
  # CHAT (SSE)
  # ----------------------------------------------------------------------
  /sse/chat/{chatId}:
    get:
      tags: [chat]
      summary: SSE-стрим сообщений чата
      description: |
        Server-Sent Events поток новых сообщений. Соединение держится открытым,
        каждое новое сообщение отправляется как событие `message` с JSON-payload.
      parameters:
        - name: chatId
          in: path
          required: true
          schema: { type: string }
      responses:
        '200':
          description: text/event-stream
          content:
            text/event-stream:
              schema:
                type: string
                example: |
                  event: message
                  data: {"id":"msg_...","text":"...","senderId":"..."}
        '404':
          $ref: '#/components/responses/NotFound'

  /chats:
    get:
      tags: [chat]
      summary: Список чатов пользователя
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/Chat' }

  /chats/{id}/messages:
    get:
      tags: [chat]
      summary: История сообщений
      parameters:
        - $ref: '#/components/parameters/IdPath'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/Message' }
    post:
      tags: [chat]
      summary: Отправить сообщение
      parameters:
        - $ref: '#/components/parameters/IdPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                text: { type: string }
                attachments: { type: array, items: { type: string } }
      responses:
        '201':
          description: Отправлено

  # ----------------------------------------------------------------------
  # AI RECOMMENDATIONS
  # ----------------------------------------------------------------------
  /ai/match-contractors:
    post:
      tags: [ai]
      summary: AI-подбор подрядчиков под заявку
      description: |
        Возвращает топ-10 подрядчиков с факторами объяснимости.
        Формула: цена 30% / рейтинг 20% / сроки 20% / документы 15% / опыт 10% / споры 5%.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [requestId]
              properties:
                requestId: { type: string }
                limit: { type: integer, default: 10 }
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/Recommendation' }

  /ai/score-proposal:
    post:
      tags: [ai]
      summary: Скоринг конкретного предложения
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                proposalId: { type: string }
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  score: { type: number }
                  factors: { type: object }

  # ----------------------------------------------------------------------
  # SUBSCRIPTIONS
  # ----------------------------------------------------------------------
  /subscriptions/plans:
    get:
      tags: [subscriptions]
      summary: Доступные тарифы
      parameters:
        - name: forRole
          in: query
          schema: { type: string, enum: [CONTRACTOR, SERVICE_PROVIDER, SUPPLIER] }
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/SubscriptionPlan' }

  /subscriptions/checkout:
    post:
      tags: [subscriptions]
      summary: Оформить подписку PRO
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [planCode]
              properties:
                planCode: { type: string, enum: [starter, pro, premium] }
                autoRenew: { type: boolean, default: true }
      responses:
        '200':
          description: Создана платёжная сессия
          content:
            application/json:
              schema:
                type: object
                properties:
                  paymentUrl: { type: string }

  /subscriptions/me:
    get:
      tags: [subscriptions]
      summary: Текущая подписка организации
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Subscription' }

  /subscriptions/cancel:
    post:
      tags: [subscriptions]
      summary: Отменить автопродление
      responses:
        '200':
          description: Отменено

  # ----------------------------------------------------------------------
  # BONUSES
  # ----------------------------------------------------------------------
  /bonuses:
    get:
      tags: [bonuses]
      summary: Транзакции бонусной программы (3% поставщик → подрядчик)
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/BonusTransaction' }

  /bonuses/withdraw:
    post:
      tags: [bonuses]
      summary: Запрос на выплату накопленных бонусов
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                amount: { type: number }
      responses:
        '200':
          description: Запрос принят

  # ----------------------------------------------------------------------
  # TECHNADZOR
  # ----------------------------------------------------------------------
  /technadzor/assignments:
    get:
      tags: [technadzor]
      summary: Назначения технадзора (на текущего инспектора)
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/TechnadzorAssignment' }

  /technadzor/assign:
    post:
      tags: [technadzor]
      summary: Назначить технадзор на проект
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [projectId, technadzorUserId]
              properties:
                projectId: { type: string }
                technadzorUserId: { type: string }
                scope: { type: string, enum: [ALL, KEY_STAGES, FINAL] }
                feeAmount: { type: number }
      responses:
        '201':
          description: Создано

  # ----------------------------------------------------------------------
  # DOCUMENTS / PDF
  # ----------------------------------------------------------------------
  /pdf/act-ks2/{stageId}:
    get:
      tags: [documents]
      summary: Сгенерировать PDF КС-2 (акт о приёмке выполненных работ)
      parameters:
        - name: stageId
          in: path
          required: true
          schema: { type: string }
      responses:
        '200':
          description: PDF файл
          content:
            application/pdf:
              schema: { type: string, format: binary }

  /pdf/act-ks3/{orderId}:
    get:
      tags: [documents]
      summary: Сгенерировать PDF КС-3 (справка о стоимости работ)
      parameters:
        - name: orderId
          in: path
          required: true
          schema: { type: string }
      responses:
        '200':
          description: PDF файл
          content:
            application/pdf:
              schema: { type: string, format: binary }

  /pdf/contract/{orderId}:
    get:
      tags: [documents]
      summary: Сгенерировать договор подряда
      parameters:
        - name: orderId
          in: path
          required: true
          schema: { type: string }
      responses:
        '200':
          description: PDF файл
          content:
            application/pdf:
              schema: { type: string, format: binary }

  /pdf/invoice/{paymentId}:
    get:
      tags: [documents]
      summary: Счёт-фактура (PDF)
      parameters:
        - name: paymentId
          in: path
          required: true
          schema: { type: string }
      responses:
        '200':
          description: PDF файл
          content:
            application/pdf:
              schema: { type: string, format: binary }

  /documents/sign:
    post:
      tags: [documents]
      summary: Электронная подпись документа (КЭП / Госключ)
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                documentId: { type: string }
                signatureProvider: { type: string, enum: [kep, ukep, goskey] }
      responses:
        '200':
          description: Документ подписан

  # ----------------------------------------------------------------------
  # SEARCH
  # ----------------------------------------------------------------------
  /search:
    get:
      tags: [catalog]
      summary: Полнотекстовый поиск по платформе
      parameters:
        - name: q
          in: query
          required: true
          schema: { type: string }
        - name: type
          in: query
          schema: { type: string, enum: [product, contractor, supplier, sp, project] }
      responses:
        '200':
          description: HTML страница с результатами либо JSON
          content:
            text/html:
              schema: { type: string }
            application/json:
              schema:
                type: object
                properties:
                  total: { type: integer }
                  items: { type: array, items: { type: object } }

  # ----------------------------------------------------------------------
  # WEBHOOKS
  # ----------------------------------------------------------------------
  /webhooks/payment:
    post:
      tags: [webhooks]
      summary: Webhook платёжного провайдера
      description: |
        Подписан секретом провайдера, заголовок `X-Signature`.
        Поддерживается Stripe, Tinkoff, Sberpay.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                event: { type: string }
                paymentId: { type: string }
                status: { type: string }
                provider: { type: string }
                amount: { type: number }
                signature: { type: string }
      responses:
        '200':
          description: Принят
        '400':
          description: Невалидная подпись

  /webhooks/exchange-rate:
    post:
      tags: [webhooks]
      summary: Обновление курсов валют от ЦБ
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                date: { type: string, format: date }
                rates: { type: object, additionalProperties: { type: number } }
      responses:
        '200':
          description: Принят

  # ----------------------------------------------------------------------
  # ADMIN
  # ----------------------------------------------------------------------
  /admin/moderation/queue:
    get:
      tags: [users]
      summary: Очередь модерации (организации, отзывы, споры)
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  organizations: { type: array, items: { $ref: '#/components/schemas/Organization' } }
                  reviews: { type: array, items: { $ref: '#/components/schemas/Review' } }
                  disputes: { type: array, items: { $ref: '#/components/schemas/Dispute' } }

  /admin/audit-log:
    get:
      tags: [users]
      summary: Журнал аудита
      parameters:
        - name: userId
          in: query
          schema: { type: string }
        - name: action
          in: query
          schema: { type: string }
        - $ref: '#/components/parameters/Page'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/AuditLog' }

components:
  securitySchemes:
    sessionCookie:
      type: apiKey
      in: cookie
      name: buildcore-session
    webhookSignature:
      type: apiKey
      in: header
      name: X-Signature

  parameters:
    IdPath:
      name: id
      in: path
      required: true
      schema: { type: string }
    Page:
      name: page
      in: query
      schema: { type: integer, default: 1, minimum: 1 }
    PageSize:
      name: pageSize
      in: query
      schema: { type: integer, default: 24, minimum: 1, maximum: 200 }

  responses:
    BadRequest:
      description: Невалидный запрос
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    Unauthorized:
      description: Не авторизован
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    Forbidden:
      description: Нет прав
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    NotFound:
      description: Не найдено
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }

  schemas:
    Error:
      type: object
      properties:
        code: { type: string }
        message: { type: string }
        details: { type: object }

    PageMeta:
      type: object
      properties:
        total: { type: integer }
        page: { type: integer }
        pageSize: { type: integer }

    User:
      type: object
      properties:
        id: { type: string }
        email: { type: string, format: email }
        name: { type: string }
        phone: { type: string }
        avatarUrl: { type: string }
        role:
          type: string
          enum: [GUEST, CUSTOMER, CONTRACTOR, SUPPLIER, SERVICE_PROVIDER, WORKER, TECHNADZOR, MODERATOR, ADMIN, FINANCE, SUPER_ADMIN]
        organizationId: { type: string }
        twoFactorEnabled: { type: boolean }
        createdAt: { type: string, format: date-time }

    Organization:
      type: object
      properties:
        id: { type: string }
        name: { type: string }
        type: { type: string, enum: [CUSTOMER, SUPPLIER, CONTRACTOR, SERVICE_PROVIDER] }
        inn: { type: string }
        ogrn: { type: string }
        city: { type: string }
        region: { type: string }
        verified: { type: boolean }
        status: { type: string }
        rating: { type: number }
        completedJobs: { type: integer }
        subscriptionPlan: { type: string }

    Contractor:
      type: object
      properties:
        id: { type: string }
        organizationId: { type: string }
        organization: { $ref: '#/components/schemas/Organization' }
        legalType: { type: string }
        specializations: { type: array, items: { type: string } }
        regions: { type: array, items: { type: string } }
        responseTimeMin: { type: integer }
        workersCount: { type: integer }
        brigadesCount: { type: integer }
        bonusPoints: { type: number }

    ServiceProvider:
      type: object
      properties:
        id: { type: string }
        organizationId: { type: string }
        categories: { type: array, items: { type: string } }
        regions: { type: array, items: { type: string } }
        travelRadiusKm: { type: integer }
        hourlyRate: { type: integer }
        callOutFee: { type: integer }
        warrantyMonths: { type: integer }
        responseTimeMin: { type: integer }

    Supplier:
      type: object
      properties:
        id: { type: string }
        organizationId: { type: string }
        categories: { type: array, items: { type: string } }
        regions: { type: array, items: { type: string } }
        totalSku: { type: integer }
        totalOrders: { type: integer }
        conversionRate: { type: number }

    Category:
      type: object
      properties:
        id: { type: string }
        slug: { type: string }
        parentId: { type: string }
        name: { type: string }
        kind: { type: string, enum: [PRODUCT, SERVICE] }
        unit: { type: string }
        commission: { type: number }

    Product:
      type: object
      properties:
        id: { type: string }
        sku: { type: string }
        name: { type: string }
        brand: { type: string }
        manufacturer: { type: string }
        unit: { type: string }
        priceRetail: { type: number }
        priceWholesale: { type: number }
        stock: { type: number }
        minOrder: { type: number }
        status: { type: string }
        supplierId: { type: string }
        categoryId: { type: string }

    Project:
      type: object
      properties:
        id: { type: string }
        ownerId: { type: string }
        name: { type: string }
        type: { type: string, enum: [RESIDENTIAL, COMMERCIAL, INDUSTRIAL, INFRASTRUCTURE] }
        status: { type: string }
        city: { type: string }
        area: { type: number }
        budget: { type: number }
        budgetSpent: { type: number }
        budgetEscrow: { type: number }
        progress: { type: integer }
        startDate: { type: string, format: date-time }
        endDate: { type: string, format: date-time }

    ProjectInput:
      type: object
      required: [name, type]
      properties:
        name: { type: string }
        type: { type: string }
        description: { type: string }
        city: { type: string }
        region: { type: string }
        area: { type: number }
        budget: { type: number }
        startDate: { type: string, format: date-time }
        endDate: { type: string, format: date-time }

    Budget:
      type: object
      properties:
        id: { type: string }
        version: { type: integer }
        title: { type: string }
        status: { type: string }
        totalAmount: { type: number }
        items:
          type: array
          items:
            type: object
            properties:
              name: { type: string }
              unit: { type: string }
              qty: { type: number }
              price: { type: number }
              total: { type: number }

    BudgetInput:
      type: object
      properties:
        title: { type: string }
        items: { type: array }
        notes: { type: string }

    Request:
      type: object
      properties:
        id: { type: string }
        projectId: { type: string }
        title: { type: string }
        type: { type: string, enum: [MATERIALS, WORK, TURNKEY] }
        scope: { type: string, enum: [OPEN, CLOSED] }
        status: { type: string }
        budget: { type: number }
        city: { type: string }
        region: { type: string }
        responsesCount: { type: integer }
        responseDeadline: { type: string, format: date-time }
        startDate: { type: string, format: date-time }
        endDate: { type: string, format: date-time }

    RequestInput:
      type: object
      required: [title, type]
      properties:
        projectId: { type: string }
        title: { type: string }
        description: { type: string }
        type: { type: string }
        scope: { type: string }
        budget: { type: number }
        region: { type: string }
        city: { type: string }
        responseDeadline: { type: string, format: date-time }

    Proposal:
      type: object
      properties:
        id: { type: string }
        requestId: { type: string }
        authorId: { type: string }
        contractorId: { type: string }
        serviceProviderId: { type: string }
        price: { type: number }
        durationDays: { type: integer }
        warrantyMonths: { type: integer }
        paymentTerms: { type: string }
        message: { type: string }
        score: { type: number }
        status: { type: string }

    ProposalInput:
      type: object
      required: [price, durationDays]
      properties:
        price: { type: number }
        durationDays: { type: integer }
        warrantyMonths: { type: integer }
        paymentTerms: { type: string }
        message: { type: string }
        attachments: { type: array, items: { type: string } }

    Order:
      type: object
      properties:
        id: { type: string }
        number: { type: string }
        projectId: { type: string }
        contractorId: { type: string }
        serviceProviderId: { type: string }
        type: { type: string, enum: [GOODS, SERVICE, COMPLEX] }
        totalAmount: { type: number }
        status: { type: string }
        paymentScheme: { type: string }
        items: { type: array, items: { $ref: '#/components/schemas/OrderItem' } }
        stages: { type: array, items: { $ref: '#/components/schemas/OrderStage' } }

    OrderItem:
      type: object
      properties:
        id: { type: string }
        productId: { type: string }
        supplierId: { type: string }
        name: { type: string }
        qty: { type: number }
        unit: { type: string }
        price: { type: number }
        total: { type: number }

    OrderStage:
      type: object
      properties:
        id: { type: string }
        orderId: { type: string }
        num: { type: integer }
        title: { type: string }
        description: { type: string }
        amount: { type: number }
        status: { type: string }
        startDate: { type: string, format: date-time }
        endDate: { type: string, format: date-time }
        acceptedAt: { type: string, format: date-time }
        rejectedReason: { type: string }

    Payment:
      type: object
      properties:
        id: { type: string }
        orderId: { type: string }
        stageId: { type: string }
        userId: { type: string }
        type: { type: string, enum: [IN, OUT, REFUND, FEE, BONUS] }
        amount: { type: number }
        status: { type: string, enum: [PENDING, IN_ESCROW, RELEASED, REFUNDED, FROZEN, CANCELLED] }
        escrow: { type: boolean }
        createdAt: { type: string, format: date-time }
        releasedAt: { type: string, format: date-time }

    Dispute:
      type: object
      properties:
        id: { type: string }
        orderId: { type: string }
        openedById: { type: string }
        reason: { type: string }
        description: { type: string }
        amount: { type: number }
        status: { type: string }
        resolution: { type: string }
        createdAt: { type: string, format: date-time }
        resolvedAt: { type: string, format: date-time }

    Review:
      type: object
      properties:
        id: { type: string }
        authorId: { type: string }
        targetUserId: { type: string }
        contractorId: { type: string }
        orderId: { type: string }
        rating: { type: number }
        qualityScore: { type: integer }
        timelinesScore: { type: integer }
        communicationScore: { type: integer }
        matchToScopeScore: { type: integer }
        responseSpeedScore: { type: integer }
        documentsScore: { type: integer }
        repeatBusinessScore: { type: integer }
        text: { type: string }

    ReviewInput:
      type: object
      required: [rating, text]
      properties:
        orderId: { type: string }
        targetUserId: { type: string }
        contractorId: { type: string }
        rating: { type: number, minimum: 1, maximum: 5 }
        qualityScore: { type: integer }
        timelinesScore: { type: integer }
        communicationScore: { type: integer }
        matchToScopeScore: { type: integer }
        responseSpeedScore: { type: integer }
        documentsScore: { type: integer }
        repeatBusinessScore: { type: integer }
        text: { type: string }

    Chat:
      type: object
      properties:
        id: { type: string }
        title: { type: string }
        kind: { type: string }
        projectId: { type: string }
        orderId: { type: string }
        lastMessageAt: { type: string, format: date-time }

    Message:
      type: object
      properties:
        id: { type: string }
        chatId: { type: string }
        senderId: { type: string }
        text: { type: string }
        attachments: { type: array, items: { type: string } }
        createdAt: { type: string, format: date-time }

    Recommendation:
      type: object
      properties:
        id: { type: string }
        requestId: { type: string }
        targetUserId: { type: string }
        targetContractorId: { type: string }
        targetSpId: { type: string }
        score: { type: number }
        factors:
          type: object
          properties:
            category: { type: number }
            region: { type: number }
            rating: { type: number }
            docs: { type: number }
            exp: { type: number }
            disputes: { type: number }
            price: { type: number }
            speed: { type: number }
        reason: { type: string }
        status: { type: string }

    SubscriptionPlan:
      type: object
      properties:
        id: { type: string }
        code: { type: string }
        name: { type: string }
        forRole: { type: string }
        priceCents: { type: integer }
        currency: { type: string }
        monthlyResponseLimit: { type: integer, nullable: true }
        visibilityBoost: { type: integer }
        analyticsLevel: { type: string }
        features: { type: array, items: { type: string } }

    Subscription:
      type: object
      properties:
        id: { type: string }
        organizationId: { type: string }
        planId: { type: string }
        status: { type: string }
        startedAt: { type: string, format: date-time }
        expiresAt: { type: string, format: date-time }
        autoRenew: { type: boolean }
        responsesUsed: { type: integer }

    BonusTransaction:
      type: object
      properties:
        id: { type: string }
        fromSupplierOrgId: { type: string }
        toContractorOrgId: { type: string }
        sourceOrderId: { type: string }
        baseAmount: { type: number }
        percent: { type: number }
        amount: { type: number }
        status: { type: string }
        createdAt: { type: string, format: date-time }
        paidAt: { type: string, format: date-time }

    TechnadzorAssignment:
      type: object
      properties:
        id: { type: string }
        projectId: { type: string }
        technadzorUserId: { type: string }
        scope: { type: string }
        feeAmount: { type: number }
        status: { type: string }
        createdAt: { type: string, format: date-time }

    AuditLog:
      type: object
      properties:
        id: { type: string }
        userId: { type: string }
        organizationId: { type: string }
        action: { type: string }
        entityType: { type: string }
        entityId: { type: string }
        details: { type: object }
        ip: { type: string }
        userAgent: { type: string }
        createdAt: { type: string, format: date-time }
