Implementa planeamento de compras com marcos e responsabilidades por empresa
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Enums\PurchasingRole;
|
||||
use App\Models\AuditLog;
|
||||
use App\Models\Department;
|
||||
use App\Models\PurchasePlanItem;
|
||||
use App\Models\PurchasePlanMilestone;
|
||||
use App\Models\Requisition;
|
||||
use App\Models\User;
|
||||
use App\Services\PurchasePlanningWorkflow;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Inertia\Inertia;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class PurchasePlanningController extends Controller
|
||||
{
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$actor = $request->user();
|
||||
$canManage = in_array($actor->purchasing_role, [PurchasingRole::Admin, PurchasingRole::Procurement]);
|
||||
$filters = $request->validate(['item' => 'nullable|integer|min:1']);
|
||||
$items = PurchasePlanItem::query()->visibleTo($actor)->when($filters['item'] ?? null, fn ($query, $id) => $query->whereKey($id))->latest('id')->paginate(15)->withQueryString();
|
||||
$milestones = PurchasePlanMilestone::query()->whereIn('purchase_plan_item_id', $items->pluck('id'))->orderBy('planned_on')->orderBy('id')->get();
|
||||
$responsibles = User::query()->where('tenant_id', $actor->tenant_id)->whereIn('id', $items->pluck('responsible_id'))->pluck('name', 'id');
|
||||
$departments = Department::query()->whereIn('id', $items->pluck('department_id'))->pluck('name', 'id');
|
||||
$requisitions = Requisition::query()->visibleTo($actor)->whereIn('id', $items->pluck('requisition_id'))->get(['id', 'number'])->keyBy('id');
|
||||
$rows = $items->through(fn (PurchasePlanItem $item) => [
|
||||
...$item->only(['id', 'title', 'nature', 'responsible_id', 'version', 'department_id']),
|
||||
'canLink' => $canManage && $item->requisition_id === null,
|
||||
'responsible' => $responsibles[$item->responsible_id] ?? 'Responsável indisponível',
|
||||
'department' => $departments[$item->department_id] ?? '',
|
||||
'requisition' => isset($requisitions[$item->requisition_id]) ? $requisitions[$item->requisition_id]->only(['id', 'number']) : null,
|
||||
'canRecord' => $canManage || $item->responsible_id === $actor->id,
|
||||
]);
|
||||
$history = [];
|
||||
if ($canManage) {
|
||||
$history = AuditLog::query()->whereIn('action', ['planning.created', 'planning.linked', 'planning.assigned', 'planning.milestone_created', 'planning.milestone_updated'])->latest('id')->limit(50)->get(['id', 'action', 'user_id', 'before', 'after', 'created_at']);
|
||||
$authors = User::query()->where('tenant_id', $actor->tenant_id)->whereIn('id', $history->pluck('user_id'))->pluck('name', 'id');
|
||||
foreach ($history as $event) {
|
||||
$event->setAttribute('actor', $authors[$event->user_id] ?? 'Utilizador indisponível');
|
||||
}
|
||||
}
|
||||
Inertia::encryptHistory();
|
||||
$response = Inertia::render('planning/index', [
|
||||
'items' => $rows, 'milestones' => $milestones, 'history' => $history, 'canManage' => $canManage,
|
||||
'today' => today()->format('Y-m-d'),
|
||||
'departments' => $canManage ? Department::query()->orderBy('name')->get(['id', 'name']) : [],
|
||||
'users' => $canManage ? User::query()->where('tenant_id', $actor->tenant_id)->orderBy('name')->get(['id', 'name']) : [],
|
||||
'requisitions' => $canManage ? Requisition::query()->visibleTo($actor)->whereNotIn('id', PurchasePlanItem::query()->whereNotNull('requisition_id')->select('requisition_id'))->latest('id')->get(['id', 'number', 'department_id']) : [],
|
||||
])->toResponse($request);
|
||||
$response->headers->set('Cache-Control', 'private, no-store, max-age=0');
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function store(Request $request, PurchasePlanningWorkflow $workflow): RedirectResponse
|
||||
{
|
||||
$data = $request->validate(['title' => 'required|string|min:3|max:200', 'department_id' => 'required|integer', 'responsible_id' => 'required|integer', 'requisition_id' => 'nullable|integer', 'nature' => 'required|in:goods,works,services,consulting']);
|
||||
$data['department_id'] = (int) $data['department_id'];
|
||||
$data['responsible_id'] = (int) $data['responsible_id'];
|
||||
$workflow->create($request->user(), $data);
|
||||
|
||||
return back();
|
||||
}
|
||||
|
||||
public function assign(Request $request, PurchasePlanItem $item, PurchasePlanningWorkflow $workflow): RedirectResponse
|
||||
{
|
||||
$data = $request->validate(['responsible_id' => 'required|integer', 'version' => 'required|integer|min:1', 'reason' => 'required|string|min:10|max:2000']);
|
||||
$workflow->assign($request->user(), $item, (int) $data['responsible_id'], (int) $data['version'], $data['reason']);
|
||||
|
||||
return back();
|
||||
}
|
||||
|
||||
public function milestone(Request $request, PurchasePlanItem $item, PurchasePlanningWorkflow $workflow): RedirectResponse
|
||||
{
|
||||
$data = $request->validate(['label' => 'required|string|min:3|max:120', 'planned_on' => 'required|date_format:Y-m-d']);
|
||||
$workflow->milestone($request->user(), $item, $data['label'], $data['planned_on']);
|
||||
|
||||
return back();
|
||||
}
|
||||
|
||||
public function link(Request $request, PurchasePlanItem $item, PurchasePlanningWorkflow $workflow): RedirectResponse
|
||||
{
|
||||
$data = $request->validate(['requisition_id' => 'required|integer', 'version' => 'required|integer|min:1', 'reason' => 'required|string|min:10|max:2000']);
|
||||
$workflow->link($request->user(), $item, (int) $data['requisition_id'], (int) $data['version'], $data['reason']);
|
||||
|
||||
return back();
|
||||
}
|
||||
|
||||
public function update(Request $request, PurchasePlanMilestone $milestone, PurchasePlanningWorkflow $workflow): RedirectResponse
|
||||
{
|
||||
$data = $request->validate(['version' => 'required|integer|min:1', 'planned_on' => 'sometimes|required|date_format:Y-m-d', 'actual_on' => 'sometimes|nullable|date_format:Y-m-d|before_or_equal:today', 'reason' => 'required|string|min:10|max:2000']);
|
||||
if (! array_key_exists('planned_on', $data) && ! array_key_exists('actual_on', $data)) {
|
||||
throw ValidationException::withMessages(['actual_on' => 'Indique a data prevista ou a data realizada a actualizar.']);
|
||||
}
|
||||
$changes = ['version' => (int) $data['version'], 'reason' => $data['reason']];
|
||||
if (array_key_exists('planned_on', $data)) {
|
||||
$changes['planned_on'] = $data['planned_on'];
|
||||
}
|
||||
if (array_key_exists('actual_on', $data)) {
|
||||
$changes['actual_on'] = $data['actual_on'];
|
||||
}
|
||||
$workflow->update($request->user(), $milestone, $changes);
|
||||
|
||||
return back();
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ use App\Models\GoodsReceipt;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\PurchaseAward;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\PurchasePlanItem;
|
||||
use App\Models\PurchaseRevision;
|
||||
use App\Models\Quotation;
|
||||
use App\Models\Requisition;
|
||||
@@ -34,6 +35,10 @@ class PurchaseProcessController extends Controller
|
||||
$base = '/requisicoes/'.$requisition->id;
|
||||
$stages = [];
|
||||
$documents = [['label' => $requisition->number, 'kind' => 'Requisição', 'href' => $base]];
|
||||
$planning = PurchasePlanItem::query()->visibleTo($actor)->where('requisition_id', $requisition->id)->first(['id', 'title']);
|
||||
if ($planning) {
|
||||
$documents[] = ['label' => $planning->title, 'kind' => 'Planeamento', 'href' => '/planeamento?item='.$planning->id];
|
||||
}
|
||||
$decisions = [];
|
||||
$stages[] = ['label' => 'Requisição e aprovação da necessidade', 'state' => $requisition->status, 'owner' => 'Solicitante e aprovadores', 'detail' => 'Estado do pedido; as decisões individuais constam abaixo.', 'href' => $base];
|
||||
foreach ($requisition->steps as $step) {
|
||||
@@ -108,7 +113,7 @@ class PurchaseProcessController extends Controller
|
||||
'stages' => $stages, 'documents' => $documents, 'decisions' => $decisions, 'related' => $related,
|
||||
'cancellation' => $cancellation ? ['status' => $cancellation->status, 'href' => '/revisao-compras'] : null,
|
||||
'history' => AuditLog::query()->where('requisition_id', $requisition->id)->whereIn('action', $actions)->orderBy('id')->get(['id', 'action', 'created_at']),
|
||||
'future' => ['Plano de compras e marcos previstos/realizados', 'Sessão de abertura e acta', 'Qualificação específica do concurso', 'Relatório de recomendação em etapas'],
|
||||
'future' => ['Sessão de abertura e acta', 'Qualificação específica do concurso', 'Relatório de recomendação em etapas'],
|
||||
])->toResponse($request);
|
||||
$response->headers->set('Cache-Control', 'private, no-store, max-age=0');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user