Перейти к содержанию

Ключевые проектные решения

Здесь собраны архитектурные решения, которые не выводятся напрямую из кода и важны для понимания системы.

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 даёт ложные падения — отсюда жёсткий регламент очистки кэша (см. Инфраструктура).