{
  "openapi": "3.0.3",
  "info": {
    "title": "Ротор.Склад API",
    "version": "1.0.0",
    "description": "REST API учёта склада автозапчастей «Ротор». Остаток считается по движениям: приход плюс, списание минус, резерв уменьшает доступный остаток, снятие резерва возвращает. Доступ — по API-ключу в заголовке X-Api-Key; административные операции — по заголовку X-Admin-Key."
  },
  "servers": [
    { "url": "/" }
  ],
  "tags": [
    { "name": "auth", "description": "Проверка API-ключей" },
    { "name": "products", "description": "Товары" },
    { "name": "suppliers", "description": "Поставщики" },
    { "name": "movements", "description": "Движения остатков" },
    { "name": "reports", "description": "Отчёты остатков" },
    { "name": "admin", "description": "Управление API-ключами (только X-Admin-Key)" }
  ],
  "paths": {
    "/api/auth/check": {
      "post": {
        "tags": ["auth"],
        "summary": "Проверить API-ключ из заголовка",
        "parameters": [
          { "name": "X-Api-Key", "in": "header", "required": true, "schema": { "type": "string" }, "description": "Ключ доступа системы" }
        ],
        "responses": {
          "200": { "description": "Ключ действителен", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AuthCheck" } } } },
          "401": { "description": "Неверный или отсутствующий ключ", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/products": {
      "get": {
        "tags": ["products"],
        "summary": "Список и поиск товаров по артикулу и названию",
        "parameters": [
          { "name": "X-Api-Key", "in": "header", "required": true, "schema": { "type": "string" } },
          { "name": "search", "in": "query", "schema": { "type": "string" }, "description": "Подстрока артикула или названия" },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 50 } },
          { "name": "offset", "in": "query", "schema": { "type": "integer", "default": 0 } }
        ],
        "responses": {
          "200": { "description": "Список товаров", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProductList" } } } }
        }
      },
      "post": {
        "tags": ["products"],
        "summary": "Создать товар",
        "parameters": [
          { "name": "X-Api-Key", "in": "header", "required": true, "schema": { "type": "string" } }
        ],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProductCreate" } } } },
        "responses": {
          "201": { "description": "Товар создан", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Product" } } } },
          "409": { "description": "Артикул уже существует", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/products/{id}": {
      "get": {
        "tags": ["products"],
        "summary": "Карточка товара с вычисленным остатком",
        "parameters": [
          { "name": "X-Api-Key", "in": "header", "required": true, "schema": { "type": "string" } },
          { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }
        ],
        "responses": {
          "200": { "description": "Карточка товара", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProductCard" } } } },
          "404": { "description": "Товар не найден", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      },
      "patch": {
        "tags": ["products"],
        "summary": "Изменить товар",
        "parameters": [
          { "name": "X-Api-Key", "in": "header", "required": true, "schema": { "type": "string" } },
          { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }
        ],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProductPatch" } } } },
        "responses": {
          "200": { "description": "Товар обновлён", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Product" } } } }
        }
      }
    },
    "/api/products/{id}/movements": {
      "get": {
        "tags": ["movements"],
        "summary": "История движений по конкретному товару",
        "parameters": [
          { "name": "X-Api-Key", "in": "header", "required": true, "schema": { "type": "string" } },
          { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }
        ],
        "responses": {
          "200": { "description": "История движений", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MovementList" } } } }
        }
      }
    },
    "/api/suppliers": {
      "get": {
        "tags": ["suppliers"],
        "summary": "Список поставщиков",
        "parameters": [
          { "name": "X-Api-Key", "in": "header", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "Список поставщиков", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SupplierList" } } } }
        }
      },
      "post": {
        "tags": ["suppliers"],
        "summary": "Создать поставщика",
        "parameters": [
          { "name": "X-Api-Key", "in": "header", "required": true, "schema": { "type": "string" } }
        ],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SupplierCreate" } } } },
        "responses": {
          "201": { "description": "Поставщик создан", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Supplier" } } } }
        }
      }
    },
    "/api/suppliers/{id}": {
      "get": {
        "tags": ["suppliers"],
        "summary": "Получить поставщика по id",
        "parameters": [
          { "name": "X-Api-Key", "in": "header", "required": true, "schema": { "type": "string" } },
          { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }
        ],
        "responses": {
          "200": { "description": "Поставщик", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Supplier" } } } },
          "404": { "description": "Поставщик не найден", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      },
      "patch": {
        "tags": ["suppliers"],
        "summary": "Изменить поставщика",
        "parameters": [
          { "name": "X-Api-Key", "in": "header", "required": true, "schema": { "type": "string" } },
          { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }
        ],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SupplierPatch" } } } },
        "responses": {
          "200": { "description": "Поставщик обновлён", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Supplier" } } } }
        }
      }
    },
    "/api/movements": {
      "get": {
        "tags": ["movements"],
        "summary": "История движений с фильтрами",
        "parameters": [
          { "name": "X-Api-Key", "in": "header", "required": true, "schema": { "type": "string" } },
          { "name": "product_id", "in": "query", "schema": { "type": "integer" } },
          { "name": "supplier_id", "in": "query", "schema": { "type": "integer" } },
          { "name": "movement_type", "in": "query", "schema": { "type": "string", "enum": ["income", "expense", "reserve", "unreserve"] } },
          { "name": "from", "in": "query", "schema": { "type": "string", "format": "date-time" } },
          { "name": "to", "in": "query", "schema": { "type": "string", "format": "date-time" } }
        ],
        "responses": {
          "200": { "description": "История движений", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MovementList" } } } }
        }
      },
      "post": {
        "tags": ["movements"],
        "summary": "Создать движение: приход, списание, резерв или снятие резерва",
        "parameters": [
          { "name": "X-Api-Key", "in": "header", "required": true, "schema": { "type": "string" } }
        ],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MovementCreate" } } } },
        "responses": {
          "201": { "description": "Движение создано; возвращает новый доступный остаток", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MovementResult" } } } },
          "409": { "description": "Отрицательный остаток запрещён", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Conflict" } } } }
        }
      }
    },
    "/api/reports/stock": {
      "get": {
        "tags": ["reports"],
        "summary": "Текущие остатки по всем товарам",
        "parameters": [
          { "name": "X-Api-Key", "in": "header", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "Остатки", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BalanceReport" } } } }
        }
      }
    },
    "/api/reports/stock-on-date": {
      "get": {
        "tags": ["reports"],
        "summary": "Отчёт остатков на дату в JSON",
        "parameters": [
          { "name": "X-Api-Key", "in": "header", "required": true, "schema": { "type": "string" } },
          { "name": "date", "in": "query", "required": true, "schema": { "type": "string" }, "description": "Дата в ISO 8601 (YYYY-MM-DD или RFC3339)" }
        ],
        "responses": {
          "200": { "description": "Отчёт остатков на дату", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BalanceReport" } } } },
          "400": { "description": "Некорректная дата", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/admin/keys": {
      "get": {
        "tags": ["admin"],
        "summary": "Список API-ключей",
        "parameters": [
          { "name": "X-Admin-Key", "in": "header", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "Список ключей", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/APIKeyList" } } } },
          "403": { "description": "Недостаточно прав", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      },
      "post": {
        "tags": ["admin"],
        "summary": "Создать API-ключ (ключ показывается один раз)",
        "parameters": [
          { "name": "X-Admin-Key", "in": "header", "required": true, "schema": { "type": "string" } }
        ],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/APIKeyCreate" } } } },
        "responses": {
          "201": { "description": "Ключ создан", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/APIKeyCreated" } } } }
        }
      }
    },
    "/api/admin/keys/{id}": {
      "patch": {
        "tags": ["admin"],
        "summary": "Отозвать или активировать ключ",
        "parameters": [
          { "name": "X-Admin-Key", "in": "header", "required": true, "schema": { "type": "string" } },
          { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }
        ],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/APIKeyPatch" } } } },
        "responses": {
          "200": { "description": "Ключ обновлён", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/APIKey" } } } }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": { "type": "apiKey", "in": "header", "name": "X-Api-Key" },
      "AdminKeyAuth": { "type": "apiKey", "in": "header", "name": "X-Admin-Key" }
    },
    "schemas": {
      "Product": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "article": { "type": "string", "example": "2108-1003011" },
          "name": { "type": "string", "example": "Подшипник ступицы передний" },
          "price": { "type": "number", "example": 890 },
          "unit": { "type": "string", "example": "шт" },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" }
        }
      },
      "ProductCreate": {
        "type": "object",
        "required": ["article", "name"],
        "properties": {
          "article": { "type": "string" },
          "name": { "type": "string" },
          "price": { "type": "number", "default": 0 },
          "unit": { "type": "string", "default": "шт" }
        }
      },
      "ProductPatch": {
        "type": "object",
        "properties": {
          "article": { "type": "string" },
          "name": { "type": "string" },
          "price": { "type": "number" },
          "unit": { "type": "string" }
        }
      },
      "Balance": {
        "type": "object",
        "properties": {
          "product_id": { "type": "integer" },
          "article": { "type": "string" },
          "name": { "type": "string" },
          "unit": { "type": "string" },
          "received": { "type": "number" },
          "written_off": { "type": "number" },
          "reserved": { "type": "number" },
          "available": { "type": "number" }
        }
      },
      "ProductCard": {
        "type": "object",
        "properties": {
          "product": { "$ref": "#/components/schemas/Product" },
          "balance": { "$ref": "#/components/schemas/Balance" }
        }
      },
      "ProductList": {
        "type": "array",
        "items": { "$ref": "#/components/schemas/Product" }
      },
      "Supplier": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "name": { "type": "string", "example": "ООО «АвтоДеталь»" },
          "contact": { "type": "string" },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "SupplierCreate": {
        "type": "object",
        "required": ["name"],
        "properties": {
          "name": { "type": "string" },
          "contact": { "type": "string" }
        }
      },
      "SupplierPatch": {
        "type": "object",
        "properties": {
          "name": { "type": "string" },
          "contact": { "type": "string" }
        }
      },
      "SupplierList": {
        "type": "array",
        "items": { "$ref": "#/components/schemas/Supplier" }
      },
      "StockMovement": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "product_id": { "type": "integer" },
          "supplier_id": { "type": "integer", "nullable": true },
          "movement_type": { "type": "string", "enum": ["income", "expense", "reserve", "unreserve"] },
          "quantity": { "type": "number" },
          "api_key_id": { "type": "integer" },
          "api_key_name": { "type": "string" },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "MovementCreate": {
        "type": "object",
        "required": ["product_id", "movement_type", "quantity"],
        "properties": {
          "product_id": { "type": "integer" },
          "supplier_id": { "type": "integer", "description": "Обязателен для прихода (income)" },
          "movement_type": { "type": "string", "enum": ["income", "expense", "reserve", "unreserve"] },
          "quantity": { "type": "number", "minimum": 0, "exclusiveMinimum": true }
        }
      },
      "MovementResult": {
        "type": "object",
        "properties": {
          "movement": { "$ref": "#/components/schemas/StockMovement" },
          "balance": { "$ref": "#/components/schemas/Balance" }
        }
      },
      "MovementList": {
        "type": "array",
        "items": { "$ref": "#/components/schemas/StockMovement" }
      },
      "BalanceReport": {
        "type": "object",
        "properties": {
          "as_of": { "type": "string", "format": "date-time" },
          "date": { "type": "string" },
          "generated_at": { "type": "string", "format": "date-time" },
          "items": { "type": "array", "items": { "$ref": "#/components/schemas/Balance" } }
        }
      },
      "AuthCheck": {
        "type": "object",
        "properties": {
          "valid": { "type": "boolean" },
          "name": { "type": "string" },
          "role": { "type": "string" },
          "id": { "type": "integer" }
        }
      },
      "APIKey": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "name": { "type": "string" },
          "role": { "type": "string", "enum": ["admin", "site", "accounting"] },
          "active": { "type": "boolean" },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "APIKeyCreate": {
        "type": "object",
        "required": ["name", "role"],
        "properties": {
          "name": { "type": "string" },
          "role": { "type": "string", "enum": ["admin", "site", "accounting"] }
        }
      },
      "APIKeyCreated": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "name": { "type": "string" },
          "role": { "type": "string" },
          "api_key": { "type": "string", "description": "Показывается один раз" },
          "note": { "type": "string" }
        }
      },
      "APIKeyPatch": {
        "type": "object",
        "properties": {
          "active": { "type": "boolean" }
        }
      },
      "APIKeyList": {
        "type": "object",
        "properties": {
          "items": { "type": "array", "items": { "$ref": "#/components/schemas/APIKey" } }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "code": { "type": "string" },
          "message": { "type": "string" },
          "request_id": { "type": "string" }
        }
      },
      "Conflict": {
        "type": "object",
        "properties": {
          "code": { "type": "string", "example": "negative_stock" },
          "message": { "type": "string", "example": "Отрицательный остаток запрещён" },
          "request_id": { "type": "string" },
          "conflict_reason": { "type": "string" },
          "current_available": { "type": "number" },
          "shortage": { "type": "number" }
        }
      }
    }
  }
}
