Recreate the formatting baseline from f5ac179 using only Prettier output. Leave sync and tenant-isolation files byte-identical to the base so Antigravity and Grok retain ownership. Normalized comparison confirms all 19 changed files are formatting-only; npm run check passes. Full format check retains five upstream warnings in excluded files.
1083 lines
43 KiB
YAML
1083 lines
43 KiB
YAML
openapi: 3.1.0
|
||
info:
|
||
title: Krona Staff API
|
||
version: 0.3.2
|
||
description: API посещаемости Krona Staff для кабинета сотрудника, терминала и табеля руководителя.
|
||
license:
|
||
name: Proprietary
|
||
url: https://git.ochenstarik.ru/ochenstarik/krona-staff/src/branch/main/LICENSE
|
||
servers:
|
||
- url: /api/v1/staff
|
||
security: []
|
||
paths:
|
||
/live:
|
||
get:
|
||
summary: Check process liveness
|
||
operationId: getLiveness
|
||
tags: [Operations]
|
||
responses:
|
||
'200':
|
||
description: Process is alive
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/Health' }
|
||
'400': { $ref: '#/components/responses/BadRequest' }
|
||
/ready:
|
||
get:
|
||
summary: Check dependency readiness
|
||
operationId: getReadiness
|
||
tags: [Operations]
|
||
responses:
|
||
'200':
|
||
description: Required dependencies are ready
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/Health' }
|
||
'503': { description: A required dependency is unavailable }
|
||
'400': { $ref: '#/components/responses/BadRequest' }
|
||
/version:
|
||
get:
|
||
summary: Read runtime build identity
|
||
operationId: getVersion
|
||
tags: [Operations]
|
||
responses:
|
||
'200':
|
||
description: Runtime build identity
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/Version' }
|
||
'400': { $ref: '#/components/responses/BadRequest' }
|
||
/session:
|
||
post:
|
||
summary: Открыть защищённую сессию администратора
|
||
operationId: createStaffSession
|
||
tags: [Authentication]
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
additionalProperties: false
|
||
required: [access_token]
|
||
properties:
|
||
access_token: { type: string, minLength: 32, maxLength: 4096, writeOnly: true }
|
||
responses:
|
||
'200':
|
||
description: Сессия открыта; токен перенесён в HttpOnly Secure cookie
|
||
'401': { description: Ключ недействителен }
|
||
'403': { description: Ключ не имеет прав администратора }
|
||
delete:
|
||
summary: Завершить сессию администратора
|
||
operationId: deleteStaffSession
|
||
tags: [Authentication]
|
||
responses:
|
||
'204': { description: Сессия завершена }
|
||
'403': { description: Запрос с постороннего сайта отклонён }
|
||
/portal:
|
||
get:
|
||
summary: Получить доступную пользователю сводку посещаемости и табеля
|
||
operationId: getStaffPortal
|
||
tags: [Attendance]
|
||
security: [{ bearerAuth: [] }]
|
||
responses:
|
||
'200':
|
||
description: Сводка с автоматическим ограничением данными текущего сотрудника
|
||
content:
|
||
application/json:
|
||
schema: { type: object }
|
||
'403': { description: Недостаточно прав }
|
||
/employees:
|
||
post:
|
||
summary: Добавить сотрудника
|
||
operationId: createStaffEmployee
|
||
tags: [Employees]
|
||
security: [{ bearerAuth: [] }]
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/EmployeeCreate'
|
||
responses:
|
||
'201':
|
||
description: Сотрудник создан
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/Employee' }
|
||
'403': { description: Недостаточно прав }
|
||
'409': { description: Табельный номер уже используется }
|
||
/professions:
|
||
get:
|
||
summary: Получить кадровый справочник профессий
|
||
operationId: listStaffProfessions
|
||
tags: [Employees]
|
||
security: [{ bearerAuth: [] }]
|
||
responses:
|
||
'200': { description: Tenant profession catalogue }
|
||
'401': { description: Требуется авторизация }
|
||
post:
|
||
summary: Добавить профессию в кадровый справочник
|
||
operationId: createStaffProfession
|
||
tags: [Employees]
|
||
security: [{ bearerAuth: [] }]
|
||
responses:
|
||
'201': { description: Profession created }
|
||
'403': { description: Недостаточно прав }
|
||
'409': { description: Профессия уже существует }
|
||
/professions/{professionId}:
|
||
parameters:
|
||
- in: path
|
||
name: professionId
|
||
required: true
|
||
schema: { type: string, pattern: '^[0-9A-HJKMNP-TV-Z]{26}$' }
|
||
patch:
|
||
summary: Изменить профессию или её статус
|
||
operationId: updateStaffProfession
|
||
tags: [Employees]
|
||
security: [{ bearerAuth: [] }]
|
||
responses:
|
||
'200': { description: Profession updated }
|
||
'403': { description: Недостаточно прав }
|
||
'404': { description: Профессия не найдена }
|
||
/employees/{employeeId}:
|
||
patch:
|
||
summary: Изменить карточку или статус сотрудника
|
||
operationId: updateStaffEmployee
|
||
tags: [Employees]
|
||
security: [{ bearerAuth: [] }]
|
||
parameters:
|
||
- in: path
|
||
name: employeeId
|
||
required: true
|
||
schema: { $ref: '#/components/schemas/Ulid' }
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/EmployeeUpdate'
|
||
responses:
|
||
'200':
|
||
description: Сотрудник изменён
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/Employee' }
|
||
'403': { description: Недостаточно прав }
|
||
'404': { description: Сотрудник не найден }
|
||
/attendance/events:
|
||
post:
|
||
summary: Принять идемпотентную отметку терминала
|
||
operationId: recordAttendanceEvent
|
||
tags: [Attendance]
|
||
security: [{ bearerAuth: [] }]
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
additionalProperties: false
|
||
required: [employee_id, event_type, occurred_at, source_event_id]
|
||
properties:
|
||
event_id: { $ref: '#/components/schemas/Ulid' }
|
||
employee_id: { $ref: '#/components/schemas/Ulid' }
|
||
event_type: { enum: [check_in, check_out] }
|
||
occurred_at: { type: string, format: date-time }
|
||
source_event_id: { type: string, minLength: 1, maxLength: 200 }
|
||
device_id: { $ref: '#/components/schemas/Ulid' }
|
||
org_unit_id: { $ref: '#/components/schemas/Ulid' }
|
||
responses:
|
||
'201': { description: Отметка принята }
|
||
'200': { description: Повтор уже принятой отметки }
|
||
'403': { description: Недостаточно прав }
|
||
/work-sessions/reconcile:
|
||
post:
|
||
summary: Сверить отметки терминала и сформировать смены за период
|
||
operationId: reconcileStaffWorkSessions
|
||
tags: [Work Sessions]
|
||
security: [{ bearerAuth: [] }]
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
additionalProperties: false
|
||
required: [date_from, date_to]
|
||
properties:
|
||
date_from: { type: string, format: date }
|
||
date_to: { type: string, format: date }
|
||
business_day_start_hour: { type: integer, minimum: 0, maximum: 23, default: 4 }
|
||
automatic_close_hour: { type: integer, minimum: 0, maximum: 23, default: 2 }
|
||
open_shift_threshold_minutes:
|
||
{ type: integer, minimum: 60, maximum: 2880, default: 840 }
|
||
responses:
|
||
'200':
|
||
description: Итоги сверки, включая события без назначенного подразделения
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required:
|
||
[
|
||
groups,
|
||
sessions_created_or_updated,
|
||
confirmed_sessions_skipped,
|
||
anomalous_sessions,
|
||
unresolved_events,
|
||
]
|
||
properties:
|
||
groups: { type: integer, minimum: 0 }
|
||
sessions_created_or_updated: { type: integer, minimum: 0 }
|
||
confirmed_sessions_skipped: { type: integer, minimum: 0 }
|
||
anomalous_sessions: { type: integer, minimum: 0 }
|
||
unresolved_events: { type: integer, minimum: 0 }
|
||
'403': { description: Недостаточно прав }
|
||
/work-sessions/{sessionId}:
|
||
patch:
|
||
summary: Подтвердить, отклонить или скорректировать рабочую смену
|
||
operationId: reviewStaffWorkSession
|
||
tags: [Work Sessions]
|
||
security: [{ bearerAuth: [] }]
|
||
parameters:
|
||
- in: path
|
||
name: sessionId
|
||
required: true
|
||
schema: { $ref: '#/components/schemas/Ulid' }
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
additionalProperties: false
|
||
required: [status]
|
||
properties:
|
||
status: { enum: [confirmed, rejected] }
|
||
worked_minutes: { type: integer, minimum: 0, maximum: 2880 }
|
||
started_at:
|
||
oneOf: [{ type: string, format: date-time }, { type: 'null' }]
|
||
ended_at:
|
||
oneOf: [{ type: string, format: date-time }, { type: 'null' }]
|
||
correction_note: { type: string, maxLength: 1000 }
|
||
responses:
|
||
'200': { description: Смена проверена; подтверждённые часы перенесены в табель }
|
||
'400': { description: Для корректировки не указана причина или интервал некорректен }
|
||
'403': { description: Недостаточно прав }
|
||
'404': { description: Смена не найдена }
|
||
/timesheets/{employeeId}/{workDate}:
|
||
put:
|
||
summary: Сохранить строку табеля
|
||
operationId: saveTimesheetEntry
|
||
tags: [Timesheet]
|
||
security: [{ bearerAuth: [] }]
|
||
parameters:
|
||
- in: path
|
||
name: employeeId
|
||
required: true
|
||
schema: { $ref: '#/components/schemas/Ulid' }
|
||
- in: path
|
||
name: workDate
|
||
required: true
|
||
schema: { type: string, format: date }
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
additionalProperties: false
|
||
required: [minutes_worked, status]
|
||
properties:
|
||
minutes_worked: { type: integer, minimum: 0, maximum: 1440 }
|
||
status: { enum: [draft, submitted, approved, rejected] }
|
||
note: { type: string, maxLength: 1000 }
|
||
responses:
|
||
'200': { description: Строка табеля сохранена }
|
||
'403': { description: Недостаточно прав }
|
||
/employees/{employeeId}/access-token:
|
||
post:
|
||
summary: Выпустить новый одноразово показываемый ключ личного кабинета сотрудника
|
||
operationId: rotateEmployeeAccessToken
|
||
tags: [Employees]
|
||
security: [{ bearerAuth: [] }]
|
||
parameters:
|
||
- in: path
|
||
name: employeeId
|
||
required: true
|
||
schema: { $ref: '#/components/schemas/Ulid' }
|
||
responses:
|
||
'200': { description: Ключ выпущен; на сервере сохранён только его SHA-256 отпечаток }
|
||
'403': { description: Недостаточно прав }
|
||
'404': { description: Сотрудник не найден }
|
||
/employees/{employeeId}/pay-assignment:
|
||
get:
|
||
summary: Получить зарплатный профиль сотрудника
|
||
operationId: getEmployeePayAssignment
|
||
tags: [Payroll]
|
||
security: [{ bearerAuth: [] }]
|
||
parameters:
|
||
- in: path
|
||
name: employeeId
|
||
required: true
|
||
schema: { $ref: '#/components/schemas/Ulid' }
|
||
responses:
|
||
'200':
|
||
description: Зарплатный профиль сотрудника
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/PayAssignment' }
|
||
'403': { description: Недостаточно прав }
|
||
'404': { description: Сотрудник или зарплатный профиль не найден }
|
||
put:
|
||
summary: Сохранить зарплатный профиль сотрудника
|
||
operationId: saveEmployeePayAssignment
|
||
tags: [Payroll]
|
||
security: [{ bearerAuth: [] }]
|
||
parameters:
|
||
- in: path
|
||
name: employeeId
|
||
required: true
|
||
schema: { $ref: '#/components/schemas/Ulid' }
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/PayAssignmentSave' }
|
||
responses:
|
||
'200':
|
||
description: Зарплатный профиль сохранён
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/PayAssignment' }
|
||
'403': { description: Недостаточно прав }
|
||
'404': { description: 'Сотрудник, точка или схема не найдены' }
|
||
/pay-rules:
|
||
get:
|
||
summary: Получить список схем расчёта зарплаты
|
||
operationId: listStaffPayRules
|
||
tags: [Payroll]
|
||
security: [{ bearerAuth: [] }]
|
||
responses:
|
||
'200':
|
||
description: Список доступных схем расчёта
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [pay_rules]
|
||
properties:
|
||
pay_rules:
|
||
type: array
|
||
items: { type: object }
|
||
'403': { description: Недостаточно прав }
|
||
/org-units:
|
||
get:
|
||
summary: Получить доступные торговые точки
|
||
operationId: listStaffOrgUnits
|
||
tags: [Organization]
|
||
security: [{ bearerAuth: [] }]
|
||
responses:
|
||
'200': { description: Список доступных торговых точек }
|
||
'403': { description: Недостаточно прав }
|
||
/finance:
|
||
get:
|
||
summary: Получить доходы, расходы и дневную динамику за период
|
||
operationId: getStaffFinance
|
||
tags: [Finance]
|
||
security: [{ bearerAuth: [] }]
|
||
parameters:
|
||
- { in: query, name: from, required: true, schema: { type: string, format: date } }
|
||
- { in: query, name: to, required: true, schema: { type: string, format: date } }
|
||
responses:
|
||
'200': { description: Финансовые операции и агрегаты }
|
||
'403': { description: Недостаточно прав }
|
||
/finance/{orgUnitId}/{businessDate}/{metricKey}:
|
||
put:
|
||
summary: Сохранить сумму по финансовой категории за день
|
||
operationId: saveStaffFinanceMetric
|
||
tags: [Finance]
|
||
security: [{ bearerAuth: [] }]
|
||
parameters:
|
||
- {
|
||
in: path,
|
||
name: orgUnitId,
|
||
required: true,
|
||
schema: { $ref: '#/components/schemas/Ulid' },
|
||
}
|
||
- { in: path, name: businessDate, required: true, schema: { type: string, format: date } }
|
||
- { in: path, name: metricKey, required: true, schema: { type: string } }
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [value_minor]
|
||
properties:
|
||
value_minor:
|
||
{ type: integer, minimum: -9000000000000000, maximum: 9000000000000000 }
|
||
currency_code: { type: string, pattern: '^[A-Z]{3}$' }
|
||
responses:
|
||
'200': { description: Сумма сохранена }
|
||
'403': { description: Недостаточно прав }
|
||
/schedule:
|
||
get:
|
||
summary: Получить плановые смены и фактические отметки сканера
|
||
operationId: getStaffSchedule
|
||
tags: [Schedule]
|
||
security: [{ bearerAuth: [] }]
|
||
parameters:
|
||
- { in: query, name: from, required: true, schema: { type: string, format: date } }
|
||
- { in: query, name: to, required: true, schema: { type: string, format: date } }
|
||
responses:
|
||
'200': { description: Календарь плановых и фактических смен }
|
||
'403': { description: Недостаточно прав }
|
||
/schedule/{employeeId}/{businessDate}:
|
||
put:
|
||
summary: Запланировать или удалить смену сотрудника
|
||
operationId: saveStaffPlannedShift
|
||
tags: [Schedule]
|
||
security: [{ bearerAuth: [] }]
|
||
parameters:
|
||
- {
|
||
in: path,
|
||
name: employeeId,
|
||
required: true,
|
||
schema: { $ref: '#/components/schemas/Ulid' },
|
||
}
|
||
- { in: path, name: businessDate, required: true, schema: { type: string, format: date } }
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [org_unit_id, planned_minutes]
|
||
properties:
|
||
org_unit_id: { $ref: '#/components/schemas/Ulid' }
|
||
planned_minutes: { type: integer, minimum: 0, maximum: 1440 }
|
||
start_time: { type: string }
|
||
end_time: { type: string }
|
||
note: { type: string, maxLength: 500 }
|
||
responses:
|
||
'200': { description: План смены сохранён }
|
||
'403': { description: Недостаточно прав }
|
||
/payroll:
|
||
get:
|
||
summary: Получить зарплатные итоги и расчётный лист за период
|
||
operationId: getStaffPayroll
|
||
tags: [Payroll]
|
||
security: [{ bearerAuth: [] }]
|
||
parameters:
|
||
- { in: query, name: from, required: true, schema: { type: string, format: date } }
|
||
- { in: query, name: to, required: true, schema: { type: string, format: date } }
|
||
responses:
|
||
'200': { description: 'Начисления, выплаты, остаток и подтверждённые часы' }
|
||
'403': { description: Недостаточно прав }
|
||
/payroll/calculate:
|
||
post:
|
||
summary: Рассчитать зарплату за период
|
||
operationId: calculateStaffPayroll
|
||
tags: [Payroll]
|
||
security: [{ bearerAuth: [] }]
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
month: { type: string, pattern: '^\\d{4}-\\d{2}$' }
|
||
from: { type: string, format: date }
|
||
to: { type: string, format: date }
|
||
org_unit_id: { $ref: '#/components/schemas/Ulid' }
|
||
responses:
|
||
'200':
|
||
description: Предварительный результат расчёта
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required:
|
||
[run_id, status, period_start, period_end, total_accrued_minor, totals, warnings]
|
||
properties:
|
||
run_id: { $ref: '#/components/schemas/Ulid' }
|
||
status: { const: calculated }
|
||
period_start: { type: string, format: date }
|
||
period_end: { type: string, format: date }
|
||
total_accrued_minor: { type: string }
|
||
totals: { type: array, items: { type: object } }
|
||
warnings: { type: array, items: { type: object } }
|
||
'400': { description: Некорректный период }
|
||
'403': { description: Недостаточно прав }
|
||
'409': { description: Расчёт за этот период уже утверждён }
|
||
/payroll/runs/{runId}/approve:
|
||
post:
|
||
summary: Утвердить расчёт зарплаты и записать начисления в реестр
|
||
operationId: approveStaffPayrollRun
|
||
tags: [Payroll]
|
||
security: [{ bearerAuth: [] }]
|
||
parameters:
|
||
- in: path
|
||
name: runId
|
||
required: true
|
||
schema: { $ref: '#/components/schemas/Ulid' }
|
||
responses:
|
||
'200':
|
||
description: Расчёт утверждён и проведён в реестр
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required:
|
||
[run_id, status, approved_by, approved_at, total_accrued_minor, entries_posted]
|
||
properties:
|
||
run_id: { $ref: '#/components/schemas/Ulid' }
|
||
status: { const: approved }
|
||
approved_by: { $ref: '#/components/schemas/Ulid' }
|
||
approved_at: { type: string, format: date-time }
|
||
total_accrued_minor: { type: string }
|
||
entries_posted: { type: integer }
|
||
'400': { description: Расчёт не в статусе calculated }
|
||
'403': { description: Недостаточно прав }
|
||
'404': { description: Расчёт не найден }
|
||
/payroll/runs/{runId}:
|
||
get:
|
||
summary: Получить подробности расчёта зарплаты
|
||
operationId: getStaffPayrollRun
|
||
tags: [Payroll]
|
||
security: [{ bearerAuth: [] }]
|
||
parameters:
|
||
- in: path
|
||
name: runId
|
||
required: true
|
||
schema: { $ref: '#/components/schemas/Ulid' }
|
||
responses:
|
||
'200':
|
||
description: Детали расчёта со строками
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [run, lines]
|
||
properties:
|
||
run: { type: object }
|
||
lines: { type: array, items: { type: object } }
|
||
'403': { description: Недостаточно прав }
|
||
'404': { description: Расчёт не найден }
|
||
/imports/owner-workbook:
|
||
post:
|
||
summary: Идемпотентно импортировать локально разобранную книгу владельца
|
||
operationId: importStaffOwnerWorkbook
|
||
tags: [Import]
|
||
security: [{ bearerAuth: [] }]
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [source_name]
|
||
properties:
|
||
source_name: { type: string, maxLength: 240 }
|
||
units: { type: array, items: { type: object } }
|
||
financial_entries: { type: array, items: { type: object } }
|
||
employees: { type: array, items: { type: object } }
|
||
schedule_items: { type: array, items: { type: object } }
|
||
payroll_entries: { type: array, items: { type: object } }
|
||
responses:
|
||
'200': { description: Пакет импортирован повторяемо без дублирования }
|
||
'403': { description: Недостаточно прав }
|
||
/push/public-key:
|
||
get:
|
||
summary: Получить публичный VAPID-ключ для WebPush
|
||
operationId: getPushPublicKey
|
||
tags: [PushNotifications]
|
||
responses:
|
||
'200':
|
||
description: Публичный VAPID-ключ
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [public_key]
|
||
properties:
|
||
public_key: { type: string }
|
||
'400': { $ref: '#/components/responses/BadRequest' }
|
||
/push/subscriptions:
|
||
post:
|
||
summary: Сохранить WebPush-подписку текущего сотрудника
|
||
operationId: createPushSubscription
|
||
tags: [PushNotifications]
|
||
security: [{ bearerAuth: [] }]
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [endpoint, keys]
|
||
properties:
|
||
endpoint: { type: string, minLength: 10, maxLength: 2000 }
|
||
keys:
|
||
type: object
|
||
required: [p256dh, auth]
|
||
properties:
|
||
p256dh: { type: string, minLength: 10, maxLength: 250 }
|
||
auth: { type: string, minLength: 10, maxLength: 100 }
|
||
user_agent: { type: string, maxLength: 500 }
|
||
responses:
|
||
'201':
|
||
description: Подписка сохранена
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [id, endpoint]
|
||
properties:
|
||
id: { type: string }
|
||
endpoint: { type: string }
|
||
'401': { description: Требуется авторизация }
|
||
'403': { description: Недостаточно прав }
|
||
'404': { description: Сотрудник не найден }
|
||
delete:
|
||
summary: Удалить WebPush-подписку
|
||
operationId: deletePushSubscription
|
||
tags: [PushNotifications]
|
||
security: [{ bearerAuth: [] }]
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [endpoint]
|
||
properties:
|
||
endpoint: { type: string, minLength: 10, maxLength: 2000 }
|
||
responses:
|
||
'204': { description: Подписка удалена }
|
||
'401': { description: Требуется авторизация }
|
||
'403': { description: Недостаточно прав }
|
||
/sync/devices:
|
||
post:
|
||
summary: Регистрация нового устройства синхронизации
|
||
operationId: registerSyncDevice
|
||
tags: [Sync]
|
||
security: [{ bearerAuth: [] }]
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [device_code, name]
|
||
properties:
|
||
device_code: { type: string, minLength: 1, maxLength: 64 }
|
||
name: { type: string, minLength: 1, maxLength: 255 }
|
||
org_unit_id: { $ref: '#/components/schemas/Ulid' }
|
||
allowed_scopes:
|
||
type: array
|
||
items: { type: string }
|
||
responses:
|
||
'201':
|
||
description: Устройство зарегистрировано, выдан токен
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [device, token]
|
||
properties:
|
||
device:
|
||
type: object
|
||
required:
|
||
[
|
||
id,
|
||
tenant_id,
|
||
device_code,
|
||
name,
|
||
token_hint,
|
||
status,
|
||
allowed_scopes,
|
||
registered_at,
|
||
created_by,
|
||
]
|
||
properties:
|
||
id: { $ref: '#/components/schemas/Ulid' }
|
||
tenant_id: { $ref: '#/components/schemas/Ulid' }
|
||
device_code: { type: string }
|
||
name: { type: string }
|
||
token_hint: { type: string }
|
||
status: { type: string, enum: [active, revoked, pending] }
|
||
allowed_scopes: { type: array, items: { type: string } }
|
||
org_unit_id:
|
||
oneOf:
|
||
- { $ref: '#/components/schemas/Ulid' }
|
||
- { type: 'null' }
|
||
registered_at: { type: string, format: date-time }
|
||
created_by: { $ref: '#/components/schemas/Ulid' }
|
||
token: { type: string }
|
||
'400': { $ref: '#/components/responses/BadRequest' }
|
||
'401': { description: Требуется авторизация руководителя }
|
||
'403': { description: Недостаточно прав }
|
||
/sync/devices/{deviceId}/revoke:
|
||
post:
|
||
summary: Отозвать токен и доступ устройства
|
||
operationId: revokeSyncDevice
|
||
tags: [Sync]
|
||
security: [{ bearerAuth: [] }]
|
||
parameters:
|
||
- in: path
|
||
name: deviceId
|
||
required: true
|
||
schema: { $ref: '#/components/schemas/Ulid' }
|
||
responses:
|
||
'200':
|
||
description: Устройство отозвано
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [id, status, revoked_at]
|
||
properties:
|
||
id: { $ref: '#/components/schemas/Ulid' }
|
||
status: { type: string, enum: [revoked] }
|
||
revoked_at: { type: string, format: date-time }
|
||
'401': { description: Требуется авторизация руководителя }
|
||
'403': { description: Недостаточно прав }
|
||
'404': { description: Устройство не найдено }
|
||
/sync/status:
|
||
get:
|
||
summary: Получить статус сервера синхронизации и чекпоинта устройства
|
||
operationId: getSyncStatus
|
||
tags: [Sync]
|
||
security: [{ bearerAuth: [] }]
|
||
responses:
|
||
'200':
|
||
description: Текущее состояние синхронизации
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [server_time_utc, latest_cursor, schema_version]
|
||
properties:
|
||
server_time_utc: { type: string, format: date-time }
|
||
latest_cursor: { type: string }
|
||
device_checkpoint:
|
||
oneOf:
|
||
- type: object
|
||
properties:
|
||
last_acked_cursor:
|
||
oneOf:
|
||
- { type: string }
|
||
- { type: 'null' }
|
||
acknowledged_at:
|
||
oneOf:
|
||
- { type: string, format: date-time }
|
||
- { type: 'null' }
|
||
- { type: 'null' }
|
||
schema_version: { type: integer }
|
||
'401': { description: Недействительный токен устройства }
|
||
'403': { description: Устройство отозвано или заблокировано }
|
||
/sync/pull:
|
||
post:
|
||
summary: Получить изменения сущностей после заданного курсора или начальный снимок
|
||
operationId: pullSyncChanges
|
||
tags: [Sync]
|
||
security: [{ bearerAuth: [] }]
|
||
requestBody:
|
||
required: false
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
cursor:
|
||
oneOf:
|
||
- { type: string }
|
||
- { type: 'null' }
|
||
limit: { type: integer, minimum: 1, maximum: 100, default: 100 }
|
||
entity_types:
|
||
type: array
|
||
items: { type: string }
|
||
responses:
|
||
'200':
|
||
description: Пакет изменений или начальный снимок
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [changes, next_cursor, has_more]
|
||
properties:
|
||
changes:
|
||
type: array
|
||
items:
|
||
type: object
|
||
required:
|
||
[cursor, entity_type, entity_id, operation, revision, payload, created_at]
|
||
properties:
|
||
cursor: { type: string }
|
||
entity_type: { type: string }
|
||
entity_id: { type: string }
|
||
operation: { type: string, enum: [upsert, delete] }
|
||
revision: { type: integer }
|
||
payload: { type: object }
|
||
created_at: { type: string, format: date-time }
|
||
next_cursor: { type: string }
|
||
has_more: { type: boolean }
|
||
'400': { $ref: '#/components/responses/BadRequest' }
|
||
'401': { description: Недействительный токен устройства }
|
||
'403': { description: Устройство отозвано или заблокировано }
|
||
/sync/push:
|
||
post:
|
||
summary: Пакетная отправка команд от локального устройства
|
||
operationId: pushSyncCommands
|
||
tags: [Sync]
|
||
security: [{ bearerAuth: [] }]
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [commands]
|
||
properties:
|
||
commands:
|
||
type: array
|
||
minItems: 1
|
||
maxItems: 100
|
||
items:
|
||
type: object
|
||
required:
|
||
[
|
||
command_type,
|
||
schema_version,
|
||
idempotency_key,
|
||
correlation_id,
|
||
created_at,
|
||
payload,
|
||
]
|
||
properties:
|
||
command_type: { type: string }
|
||
schema_version: { type: integer }
|
||
idempotency_key: { type: string, minLength: 1, maxLength: 128 }
|
||
correlation_id: { type: string }
|
||
causation_id:
|
||
oneOf:
|
||
- { type: string }
|
||
- { type: 'null' }
|
||
created_at: { type: string, format: date-time }
|
||
payload: { type: object }
|
||
responses:
|
||
'200':
|
||
description: Результаты применения команд
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [results]
|
||
properties:
|
||
results:
|
||
type: array
|
||
items:
|
||
type: object
|
||
required: [idempotency_key, command_type, status]
|
||
properties:
|
||
idempotency_key: { type: string }
|
||
command_type: { type: string }
|
||
status: { type: string, enum: [applied, rejected, conflict] }
|
||
result:
|
||
oneOf:
|
||
- { type: object }
|
||
- { type: 'null' }
|
||
conflict:
|
||
oneOf:
|
||
- type: object
|
||
required:
|
||
[
|
||
conflict_code,
|
||
entity_type,
|
||
entity_id,
|
||
expected_revision,
|
||
actual_revision,
|
||
safe_description,
|
||
client_action,
|
||
]
|
||
properties:
|
||
conflict_code: { type: string, enum: [REVISION_MISMATCH] }
|
||
entity_type: { type: string }
|
||
entity_id: { type: string }
|
||
expected_revision: { type: integer }
|
||
actual_revision: { type: integer }
|
||
safe_description: { type: string }
|
||
client_action: { type: string, enum: [FETCH_LATEST_AND_RETRY] }
|
||
- { type: 'null' }
|
||
error_code:
|
||
oneOf:
|
||
- { type: string }
|
||
- { type: 'null' }
|
||
error_message:
|
||
oneOf:
|
||
- { type: string }
|
||
- { type: 'null' }
|
||
'400': { $ref: '#/components/responses/BadRequest' }
|
||
'401': { description: Недействительный токен устройства }
|
||
'403': { description: Устройство отозвано или заблокировано }
|
||
/sync/ack:
|
||
post:
|
||
summary: Подтвердить получение и обработку изменений до курсора
|
||
operationId: ackSyncCheckpoint
|
||
tags: [Sync]
|
||
security: [{ bearerAuth: [] }]
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [cursor]
|
||
properties:
|
||
cursor: { type: string, minLength: 5 }
|
||
responses:
|
||
'200':
|
||
description: Чекпоинт зафиксирован
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [acknowledged, cursor, acknowledged_at]
|
||
properties:
|
||
acknowledged: { type: boolean }
|
||
cursor: { type: string }
|
||
acknowledged_at: { type: string, format: date-time }
|
||
'400': { $ref: '#/components/responses/BadRequest' }
|
||
'401': { description: Недействительный токен устройства }
|
||
'403': { description: Устройство отозвано или заблокировано }
|
||
components:
|
||
securitySchemes:
|
||
bearerAuth:
|
||
type: http
|
||
scheme: bearer
|
||
bearerFormat: JWT
|
||
responses:
|
||
BadRequest:
|
||
description: Invalid request
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/Problem' }
|
||
schemas:
|
||
Ulid:
|
||
type: string
|
||
pattern: '^[0-9A-HJKMNP-TV-Z]{26}$'
|
||
examples: [01J00000000000000000000000]
|
||
Health:
|
||
type: object
|
||
additionalProperties: false
|
||
required: [status]
|
||
properties:
|
||
status: { type: string }
|
||
Employee:
|
||
type: object
|
||
additionalProperties: false
|
||
required: [id, personnel_number, display_name, position, gender, status]
|
||
properties:
|
||
id: { $ref: '#/components/schemas/Ulid' }
|
||
core_user_id:
|
||
oneOf:
|
||
- { $ref: '#/components/schemas/Ulid' }
|
||
- { type: 'null' }
|
||
personnel_number: { type: string }
|
||
display_name: { type: string }
|
||
position: { type: string }
|
||
gender: { enum: ['', male, female] }
|
||
status: { enum: [active, blocked, dismissed] }
|
||
EmployeeCreate:
|
||
type: object
|
||
additionalProperties: false
|
||
required: [personnel_number, display_name]
|
||
properties:
|
||
personnel_number: { type: string, minLength: 1, maxLength: 80 }
|
||
display_name: { type: string, minLength: 1, maxLength: 240 }
|
||
position: { type: string, maxLength: 160 }
|
||
gender: { enum: ['', male, female] }
|
||
EmployeeUpdate:
|
||
type: object
|
||
additionalProperties: false
|
||
minProperties: 1
|
||
properties:
|
||
personnel_number: { type: string, minLength: 1, maxLength: 80 }
|
||
display_name: { type: string, minLength: 1, maxLength: 240 }
|
||
position: { type: string, maxLength: 160 }
|
||
gender: { enum: ['', male, female] }
|
||
status: { enum: [active, blocked, dismissed] }
|
||
Problem:
|
||
type: object
|
||
additionalProperties: false
|
||
required: [type, title, status, trace_id]
|
||
properties:
|
||
type: { type: string, format: uri-reference }
|
||
title: { type: string }
|
||
status: { type: integer, minimum: 400, maximum: 599 }
|
||
trace_id: { $ref: '#/components/schemas/Ulid' }
|
||
Version:
|
||
type: object
|
||
additionalProperties: false
|
||
required: [name, version, mode]
|
||
properties:
|
||
name: { const: krona-staff }
|
||
version: { type: string }
|
||
mode: { enum: [standalone, platform] }
|
||
PayAssignment:
|
||
type: object
|
||
additionalProperties: false
|
||
required:
|
||
[
|
||
id,
|
||
employee_id,
|
||
pay_rule_id,
|
||
pay_rule_code,
|
||
pay_rule_name,
|
||
rate_minor,
|
||
percent_bp,
|
||
guarantee_minor,
|
||
pool_weight_bp,
|
||
effective_from,
|
||
]
|
||
properties:
|
||
id: { $ref: '#/components/schemas/Ulid' }
|
||
employee_id: { $ref: '#/components/schemas/Ulid' }
|
||
org_unit_id:
|
||
oneOf:
|
||
- { $ref: '#/components/schemas/Ulid' }
|
||
- { type: 'null' }
|
||
pay_rule_id: { $ref: '#/components/schemas/Ulid' }
|
||
pay_rule_code: { type: string }
|
||
pay_rule_name: { type: string }
|
||
rate_minor: { type: integer, minimum: 0 }
|
||
percent_bp: { type: integer, minimum: 0 }
|
||
guarantee_minor: { type: integer, minimum: 0 }
|
||
pool_weight_bp: { type: integer, minimum: 0 }
|
||
effective_from: { type: string, format: date }
|
||
effective_to:
|
||
oneOf:
|
||
- { type: string, format: date }
|
||
- { type: 'null' }
|
||
params: { type: object }
|
||
note: { type: string }
|
||
PayAssignmentSave:
|
||
type: object
|
||
additionalProperties: false
|
||
required: [pay_rule_id]
|
||
properties:
|
||
org_unit_id:
|
||
oneOf:
|
||
- { $ref: '#/components/schemas/Ulid' }
|
||
- { type: 'null' }
|
||
pay_rule_id: { $ref: '#/components/schemas/Ulid' }
|
||
rate_minor: { type: integer, minimum: 0 }
|
||
percent_bp: { type: integer, minimum: 0, maximum: 10000 }
|
||
guarantee_minor: { type: integer, minimum: 0 }
|
||
pool_weight_bp: { type: integer, minimum: 0, maximum: 20000 }
|
||
effective_from: { type: string, format: date }
|
||
note: { type: string, maxLength: 500 }
|