AllocationIn.side — позиция строки (D/K), vgo_assignment получает side и уникальность по ней. MoneyIn ограничен numeric(18,2): не больше 16 цифр до запятой. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
453 lines
20 KiB
YAML
453 lines
20 KiB
YAML
openapi: 3.1.0
|
||
info:
|
||
title: АРМ КА — таблица корректировок
|
||
version: 0.1.0-draft
|
||
description: >
|
||
Черновик контракта AURORA-1540 (бэкенд) / AURORA-1541 (фронт). Текстовое описание, источники и открытые вопросы —
|
||
dev/api-contract-corrections.md; оба файла держатся одинаковыми. Деньги — строки с двумя знаками в рублях, проценты —
|
||
строки с двумя знаками. Чтение — CONSOLIDATION_VIEW, правки — CONSOLIDATION_EDIT.
|
||
servers:
|
||
- url: /api/v1
|
||
security:
|
||
- bearerAuth: []
|
||
|
||
paths:
|
||
/consolidations/versions/{version_id}/corrections:
|
||
get:
|
||
operationId: getCorrectionTable
|
||
summary: Таблица корректировок версии
|
||
x-permission: CONSOLIDATION_VIEW
|
||
parameters:
|
||
- $ref: '#/components/parameters/VersionId'
|
||
- $ref: '#/components/parameters/StatementTypeQuery'
|
||
responses:
|
||
'200':
|
||
description: Агрегаты со строками, нераспределённые строки, итоги
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/CorrectionTableOut' }
|
||
'401': { $ref: '#/components/responses/Unauthorized' }
|
||
'403': { $ref: '#/components/responses/Forbidden' }
|
||
'404': { $ref: '#/components/responses/DomainError' }
|
||
|
||
/consolidations/versions/{version_id}/corrections/aggregates/{rule_id}/{company_1_id}/{company_2_id}:
|
||
put:
|
||
operationId: saveCorrection
|
||
summary: Сохранить корректировку агрегата (создать или заменить целиком)
|
||
description: >
|
||
Аудит CORRECTION_CREATE (первая) / CORRECTION_EDIT; version_no++; результат КФО устаревает.
|
||
Ошибки 409 — VERSION_NOT_EDITABLE, LOCK_REQUIRED, LOCKED_BY_OTHER, CORRECTION_VERSION_CONFLICT;
|
||
422 — COMMENT_REQUIRED, EMPTY_CORRECTION, INVALID_LINE_CODE (detail.line_code).
|
||
x-permission: CONSOLIDATION_EDIT
|
||
parameters:
|
||
- $ref: '#/components/parameters/VersionId'
|
||
- $ref: '#/components/parameters/RuleId'
|
||
- $ref: '#/components/parameters/Company1Id'
|
||
- $ref: '#/components/parameters/Company2Id'
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/CorrectionIn' }
|
||
responses:
|
||
'200':
|
||
description: Пересчитанный агрегат
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/CorrectionAggregateOut' }
|
||
'401': { $ref: '#/components/responses/Unauthorized' }
|
||
'403': { $ref: '#/components/responses/Forbidden' }
|
||
'404': { $ref: '#/components/responses/DomainError' }
|
||
'409': { $ref: '#/components/responses/DomainError' }
|
||
'422': { $ref: '#/components/responses/DomainOrValidationError' }
|
||
|
||
/consolidations/versions/{version_id}/corrections/aggregates/{rule_id}/{company_1_id}/{company_2_id}/revoke:
|
||
post:
|
||
operationId: revokeCorrection
|
||
summary: Отозвать действующую корректировку агрегата
|
||
description: >
|
||
Статус REVOKED, запись остаётся в истории; аудит CORRECTION_REVOKE.
|
||
409 — правки версии, CORRECTION_NOT_APPLIED; 422 — COMMENT_REQUIRED.
|
||
x-permission: CONSOLIDATION_EDIT
|
||
parameters:
|
||
- $ref: '#/components/parameters/VersionId'
|
||
- $ref: '#/components/parameters/RuleId'
|
||
- $ref: '#/components/parameters/Company1Id'
|
||
- $ref: '#/components/parameters/Company2Id'
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/CorrectionRevokeIn' }
|
||
responses:
|
||
'200':
|
||
description: Пересчитанный агрегат
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/CorrectionAggregateOut' }
|
||
'401': { $ref: '#/components/responses/Unauthorized' }
|
||
'403': { $ref: '#/components/responses/Forbidden' }
|
||
'404': { $ref: '#/components/responses/DomainError' }
|
||
'409': { $ref: '#/components/responses/DomainError' }
|
||
'422': { $ref: '#/components/responses/DomainOrValidationError' }
|
||
|
||
/consolidations/versions/{version_id}/corrections/allocations:
|
||
post:
|
||
operationId: allocateEntry
|
||
summary: Решение по строке сводной ОСВ (vgo_assignment)
|
||
description: >
|
||
ASSIGN — отнести/перенести в правило; UNASSIGN — убрать из агрегата; EXCLUDE / INCLUDE — «Не включать в КФО» и снятие;
|
||
RESET — вернуть к автоматике. 409 — правки версии; 422 — ENTRY_NOT_IN_VERSION, RULE_NOT_APPLICABLE (detail.reason),
|
||
RULE_REQUIRED. Ответ — вся таблица.
|
||
x-permission: CONSOLIDATION_EDIT
|
||
parameters:
|
||
- $ref: '#/components/parameters/VersionId'
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/AllocationIn' }
|
||
responses:
|
||
'200':
|
||
description: Пересчитанная таблица
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/CorrectionTableOut' }
|
||
'401': { $ref: '#/components/responses/Unauthorized' }
|
||
'403': { $ref: '#/components/responses/Forbidden' }
|
||
'404': { $ref: '#/components/responses/DomainError' }
|
||
'409': { $ref: '#/components/responses/DomainError' }
|
||
'422': { $ref: '#/components/responses/DomainOrValidationError' }
|
||
|
||
/consolidations/versions/{version_id}/corrections/export:
|
||
get:
|
||
operationId: exportCorrectionTable
|
||
summary: Выгрузка таблицы корректировок в xlsx
|
||
description: Аудит EXPORT.
|
||
x-permission: CONSOLIDATION_VIEW
|
||
parameters:
|
||
- $ref: '#/components/parameters/VersionId'
|
||
- $ref: '#/components/parameters/StatementTypeQuery'
|
||
responses:
|
||
'200':
|
||
description: Файл xlsx
|
||
content:
|
||
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:
|
||
schema: { type: string, format: binary }
|
||
'401': { $ref: '#/components/responses/Unauthorized' }
|
||
'403': { $ref: '#/components/responses/Forbidden' }
|
||
'404': { $ref: '#/components/responses/DomainError' }
|
||
|
||
components:
|
||
securitySchemes:
|
||
bearerAuth: { type: http, scheme: bearer, bearerFormat: JWT }
|
||
|
||
parameters:
|
||
VersionId:
|
||
name: version_id
|
||
in: path
|
||
required: true
|
||
schema: { type: string, format: uuid }
|
||
RuleId:
|
||
name: rule_id
|
||
in: path
|
||
required: true
|
||
schema: { type: string, format: uuid }
|
||
Company1Id:
|
||
name: company_1_id
|
||
in: path
|
||
required: true
|
||
description: Компания стороны 1 правила (дебетовая сторона агрегата)
|
||
schema: { type: string, format: uuid }
|
||
Company2Id:
|
||
name: company_2_id
|
||
in: path
|
||
required: true
|
||
description: Компания стороны 2 правила (кредитовая сторона агрегата)
|
||
schema: { type: string, format: uuid }
|
||
StatementTypeQuery:
|
||
name: statement_type
|
||
in: query
|
||
required: false
|
||
schema: { $ref: '#/components/schemas/StatementType' }
|
||
|
||
responses:
|
||
Unauthorized:
|
||
description: Нет или плохой токен
|
||
Forbidden:
|
||
description: Нет права
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties: { detail: { type: string } }
|
||
DomainError:
|
||
description: Доменная ошибка
|
||
content:
|
||
application/json:
|
||
schema: { $ref: '#/components/schemas/DomainErrorOut' }
|
||
DomainOrValidationError:
|
||
description: Доменная ошибка или массив ошибок валидации pydantic
|
||
content:
|
||
application/json:
|
||
schema:
|
||
oneOf:
|
||
- $ref: '#/components/schemas/DomainErrorOut'
|
||
- type: object
|
||
properties:
|
||
detail:
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
loc: { type: array, items: {} }
|
||
msg: { type: string }
|
||
type: { type: string }
|
||
|
||
schemas:
|
||
Money:
|
||
type: string
|
||
pattern: '^-?\d+\.\d{2}$'
|
||
description: Сумма в рублях, два знака
|
||
examples: ['1000000.00']
|
||
MoneyIn:
|
||
type: string
|
||
pattern: '^\d{1,16}(\.\d{1,2})?$'
|
||
description: Неотрицательная сумма в рублях, не больше 16 цифр до запятой и двух после (numeric(18,2))
|
||
Percent:
|
||
type: [string, 'null']
|
||
pattern: '^\d+\.\d{2}$'
|
||
description: Процент с двумя знаками; null — не определён
|
||
StatementType:
|
||
type: string
|
||
enum: [balance, income]
|
||
default: balance
|
||
Side:
|
||
type: string
|
||
enum: [D, K]
|
||
Ref:
|
||
type: object
|
||
required: [id, name]
|
||
properties:
|
||
id: { type: string, format: uuid }
|
||
name: { type: string }
|
||
|
||
DomainErrorOut:
|
||
type: object
|
||
required: [detail]
|
||
properties:
|
||
detail:
|
||
type: object
|
||
required: [code, message]
|
||
properties:
|
||
code:
|
||
type: string
|
||
enum:
|
||
- NOT_FOUND
|
||
- VERSION_NOT_EDITABLE
|
||
- LOCK_REQUIRED
|
||
- LOCKED_BY_OTHER
|
||
- CORRECTION_VERSION_CONFLICT
|
||
- CORRECTION_NOT_APPLIED
|
||
- COMMENT_REQUIRED
|
||
- EMPTY_CORRECTION
|
||
- INVALID_LINE_CODE
|
||
- ENTRY_NOT_IN_VERSION
|
||
- RULE_NOT_APPLICABLE
|
||
- RULE_REQUIRED
|
||
message: { type: string, description: Текст по-русски }
|
||
additionalProperties: true
|
||
|
||
CorrectionTableOut:
|
||
type: object
|
||
required: [version_id, statement_type, rules_version_no, editable, read_only_reason, rules, aggregates, unallocated, totals]
|
||
properties:
|
||
version_id: { type: string, format: uuid }
|
||
statement_type: { $ref: '#/components/schemas/StatementType' }
|
||
rules_version_no: { type: integer }
|
||
editable: { type: boolean, description: Может ли текущий пользователь править таблицу }
|
||
read_only_reason:
|
||
type: [string, 'null']
|
||
enum: [VERSION_NOT_EDITABLE, LOCK_REQUIRED, LOCKED_BY_OTHER, null]
|
||
rules:
|
||
type: array
|
||
items: { $ref: '#/components/schemas/CorrectionRuleOut' }
|
||
aggregates:
|
||
type: array
|
||
items: { $ref: '#/components/schemas/CorrectionAggregateOut' }
|
||
unallocated:
|
||
type: array
|
||
items: { $ref: '#/components/schemas/UnallocatedEntryOut' }
|
||
totals: { $ref: '#/components/schemas/CorrectionTotalsOut' }
|
||
|
||
CorrectionRuleOut:
|
||
type: object
|
||
required: [id, rule_no, article_1_line, article_2_line, side_1, measure, company_1_accounts, company_2_accounts, company_1_excluded, company_2_excluded]
|
||
properties:
|
||
id: { type: string, format: uuid }
|
||
rule_no: { type: integer }
|
||
article_1_line: { type: string, description: 'Код статьи; составная — коды через «/» ("1450/1550")' }
|
||
article_2_line: { type: string, description: 'Код статьи; составная — коды через «/» ("1310/1350")' }
|
||
side_1: { $ref: '#/components/schemas/Side' }
|
||
measure: { type: string, enum: [closing, turnover] }
|
||
company_1_accounts: { type: array, items: { type: string }, description: Коды счетов стороны 1 }
|
||
company_2_accounts: { type: array, items: { type: string } }
|
||
company_1_excluded: { type: array, items: { type: string } }
|
||
company_2_excluded: { type: array, items: { type: string } }
|
||
|
||
CorrectionAggregateOut:
|
||
type: object
|
||
required: [rule_id, rule_no, company_1, company_2, side_1, side_2, deviation_amount, deviation_percent, exceeds_threshold, status, eliminated_amount, composition_changed, correction]
|
||
properties:
|
||
rule_id: { type: string, format: uuid }
|
||
rule_no: { type: integer }
|
||
company_1: { $ref: '#/components/schemas/Ref' }
|
||
company_2: { $ref: '#/components/schemas/Ref' }
|
||
side_1: { $ref: '#/components/schemas/AggregateSideOut' }
|
||
side_2: { $ref: '#/components/schemas/AggregateSideOut' }
|
||
deviation_amount: { $ref: '#/components/schemas/Money' }
|
||
deviation_percent: { $ref: '#/components/schemas/Percent' }
|
||
exceeds_threshold:
|
||
type: boolean
|
||
description: deviation_percent ≥ 5, или одна сторона пуста при ненулевой другой
|
||
status: { type: string, enum: [AUTO, DISCREPANCY, ONE_SIDED, MANUAL] }
|
||
eliminated_amount: { $ref: '#/components/schemas/Money' }
|
||
composition_changed: { type: boolean, description: Состав или суммы изменились после применённой корректировки }
|
||
correction:
|
||
oneOf:
|
||
- $ref: '#/components/schemas/CorrectionOut'
|
||
- type: 'null'
|
||
|
||
AggregateSideOut:
|
||
type: object
|
||
required: [article_line, accounts, amount, entries]
|
||
properties:
|
||
article_line: { type: string }
|
||
accounts: { type: array, items: { type: string }, description: Коды счетов строк, вошедших в сторону }
|
||
amount: { $ref: '#/components/schemas/Money' }
|
||
entries:
|
||
type: array
|
||
items: { $ref: '#/components/schemas/CorrectionEntryOut' }
|
||
|
||
CorrectionEntryOut:
|
||
type: object
|
||
required: [entry_ids, legal_entity_id, counterparty_id, account_code, side, amount, decision, excluded, candidate_rule_ids]
|
||
properties:
|
||
entry_ids: { type: array, items: { type: string, format: uuid }, minItems: 1 }
|
||
legal_entity_id: { type: string, format: uuid }
|
||
counterparty_id: { type: string, format: uuid }
|
||
account_code: { type: string }
|
||
side: { $ref: '#/components/schemas/Side' }
|
||
amount: { $ref: '#/components/schemas/Money' }
|
||
decision: { type: string, enum: [AUTO, ASSIGN] }
|
||
excluded: { type: boolean }
|
||
candidate_rule_ids:
|
||
type: array
|
||
items: { type: string, format: uuid }
|
||
description: Другие правила, под которые строка подходит по счёту и стороне
|
||
|
||
UnallocatedEntryOut:
|
||
type: object
|
||
required: [entry_ids, owner, counterparty, account_code, side, amount, candidate_rule_ids, reason, excluded]
|
||
properties:
|
||
entry_ids: { type: array, items: { type: string, format: uuid }, minItems: 1 }
|
||
owner: { $ref: '#/components/schemas/Ref' }
|
||
counterparty: { $ref: '#/components/schemas/Ref' }
|
||
account_code: { type: string }
|
||
side: { $ref: '#/components/schemas/Side' }
|
||
amount: { $ref: '#/components/schemas/Money' }
|
||
candidate_rule_ids: { type: array, items: { type: string, format: uuid } }
|
||
reason: { type: string, enum: [MULTIPLE_RULES, UNASSIGNED] }
|
||
excluded: { type: boolean }
|
||
|
||
CorrectionOut:
|
||
type: object
|
||
required: [status, version_no, debit, credit, comment, reason_code, balance, updated_by, updated_at]
|
||
properties:
|
||
status: { type: string, enum: [DRAFT, APPLIED] }
|
||
version_no: { type: integer }
|
||
debit:
|
||
type: array
|
||
items: { $ref: '#/components/schemas/CorrectionLineOut' }
|
||
description: Статьи стороны 1, первая — основная
|
||
credit:
|
||
type: array
|
||
items: { $ref: '#/components/schemas/CorrectionLineOut' }
|
||
description: Статьи стороны 2, первая — основная
|
||
comment: { type: string }
|
||
reason_code: { type: [string, 'null'] }
|
||
balance: { $ref: '#/components/schemas/CorrectionBalanceOut' }
|
||
updated_by:
|
||
oneOf:
|
||
- $ref: '#/components/schemas/Ref'
|
||
- type: 'null'
|
||
updated_at: { type: string, format: date-time }
|
||
|
||
CorrectionLineOut:
|
||
type: object
|
||
required: [line_code, amount]
|
||
properties:
|
||
line_code: { type: string }
|
||
amount: { $ref: '#/components/schemas/Money' }
|
||
|
||
CorrectionBalanceOut:
|
||
type: object
|
||
required: [debit_total, credit_total, difference, difference_percent, remainder_1, remainder_2]
|
||
properties:
|
||
debit_total: { $ref: '#/components/schemas/Money' }
|
||
credit_total: { $ref: '#/components/schemas/Money' }
|
||
difference: { $ref: '#/components/schemas/Money' }
|
||
difference_percent: { $ref: '#/components/schemas/Percent' }
|
||
remainder_1: { $ref: '#/components/schemas/Money', description: Сумма стороны 1 минус debit_total }
|
||
remainder_2: { $ref: '#/components/schemas/Money', description: Сумма стороны 2 минус credit_total }
|
||
|
||
CorrectionTotalsOut:
|
||
type: object
|
||
required: [amount_1, amount_2, deviation_amount, correction_debit, correction_credit]
|
||
properties:
|
||
amount_1: { $ref: '#/components/schemas/Money' }
|
||
amount_2: { $ref: '#/components/schemas/Money' }
|
||
deviation_amount: { $ref: '#/components/schemas/Money' }
|
||
correction_debit: { $ref: '#/components/schemas/Money' }
|
||
correction_credit: { $ref: '#/components/schemas/Money' }
|
||
|
||
CorrectionLineIn:
|
||
type: object
|
||
required: [line_code, amount]
|
||
properties:
|
||
line_code: { type: string, minLength: 1 }
|
||
amount: { $ref: '#/components/schemas/MoneyIn' }
|
||
|
||
CorrectionIn:
|
||
type: object
|
||
required: [debit, credit, comment, expected_version_no]
|
||
properties:
|
||
debit: { type: array, items: { $ref: '#/components/schemas/CorrectionLineIn' } }
|
||
credit: { type: array, items: { $ref: '#/components/schemas/CorrectionLineIn' } }
|
||
comment: { type: string, maxLength: 1000 }
|
||
reason_code: { type: [string, 'null'] }
|
||
status: { type: string, enum: [APPLIED, DRAFT], default: APPLIED }
|
||
expected_version_no:
|
||
type: [integer, 'null']
|
||
description: >
|
||
Редакция действующей (DRAFT/APPLIED) корректировки, которую видел аналитик; null — действующей нет
|
||
(в том числе после отзыва). Несовпадение — 409 CORRECTION_VERSION_CONFLICT.
|
||
|
||
CorrectionRevokeIn:
|
||
type: object
|
||
required: [comment]
|
||
properties:
|
||
comment: { type: string, maxLength: 1000 }
|
||
|
||
AllocationIn:
|
||
type: object
|
||
required: [statement_type, entry_ids, side, action]
|
||
properties:
|
||
statement_type: { $ref: '#/components/schemas/StatementType' }
|
||
entry_ids: { type: array, items: { type: string, format: uuid }, minItems: 1 }
|
||
side:
|
||
$ref: '#/components/schemas/Side'
|
||
description: Позиция строки — дебетовая или кредитовая (строка с обоими сальдо даёт две позиции)
|
||
action: { type: string, enum: [ASSIGN, UNASSIGN, EXCLUDE, INCLUDE, RESET] }
|
||
rule_id: { type: [string, 'null'], format: uuid, description: Обязателен для ASSIGN }
|