Files
portal/app/app/Console/Commands/ResetDeliveredTodayCommand.php
T
Дмитрий 6d6181b8cc feat(supplier): Plan 3 Task 3 — switch supplier-flow на pgsql_supplier (BYPASSRLS)
Закрывает 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.
2026-05-11 01:00:47 +03:00

37 lines
1.3 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
/**
* Сброс projects.delivered_today=0 для всех tenant'ов.
*
* Spec: docs/superpowers/specs/2026-05-10-supplier-integration-design.md §6.1 +
* docs/superpowers/specs/2026-05-11-plan3-supplier-sync-design.md §1.
* Расписание: каждый день в 00:00 МСК (timezone Europe/Moscow).
*
* Plan 3 Task 3 (WARN #3): UPDATE идёт через connection `pgsql_supplier`
* (BYPASSRLS-роль crm_supplier_worker), что позволяет одним statement'ом сбросить
* счётчики по всем tenant'ам без SET LOCAL app.current_tenant_id для каждого.
*/
class ResetDeliveredTodayCommand extends Command
{
protected $signature = 'projects:reset-delivered-today';
protected $description = 'Сброс projects.delivered_today=0 (00:00 МСК cron, spec §6.1)';
public function handle(): int
{
$affected = DB::connection('pgsql_supplier')
->update('UPDATE projects SET delivered_today = 0 WHERE delivered_today <> 0');
$this->info("Reset delivered_today on {$affected} project(s).");
return self::SUCCESS;
}
}