@@ -8,6 +8,7 @@ use App\Models\StockItem;
|
||||
use App\Models\User;
|
||||
use App\Models\Warehouse;
|
||||
use App\Models\WarehouseRequest;
|
||||
use App\Models\WarehouseRequestEvent;
|
||||
use App\Services\InternalSupplyTrace;
|
||||
use App\Services\OrganizationAccess;
|
||||
use App\Services\WarehouseFulfilment;
|
||||
@@ -22,6 +23,12 @@ use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class WarehouseRequestController extends Controller
|
||||
{
|
||||
/** @return array<string,mixed> */
|
||||
private function filters(Request $request, User $actor): array
|
||||
{
|
||||
return $request->validate(['view' => 'nullable|in:all,review,reserve,delivery,receipt', 'state' => 'nullable|in:all,draft,submitted,authorized,partial,delivered,closed,rejected,cancelled', 'search' => 'nullable|string|max:100', 'warehouse_id' => ['nullable', 'integer', Rule::exists('warehouses', 'id')->where('tenant_id', $actor->tenant_id)], 'page' => 'nullable|integer|min:1|max:1000000']);
|
||||
}
|
||||
|
||||
public function index(Request $request, WarehouseWorkdesk $desk): Response
|
||||
{
|
||||
$actor = $request->user('web');
|
||||
@@ -31,21 +38,10 @@ class WarehouseRequestController extends Controller
|
||||
if (DB::getDriverName() === 'pgsql' && DB::transactionLevel() === 1) {
|
||||
DB::statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY');
|
||||
}
|
||||
$filters = $request->validate(['view' => 'nullable|in:all,review,reserve,delivery,receipt', 'state' => 'nullable|in:all,draft,submitted,authorized,partial,delivered,closed,rejected,cancelled', 'search' => 'nullable|string|max:100', 'warehouse_id' => ['nullable', 'integer', Rule::exists('warehouses', 'id')->where('tenant_id', $actor->tenant_id)], 'page' => 'nullable|integer|min:1|max:1000000']);
|
||||
$filters = $this->filters($request, $actor);
|
||||
$prepare = $actor->hasFunctions(['warehouse_request_prepare']) && ! app(OrganizationAccess::class)->maintenanceOnly($actor);
|
||||
$ready = Schema::hasTable('warehouse_requests');
|
||||
$base = $desk->visible($actor);
|
||||
if (! empty($filters['warehouse_id'])) {
|
||||
$base->where('warehouse_id', $filters['warehouse_id']);
|
||||
}
|
||||
if (! empty($filters['search'])) {
|
||||
$search = trim($filters['search']);
|
||||
if (preg_match('/^(?:RA-)?([1-9][0-9]{0,9})$/iD', $search, $match)) {
|
||||
$base->whereKey($match[1]);
|
||||
} else {
|
||||
$base->where(fn ($q) => $q->whereLike('snapshot->requester->name', '%'.$search.'%')->orWhereLike('snapshot->destination', '%'.$search.'%'));
|
||||
}
|
||||
}
|
||||
$base = $desk->filteredBase($actor, $filters);
|
||||
$views = $ready ? $desk->views(clone $base, $actor) : [];
|
||||
$query = $desk->forView(clone $base, $actor, $filters['view'] ?? 'all');
|
||||
$states = $ready ? DB::query()->fromSub((clone $query)->selectRaw($desk->stateExpression().' as projected_state')->toBase(), 'request_states')->selectRaw('projected_state, COUNT(*) as total')->groupBy('projected_state')->pluck('total', 'projected_state') : [];
|
||||
@@ -70,6 +66,49 @@ class WarehouseRequestController extends Controller
|
||||
});
|
||||
}
|
||||
|
||||
public function csv(Request $request, WarehouseWorkdesk $desk): Response
|
||||
{
|
||||
$actor = $request->user('web');
|
||||
abort_unless($actor instanceof User && $actor->hasFunctions(['admin', 'warehouse_request_read', 'warehouse_request_prepare', 'warehouse_request_review', 'warehouse_request_deliver']), 403);
|
||||
$filters = $this->filters($request, $actor);
|
||||
abort_unless(Schema::hasTable('warehouse_requests'), 503);
|
||||
|
||||
return response()->streamDownload(function () use ($actor, $filters, $desk) {
|
||||
DB::transaction(function () use ($actor, $filters, $desk) {
|
||||
if (DB::getDriverName() === 'pgsql' && DB::transactionLevel() === 1) {
|
||||
DB::statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY');
|
||||
}
|
||||
$actor = User::query()->findOrFail($actor->id);
|
||||
abort_unless($actor->hasFunctions(['admin', 'warehouse_request_read', 'warehouse_request_prepare', 'warehouse_request_review', 'warehouse_request_deliver']), 403);
|
||||
$query = $desk->forView($desk->filteredBase($actor, $filters), $actor, $filters['view'] ?? 'all');
|
||||
if (($filters['state'] ?? 'all') !== 'all') {
|
||||
$query->whereRaw($desk->stateExpression().' = ?', [$filters['state']]);
|
||||
}
|
||||
$out = fopen('php://output', 'w');
|
||||
if ($out === false) {
|
||||
throw new \RuntimeException('Não foi possível gerar o ficheiro.');
|
||||
}
|
||||
$safe = fn (string $value): string => preg_match('/^[\s]*[=+\-@]|^[\t\r\n]/u', $value) ? "'".$value : $value;
|
||||
try {
|
||||
fwrite($out, "\xEF\xBB\xBF");
|
||||
fputcsv($out, ['Requisições internas', now()->toIso8601String(), 'Sem factura; entregas acumuladas não descontam devoluções ou correcções.'], ';', '"', '');
|
||||
fputcsv($out, ['Filtros', json_encode(array_diff_key($filters, ['page' => true]), JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR)], ';', '"', '');
|
||||
fputcsv($out, ['Requisição', 'Estado', 'Versão', 'Requisitante', 'Armazém', 'Centro de custo', 'Destino', 'Finalidade', 'Linha', 'Artigo', 'Descrição', 'Unidade', 'Utilização', 'Equipamento', 'Pedida', 'Reservada agora', 'Entregue acumulada', 'Por entregar', 'Cancelada', 'Reservas disponíveis', 'Consulta'], ';', '"', '');
|
||||
foreach ($query->lazyById(100) as $record) {
|
||||
$p = app(WarehouseFulfilment::class)->progress($record);
|
||||
$s = $record->snapshot;
|
||||
foreach ($s['lines'] as $index => $line) {
|
||||
$row = ['RA-'.$record->id, $p['state'], (string) $p['version'], $s['requester']['name'], $s['warehouse']['code'], $s['cost_center']['code'], $s['destination'], $s['reason'], (string) ($index + 1), $line['item']['code'], $line['item']['name'], $line['item']['unit'], $line['destination'], $line['equipment']['code'] ?? '', ...array_map(fn ($quantity) => number_format($quantity / 1000, 3, '.', ''), [$line['quantity_milli'], $p['reserved'][$index], $p['delivered'][$index], $p['remaining'][$index], $p['cancelled'][$index]]), $p['reservation_ready'] ? 'Sim' : 'Não', url('/stock/requisicoes/'.$record->id)];
|
||||
fputcsv($out, array_map($safe, $row), ';', '"', '');
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
fclose($out);
|
||||
}
|
||||
});
|
||||
}, 'requisicoes-internas-'.now()->format('Ymd-His').'.csv', ['Content-Type' => 'text/csv; charset=UTF-8', 'Cache-Control' => 'private, no-store, max-age=0']);
|
||||
}
|
||||
|
||||
public function store(Request $request, WarehouseRequests $service): Response
|
||||
{
|
||||
$actor = $request->user('web');
|
||||
@@ -94,7 +133,7 @@ class WarehouseRequestController extends Controller
|
||||
$p = $service->progress($r);
|
||||
Inertia::encryptHistory();
|
||||
$response = Inertia::render('stock/request', ['record' => $r->only(['id', 'snapshot', 'created_at']), 'progress' => $p, 'ready' => Schema::hasTable('warehouse_request_events'),
|
||||
'canViewReturns' => config('stock_returns.enabled') && $actor->hasFunctions(['warehouse_return_read']) && !app(OrganizationAccess::class)->maintenanceOnly($actor),
|
||||
'canViewReturns' => config('stock_returns.enabled') && $actor->hasFunctions(['warehouse_return_read']) && ! app(OrganizationAccess::class)->maintenanceOnly($actor),
|
||||
'health' => $desk->health($r, $p), 'people' => $desk->people($r, $p),
|
||||
'supplyTrace' => app(InternalSupplyTrace::class)->forRequest($actor, $r, $p), ...$desk->actions($actor, $r, $p)])->toResponse($request);
|
||||
$response->headers->set('Cache-Control', 'private, no-store, max-age=0');
|
||||
@@ -111,4 +150,44 @@ class WarehouseRequestController extends Controller
|
||||
|
||||
return redirect('/stock/requisicoes/'.$id);
|
||||
}
|
||||
|
||||
public function printRequest(Request $request, int $id, WarehouseFulfilment $service): Response
|
||||
{
|
||||
$actor = $request->user('web');
|
||||
abort_unless($actor instanceof User, 403);
|
||||
|
||||
return DB::transaction(function () use ($actor, $id, $service) {
|
||||
if (DB::getDriverName() === 'pgsql' && DB::transactionLevel() === 1) {
|
||||
DB::statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY');
|
||||
}
|
||||
$record = WarehouseRequest::query()->findOrFail($id);
|
||||
abort_unless(app(WarehouseWorkdesk::class)->canView($actor, $record), 403);
|
||||
|
||||
return response()->view('stock.request-print', ['record' => $record, 'progress' => $service->progress($record), 'generatedAt' => now()])
|
||||
->header('Cache-Control', 'private, no-store, max-age=0')->header('X-Content-Type-Options', 'nosniff');
|
||||
});
|
||||
}
|
||||
|
||||
public function printDelivery(Request $request, int $id, int $delivery): Response
|
||||
{
|
||||
$actor = $request->user('web');
|
||||
abort_unless($actor instanceof User, 403);
|
||||
|
||||
return DB::transaction(function () use ($actor, $id, $delivery) {
|
||||
if (DB::getDriverName() === 'pgsql' && DB::transactionLevel() === 1) {
|
||||
DB::statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY');
|
||||
}
|
||||
$record = WarehouseRequest::query()->findOrFail($id);
|
||||
abort_unless(app(WarehouseWorkdesk::class)->canView($actor, $record), 403);
|
||||
abort_unless(Schema::hasTable('warehouse_request_events'), 503, 'Registo de entregas indisponível.');
|
||||
$event = WarehouseRequestEvent::query()->where('warehouse_request_id', $record->id)->where('action', 'deliver')->findOrFail($delivery);
|
||||
$confirmation = WarehouseRequestEvent::query()->where('warehouse_request_id', $record->id)->where('action', 'receive')
|
||||
->where('data->delivery_event_id', $event->id)->orderBy('version')->first();
|
||||
$authorization = WarehouseRequestEvent::query()->where('warehouse_request_id', $record->id)->where('action', 'authorize')
|
||||
->where('version', '<', $event->version)->orderByDesc('version')->first();
|
||||
|
||||
return response()->view('stock.delivery-print', ['record' => $record, 'delivery' => $event, 'confirmation' => $confirmation, 'authorization' => $authorization])
|
||||
->header('Cache-Control', 'private, no-store, max-age=0')->header('X-Content-Type-Options', 'nosniff');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user