165 lines
6.0 KiB
PHP
165 lines
6.0 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Manage;
|
|
|
|
use App\Enums\OwnerVerificationAction;
|
|
use App\Enums\OwnerVerificationStatus;
|
|
use App\Enums\Permission;
|
|
use App\Models\OwnerVerificationRequest;
|
|
use App\Models\ProductVariant;
|
|
use App\Models\RetailStockHistory;
|
|
use App\Models\User;
|
|
use App\Services\System\PushNotificationService;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Validation\ValidationException;
|
|
|
|
class RetailStockService
|
|
{
|
|
public function __construct(
|
|
private readonly PushNotificationService $pushNotificationService,
|
|
) {}
|
|
|
|
public function transfer(int $variantId, int $quantity, User $user, ?string $notes = null): ?OwnerVerificationRequest
|
|
{
|
|
if ($quantity <= 0) {
|
|
throw ValidationException::withMessages([
|
|
'quantity' => 'Jumlah harus lebih dari 0.',
|
|
]);
|
|
}
|
|
|
|
$variant = ProductVariant::query()->with('product')->findOrFail($variantId);
|
|
|
|
if ($variant->stock < $quantity) {
|
|
throw ValidationException::withMessages([
|
|
'quantity' => "Stok bagus tidak mencukupi. Stok saat ini: {$variant->stock} pcs.",
|
|
]);
|
|
}
|
|
|
|
$isOwner = $user->can(Permission::OWNER_VERIFICATIONS_VERIFY->value);
|
|
|
|
if ($isOwner) {
|
|
$this->executeTransfer($variant, $quantity, $user, $notes);
|
|
|
|
return null;
|
|
}
|
|
|
|
$existingPending = OwnerVerificationRequest::query()
|
|
->where('subject_type', ProductVariant::class)
|
|
->where('subject_id', $variant->id)
|
|
->where('action', OwnerVerificationAction::RETAIL_STOCK_TRANSFER)
|
|
->where('status', OwnerVerificationStatus::PENDING)
|
|
->exists();
|
|
|
|
if ($existingPending) {
|
|
throw ValidationException::withMessages([
|
|
'quantity' => 'Masih ada pengajuan transfer stok ecer yang menunggu verifikasi untuk varian ini.',
|
|
]);
|
|
}
|
|
|
|
try {
|
|
$request = OwnerVerificationRequest::create([
|
|
'action' => OwnerVerificationAction::RETAIL_STOCK_TRANSFER,
|
|
'status' => OwnerVerificationStatus::PENDING,
|
|
'subject_type' => ProductVariant::class,
|
|
'subject_id' => $variant->id,
|
|
'submitted_by_id' => $user->id,
|
|
'payload' => [
|
|
'old' => [
|
|
'stock' => $variant->stock,
|
|
'retail_stock' => $variant->retail_stock,
|
|
],
|
|
'new' => [
|
|
'quantity' => $quantity,
|
|
'stock' => $variant->stock - $quantity,
|
|
'retail_stock' => $variant->retail_stock + $quantity,
|
|
'notes' => $notes,
|
|
'variant_name' => $variant->name,
|
|
'product_name' => $variant->product?->name ?? '-',
|
|
],
|
|
],
|
|
]);
|
|
|
|
$productName = $variant->product?->name ?? $variant->name;
|
|
|
|
$this->pushNotificationService->sendToRoles(
|
|
'📦 Transfer Stok Ecer Menunggu Persetujuan Owner',
|
|
"Pengajuan transfer {$quantity} pcs stok ecer untuk varian '{$variant->name}' menunggu verifikasi owner.",
|
|
['owner', 'developer'],
|
|
route('admin.master.products.index', ['search' => $productName]),
|
|
);
|
|
|
|
$this->pushNotificationService->sendToUser(
|
|
'📤 Pengajuan Transfer Terkirim',
|
|
"Pengajuan transfer {$quantity} pcs stok ecer untuk varian '{$variant->name}' telah dikirim dan menunggu verifikasi owner.",
|
|
$user->id,
|
|
route('admin.master.products.index', ['search' => $productName]),
|
|
);
|
|
|
|
return $request;
|
|
} catch (ValidationException $e) {
|
|
throw $e;
|
|
} catch (\Throwable $e) {
|
|
Log::error('Gagal mengajukan transfer stok ecer: '.$e->getMessage(), [
|
|
'trace' => $e->getTraceAsString(),
|
|
]);
|
|
|
|
throw ValidationException::withMessages([
|
|
'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
|
|
]);
|
|
}
|
|
}
|
|
|
|
public function applyRetailStockTransfer(OwnerVerificationRequest $request): void
|
|
{
|
|
$payload = $request->payload ?? [];
|
|
$newData = $payload['new'] ?? [];
|
|
$quantity = (int) ($newData['quantity'] ?? 0);
|
|
|
|
if ($quantity <= 0) {
|
|
return;
|
|
}
|
|
|
|
$variant = ProductVariant::query()->find($request->subject_id);
|
|
|
|
if (! $variant) {
|
|
return;
|
|
}
|
|
|
|
$this->executeTransfer($variant, $quantity, $request->submittedBy, $newData['notes'] ?? null);
|
|
}
|
|
|
|
private function executeTransfer(ProductVariant $variant, int $quantity, User $user, ?string $notes = null): void
|
|
{
|
|
DB::transaction(function () use ($variant, $quantity, $user, $notes): void {
|
|
$variant = ProductVariant::query()
|
|
->whereKey($variant->id)
|
|
->lockForUpdate()
|
|
->firstOrFail();
|
|
|
|
$stockBefore = $variant->stock;
|
|
$retailStockBefore = $variant->retail_stock;
|
|
|
|
$variant->decrement('stock', $quantity);
|
|
$variant->increment('retail_stock', $quantity);
|
|
|
|
RetailStockHistory::create([
|
|
'product_variant_id' => $variant->id,
|
|
'user_id' => $user->id,
|
|
'quantity' => $quantity,
|
|
'stock_before' => $stockBefore,
|
|
'retail_stock_before' => $retailStockBefore,
|
|
'stock_after' => $stockBefore - $quantity,
|
|
'retail_stock_after' => $retailStockBefore + $quantity,
|
|
'notes' => $notes,
|
|
'created_at' => now(),
|
|
]);
|
|
});
|
|
}
|
|
|
|
public function rejectRetailStockTransfer(OwnerVerificationRequest $request): void
|
|
{
|
|
// No-op: nothing was changed yet, so nothing to rollback.
|
|
}
|
|
}
|