6d6181b8cc
Закрывает 3 backlog-айтема Plan 2.6 одной правкой:
- BLOCKER #6: failed_webhook_jobs INSERT с tenant_id=NULL теперь проходит
(BYPASSRLS обходит RLS-политику отвергавшую NULL под обычной ролью)
- WARN #2: LeadRouter::matchEligibleProjects видит projects всех tenant'ов
через Project::on('pgsql_supplier') без SET LOCAL app.current_tenant_id
- WARN #3: ResetDeliveredTodayCommand обновляет projects всех tenant'ов
через DB::connection('pgsql_supplier')
Архитектура: crm_supplier_worker BYPASSRLS-роль (создана Plan 2.6 #iv 7899071)
+ новый pgsql_supplier connection в config/database.php. WHERE(tenant_id=)
фильтры сохраняются как defense-in-depth.
Уточнение по Job's $connection: оригинальный план предполагал public $connection
= 'pgsql_supplier' на RouteSupplierLeadJob, но в Laravel Job's $connection
управляет очередью (sync/database/redis), не БД. Заменено на константу
RouteSupplierLeadJob::DB_CONNECTION + явный DB::connection(self::DB_CONNECTION)
в failed() callback'е. Это:
1) не ломает queue resolution (без этой правки тесты падают
'pgsql_supplier queue connection has not been configured')
2) явно документирует intent — failed_webhook_jobs INSERT идёт через BYPASSRLS
3) handle()'s tenant-scoped транзакции остаются на default pgsql + SET LOCAL,
где RLS нужна для defense-in-depth.
Также добавлено в tests/TestCase.php разделение PDO между pgsql и
pgsql_supplier connection'ами через setPdo/setReadPdo — иначе DatabaseTransactions
не откатывал бы supplier-side данные (две PDO-сессии = две независимые транзакции,
supplier не видит uncommitted INSERTs default-side).
Brainstorm decision: вариант C из 3 опций (A=schema bump, B=отдельная таблица,
C=BYPASSRLS-role). См. docs/superpowers/specs/2026-05-11-plan3-supplier-sync-design.md §1.
+4 теста в Feature/Supplier/SupplierConnectionTest.php (DB_CONNECTION constant +
BLOCKER#6 + WARN#2 + WARN#3). 0 schema changes.
Pest: 562/560 + 2 skipped (baseline 558/556 + 4 new = 562/560, ok). PHPStan: 0 errors
(добавлен 1 baseline entry для известного Pest+PHPStan limitation на artisan()).
Pint: clean.
81 lines
1.7 KiB
Bash
81 lines
1.7 KiB
Bash
APP_NAME=Liderra
|
|
APP_ENV=local
|
|
APP_KEY=
|
|
APP_DEBUG=true
|
|
APP_URL=http://localhost
|
|
|
|
APP_LOCALE=ru
|
|
APP_FALLBACK_LOCALE=ru
|
|
APP_FAKER_LOCALE=ru_RU
|
|
APP_TIMEZONE=Europe/Moscow
|
|
|
|
APP_MAINTENANCE_DRIVER=file
|
|
# APP_MAINTENANCE_STORE=database
|
|
|
|
# PHP_CLI_SERVER_WORKERS=4
|
|
|
|
BCRYPT_ROUNDS=12
|
|
|
|
LOG_CHANNEL=stack
|
|
LOG_STACK=single
|
|
LOG_DEPRECATIONS_CHANNEL=null
|
|
LOG_LEVEL=debug
|
|
|
|
DB_CONNECTION=pgsql
|
|
DB_HOST=127.0.0.1
|
|
DB_PORT=5432
|
|
DB_DATABASE=liderra
|
|
DB_USERNAME=postgres
|
|
DB_PASSWORD=
|
|
|
|
# Supplier Sync (Plan 3 Task 3) — crm_supplier_worker BYPASSRLS connection.
|
|
# На production указывают на роль crm_supplier_worker (создана db/00_create_roles.sql v1.1).
|
|
# Если не заданы — fallback на DB_USERNAME / DB_PASSWORD (dev: postgres superuser, BYPASSRLS implicit).
|
|
DB_SUPPLIER_USERNAME=crm_supplier_worker
|
|
DB_SUPPLIER_PASSWORD=
|
|
|
|
# Supplier Portal credentials (Playwright login — Task 5)
|
|
SUPPLIER_LOGIN=
|
|
SUPPLIER_PASSWORD=
|
|
SUPPLIER_PORTAL_URL=https://crm.bp-gr.ru
|
|
|
|
# Supplier alerts (email через Unisender Go relay)
|
|
SUPPLIER_ALERT_EMAIL=
|
|
|
|
SESSION_DRIVER=database
|
|
SESSION_LIFETIME=120
|
|
SESSION_ENCRYPT=false
|
|
SESSION_PATH=/
|
|
SESSION_DOMAIN=null
|
|
|
|
BROADCAST_CONNECTION=log
|
|
FILESYSTEM_DISK=local
|
|
QUEUE_CONNECTION=database
|
|
|
|
CACHE_STORE=database
|
|
# CACHE_PREFIX=
|
|
|
|
MEMCACHED_HOST=127.0.0.1
|
|
|
|
REDIS_CLIENT=predis
|
|
REDIS_HOST=127.0.0.1
|
|
REDIS_PASSWORD=null
|
|
REDIS_PORT=6379
|
|
|
|
MAIL_MAILER=log
|
|
MAIL_SCHEME=null
|
|
MAIL_HOST=127.0.0.1
|
|
MAIL_PORT=2525
|
|
MAIL_USERNAME=null
|
|
MAIL_PASSWORD=null
|
|
MAIL_FROM_ADDRESS="hello@example.com"
|
|
MAIL_FROM_NAME="${APP_NAME}"
|
|
|
|
AWS_ACCESS_KEY_ID=
|
|
AWS_SECRET_ACCESS_KEY=
|
|
AWS_DEFAULT_REGION=us-east-1
|
|
AWS_BUCKET=
|
|
AWS_USE_PATH_STYLE_ENDPOINT=false
|
|
|
|
VITE_APP_NAME="${APP_NAME}"
|