krona-staff/api/openapi.yaml
Ochenstarik b283736312 fix(sync): reject work session and timesheet mutations from devices
Предотвращение несанкционированного создания подтверждённых смен и начислений с устройств синхронизации. Команды upsert_work_session и upsert_timesheet_entry отклоняются сервером со статусом rejected и кодом COMMAND_NOT_SUPPORTED без записи в базу данных. Очистка интеграционного теста выполняется соединением мигратора, вставка в staff_employees согласована со схемой миграций.
2026-09-26 16:12:02 +07:00

1086 lines
43 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

openapi: 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:
description: >
Код ошибки, если status = rejected.
Команды `upsert_work_session` и `upsert_timesheet_entry` отклоняются с кодом COMMAND_NOT_SUPPORTED.
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 }