Files
compras/app/Http/Controllers/SupplyCoordinationReportController.php
2026-11-03 11:51:44 +02:00

75 lines
4.8 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Models\SupplyRecord;
use App\Models\User;
use App\Services\SupplyCoordination;
use App\Services\SupplyCoordinationRead;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Validation\Rule;
use Symfony\Component\HttpFoundation\StreamedResponse;
class SupplyCoordinationReportController extends Controller
{
public function csv(Request $request, SupplyCoordinationRead $read): StreamedResponse
{
$actor = $request->user('web');
abort_unless($actor instanceof User, 403);
$view = $request->validate(['view' => 'required|in:reservations,deliveries,alternatives'])['view'];
$kind = $read->kind($actor, $view);
$filters = $read->filters($request, $kind);
abort_unless(Schema::hasTable('supply_records'), 503, 'Instalação do fornecimento pendente.');
// Validate the same focus as the page, without silently exporting other records.
$focus = $request->validate(['record_id' => ['nullable', 'integer', 'min:1', Rule::exists('supply_records', 'id')->where('tenant_id', $actor->tenant_id)->where('kind', $kind)]]);
return response()->streamDownload(function () use ($read, $actor, $view, $kind, $filters, $focus) {
DB::transaction(function () use ($read, $actor, $view, $kind, $filters, $focus) {
if (DB::getDriverName() === 'pgsql' && DB::transactionLevel() === 1) {
DB::statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY');
}
// Recheck access when the response is consumed.
$read->kind($actor, $view);
$query = $read->query($kind, isset($focus['record_id']) ? ['record_search' => '', 'record_state' => ''] : $filters);
if (isset($focus['record_id'])) {
$key = SupplyRecord::query()->whereKey($focus['record_id'])->value('record_key');
$query->where('record_key', $key);
}
$out = fopen('php://output', 'w');
if ($out === false) {
throw new \RuntimeException('Não foi possível preparar a exportação.');
}
$text = static function (mixed $value): string {
$value = (string) ($value ?? '');
return preg_match('/^[\s\x00-\x1F]*[=+\-@]/u', $value) ? "'".$value : $value;
};
try {
fwrite($out, "\xEF\xBB\xBF");
fputcsv($out, ['Acompanhamento de fornecimentos', $view, 'Conferido em', now()->toIso8601String()], ';', '"', '');
fputcsv($out, ['Filtros', $text(json_encode($filters, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR)), 'Registo em foco', $focus['record_id'] ?? ''], ';', '"', '');
fputcsv($out, ['Registo', 'Versão', 'Estado registado', 'Artigo', 'Designação preservada', 'Local preservado', 'Referência', 'Encomenda', 'Quantidade', 'Unidade', 'Data prevista/necessidade', 'Quantidade por satisfazer (ver estado)', 'Substituto', 'Aplicação', 'Validade', 'Processo'], ';', '"', '');
foreach ($query->lazyById(100) as $record) {
$pending = match ($kind) {
'reservation' => app(SupplyCoordination::class)->remainingReservation($record),
'delivery' => app(SupplyCoordination::class)->remainingDelivery($record),
default => null,
};
fputcsv($out, array_map($text, [$record->record_key, $record->version, $record->state,
$record->snapshot['item']['code'] ?? '', $record->snapshot['item']['name'] ?? '', $record->snapshot['warehouse']['name'] ?? '',
$record->data['reference'] ?? '', $record->snapshot['order']['number'] ?? '',
isset($record->data['quantity_milli']) ? $record->data['quantity_milli'] / 1000 : '', $record->snapshot['item']['unit'] ?? '',
$record->data['needed_on'] ?? $record->data['expected_on'] ?? '', $pending === null ? '' : $pending / 1000,
$record->snapshot['substitute']['code'] ?? '', $record->data['application'] ?? '', $record->data['valid_until'] ?? '',
url('/compras/fornecimento?view='.$view.'&record_id='.$record->id).'#supply-record-'.$record->id]), ';', '"', '');
}
} finally {
fclose($out);
}
});
}, 'fornecimentos-'.$view.'-'.now()->format('Ymd-His').'.csv', ['Content-Type' => 'text/csv; charset=UTF-8', 'Cache-Control' => 'private, no-store, max-age=0']);
}
}