Files
stroy-bot-monorepo/docs/MIGRATION-CHECKLIST.md
T
Тимур Городецкий 0ae52ccd6b docs: drop open questions to the API team
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 10:12:18 +05:00

307 lines
38 KiB
Markdown
Raw 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.
# Чек-лист миграции StroyBot → монорепо
Каждый раздел считается перенесённым только когда все пункты проверены в workbench.
Пишущие операции (создание / правка / удаление / импорт) проверяем **только на тестовом проекте «ТЕСТ StroyBot»**.
Правила переноса:
- Запросы, поля, фильтры, права и тексты переносятся **1:1** из исходного репозитория.
- Известные баги переносятся как есть (помечены 🐞) — исправляем позже отдельными коммитами.
- Старые страницы и web parts не трогаем, поддержка старых URL не нужна.
Легенда: `[ ]` — не проверено, `[x]` — проверено в workbench.
---
## 0. Общее (оболочка)
> 🔧 **Исправлено при проверке T-RPL (2026-10-19):** «Отчёты по проекту» и форма отчёта брали id пользователя из контекста страницы (`legacyPageContext.userId`) и путь к DocLib — от сайта страницы. У каждого сайта свой id пользователя, поэтому в workbench корневого сайта «Отчёты» показывали «Нет доступа». Теперь всё — от сайта со списками (`settings.siteUrl`): id через `_api/web/currentuser`, путь папки фото — от `siteUrl`. Workbench по умолчанию открывается на `/sites/stroy`.
- [ ] Одна web part, разделы переключаются через `?page=…`, проект — через `?projectId=…`
- [ ] Кнопки «назад/вперёд» браузера переключают разделы
- [ ] Ссылку на любой раздел можно скопировать и открыть в новой вкладке
- [ ] Выбранный проект сохраняется между перезагрузками (`sessionStorage`, ключ `stroybot_selected_project`)
- [ ] Шрифты Inter и Lora подгружаются (раньше грузили report-list и assigner)
- [ ] `webApiPermissionRequests` в одном пакете: `api.tnsu.kz` (`9232d9e2-…`, `Reports.Read` и `user_impersonation`) + Dataverse `https://tansu.crm5.dynamics.com` (`user_impersonation`)
**Сознательные изменения (не функционал, а настройка):**
- Настройки property pane из 8 web parts сведены в одну группу «Подключение». Имена списков больше не настраиваются в property pane — они в константах.
- `worksPageUrl` (objects → works) больше не нужен: переход внутри приложения.
---
## 1. Главная (stroy-bot-main)
### Выбор проекта
- [ ] Поле «Проект:», плейсхолдер «Выберите проект», иконка поиска
- [ ] Список грузится при первом фокусе, «Загрузка проектов…»
- [ ] При фокусе текст выделяется
- [ ] Фильтр по Title (без учёта регистра, подстрока), максимум **7** результатов
- [ ] У каждого — бейдж статуса, «Без статуса» если пусто
- [ ] Сообщения: «Нет доступных проектов», «Проекты не найдены», «Ошибка загрузки. Попробуйте ещё раз.»
- [ ] Клик вне / Esc — закрыть и вернуть выбранное название (или очистить)
- [ ] Бейдж статуса рядом с полем: текущий — зелёный, завершен/завершён — серый, архивный — золотой, иначе — нейтральный
- [ ] Сортировка по алфавиту, затем до 7 недавно активных (из Dataverse `cree9_reports`) — наверх
- [ ] Права из «Доступы»: `read | edit0 | aproove`, `canEdit` = OR по `edit0`
- [ ] Подсказка «Выберите проект, чтобы активировать ссылки ниже»
### Разделы проекта
- [ ] Объекты / «Объекты по проекту»
- [ ] Отчёты по проекту / «Просмотр отчётности»
- [ ] Отправить отчёт / «Отправка отчёта по проекту»
- [ ] Загрузить работы из сметы / «Импорт работ в систему»
- [ ] Архив проекта / «Библиотека документов» → `{SITE}/Shared Documents/Архив/{Title}` (внешняя ссылка, новая вкладка)
- [ ] Ресурсы проекта / «Профессии и компании»
- [ ] Без выбранного проекта карточки неактивны
- [ ] Статус «архивный» — неактивно всё, кроме Архива; экспорт тоже неактивен
- [ ] Статус «завершен/завершён» — неактивно «Отправить отчёт»
- [ ] `canEdit = false` — неактивно «Отправить отчёт»
### Экспорт данных «Выгрузить за период»
- [ ] Кнопка «?» — поповер с 3 строками (день / Пн–Вс / произвольный), закрытие по клику мимо
- [ ] Поля «Начало» и «Конец» видны только при выбранном проекте
- [ ] «Укажите период для активации ссылки», пока даты не заданы
- [ ] Ошибка «Дата начала не может быть позже даты конца», кнопка неактивна
- [ ] Бейдж: «Ежедневный отчёт» (`daily`) / «Еженедельный отчёт» (Пн–Вс, `weekly`) / «Отчёт за период» (`period`)
- [ ] «Выгрузить» → спиннер «Формируется…» → файл `{prefix}_{from}_{to}.xlsx`
- [ ] Запрос `GET https://api.tnsu.kz/Report/StroyBot?start=…&end=…&projectId=…` (unix UTC 00:00:00 / 23:59:59)
- [ ] Ошибки: «Ошибка API: {status} {statusText}» / «Ошибка при загрузке отчёта»
**Сознательные отличия от main:**
- Лишний запрос к «Доступы» без фильтра по пользователю (его результат не использовался) — убран, на поведение не влияет.
- Список проектов грузится сразу при открытии, а не при первом фокусе: от статуса и прав проекта теперь зависит и меню слева.
- Карточки разделов открывают раздел в этой же вкладке; новая вкладка — Ctrl+клик или средняя кнопка (раньше всегда `_blank`). Архив — по-прежнему в новой вкладке.
- Ошибки выгрузки показываются под кнопкой, а не через `alert()`. Тексты те же.
- Поповер «?» всегда открывается вниз (раньше переворачивался вверх у края экрана).
- Адрес выгрузки берётся из настройки «Базовый URL API» (по умолчанию тот же `https://api.tnsu.kz`).
- Меню слева скрывает разделы по правилам карточек: архивный — только «Проект»; завершён или нет `edit0` — без «Отправить отчёт»; «Привязка к справочнику» — только с `edit0`.
### Сверка погодных условий — `?page=weather&projectId=&start=&end=`
Добавлена в stroy-bot-main 16.10.2026 (коммиты `d54a581`…`594fc7b`, Mariya Trishina), перенесена 20.10.2026.
- [ ] Кнопка «Сверка погодных условий» рядом с «Выгрузить», активна при обоих датах и верном периоде
- [ ] Ссылка «‹ Разделы проекта» — на главную с теми же датами
- [ ] Шапка: «Сверка погодных условий», название проекта, «Период: ДД.ММ.ГГГГ — ДД.ММ.ГГГГ» (один день — одна дата)
- [ ] Без проекта — «Проект не выбран…», без периода — «Период не указан…», загрузка — «Загрузка данных…»
- [ ] Запрос `GET https://api.tnsu.kz/Report/StroyBot/WeatherCheck?start=…&end=…&projectId=…` (unix как у выгрузки); ошибка — «Не удалось загрузить данные: {status} {statusText}»
- [ ] Метеостанция: «Метеостанция {name}», «WIGOS {code} · {N} км от объекта»
- [ ] «Погода по дням»: Дата, Наблюдений, T мин/макс, Ветер, Порывы, Осадки, Явления; пусто — «Нет данных наблюдений за период»
- [ ] «Позиции и оценка условий труда»: Дата, Профессия / вид работ, Смена (+время), Часы, Тип работ (`hours_type`), Чел., Погода в смену, Оценка, Комментарий, Акт простоя; пусто — «Нет позиций за период»
- [ ] Погода в смену: T мин…макс, ветер (+порывы), осадки, точка росы, видимость (от 1000 м — км), наблюдений, явления; нет — «нет данных»
- [ ] «Запросить оценку ИИ…» → `POST …/WeatherCheck/Assess` с `{ positions }` → «Идёт оценка, это может занять до минуты…» → позиции заменяются ответом, «Оценено позиций: N[, пропущено: M]»; ошибка — «Не удалось получить оценку: …»
- [ ] Оценка «В пределах нормы» — зелёная, другая — жёлтая; акт простоя: «Рекомендован» (+причина) / «Не требуется» / «—»
- [ ] «Примечания» — список, если API их прислал
- [ ] Числа с запятой, пустые — «—»
**Сознательные отличия от main:**
- Вид открывается обычным разделом `?page=weather&start=&end=` вместо хэша `#weathercheck?start=&end=`: «назад/вперёд» и ссылки работают, как у остальных разделов.
- Даты на главной сохраняются в адресе (`?start=&end=`), поэтому возвращаются и по ссылке «Разделы проекта», и кнопкой «назад» браузера (в main — только пока страницу не перезагрузили).
- Ошибки загрузки и оценки — плашкой ui-kit `Alert`, бейджи — `Badge` ui-kit; иконки облака и «искр» — локальный `InlineIcon` (в ките их пока нет).
- Пустое название проекта в списке (правка `?? ''`, которая была незакоммичена в main) у нас уже обработано в `projectRules.ts`.
---
## 2. Объекты (stroy-bot-objects) — `?page=objects&projectId=`
- [ ] Шапка «Объекты проекта» + название проекта («Загрузка...» / «Ошибка»)
- [ ] «Добавить объект» (только `canEdit`), «Экспорт» → «Экспортируется...» → `objects-{projectId}.xlsx` (`GET https://api.tnsu.kz/Report/ProjectObjects?projectId=`)
- [ ] Без projectId — «Вы не выбрали проект»; «Проект с ID={id} не найден»; «Ошибка: …»
- [ ] Пусто — «Объекты не найдены» + «+ Добавить объект» (если `canEdit`)
- [ ] Тулбар «{N} объектов» (рекурсивно), «Развернуть», «Свернуть»
- [ ] Дерево: отступ, иконка папки/файла, счётчик детей, шеврон, клик по строке — выделить + раскрыть
- [ ] Раскрытые и выделенный узлы сохраняются при перезагрузке дерева после операций (в памяти страницы, как в старом коде; при обновлении страницы браузера — сбрасываются)
- [ ] Уровни: 0 — Объект-пятно, 1 — Раздел, 2 — Вид работ (не глубже 3 уровней)
- [ ] Контекстное меню (⋮ и правый клик, только `canEdit`): Добавить объект (не на уровне 2), Редактировать, Работы, Удалить
- [ ] Модалка: «Название *» (обязательно, Enter — сохранить), «Родительский объект» (select только при правке Раздела: узлы уровня 0, сортировка ru, без себя и потомков), «Проект» (read-only)
- [ ] Заголовки модалки: «Добавить Объект-пятно / Раздел / Вид работ», «Редактировать …», «— (корневой)»
- [ ] Удаление: подтверждение с числом дочерних, «Это действие нельзя отменить.»
- [-] ~~Drag & drop~~ — **убрано по решению 2026-10-16** (неудобно, зона «внутрь» не работала, лишние случайные переносы). Родителя Раздела меняют через «Редактировать».
- [ ] «Работы» → раздел Работы с `objectId`
- [ ] После любой операции дерево перезагружается
- [ ] Список «Объекты»: `Title`, `project` (текст = название проекта), `parent` (self-lookup)
- [ ] Права: «Доступы» `projectId`/`userId` → `edit0`
🐞 Удаление узла не удаляет детей.
ℹ️ Экспорт падает с 500 на стороне `api.tnsu.kz`: «Failed to acquire Graph token … SPO_TENANT_ID, SPO_CLIENT_ID, SPO_CERT_PATH, SPO_CERT_PASS» — у API не настроен доступ к Graph (проверка 2026-10-16). Наш запрос доходит до API корректно; чинится на сервере.
**Сознательные отличия от objects:**
- `alert()` → сообщение `Alert` на странице (ошибки переноса, удаления, экспорта) или в модалке (ошибка сохранения). Тексты те же.
- `confirm()` удаления → `confirmModal` с кнопкой «Удалить»; текст тот же, но переносы строк ui-kit показывает одним абзацем.
- Адрес сайта — настройка «URL сайта SharePoint» (раньше `pageContext.web.absoluteUrl`), экспорт — через клиент корпоративного API и настройку «Базовый URL API» (по умолчанию тот же `https://api.tnsu.kz`).
---
## 3. Работы (stroy-bot-works) — `?page=works&projectId=&objectId=`
- [ ] Шапка с названием проекта
- [ ] «Добавить работу» (только `canEdit`)
- [ ] Таблица-дерево: #, Наименование, Ед. изм., Проектный объём (ru-RU, 3 знака), Дата (`dd.mm.yy`, «start – finish» / одна / «—»)
- [ ] Родители свёрнуты по умолчанию, шеврон «Свернуть/Развернуть», нумерация по видимым строкам
- [ ] «Загрузка данных...», «Работы не найдены» (+ кнопка), ошибки Dataverse
- [ ] Меню строки ⋮ (`canEdit`): Добавить дочернюю, Редактировать, Удалить
- [ ] Модалка: НАИМЕНОВАНИЕ *, ЕД. ИЗМ. (список «Единицы измерения», с пагинацией; текущее значение не из списка — добавить наверх), ПРОЕКТНЫЙ ОБЪЁМ (шаг 0.001), РОДИТЕЛЬСКАЯ РАБОТА (без себя и потомков), ОБЪЕКТ (read-only), ДАТА НАЧАЛА / ОКОНЧАНИЯ
- [ ] «Сохранить и добавить еще» (в режиме добавления: модалка остаётся, родитель сохраняется)
- [ ] Подтверждение удаления «Подтвердите удаление»
- [ ] Dataverse `cree9_tables`, фильтр `cree9_objectid eq '{objectId}'`; без objectId — все работы
- [ ] Навигационное свойство родителя определяется через `EntityDefinitions` (fallback `cree9_parentid`)
- [ ] Проект находится подъёмом по родителям объекта до непустого `project`
🐞 Путь объекта (`#headerTitle`) не выводится. 🐞 Отступы в списке «Родительская работа» — обычные пробелы, браузер их схлопывает (как было).
**Сознательные отличия от works:**
- `alert()` → сообщение `Alert`: ошибка сохранения — в модалке, ошибки удаления — над таблицей. Тексты те же.
- Своё окно подтверждения удаления → `confirmModal` из ui-kit с тем же заголовком, текстом и кнопками.
- PATCH и DELETE идут через тот же AAD-клиент Dataverse, а не через `fetch` с токеном вручную; заголовки те же.
- Адрес сайта — настройка «URL сайта SharePoint» (раньше `pageContext.web.absoluteUrl`), Dataverse — настройка «URL Dataverse»; имя таблицы — константа (раньше настройка `worksTable`).
- Таблица — `DataTable` из ui-kit; меню ⋮ — общее меню из `pages/_tree`.
- 🔧 **Исправлено при проверке T-WRK (2026-10-19):** дату окончания нельзя поставить раньше даты начала — календарь не даёт, а при сохранении ошибка «Дата окончания не может быть раньше даты начала» (в старом коде проверки не было).
- ✨ **Улучшено по просьбе (2026-10-19):** «Ед. изм.» — список с поиском: можно начать печатать, Enter выбирает первое найденное. Выбрать можно только значение из справочника, как и раньше.
---
## 4. Отчёты по проекту (stroy-bot-report-list) — `?page=reports&projectId=`
- [ ] Заголовок — название проекта
- [ ] Нет доступа — «Нет доступа» / «У вас нет прав на просмотр отчётов данного проекта.»
- [ ] Период по умолчанию — текущая неделя Пн–Вс; «С:», «По:», «Применить»
- [ ] Поиск «Поиск по любому полю...» (дата, номер, автор, компания, путь объекта, комментарий)
- [ ] Карточка «Отправить отчёт» (если `edit0 || aproove`)
- [ ] Карточки: `#номер`, дата, «✓ Утверждено», Пользователь, Компания (или «ID: n»), Объект (путь), Комментарий
- [ ] Копировать (`edit0 || aproove`) → форма отчёта с `copyFromId`
- [ ] Удалить (`aproove`, или `edit0` и не утверждён) → «Удалить отчёт?» → мягкое удаление `cree9_deleted = true`
- [ ] Клик / Enter / Space по карточке → форма отчёта с `reportId`
- [ ] Сортировка: дата desc, номер desc
- [ ] Пусто — «Отчёты не найдены за выбранный период.»
- [ ] Права: «Доступы» `user/Id` + `project/Id` (lookup)
🐞 Нет пагинации Dataverse (`@odata.nextLink` не читается). 🐞 Неделя по умолчанию считается в UTC.
**Сознательные отличия от report-list:**
- Карточка отчёта и «Создать на основе» открывают раздел «Форма отчёта» этого приложения (`?page=report&projectId=&reportId=` / `&copyFromId=`) — по-прежнему в новой вкладке. «Создать на основе» теперь настоящая ссылка (работает Ctrl+клик / средняя кнопка).
- «Отправить отчёт» — переход в раздел формы отчёта в этой же вкладке (раньше `SendReport.aspx?projectId=` тоже в этой вкладке).
- Списки читаются с сайта из настройки «URL сайта SharePoint» (по умолчанию `…/sites/stroy`), а не с сайта, где стоит web part. Id пользователя — по-прежнему `legacyPageContext.userId`.
- Тёмная тема (`onThemeChanged`) и загрузка шрифтов не переносились: шрифты грузит оболочка, оформление — ui-kit.
- Модалка удаления — `Dialog` из ui-kit (тексты те же, номер отчёта жирным).
---
## 5. Форма отчёта (stroy-bot-report) — `?page=report&projectId=[&reportId=|&copyFromId=]`
### Режимы
- [ ] Нет projectId — «Не указан projectId в URL»
- [ ] `reportId`: NoAccess / Approve (`aproove && !approved`) / Edit (`edit0 && !approved && дней < editDays`, по умолчанию 5) / ReadOnly
- [ ] `copyFromId`: Create, предзаполнены объект, компания, смена, комментарий, работы (с персоналом/техникой), персонал, техника; дата — сегодня, номер — 0, фото — пусто
- [ ] Без параметров — Create, смена — первая из «Смены», дата — сегодня
### Поля
- [ ] «← Вернуться назад»
- [ ] Заголовок «Отчет №N» / «Новый отчет», «Проект: …», бейдж «Утверждён» / «На утверждении»
- [ ] Дата отчета*, Смена*, Объект* (пикер с путями; смена объекта очищает работы), Компания* (с фильтром по «Компании по проекту», fallback — все)
- [ ] Работы: «+ Выбрать работу» («Сначала выберите объект»), Значение, План-факт (П / Ф / +/-, цвет), Комментарий
- [ ] Вложенные «Персонал на работе» / «Техника на работе»: Кол-во, Часы, «+ Добавить профессию / технику»
- [ ] Персонал: Кол-во, Часы, Тип часов (работы / простоя), Комментарий (обязателен для простоя)
- [ ] Техника: то же + «Часы ремонта» (комментарий обязателен для простоя и ремонта)
- [ ] Общий комментарий (contentEditable), Enter не отправляет форму SharePoint
- [ ] Фото: «Открыть папку с фото», «Добавить фото» (multiple), превью с удалением
### Пикер
- [ ] Поиск, группы для техники («Фильтры», чипы, «Все»), drill-down для работ (крошки «Все работы › …», «Выбрать «X»», «Нет работ на этом уровне»)
- [ ] Создание новой техники и новой работы (Ед. изм., План, Родительская работа)
### Сохранение
- [ ] Валидация (все сообщения из исходника)
- [ ] Номер = наибольший номер в проекте (включая удалённые) + 1 — в старом коде «количество неудалённых + 1», см. 🔧 ниже
- [ ] При правке — удалить все старые строки (сначала дочерние), создать заново
- [ ] Фото → `DocLib/{projectId}_{number}`
- [ ] «Отчет успешно сохранен!», кнопка меняется на «← Вернуться назад»
- [ ] Утверждение: подтверждение → `cree9_aprooved = true` → «Отчет утвержден!»
- [ ] Права: «Доступы» `projectId` + `userId*` (несколько вариантов имени поля)
🐞 Create без проверки NoAccess. 🐞 Дата «сегодня» — UTC.
🔧 **Исправлено при проверке T-REP (2026-10-19):**
- Только что созданная из пикера работа попадала в карточку с полным путём вместо названия, без ед. изм. и с планом 0 (в старом коде так же).
- Окно выбора на экране шире 640px — по центру (было всегда прижато к низу), поле «Значение» не вылезает за карточку.
- После «Утвердить отчет» бейдж оставался «На утверждении» до F5 — теперь сразу «Утверждён».
- «← Вернуться назад» во вкладке, открытой из списка отчётов (истории нет), ничего не делал — теперь ведёт в «Отчёты по проекту».
- Номер отчёта повторялся после мягкого удаления (номер = количество неудалённых + 1), а вместе с ним и папка фото `{projectId}_{номер}`. Теперь номер = наибольший номер в проекте, включая удалённые, + 1.
**Сознательные отличия от report:**
- `confirm('Утвердить отчет? …')` → `confirmModal` из ui-kit, текст тот же.
- Адрес сайта для списков и ссылки на папку фото — настройка «URL сайта SharePoint» (раньше `pageContext.web.absoluteUrl`); путь папки при создании и id пользователя для проверки прав — тоже от этого сайта (раньше — от сайта, где стоит страница). По умолчанию это один и тот же сайт `/sites/stroy`.
- Если Dataverse ответит на создание записи `204` без тела, старый код брал id из заголовка `OData-EntityId`, а новый клиент заголовков не видит. С `Prefer: return=representation` Dataverse отвечает `201` с телом, поэтому на практике не встречается; при пустом id сохранение останавливается той же ошибкой `reportId is empty`.
- Логика режимов, валидации, сборки строк и порядка записи / удаления сверена со старым кодом построчно и покрыта тестами (45 тестов).
---
## 6. Импорт работ (stroy-bot-import-works) — `?page=import&projectId=`
- [ ] «ИМПОРТ РАБОТ» + название проекта
- [ ] Нет projectId — «Не указан projectId в URL»; нет `UniqueId0` — «Проект не привязан к справочнику проектов. Обратитесь к администратору.»
- [ ] Загрузка работ из API постранично по 200 с прогрессом «Загрузка работ... X из Y»
- [ ] Выбор объекта (модалка с поиском, сброс ×)
- [ ] Таблица: чекбокс, #, Наименование (+ бейдж объекта, «✓ импортировано», подзаголовок), Код СНБ, Ед. изм., Объём
- [ ] Поиск с debounce 250 мс по всем полям; пагинация по 100
- [ ] «Выделить всё на странице» / «Снять все на странице» (фактически — все отфильтрованные)
- [ ] Уже импортированные — неактивны, «Уже импортировано»
- [ ] «Импортировать» (нужен объект, иначе тост) и «Импорт с созданием объекта» (поиск/создание объекта и раздела по названиям из сметы)
- [ ] Подсказка «Объекты из сметы:» (3 строки + «ещё N»)
- [ ] Тосты «✓ Успешно импортировано: N» / «Импортировано: N, ошибок: M»
- [ ] Защита от дублей по `cree9_comment` = id работы из сметы
🐞 Пагинация в модалке объектов не выводится (видны только первые 100).
**Сознательные отличия от import-works:**
- Адрес Dataverse берётся из настройки «URL Dataverse», адрес сайта со списками — из «URL сайта SharePoint», API — из «Базовый URL API» (значения по умолчанию прежние; раньше Dataverse был захардкожен).
- Если объект выбрать до окончания загрузки работ, загрузка продолжается (раньше перерисовка показывала «Работы не найдены», пока загрузка не заканчивалась).
- Клик по ячейке с флажком вне самого флажка строку не переключает (флажок ui-kit обёрнут в label — иначе было бы двойное переключение). Клик по остальной части строки работает как раньше.
- Тосты при нескольких подряд показываются стопкой, а не друг поверх друга.
- Окно «Выбор объекта» — `Dialog` из ui-kit (у крестика появилась подпись «Закрыть»); поле «Объект» доступно с клавиатуры.
---
## 7. Ресурсы проекта (stroy-bot-project-assigner) — `?page=resources&projectId=`
- [ ] Нет projectId — «Проект не найден. Откройте страницу через список проектов.»
- [ ] Вкладки «Профессии (N)» и «Компании (N)»
- [ ] Слева «Доступные»: поиск, очистка ✕, «уже добавлено», «+», «Ничего не найдено» / «Нет элементов»
- [ ] Справа «Привязаны к проекту»: «−», «Ничего не добавлено»
- [ ] Оптимистичное добавление / удаление с откатом и ошибкой
- [ ] На узких экранах колонки друг под другом
🐞 Удаление ещё не сохранённой записи (временный отрицательный id) уходит на сервер, падает и откатывается с ошибкой. 🐞 Если запись удалили до ответа на добавление, она пропадает из списка, хотя на сервере создана. 🐞 Два добавления в одну миллисекунду получают одинаковый временный id.
**Сознательные отличия от assigner:**
- PnPjs заменён на SharePoint REST с теми же `$select` / `$expand` / `$filter`. Постраничная загрузка PnP (`for await`) → проход по `@odata.nextLink` / `odata.nextLink` до конца (без `$top`, страницы по умолчанию — 100).
- Добавление: `POST …/items` с тем же телом (`Title`, `projectIdId`, `professionIdId` / `companyIdId`), заголовки `Accept` и `Content-Type: application/json;odata=nometadata` + пустой `odata-version` (как в binder). Удаление: `POST …/items({id})` с `X-HTTP-Method: DELETE` и `IF-MATCH: *` — так удаляет сам PnP.
- Текст после префикса ошибки («Ошибка загрузки данных: …» и т.п.) — сообщение `HttpError` (`{status} {statusText}: {ответ сервера}`), а не формат ошибки PnP.
- Счётчик во вкладке — часть подписи («Профессии (0)»): `count` в Tabs ui-kit скрывает ноль.
- Кнопки «+» / «−» — `Button` ui-kit (с подсказками «Добавить» / «Удалить»), сообщение об ошибке закрывается крестиком рядом с `Alert`.
- 🔧 **Исправлено (найдено при проверке T-RES, 2026-10-16):** перед добавлением проверяется, нет ли уже такой привязки на сервере. Раньше при двух открытых вкладках (или двух пользователях) одна профессия / компания добавлялась дважды. Если привязка уже есть — новая запись не создаётся, показывается существующая.
---
## 8. Привязка к справочнику (stroy-bot-projects-binder) — `?page=binder&projectId=`
- [ ] Карточка проекта: название, Описание, Адрес, Статус (цвет), Начало, Окончание, Координаты
- [ ] Бейдж «Привязан» / «Не привязан», `UniqueName · UniqueId0`, «Отвязать»
- [ ] Поиск по справочнику (debounce 400 мс, пустой — все), пагинация ← / →, «{page} / {total} · всего: N»
- [ ] Карточки результатов: name, Код, Описание, Адрес, Координаты; выбор / снятие выбора; «Связать»
- [ ] Сообщения успеха и ошибок
- [ ] Запись `UniqueId0` и `UniqueName` в «Проекты»
🐞 Ответы поиска не сверяются с последним запросом — при быстром вводе может победить устаревший. 🐞 У результата без полей `latitude`/`longitude` выводится «Координаты: undefined, undefined» (проверка только на `null`).
**Сознательные отличия от binder:**
- Сайт списка «Проекты» — из общей настройки «URL сайта SharePoint» (было отдельное свойство `listSiteUrl`, значение по умолчанию то же), адрес API — из «Базовый URL API».
- У поля поиска своя кнопка очистки ✕ («Очистить») вместо встроенной в Fluent `SearchBox`; очистка по Esc не переносилась.
- Сообщения успеха / ошибки сохранения закрываются крестиком рядом с `Alert` (у `Alert` ui-kit нет закрытия).
- Статус, «Привязан» / «Не привязан» — `Badge` ui-kit тех же цветов; кнопка «Связать» — `primary` ui-kit вместо принудительно синей Fluent.
---
## Открытые вопросы
- ✅ Схема списка **«Доступы»** проверена на сайте (2026-10-16): `project` — Lookup на «Проекты», `user` — Person, права `read` / `edit0` / `aproove`. SharePoint отдаёт к ним `projectId` / `userId` — поэтому оба способа из старых репозиториев (`project/Id` + `$expand` и `projectId` / `userId`) обращаются к одним и тем же полям. Расхождения нет.
- ✅ Схема списка **«Объекты»** проверена на сайте (2026-10-16): `parent` — Lookup («Объект»), SharePoint отдаёт к нему `parentId` — чтение через `parent/Id` и запись через `parentId` обращаются к одному полю. `project` — Text (название проекта), как и ожидает код.
- ✅ В «Объектах» есть поля `level` (Number) и `skipLevelUpdate` (Boolean), которые не писал ни один старый репозиторий. Проверено 2026-10-16: `level` пустой и у старых настоящих объектов — поле никто не заполняет и ни на что не влияет. Новое приложение его тоже не трогает.