Project

General

Profile

Actions

Task #6130

open

Пользователи: пакетная загрузка из Excel с ролями и выгрузкой паролей

Added by Redmine Admin about 5 hours ago. Updated about 5 hours ago.

Status:
New
Priority:
Normal
Assignee:
-
Start date:
08/26/2026
Due date:
% Done:

0%

Estimated time:

Description

Страница для суперпользователя: загрузка пользователей пачкой из Excel через существующий эндпоинт создания пользователя, с возвратом файла сгенерированных паролей.

Столбцы файла: № | Фамилия | Имя | Отчество | Организация | Роль | Телефон | Почта

Реализовано (6a8a048), маршрут /users/batch:

  • users/common/roles.ts — справочник ролей (Исполнитель, Заявитель, ЦИК, ultra) и нормализация значения из файла с поддержкой синонимов. Раньше роли были зашиты инлайном в select формы редактирования.
  • users/batch/passwords.ts — генерация паролей через crypto.getRandomValues, алфавит без похожих символов (0/O, 1/l/I), гарантированно есть строчная, заглавная и цифра.
  • users/batch/UsersBatchUploadPage.tsx — скачивание шаблона (с отдельным листом-справочником организаций), предпросмотр с построчной валидацией, последовательное создание с прогрессом «N из M», экран результата и выгрузка файла с паролями.

Валидация до отправки: обязательные Фамилия / Имя / Почта, формат почты, роль строго из справочника, организация должна находиться в справочнике по названию (сопоставление без учёта регистра, кавычек и лишних пробелов). Отдельно показываются дубли почты внутри файла. Некорректные строки можно удалить из предпросмотра, загружаются только валидные.

Доступ: страница и кнопка входа на неё видны только при role === "ultra".

Связь с ролями. Валидация роли закрывает тот же класс проблем, из-за которого появлялись пользователи с ролью user без прав: значение вне справочника не пройдёт импорт.

На согласование / доработку:

  • Пароли отдаются файлом в открытом виде — на экране результата есть предупреждение сохранить его в защищённом месте и удалить. Повторно получить пароли нельзя, только сбросить.
  • Эндпоинт создания пользователя вызывается по одному в цикле, отката нет: если часть строк упала, созданные остаются созданными. В файле результата у каждой строки проставлен статус и текст ошибки.
  • Письма с доступами не отправляются — рассылка на стороне оператора.
Actions #1

Updated by Redmine Admin about 5 hours ago

Доработано (9bbc58f) по двум замечаниям.

1. Не распознавались столбцы «Телефон» и «Почта».

Проверил разбор на файле с точно такой шапкой, как в задаче (№ Фамилия Имя Отчество Организация Роль Телефон Почта) — она разбирается корректно. Значит, в реальном файле заголовки этих двух столбцов записаны иначе, а сопоставление было слишком строгим: делался только toLowerCase().trim(), поэтому мимо карты проходили «Телефон:», «Эл. почта», «Тел.», «Мобильный телефон», а также неразрывный пробел и zero-width символы, которые появляются при копировании шапки из веба. Столбец при этом игнорировался молча — данные просто терялись.

Разбор шапки вынесен в users/batch/headers.ts: жёсткая нормализация (удаление zero-width и BOM, приведение юникодных пробелов к обычному, снятие хвостовых : . *, регистр), расширенный список точных заголовков и запасной поиск по вхождению ключевого слова. Проверено на 22 вариантах написания.

Главное — нераспознанные столбцы больше не игнорируются молча: на экране показывается список пропущенных столбцов, а если не найден обязательный (Фамилия / Имя / Роль / Почта) — отдельное сообщение об ошибке с подсказкой проверить, что шапка находится в первой строке листа.

2. Телефон — только цифры, формат 7XXXXXXXXXX.

Из файла принимается любое написание (+7 (999) 123-45-67, 8 999 123-45-67, 9991234567), на сервер уходит 79991234567. Если номер не приводится к 11 цифрам — строка помечается ошибкой и не загружается. В предпросмотре показывается уже нормализованное значение, то есть ровно то, что уйдёт на сервер. Шаблон и пример в нём обновлены.

Замечание на будущее. Тем же слабым местом обладает импорт волонтеров (ImportVolunteersModal) — там сопоставление заголовков тоже сделано через toLowerCase().trim(), и незнакомый столбец так же молча пропускается. Пока не трогал, чтобы не менять поведение работающего импорта; при желании нормализацию можно переиспользовать оттуда же.

Actions

Also available in: Atom PDF