feat: implement product logging for status changes and updates in ProductService and ProductVariantService

This commit is contained in:
Yoga Pangestu 2026-08-17 23:01:00 +07:00
parent 42f7214efa
commit 3e715518fb
2 changed files with 78 additions and 1 deletions

View File

@ -468,6 +468,8 @@ public function toggleStatus(Product $product): void
{
$this->assertNotPending($product);
$oldValues = $this->getProductLogValues($product);
$product->update([
'status' => $product->status->value === 'active' ? 'inactive' : 'active',
]);
@ -480,12 +482,16 @@ public function toggleStatus(Product $product): void
body: "Produk \"{$product->name}\" berhasil {$status}".' oleh '.auth()->user()->full_name.'.',
url: route('admin.master.products.index', ['highlight' => $product->id]),
);
$this->logUpdated($product, 'Produk', $oldValues, $this->getProductLogValues($product));
}
public function toggleFeatured(Product $product): void
{
$this->assertNotPending($product);
$oldValues = $this->getProductLogValues($product);
$product->update([
'is_featured' => ! $product->is_featured,
]);
@ -498,10 +504,14 @@ public function toggleFeatured(Product $product): void
body: "Produk \"{$product->name}\" berhasil {$status}".' oleh '.auth()->user()->full_name.'.',
url: route('admin.master.products.index', ['highlight' => $product->id]),
);
$this->logUpdated($product, 'Produk', $oldValues, $this->getProductLogValues($product));
}
public function approve(Product $product): void
{
$oldValues = $this->getProductLogValues($product);
$product->update([
'status' => ProductStatus::ACTIVE,
]);
@ -513,10 +523,14 @@ public function approve(Product $product): void
url: route('admin.master.products.index', ['highlight' => $product->id]),
additionalUser: $product->createdBy ?? null,
);
$this->logUpdated($product, 'Produk', $oldValues, $this->getProductLogValues($product));
}
public function reject(Product $product, string $reason = ''): void
{
$oldValues = $this->getProductLogValues($product);
$product->update([
'status' => ProductStatus::REJECTED,
'rejection_reason' => $reason,
@ -529,10 +543,14 @@ public function reject(Product $product, string $reason = ''): void
url: route('admin.master.products.index', ['highlight' => $product->id]),
additionalUser: $product->createdBy ?? null,
);
$this->logUpdated($product, 'Produk', $oldValues, $this->getProductLogValues($product));
}
public function resubmit(Product $product): void
{
$oldValues = $this->getProductLogValues($product);
$product->update([
'status' => ProductStatus::PENDING,
'rejection_reason' => null,
@ -544,6 +562,8 @@ public function resubmit(Product $product): void
body: "Produk \"{$product->name}\" telah diajukan ulang oleh ".auth()->user()->full_name.'.',
url: route('admin.master.products.index', ['highlight' => $product->id]),
);
$this->logUpdated($product, 'Produk', $oldValues, $this->getProductLogValues($product));
}
private function assertNotPending(Product $product): void

View File

@ -8,6 +8,7 @@
use App\Enums\Role;
use App\Models\Product;
use App\Models\ProductVariant;
use App\Services\Concerns\LogsFormHistory;
use App\Services\Concerns\RegistersMedia;
use App\Services\NotificationService;
use App\Services\S3PresignedService;
@ -18,7 +19,7 @@
class ProductVariantService
{
use HasRoleChecks, RegistersMedia;
use HasRoleChecks, LogsFormHistory, RegistersMedia;
public function __construct(
private S3PresignedService $s3Service,
@ -171,6 +172,8 @@ public function update(ProductVariant $variant, array $data): ProductVariant
{
$this->assertNotPending($variant->product);
$oldValues = $this->getProductLogValues($variant->product);
DB::transaction(function () use ($variant, $data) {
$oldData = $variant->only(['stock', 'reject_stock', 'retail_stock']);
@ -210,6 +213,9 @@ public function update(ProductVariant $variant, array $data): ProductVariant
url: route('admin.master.products.index', ['highlight' => $variant->product_id]),
);
$product = $variant->product()->with(['categories:id,name', 'productVariants.productPrices'])->first();
$this->logUpdated($product, 'Produk', $oldValues, $this->getProductLogValues($product));
return $variant->fresh();
}
@ -217,6 +223,8 @@ public function destroy(Product $product, ProductVariant $variant): bool
{
$this->assertNotPending($product);
$oldValues = $this->getProductLogValues($product);
$result = DB::transaction(function () use ($variant) {
return $variant->delete();
});
@ -228,6 +236,9 @@ public function destroy(Product $product, ProductVariant $variant): bool
url: route('admin.master.products.index'),
);
$product->refresh()->load(['categories:id,name', 'productVariants.productPrices']);
$this->logDeleted($product, 'Produk', $oldValues);
return $result;
}
@ -237,6 +248,8 @@ public function transferStock(ProductVariant $variant, array $data): ProductVari
$this->assertNotPending($variant->product);
$oldValues = $this->getProductLogValues($variant->product);
if ($variant->stock < $quantity) {
throw ValidationException::withMessages([
'quantity' => "Stok bagus tidak mencukupi. Stok tersedia: {$variant->stock}.",
@ -270,6 +283,9 @@ public function transferStock(ProductVariant $variant, array $data): ProductVari
url: route('admin.master.products.index', ['highlight' => $variant->product_id]),
);
$product = $variant->product()->with(['categories:id,name', 'productVariants.productPrices'])->first();
$this->logUpdated($product, 'Produk', $oldValues, $this->getProductLogValues($product));
return $variant->fresh();
}
@ -281,4 +297,45 @@ private function assertNotPending(Product $product): void
]);
}
}
private function getProductLogValues(Product $product): array
{
$product->load(['categories:id,name', 'productVariants.productPrices']);
$priceTypeLabels = [
'distributor' => 'Harga Distributor',
'agent' => 'Harga Agen',
'sub_agent' => 'Harga Sub Agen',
'wholesale' => 'Harga Grosir',
'retail' => 'Harga Ecer',
'tiktok' => 'Harga TikTok',
'shopee' => 'Harga Shopee',
'capital' => 'Harga Modal',
'reject_capital' => 'Harga Reject Modal',
'reject_selling' => 'Harga Reject Jual',
];
return [
'Nama Produk' => $product->name,
'Status' => $product->status?->label(),
'Unggulan' => $this->formatBoolean($product->is_featured),
'Kategori' => $product->categories->pluck('name')->toArray(),
'Deskripsi' => $product->description,
'Varian' => $product->productVariants->map(function ($variant) use ($priceTypeLabels) {
$variantData = [
'Nama Varian' => $variant->name,
'Stok Bagus' => $variant->stock,
'Stok Reject' => $variant->reject_stock,
'Stok Retail' => $variant->retail_stock,
];
foreach ($variant->productPrices as $price) {
$label = $priceTypeLabels[$price->type->value] ?? $price->type->label();
$variantData[$label] = $this->formatCurrency($price->price);
}
return $variantData;
})->toArray(),
];
}
}