- PricelistFile::canBeApproved() / hasExecutionStarted(): a jóváhagyás feltételei egy helyen, hogy a felületi gomb és a service ugyanazt ellenőrizze - approve(): a státusz MÉG A DISPATCH ELŐTT billen át, különben a queue-latency alatt a gomb látszana és egy második kattintás párhuzamos importot indítana - reject(): új 'rejected' lépésstátusz - nem 'failed', mert azt az updateStepStatus fail fájlstátuszra fordítaná, az elutasítás viszont closed - a döntés (ki, mikor, milyen indokkal) a file_meta.approval-ba kerül, mert a BaseAuditable a nevével ellentétben nem naplóz - Edit gomb szűkítése hasExecutionStarted()-tel három helyen, köztük az EditPricelistFile::afterSave()-ben, ami a teljes láncot újraindítja Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
206 lines
7.1 KiB
PHP
206 lines
7.1 KiB
PHP
<?php
|
|
|
|
use App\Enums\PricelistFileLineStatusEnum;
|
|
use App\Enums\PricelistFileStatusEnum;
|
|
use App\Enums\PricelistWorkflowStep;
|
|
use App\Filament\Resources\PricelistFiles\Pages\ViewPricelistFile;
|
|
use App\Jobs\PricelistExecutionJob;
|
|
use App\Models\FeatureFlag;
|
|
use App\Models\PricelistFile;
|
|
use App\Models\PricelistFileLine;
|
|
use App\Models\Role;
|
|
use App\Models\Supplier;
|
|
use App\Models\User;
|
|
use App\Services\FeatureFlagRegistrar;
|
|
use App\Services\PricelistFileProcessService;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Queue;
|
|
use Livewire\Livewire;
|
|
use Tests\TestCase;
|
|
|
|
uses(TestCase::class, RefreshDatabase::class);
|
|
|
|
/**
|
|
* Jóváhagyásra váró fájl: a validálás lezárult, a jóváhagyás a soron következő lépés.
|
|
*/
|
|
function approvableFile(array $attributes = []): PricelistFile
|
|
{
|
|
return PricelistFile::create(array_merge([
|
|
'filename' => 'arlista.xlsx',
|
|
'supplier_id' => Supplier::factory()->create()->id,
|
|
'available_date' => now()->addWeek(),
|
|
'status' => PricelistFileStatusEnum::waiting_for_approval,
|
|
'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' => 'inprogress'],
|
|
['name' => PricelistWorkflowStep::Execution->value, 'label' => 'Végrehajtás', 'status' => 'pending'],
|
|
],
|
|
], $attributes));
|
|
}
|
|
|
|
function addLine(PricelistFile $file, PricelistFileLineStatusEnum $status, int $rowNumber = 5): PricelistFileLine
|
|
{
|
|
return PricelistFileLine::create([
|
|
'pricelist_file_id' => $file->id,
|
|
'row_number' => $rowNumber,
|
|
'status' => $status,
|
|
'payload' => [],
|
|
]);
|
|
}
|
|
|
|
function developerUser(): User
|
|
{
|
|
$user = User::factory()->create();
|
|
$user->addRole(Role::create(['name' => 'developer', 'display_name' => 'Developer']));
|
|
|
|
return $user;
|
|
}
|
|
|
|
function definePricelistExecutionFlag(array $attributes = []): void
|
|
{
|
|
FeatureFlag::create(array_merge([
|
|
'name' => 'PricelistExecution',
|
|
'label' => 'Árlista végrehajtás',
|
|
'enabled' => true,
|
|
'stages' => null,
|
|
'roles' => ['developer'],
|
|
], $attributes));
|
|
|
|
app(FeatureFlagRegistrar::class)->registerAll();
|
|
}
|
|
|
|
test('a hibátlan, jóváhagyásra váró fájl jóváhagyható', function () {
|
|
$file = approvableFile();
|
|
addLine($file, PricelistFileLineStatusEnum::new_product);
|
|
addLine($file, PricelistFileLineStatusEnum::updated, 6);
|
|
|
|
expect($file->canBeApproved())->toBeTrue();
|
|
});
|
|
|
|
test('hibás sor esetén nem hagyható jóvá', function () {
|
|
$file = approvableFile();
|
|
addLine($file, PricelistFileLineStatusEnum::ok);
|
|
addLine($file, PricelistFileLineStatusEnum::error, 6);
|
|
|
|
expect($file->canBeApproved())->toBeFalse();
|
|
});
|
|
|
|
test('nem jóváhagyásra váró státuszban nem hagyható jóvá', function () {
|
|
$file = approvableFile(['status' => PricelistFileStatusEnum::inprogress]);
|
|
|
|
expect($file->canBeApproved())->toBeFalse();
|
|
});
|
|
|
|
test('befejezetlen validálás esetén nem hagyható jóvá', function () {
|
|
$file = approvableFile([
|
|
'workflow_steps' => [
|
|
['name' => PricelistWorkflowStep::Validation->value, 'label' => 'Validálás', 'status' => 'failed'],
|
|
['name' => PricelistWorkflowStep::Approval->value, 'label' => 'Jóváhagyás', 'status' => 'pending'],
|
|
],
|
|
]);
|
|
|
|
expect($file->canBeApproved())->toBeFalse();
|
|
});
|
|
|
|
test('a jóváhagyás elindítja a végrehajtást és azonnal átbillenti a státuszt', function () {
|
|
Queue::fake();
|
|
|
|
$file = approvableFile();
|
|
addLine($file, PricelistFileLineStatusEnum::new_product);
|
|
|
|
expect(app(PricelistFileProcessService::class)->approve($file))->toBeTrue();
|
|
|
|
Queue::assertPushed(PricelistExecutionJob::class);
|
|
|
|
$file->refresh();
|
|
|
|
// A státusz még a dispatch előtt átbillen, különben a queue-latency alatt a
|
|
// Jóváhagyás gomb látszana, és egy második kattintás párhuzamos importot indítana.
|
|
expect($file->status)->toBe(PricelistFileStatusEnum::inprogress)
|
|
->and($file->stepStatus(PricelistWorkflowStep::Approval))->toBe('completed')
|
|
->and($file->stepStatus(PricelistWorkflowStep::Execution))->toBe('inprogress')
|
|
->and($file->file_meta['approval']['decision'])->toBe('approved');
|
|
});
|
|
|
|
test('a már nem jóváhagyható fájlra a jóváhagyás nem indít végrehajtást', function () {
|
|
Queue::fake();
|
|
|
|
$file = approvableFile();
|
|
addLine($file, PricelistFileLineStatusEnum::error);
|
|
|
|
expect(app(PricelistFileProcessService::class)->approve($file))->toBeFalse();
|
|
|
|
Queue::assertNothingPushed();
|
|
expect($file->refresh()->status)->toBe(PricelistFileStatusEnum::waiting_for_approval);
|
|
});
|
|
|
|
test('az elutasítás lezárja a fájlt indoklással, végrehajtás nélkül', function () {
|
|
Queue::fake();
|
|
|
|
$file = approvableFile();
|
|
addLine($file, PricelistFileLineStatusEnum::updated);
|
|
|
|
expect(app(PricelistFileProcessService::class)->reject($file, 'Rossz árlistát töltöttek fel.'))->toBeTrue();
|
|
|
|
Queue::assertNothingPushed();
|
|
|
|
$file->refresh();
|
|
|
|
expect($file->status)->toBe(PricelistFileStatusEnum::closed)
|
|
->and($file->stepStatus(PricelistWorkflowStep::Approval))->toBe('rejected')
|
|
->and($file->file_meta['approval']['reason'])->toBe('Rossz árlistát töltöttek fel.');
|
|
});
|
|
|
|
test('a végrehajtás elindulása után a fájl nem tölthető újra', function () {
|
|
$file = approvableFile();
|
|
|
|
expect($file->hasExecutionStarted())->toBeFalse();
|
|
|
|
app(PricelistFileProcessService::class)->updateStepStatus($file, PricelistWorkflowStep::Execution, 'failed', 'Hiba.');
|
|
|
|
expect($file->refresh()->hasExecutionStarted())->toBeTrue();
|
|
});
|
|
|
|
test('a döntési gombok csak aktív flaggel látszanak', function () {
|
|
definePricelistExecutionFlag();
|
|
|
|
$file = approvableFile();
|
|
addLine($file, PricelistFileLineStatusEnum::new_product);
|
|
|
|
$this->actingAs(developerUser());
|
|
|
|
Livewire::test(ViewPricelistFile::class, ['record' => $file->id])
|
|
->assertActionVisible('approve')
|
|
->assertActionVisible('reject');
|
|
});
|
|
|
|
test('a döntési gombok rejtve maradnak a flag nélküli felhasználónak', function () {
|
|
definePricelistExecutionFlag();
|
|
|
|
$file = approvableFile();
|
|
addLine($file, PricelistFileLineStatusEnum::new_product);
|
|
|
|
$user = User::factory()->create();
|
|
$user->addRole(Role::create(['name' => 'admin', 'display_name' => 'Admin']));
|
|
|
|
$this->actingAs($user);
|
|
|
|
Livewire::test(ViewPricelistFile::class, ['record' => $file->id])
|
|
->assertActionHidden('approve')
|
|
->assertActionHidden('reject');
|
|
});
|
|
|
|
test('hibás sor esetén a döntési gombok rejtve maradnak flaggel is', function () {
|
|
definePricelistExecutionFlag();
|
|
|
|
$file = approvableFile();
|
|
addLine($file, PricelistFileLineStatusEnum::error);
|
|
|
|
$this->actingAs(developerUser());
|
|
|
|
Livewire::test(ViewPricelistFile::class, ['record' => $file->id])
|
|
->assertActionHidden('approve')
|
|
->assertActionHidden('reject');
|
|
});
|