77 lines
5.0 KiB
PHP
77 lines
5.0 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Enums\PurchasingRole;
|
|
use App\Models\ProposalOpening;
|
|
use App\Models\PurchaseAward;
|
|
use App\Models\PurchaseOrder;
|
|
use App\Models\QualificationAssessment;
|
|
use App\Models\QualificationSet;
|
|
use App\Models\Requisition;
|
|
use App\Models\Tenant;
|
|
use App\Models\User;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class ProcurementReviewReport
|
|
{
|
|
/** @return array<string,mixed> */
|
|
public function build(User $actor, int $requisitionId): array
|
|
{
|
|
abort_unless(auth()->id() === $actor->id && $actor->tenant_id !== null, 403);
|
|
if (DB::connection()->getDriverName() === 'pgsql' && DB::transactionLevel() !== 0) {
|
|
throw new \LogicException('O relatório PostgreSQL exige uma transacção própria de leitura.');
|
|
}
|
|
|
|
return DB::transaction(function () use ($actor, $requisitionId) {
|
|
if (DB::connection()->getDriverName() === 'pgsql') {
|
|
DB::statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ ONLY');
|
|
}
|
|
$actor = User::query()->where('tenant_id', $actor->tenant_id)->findOrFail($actor->id);
|
|
abort_unless(in_array($actor->purchasing_role, [PurchasingRole::Admin, PurchasingRole::Procurement, PurchasingRole::Finance, PurchasingRole::DepartmentHead]), 403);
|
|
$request = Requisition::query()->visibleTo($actor)->with(['department:id,name', 'requester:id,name'])->findOrFail($requisitionId);
|
|
$comparison = app(ProposalComparison::class)->inspect($request);
|
|
$rows = [];
|
|
foreach ($comparison['rows'] as $row) {
|
|
$rows[] = [
|
|
...array_intersect_key($row, array_flip(['id', 'reference', 'currency', 'subtotal_minor', 'tax_minor', 'total_minor', 'delivery_days', 'valid_until', 'weights', 'eligible', 'ineligibility_reasons', 'evaluation_count', 'score_minor'])),
|
|
'supplier' => $row['supplier']['name'] ?? 'Fornecedor indisponível',
|
|
'is_top' => $comparison['ready'] && in_array($row['id'], $comparison['top_ids'], true),
|
|
];
|
|
}
|
|
$openingWorkflow = app(ProposalOpeningWorkflow::class);
|
|
$basis = $openingWorkflow->basis($openingWorkflow->proposals($request));
|
|
$set = QualificationSet::query()->where('requisition_id', $request->id)->latest('revision')->first();
|
|
$qualification = null;
|
|
if ($set) {
|
|
$latestIds = QualificationAssessment::query()->where('qualification_set_id', $set->id)->selectRaw('MAX(id)')->groupBy('quotation_id', 'analyst_id');
|
|
$records = QualificationAssessment::query()->whereIn('id', $latestIds)->orderBy('quotation_id')->orderBy('analyst_id')->get();
|
|
$proposals = [];
|
|
foreach ($set->snapshot['proposals'] as $proposal) {
|
|
$analyses = [];
|
|
foreach ($records->where('quotation_id', $proposal['id']) as $record) {
|
|
$analyses[] = ['id' => $record->id, 'revision' => $record->revision, 'analyst' => $record->snapshot['analyst'], 'created_at' => $record->created_at, 'reason' => $record->reason, 'answers' => $record->answers];
|
|
}
|
|
$proposals[] = ['id' => $proposal['id'], 'reference' => $proposal['reference'], 'supplier' => $proposal['supplier']['name'] ?? 'Fornecedor indisponível', 'analyses' => $analyses];
|
|
}
|
|
$qualification = ['id' => $set->id, 'revision' => $set->revision, 'created_at' => $set->created_at, 'author' => $set->snapshot['author']['name'], 'reason' => $set->reason, 'criteria' => $set->criteria, 'is_current' => $set->basis_hash === $basis, 'proposals' => $proposals];
|
|
}
|
|
$opening = ProposalOpening::query()->where('requisition_id', $request->id)->latest('revision')->first();
|
|
$award = PurchaseAward::query()->where('requisition_id', $request->id)->first();
|
|
$order = PurchaseOrder::query()->visibleTo($actor)->where('requisition_id', $request->id)->first();
|
|
|
|
return [
|
|
'generated_at' => now()->toIso8601String(), 'generated_by' => $actor->name,
|
|
'company' => Tenant::query()->findOrFail($actor->tenant_id)->name,
|
|
'requisition' => [...$request->only(['id', 'number', 'status', 'justification']), 'department' => $request->department?->name, 'requester' => $request->requester?->name],
|
|
'comparison' => ['ready' => $comparison['ready'], 'top_count' => $comparison['ready'] ? count($comparison['top_ids']) : 0, 'rows' => $rows],
|
|
'qualification' => $qualification,
|
|
'admission' => app(FormalAdmission::class)->adoption($request)?->only(['id', 'revision', 'snapshot', 'reason']),
|
|
'opening' => $opening ? [...$opening->only(['id', 'revision', 'reference']), 'source_changed' => $opening->basis_hash !== $basis] : null,
|
|
'award' => $award?->only(['id', 'status', 'total_minor']),
|
|
'order' => $order?->only(['id', 'number', 'currency', 'total_minor']),
|
|
];
|
|
});
|
|
}
|
|
}
|