Files
portal/app/database/factories/ProjectFactory.php
T
Дмитрий 99afcbc25c feat(models): extend Project with signal_type, sms_senders, supplier_b1/b2/b3 relations + scopes
- app/Models/Project.php — добавлены fillable+casts для supplier integration:
  signal_type, signal_identifier, sms_senders (jsonb array), sms_keyword,
  delivered_in_month, supplier_b{1,2,3}_project_id.
  + supplierB1/B2/B3() BelongsTo relations на SupplierProject (sharing-model).
  + scopeActiveOnDay($iso) — bitmask проверка по delivery_days_mask
    (bit 0 = Mon, bit 6 = Sun; ISO=1 → 1<<0 = 1; ISO=7 → 1<<6 = 64).
  + scopeForSignal($type, $identifier) — фильтр по сигналу (для роутинга в Plan 2).
- database/factories/ProjectFactory.php — defaults null/0 для новых полей
  (CHECK constraints не нарушаются: signal_type IS NULL → остальные опциональны).
  + state-методы asSiteSignal($domain), asCallSignal($phone), asSmsSignal($senders, $keyword).
- tests/Feature/Models/ProjectExtensionsTest.php — 6 тестов: signal_type fillable,
  sms_senders array cast + sms_keyword, SMS без keyword, supplierB1/B2/B3 relations,
  scopeActiveOnDay (bitmask Mon/Sat), scopeForSignal (3 сигнала + edge-case).

Pest: 469 / 467 passed / 2 skipped (461 + 6 новых = 467, с retry на transient
PG connection issues — на параллельных тестах с testing_rls_user GRANT тяжёл).
Larastan: 0 errors. Pint passed.

Spec: §2.1
Plan: Task 10

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 16:59:53 +03:00

82 lines
2.3 KiB
PHP

<?php
declare(strict_types=1);
namespace Database\Factories;
use App\Models\Project;
use App\Models\Tenant;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<Project>
*/
class ProjectFactory extends Factory
{
/**
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'tenant_id' => Tenant::factory(),
'name' => fake()->words(3, true),
'type' => 'webhook',
'is_active' => true,
'daily_limit_target' => 10,
'region_mask' => 255,
'region_mode' => 'include',
'delivery_days_mask' => 127,
'assignment_strategy' => 'manual',
'ttfr_target_minutes' => 15,
// Supplier integration (Plan 1/5 Task 1+10) — по умолчанию пусто,
// CHECK constraint'ы projects не нарушаются (signal_type IS NULL → все остальные опциональны).
'signal_type' => null,
'signal_identifier' => null,
'sms_senders' => null,
'sms_keyword' => null,
'delivered_in_month' => 0,
'supplier_b1_project_id' => null,
'supplier_b2_project_id' => null,
'supplier_b3_project_id' => null,
];
}
/**
* Сайт-сигнал с указанным доменом.
*/
public function asSiteSignal(string $domain): self
{
return $this->state([
'signal_type' => 'site',
'signal_identifier' => $domain,
]);
}
/**
* Звонок-сигнал с указанным телефоном.
*/
public function asCallSignal(string $phone): self
{
return $this->state([
'signal_type' => 'call',
'signal_identifier' => $phone,
]);
}
/**
* SMS-сигнал. Sender-имена в массиве (1+); keyword optional (пусто = только B3).
*
* @param array<int, string> $senders
*/
public function asSmsSignal(array $senders, ?string $keyword = null): self
{
return $this->state([
'signal_type' => 'sms',
'signal_identifier' => null,
'sms_senders' => $senders,
'sms_keyword' => $keyword,
]);
}
}