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 }