select(['id', 'created_by_id', 'total', 'notes', 'stock_type', 'created_at']) ->with([ 'createdBy:id', 'createdBy.userProfile:id,user_id,full_name', ]) ->selectRaw("{$itemsCountQuery} as items_count") ->selectRaw("{$totalQtyQuery} as total_qty") ->selectRaw("{$productNamesQuery} as product_names") ->when($highlight, fn ($q) => $q->where('id', $highlight)) ->when($search, function ($q) use ($search) { $q->whereHas('restockItems.productVariant.product', fn ($sq) => $sq->where('name', 'like', "%{$search}%")) ->orWhere('notes', 'like', "%{$search}%"); }) ->orderBy($sort, $direction) ->paginate($perPage); return $paginator; } public function getItems(Restock $restock): \Illuminate\Support\Collection { return $restock->restockItems() ->select(['id', 'restock_id', 'product_variant_id', 'quantity', 'unit_price', 'subtotal']) ->with(['productVariant:id,product_id,name', 'productVariant.product:id,name']) ->orderByRaw('(SELECT name FROM product_variants WHERE product_variants.id = restock_items.product_variant_id)') ->get() ->each(function (RestockItem $item) { if (! $item->productVariant) { return; } $media = $item->productVariant->getFirstMedia('images'); $item->productVariant->photo_url = $media ? $this->s3Service->getTemporaryUrl($media->getPath()) : null; $item->productVariant->photo_conversion_url = $media ? $this->s3Service->getTemporaryUrl($media->getPath('thumb')) : null; }); } public function store(array $data): Restock { return DB::transaction(function () use ($data) { $now = now(); $total = 0; $stockType = $data['stock_type'] ?? ProductStockQuality::GOOD->value; $itemRows = $this->buildItemRows($data['items'], $stockType, $now, $total); $restock = Restock::create([ 'created_by_id' => auth()->id(), 'total' => $total, 'notes' => $data['notes'] ?? null, 'stock_type' => $stockType, ]); foreach ($itemRows as &$row) { $row['restock_id'] = $restock->id; } DB::table('restock_items')->insert($itemRows); $this->applyStock($data['items'], $stockType, 1); $this->syncPhoto($restock, $data); NotificationService::notify( roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_TOKO], title: 'Restock Baru', body: 'Restock '.($stockType === ProductStockQuality::GOOD->value ? 'produk' : 'reject').' sebesar Rp '.number_format($total, 0, ',', '.').' berhasil dicatat oleh '.auth()->user()->full_name.'.', url: route('admin.manage.restocks.index', ['highlight' => $restock->id]), ); return $restock; }); } public function update(Restock $restock, array $data): Restock { $restock = DB::transaction(function () use ($restock, $data) { $restock->load('restockItems'); $restock->restockItems->each(function (RestockItem $item) use ($restock) { $this->adjustVariantStock($item->product_variant_id, $item->quantity, -1, $restock->stock_type->value); }); $restock->restockItems()->delete(); $now = now(); $total = 0; $stockType = $data['stock_type'] ?? $restock->stock_type->value; $itemRows = $this->buildItemRows($data['items'], $stockType, $now, $total); foreach ($itemRows as &$row) { $row['restock_id'] = $restock->id; } DB::table('restock_items')->insert($itemRows); $restock->update([ 'total' => $total, 'notes' => $data['notes'] ?? null, 'stock_type' => $stockType, ]); $this->applyStock($data['items'], $stockType, 1); $this->syncPhoto($restock, $data); return $restock; }); NotificationService::notify( roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_TOKO], title: 'Restock Diperbarui', body: 'Restock berhasil diperbarui oleh '.auth()->user()->full_name.'.', url: route('admin.manage.restocks.index', ['highlight' => $restock->id]), ); return $restock; } public function destroy(Restock $restock): bool { $result = DB::transaction(function () use ($restock) { $restock->load('restockItems'); $restock->restockItems->each(function (RestockItem $item) use ($restock) { $this->adjustVariantStock($item->product_variant_id, $item->quantity, -1, $restock->stock_type->value); }); $restock->restockItems()->delete(); $restock->delete(); return true; }); NotificationService::notify( roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_TOKO], title: 'Restock Dihapus', body: 'Restock berhasil dihapus oleh '.auth()->user()->full_name.'.', url: route('admin.manage.restocks.index'), ); return $result; } private function buildItemRows(array $items, string $stockType, $now, int &$total): array { $priceType = $stockType === ProductStockQuality::REJECT->value ? PriceType::REJECT : PriceType::CAPITAL; $variantIds = collect($items)->pluck('product_variant_id')->unique()->all(); $prices = ProductVariant::query() ->whereKey($variantIds) ->with('productPrices:id,variant_id,type,price') ->get() ->mapWithKeys(function (ProductVariant $variant) use ($priceType) { $price = $variant->productPrices ->first(fn ($p) => $p->type === $priceType); return [$variant->id => $price?->price ?? 0]; }); return collect($items)->map(function ($item) use ($now, $prices, &$total) { $quantity = (int) $item['quantity']; $unitPrice = (int) ($prices[$item['product_variant_id']] ?? 0); $itemSubtotal = $unitPrice * $quantity; $total += $itemSubtotal; return [ 'restock_id' => null, 'user_id' => auth()->id(), 'product_variant_id' => $item['product_variant_id'], 'quantity' => $quantity, 'unit_price' => $unitPrice, 'subtotal' => $itemSubtotal, 'created_at' => $now, 'updated_at' => $now, ]; })->toArray(); } }