ADD EV3-357 Árlista feldolgozás phase5/1 PricelistExecution feature flag + migrációk

- új PricelistExecution Pennant flag (enabled + roles=[developer]) a jóváhagyás,
  végrehajtás és visszavonás rollout-kapujaként
- új execution_failed fájlstátusz: a validálásig tartó szakasz fail státuszától
  szándékosan elkülönítve, mert itt már történhettek termék- és árírások
- PricelistFileStatusEnum::color(): a badge-színek eddig két helyen, kimerítő
  match-ben voltak duplikálva, amit az új case eltört volna
- pricelist_file_lines.applied_snapshot + executed_at a chunkolt, idempotens
  végrehajtáshoz és a kompenzáló visszaállításhoz

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
E98Developer 2026-08-10 14:19:09 +02:00
parent cbda830c5b
commit 5bf2f83092
7 changed files with 125 additions and 16 deletions

View File

@ -10,6 +10,13 @@ enum PricelistFileStatusEnum: string
case done = 'done';
case waiting_for_approval = 'waiting_for_approval';
case closed = 'closed';
/**
* A Végrehajtás (Execution) lépés futott hibára. Szándékosan külön áll a `fail`-től:
* a `fail` a validálásig tartó szakasz hibája, ahonnan a fájl újratöltésével (Edit)
* biztonságosan újraindítható a lánc - itt viszont már történhettek termék- és
* árírások, ezért csak a Folytatás vagy a Visszavonás akció indítható.
*/
case execution_failed = 'execution_failed';
public function label(): string
{
@ -20,6 +27,24 @@ public function label(): string
self::done => 'elkészült',
self::waiting_for_approval => 'jóváhagyásra vár',
self::closed => 'lezárva',
self::execution_failed => 'végrehajtási hiba',
};
}
/**
* Badge szín a felülethez (a PricelistFileLineStatusEnum mintáját követve, hogy a
* színek egy helyen legyenek, és egy új státusz ne törjön el minden match-et).
*/
public function color(): string
{
return match ($this) {
self::todo => 'gray',
self::inprogress => 'info',
self::done => 'success',
self::fail => 'danger',
self::waiting_for_approval => 'primary',
self::closed => 'warning',
self::execution_failed => 'danger',
};
}
}

View File

@ -47,14 +47,7 @@ public static function configure(Schema $schema): Schema
->label('Státusz')
->badge()
->formatStateUsing(fn (PricelistFileStatusEnum $state): string => $state->label())
->color(fn (PricelistFileStatusEnum $state): string => match ($state) {
PricelistFileStatusEnum::todo => 'gray',
PricelistFileStatusEnum::inprogress => 'info',
PricelistFileStatusEnum::done => 'success',
PricelistFileStatusEnum::fail => 'danger',
PricelistFileStatusEnum::waiting_for_approval => 'primary',
PricelistFileStatusEnum::closed => 'warning',
}),
->color(fn (PricelistFileStatusEnum $state): string => $state->color()),
]),
Grid::make(3)
->schema([

View File

@ -32,14 +32,7 @@ public static function configure(Table $table): Table
->label('Státusz')
->badge()
->formatStateUsing(fn (PricelistFileStatusEnum $state): string => $state->label())
->color(fn (PricelistFileStatusEnum $state): string => match ($state) {
PricelistFileStatusEnum::todo => 'gray',
PricelistFileStatusEnum::inprogress => 'info',
PricelistFileStatusEnum::done => 'success',
PricelistFileStatusEnum::fail => 'danger',
PricelistFileStatusEnum::waiting_for_approval => 'primary',
PricelistFileStatusEnum::closed => 'warning',
})
->color(fn (PricelistFileStatusEnum $state): string => $state->color())
->sortable(),
TextColumn::make('processing_current_step')
->label('Aktuális lépés')

View File

@ -16,6 +16,8 @@ class PricelistFileLine extends BaseAuditable
'payload' => 'array',
'validation_messages' => 'array',
'diff' => 'array',
'applied_snapshot' => 'array',
'executed_at' => 'datetime',
];
public function pricelistFile(): BelongsTo

View File

@ -0,0 +1,45 @@
<?php
use App\Enums\PricelistFileStatusEnum;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
/**
* A `pricelist_files.status` valódi DB enum oszlop, ezért egy új PHP enum case
* (execution_failed) önmagában nem elég - az oszlop definícióját is bővíteni kell.
*
* A 2026_03_11_153652_update_enums_in_pricelist_tables mintáját követjük: az enum
* értékkészletet a PHP enumból generáljuk, így nem csúszhat el a kettő egymástól.
*/
public function up(): void
{
$statuses = array_map(fn ($case) => "'{$case->value}'", PricelistFileStatusEnum::cases());
$statusesStr = implode(', ', $statuses);
DB::statement("ALTER TABLE pricelist_files MODIFY COLUMN status ENUM({$statusesStr}) DEFAULT 'todo' NOT NULL");
}
/**
* Visszagörgetéskor az execution_failed státuszú rekordokat előbb `fail`-re állítjuk,
* különben a szűkített enum miatt az ALTER hibára futna (vagy némán ürítené a mezőt).
*/
public function down(): void
{
DB::table('pricelist_files')
->where('status', PricelistFileStatusEnum::execution_failed->value)
->update(['status' => PricelistFileStatusEnum::fail->value]);
$statuses = array_map(
fn ($case) => "'{$case->value}'",
array_filter(
PricelistFileStatusEnum::cases(),
fn ($case) => $case !== PricelistFileStatusEnum::execution_failed,
),
);
$statusesStr = implode(', ', $statuses);
DB::statement("ALTER TABLE pricelist_files MODIFY COLUMN status ENUM({$statusesStr}) DEFAULT 'todo' NOT NULL");
}
};

