store/app/Services/Manage/StockRetailService.php

152 lines
5.5 KiB
PHP

<?php
namespace App\Services\Manage;
use App\Enums\OwnerVerificationAction;
use App\Enums\OwnerVerificationStatus;
use App\Models\OwnerVerificationRequest;
use App\Models\ProductVariant;
use App\Models\StockRetailHistory;
use App\Models\User;
use App\Services\System\PushNotificationService;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException;
class StockRetailService
{
public function __construct(
private readonly PushNotificationService $pushNotificationService,
) {}
/**
* Submit a stock retail transfer request for owner verification.
*/
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()->findOrFail($variantId);
if ($variant->stock < $quantity) {
throw ValidationException::withMessages([
'quantity' => "Stok bagus tidak mencukupi. Stok saat ini: {$variant->stock} pcs.",
]);
}
$existingPending = OwnerVerificationRequest::query()
->where('subject_type', ProductVariant::class)
->where('subject_id', $variant->id)
->where('action', OwnerVerificationAction::STOCK_RETAIL_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::STOCK_RETAIL_TRANSFER,
'status' => OwnerVerificationStatus::PENDING,
'subject_type' => ProductVariant::class,
'subject_id' => $variant->id,
'submitted_by_id' => $user->id,
'payload' => [
'old' => [
'stock' => $variant->stock,
'stock_retail' => $variant->stock_retail,
],
'new' => [
'quantity' => $quantity,
'stock' => $variant->stock - $quantity,
'stock_retail' => $variant->stock_retail + $quantity,
'notes' => $notes,
'variant_name' => $variant->name,
'product_name' => $variant->product?->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'),
);
$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'),
);
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.',
]);
}
}
/**
* Apply the stock retail transfer when owner approves.
*/
public function applyStockRetailTransfer(OwnerVerificationRequest $request): void
{
$payload = $request->payload ?? [];
$newData = $payload['new'] ?? [];
$quantity = (int) ($newData['quantity'] ?? 0);
if ($quantity <= 0) {
return;
}
DB::transaction(function () use ($request, $quantity, $newData): void {
$variant = ProductVariant::query()
->whereKey($request->subject_id)
->lockForUpdate()
->firstOrFail();
$stockBefore = $variant->stock;
$stockRetailBefore = $variant->stock_retail;
$variant->decrement('stock', $quantity);
$variant->increment('stock_retail', $quantity);
StockRetailHistory::create([
'product_variant_id' => $variant->id,
'user_id' => $request->submitted_by_id,
'quantity' => $quantity,
'stock_before' => $stockBefore,
'stock_retail_before' => $stockRetailBefore,
'stock_after' => $stockBefore - $quantity,
'stock_retail_after' => $stockRetailBefore + $quantity,
'notes' => $newData['notes'] ?? null,
'created_at' => now(),
]);
});
}
/**
* Reject the stock retail transfer (no-op since nothing changed).
*/
public function rejectStockRetailTransfer(OwnerVerificationRequest $request): void
{
// No-op: nothing was changed yet, so nothing to rollback.
}
}