load('productPrices'); $media = $variant->getMedia('photos')->first(); return [ 'id' => $variant->id, 'product_id' => $variant->product_id, 'name' => $variant->name, 'stock' => $variant->stock, 'reject_stock' => $variant->reject_stock, 'retail_stock' => $variant->retail_stock, 'photo_key' => $media?->file_name, 'photo_url' => $media ? $this->s3Service->getTemporaryUrl($media->file_name) : null, 'prices' => $variant->productPrices->map(fn ($p) => [ 'type' => $p->type->value, 'price' => $p->price, ]), ]; } public function update(ProductVariant $variant, array $data): ProductVariant { DB::transaction(function () use ($variant, $data) { $variant->update([ 'name' => $data['name'], 'stock' => $data['stock'], 'reject_stock' => $data['reject_stock'], 'retail_stock' => $data['retail_stock'], ]); $variant->productPrices()->delete(); foreach ($data['prices'] as $priceData) { ProductPrice::create([ 'variant_id' => $variant->id, 'type' => $priceData['type'], 'price' => $priceData['price'], ]); } if (! empty($data['photo_key'])) { $variant->clearMediaCollection('photos'); $this->registerPhotos($variant, [$data['photo_key']]); } }); NotificationService::notify( roles: ['Owner', 'Developer', 'Direktur', 'Admin Toko'], title: 'Varian Diperbarui', body: "Varian \"{$variant->name}\" berhasil diperbarui".' oleh '.auth()->user()->full_name.'.', url: route('admin.master.products.index'), ); return $variant->fresh(); } public function delete(Product $product, ProductVariant $variant): bool { $result = DB::transaction(function () use ($variant) { $variant->productPrices()->delete(); $variant->clearMediaCollection('photos'); return $variant->delete(); }); NotificationService::notify( roles: ['Owner', 'Developer', 'Direktur', 'Admin Toko'], title: 'Varian Dihapus', body: "Varian \"{$variant->name}\" dari produk \"{$product->name}\" berhasil dihapus".' oleh '.auth()->user()->full_name.'.', url: route('admin.master.products.index'), ); return $result; } public function registerPhotos(ProductVariant $variant, array $photoKeys): void { foreach ($photoKeys as $order => $s3Key) { $this->registerMedia( model: $variant, s3Key: $s3Key, collectionName: 'photos', orderColumn: $order + 1, ); } } public function getTemporaryUrl(ProductVariant $variant): ?string { $media = $variant->getMedia('photos')->first(); return $media ? $this->s3Service->getTemporaryUrl($media->file_name) : null; } public function transferStock(ProductVariant $variant, array $data): ProductVariant { $quantity = (int) $data['quantity']; if ($variant->stock < $quantity) { throw ValidationException::withMessages([ 'quantity' => "Stok bagus tidak mencukupi. Stok tersedia: {$variant->stock}.", ]); } DB::transaction(function () use ($variant, $quantity, $data) { $stockBefore = $variant->stock; $retailBefore = $variant->retail_stock; $variant->update([ 'stock' => $stockBefore - $quantity, 'retail_stock' => $retailBefore + $quantity, ]); StockMutation::create([ 'user_id' => auth()->id(), 'stockable_type' => ProductVariant::class, 'stockable_id' => $variant->id, 'type' => 'out', 'quantity' => -$quantity, 'stock_before' => $stockBefore, 'stock_after' => $stockBefore - $quantity, 'stock_quality' => 'good', 'description' => $data['description'] ?? 'Transfer stok bagus ke stok ecer', ]); StockMutation::create([ 'user_id' => auth()->id(), 'stockable_type' => ProductVariant::class, 'stockable_id' => $variant->id, 'type' => 'in', 'quantity' => $quantity, 'stock_before' => $retailBefore, 'stock_after' => $retailBefore + $quantity, 'stock_quality' => 'retail', 'description' => $data['description'] ?? 'Transfer stok bagus ke stok ecer', ]); }); NotificationService::notify( roles: ['Owner', 'Developer', 'Direktur', 'Admin Toko'], title: 'Transfer Stok', body: "{$quantity} unit dari varian \"{$variant->name}\" berhasil ditransfer dari stok bagus ke stok ecer".' oleh '.auth()->user()->full_name.'.', url: route('admin.master.products.index'), ); return $variant->fresh(); } }