Направление работ: Mobile Код раздела: 1.2.2 Авторизация > Регистрация > Подтверждение почты Статус: Готов Ответственный: Панкова Л.А. Версия: 0.1 Обновлено: 14.07.26
1. Назначение
Раздел описывает ввод контактных данных и подтверждение email в мобильном приложении после регистрации.
Интерфейс должен позволить пользователю указать имя и email, подтвердить email кодом из письма или пропустить заполнение данных на этапе завершения регистрации.
Раздел зависит от Back: 1.1 Организация аккаунтов и Back: 1.1.1 Регистрация.
2. Ключевые понятия
Контактные данные — имя пользователя и email, которые пользователь может указать после завершения обязательной регистрации.
Email-код — 6-значный код подтверждения почты.
Заполню потом — действие, позволяющее завершить шаг регистрации в мобильном приложении без ввода контактных данных.
3. Scope
Входит
- Ввод имени и email
- Проверка формата имени и email на фронте
- Пропуск заполнения контактных данных
- Отправка письма с кодом подтверждения email
- Ввод и проверка email-кода
- Повторная отправка email-кода
- Ошибки ввода, проверки кода и технические ошибки
- Переход к следующему шагу
Не входит
Mobile: 1.2 Регистрация
Mobile: 1.2.1 Задать код-пароль и биометрию
Back: 7.1 Шаблоны писем и правила доставки уведомлений
Mobile: 5.2.1 Редактирование учетных данных вне регистрации
4. BR
Бизнес-правила и ограничения указаны в Back: 1.1 Организация аккаунтов и Back: 1.1.1 Регистрация.
В этом документе связи с BR указаны только при прямом соответствии фронтового требования backend-правилу.
5. Функциональные требования (UC + FR)
FR – functional requirements, что интерфейс должен уметь делать по определенному UC (триггеру).
1.2.2 Подтверждение почты Обновлено 5 июня 2026, 17:05 Лайза Панкова
| ID | Сценарий | Функциональное требование | BR | Вес | Статус |
|---|---|---|---|---|---|
| FR-1 | Пользователь открывает шаг контактных данных | Интерфейс показывает поля имени и email, действие продолжения и возможность заполнить данные позже. | — | 1 | Реализовано \ #OQ1.2.2-5 |
| FR-2 | Пользователь вводит имя | Интерфейс принимает имя и показывает ошибку, если значение содержит недопустимые символы. | — | 1 | Частично \ #OQ1.2.2-3 \ #OQ1.2.2-16 |
| FR-3 | Пользователь вводит email | Интерфейс принимает email и проверяет базовый формат адреса до отправки данных. | Back: 1.1.1: | 1 | Реализовано |
| FR-4 | Пользователь нажимает продолжить | Интерфейс отправляет введенные контактные данные и при успешном ответе переводит пользователя к вводу кода из письма. | Back: 1.1: Back: 1.1.1: | 1 | Частично \ #OQ1.2.2-5 \ #OQ1.2.2-14 |
| FR-5 | Пользователь нажимает “Заполню потом” | Интерфейс пропускает ввод контактных данных и переводит пользователя к следующему шагу Mobile 2.1 Поиск сфер. | — | 1 | Частично \ #OQ1.2.2-1 \ #OQ1.2.2-15 |
| FR-6 | Email или Имя имеют некорректный формат | Интерфейс показывает ошибку emai или Имениl и не отправляет данные до исправления. | Back: 1.1.1: | 1 | Частично \ #OQ1.2.2-3 \ #OQ1.2.2-16 |
| FR-7 | Ввод email-кода | Интерфейс показывает email, на который отправлено письмо, поле ввода 6 цифр и таймер повторной отправки. | Back: 1.1.1: | 1 | Реализовано \ #OQ1.2.2-11 |
| FR-8 | Пользователь вводит email-код | Интерфейс принимает 6 цифр и отправляет код на проверку после заполнения или явного действия пользователя. | Back: 1.1.1: | 1 | Реализовано \ #OQ1.2.2-12 |
| FR-9 | Статус Email-кода | Подтвержден: Интерфейс фиксирует успешное подтверждение и переводит пользователя к следующему шагу. Не подтвержден: Интерфейс показывает ошибку проверки и позволяет повторить ввод в пределах действия таймера (60сек). | Back: 1.1.1: | 1 | Частично \ #OQ1.2.2-2 \ #OQ1.2.2-7 |
| FR-10 | Срок действия email-кода истек | Интерфейс предлагает отправить новый код и возвращает пользователя к вводу нового кода после успешной отправки. | Back: 1.1.1: | 2 | Не реализовано \ #OQ1.2.2-4 |
| FR-11 | Пользователь запрашивает новый email-код | Интерфейс блокирует повторную отправку до завершения таймера и перезапускает таймер после успешной отправки. | Back: 1.1.1: | 2 | Реализовано \ #OQ1.2.2-2 \ #OQ1.2.2-13 |
| FR-12 | Возникла техническая ошибка | Интерфейс показывает общее состояние ошибки и позволяет повторить действие без потери текущего шага. | — | 2 | Частично \ #OQ1.2.2-14 \ #OQ1.2.2-15 |
| FR-13 | Шаг подтверждения почты завершен | Интерфейс переводит пользователя к следующему продуктовому сценарию: Mobile 2.1 Поиск сфер. | — | 1 | Реализовано \ #OQ1.2.2-1 |
6. Открытые вопросы
Контекст сверки (пин). Версия BRD: 0.1 (шапка — обновлено 14.07.26; таблица ФТ — обновлено 05.06.26). Репозиторий:
booking.mobile.flutter, веткаdev, commit89ca502. Дата сверки: 2026-07-15. Прошлый обход: commitdad0c65(2026-07-06). Переобход относительно dad0c65 (2026-07-06): снято 2 — OQ1.2.2-6 (finally-баг: «Продолжить» теперьawait-ит запрос, «успешно» и переход только в ветке успеха) и OQ1.2.2-8 (при ошибке шаг больше не завершается переходом). Осталось открытыми 10 из 13 — OQ1.2.2-1…5, 7, 9, 10, 11, 13: по ним код не менялся; частично сдвинулся только OQ1.2.2-3 (экран регистрации теперь enforce-ит формат имени, но канонический формат с Back 1.1.1 не согласован). Появилось 3 новых — OQ1.2.2-14 (успех показывается и при ошибочном ответе бэкенда — остаток OQ1.2.2-6, уровнем ниже), OQ1.2.2-15 («Заполню потом» блокируется навсегда после ошибки — ловушка, которую активировал фикс OQ1.2.2-6), OQ1.2.2-16 (ошибка формата имени/email пользователю не показывается). OQ1.2.2-12 остаётсяClosed— решение команды не пересматривалось. Якорь в коде:lib/src/feature/auth/widget/pages/reg_contact_data/reg_set_contact_data.dart,lib/src/feature/check_email_phone_modals/**,lib/src/core/components/repositories/identity_service/**,lib/src/feature/profile_page/profile/data/user_repository.dart,lib/src/core/components/grpc/**,lib/src/core/utils/regex.dart,lib/src/core/utils/validators.dart,lib/src/core/utils/custom_timer.dart. Стек: Flutter/Dart, Cubit (flutter_bloc), gRPC-gateway поверх REST (IdentityService,ClientsService). Обзор диффаdad0c65..89ca502: 14 коммитов, 75 файлов. Тему раздела задели3fe6bab(1.2 bugfixes — фикс «Продолжить», валидаторы имени, заголовок экрана) иdd54f5d(registration code + pin —approveCodeтеперь принимаетString). Коммиты626cbde/7610f28(AppLockGate поверх роутера) и снятие редиректа наLogPinRouteвapp_router.dart:169-174проверены: они управляют блокировкой уже авторизованного приложения и на шаг контактных данных / модалку почты напрямую не влияют — новых расхождений по FR-1…FR-13 не дают. Маршрутизация без сверки с гайдбуком (гайдбук в этот прогон не передавался). Правило вывода: ни одного утверждения без пруфаfile:line; где не нашли за обход — помечено явно.
| ID | Источник | Вопрос | Кому | Статус | Рекомендации | Итоговое решение |
|---|---|---|---|---|---|---|
| #OQ1.2.2-1 | Код FR-5, FR-13 | Требование говорит: после «Заполню потом» (FR-5) и завершения шага (FR-13) — переход к «2.1 Поиск сфер» (SpheresRoute). В коде оба случая делают replaceAll([HomeRoute()]), SpheresRoute закомментирован; HomeRoute — стартовый таб-контейнер, поиск сфер — вкладка внутри. \ reg_set_contact_data.dart:257,280,282; app_router.dart:340-347 \ Переобход 89ca502: без изменений, код тот же, пруф переехал по строкам — reg_set_contact_data.dart:265 («Продолжить»), :300 («Заполню потом»), :298 (SpheresRoute всё так же закомментирован); app_router.dart:334,341 | Разработка | Closed | Продуктовое решение о формулировке приземления. (А) уточнить BRD, что «2.1 Поиск сфер» = домашний экран с активной вкладкой поиска (тогда HomeRoute корректен); (Б) навести навигацию на SpheresRoute внутри HomeRoute. Тот же узел приземления, что в 1.3/OQ1.3-4 и 1.2.1/OQ1.2.1-1. | Не является дефектом — вопрос закрыт, доработок не требуется. |
| #OQ1.2.2-2 | Код FR-9, FR-11 | Требование говорит: окно повтора кода — 60 сек. В коде таймер ставится из ответа сервера (response.timeout), значения 60 в коде нет — длительность задаёт бэкенд, фронт отображает. \ check_contacts_modal_cubit.dart:129,136 \ Переобход 89ca502: без изменений — таймер по-прежнему ставится только из response.timeout, константы 60 в коде нет. \ check_contacts_modal_cubit.dart:129,136 | Бизнес/Разработка | Closed | Кто источник истины по окну повтора; согласовать с Back: 1.1.1. (А, рекоменд.) бэкенд задаёт timeout, а «60 сек» в BRD пометить как справочное/дефолт (фронт уже берёт из ответа); (Б) если 60 — жёсткое требование, на фронте сейчас нет защиты от иного значения. | Не является дефектом — вопрос закрыт, доработок не требуется. |
| #OQ1.2.2-3 | Код FR-2, FR-6 | Требование говорит: имя принимается, при недопустимых символах — ошибка. В коде два несогласованных набора: Validator.validateName (мин. 2 буквы, запрет цифр/крайних пробелов/сдвоенных спецсимволов) на экране регистрации, а в модалке имя проверяется только length < 2, RegexString.checkNamePattern (2–50 символов, буквы + '-) не вызывается вовсе. \ validators.dart:8-50; reg_set_contact_data.dart:75; regex.dart:2-23; new_contact_enter.dart:106 \ Переобход 89ca502: частично исправлено — на экране регистрации формат имени теперь реально enforce-ится: подключён NameInputFormatter и maxLength: Validator.maxNameLength (50), в validateName добавлены проверка верхней границы 50 и запрет спецсимвола в начале. \ reg_set_contact_data.dart:114-115; validators.dart:7,34-35,55,103,129. Остаётся: канонический формат с Back 1.1.1 не согласован (вопрос бизнеса открыт); RegexString.checkNamePattern по-прежнему не вызывается ниоткуда (мёртвый код, regex.dart:2); в модалке имя всё так же проверяется только length < 2 (new_contact_enter.dart:106) — но на путь регистрации это не влияет, поле имени в модалке скрыто (isNameNeedForUpdate: false, reg_set_contact_data.dart:174; new_contact_enter.dart:206) и относится к сценарию заказа/профиля (см. OQ1.2.2-9). | Бизнес/Разработка | Open | Канонический формат имени — согласовать с Back: 1.1.1. (А, рекоменд.) свести к одному валидатору в обоих местах (экран + модалка); сейчас модалка формат не enforce-ит, только длину. Уточнить верхнюю границу 50, запрет цифр, минимум 2 буквы. | От бизнеса: внесли изменение в БРД 1.3. |
| #OQ1.2.2-4 | Код FR-10 | Требование говорит: отдельный сценарий «код истёк» — предложить новый и вернуть к вводу. В коде явного состояния нет: серверный codeTimeOut сводится к строке «Повторите через N минут», поле expiresIn из ответа нигде не читается; при проверке различается только «успех vs любая ошибка». \ update_data_response.dart:12,26; errors.dart:20-21; check_contacts_modal_cubit.dart:66-74 \ Переобход 89ca502: без изменений — expiresIn по-прежнему только объявлен и нигде не читается (update_data_response.dart:12,60 — единственные вхождения по репозиторию), codeTimeOut всё так же даёт литеральную строку «Повторите попытку через N минут» с неподставленным N (errors.dart:20-21). | Бизнес/Разработка | Open | Нужен ли явный сценарий истечения TTL и каков сам TTL — согласовать с Back: 1.1.1. (А) если да — задействовать expiresIn/codeTimeOut (уже приходят, но проигнорированы); (Б) оставить общей ошибкой и смягчить FR-10. Связать с OQ1.2.2-7. | От бизнеса: доработка, вес 2. Внедрить текст ошибки. |
| #OQ1.2.2-5 | Код FR-1, FR-4 | Требование говорит: поля имени и email + продолжение (FR-1); «Продолжить» отправляет введённые контактные данные (FR-4). В коде «Продолжить» вызывает changeUserOnlyName(name:) — email в вызов не входит; email подтверждается отдельно в модалке при тапе на поле. «Отправка email» и «продолжение» разъединены. \ reg_set_contact_data.dart:244,160-169 \ Переобход 89ca502: без изменений по сути вопроса — «Продолжить» по-прежнему шлёт только имя (changeUserOnlyName(name: nameEditController.text)), email в вызов не входит. \ reg_set_contact_data.dart:258-259,166-190. Попутно в этом коммите исправлен заголовок экрана: было 'Вход по Код-паролю' — стало 'Персональные данные' (reg_set_contact_data.dart:88), поэтому по FR-1 (показ полей, продолжения и «заполню потом») расхождений не осталось — статус FR-1 поднят до «Реализовано». | Бизнес/Разработка | Closed | Решение: by design или дефект потока. (А) email обязательно подтверждается в модалке до «Продолжить», тогда «Продолжить» шлёт только имя — задокументировать в FR-4; (Б) «Продолжить» должен слать и email — добавить вызов updateEmailMobile в обработчик. | Не является дефектом — вопрос закрыт, доработок не требуется. |
| #OQ1.2.2-6 | Код FR-4 | Проблема: «Продолжить» помечает успех и уходит на HomeRoute безусловно. changeUserOnlyName (async Future<bool>) вызывается без await, поэтому catch не ловит асинхронную ошибку сети/gRPC (блок мёртв), а finally показывает «Имя установлено успешно» и делает переход сразу. Ошибка ответа не ловится и не показывается. Противоречит FR-4 (переход только при успехе) и FR-12. \ reg_set_contact_data.dart:243-258; user_repository.dart:105 \ Переобход 89ca502: исправлено — блок finally убран, вызов теперь await userRepository.changeUserOnlyName(...), «Имя установлено успешно» + endInstallBiometric() + replaceAll([HomeRoute()]) перенесены в ветку успеха, в catch — «Ошибка установки имени» и isError = true без перехода; добавлены guard-ы if (!context.mounted) return. Обработчик стал async. \ reg_set_contact_data.dart:246,253-274. Остаток вынесен в OQ1.2.2-14: await чинит fire-and-forget и оживляет catch для брошенных исключений, но ошибочный ответ бэкенда исключением не является и по-прежнему приводит к «успеху». | Разработка | Closed | Программный дефект, продуктового вопроса нет. Закрываю сам: убрать безусловный успех/переход из finally; добавить await, перенести переход и «успешно» в ветку успеха, при ошибке — оставаться на шаге → тикет в Bitrix. Чинит FR-4 и FR-12 разом (см. OQ1.2.2-8). | От разработки: реализовано. |
| #OQ1.2.2-7 | Код FR-9 | Требование говорит: осмысленный статус email-кода (подтверждён / не подтверждён). В коде огрублено до «state==6 vs всё остальное»: invalidCode, codeTimeOut, tooManyRequests не разводятся — одинаковый путь «показать ошибку, встряхнуть поле». Повтор в пределах таймера возможен. \ check_contacts_modal_cubit.dart:66-74; confirmed_pin_page.dart:216-222 \ Переобход 89ca502: без изменений — approveCode всё так же возвращает null только при state.value == 6, иначе строку ошибки; развода состояний нет. \ check_contacts_modal_cubit.dart:66-76; confirmed_pin_page.dart:216-222 | Бизнес/Разработка | Closed | (А, рекоменд.) развести invalidCode/codeTimeOut/tooManyRequests в разные UX-состояния (данные уже приходят в StateResponseEmail); (Б) если продукту достаточно бинарного — зафиксировать в FR-9. Связать с OQ1.2.2-4. | Задача на бэкенд: покрыть и проверить статус-коды ответа по подтверждению email. |
| #OQ1.2.2-8 | Код FR-12 | Требование говорит: общее состояние ошибки с повтором без потери шага. В коде внутри модалки покрыто (ErrorSendPage + «Попробовать ещё раз» → возврат на ввод), но на экране регистрации при ошибке имени шаг всё равно завершается переходом (из-за finally-бага, OQ1.2.2-6). \ error_modal.dart:66-84; reg_set_contact_data.dart:243-258 \ Переобход 89ca502: исправлено — той же правкой, что и OQ1.2.2-6: при брошенной ошибке экран регистрации остаётся на шаге (снекбар «Ошибка установки имени», isError = true, перехода нет), повтор через «Продолжить» доступен — кнопка гасится только валидацией полей. \ reg_set_contact_data.dart:266-274,244. Оговорки вынесены отдельно: OQ1.2.2-15 («Заполню потом» после ошибки блокируется навсегда) и OQ1.2.2-14 (ошибочный ответ бэкенда всё ещё уводит с шага). | Разработка | Closed | В модалке FR-12 выполнен; «без потери шага» на экране регистрации ломает finally-баг. Закрываю сам: решается той же правкой, что OQ1.2.2-6 (перенос перехода в ветку успеха). Отдельного продуктового вопроса нет. Связать с OQ1.2.2-6. | От разработки: реализовано. |
| #OQ1.2.2-9 | Код вне BRD | Что нашли: модалка check_email_phone_modals/ — generic для email и телефона (TypeContactData.email | phone, ветвления по всему кубиту). В scope 1.2.2 телефон не входит — код сверх текущего BRD. \ type_contact_data.dart:1-3; check_contacts_modal_cubit.dart; new_contact_enter.dart \ Переобход 89ca502: без изменений — модалка по-прежнему generic email/phone, ветвления по typeContactData на месте. \ check_contacts_modal_cubit.dart:55-64,98-142; new_contact_enter.dart:113-121 | Бизнес/Разработка | Closed | Не дефект. Решение: пометить «телефонную» логику как generic-компонент вне scope 1.2.2 (используется в заказе/профиле), чтобы не считать её частью раздела. Дом — 5.2.1 Редактирование учётных данных / сценарий заказа. | Не является дефектом — вопрос закрыт, доработок не требуется. |
| #OQ1.2.2-10 | Код вне BRD | Что нашли: признаки незавершённости в якорных файлах: _sendDataToServer(){} пустой; большие закомментированные блоки отправки/навигации; custom_timer.dart помечен «Возможно не используется FIXME»; отладочные print в проде. Код в состоянии черновика — влияет на достоверность «реализовано». \ reg_set_contact_data.dart:37,47-67,70; new_contact_enter.dart:344-391; confirmed_pin_page.dart:271-290,81,134,200; regex.dart:70; custom_timer.dart:4 \ Переобход 89ca502: без изменений — уборки не было: _sendDataToServer(){} (reg_set_contact_data.dart:37), закомментированный блок (:47-67), print (:70,:183; regex.dart:70; confirmed_pin_page.dart:81,134,200; check_contacts_modal_cubit.dart:67,106; error_modal.dart:70), закомментированные блоки (new_contact_enter.dart:344-391; confirmed_pin_page.dart:271-290), FIXME в custom_timer.dart:4. Уточнение по FIXME: CountDownTimer используется — check_contacts_modal_cubit.dart:27 (CountDownTimer timerTimeout = CountDownTimer(0)), т.е. пометка «возможно не используется» неверна и вводит в заблуждение; удалять файл нельзя, надо снять комментарий. В этот же коммит добавлена инфраструктура логирования (app_talker.dart, debug_log_overlay.dart, override logs via defines) — на неё имеет смысл перевести оставшиеся print. | Разработка | Open | Тех-долг/уборка, продуктового вопроса нет. Закрываю сам: перед приёмкой убрать print, мёртвый/закомментированный код и _sendDataToServer(){}, определить судьбу CountDownTimer (FIXME) → тикет в Bitrix. До этого «реализовано» по разделу — предварительно. | От бизнеса: чистка кода, вес 3. |
| #OQ1.2.2-11 | Код FR-7 | Что подтверждено: показ email-адреса и поле ввода 6 цифр есть (Pinput length: 6, заголовок «Подтвердите кодом из письма»). FR-7 выполнен. \ confirmed_pin_page.dart:184,158; check_contacts_texts.dart:17 \ Переобход 89ca502: без изменений, подтверждение в силе — length: 6 (confirmed_pin_page.dart:184), показ адреса (:158), текст (check_contacts_texts.dart:15,17). Замечание про захардкоженную длину остаётся. | Разработка | Closed | Расхождения по FR-7 нет. \ Подтверждение: FR-7 подтверждён кодом. Замечание: длина 6 захардкожена в общем компоненте email/phone — при расхождении длины email/sms-кода потребуется разветвление (на живучесть). | Расхождений нет. |
| #OQ1.2.2-12 | Код FR-8 | Что подтверждено: код уходит на проверку автоматически по заполнению 6 цифр (из Pinput.validator). FR-8 допускает «после заполнения или явного действия» — вариант «после заполнения» покрыт. \ confirmed_pin_page.dart:202-229 \ Переобход 89ca502: решение команды не пересматривается, остаётся Closed. Механика та же (confirmed_pin_page.dart:202-229). Попутно в dd54f5d исправлен смежный дефект, ранее не зафиксированный: approveCode принимал int и вызывался как approveCode(int.parse(value)) — код с ведущим нулём («012345») терял его и уходил на бэкенд как 12345. Теперь сигнатура Future<String?> approveCode(String value) и код передаётся строкой без изменений. \ check_contacts_modal_cubit.dart:52; confirmed_pin_page.dart:211 | — | Closed | Расхождения нет. \ Подтверждение: FR-8 покрыт вариантом «после заполнения»; отдельной кнопки «Подтвердить» нет. Если продукт хочет явное действие — это уточнение FR-8, не дефект. | Почистить код. |
| #OQ1.2.2-13 | Код FR-11 | Что подтверждено: повторная отправка до конца таймера заблокирована по UI — кнопка «Отправить новый код» рендерится только при remainingSeconds == 0. FR-11 по UI покрыт. \ confirmed_pin_page.dart:252-267 \ Переобход 89ca502: без изменений — блокировка по-прежнему только на уровне виджета (confirmed_pin_page.dart:252-267), updateContact остаток таймера не проверяет (check_contacts_modal_cubit.dart:84-143). | Разработка | Closed | Расхождения по UI нет. \ Подтверждение: блокировка на уровне виджета. Замечание: updateContact остаток таймера не проверяет — реальная защита только серверный tooManyRequests; опционально продублировать проверку в кубите. | Не является дефектом — вопрос закрыт, доработок не требуется. |
| #OQ1.2.2-14 | Код FR-4, FR-12 | Требование говорит: «Продолжить» переводит пользователя дальше при успешном ответе (FR-4); при ошибке шаг не теряется (FR-12). В коде (остаток после фикса OQ1.2.2-6) await вернул контроль над ошибками, но только над брошенными: changeUserOnlyName вызывает _grpclient.sendRequest(...) и безусловно возвращает true, не проверяя res.hasError() — в отличие от соседнего getUser(), который такую проверку делает (user_repository.dart:52). При этом sendRequest возвращает GatewayResponse и на ошибку бэкенда не бросает исключение (grpc_client.dart:248; GatewayException бросает не он, а GrpcRequestHandler.handleRequest — gateway_exception.dart:3-14). Плюс возвращаемый bool на месте вызова игнорируется. Итог: при ошибочном ответе бэкенда (например, сервер отклонил имя) пользователь всё равно видит «Имя установлено успешно» и уходит на HomeRoute. Ловится только транспортная ошибка. \ user_repository.dart:105-126,52,117-122; reg_set_contact_data.dart:258-265; grpc_client.dart:248,326-334 | Разработка | Closed | Программный дефект, продуктового вопроса нет — прямое продолжение OQ1.2.2-6 на уровень ниже (репозиторий вместо виджета). Закрываю сам: в changeUserOnlyName проверять res.hasError() и не возвращать true вслепую (либо бросать по образцу GrpcRequestHandler.handleRequest, либо вернуть false), а на экране — не игнорировать результат: переход и «успешно» только при подтверждённом успехе → тикет в Bitrix. До этой правки «переход только при успехе» (FR-4) обеспечен лишь для транспортных сбоев. | Доработать код: переводить пользователя дальше только при успешном ответе сервера, при ошибке оставаться на шаге. |
| #OQ1.2.2-15 | Код FR-5, FR-12 | Требование говорит: пользователь может пропустить ввод контактных данных и завершить шаг (FR-5); при технической ошибке действие можно повторить без потери шага (FR-12). В коде флаг isError выставляется в true в catch при неудачном сохранении имени и нигде не сбрасывается обратно (единственные вхождения — объявление bool isError = false и присваивание в catch), а «Заполню потом» отключается именно по нему: onTap: isError ? null : .... Итог: после одной неудачной попытки сохранить имя пропуск шага становится недоступен до конца жизни экрана — единственный выход остаётся через «Продолжить», которое требует валидных имени и email. До фикса OQ1.2.2-6 ветка catch была мёртвой и ловушка не срабатывала; сейчас она достижима. \ reg_set_contact_data.dart:35,271-273,291-293 | Разработка | Open | Программный дефект, продуктового вопроса нет. Закрываю сам: сбрасывать isError в false при новой попытке (в начале обработчика «Продолжить» и/или в onChanged полей), либо не завязывать «Заполню потом» на isError — пропуск шага не должен зависеть от неудачи сохранения имени → тикет в Bitrix. Уточнить у бизнеса стоит только одно: задумывалась ли блокировка пропуска после ошибки намеренно (в BRD такого условия нет). | От бизнеса: доработка, вес 1. |
| #OQ1.2.2-16 | Код FR-2, FR-6 | Требование говорит: интерфейс показывает ошибку, если имя содержит недопустимые символы (FR-2), и показывает ошибку email или имени и не отправляет данные до исправления (FR-6). В коде ошибка пользователю не показывается ни для имени, ни для email: у обоих полей нет errorText, а результат валидации используется только для включения/выключения кнопки — checkFields() кладёт Validator.validateName(name) == null && RegexString.checkEmailPattern(email) в isBtnContinueActive, и текст ошибки из validateName («Цифры в имени не допускаются.», «Не более 50 символов.» и т.д.) никуда не выводится. Недопустимые символы в имени теперь молча не вводятся вообще — их отсекает NameInputFormatter (добавлен в 89ca502). Половина «не отправляет данные до исправления» выполнена (кнопка неактивна), половина «показывает ошибку» — нет: пользователь видит только неактивную кнопку без объяснения причины. \ reg_set_contact_data.dart:69-82,112-156,164-224,244; validators.dart:9-58 | Бизнес/Разработка | Closed | Расхождение вскрыто на переобходе: сам разрыв существовал и раньше, но с приходом NameInputFormatter поведение «молча блокируем ввод» стало основным, поэтому вопрос вынесен явно. Решение: «предотвращать» или «объяснять». (А, рекоменд.) показывать текст ошибки под полем — validateName уже возвращает готовые человеческие сообщения, их достаточно пробросить в errorText; для email — сообщение о неверном формате. (Б) если продукт сознательно выбирает «не пустить ввод вместо ошибки» — смягчить формулировки FR-2/FR-6 («блокирует ввод недопустимых символов»), иначе требование в текущем виде не выполняется. | Доработать код: показывать текст ошибки для имени и email, а не только гасить кнопку «Продолжить». |