View File

@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* A chunkolt végrehajtás két dolgot igényel soronként:
*
* - `applied_snapshot`: a termék TÉNYLEGES, írás előtti DB-állapota, ugyanabban a
* chunk-tranzakcióban rögzítve, mint maga az írás. Ebből áll vissza a termék a
* Visszavonás akcióban. Szándékosan NEM a validáláskor számolt `diff['old']`-ot
* használjuk erre: az a jóváhagyás pillanatában készült, és egy időközbeni kézi
* módosítást felülírna. A snapshotban a termék `updated_at`-jét is eltároljuk,
* hogy a visszavonás ki tudja hagyni (és jelenteni tudja) a közben módosított
* termékeket.
* - `executed_at`: a sor feldolgozottságának jelölése. Ez teszi a végrehajtást
* idempotenssé, azaz a megszakadt futás a kurzortól folytathatóvá - a már
* megjelölt sorok kimaradnak, nem jön létre duplikált termék vagy ár.
*/
public function up(): void
{
Schema::table('pricelist_file_lines', function (Blueprint $table) {
$table->json('applied_snapshot')->nullable()->after('diff');
$table->timestamp('executed_at')->nullable()->after('applied_snapshot');
});
}
public function down(): void
{
Schema::table('pricelist_file_lines', function (Blueprint $table) {
$table->dropColumn(['applied_snapshot', 'executed_at']);
});
}
};

View File

@ -46,5 +46,19 @@ public function run(): void
'roles' => ['developer'],
],
);
FeatureFlag::updateOrCreate(
['name' => 'PricelistExecution'],
[
'label' => 'Árlista jóváhagyás és végrehajtás',
'description' => 'Az árlista feldolgozó Jóváhagyás/Elutasítás, Végrehajtás folytatása és Visszavonás akcióit, valamint a tényleges árlista-importot szabályozza a rollout alatt. Átmeneti kapcsoló, a funkció élesítése után eltávolítható - a kényszerlezárás jogosultsági korlátozása viszont a flag megszűnése után is maradjon.',
// enabled=true + roles=['developer']: a developer felhasználók automatikusan
// megkapják, rajtuk kívül senki. Lásd a fenti magyarázatot arról, miért nem
// enabled=false-szal fejezzük ki a "csak developer lássa" állapotot.
'enabled' => true,
'stages' => null,
'roles' => ['developer'],
],
);
}
}