d2d.emegrendeles.hu/app/Models/PricelistFile.php
E98Developer eee27aa659 ADD EV3-357 Árlista feldolgozás phase5/2 jóváhagyás/elutasítás akció
- 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>
2026-08-10 15:00:33 +02:00

111 lines
3.4 KiB
PHP

<?php
namespace App\Models;
use App\Enums\PricelistFileLineStatusEnum;
use App\Enums\PricelistFileStatusEnum;
use App\Enums\PricelistWorkflowStep;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class PricelistFile extends BaseAuditable
{
use SoftDeletes;
protected $casts = [
'status' => PricelistFileStatusEnum::class,
'processing_current_step_percentage' => 'integer',
'available_date' => 'date',
'workflow_steps' => 'array',
'file_meta' => 'array',
];
protected static function booted()
{
static::creating(function ($pricelistFile) {
if (empty($pricelistFile->workflow_steps)) {
$pricelistFile->workflow_steps = \App\Enums\PricelistWorkflowStep::defaultSteps();
}
});
}
public function supplier(): BelongsTo
{
return $this->belongsTo(Supplier::class);
}
public function priceList(): BelongsTo
{
return $this->belongsTo(PriceList::class);
}
public function lines(): HasMany
{
return $this->hasMany(PricelistFileLine::class);
}
/**
* Egy workflow lépés aktuális státusza a `workflow_steps` tömbből
* ('pending' / 'inprogress' / 'completed' / 'failed'), vagy null, ha a lépés
* nem szerepel a rekordban (régi fájloknál előfordulhat).
*/
public function stepStatus(PricelistWorkflowStep $step): ?string
{
foreach ($this->workflow_steps ?? [] as $workflowStep) {
if (($workflowStep['name'] ?? null) === $step->value) {
return $workflowStep['status'] ?? null;
}
}
return null;
}
/**
* Van-e a fájlban blokkoló (hibás) sor. Ez a jóváhagyás egyik feltétele:
* hibás sorral nem indítható végrehajtás.
*/
public function hasErrorLines(): bool
{
return $this->lines()
->where('status', PricelistFileLineStatusEnum::error->value)
->exists();
}
/**
* Jóváhagyható-e a fájl. Egyetlen igazságforrás: ezt nézi a felületi gomb
* láthatósága ÉS a végrehajtási job is induláskor - a felület 5 mp-enként
* pollozik, így a gomb megjelenítése és a kattintás között változhat az állapot.
*/
public function canBeApproved(): bool
{
return $this->status === PricelistFileStatusEnum::waiting_for_approval
&& $this->stepStatus(PricelistWorkflowStep::Validation) === 'completed'
&& ! $this->hasErrorLines();
}
/**
* Elindult-e már a végrehajtás. Ha igen, a fájl újratöltése (Edit -> a teljes
* lánc újraindítása) TILOS: addigra már létrejöhettek termékek és árak, egy
* nulláról induló újrafuttatás duplikálna. Helyette a Folytatás vagy a
* Visszavonás akció használható.
*/
public function hasExecutionStarted(): bool
{
return ! in_array($this->stepStatus(PricelistWorkflowStep::Execution), ['pending', null], true);
}
/**
* Soronkénti státuszok darabszáma (státusz => darab), a jóváhagyás előtti
* összegző modalhoz.
*/
public function lineStatusCounts(): array
{
return $this->lines()
->selectRaw('status, COUNT(*) as total')
->groupBy('status')
->pluck('total', 'status')
->all();
}
}