user()->purchasing_role, $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(), ]); return Inertia::render('stock/index', [ 'items' => $items, 'warehouses' => $warehouses, 'balances' => $balances, 'movements' => $movements->latest('id')->paginate(20)->withQueryString(), 'filters' => $filters, 'canManage' => in_array($request->user()->purchasing_role, [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(); } }