Files
ct-forms-portal/docs/adr/0001-isklyuchenie-postavshchikov.md
2026-10-27 12:52:09 +05:00

116 lines
9.6 KiB
Markdown
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.
# 0001. Исключение поставщика из сравнения вместо удаления
- Статус: Принято
- Дата: 2026-10-27
- Задача: #1 (СТ ОМТС); ОИД — #3, КПС — #4
- Источник: Асиля Сарниязова, Teams, 2026-10-23, п.4: «Я случайно удалила из таблицы
поставщика — и не смогла вернуть его назад, хоть и есть КП»
## Контекст
На Шаге 3 (таблица сравнения) крестик на колонке поставщика удаляет его безвозвратно во всех
трёх формах — СТ, ОИД, КПС:
- колонка убирается из состояния формы, введённые цены стираются;
- при сохранении все офферы по позициям СТ уходят в корзину и заново пишутся только
оставшиеся поставщики (обычная правка записи на списках офферов заблокирована ресивером);
- вернуть поставщика можно только повторной загрузкой КП на Шаге 2, цены, введённые руками,
теряются.
Офферы читает не только портал:
- СТ — бэкенд `api.tnsu.kz/Report/CT` (PDF/Excel-отчёт, уходит в пакет INCORE);
- ОИД — поток ИИ-комментария читает «Оценку поставщиков ОИД» сам;
- потоки уведомлений — внешние, читают ли они офферы, по коду не определить.
В закупочных системах (SAP Ariba, Coupa, Procore и др.) участника не удаляют, а отклоняют
с причиной, и это остаётся в истории.
## Варианты
**А. Флаг `isExcluded` на каждом оффере** в списках «Оценка поставщиков», «… ОИД», «… КПС».
- Плюс: одна колонка, исключённые лежат рядом с обычными.
- Минус: все сторонние читатели начнут видеть исключённых, пока каждый не добавит фильтр —
бэкенд отчёта СТ (иначе исключённый попадёт в PDF и INCORE), поток ИИ ОИД, возможно потоки
уведомлений. Выкатка строго по порядку: колонка → бэкенд и потоки → портал.
**Б. JSON-снимок в шапке СТ** — колонка `excludedSuppliers` (Note) в «Сравнительной таблице»
каждой формы.
- Плюс: «Оценка поставщиков» не меняется, сторонние читатели ничего не замечают.
- Минус: размер. Строка оффера — 200–300 символов; 3 поставщика × 100 позиций = 60–90 тыс.
символов, с комплектами — вдвое больше. Предел многострочной колонки SP — около 64 тыс.
Сохранение упадёт на обычных объёмах.
**В. Отдельный список «Исключённые предложения»** под каждую форму («…», «… ОИД», «… КПС»):
те же колонки, что в «Оценке поставщиков», плюс лукап на СТ.
- Плюс: в «Оценке поставщиков» только активные поставщики — бэкенд, отчёт, INCORE и потоки
не дорабатываются, порядок выкатки не важен.
- Плюс: одна строка на оффер, объём не ограничен.
- Минус: три новых списка, перенос строк между списками при исключении и возврате.
## Решение
Вариант **В**.
- Крестик исключает поставщика: колонка скрывается, итоги, минимумы, рекомендация и ИИ его
не учитывают. Если исключили выбранного — выбор сбрасывается.
- При исключении **причина обязательна**. Она хранится вместе с исключёнными строками.
- Под таблицей у автора — плашка «Исключены из сравнения: поставщик — причина [Вернуть]».
- **Согласующий исключённых пока не видит.** Данные для него грузятся так же, но показ
выключен константой — включить можно без доработки хранения.
- **Повторная загрузка КП** исключённого поставщика на Шаге 2 его не возвращает: он остаётся
исключённым до «Вернуть».
- При сохранении цены исключённых пишутся в «Исключённые предложения», в «Оценку
поставщиков» не попадают. При открытии СТ исключённые читаются по id СТ.
- «Вернуть» → при сохранении строки переезжают обратно в «Оценку поставщиков».
- Поставщик без цен — одна строка-маркер без позиции, чтобы колонка не терялась.
- Позицию сняли на Шаге 1 — её строки в «Исключённых» тоже уходят в корзину.
- Запись офферов и исключённых — в общем модуле для трёх форм, чтобы позже перенести
сохранение на бэкенд без переделки компонентов.
## Устройство списков
Манифесты — `provisioning/manifests/{ct,oid,kps}.provisioning.json`.
- Колонки — копия «Оценки поставщиков» своей формы (чтобы «Вернуть» восстановило оффер
целиком) плюс `CT` (найти исключённых при открытии СТ) и `reason` (причина).
- Ссылки на обычные списки — Lookup, файл КП — сразу `offerFileId: Number`: лукап на
библиотеку документов не читается через REST.
- Номер строки комплекта — в `Title`, как в «Оценке поставщиков» (`поставщик-позиция-N`):
один формат во всех списках, разбор переиспользуется (`parseOfferItemIndex`).
- Причина повторяется в каждой строке поставщика — осознанная денормализация. Отдельный
список «Исключённые поставщики» дал бы 6 списков вместо 3 ради короткого текста.
- Поставщик без цен — строка без `position`, чтобы колонка не терялась.
## Последствия
- Три новых списка на `/sites/sed` — описать в `provisioning/manifests/{ct,oid,kps}`,
создать через `Provision-Lists.ps1` (сначала тест, потом бой).
- Доработка Шага 3 и загрузки СТ во всех трёх формах, Jest-тесты на исключение и возврат.
- Попутно закрываются баги удаления: в ОИД выбор остаётся на удалённом поставщике, в КПС
удалённый поставщик без КП возвращается после перезагрузки.
## Известные ограничения
- **«Дополнительная информация» (условия оплаты, поставки, сроки) исключённого поставщика в SP
не пишется** — «Критерии» читает отчёт СТ, и исключённый попал бы в него. В сессии автора
условия сохраняются; если вернуть поставщика после открытия СТ в другом браузере, их придётся
заполнить заново. Цены возвращаются всегда.
- Если сессия автора уже есть, СТ берёт поставщиков из неё, а не из SP (как и раньше для
обычных поставщиков).
## Объём
Сейчас — только СТ ОМТС (#1). ОИД (#3) и КПС (#4) — позже: списки для них уже созданы, общий
модуль `shared/offers/excludedOffers.ts` готов к переиспользованию.
## Ответы на открытые вопросы (2026-10-27, Тимур)
1. Согласующий исключённых — заложить, но пока не показывать.
2. Повторная загрузка КП не возвращает исключённого поставщика.
3. Причина исключения — обязательная.
4. Списки создаются через `Provision-Lists.ps1` по манифестам (сначала тест, потом бой).