36 KiB
Таблица корректировки (UI) — план реализации
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: вкладка «Таблица корректировки» страницы отчёта версии: таблица агрегатов ВГО со строками ОСВ, нераспределённые строки, решения по строкам, окно корректировки агрегата, экспорт — по дизайну 23.10 и аналитике Лысенко.
Architecture: только фронт web/. Данные — по контракту ../docs/dev/api-contract-corrections.md (+ .openapi.yaml);
пока бэкенда (AURORA-1540) нет, клиент services/api/corrections.ts при REACT_APP_CORRECTIONS_MOCK=true отвечает из фейка
в памяти. Вся логика (строки таблицы, фильтры, итоги, окно, черновик) — чистые функции с тестами vitest; компоненты тонкие и
проверяются в браузере против дизайна.
Tech Stack: React 18, TS strict, antd 6, react-router 7, vitest 3, biome (+ ESLint/Prettier npm run lint).
Spec: openspec/changes/corrections-table-ui/ — design.md (главное), proposal.md, specs/corrections-table-ui/spec.md,
дельта specs/intercompany-matrix-ui/spec.md. Контракт — /Users/andrey.epifancev/projects/awp-ca/.wt/corrections-ui/docs/dev/api-contract-corrections.md.
Дизайн — /Users/andrey.epifancev/projects/awp-ca/ДКБ_таблица_корректировок_23_10_26_1.html (SPA; открывать в браузере).
Ветка: feat/206-corrections-ui, worktree /Users/andrey.epifancev/projects/awp-ca/.wt/corrections-ui/arm-core, issue
RSHB/arm-core#206, Jira AURORA-1541.
Global Constraints
- Только
web/; бэкенд, DBML,api/не трогать. - Деньги — строки контракта в рублях; показ —
formatScaledAmount(utils/money.ts) черезuseNumberDisplay; сложение — толькоsumAmounts; никакихNumber(...)/parseFloatнад суммами. - Фронт не считает разнесение, отклонения, порог и статусы — берёт из ответа (
exceeds_threshold,deviation_*,status,candidate_rule_ids). Исключение — живой контроль в окне (суммы несохранённых статей) и «Итого» видимых строк. - Только
statement_type = 'balance'. - Тексты (дословно): вкладка «Таблица корректировки»; «Учитывать в КФО»; группы «Дебет», «Кредит», «Контроль»,
«Корректировка»; «Отклонение, %», «Отклонение, сумма»; «Корректировать»; «Корректировка операции»; «Исходные данные ОСВ»;
«Добавить строку ОСВ»; «Распределение корректировки»; «Добавить строку»; «Комментарий к корректировке»; «Отменить»;
«Сохранить корректировки»; «Отображение» — «Общий» / «По пресету»; «Правило»; «Тип записи» — «Все» / «Пары счетов» /
«Нераспределенные записи»; «Сбросить фильтры»; «Настройка таблицы»; «Экспорт»; «На весь экран»; «Итого»;
«Внутригрупповых остатков между компаниями периметра нет»; «Нет записей для выбранных фильтров»; «Состав изменился после
корректировки»; «черновик»; «Перенести в правило…»; «Отнести к правилу…»; «Убрать из правила»; «Вернуть к автоматике»;
«Отменить корректировку»; «Да» / «Частично» / «Нет»; «Восстановлены несохранённые правки от ЧЧ:ММ»; «Сбросить»;
«Корректировку изменили после ваших правок»; «Есть несохранённые изменения. Закрыть без сохранения?»; «Остаться»;
«Корректировку уже изменили, данные обновлены»; подсказки только чтения:
VERSION_NOT_EDITABLE— «Версия закрыта для изменений»,LOCK_REQUIRED— «Возьмите консолидацию в работу»,LOCKED_BY_OTHER— «Консолидацию редактирует другой пользователь». - Подпись правила:
Правило N: <статья 1> — <статья 2>, составная статья"1450/1550"показывается1450 / 1550. - Порог для красного в окне (живой контроль): процент к меньшей стороне
≥ 5или одна сторона 0 при ненулевой другой. localStorage/sessionStorage— только вtry/catch; недоступно — работа без черновика.- Стиль: табы, одинарные кавычки, ширина 140 (biome); комментарии в коде — по-русски; файлы виджета — в
web/src/widgets/CorrectionTable/. - Проверки перед коммитом задачи: из
web/—npm test,npx tsc --noEmit -p .,npx biome check src; в Task 7 — ещёnpm run buildиmake web-checkиз корня репозитория.
Review Focus
- Ответ на устаревший запрос (аналитик быстро щёлкнул два флажка) перетирает свежую таблицу —
useCorrectionTableприменяет только ответ последнего запроса (счётчик запросов, какusePagedRows); тест в Task 4 на функцию выбора ответа. - Сумма статьи «1 000 000,5» / «1000000,567» / «-5» / пусто в окне: принимается только
parseAmountInput-валидное ≥ 0 с ≤ 2 знаками, иначе поле с ошибкой и «Сохранить» неактивна — тест в Task 3 (validateLines). - Черновик из
localStorageот другой формы данных (старый формат, мусор) не ломает окно —loadDraftвозвращаетnull— тест в Task 3. - Агрегат исчез из ответа после переноса строк, пока окно открыто (все строки убраны) — окно показывает «—» по сторонам и
не падает;
findAggregateвозвращаетnull— тест в Task 2. - Фильтр «ЮЛ» с латиницей/регистром/кавычками («ооо "бск"») находит «ООО "БСК"» — сравнение без регистра — тест в Task 2.
Task 1: Типы контракта, клиент API, флаг фейка
Files:
- Create:
web/src/types/corrections.ts - Create:
web/src/services/api/corrections.ts - Modify:
web/src/conf/config.ts - Test:
web/src/services/api/corrections.test.ts
Interfaces:
-
Produces (
types/corrections.ts) — типы 1:1 сcomponents.schemasOpenAPI:StatementType,Side = 'D' | 'K',Ref,CorrectionTableOut,CorrectionRuleOut,CorrectionAggregateOut,AggregateSideOut,CorrectionEntryOut,UnallocatedEntryOut,CorrectionOut,CorrectionLineOut,CorrectionBalanceOut,CorrectionTotalsOut,CorrectionLineIn,CorrectionIn,CorrectionRevokeIn,AllocationAction = 'ASSIGN' | 'UNASSIGN' | 'EXCLUDE' | 'INCLUDE' | 'RESET',AllocationIn,ReadOnlyReason,AggregateKey = { rule_id: string; company_1_id: string; company_2_id: string }. -
Produces (
services/api/corrections.ts):getCorrectionTable(versionId: string): Promise<CorrectionTableOut>;saveCorrection(versionId: string, key: AggregateKey, body: CorrectionIn): Promise<CorrectionAggregateOut>;revokeCorrection(versionId: string, key: AggregateKey, body: CorrectionRevokeIn): Promise<CorrectionAggregateOut>;allocate(versionId: string, body: AllocationIn): Promise<CorrectionTableOut>;exportCorrectionTable(versionId: string): Promise<{ blob: Blob; filename: string | null }>(форма ответа — как уapiFetchBlob; если она другая — вернуть то, что даётapiFetchBlob, и записать в Interfaces);correctionsPath(versionId: string, key?: AggregateKey): string. ПриConfig.CORRECTIONS_MOCKвсе функции делегируют вcorrectionsFake(Task 2); флаг читается в рантайме, фейк остаётся в бандле — это допустимо (данные фейка — небольшие, без персональных данных). -
Produces (
conf/config.ts):Config.CORRECTIONS_MOCK: boolean=environment.REACT_APP_CORRECTIONS_MOCK === 'true'. -
Step 1: node_modules в worktree.
ln -s /Users/andrey.epifancev/projects/awp-ca/arm-core/web/node_modules web/node_modules(тот жеpackage-lock.json, что уmain; в git не попадает). Проверка:cd web && npx vitest --versionпечатает3.1.4. -
Step 2: Failing test
corrections.test.ts:correctionsPath('v1')→'/consolidations/versions/v1/corrections';correctionsPath('v1', {rule_id: 'r', company_1_id: 'a', company_2_id: 'b'})→'/consolidations/versions/v1/corrections/aggregates/r/a/b'. -
Step 3: Run
cd web && npx vitest run src/services/api/corrections.test.ts— FAIL (нет модуля). -
Step 4: Implement типы,
correctionsPath, функции поверхapi.get/put/postиapiFetchBlob(?statement_type=balanceв GET и export), флаг вConfig. До Task 2 ветка фейка —throw new Error('фейк в Task 2'). -
Step 5: Run тест — PASS;
npx tsc --noEmit -p .иnpx biome check src— чисто. -
Step 6: Commit
feat(web): типы и клиент API таблицы корректировок (#206).
Task 2: Фейк контракта и модель таблицы
Files:
- Create:
web/src/widgets/CorrectionTable/mockData.ts— данные из дизайна 23.10 - Create:
web/src/services/api/correctionsFake.ts, Test:web/src/services/api/correctionsFake.test.ts - Create:
web/src/widgets/CorrectionTable/tableModel.ts, Test:web/src/widgets/CorrectionTable/tableModel.test.ts - Modify:
web/src/services/api/corrections.ts(ветка фейка)
Interfaces:
-
Consumes: типы Task 1.
-
Produces (
correctionsFake.ts): объектcorrectionsFakeс теми же сигнатурами, что функции Task 1, состояние в памяти модуля,resetCorrectionsFake(): voidдля тестов. Правила фейка: агрегат пересчитывается из своих строк (суммы сторонsumAmountsпо не-excluded,deviation_amount,deviation_percentк меньшей с двумя знаками,exceeds_thresholdпри≥ 5или одна сторона 0),composition_changed = trueпри изменении состава агрегата с корректировкой;ASSIGN— только в правило изcandidate_rule_ids, иначеApiError422RULE_NOT_APPLICABLE; пустой комментарий — 422COMMENT_REQUIRED;expected_version_noне совпал — 409CORRECTION_VERSION_CONFLICT;revokeбез корректировки — 409CORRECTION_NOT_APPLIED;editable = true. Деление для процента — BigInt-копейки с округлением половины вверх.mockData.ts: 8 правил (какapi/seed/core/elimination_rules_v1.yaml, статьи1150/1450/1550,1170/1310/1350,1170/1410,1240/1510,1230/1510,1230/1520,1230/1410,1230/1510), агрегаты и нераспределённые строки из дизайна (суммы в рублях: «ЭПТ — ИЛС» 1000000.00 / 202320.00 с корректировкой 1230 / 1520 и комментарием «Требуется сверка с контрагентом»; «ООО "БЕКОВСКИЙ САХАРНЫЙ ЗАВОД" — БЕКОВСКИЙ САХАРНЫЙ КОМБИНАТ ООО» правила 6 — четыре строки с каждой стороны; правило 3 — «Пензенский Мир Продуктов — Регион-Сахар» 29742550.00 / 29242550.00; нераспределённые — не меньше пяти, включая кредитовую 76.05 «ООО "БСК"» → «ПМП ООО» 142878.00 сcandidate_rule_idsправил 6 и 7). -
Produces (
tableModel.ts):type TableRow = | { kind: 'aggregate'; key: string; aggregate: CorrectionAggregateOut; include: 'Да' | 'Частично' | 'Нет' } | { kind: 'entry'; key: string; parentKey: string; side: 1 | 2; entry: CorrectionEntryOut } | { kind: 'unallocated'; key: string; entry: UnallocatedEntryOut }; type DisplayMode = { mode: 'all' } | { mode: 'preset'; ruleId: string }; type RecordType = 'all' | 'posting' | 'unallocated'; type DeviationFilter = 'all' | 'over' | 'under'; type TableFilters = { display: DisplayMode; recordType: RecordType; entity: string; line: string | null; account: string; deviation: DeviationFilter }; const EMPTY_FILTERS: TableFilters; aggregateKey(a: CorrectionAggregateOut): string // `${rule_id}|${company_1.id}|${company_2.id}` toAggregateKey(a: CorrectionAggregateOut): AggregateKey includeState(a: CorrectionAggregateOut): 'Да' | 'Частично' | 'Нет' ruleLabel(r: CorrectionRuleOut): string // «Правило 6: 1230 — 1520», «Правило 1: 1150 — 1450 / 1550» articleCodes(line: string): string[] // '1450/1550' → ['1450','1550'] lineOptions(table: CorrectionTableOut): string[] // отдельные коды статей агрегатов, по возрастанию filterTable(table: CorrectionTableOut, f: TableFilters): { aggregates: CorrectionAggregateOut[]; unallocated: UnallocatedEntryOut[] } buildRows(view: ReturnType<typeof filterTable>, expanded: ReadonlySet<string>): TableRow[] visibleTotals(aggregates: CorrectionAggregateOut[]): { amount_1: string | null; amount_2: string | null; deviation_amount: string | null; correction_debit: string | null; correction_credit: string | null } isFiltered(f: TableFilters): boolean findAggregate(table: CorrectionTableOut, key: string): CorrectionAggregateOut | nullПравила
filterTable— design.md «Панель»: пресет — агрегаты правила + нераспределённые с этим правилом вcandidate_rule_ids;recordType;entity— подстрока без регистра в именах обеих сторон (у нераспределённых —owner,counterparty);account— начало кода любого счёта строки (accountsсторон /account_code);lineиdeviation— только агрегаты (поarticleCodesстатей сторон и поexceeds_threshold), при любом из них нераспределённые скрываются. -
Step 1: Failing tests
tableModel.test.ts(наmockData):ruleLabelправила 1 ='Правило 1: 1150 — 1450 / 1550';articleCodes('1310/1350')=['1310','1350'];includeState— все не исключены →'Да', одна из восьми исключена →'Частично', все →'Нет'; пресет правила 3 → только агрегаты правила 3 и нераспределённые, где правило 3 вcandidate_rule_ids;recordType: 'unallocated'→aggregates = [];entity: 'ооо "бск"'→ агрегаты и нераспределённые «ООО "БСК"» (Review Focus);deviation: 'over'→ толькоexceeds_threshold,unallocated = [];buildRowsс раскрытым агрегатом правила 6 «Бековский завод — комбинат» → после агрегата 4 строкиside: 1, затем 4side: 2;visibleTotalsпо агрегатам «ЭПТ — ИЛС» и правила 3 →amount_1 = '30742550.00';findAggregate(table, 'нет')→null(Review Focus). -
Step 2: Failing tests
correctionsFake.test.ts:getCorrectionTableпослеresetCorrectionsFakeотдаёт агрегат «ЭПТ — ИЛС» сdeviation_percent = '394.27',exceeds_threshold = true;allocate EXCLUDEстроки 62.03 агрегата правила 6 уменьшаетside_1.amountна её сумму;allocate ASSIGNв правило не изcandidate_rule_ids—ApiErrorсcode = 'RULE_NOT_APPLICABLE';saveCorrectionсcomment: ' '—COMMENT_REQUIRED; повторныйsaveCorrectionсexpected_version_no: null, когда корректировка есть, —CORRECTION_VERSION_CONFLICT;revokeCorrection→correction = null, затемsaveCorrectionсexpected_version_no: nullпроходит. -
Step 3: Run
cd web && npx vitest run src/widgets/CorrectionTable src/services/api/correctionsFake.test.ts— FAIL. -
Step 4: Implement
mockData.ts,correctionsFake.ts,tableModel.ts; вcorrections.tsветка флага вызываетcorrectionsFake.ApiErrorсоздавать так же, как это делаетapi/client.ts(посмотреть конструктор). -
Step 5: Run тесты — PASS; tsc и biome — чисто.
-
Step 6: Commit
feat(web): фейк контракта корректировок и модель таблицы (#206).
Task 3: Модель окна корректировки и черновик
Files:
- Create:
web/src/widgets/CorrectionTable/editorModel.ts, Test:web/src/widgets/CorrectionTable/editorModel.test.ts - Create:
web/src/widgets/CorrectionTable/draftStorage.ts, Test:web/src/widgets/CorrectionTable/draftStorage.test.ts
Interfaces:
-
Consumes: типы Task 1,
articleCodes,aggregateKey(Task 2),parseAmountInput,sumAmounts(utils/money.ts). -
Produces (
editorModel.ts):type EditLine = { line_code: string; amount: string }; // amount — как ввёл пользователь type EditorState = { debit: EditLine[]; credit: EditLine[]; comment: string }; initialEditorState(a: CorrectionAggregateOut): EditorState // есть корректировка — её статьи (суммы как в API); // нет — по стороне одна статья: первый код articleCodes(article_line), сумма стороны validateLines(s: EditorState): { ok: true; body: Pick<CorrectionIn, 'debit' | 'credit' | 'comment'> } | { ok: false; errors: Record<string, string> } // ключ ошибки `debit.0.amount`; пустая сторона ок, обе пустые — ошибка «Добавьте статью»; // код — 4 цифры; сумма — parseAmountInput, ≥ 0, ≤ 2 знаков liveControl(a: CorrectionAggregateOut, s: EditorState): { sourceDiff: Diff; allocationDiff: Diff } type Diff = { left: string | null; right: string | null; difference: string | null; percent: string | null; red: boolean } // percent к меньшей стороне, 2 знака; red — percent ≥ 5 или одна сторона 0 при ненулевой другой isDirty(initial: EditorState, current: EditorState): boolean pickerCandidates(table: CorrectionTableOut, a: CorrectionAggregateOut, side: 1 | 2): UnallocatedEntryOut[] // та же пара: side 1 — owner = company_1, counterparty = company_2, entry.side = rule.side_1; // side 2 — наоборот и противоположная сторона; правило агрегата в candidate_rule_ids; не excluded toCorrectionIn(body: …, expectedVersionNo: number | null): CorrectionIn // status 'APPLIED' -
Produces (
draftStorage.ts):type Draft = { state: EditorState; savedAt: string; baseVersionNo: number | null }; draftKey(userId: string, versionId: string, aggregateKey: string): string // 'armka:correction-draft:<user>:<version>:<aggregate>' saveDraft(key: string, draft: Draft, storage?: Storage | null): void loadDraft(key: string, storage?: Storage | null): Draft | null // мусор/старый формат/исключение — null clearDraft(key: string, storage?: Storage | null): void isStale(draft: Draft, a: CorrectionAggregateOut): boolean // baseVersionNo ≠ (a.correction?.version_no ?? null)storageпо умолчанию —window.localStorage, доступ вtry/catch. -
Step 1: Failing tests
editorModel.test.ts:initialEditorStateагрегата правила 3 без корректировки →debit = [{line_code: '1170', amount: '29742550.00'}],credit = [{line_code: '1410', amount: '29242550.00'}]; агрегат правила 1 без корректировки → кредитline_code = '1450';validateLinesс суммами'1 000 000,5','1000000,567','-5',''→ ошибки уamount(Review Focus), с'500000,5'→'500000.50'вbody;liveControlагрегата «ЭПТ — ИЛС» с исходным состоянием →sourceDiff.percent = '394.27',red = true; при Дт 1000000.00 и Кт 1000000.00 распределения →allocationDiff.difference = '0.00',red = false;pickerCandidatesдля агрегата «Пензенский Мир Продуктов — Бековский сахарный комбинат» правила 6, сторона 2 → только кредитовые нераспределённые строки комбината на ПМП с правилом 6. -
Step 2: Failing tests
draftStorage.test.ts(storage— простая заглушкаMapсgetItem/setItem/removeItem):saveDraft→loadDraftвозвращает то же;setItemстроки'{"x":1}'→loadDraft=null(Review Focus); заглушка, бросающая наgetItem, →nullбез исключения;isStaleприbaseVersionNo = 2иcorrection.version_no = 3→true. -
Step 3: Run
cd web && npx vitest run src/widgets/CorrectionTable— FAIL. -
Step 4: Implement оба модуля.
-
Step 5: Run — PASS; tsc и biome — чисто.
-
Step 6: Commit
feat(web): модель окна корректировки и черновик (#206).
Task 4: Вкладка и таблица только на чтение
Files:
- Create:
web/src/widgets/CorrectionTable/useCorrectionTable.ts, Test:web/src/widgets/CorrectionTable/useCorrectionTable.test.ts - Create:
web/src/widgets/CorrectionTable/CorrectionTable.tsx(таблица),CorrectionToolbar.tsx(панель),CorrectionContent.tsx(вкладка: загрузка, состояния, панель + таблица) - Modify:
web/src/widgets/ConsolidatedOsv/OsvTabs.tsx(уcorrectionубратьdisabled/tooltip) - Modify:
web/src/layouts/ConsolidationReportPage.tsx(TAB_COMPONENTS.correction,isValidTabсcorrection)
Interfaces:
-
Consumes: Task 1–2 (
getCorrectionTable,filterTable,buildRows,visibleTotals,ruleLabel,lineOptions,isFiltered,EMPTY_FILTERS),useNumberDisplay,ColumnSettings/useVisibleColumns(components/GenericTable), полноэкранный режим — какtoggleFullscreenвConsolidatedChessContent.tsx. -
Produces:
latestOnly(): { next(): number; isLatest(id: number): boolean }(вuseCorrectionTable.ts, чистая, тестируется);useCorrectionTable(versionId: string): { table: CorrectionTableOut | null; loading: boolean; error: string | null; reload(): void; replace(t: CorrectionTableOut): void; replaceAggregate(a: CorrectionAggregateOut): void }— применяет только ответ последнего запроса (Review Focus);CorrectionContent({ versionId }: { versionId: string });CorrectionTableprops:{ rows: TableRow[]; totals; format; expanded; onToggle(key); rules; editable; readOnlyHint: string | null; actions: RowActions }, гдеRowActions— колбэки Task 5–6 (onCorrect(a),onRevoke(a),onAllocate(body)), в этой задаче — заглушки. -
Step 1: Failing test
useCorrectionTable.test.ts:latestOnly— послеnext()= 1 иnext()= 2isLatest(1) = false,isLatest(2) = true. -
Step 2: Run — FAIL.
-
Step 3: Implement хук, вкладку и таблицу: колонки и тексты — Global Constraints и spec «Колонки и строка агрегата», «Раскрытие», «Нераспределённые строки», «Панель и фильтры»; суммы —
formatизuseNumberDisplay(по умолчанию «тыс. руб.», 2 знака, как в дизайне); красное — приexceeds_threshold;composition_changed— фон строки и значок с подсказкой;DRAFT— метка «черновик»; «Итого» —visibleTotals, процент «—»; флажки и меню пока неактивны. Экспорт —exportCorrectionTablesaveBlob(blob, filename ?? xlsxFilename('Таблица_корректировки', …)). Вкладка доступна, адрес…/correction.
-
Step 4: Run тест — PASS;
npx tsc --noEmit -p .,npx biome check src— чисто. -
Step 5: Проверка в браузере (фейк):
cd web && REACT_APP_CORRECTIONS_MOCK=true npm run start, бэкенд ядра для карточки версии — как в../docs/dev/qa-guide.md; открыть…/versions/<id>/correction; сверить с дизайном (открытьДКБ_таблица_корректировок_23_10_26_1.htmlрядом): колонки, раскрытие агрегата правила 6, нераспределённые строки, пресет правила 3, фильтры, «Итого», единицы. Снимки — в/private/tmp/claude-501/(scratchpad сессии), в репозиторий не класть. -
Step 6: Commit
feat(web): вкладка «Таблица корректировки» — таблица и панель (#206).
Task 5: Решения по строкам, отмена корректировки, только чтение
Files:
- Create:
web/src/widgets/CorrectionTable/RowActions.tsx(флажок «Учитывать в КФО», меню строки, выбор правила) - Create:
web/src/widgets/CorrectionTable/RevokeModal.tsx - Create:
web/src/widgets/CorrectionTable/readOnly.ts, Test:web/src/widgets/CorrectionTable/readOnly.test.ts - Modify:
CorrectionTable.tsx,CorrectionContent.tsx
Interfaces:
-
Consumes:
allocate,revokeCorrection(Task 1),replace/replaceAggregate/reload(Task 4),ruleLabel. -
Produces:
readOnlyHint(reason: ReadOnlyReason | null): string | null(тексты — Global Constraints);allocationMenu(row: TableRow, rules: CorrectionRuleOut[]): { key: AllocationAction | 'MOVE'; label: string; disabled: boolean }[](тестируется: у строки агрегата сdecision = 'AUTO'нет «Вернуть к автоматике»; пустойcandidate_rule_ids— «Перенести в правило…» неактивен; у нераспределённойreason = 'UNASSIGNED'есть «Вернуть к автоматике»). -
Step 1: Failing tests
readOnly.test.ts:readOnlyHint('LOCK_REQUIRED')= «Возьмите консолидацию в работу»,null→null; три случаяallocationMenuвыше. -
Step 2: Run — FAIL.
-
Step 3: Implement: флажок строки →
allocate({statement_type: 'balance', entry_ids, action: checked ? 'INCLUDE' : 'EXCLUDE'}), ответ —replace; меню →ASSIGN(выбор правила изcandidate_rule_ids, подписиruleLabel),UNASSIGN,RESET; «Отменить корректировку» в меню агрегата →RevokeModal(комментарий обязателен, пустой — «Сохранить» неактивна) →revokeCorrection→replaceAggregate. Ошибки API — уведомление сApiError.message; 409 правок версии — ещёreload().editable = false— всё неактивно с подсказкойreadOnlyHint. -
Step 4: Run тесты — PASS; tsc, biome — чисто.
-
Step 5: Проверка в браузере (фейк): исключить строку 62.03 → у агрегата «Частично», сумма стороны меньше; «Отнести к правилу…» у кредитовой 76.05 «ООО "БСК"» → строка в агрегате; «Отменить корректировку» у «Сатурн — Архол». Флаг фейка
editable = false(временно в тесте руками) — подсказки. -
Step 6: Commit
feat(web): решения по строкам и отмена корректировки (#206).
Task 6: Окно «Корректировка операции»
Files:
- Create:
web/src/widgets/CorrectionTable/CorrectionEditorModal.tsx - Modify:
CorrectionContent.tsx(открытие окна из «Корректировать»)
Interfaces:
-
Consumes: Task 3 целиком,
allocate,saveCorrection,findAggregate,toAggregateKey,useCurrentUser().id,replace/replaceAggregate/reload. -
Produces:
CorrectionEditorModal({ versionId, table, aggregateKey, editable, onClose, onTable, onAggregate }). -
Step 1: Implement по spec «Окно…», «Сохранение…», «Черновик окна…»: состояние —
initialEditorStateили черновик (loadDraft(draftKey(...)), плашки «Восстановлены несохранённые правки от ЧЧ:ММ» / «Корректировку изменили после ваших правок» поisStale, кнопка «Сбросить»);saveDraftпри каждой правке; «×» исходной строки и «Добавить строку ОСВ» (pickerCandidates) —allocateсразу, таблица —onTable, агрегат окна —findAggregate(нет — по сторонам «—», Review Focus); контроль —liveControl; «Сохранить корректировки» —validateLines→saveCorrection(toCorrectionIn(body, aggregate.correction?.version_no ?? null))→clearDraft,onAggregate, закрыть; ошибки:COMMENT_REQUIRED— у комментария,INVALID_LINE_CODE— у статьиdetail.line_code,CORRECTION_VERSION_CONFLICT— сообщение «Корректировку уже изменили, данные обновлены» +reload(); «Отменить» приisDirty—Modal.confirm«Есть несохранённые изменения. Закрыть без сохранения?» («Остаться» / закрыть сclearDraft).editable = false— всё только на просмотр, без «Сохранить корректировки». -
Step 2: Run
npx tsc --noEmit -p .,npx biome check src,npm test— чисто. -
Step 3: Проверка в браузере (фейк): окно «ЭПТ — ИЛС» (суммы, 394,27 %, красное); правило 3 без корректировки — подстановка 1170 / 1410; добавить статью 1550 на стороне Кт, сохранить без комментария → ошибка у поля; с комментарием → таблица показывает две статьи; правка суммы → «Назад» в браузере → вернуться → плашка восстановления; «Отменить» → вопрос. Сверка вида окна с дизайном (
editor0.png,editor-unalloc.png— снимки в/private/tmp/claude-501/-Users-andrey-epifancev-projects-awp-ca/d22b6541-bb55-46fc-a4fe-8bba06387616/scratchpad/mockup-1023/). -
Step 4: Commit
feat(web): окно «Корректировка операции» с черновиком (#206).
Task 7: Сверка с дизайном, проверки, документация
Files:
-
Modify:
web/README.md(раздел про флагREACT_APP_CORRECTIONS_MOCK),web/ARCHITECTURE.md(виджетCorrectionTable) -
Modify:
openspec/changes/corrections-table-ui/tasks.md(создать по факту выполненного, все пункты отмечены) -
Step 1: полный проход сценариев spec
corrections-table-uiв браузере на фейке, сверка каждой части экрана с дизайном в Playwright (оба окна рядом, одинаковая ширина 1920); расхождения — исправить или записать в отчёт с причиной. -
Step 2: из корня репозитория
make web-check— PASS; изweb/—npm test,npm run lint— PASS. -
Step 3:
openspec validate corrections-table-ui --strict— valid. -
Step 4: Commit
docs(web): таблица корректировок — README, ARCHITECTURE, tasks (#206).
Task 8 (после слияния AURORA-1540): подключение к API
Выполняется отдельно, когда эндпоинты 1540 в main. Влить main в ветку; запустить без флага на стенде
(../docs/dev/qa-guide.md) на реальной версии; пройти сценарии spec; расхождения ответа с контрактом — правка в фейке и
типах или замечание в PR 1540; make check-full; PR в main (до этого PR не открывать — фронт без API не сливаем, если
владелец не решит иначе).
Уточнение (R-27): в Task 8 удаляются только services/api/correctionsFake.ts (+ correctionsFake.test.ts) и ветка флага CORRECTIONS_MOCK в corrections.ts/config.ts; widgets/CorrectionTable/mockData.ts и mockCalc.ts остаются как тестовые фикстуры — их импортируют тесты моделей.