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

144 lines
5.8 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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]);
}
}