113 lines
6.5 KiB
PHP
113 lines
6.5 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\StockItem;
|
|
use App\Models\SupplyRecord;
|
|
use App\Models\User;
|
|
use App\Models\Warehouse;
|
|
use App\Services\InventoryLots;
|
|
use App\Services\SupplyPlanning;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Pagination\LengthAwarePaginator;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Illuminate\Validation\Rule;
|
|
use Inertia\Inertia;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
class InventoryLotController extends Controller
|
|
{
|
|
public function index(Request $request, InventoryLots $lots): Response
|
|
{
|
|
$actor = $request->user('web');
|
|
abort_unless($actor instanceof User, 403);
|
|
$capabilities = [];
|
|
foreach (['read', 'prepare', 'review', 'hold', 'release'] as $capability) {
|
|
$capabilities[$capability] = $lots->allows($actor, 'inventory_lot_'.$capability);
|
|
}
|
|
abort_unless(collect($capabilities)->contains(fn (bool $allowed) => $allowed), 403);
|
|
|
|
return DB::transaction(function () use ($request, $lots, $actor, $capabilities) {
|
|
if (DB::getDriverName() === 'pgsql' && DB::transactionLevel() === 1) {
|
|
DB::statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY');
|
|
}
|
|
|
|
return $this->page($request, $lots, $actor, $capabilities);
|
|
});
|
|
}
|
|
|
|
/** @param array<string,bool> $capabilities */
|
|
private function page(Request $request, InventoryLots $lots, User $actor, array $capabilities): Response
|
|
{
|
|
$ready = Schema::hasTable('inventory_lot_allocations');
|
|
$filter = $request->validate([
|
|
'record_id' => ['nullable', Rule::prohibitedIf($request->filled('lot_id')), 'integer', 'min:1', Rule::exists('supply_records', 'id')->where('tenant_id', $actor->tenant_id)->where('kind', 'lot_setup')],
|
|
'lot_id' => ['nullable', 'required_with:warehouse_id', 'integer', 'min:1', Rule::exists('supply_records', 'id')->where('tenant_id', $actor->tenant_id)->where('kind', 'lot')->where('version', 1)],
|
|
'warehouse_id' => ['nullable', 'required_with:lot_id', 'integer', 'min:1', Rule::exists('warehouses', 'id')->where('tenant_id', $actor->tenant_id)],
|
|
'controls_page' => 'nullable|integer|min:1|max:1000000',
|
|
'history_page' => 'nullable|integer|min:1|max:1000000',
|
|
]);
|
|
$focus = isset($filter['record_id']) ? SupplyRecord::query()->where('tenant_id', $actor->tenant_id)->where('kind', 'lot_setup')->findOrFail((int) $filter['record_id']) : null;
|
|
if (isset($filter['lot_id'])) {
|
|
$identity = SupplyRecord::query()->findOrFail((int) $filter['lot_id']);
|
|
$focus = $lots->setup($identity->stock_item_id, (int) $filter['warehouse_id']);
|
|
abort_if($focus === null, 404);
|
|
}
|
|
$controls = $ready && $focus === null ? app(SupplyPlanning::class)->latest('lot_setup')->latest('id')->paginate(25, ['record_key'], 'controls_page')->withQueryString() : null;
|
|
$keys = $focus !== null ? collect([$focus->record_key]) : ($controls?->getCollection()->pluck('record_key') ?? collect());
|
|
$records = $ready ? app(SupplyPlanning::class)->latest('lot_setup')->whereIn('record_key', $keys)->latest('id')->get() : collect();
|
|
if ($ready) {
|
|
$records = $records->concat($lots->activeSetups()->whereIn('record_key', $keys)->latest('id')->get())->unique('id')->sortByDesc('id')->values();
|
|
}
|
|
$contexts = [];
|
|
foreach ($records->where('state', 'active') as $record) {
|
|
$contexts[$record->record_key] = $lots->context($record->stock_item_id, $record->warehouse_id);
|
|
foreach ($contexts[$record->record_key]['lots'] as &$entry) {
|
|
$metadata = SupplyRecord::query()->whereKey($entry['metadata_id'])->firstOrFail();
|
|
$entry['canIdentify'] = $lots->canIdentify($actor, $metadata);
|
|
$entry['canRelease'] = $lots->canDecideLot($actor, $metadata, $contexts[$record->record_key], $entry, 'release');
|
|
$entry['canHold'] = $lots->canDecideLot($actor, $metadata, $contexts[$record->record_key], $entry, 'hold');
|
|
}
|
|
unset($entry);
|
|
}
|
|
if (isset($filter['lot_id'])) {
|
|
abort_unless(collect($contexts[$focus->record_key]['lots'] ?? [])->contains('id', (int) $filter['lot_id']), 404);
|
|
}
|
|
$history = $ready ? SupplyRecord::query()->whereIn('kind', ['lot_setup', 'lot', 'lot_state'])->latest('id')->paginate(50, ['*'], 'history_page')->withQueryString() : null;
|
|
|
|
Inertia::encryptHistory();
|
|
$response = Inertia::render('stock/lots', ['ready' => $ready, 'actorId' => $actor->id, 'capabilities' => $capabilities,
|
|
'focusRecordId' => $focus->id ?? null, 'focusLotId' => isset($filter['lot_id']) ? (int) $filter['lot_id'] : null,
|
|
'focusChanged' => ! isset($filter['lot_id']) && $focus !== null && $records->first()?->id !== $focus->id,
|
|
'controlPagination' => $this->pagination($controls), 'historyPagination' => $this->pagination($history),
|
|
'records' => $records->map(fn (SupplyRecord $record) => [...$record->toArray(), 'canActivate' => $lots->canActivate($actor, $record)]), 'contexts' => $contexts,
|
|
'items' => $capabilities['prepare'] ? StockItem::query()->orderBy('code')->get(['id', 'code', 'name', 'unit']) : [],
|
|
'warehouses' => $capabilities['prepare'] ? Warehouse::query()->orderBy('code')->get(['id', 'code', 'name']) : [],
|
|
'history' => $history?->getCollection() ?? []])->toResponse($request);
|
|
$response->headers->set('Cache-Control', 'private, no-store, max-age=0');
|
|
|
|
return $response;
|
|
}
|
|
|
|
/** @param LengthAwarePaginator<int,SupplyRecord>|null $page
|
|
* @return array<string,mixed>|null
|
|
*/
|
|
private function pagination(?LengthAwarePaginator $page): ?array
|
|
{
|
|
return $page === null ? null : ['current_page' => $page->currentPage(), 'last_page' => $page->lastPage(), 'total' => $page->total(),
|
|
'prev_page_url' => $page->previousPageUrl(), 'next_page_url' => $page->nextPageUrl()];
|
|
}
|
|
|
|
public function save(Request $request, InventoryLots $lots): RedirectResponse
|
|
{
|
|
$actor = $request->user('web');
|
|
abort_unless($actor instanceof User, 403);
|
|
abort_unless(Schema::hasTable('inventory_lot_allocations'), 503, 'Instalação do controlo por lotes pendente.');
|
|
$lots->save($actor, $request->all());
|
|
|
|
return back()->with('success', 'Conferência de lotes registada.');
|
|
}
|
|
}
|