Ключевые проектные решения¶
Здесь собраны архитектурные решения, которые не выводятся напрямую из кода и важны для понимания системы.
1. Inertia + Vue для кабинетов, Filament — только для админки¶
Стек выбран заказчиком: Filament обслуживает исключительно админ-панель СРМ (/admin), а кабинеты посредника и клиента — это Inertia + Vue 3 SPA. Это разделение проходит через всю кодовую базу: контроллеры Agent\* / Client\* рендерят Inertia-страницы, а app/Filament/* — ресурсы админки.
2. Мультиарендность через agent_id + глобальный scope¶
Все доменные сущности принадлежат посреднику (agent_id). Трейт BelongsToAgent добавляет глобальный scope: когда активен guard agent, запросы автоматически фильтруются по agent_id текущего сотрудника, а при создании agent_id проставляется автоматически.
Почему не на auth-моделях
Трейт намеренно не применяется к самим auth-моделям (AgentStaff, Agent), чтобы избежать рекурсии при резолве пользователя. Скоупинг идёт через agent_staff.agent_id — у самого agents своего agent_id нет, это и есть арендатор.
3. Система прав ПДИУ (Permissions × Levels × Scope)¶
access_objects— объекты доступа (заказы, клиенты, сотрудники, …), различаются поside(agent/client).access_groups— группы прав, клонируются каждому посреднику из эталонных (is_reference). Владелец получает группу «Администратор» с полным доступом.access_group_permissions— матрица:can_view / can_add / can_edit / can_delete+scope(own/foreign/all).- Проверка:
AgentStaff::hasPermission(code, level, ?model).own→ сверка сassigned_manager_id.
Группы редактируются на стороне посредника (/agent/access-groups), а не в админке, так как они per-agent.
4. «Код вне вебрута» (ограничение ISPmanager)¶
Docroot в ISPmanager не редактируется (basedir locked). Поэтому применён паттерн разделения:
- Код приложения:
/var/www/www-root/data/taomaster/(vendor, app, .env, storage). - Вебрут:
/var/www/www-root/data/www/test142.ru/— содержимоеpublic/+ кастомныйindex.php, который подключает автозагрузчик из каталога кода и вызывает$app->usePublicPath(__DIR__). - Vite собирает ассеты прямо в вебрут (
publicDirectory: '../www/test142.ru').
5. Поддомен посредника обязателен¶
Каждый посредник получает уникальный subdomain (agents.subdomain). После входа авторизованный посредник/клиент редиректится на поддомен {sub}.test142.ru (middleware ResolveAgentSubdomain + трейт RedirectsToSubdomain).
SESSION_DOMAIN=.test142.ru
Чтобы сессионная cookie работала и на апексе, и на поддоменах, SESSION_DOMAIN установлен в .test142.ru (общий для всех поддоменов). Без этого редирект на поддомен выкидывал на логин.
6. Единый телефон-вход (клиент + посредник)¶
Вход существующего пользователя — по паролю (OTP оставлен как альтернатива). Подтверждение телефона при регистрации: Telegram — по deeplink /start <token> (без «поделиться контактом»), MAX — кодом. Регистрация клиента возможна только в контексте посредника (реф-ссылка ?agent= / ?hash= или поддомен).
Безопасность восстановления ужесточена: код шлётся только в уже подключённый канал; привязать новый мессенджер во время восстановления нельзя (закрыт вектор угона аккаунта).
7. Расчёт по данным посредника, оплата — через баланс¶
- При проверке заказа посредник может переопределить цену/количество/доставку — значения пишутся в
agent_*-колонкиorder_items, не затирая клиентские. Расчёт идёт по эффективным значениям (agent_* ?? client), клиентские показываются зачёркнутыми. - Оплата ≠ списание в заказ. «Реквизиты оплачены» пополняет общий ¥-баланс клиента. Отдельное действие «Оплатить с баланса» списывает сумму заказа и двигает его в выкуп. Не хватает баланса → «Запросить пополнение».
- Чат используется только для уведомлений — приём оплаты в чат намеренно не переносился (решение заказчика).
8. Деньги: единый леджер с блокировкой¶
Все движения средств идут через LedgerService — единственную точку, использующую lockForUpdate для атомарности. У клиента два независимых счёта (RUR / CNY); оплаты заказов — в CNY. Каждое движение фиксируется в balance_ops с остатком после операции.
9. Мультивалютность: учёт в ¥/$, отображение в валюте клиента¶
Внутренний учёт и комиссии — в ¥ (и $ для цен доставки). Клиент видит суммы в display_currency (валюта его страны). Конвертация — через курсы посредника. USD — третья расчётная валюта (только цены доставки/услуг); балансы остаются RUR/CNY, итог сводится в ₽.
10. Сетевая доступность с РФ-сервера¶
- Telegram (
api.telegram.org) заблокирован для прямого egress → все вызовы идут через HTTP-прокси с фейловером (TELEGRAM_PROXIES). - MAX (
botapi.max.ru) доступен напрямую. - bestchange и ЦБ РФ доступны напрямую.
11. Локализация: статика + автоперевод динамики¶
- Статические UI-строки:
$t('key', 'RU fallback')— RU всегда из фолбэка, EN/CN подгружаются. - Динамические данные (имена статусов, справочники, тексты посредника): Google Translate на лету, с кэшем в
translation_cache. - Локальная таблица
translations— overlay поверх удалённого словаря; длинные тексты (is_long) правятся в админке и не перезатираются. - Каталоги-справочники переводятся на язык зрителя на выводе (cn-посредник ввёл → русскому клиенту показывается по-русски).
12. Тесты на SQLite, прод-кэш — враг тестов¶
Тесты форсируют SQLite in-memory через phpunit.xml. Боевые данные защищены. Но закэшированный прод-конфиг при php artisan test без optimize:clear даёт ложные падения — отсюда жёсткий регламент очистки кэша (см. Инфраструктура).