feat(matrix): понятное имя и реквизиты правила в XLSX пресета
This commit is contained in:
parent
b078933428
commit
9e39c4d426
@ -875,7 +875,8 @@ API: список счетов `GET /directories/accounts`, правила ве
|
|||||||
`…/preset/{rule_id}/unpaired-osv`.
|
`…/preset/{rule_id}/unpaired-osv`.
|
||||||
`GET …/elimination-rules` отдаёт объект `{version_no, is_version_fixed, rules}` (у правила есть `id`): при незаданной
|
`GET …/elimination-rules` отдаёт объект `{version_no, is_version_fixed, rules}` (у правила есть `id`): при незаданной
|
||||||
версии правил — открытые правила (`valid_to IS NULL`) с максимальным `version_no`; ответ сообщает этот номер. Ошибки PRESET: 422 `RULE_REQUIRED`, `INVALID_RULE`,
|
версии правил — открытые правила (`valid_to IS NULL`) с максимальным `version_no`; ответ сообщает этот номер. Ошибки PRESET: 422 `RULE_REQUIRED`, `INVALID_RULE`,
|
||||||
`ACCOUNTS_REQUIRED`; правило недействительное для версии или несуществующее — 404. В шапке xlsx — строка «Показатель»,
|
`ACCOUNTS_REQUIRED`; правило недействительное для версии или несуществующее — 404. Имя XLSX пресета оканчивается на
|
||||||
|
`_rule-balance-<rule_no>.xlsx`; в шапке — номер, статьи и версия правила, строка «Показатель»,
|
||||||
audit `EXPORT` пишет `mode` (`ALL`/`MANUAL`/`PRESET`), для PRESET — `rule_id`, `rule_no` и фактический
|
audit `EXPORT` пишет `mode` (`ALL`/`MANUAL`/`PRESET`), для PRESET — `rule_id`, `rule_no` и фактический
|
||||||
`rule_version_no`. Открытые вопросы по пресетам —
|
`rule_version_no`. Открытые вопросы по пресетам —
|
||||||
`openspec/changes/intercompany-matrix-presets/design.md`. Контракт для фронта —
|
`openspec/changes/intercompany-matrix-presets/design.md`. Контракт для фронта —
|
||||||
|
|||||||
@ -103,10 +103,32 @@ class MatrixExportInfo:
|
|||||||
rule_id: uuid.UUID | None = None
|
rule_id: uuid.UUID | None = None
|
||||||
rule_no: int | None = None
|
rule_no: int | None = None
|
||||||
rule_version_no: int | None = None
|
rule_version_no: int | None = None
|
||||||
|
rule_statement_type: StatementType | None = None
|
||||||
|
article_1_line: str | None = None
|
||||||
|
article_2_line: str | None = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def preset_details(self) -> tuple[StatementType, int, int, str, str]:
|
||||||
|
details = (
|
||||||
|
self.rule_statement_type,
|
||||||
|
self.rule_no,
|
||||||
|
self.rule_version_no,
|
||||||
|
self.article_1_line,
|
||||||
|
self.article_2_line,
|
||||||
|
)
|
||||||
|
if any(value is None for value in details):
|
||||||
|
raise ValueError("Для выгрузки PRESET нужны тип, номер, версия и статьи правила")
|
||||||
|
statement_type, rule_no, rule_version_no, article_1_line, article_2_line = details
|
||||||
|
assert statement_type is not None and rule_no is not None and rule_version_no is not None
|
||||||
|
assert article_1_line is not None and article_2_line is not None
|
||||||
|
return statement_type, rule_no, rule_version_no, article_1_line, article_2_line
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def filename(self) -> str:
|
def filename(self) -> str:
|
||||||
preset = f"_preset-{self.rule_id}" if self.rule_id is not None else ""
|
preset = ""
|
||||||
|
if self.mode == "PRESET":
|
||||||
|
statement_type, rule_no, _, _, _ = self.preset_details
|
||||||
|
preset = f"_rule-{statement_type.value}-{rule_no}"
|
||||||
return f"intercompany-matrix_{self.group_name}_{self.year}-{self.quarter.value}_v{self.version_no}{preset}.xlsx"
|
return f"intercompany-matrix_{self.group_name}_{self.year}-{self.quarter.value}_v{self.version_no}{preset}.xlsx"
|
||||||
|
|
||||||
|
|
||||||
@ -400,6 +422,9 @@ class IntercompanyMatrixService:
|
|||||||
rule_id=result.rule.id if result.rule is not None else None,
|
rule_id=result.rule.id if result.rule is not None else None,
|
||||||
rule_no=result.rule.rule_no if result.rule is not None else None,
|
rule_no=result.rule.rule_no if result.rule is not None else None,
|
||||||
rule_version_no=result.rule.version_no if result.rule is not None else None,
|
rule_version_no=result.rule.version_no if result.rule is not None else None,
|
||||||
|
rule_statement_type=result.rule.statement_type if result.rule is not None else None,
|
||||||
|
article_1_line=result.rule.article_1_line if result.rule is not None else None,
|
||||||
|
article_2_line=result.rule.article_2_line if result.rule is not None else None,
|
||||||
)
|
)
|
||||||
return result.matrix, info
|
return result.matrix, info
|
||||||
|
|
||||||
|
|||||||
@ -12,8 +12,6 @@ from src.services.intercompany_matrix import MatrixExportInfo
|
|||||||
from src.services.osv_export import MONEY_FORMAT
|
from src.services.osv_export import MONEY_FORMAT
|
||||||
|
|
||||||
METRICS = ("Разница", "Дебет", "Кредит", "Расхождение, %")
|
METRICS = ("Разница", "Дебет", "Кредит", "Расхождение, %")
|
||||||
TABLE_HEADER_ROW = 10
|
|
||||||
DATA_START_ROW = TABLE_HEADER_ROW + 1
|
|
||||||
PERCENT_FORMAT = '0.00"%"'
|
PERCENT_FORMAT = '0.00"%"'
|
||||||
THRESHOLD_FILL = PatternFill("solid", fgColor="FCE8E6")
|
THRESHOLD_FILL = PatternFill("solid", fgColor="FCE8E6")
|
||||||
THRESHOLD_FONT = Font(color="9C0006", bold=True)
|
THRESHOLD_FONT = Font(color="9C0006", bold=True)
|
||||||
@ -33,10 +31,10 @@ def _skipped_comment(sheet: Worksheet, row: int, column: int, count: int) -> Non
|
|||||||
sheet.cell(row, column).comment = Comment(f"Пропущено строк ОСВ без сопоставленного счёта: {count}", "АРМ КА")
|
sheet.cell(row, column).comment = Comment(f"Пропущено строк ОСВ без сопоставленного счёта: {count}", "АРМ КА")
|
||||||
|
|
||||||
|
|
||||||
def _write_context(sheet: Worksheet, info: MatrixExportInfo) -> None:
|
def _write_context(sheet: Worksheet, info: MatrixExportInfo) -> int:
|
||||||
_text(sheet, 1, 1, "Шахматка внутригрупповых операций")
|
_text(sheet, 1, 1, "Шахматка внутригрупповых операций")
|
||||||
mode_name = {"ALL": "Все счета", "MANUAL": "Выбранные счета", "PRESET": "Пресет"}[info.mode]
|
mode_name = {"ALL": "Все счета", "MANUAL": "Выбранные счета", "PRESET": "Пресет"}[info.mode]
|
||||||
details = (
|
details = [
|
||||||
("ГК", info.group_name),
|
("ГК", info.group_name),
|
||||||
("Период", f"{info.year}-{info.quarter.value}"),
|
("Период", f"{info.year}-{info.quarter.value}"),
|
||||||
("Версия", f"v{info.version_no}"),
|
("Версия", f"v{info.version_no}"),
|
||||||
@ -45,11 +43,23 @@ def _write_context(sheet: Worksheet, info: MatrixExportInfo) -> None:
|
|||||||
("Счета дебета", ", ".join(info.debit_account_codes) if info.mode != "ALL" else "Все счета"),
|
("Счета дебета", ", ".join(info.debit_account_codes) if info.mode != "ALL" else "Все счета"),
|
||||||
("Счета кредита", ", ".join(info.credit_account_codes) if info.mode != "ALL" else "Все счета"),
|
("Счета кредита", ", ".join(info.credit_account_codes) if info.mode != "ALL" else "Все счета"),
|
||||||
("Показатель", "Сальдо" if info.measure == "closing" else "Оборот"),
|
("Показатель", "Сальдо" if info.measure == "closing" else "Оборот"),
|
||||||
)
|
]
|
||||||
|
if info.mode == "PRESET":
|
||||||
|
statement_type, rule_no, rule_version_no, article_1_line, article_2_line = info.preset_details
|
||||||
|
rule_type = "ББ" if statement_type.value == "balance" else "ОФР"
|
||||||
|
details.extend(
|
||||||
|
(
|
||||||
|
("Правило", f"{rule_type} № {rule_no}"),
|
||||||
|
("Статья А", article_1_line),
|
||||||
|
("Статья Б", article_2_line),
|
||||||
|
("Версия правил", f"v{rule_version_no}"),
|
||||||
|
)
|
||||||
|
)
|
||||||
for row, (label, value) in enumerate(details, start=2):
|
for row, (label, value) in enumerate(details, start=2):
|
||||||
_text(sheet, row, 1, label)
|
_text(sheet, row, 1, label)
|
||||||
_text(sheet, row, 2, value)
|
_text(sheet, row, 2, value)
|
||||||
sheet.cell(row, 1).font = Font(bold=True)
|
sheet.cell(row, 1).font = Font(bold=True)
|
||||||
|
return len(details) + 2
|
||||||
|
|
||||||
|
|
||||||
def _write_values(sheet: Worksheet, row: int, column: int, cell: IntercompanyMatrixCellOut) -> None:
|
def _write_values(sheet: Worksheet, row: int, column: int, cell: IntercompanyMatrixCellOut) -> None:
|
||||||
@ -65,24 +75,25 @@ def build_matrix_workbook(matrix: IntercompanyMatrixOut, info: MatrixExportInfo)
|
|||||||
sheet = workbook.active
|
sheet = workbook.active
|
||||||
assert sheet is not None
|
assert sheet is not None
|
||||||
sheet.title = "Шахматка"
|
sheet.title = "Шахматка"
|
||||||
_write_context(sheet, info)
|
table_header_row = _write_context(sheet, info)
|
||||||
_text(sheet, TABLE_HEADER_ROW, 1, "Дебет ↓ / Кредит →")
|
data_start_row = table_header_row + 1
|
||||||
_text(sheet, TABLE_HEADER_ROW, 2, "Показатель")
|
_text(sheet, table_header_row, 1, "Дебет ↓ / Кредит →")
|
||||||
|
_text(sheet, table_header_row, 2, "Показатель")
|
||||||
sheet.column_dimensions["A"].width = 32
|
sheet.column_dimensions["A"].width = 32
|
||||||
sheet.column_dimensions["B"].width = 40
|
sheet.column_dimensions["B"].width = 40
|
||||||
|
|
||||||
for column, company in enumerate(matrix.companies, start=3):
|
for column, company in enumerate(matrix.companies, start=3):
|
||||||
_text(sheet, TABLE_HEADER_ROW, column, company.name)
|
_text(sheet, table_header_row, column, company.name)
|
||||||
sheet.column_dimensions[get_column_letter(column)].width = 26
|
sheet.column_dimensions[get_column_letter(column)].width = 26
|
||||||
sheet.row_dimensions[TABLE_HEADER_ROW].height = 36
|
sheet.row_dimensions[table_header_row].height = 36
|
||||||
for header in sheet[TABLE_HEADER_ROW]:
|
for header in sheet[table_header_row]:
|
||||||
header.font = Font(bold=True)
|
header.font = Font(bold=True)
|
||||||
header.fill = HEADER_FILL
|
header.fill = HEADER_FILL
|
||||||
header.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
|
header.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
|
||||||
|
|
||||||
cells = {(cell.company_a_id, cell.company_b_id): cell for cell in matrix.cells}
|
cells = {(cell.company_a_id, cell.company_b_id): cell for cell in matrix.cells}
|
||||||
for company_index, debit_company in enumerate(matrix.companies):
|
for company_index, debit_company in enumerate(matrix.companies):
|
||||||
start_row = DATA_START_ROW + company_index * len(METRICS)
|
start_row = data_start_row + company_index * len(METRICS)
|
||||||
end_row = start_row + len(METRICS) - 1
|
end_row = start_row + len(METRICS) - 1
|
||||||
sheet.merge_cells(start_row=start_row, start_column=1, end_row=end_row, end_column=1)
|
sheet.merge_cells(start_row=start_row, start_column=1, end_row=end_row, end_column=1)
|
||||||
_text(sheet, start_row, 1, debit_company.name)
|
_text(sheet, start_row, 1, debit_company.name)
|
||||||
@ -113,7 +124,7 @@ def build_matrix_workbook(matrix: IntercompanyMatrixOut, info: MatrixExportInfo)
|
|||||||
marked.fill = THRESHOLD_FILL
|
marked.fill = THRESHOLD_FILL
|
||||||
marked.font = THRESHOLD_FONT
|
marked.font = THRESHOLD_FONT
|
||||||
|
|
||||||
sheet.freeze_panes = f"C{DATA_START_ROW}"
|
sheet.freeze_panes = f"C{data_start_row}"
|
||||||
buffer = BytesIO()
|
buffer = BytesIO()
|
||||||
workbook.save(buffer)
|
workbook.save(buffer)
|
||||||
return buffer.getvalue()
|
return buffer.getvalue()
|
||||||
|
|||||||
@ -166,14 +166,18 @@ async def test_preset_uses_rule_sides_and_exclusions(auth_client: AuthClientFact
|
|||||||
f"/consolidations/versions/{world.version.id}/intercompany-matrix/preset/{rule.id}/export"
|
f"/consolidations/versions/{world.version.id}/intercompany-matrix/preset/{rule.id}/export"
|
||||||
)
|
)
|
||||||
assert exported.status_code == OK, exported.text
|
assert exported.status_code == OK, exported.text
|
||||||
assert f"preset-{rule.id}" in unquote(exported.headers["content-disposition"])
|
assert f"_rule-balance-{rule.rule_no}.xlsx" in unquote(exported.headers["content-disposition"])
|
||||||
sheet = load_workbook(BytesIO(exported.content))["Шахматка"]
|
sheet = load_workbook(BytesIO(exported.content))["Шахматка"]
|
||||||
assert sheet["B6"].value == "Пресет"
|
assert sheet["B6"].value == "Пресет"
|
||||||
assert "76.07.5" not in sheet["B7"].value
|
assert "76.07.5" not in sheet["B7"].value
|
||||||
assert "76.07.1" not in sheet["B8"].value
|
assert "76.07.1" not in sheet["B8"].value
|
||||||
assert sheet["B9"].value == "Сальдо"
|
assert sheet["B9"].value == "Сальдо"
|
||||||
assert Decimal(str(sheet["D12"].value)) == Decimal("100")
|
assert sheet["B10"].value == f"ББ № {rule.rule_no}"
|
||||||
assert Decimal(str(sheet["D13"].value)) == Decimal("90")
|
assert sheet["B11"].value == rule.article_1_line
|
||||||
|
assert sheet["B12"].value == rule.article_2_line
|
||||||
|
assert sheet["B13"].value == f"v{rule.version_no}"
|
||||||
|
assert Decimal(str(sheet["D16"].value)) == Decimal("100")
|
||||||
|
assert Decimal(str(sheet["D17"].value)) == Decimal("90")
|
||||||
audit = (await seeded.scalars(select(AuditEvent).where(AuditEvent.action == AuditAction.EXPORT))).one()
|
audit = (await seeded.scalars(select(AuditEvent).where(AuditEvent.action == AuditAction.EXPORT))).one()
|
||||||
assert audit.after_value["mode"] == "PRESET"
|
assert audit.after_value["mode"] == "PRESET"
|
||||||
assert audit.after_value["rule_id"] == str(rule.id)
|
assert audit.after_value["rule_id"] == str(rule.id)
|
||||||
|
|||||||
@ -131,6 +131,8 @@ async def test_preset_applies_exclusions_to_its_own_side_and_rule_measure(monkey
|
|||||||
id=uuid.uuid4(),
|
id=uuid.uuid4(),
|
||||||
rule_no=6,
|
rule_no=6,
|
||||||
statement_type=StatementType.balance,
|
statement_type=StatementType.balance,
|
||||||
|
article_1_line="1230",
|
||||||
|
article_2_line="1520",
|
||||||
version_no=1,
|
version_no=1,
|
||||||
side_1="D",
|
side_1="D",
|
||||||
measure="turnover",
|
measure="turnover",
|
||||||
@ -168,6 +170,8 @@ async def test_preset_applies_exclusions_to_its_own_side_and_rule_measure(monkey
|
|||||||
assert info.measure == "turnover"
|
assert info.measure == "turnover"
|
||||||
assert info.rule_no == rule.rule_no
|
assert info.rule_no == rule.rule_no
|
||||||
assert info.rule_version_no == rule.version_no
|
assert info.rule_version_no == rule.version_no
|
||||||
|
assert info.article_1_line == rule.article_1_line
|
||||||
|
assert info.article_2_line == rule.article_2_line
|
||||||
|
|
||||||
|
|
||||||
async def test_preset_rejects_income_rule_before_selecting_accounts(monkeypatch: pytest.MonkeyPatch) -> None:
|
async def test_preset_rejects_income_rule_before_selecting_accounts(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
|||||||
@ -15,7 +15,7 @@ from src.api.endpoints.consolidations.intercompany_matrix import (
|
|||||||
export_intercompany_matrix_preset,
|
export_intercompany_matrix_preset,
|
||||||
router,
|
router,
|
||||||
)
|
)
|
||||||
from src.models.enums import AuditAction, AuditSection, PeriodQuarter
|
from src.models.enums import AuditAction, AuditSection, PeriodQuarter, StatementType
|
||||||
from src.schemas.consolidations import (
|
from src.schemas.consolidations import (
|
||||||
IntercompanyMatrixCellOut,
|
IntercompanyMatrixCellOut,
|
||||||
IntercompanyMatrixCompanyOut,
|
IntercompanyMatrixCompanyOut,
|
||||||
@ -39,6 +39,9 @@ def _info(*, mode: str = "MANUAL", rule_id: UUID | None = None) -> MatrixExportI
|
|||||||
rule_id=rule_id,
|
rule_id=rule_id,
|
||||||
rule_no=6 if mode == "PRESET" else None,
|
rule_no=6 if mode == "PRESET" else None,
|
||||||
rule_version_no=3 if mode == "PRESET" else None,
|
rule_version_no=3 if mode == "PRESET" else None,
|
||||||
|
rule_statement_type=StatementType.balance if mode == "PRESET" else None,
|
||||||
|
article_1_line="1230" if mode == "PRESET" else None,
|
||||||
|
article_2_line="1520" if mode == "PRESET" else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -194,6 +197,15 @@ async def test_export_routes_use_matrix_calculation() -> None:
|
|||||||
assert preset_sheet["B7"].value == "62.01"
|
assert preset_sheet["B7"].value == "62.01"
|
||||||
assert preset_sheet["B8"].value == "60.01"
|
assert preset_sheet["B8"].value == "60.01"
|
||||||
assert preset_sheet["B9"].value == "Оборот"
|
assert preset_sheet["B9"].value == "Оборот"
|
||||||
|
assert [(preset_sheet.cell(row, 1).value, preset_sheet.cell(row, 2).value) for row in range(10, 14)] == [
|
||||||
|
("Правило", "ББ № 6"),
|
||||||
|
("Статья А", "1230"),
|
||||||
|
("Статья Б", "1520"),
|
||||||
|
("Версия правил", "v3"),
|
||||||
|
]
|
||||||
|
assert preset_sheet["A14"].value == "Дебет ↓ / Кредит →"
|
||||||
|
assert preset_sheet.freeze_panes == "C15"
|
||||||
|
assert _info(mode="PRESET", rule_id=account_id).filename.endswith("_rule-balance-6.xlsx")
|
||||||
assert [call.kwargs for call in audit.record.await_args_list] == [
|
assert [call.kwargs for call in audit.record.await_args_list] == [
|
||||||
{
|
{
|
||||||
"action": AuditAction.EXPORT,
|
"action": AuditAction.EXPORT,
|
||||||
|
|||||||
@ -24,8 +24,9 @@ change не входит. Шахматка — чистая функция от
|
|||||||
5. `unpaired-osv` строится из `one_sided`-ячеек шахматки пресета (`osv_entry_ids_*`) и возвращает строки сводной ОСВ,
|
5. `unpaired-osv` строится из `one_sided`-ячеек шахматки пресета (`osv_entry_ids_*`) и возвращает строки сводной ОСВ,
|
||||||
содержащие эти записи.
|
содержащие эти записи.
|
||||||
6. `GET …/elimination-rules` отдаёт объект с основанием выбора версии; `as_of` не вводится.
|
6. `GET …/elimination-rules` отдаёт объект с основанием выбора версии; `as_of` не вводится.
|
||||||
7. xlsx пресета: имя `intercompany-matrix_<ГК>_<год>-<квартал>_v<n>_preset-<rule_id>.xlsx`, в шапке «Режим = Пресет»,
|
7. xlsx пресета: имя `intercompany-matrix_<ГК>_<год>-<квартал>_v<n>_rule-balance-<rule_no>.xlsx`, в шапке «Режим = Пресет»,
|
||||||
коды счетов сторон (отсортированы) и строка «Показатель» (Сальдо/Оборот) для всех режимов.
|
номер правила, статьи сторон, фактическая версия правил, коды счетов сторон (отсортированы) и строка «Показатель»
|
||||||
|
(Сальдо/Оборот) для всех режимов.
|
||||||
|
|
||||||
## Решения владельца (2026-10-21, ревью PR #141)
|
## Решения владельца (2026-10-21, ревью PR #141)
|
||||||
|
|
||||||
@ -49,10 +50,6 @@ change не входит. Шахматка — чистая функция от
|
|||||||
|
|
||||||
После выноса п. 3–4 автором соответствующие требования дельты `consolidated-osv` удаляются из change.
|
После выноса п. 3–4 автором соответствующие требования дельты `consolidated-osv` удаляются из change.
|
||||||
|
|
||||||
## Known issues
|
|
||||||
|
|
||||||
1. Имя файла содержит UUID правила, шапка xlsx не содержит номер правила и версию.
|
|
||||||
|
|
||||||
## Decisions made without discussion
|
## Decisions made without discussion
|
||||||
|
|
||||||
- 2026-10-21. Change пишет ревьюер в ветке автора по решению владельца. Обоснование: автор не оформила OpenSpec. Цена
|
- 2026-10-21. Change пишет ревьюер в ветке автора по решению владельца. Обоснование: автор не оформила OpenSpec. Цена
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
- `GET …/elimination-rules` возвращает объект `{version_no, is_version_fixed, rules}` вместо массива; у правила есть
|
- `GET …/elimination-rules` возвращает объект `{version_no, is_version_fixed, rules}` вместо массива; у правила есть
|
||||||
`id`. Для версии без зафиксированного `elimination_rules_version` отдаются открытые правила (`valid_to IS NULL`)
|
`id`. Для версии без зафиксированного `elimination_rules_version` отдаются открытые правила (`valid_to IS NULL`)
|
||||||
с максимальным `version_no`; ответ содержит этот номер.
|
с максимальным `version_no`; ответ содержит этот номер.
|
||||||
- xlsx: для PRESET имя файла с `_preset-<rule_id>`, в шапке строка «Показатель»; audit `EXPORT` пишет `mode`
|
- xlsx: для PRESET имя файла с `_rule-balance-<rule_no>`, в шапке номер, статьи, версия правила и «Показатель»; audit `EXPORT` пишет `mode`
|
||||||
`ALL`/`MANUAL`/`PRESET`, а для PRESET также `rule_id`, `rule_no` и фактический `rule_version_no`.
|
`ALL`/`MANUAL`/`PRESET`, а для PRESET также `rule_id`, `rule_no` и фактический `rule_version_no`.
|
||||||
|
|
||||||
## Capabilities
|
## Capabilities
|
||||||
|
|||||||
@ -52,14 +52,14 @@
|
|||||||
|
|
||||||
### Requirement: XLSX шахматки пресета
|
### Requirement: XLSX шахматки пресета
|
||||||
`GET …/intercompany-matrix/preset/{rule_id}/export` SHALL возвращать XLSX с теми же строками, что JSON-расчёт
|
`GET …/intercompany-matrix/preset/{rule_id}/export` SHALL возвращать XLSX с теми же строками, что JSON-расчёт
|
||||||
пресета. Имя файла SHALL иметь вид `intercompany-matrix_<ГК>_<год>-<квартал>_v<n>_preset-<rule_id>.xlsx`. Шапка SHALL
|
пресета. Имя файла SHALL иметь вид `intercompany-matrix_<ГК>_<год>-<квартал>_v<n>_rule-balance-<rule_no>.xlsx`. Шапка SHALL
|
||||||
содержать режим «Пресет», коды счетов дебета и кредита правила и строку «Показатель» со значением «Сальдо» для
|
содержать режим «Пресет», коды счетов дебета и кредита правила, номер правила, статьи А и Б, фактическую версию правил
|
||||||
`closing` или «Оборот» для `turnover`. Строка «Показатель» SHALL присутствовать и в выгрузках ALL и MANUAL (значение
|
и строку «Показатель» со значением «Сальдо» для `closing` или «Оборот» для `turnover`. Строка «Показатель» SHALL присутствовать и в выгрузках ALL и MANUAL (значение
|
||||||
«Сальдо»).
|
«Сальдо»).
|
||||||
|
|
||||||
#### Scenario: Выгрузка по оборотам
|
#### Scenario: Выгрузка по оборотам
|
||||||
- **WHEN** пользователь выгружает пресет с `measure = turnover`
|
- **WHEN** пользователь выгружает пресет с `measure = turnover`
|
||||||
- **THEN** шапка содержит «Режим — Пресет» и «Показатель — Оборот», имя файла заканчивается на `_preset-<rule_id>.xlsx`
|
- **THEN** шапка содержит «Режим — Пресет», «Показатель — Оборот», номер, статьи и версию правила; имя файла заканчивается на `_rule-balance-<rule_no>.xlsx`
|
||||||
|
|
||||||
#### Scenario: Правило не найдено
|
#### Scenario: Правило не найдено
|
||||||
- **WHEN** `rule_id` не существует
|
- **WHEN** `rule_id` не существует
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user