Egy beszállítóhoz egyszerre legfeljebb egy nyitott végrehajtás tartozhat: egy execution_failed fájl után a termékek félig frissített állapotban vannak, egy új import erre a kevert alapra rétegződne rá (és a validálás is ehhez számolná a diffeket); két párhuzamos végrehajtás pedig nem determinisztikus eredményt adna. - új PricelistGuard service: egyetlen igazságforrás a blokkoláshoz - mind a NÉGY belépési pont véd: modern Filament create, régebbi PriceListProcessor oldal, legacy Admin\PriceListController import, konzol parancs. A legacy import a legfontosabb - az közvetlenül ír a products táblába, megkerülve a modult, és a beszállítókat ma még nagyrészt ott kezelik. - az `inprogress` önmagában nem blokkol: az előfeldolgozás és a validálás egyetlen terméket sem ír, csak a már elindult végrehajtás számít - a beszállító nem tűnik el a select listából, hanem konkrét magyarázatot kap a felhasználó arról, melyik fájl blokkol és miért - blokkolt beszállítónál másik fájl jóváhagyása sem indítható Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
134 lines
5.2 KiB
PHP
134 lines
5.2 KiB
PHP
<?php
|
|
|
|
use App\Enums\PricelistFileLineStatusEnum;
|
|
use App\Enums\PricelistFileStatusEnum;
|
|
use App\Enums\PricelistWorkflowStep;
|
|
use App\Models\PricelistFile;
|
|
use App\Models\PricelistFileLine;
|
|
use App\Models\Supplier;
|
|
use App\Services\PricelistFileProcessService;
|
|
use App\Services\PricelistGuard;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Queue;
|
|
use Tests\TestCase;
|
|
|
|
uses(TestCase::class, RefreshDatabase::class);
|
|
|
|
function guardTestFile(Supplier $supplier, PricelistFileStatusEnum $status, string $executionStepStatus, array $attributes = []): PricelistFile
|
|
{
|
|
return PricelistFile::create(array_merge([
|
|
'filename' => 'arlista.xlsx',
|
|
'supplier_id' => $supplier->id,
|
|
'available_date' => now()->addWeek()->toDateString(),
|
|
'status' => $status,
|
|
'workflow_steps' => [
|
|
['name' => PricelistWorkflowStep::Preprocessing->value, 'label' => 'Előfeldolgozás', 'status' => 'completed'],
|
|
['name' => PricelistWorkflowStep::Validation->value, 'label' => 'Validálás', 'status' => 'completed'],
|
|
['name' => PricelistWorkflowStep::Approval->value, 'label' => 'Jóváhagyás', 'status' => 'completed'],
|
|
['name' => PricelistWorkflowStep::Execution->value, 'label' => 'Végrehajtás', 'status' => $executionStepStatus],
|
|
],
|
|
], $attributes));
|
|
}
|
|
|
|
beforeEach(function () {
|
|
$this->supplier = Supplier::factory()->create();
|
|
$this->guard = app(PricelistGuard::class);
|
|
});
|
|
|
|
test('a hibára futott végrehajtás blokkolja a beszállítót', function () {
|
|
$blocking = guardTestFile($this->supplier, PricelistFileStatusEnum::execution_failed, 'failed');
|
|
|
|
$found = $this->guard->blockingFileFor($this->supplier->id);
|
|
|
|
expect($found)->not->toBeNull()
|
|
->and($found->id)->toBe($blocking->id)
|
|
->and($this->guard->blockingMessage($found))->toContain('hibára futott');
|
|
});
|
|
|
|
test('a futó végrehajtás blokkolja a beszállítót', function () {
|
|
guardTestFile($this->supplier, PricelistFileStatusEnum::inprogress, 'inprogress');
|
|
|
|
expect($this->guard->isBlocked($this->supplier->id))->toBeTrue();
|
|
});
|
|
|
|
test('a validálás alatt álló fájl NEM blokkol', function () {
|
|
// Az előfeldolgozás és a validálás egyetlen terméket sem ír, ezért közben
|
|
// nyugodtan feltölthető másik árlista ugyanahhoz a beszállítóhoz.
|
|
guardTestFile($this->supplier, PricelistFileStatusEnum::inprogress, 'pending');
|
|
|
|
expect($this->guard->isBlocked($this->supplier->id))->toBeFalse();
|
|
});
|
|
|
|
test('a lezárt és elkészült fájlok nem blokkolnak', function () {
|
|
guardTestFile($this->supplier, PricelistFileStatusEnum::done, 'completed');
|
|
guardTestFile($this->supplier, PricelistFileStatusEnum::closed, 'reverted');
|
|
|
|
expect($this->guard->isBlocked($this->supplier->id))->toBeFalse();
|
|
});
|
|
|
|
test('a blokkolás beszállítónként külön él', function () {
|
|
guardTestFile($this->supplier, PricelistFileStatusEnum::execution_failed, 'failed');
|
|
|
|
$otherSupplier = Supplier::factory()->create();
|
|
|
|
expect($this->guard->isBlocked($this->supplier->id))->toBeTrue()
|
|
->and($this->guard->isBlocked($otherSupplier->id))->toBeFalse();
|
|
});
|
|
|
|
test('a fájl önmagát nem blokkolja', function () {
|
|
$file = guardTestFile($this->supplier, PricelistFileStatusEnum::execution_failed, 'failed');
|
|
|
|
expect($this->guard->isBlocked($this->supplier->id, $file->id))->toBeFalse();
|
|
});
|
|
|
|
test('a visszavonás után a beszállító újra felszabadul', function () {
|
|
$file = guardTestFile($this->supplier, PricelistFileStatusEnum::execution_failed, 'failed');
|
|
|
|
expect($this->guard->isBlocked($this->supplier->id))->toBeTrue();
|
|
|
|
$file->update(['status' => PricelistFileStatusEnum::closed]);
|
|
|
|
expect($this->guard->isBlocked($this->supplier->id))->toBeFalse();
|
|
});
|
|
|
|
test('blokkolt beszállítónál másik fájl nem hagyható jóvá', function () {
|
|
Queue::fake();
|
|
|
|
guardTestFile($this->supplier, PricelistFileStatusEnum::execution_failed, 'failed');
|
|
|
|
$waiting = guardTestFile($this->supplier, PricelistFileStatusEnum::waiting_for_approval, 'pending', [
|
|
'filename' => 'masodik.xlsx',
|
|
]);
|
|
|
|
PricelistFileLine::create([
|
|
'pricelist_file_id' => $waiting->id,
|
|
'row_number' => 5,
|
|
'status' => PricelistFileLineStatusEnum::new_product,
|
|
'payload' => [],
|
|
]);
|
|
|
|
// A fájl önmagában jóváhagyható lenne - csak a beszállítói blokkolás állítja meg.
|
|
expect($waiting->canBeApproved())->toBeTrue()
|
|
->and(app(PricelistFileProcessService::class)->approve($waiting))->toBeFalse();
|
|
|
|
Queue::assertNothingPushed();
|
|
|
|
expect($waiting->refresh()->status)->toBe(PricelistFileStatusEnum::waiting_for_approval);
|
|
});
|
|
|
|
test('a konzolos indítás is elakad blokkolt beszállítónál', function () {
|
|
Queue::fake();
|
|
|
|
guardTestFile($this->supplier, PricelistFileStatusEnum::execution_failed, 'failed');
|
|
|
|
$todo = guardTestFile($this->supplier, PricelistFileStatusEnum::todo, 'pending', [
|
|
'filename' => 'harmadik.xlsx',
|
|
]);
|
|
|
|
$this->artisan('pricelist:process', ['id' => $todo->id])
|
|
->expectsOutputToContain('nem indítható újabb árlista-feldolgozás')
|
|
->assertExitCode(1);
|
|
|
|
Queue::assertNothingPushed();
|
|
});
|