# Личный кабинет и календарь

Реализация для существующего PHP 8.1 / MariaDB магазина. Каталог, остатки, правила оплаты, юридический допуск к заказам и индексация при установке модуля не меняются.

## Основные файлы изменений

| Файлы | Назначение |
|---|---|
| `app/CustomerService.php`, `app/CalendarDates.php`, `app/CustomerMigrations.php` | Покупатели, телефонные сессии, профиль/данные, согласия и календарь |
| `app/RocketSms.php`, `app/SmsService.php`, `app/SmsMigrations.php` | Адаптер провайдера, OTP, очередь, ограничения и доставка |
| `public/account.php`, `account.php`, `public/assets/account.css`, `public/assets/account.js` | Страницы и формы кабинета |
| `public/admin.php`, `app/AdminSmsView.php` | Роли, настройки и журнал SMS |
| `app/bootstrap.php`, `app/Migrations.php`, `app/OrderService.php`, `public/api.php` | Подключение модулей/миграций и безопасная привязка заказов |
| `.htaccess`, `router.php`, `public/index.php`, `public/assets/app.js`, `public/assets/style.css` | Маршруты, ссылка на кабинет, предложение сохранения даты после заказа и адаптация шапки |
| `tools/migrate-customer.php`, `tools/sms-worker.php`, `tools/sms-status.php`, `tools/install-sms-task.ps1`, `tools/run-sms-task.ps1` | Резервная копия/миграция, обработчик, статус и автоматическое расписание |
| `tests/customer-calendar.php`, `tests/sms.php`, `tests/customer-http.php`, `tests/customer-phone-http.php`, `tests/backend.php`, `tests/routes.php` | Автоматические проверки и регрессии |
| `tests/sms-admin-legal-override.php` | Видимость пропуска юридической проверки, роли и CSRF без изменения рабочего подключения |
| `.gitignore`, `README.md`, `docs/customer-account.md`, `docs/customer-validation.md`, `docs/sms-setup.md`, `docs/sms-scheduler.md`, `docs/account-legal-research.md` | Закрытая конфигурация и эксплуатационная документация |

## Вход покупателя

`/account/login` и совместимый адрес `/account/register` показывают один вход по телефону и одноразовому SMS-коду. Флаг, код и номер находятся в одном поле; по умолчанию выбрана Беларусь `+375`. Поиск предлагает [245 стран и территорий](phone-countries.md), ввод полного международного номера доступен и для стран вне списка. Доступность доставки кода за границу зависит от договора и направлений провайдера. Email не используется для входа и необязателен в профиле.

До правильного кода покупатель не создаётся. Код не возвращается браузеру: непрозрачный токен хранится только в серверной сессии, в БД — его хеш и привязка к сессии/назначению. Срок — 10 минут, максимум пять попыток, повторный запрос через минуту и ограничения на номер/IP. При выключенной интеграции показывается недоступность, запрос кода возвращает 503. Тестового входа или универсального кода нет. Успешный код не подключает календарь, напоминания или рекламу автоматически.

Повторный вход на подтверждённый номер возвращает существующий аккаунт. Неподтверждённые телефоны и совпавший email не используются для объединения аккаунтов. Старые серверные методы пароля сохранены для совместимости данных, но публичные формы регистрации и входа через email/пароль удалены.

Для изменения контактной почты, смены основного номера и удаления аккаунта требуется подтверждение кода не старше 10 минут. Для смены номера подтверждаются текущий доступ и новый номер отдельным кодом; прочие сеансы завершаются, прежняя очередь и SMS-подписка отключаются. В форме профиля основной номер изменить нельзя.

## Разделы

- `/account`: имя, необязательная почта, подтверждённый номер, повторное подтверждение доступа и смена номера.
- `/account/orders`: только заказы с явной серверной связью `orders.customer_id`; совпадение гостевого телефона/почты не даёт доступ к заказу.
- `/account/events`: календарь месяца, ближайшие события, создание/изменение/удаление, несколько событий на дату, ежегодное или однократное повторение. Для 29 февраля выбирается 28 февраля или 1 марта в обычный год. Сроки напоминаний: 7, 2, 1, 0 дней; по умолчанию 2 дня.
- `/account/notifications`: отдельное согласие на напоминания, включение SMS и отдельная настройка включения названия события в текст. Номер получателя подарка не запрашивается.
- `/account/privacy`: точные версии согласий, предоставление/отзыв, JSON-экспорт и удаление аккаунта. Отзыв календаря удаляет его содержимое и отменяет напоминания.

После заказа предлагается самостоятельно сохранить дату. Событие не создаётся автоматически из сведений получателя. Все страницы кабинета закрыты от поисковой индексации и кеширования.

## Установка и откат

1. Сохранить код и закрытые конфигурации отдельно. Проверить PHP 8.1+, расширения PDO MySQL, mbstring, cURL, доступ к MariaDB и TLS CA.
2. Из корня проекта запустить:

   ```powershell
   & 'C:\OSPanel\modules\php\PHP_8.1\php.exe' tools\migrate-customer.php --apply
   ```

   Команда сначала создаёт полную приватную SQL-копию и отчёт в `storage/backups/customer-schema-*`, затем применяет повторяемые миграции. Хеши исходных столбцов/строк проверяются после миграции. При любой ошибке работу продолжать только после выяснения причины. Миграции не запускаются из HTTP.

