Files
portal/app/tests/Support/Imitation/ConditionLevers.php
T

144 lines
5.8 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
namespace Tests\Support\Imitation;
use App\Models\Project;
use App\Models\Tenant;
use Illuminate\Support\Facades\DB;
/**
* Рычаги условий для имитационного стенда (Phase 1).
*
* Напрямую пишет в реальные колонки, которые читает прод-код:
* - tenants.balance_rub — LedgerService (bcmath balance_rub*100 >= price)
* - tenants.frozen_by_balance_at — PreflightBalanceService (NULL = активен)
* - projects.is_active — SnapshotRebuildCommand eligibility
* - projects.delivered_today — LeadRouter остаток лимита
* - projects.delivery_days_mask — LeadRouter / SnapshotRebuildCommand
* - projects.regions — LeadRouter regional cascade
* - projects.preflight_blocked_at — SnapshotRebuildCommand eligibility
*
* Имена колонок подтверждены чтением db/schema.sql и прод-кода:
* - balance_rub: tenants, DECIMAL(12,2) DEFAULT 0
* - frozen_by_balance_at: tenants, TIMESTAMPTZ NULL (NULL = не заморожен)
* - regions: projects, INT[] NOT NULL DEFAULT '{}' (порядковые коды 1..89, НЕ ГИБДД)
* - delivery_days_mask: projects, INT NOT NULL DEFAULT 127 (bit 0=Пн..bit 6=Вс)
* - daily_limit_target: projects, INT NOT NULL DEFAULT 10
* - delivered_today: projects, INT (остаток лимита)
* - preflight_blocked_at: projects, TIMESTAMPTZ NULL
*
* Коды субъектов — ПОРЯДКОВЫЕ 1..89 (конституционный порядок), НЕ коды ГИБДД.
* Использовать только через App\Support\RussianRegions::CODE_TO_NAME / nameToCode().
* Например: Москва = 82, Санкт-Петербург = 83.
*
* Task 3 — Phase 1 Portal Client Imitation Harness.
* Spec: docs/superpowers/specs/2026-06-03-portal-client-imitation-phase1-design.md
*/
final class ConditionLevers
{
/**
* Установить баланс тенанта (в рублях, как DECIMAL(12,2)).
*
* @param int|float|string $rub Сумма в рублях (например 500.00 или 0).
*/
public static function setBalance(Tenant $tenant, int|float|string $rub): void
{
DB::table('tenants')
->where('id', $tenant->id)
->update(['balance_rub' => $rub]);
}
/**
* Обнулить баланс тенанта до 0 (лид не пройдёт LedgerService::chargeForDelivery).
*/
public static function drainBalance(Tenant $tenant): void
{
self::setBalance($tenant, 0);
}
/**
* Выставить delivered_today = daily_limit_target у проекта,
* чтобы LeadRouter не считал его eligible (лимит исчерпан).
*
* LeadRouter: `projects.delivered_today < snap.daily_limit` — равенство = не eligible.
*/
public static function fillToLimit(Project $project): void
{
$limit = (int) DB::table('projects')
->where('id', $project->id)
->value('daily_limit_target');
DB::table('projects')
->where('id', $project->id)
->update(['delivered_today' => $limit]);
}
/**
* Приостановить проект: is_active = false + paused_at = NOW().
*
* SnapshotRebuildCommand исключает проекты с is_active = false из нового snapshot.
* (Проверено: команда WHERE p.is_active = true.)
*/
public static function pause(Project $project): void
{
DB::table('projects')
->where('id', $project->id)
->update([
'is_active' => false,
'paused_at' => now(),
]);
}
/**
* Заморозить тенанта по балансу: frozen_by_balance_at = NOW().
*
* LeadRouter: WHERE tenants.frozen_by_balance_at IS NULL — заморожен = не eligible.
* SnapshotRebuildCommand: WHERE t.frozen_by_balance_at IS NULL — не попадёт в snapshot.
*/
public static function freeze(Tenant $tenant): void
{
DB::table('tenants')
->where('id', $tenant->id)
->update(['frozen_by_balance_at' => now()]);
}
/**
* Установить регионы проекта (порядковые коды 1..89, НЕ коды ГИБДД).
*
* LeadRouter Фаза 1: exact — ?::int = ANY(snap.regions).
* Пустой массив = «вся РФ» (LeadRouter Фаза 2, regions = '{}').
*
* Пример: [82] = только Москва, [82, 83] = Москва + СПб, [] = вся РФ.
* Коды через App\Support\RussianRegions::CODE_TO_NAME / nameToCode().
*
* @param array<int> $codes Порядковые коды субъектов (1..89).
*/
public static function setRegions(Project $project, array $codes): void
{
// PostgreSQL int[] литерал: '{82,83}' или '{}'.
$pgArray = '{'.implode(',', array_map('intval', $codes)).'}';
DB::table('projects')
->where('id', $project->id)
->update(['regions' => $pgArray]);
}
/**
* Установить битмаску дней приёма лидов.
*
* Бит 0 (1) = Понедельник, бит 6 (64) = Воскресенье.
* 127 = все 7 дней; 31 = Пн–Пт; 96 = Сб+Вс.
* SnapshotRebuildCommand: WHERE (p.delivery_days_mask & weekdayBit) <> 0.
*
* @param int $mask Битмаска 0..127.
*/
public static function setDays(Project $project, int $mask): void
{
DB::table('projects')
->where('id', $project->id)
->update(['delivery_days_mask' => $mask]);
}
}