- Add a custom hook `useCardTableExpand` for managing expandable card tables. - Refactor product columns to include variant edit and delete handlers. - Integrate `CardTable` component for displaying products with expandable variant details. - Create `ProductCardRow` component for rendering product information in a card format. - Implement variant editing functionality with a dedicated `ProductVariantEdit` component. - Add `VariantSubRow` component to display variant details in a table format. - Update routes to handle product variant editing and deletion. - Enhance tests to cover variant editing and deletion scenarios.
130 lines
4.3 KiB
PHP
130 lines
4.3 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Admin\Master\Product;
|
|
|
|
use App\Models\Product;
|
|
use App\Models\ProductPrice;
|
|
use App\Models\ProductVariant;
|
|
use App\Services\NotificationService;
|
|
use App\Services\S3PresignedService;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Str;
|
|
use Spatie\MediaLibrary\MediaCollections\Models\Media;
|
|
|
|
class ProductVariantService
|
|
{
|
|
public function __construct(
|
|
private S3PresignedService $s3Service = new S3PresignedService,
|
|
) {}
|
|
|
|
public function getForEdit(ProductVariant $variant): array
|
|
{
|
|
$variant->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) {
|
|
$fileName = pathinfo($s3Key, PATHINFO_BASENAME);
|
|
$name = pathinfo($s3Key, PATHINFO_FILENAME);
|
|
|
|
Media::create([
|
|
'model_type' => ProductVariant::class,
|
|
'model_id' => $variant->id,
|
|
'uuid' => Str::uuid(),
|
|
'collection_name' => 'photos',
|
|
'name' => $name,
|
|
'file_name' => $s3Key,
|
|
'mime_type' => 'image/jpeg',
|
|
'disk' => 's3',
|
|
'conversions_disk' => 's3',
|
|
'size' => 0,
|
|
'manipulations' => [],
|
|
'custom_properties' => [],
|
|
'generated_conversions' => [],
|
|
'responsive_images' => [],
|
|
'order_column' => $order + 1,
|
|
]);
|
|
}
|
|
}
|
|
|
|
public function getTemporaryUrl(ProductVariant $variant): ?string
|
|
{
|
|
$media = $variant->getMedia('photos')->first();
|
|
|
|
return $media ? $this->s3Service->getTemporaryUrl($media->file_name) : null;
|
|
}
|
|
}
|