Files
compras/app/Http/Controllers/StockController.php
T
2026-11-02 08:08:05 +02:00

90 lines
4.4 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Enums\PurchasingRole;
use App\Models\StockItem;
use App\Models\StockMovement;
use App\Models\Warehouse;
use App\Services\InventoryLots;
use App\Services\RequisitionWorkflow;
use Brick\Math\BigDecimal;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Inertia\Inertia;
use Inertia\Response;
class StockController extends Controller
{
private function access(Request $request, bool $manage = false): void
{
$roles = $manage ? [PurchasingRole::Admin, PurchasingRole::Warehouse] : [PurchasingRole::Admin, PurchasingRole::Warehouse, PurchasingRole::Procurement, PurchasingRole::Finance];
abort_unless($request->user()->hasFunctions($roles), 403);
}
public function index(Request $request): Response
{
$this->access($request);
$filters = $request->validate([
'search' => 'nullable|string|max:100',
'warehouse_id' => ['nullable', 'integer', Rule::exists('warehouses', 'id')->where('tenant_id', $request->user()->tenant_id)],
]);
$items = StockItem::query()->orderBy('code')->get(['id', 'code', 'name', 'unit']);
$warehouses = Warehouse::query()->orderBy('code')->get(['id', 'code', 'name']);
$selected = StockItem::query();
if (! empty($filters['search'])) {
$selected->where(fn ($q) => $q->where('code', 'like', '%'.$filters['search'].'%')->orWhere('name', 'like', '%'.$filters['search'].'%'));
}
$movements = StockMovement::query()->whereIn('stock_item_id', $selected->select('id'));
if (! empty($filters['warehouse_id'])) {
$movements->where('warehouse_id', $filters['warehouse_id']);
}
$balances = (clone $movements)->selectRaw('stock_item_id, warehouse_id, SUM(quantity_milli) as quantity_milli')->groupBy('stock_item_id', 'warehouse_id')->get()->map(fn ($balance) => [
'stock_item_id' => $balance->stock_item_id, 'warehouse_id' => $balance->warehouse_id,
'quantity_milli' => BigDecimal::of($balance->quantity_milli)->toInt(),
'lot_control' => app(InventoryLots::class)->summary($balance->stock_item_id, $balance->warehouse_id),
]);
return Inertia::render('stock/index', [
'items' => $items, 'warehouses' => $warehouses, 'balances' => $balances,
'movements' => $movements->latest('id')->paginate(20)->withQueryString(), 'filters' => $filters,
'canManage' => $request->user()->hasFunctions([PurchasingRole::Admin, PurchasingRole::Warehouse]),
]);
}
public function item(Request $request, RequisitionWorkflow $audit): RedirectResponse
{
$this->access($request, true);
$request->merge(['code' => Str::upper(trim($request->string('code')->toString())), 'unit' => Str::lower(trim($request->string('unit')->toString()))]);
$data = $request->validate([
'code' => ['required', 'regex:/^[A-Z0-9][A-Z0-9._-]{0,29}$/', Rule::unique('stock_items')->where('tenant_id', $request->user()->tenant_id)],
'name' => 'required|string|max:200', 'unit' => 'required|string|max:30',
]);
DB::transaction(function () use ($request, $audit, $data) {
$item = StockItem::query()->create([...$data, 'tenant_id' => $request->user()->tenant_id, 'created_by' => $request->user()->id]);
$audit->audit($request->user(), 'stock.item_created', null, null, $item->toArray());
});
return back();
}
public function warehouse(Request $request, RequisitionWorkflow $audit): RedirectResponse
{
$this->access($request, true);
$request->merge(['code' => Str::upper(trim($request->string('code')->toString()))]);
$data = $request->validate([
'code' => ['required', 'regex:/^[A-Z0-9][A-Z0-9._-]{0,29}$/', Rule::unique('warehouses')->where('tenant_id', $request->user()->tenant_id)],
'name' => 'required|string|max:200',
]);
DB::transaction(function () use ($request, $audit, $data) {
$warehouse = Warehouse::query()->create([...$data, 'tenant_id' => $request->user()->tenant_id, 'created_by' => $request->user()->id]);
$audit->audit($request->user(), 'stock.warehouse_created', null, null, $warehouse->toArray());
});
return back();
}
}