3. Развернуть `account.php`, `public/account.php`, ассеты кабинета, обновлённые `public/index.php`, `public/api.php`, `public/admin.php`, `public/assets/app.js`, `.htaccess`, `router.php`, bootstrap/OrderService и файлы классов/миграций. Корневой `account.php` нужен Apache; одного файла в `public/` недостаточно.
4. Подключить фоновые задания по [инструкции планировщика](sms-scheduler.md), затем выполнить тесты ниже.
5. Настроить интеграцию и пройти эксплуатационные проверки по [инструкции RocketSMS](sms-setup.md). Секреты вводятся на сервере или владельцем через админку, не в чат и не в frontend.

При откате сначала выключить SMS и задание. Сохранить актуальные данные, очередь и доказательства согласий. Предпочтительный откат — прежний код с сохранением новых таблиц. Полное восстановление SQL делать в отдельную пустую БД и проверять до переключения приложения: копия содержит состояние на момент её создания, более новые покупки/согласия нельзя потерять. Нельзя удалять журналы для удобства отката. Копии и конфигурации содержат персональные данные/секреты; ограничить доступ, обеспечить защищённое хранение и утверждённый срок хранения.

## Администрирование

`/admin.php?page=sms`: показатели событий/подписок/очереди, отправки и подтверждённые доставки отдельно, технические ошибки и последний запуск планировщика. В журнале нет названий событий, текста SMS или полного номера.

`admins.role`: `owner` управляет настройками, проверкой аккаунта и контролируемым тестом; `manager` и `support` видят только показатели и маскированный журнал SMS. Роли проверяются на каждом серверном действии. Существующий администратор получает `owner`; управление сотрудниками через отдельный интерфейс в этот модуль не входит. Произвольной или массовой отправки нет. Тест разрешён только владельцу и ID покупателей из серверного списка, с их актуальными согласиями, подтверждённым номером и включёнными уведомлениями.

Владелец также может явно включить пропуск юридической проверки только для SMS. По умолчанию он выключен, изменение журналируется, а статус проверки документов сохраняется отдельно. Другие условия отправки и правила оформления заказов остаются действующими; подробности — в [инструкции интеграции](sms-setup.md#пропуск-юридической-проверки).

## Проверки

```powershell
& 'C:\OSPanel\modules\php\PHP_8.1\php.exe' tests\customer-calendar.php
& 'C:\OSPanel\modules\php\PHP_8.1\php.exe' tests\sms.php
& 'C:\OSPanel\modules\php\PHP_8.1\php.exe' tests\customer-http.php
& 'C:\OSPanel\modules\php\PHP_8.1\php.exe' tests\customer-phone-http.php
$env:LAFLEUR_TEST_URL='http://flowers.local'
& 'C:\OSPanel\modules\php\PHP_8.1\php.exe' tests\customer-http.php
Remove-Item Env:LAFLEUR_TEST_URL
& 'C:\OSPanel\modules\php\PHP_8.1\php.exe' tests\backend.php
& 'C:\OSPanel\modules\php\PHP_8.1\php.exe' tests\seo.php
& 'C:\OSPanel\modules\php\PHP_8.1\php.exe' tests\routes.php http://flowers.local
```

SMS-тесты используют подменённого провайдера и временные таблицы. HTTP-тесты разрешены только на localhost при выключенной отправке и `dry_run=true`: создают собственные записи/закрытые серверные сессии, проверяют авторизацию, CSRF, IDOR, CRUD, согласия, экспорт и роли, затем удаляют свои фикстуры. `customer-phone-http.php` дополнительно проходит реальный контроллер запросом/проверкой OTP, повторным входом и сменой телефона: отдельный временный loopback-сервер использует закрытую копию контроллера и подменённого провайдера без сетевых методов. Код перехватывается только приватным тестовым процессом и не возвращается через HTTP. Рабочие файлы/настройки не меняются. Производственного обхода OTP нет; настоящий вход и доставка требуют отдельной проверки с подключённым RocketSMS. Не выполнять такие проверки против боевой базы с активной очередью. Каталог проверяется отдельно существующим `tests/catalog-import.php` с исходным манифестом.

## Перед рабочим запуском

Учётные данные RocketSMS, зарегистрированный отправитель, применимость выбранного API и юридические документы должны быть проверены ответственным лицом. Для напоминаний дополнительно требуется подтверждение категории и фактическая доставка контролируемого напоминания со статусом DELIVERED. Международные направления проверяются отдельно. Открытие магазина/индексации не следует из установки кабинета: прежние требования регистрации магазина и реквизитов остаются действующими. Подробные источники и незавершённые юридические проверки перечислены в [правовой записке](account-legal-research.md). Модуль не объявляет документы юридически утверждёнными.
