204 lines
8.1 KiB
PHP
204 lines
8.1 KiB
PHP
|
|
<?php
|
|||
|
|
|
|||
|
|
declare(strict_types=1);
|
|||
|
|
|
|||
|
|
use App\Models\Project;
|
|||
|
|
use App\Models\SupplierProject;
|
|||
|
|
use App\Models\Tenant;
|
|||
|
|
use App\Models\User;
|
|||
|
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|||
|
|
use Illuminate\Support\Facades\DB;
|
|||
|
|
use Tests\Concerns\SharesSupplierPdo;
|
|||
|
|
|
|||
|
|
uses(DatabaseTransactions::class, SharesSupplierPdo::class);
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Прод-баг 29.07.2026: карточка проекта показывала «Готовим к запуску» (жёлтое)
|
|||
|
|
* у проектов, чей заказ у поставщика реально стоит. 5 из 9 работающих проектов
|
|||
|
|
* на бою (t2 ×3, t9, t10), самый старый врал 13 дней.
|
|||
|
|
*
|
|||
|
|
* Причина: aggregateSyncStatus() читал ТОЛЬКО три legacy-колонки
|
|||
|
|
* supplier_b{1,2,3}_project_id, а ночной SyncSupplierProjectsJob (единственный,
|
|||
|
|
* кто в режиме batch реально заводит заказ) пишет ТОЛЬКО в pivot
|
|||
|
|
* project_supplier_links и этих колонок не касается вообще.
|
|||
|
|
*
|
|||
|
|
* Здесь фиксируем: источник истины для статуса — pivot; legacy-колонки читаются
|
|||
|
|
* дополнительно (объединение), чтобы не потерять проекты, у которых заполнены
|
|||
|
|
* только они (handleBatch пишет колонку без pivot-строки).
|
|||
|
|
*/
|
|||
|
|
function spOk(string $platform, string $key, string $status = 'ok', ?string $syncedAt = '2026-07-28 15:05:00'): SupplierProject
|
|||
|
|
{
|
|||
|
|
return SupplierProject::query()->create([
|
|||
|
|
'platform' => $platform,
|
|||
|
|
'signal_type' => 'call',
|
|||
|
|
'unique_key' => $key,
|
|||
|
|
'subject_code' => null,
|
|||
|
|
'current_limit' => 7,
|
|||
|
|
'sync_status' => $status,
|
|||
|
|
'last_synced_at' => $syncedAt,
|
|||
|
|
]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function linkPivot(Project $project, SupplierProject $sp): void
|
|||
|
|
{
|
|||
|
|
DB::table('project_supplier_links')->insertOrIgnore([
|
|||
|
|
'project_id' => $project->id,
|
|||
|
|
'supplier_project_id' => $sp->id,
|
|||
|
|
'platform' => $sp->platform,
|
|||
|
|
'subject_code' => null,
|
|||
|
|
]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
it('reports ok when the order exists in the pivot and legacy columns are empty', function (): void {
|
|||
|
|
$project = Project::factory()->create([
|
|||
|
|
'signal_type' => 'call',
|
|||
|
|
'signal_identifier' => '79990000001',
|
|||
|
|
'supplier_b1_project_id' => null,
|
|||
|
|
'supplier_b2_project_id' => null,
|
|||
|
|
'supplier_b3_project_id' => null,
|
|||
|
|
]);
|
|||
|
|
DB::table('project_supplier_links')->where('project_id', $project->id)->delete();
|
|||
|
|
|
|||
|
|
foreach (['B1', 'B2', 'B3'] as $platform) {
|
|||
|
|
linkPivot($project, spOk($platform, '79990000001'));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Состояние, до которого доехал прод: колонки пустые, связки есть.
|
|||
|
|
expect($project->fresh()->supplier_b1_project_id)->toBeNull()
|
|||
|
|
->and(DB::table('project_supplier_links')->where('project_id', $project->id)->count())->toBe(3);
|
|||
|
|
|
|||
|
|
expect($project->fresh()->aggregateSyncStatus())->toBe('ok');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('reports pending when a pivot-linked order is still pending', function (): void {
|
|||
|
|
$project = Project::factory()->create([
|
|||
|
|
'signal_type' => 'call',
|
|||
|
|
'signal_identifier' => '79001112233',
|
|||
|
|
'supplier_b1_project_id' => null,
|
|||
|
|
'supplier_b2_project_id' => null,
|
|||
|
|
'supplier_b3_project_id' => null,
|
|||
|
|
]);
|
|||
|
|
DB::table('project_supplier_links')->where('project_id', $project->id)->delete();
|
|||
|
|
|
|||
|
|
linkPivot($project, spOk('B1', '79001112233'));
|
|||
|
|
linkPivot($project, spOk('B2', '79001112233', 'pending', null));
|
|||
|
|
|
|||
|
|
expect($project->fresh()->aggregateSyncStatus())->toBe('pending');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('reports failed when any pivot-linked order failed', function (): void {
|
|||
|
|
$project = Project::factory()->create([
|
|||
|
|
'signal_type' => 'call',
|
|||
|
|
'signal_identifier' => '79002223344',
|
|||
|
|
'supplier_b1_project_id' => null,
|
|||
|
|
'supplier_b2_project_id' => null,
|
|||
|
|
'supplier_b3_project_id' => null,
|
|||
|
|
]);
|
|||
|
|
DB::table('project_supplier_links')->where('project_id', $project->id)->delete();
|
|||
|
|
|
|||
|
|
linkPivot($project, spOk('B1', '79002223344'));
|
|||
|
|
linkPivot($project, spOk('B2', '79002223344', 'failed'));
|
|||
|
|
|
|||
|
|
expect($project->fresh()->aggregateSyncStatus())->toBe('failed');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('still reports pending when there is no order at all', function (): void {
|
|||
|
|
$project = Project::factory()->create([
|
|||
|
|
'signal_type' => 'call',
|
|||
|
|
'signal_identifier' => '79003334455',
|
|||
|
|
'supplier_b1_project_id' => null,
|
|||
|
|
'supplier_b2_project_id' => null,
|
|||
|
|
'supplier_b3_project_id' => null,
|
|||
|
|
]);
|
|||
|
|
DB::table('project_supplier_links')->where('project_id', $project->id)->delete();
|
|||
|
|
|
|||
|
|
expect($project->fresh()->aggregateSyncStatus())->toBe('pending');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('still reports ok for legacy projects that only have the old columns filled', function (): void {
|
|||
|
|
$sp = spOk('B1', '79004445566');
|
|||
|
|
$project = Project::factory()->create([
|
|||
|
|
'signal_type' => 'call',
|
|||
|
|
'signal_identifier' => '79004445566',
|
|||
|
|
'supplier_b1_project_id' => $sp->id,
|
|||
|
|
]);
|
|||
|
|
DB::table('project_supplier_links')->where('project_id', $project->id)->delete();
|
|||
|
|
|
|||
|
|
expect($project->fresh()->aggregateSyncStatus())->toBe('ok');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('lists supplier links from the pivot when legacy columns are empty', function (): void {
|
|||
|
|
$project = Project::factory()->create([
|
|||
|
|
'signal_type' => 'call',
|
|||
|
|
'signal_identifier' => '79005556677',
|
|||
|
|
'supplier_b1_project_id' => null,
|
|||
|
|
'supplier_b2_project_id' => null,
|
|||
|
|
'supplier_b3_project_id' => null,
|
|||
|
|
]);
|
|||
|
|
DB::table('project_supplier_links')->where('project_id', $project->id)->delete();
|
|||
|
|
|
|||
|
|
foreach (['B1', 'B2', 'B3'] as $platform) {
|
|||
|
|
linkPivot($project, spOk($platform, '79005556677'));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$links = $project->fresh()->getSupplierLinks();
|
|||
|
|
|
|||
|
|
expect($links)->toHaveCount(3)
|
|||
|
|
->and(collect($links)->pluck('platform')->sort()->values()->all())->toBe(['b1', 'b2', 'b3'])
|
|||
|
|
->and(collect($links)->pluck('sync_status')->unique()->all())->toBe(['ok']);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('takes the earliest sync time across pivot-linked orders', function (): void {
|
|||
|
|
$project = Project::factory()->create([
|
|||
|
|
'signal_type' => 'call',
|
|||
|
|
'signal_identifier' => '79006667788',
|
|||
|
|
'supplier_b1_project_id' => null,
|
|||
|
|
'supplier_b2_project_id' => null,
|
|||
|
|
'supplier_b3_project_id' => null,
|
|||
|
|
]);
|
|||
|
|
DB::table('project_supplier_links')->where('project_id', $project->id)->delete();
|
|||
|
|
|
|||
|
|
linkPivot($project, spOk('B1', '79006667788', 'ok', '2026-07-28 15:05:00'));
|
|||
|
|
linkPivot($project, spOk('B2', '79006667788', 'ok', '2026-07-26 15:05:00'));
|
|||
|
|
|
|||
|
|
expect((string) $project->fresh()->aggregateLastSyncedAt())->toStartWith('2026-07-26');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('does not double-count an order linked both by pivot and by a legacy column', function (): void {
|
|||
|
|
$sp = spOk('B1', '79007778899');
|
|||
|
|
$project = Project::factory()->create([
|
|||
|
|
'signal_type' => 'call',
|
|||
|
|
'signal_identifier' => '79007778899',
|
|||
|
|
'supplier_b1_project_id' => $sp->id,
|
|||
|
|
]);
|
|||
|
|
DB::table('project_supplier_links')->where('project_id', $project->id)->delete();
|
|||
|
|
linkPivot($project, $sp);
|
|||
|
|
|
|||
|
|
expect($project->fresh()->getSupplierLinks())->toHaveCount(1);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('shows the project as collecting leads over the API when only the pivot is filled', function (): void {
|
|||
|
|
$tenant = Tenant::factory()->create();
|
|||
|
|
$user = User::factory()->create(['tenant_id' => $tenant->id]);
|
|||
|
|
$project = Project::factory()->create([
|
|||
|
|
'tenant_id' => $tenant->id,
|
|||
|
|
'signal_type' => 'call',
|
|||
|
|
'signal_identifier' => '79008889900',
|
|||
|
|
'is_active' => true,
|
|||
|
|
'preflight_blocked_at' => null,
|
|||
|
|
'supplier_b1_project_id' => null,
|
|||
|
|
'supplier_b2_project_id' => null,
|
|||
|
|
'supplier_b3_project_id' => null,
|
|||
|
|
]);
|
|||
|
|
DB::table('project_supplier_links')->where('project_id', $project->id)->delete();
|
|||
|
|
|
|||
|
|
foreach (['B1', 'B2', 'B3'] as $platform) {
|
|||
|
|
linkPivot($project, spOk($platform, '79008889900'));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$response = $this->actingAs($user)->getJson('/api/projects');
|
|||
|
|
|
|||
|
|
$row = collect($response->json('data'))->firstWhere('id', $project->id);
|
|||
|
|
expect($row['sync_status'])->toBe('ok');
|
|||
|
|
});
|