Files
compras/app/Http/Controllers/PurchasePlanningController.php
T
nala 48347e2d3a
tests / ci (push) Canceled after 0s
commit5
2026-10-30 10:55:18 +02:00

112 lines
6.5 KiB
PHP

<?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 = $actor->hasFunctions([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();
}
}