select(['id', 'created_by_id', 'subtotal', 'total', 'notes', 'stock_type', 'created_at']) ->with([ 'createdBy:id', 'createdBy.userProfile:id,user_id,full_name', 'restockItems' => fn($q) => $q ->select(['id', 'restock_id', 'product_variant_id', 'quantity', 'unit_price', 'subtotal']) ->orderByRaw('(SELECT name FROM product_variants WHERE product_variants.id = restock_items.product_variant_id)'), 'restockItems.productVariant:id,product_id,name,stock,reject_stock,retail_stock', 'restockItems.productVariant.product:id,name', ]) ->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); $paginator->getCollection()->each(function (Restock $restock) { $restock->restockItems->each(function (RestockItem $item) { if (! $item->productVariant) { return; } $media = $item->productVariant->getFirstMedia('photos'); $item->productVariant->photo_url = $media ? $this->s3Service->getTemporaryUrl($media->file_name) : null; }); }); return $paginator; } public function store(array $data): Restock { return DB::transaction(function () use ($data) { $now = now(); $subtotal = 0; $stockType = $data['stock_type'] ?? ProductStockQuality::GOOD->value; $itemRows = $this->buildItemRows($data['items'], $stockType, $now, $subtotal); $restock = Restock::create([ 'created_by_id' => auth()->id(), 'subtotal' => $subtotal, 'total' => $subtotal, '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($subtotal, 0, ',', '.') . ' berhasil dicatat oleh ' . auth()->user()->full_name . '.', url: route('admin.manage.restocks.index'), ); return $restock; }); } public function update(Restock $restock, array $data): Restock { return 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(); $subtotal = 0; $stockType = $data['stock_type'] ?? $restock->stock_type->value; $itemRows = $this->buildItemRows($data['items'], $stockType, $now, $subtotal); foreach ($itemRows as &$row) { $row['restock_id'] = $restock->id; } DB::table('restock_items')->insert($itemRows); $restock->update([ 'subtotal' => $subtotal, 'total' => $subtotal, 'notes' => $data['notes'] ?? null, 'stock_type' => $stockType, ]); $this->applyStock($data['items'], $stockType, 1); $this->syncPhoto($restock, $data); return $restock; }); } public function destroy(Restock $restock): bool { return 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->clearMediaCollection('photos'); $restock->delete(); return true; }); } private function buildItemRows(array $items, string $stockType, $now, int &$subtotal): 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, &$subtotal) { $quantity = (int) $item['quantity']; $unitPrice = (int) ($prices[$item['product_variant_id']] ?? 0); $itemSubtotal = $unitPrice * $quantity; $subtotal += $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(); } }