commit21
This commit is contained in:
@@ -12,6 +12,10 @@ use App\Models\QualificationSet;
|
||||
use App\Models\Quotation;
|
||||
use App\Models\RecommendationAttempt;
|
||||
use App\Models\Requisition;
|
||||
use App\Models\Supplier;
|
||||
use App\Models\SupplierEvaluation;
|
||||
use App\Models\SupplierImprovement;
|
||||
use App\Models\SupplierResponse;
|
||||
use App\Models\SupplyRecord;
|
||||
use App\Models\User;
|
||||
use Carbon\CarbonImmutable;
|
||||
@@ -28,12 +32,151 @@ class WorkflowTaskInbox
|
||||
if (app(OrganizationAccess::class)->maintenanceOnly($actor)) {
|
||||
return [];
|
||||
}
|
||||
$rows = $area === 'people' ? $this->people($actor) : [...$this->purchases($actor), ...$this->quotations($actor), ...$this->documentary($actor), ...$this->formalReviews($actor), ...$this->supply($actor), ...$this->awards($actor), ...$this->warehouse($actor), ...$this->finance($actor)];
|
||||
$rows = $area === 'people' ? $this->people($actor) : [...$this->purchases($actor), ...$this->suppliers($actor), ...$this->quotations($actor), ...$this->documentary($actor), ...$this->formalReviews($actor), ...$this->supply($actor), ...$this->suggestions($actor), ...$this->awards($actor), ...$this->warehouse($actor), ...$this->inventoryCorrections($actor), ...$this->lotSetups($actor), ...$this->finance($actor)];
|
||||
if ($area === 'procurement') {
|
||||
$rows = [...$rows, ...$this->supplierImprovements($actor), ...$this->supplierQuality($actor), ...$this->supplyFollowUp($actor), ...$this->lotQuality($actor)];
|
||||
}
|
||||
usort($rows, fn ($a, $b) => strcmp($a['created_at'] ?? '', $b['created_at'] ?? '') ?: strnatcmp($a['id'], $b['id']));
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/** @return list<array<string,mixed>> */
|
||||
private function lotQuality(User $actor): array
|
||||
{
|
||||
$lots = app(InventoryLots::class);
|
||||
if (! Schema::hasTable('inventory_lot_allocations')
|
||||
|| (! $lots->allows($actor, 'inventory_lot_prepare') && ! $lots->allows($actor, 'inventory_lot_release') && ! $lots->allows($actor, 'inventory_lot_hold'))) {
|
||||
return [];
|
||||
}
|
||||
$rows = [];
|
||||
foreach ($lots->activeSetups()->lazyById(100) as $setup) {
|
||||
$context = $lots->context($setup->stock_item_id, $setup->warehouse_id);
|
||||
foreach ($context['lots'] ?? [] as $entry) {
|
||||
if ($entry['quantity_milli'] <= 0) {
|
||||
continue;
|
||||
}
|
||||
$metadata = SupplyRecord::query()->whereKey($entry['metadata_id'])->firstOrFail();
|
||||
$action = $entry['state'] === 'unidentified' && $lots->canIdentify($actor, $metadata) ? 'identify'
|
||||
: ($entry['state'] === 'held' && $lots->canDecideLot($actor, $metadata, $context, $entry, 'release') ? 'release'
|
||||
: ($entry['quality_released'] && ! $entry['eligible'] && $lots->canDecideLot($actor, $metadata, $context, $entry, 'hold') ? 'hold' : null));
|
||||
if ($action === null) {
|
||||
continue;
|
||||
}
|
||||
$rows[] = ['id' => 'lot-quality:'.$setup->warehouse_id.':'.$entry['id'].':'.$action.':'.$entry['version'].':'.$entry['state_version'], 'module' => 'warehouse',
|
||||
'reference' => $setup->snapshot['item']['code'].' / LOTE-'.$entry['id'].' / '.$setup->snapshot['warehouse']['code'],
|
||||
'label' => match ($action) {
|
||||
'identify' => 'Completar identificação de lote retido', 'release' => 'Conferir qualidade para libertação independente do lote', default => 'Conferir retenção de lote anteriormente libertado com validade insuficiente'
|
||||
},
|
||||
'task' => 'lot_'.$action, 'status' => $entry['state'], 'blocked' => false, 'created_at' => $this->date($metadata->created_at), 'due_on' => null,
|
||||
'href' => '/stock/lotes?lot_id='.$entry['id'].'&warehouse_id='.$setup->warehouse_id.'#lot-'.$setup->warehouse_id.'-'.$entry['id']];
|
||||
}
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/** @return list<array<string,mixed>> */
|
||||
private function supplyFollowUp(User $actor): array
|
||||
{
|
||||
if (! Schema::hasTable('supply_records')) {
|
||||
return [];
|
||||
}
|
||||
$planning = app(SupplyPlanning::class);
|
||||
$coordination = app(SupplyCoordination::class);
|
||||
$rows = [];
|
||||
foreach (['reservation' => 'supply_reserve', 'delivery' => 'supply_delivery'] as $kind => $permission) {
|
||||
if (! $planning->allows($actor, $permission)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($planning->latest($kind)->whereIn('state', $kind === 'reservation' ? ['active', 'fulfilled'] : ['active'])->lazyById(100) as $record) {
|
||||
if (! $coordination->canManage($actor, $record, 'edit')) {
|
||||
continue;
|
||||
}
|
||||
$remaining = $kind === 'reservation' ? $coordination->remainingReservation($record) : $coordination->remainingDelivery($record);
|
||||
if ($remaining <= 0) {
|
||||
continue;
|
||||
}
|
||||
$rows[] = ['id' => 'coordination:'.$record->id.':followup', 'module' => 'supply',
|
||||
'reference' => trim(($record->snapshot['item']['code'] ?? '').' '.($record->data['reference'] ?? $record->snapshot['order']['number'] ?? 'FOR-'.$record->id)),
|
||||
'label' => $kind === 'reservation' ? 'Acompanhar necessidade planeada e associar saída efectiva' : 'Acompanhar entrega confirmada ainda por receber',
|
||||
'task' => $kind.'_followup', 'status' => $record->state, 'blocked' => false,
|
||||
'created_at' => $this->date($record->created_at), 'due_on' => $record->data[$kind === 'reservation' ? 'needed_on' : 'expected_on'],
|
||||
'href' => '/compras/fornecimento?view='.($kind === 'reservation' ? 'reservations' : 'deliveries').'&record_id='.$record->id.'#supply-record-'.$record->id];
|
||||
}
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/** @return list<array<string,mixed>> */
|
||||
private function supplierQuality(User $actor): array
|
||||
{
|
||||
if (! Schema::hasTable('supplier_evaluations') || ! $actor->hasFunctions(SupplierEvaluationWorkflow::ROLES)) {
|
||||
return [];
|
||||
}
|
||||
$workflow = app(SupplierEvaluationWorkflow::class);
|
||||
$responses = app(SupplierResponseWorkflow::class);
|
||||
$rows = [];
|
||||
foreach (SupplierEvaluation::query()->visibleTo($actor)->whereIn('status', ['draft', 'returned', 'review'])->lazyById(100) as $evaluation) {
|
||||
$action = $evaluation->status === 'review' ? 'review' : 'edit';
|
||||
if (! $workflow->canAct($actor, $evaluation, $action)
|
||||
|| ($action === 'edit' && ! in_array($actor->id, $evaluation->contributors, true))) {
|
||||
continue;
|
||||
}
|
||||
$rows[] = ['id' => 'supplier-evaluation:'.$evaluation->id.':'.$action.':'.$evaluation->version, 'module' => 'purchases',
|
||||
'reference' => 'AVA-'.$evaluation->id.' / R'.$evaluation->revision,
|
||||
'label' => $action === 'review' ? 'Conferir avaliação de qualidade do fornecedor' : 'Completar e submeter avaliação de qualidade do fornecedor',
|
||||
'task' => 'supplier_evaluation_'.$action, 'status' => $evaluation->status, 'blocked' => false,
|
||||
'created_at' => $this->date($evaluation->created_at), 'due_on' => null, 'href' => '/fornecedores/avaliacoes/'.$evaluation->id];
|
||||
}
|
||||
if (! Schema::hasTable('supplier_responses')) {
|
||||
return $rows;
|
||||
}
|
||||
foreach (SupplierResponse::query()->visibleTo($actor)->whereIn('status', ['draft', 'returned', 'review'])
|
||||
->whereHas('evaluation', fn ($q) => $q->where('status', 'approved'))->with('evaluation')->lazyById(100) as $record) {
|
||||
$evaluation = $record->evaluation;
|
||||
$action = $record->status === 'review' ? 'review' : 'edit';
|
||||
if (! $responses->canAct($actor, $evaluation, $record, $action)
|
||||
|| ($action === 'edit' && ! in_array($actor->id, $record->contributors, true))) {
|
||||
continue;
|
||||
}
|
||||
$rows[] = ['id' => 'supplier-response:'.$record->id.':'.$action.':'.$record->version, 'module' => 'purchases',
|
||||
'reference' => 'AVA-'.$evaluation->id.' / CTR-'.$record->id,
|
||||
'label' => $action === 'review' ? 'Analisar esclarecimento ou contraditório do fornecedor' : 'Completar e submeter esclarecimento do fornecedor',
|
||||
'task' => 'supplier_response_'.$action, 'status' => $record->status, 'blocked' => false,
|
||||
'created_at' => $this->date($record->created_at), 'due_on' => null,
|
||||
'href' => '/fornecedores/avaliacoes/'.$evaluation->id.'#contraditorio-'.$record->id];
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/** @return list<array<string,mixed>> */
|
||||
private function supplierImprovements(User $actor): array
|
||||
{
|
||||
if (! Schema::hasTable('supplier_improvements') || ! $actor->hasFunctions(SupplierEvaluationWorkflow::ROLES)) {
|
||||
return [];
|
||||
}
|
||||
$workflow = app(SupplierImprovementWorkflow::class);
|
||||
$rows = [];
|
||||
foreach (SupplierImprovement::query()->visibleTo($actor)->whereIn('status', ['open', 'returned', 'review'])
|
||||
->whereHas('evaluation', fn ($q) => $q->where('status', 'approved'))->with('evaluation')->lazyById(100) as $measure) {
|
||||
$action = $measure->status === 'review' ? 'review' : 'execute';
|
||||
if (! $workflow->canAct($actor, $measure->evaluation, $measure, $action)) {
|
||||
continue;
|
||||
}
|
||||
$rows[] = ['id' => 'supplier-improvement:'.$measure->id.':'.$action.':'.$measure->version, 'module' => 'purchases',
|
||||
'reference' => 'AVA-'.$measure->supplier_evaluation_id.' / MED-'.$measure->id,
|
||||
'label' => $action === 'review' ? 'Conferir resultado e evidência da medida de melhoria' : 'Registar execução da medida de melhoria atribuída',
|
||||
'task' => 'supplier_improvement_'.$action, 'status' => $measure->status, 'blocked' => false,
|
||||
'created_at' => $this->date($measure->created_at), 'due_on' => $measure->due_on,
|
||||
'href' => '/fornecedores/avaliacoes/'.$measure->supplier_evaluation_id.'#medida-'.$measure->id];
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/** @return list<array<string,mixed>> */
|
||||
private function purchases(User $actor): array
|
||||
{
|
||||
@@ -60,6 +203,81 @@ class WorkflowTaskInbox
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/** @return list<array<string,mixed>> */
|
||||
private function suppliers(User $actor): array
|
||||
{
|
||||
$access = app(SupplierParticipation::class);
|
||||
if (! $access->canReviewRole($actor)) {
|
||||
return [];
|
||||
}
|
||||
$rows = [];
|
||||
foreach (Supplier::query()->where('status', 'pending')->lazyById(100) as $supplier) {
|
||||
if (! $access->canReview($actor, $supplier)) {
|
||||
continue;
|
||||
}
|
||||
$rows[] = ['id' => 'supplier:'.$supplier->id.':review:'.$supplier->version, 'module' => 'purchases', 'reference' => 'FOR-'.$supplier->id,
|
||||
'label' => 'Conferir cadastro do fornecedor', 'task' => 'supplier_review', 'status' => $supplier->status, 'blocked' => false,
|
||||
'created_at' => $this->date($supplier->created_at), 'due_on' => null, 'href' => '/fornecedores/'.$supplier->id.'#supplier-review'];
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/** @return list<array<string,mixed>> */
|
||||
private function inventoryCorrections(User $actor): array
|
||||
{
|
||||
if (! Schema::hasTable('supply_records')) {
|
||||
return [];
|
||||
}
|
||||
$rows = [];
|
||||
foreach (['stock_event' => app(InventoryDispositions::class), 'stock_reversal' => app(InventoryReversals::class)] as $kind => $workflow) {
|
||||
if (! $workflow->allows($actor, 'review') && ! $workflow->allows($actor, 'execute')) {
|
||||
continue;
|
||||
}
|
||||
$prefix = $kind === 'stock_event' ? 'disposition' : 'reversal';
|
||||
$path = $kind === 'stock_event' ? '/stock/ocorrencias' : '/stock/estornos';
|
||||
foreach (app(SupplyPlanning::class)->latest($kind)->whereIn('state', ['draft', 'returned', 'approved'])->lazyById(100) as $record) {
|
||||
$sourceCurrent = $record->state !== 'approved' || $workflow->executionSourceCurrent($record);
|
||||
$action = $record->state === 'approved' ? ($workflow->canAct($actor, $record, 'execute') ? 'execute' : 'return') : 'approve';
|
||||
if ($action === 'return' && $sourceCurrent) {
|
||||
continue;
|
||||
}
|
||||
if (! $workflow->canAct($actor, $record, $action)) {
|
||||
continue;
|
||||
}
|
||||
$rows[] = ['id' => $prefix.':'.$record->id.':'.$action, 'module' => 'warehouse', 'reference' => $record->data['reference'],
|
||||
'label' => ! $sourceCurrent ? ($action === 'return' ? 'Conferir origem ou saldo alterados para devolução' : 'Conferir impedimento da correcção física autorizada')
|
||||
: ($kind === 'stock_reversal' ? ($action === 'execute' ? 'Conferir e executar estorno integral autorizado' : 'Conferir correcção integral de stock')
|
||||
: ($action === 'execute' ? 'Conferir e confirmar saída física autorizada' : 'Conferir devolução ou abate físico')),
|
||||
'task' => $prefix.'_'.$action, 'status' => $record->state, 'blocked' => ! $sourceCurrent, 'created_at' => $this->date($record->created_at), 'due_on' => null,
|
||||
'href' => $path.'?record_id='.$record->id.'#'.$prefix.'-'.$record->id];
|
||||
}
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/** @return list<array<string,mixed>> */
|
||||
private function lotSetups(User $actor): array
|
||||
{
|
||||
$workflow = app(InventoryLots::class);
|
||||
if (! Schema::hasTable('inventory_lot_allocations') || ! $workflow->allows($actor, 'inventory_lot_review')) {
|
||||
return [];
|
||||
}
|
||||
$rows = [];
|
||||
foreach (app(SupplyPlanning::class)->latest('lot_setup')->where('state', 'draft')->lazyById(100) as $record) {
|
||||
if (! $workflow->canActivate($actor, $record)) {
|
||||
continue;
|
||||
}
|
||||
$rows[] = ['id' => 'lot-setup:'.$record->id.':activate', 'module' => 'warehouse',
|
||||
'reference' => $record->snapshot['item']['code'].' '.$record->snapshot['warehouse']['code'], 'label' => 'Conferir configuração e saldo inicial por lotes',
|
||||
'task' => 'lot_setup_activate', 'status' => $record->state, 'blocked' => false, 'created_at' => $this->date($record->created_at), 'due_on' => null,
|
||||
'href' => '/stock/lotes?record_id='.$record->id.'#lot-setup-'.$record->id];
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/** @return list<array<string,mixed>> */
|
||||
private function warehouse(User $actor): array
|
||||
{
|
||||
@@ -260,29 +478,71 @@ class WorkflowTaskInbox
|
||||
private function supply(User $actor): array
|
||||
{
|
||||
$planning = app(SupplyPlanning::class);
|
||||
if (! Schema::hasTable('supply_records') || ! $planning->allows($actor, 'supply_review')) {
|
||||
if (! Schema::hasTable('supply_records') || (! $planning->allows($actor, 'supply_review') && ! $planning->allows($actor, 'supply_history_review') && ! $planning->allows($actor, 'supply_technical_review'))) {
|
||||
return [];
|
||||
}
|
||||
$rows = [];
|
||||
foreach (['agreement', 'policy', 'auto_rule'] as $kind) {
|
||||
foreach (['agreement', 'policy', 'auto_rule', 'history', 'alternative'] as $kind) {
|
||||
$permission = match ($kind) {
|
||||
'history' => 'supply_history_review', 'alternative' => 'supply_technical_review', default => 'supply_review'
|
||||
};
|
||||
if (! $planning->allows($actor, $permission)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($planning->latest($kind)->whereIn('state', ['draft', 'returned'])->lazyById(100) as $record) {
|
||||
$eligible = $kind === 'auto_rule' ? app(AutomaticPurchases::class)->canReview($actor, $record) : $planning->canReview($actor, $record);
|
||||
$eligible = match ($kind) {
|
||||
'auto_rule' => app(AutomaticPurchases::class)->canReview($actor, $record),
|
||||
'history' => app(SupplyHistory::class)->canReview($actor, $record),
|
||||
'alternative' => app(SupplyCoordination::class)->canReviewAlternative($actor, $record),
|
||||
default => $planning->canReview($actor, $record),
|
||||
};
|
||||
if (! $eligible) {
|
||||
continue;
|
||||
}
|
||||
$rows[] = ['id' => 'supply:'.$record->id.':review', 'module' => 'supply',
|
||||
'reference' => trim(($record->snapshot['item']['code'] ?? 'Fornecimento').' '.($record->snapshot['warehouse']['code'] ?? '')),
|
||||
'label' => match ($kind) {
|
||||
'agreement' => 'Conferir acordo de fornecimento', 'policy' => 'Conferir política de reposição', default => 'Conferir parametrização de compra automática',
|
||||
'agreement' => 'Conferir acordo de fornecimento', 'policy' => 'Conferir política de reposição', 'history' => 'Conferir histórico de consumo', 'alternative' => 'Conferir alternativa técnica', default => 'Conferir parametrização de compra automática',
|
||||
}, 'task' => $kind.'_review', 'status' => $record->state, 'blocked' => false, 'created_at' => $this->date($record->created_at), 'due_on' => null,
|
||||
'href' => $kind === 'auto_rule' ? '/stock/reposicao?rule_id='.$record->id.'#supply-rule-'.$record->id
|
||||
: '/compras/fornecimento?view='.($kind === 'agreement' ? 'agreements' : 'calendar').'&record_id='.$record->id.'#supply-record-'.$record->id];
|
||||
: '/compras/fornecimento?view='.match ($kind) {
|
||||
'agreement' => 'agreements', 'history' => 'history', 'alternative' => 'alternatives', default => 'calendar'
|
||||
}.'&record_id='.$record->id.'#supply-record-'.$record->id];
|
||||
}
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/** @return list<array<string,mixed>> */
|
||||
private function suggestions(User $actor): array
|
||||
{
|
||||
$planning = app(SupplyPlanning::class);
|
||||
if (! Schema::hasTable('supply_records') || (! $planning->allows($actor, 'supply_suggest_review') && ! $planning->allows($actor, 'supply_suggest_convert'))) {
|
||||
return [];
|
||||
}
|
||||
$service = app(SupplySuggestions::class);
|
||||
$rows = [];
|
||||
foreach ($planning->latest('suggestion')->whereIn('state', ['draft', 'returned', 'approved', 'converted'])->lazyById(100) as $record) {
|
||||
$action = match ($record->state) {
|
||||
'approved' => 'convert', 'converted' => 'close', default => 'approve'
|
||||
};
|
||||
if (! $service->canAct($actor, $record, $action)) {
|
||||
continue;
|
||||
}
|
||||
$rows[] = ['id' => 'suggestion:'.$record->id.':'.$action, 'module' => 'supply',
|
||||
'reference' => trim($record->snapshot['item']['code'].' '.$record->snapshot['warehouse']['code']),
|
||||
'label' => match ($action) {
|
||||
'convert' => 'Preparar compra a partir da sugestão aprovada', 'close' => 'Conferir e encerrar acompanhamento da reposição', default => 'Conferir sugestão de reposição'
|
||||
},
|
||||
'task' => 'suggestion_'.$action, 'status' => $record->state, 'blocked' => false,
|
||||
'created_at' => $this->date($record->created_at), 'due_on' => null,
|
||||
'href' => '/compras/fornecimento?view=suggestions&record_id='.$record->id.'#supply-suggestion-'.$record->id];
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/** @return list<array<string,mixed>> */
|
||||
private function quotations(User $actor): array
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user