- Introduced StockMutation model and service to handle stock changes. - Created migration for stock_mutations table. - Implemented stock mutation recording in various services (CuttingService, OrderService, PurchaseService, RestockService, RetailStockService). - Added StockHistoryController to manage stock history views. - Developed frontend components for displaying stock history and actions. - Updated routes to include stock history access with appropriate permissions. - Enhanced ProductVariant and RawMaterialPrice models to support stock mutations. - Added RowHistoryAction button for accessing stock history in product and raw material tables.
102 lines
3.5 KiB
PHP
102 lines
3.5 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Manage;
|
|
|
|
use App\Models\OwnerVerificationRequest;
|
|
use App\Models\ProductVariant;
|
|
use App\Models\User;
|
|
use App\Services\System\PushNotificationService;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Validation\ValidationException;
|
|
|
|
class RetailStockService
|
|
{
|
|
public function __construct(
|
|
private readonly PushNotificationService $pushNotificationService,
|
|
private readonly StockMutationService $stockMutationService,
|
|
) {}
|
|
|
|
public function transfer(int $variantId, int $quantity, User $user, ?string $notes = null): void
|
|
{
|
|
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.",
|
|
]);
|
|
}
|
|
|
|
$this->executeTransfer($variant, $quantity, $user, $notes);
|
|
|
|
$this->pushNotificationService->sendToRoles(
|
|
'📦 Perubahan Stok Ecer',
|
|
"Perubahan {$quantity} pcs stok ecer untuk varian '{$variant->name}' telah diterapkan oleh {$user->profile?->full_name}.",
|
|
['owner', 'developer', 'direktur'],
|
|
route('admin.master.products.index', ['search_id' => $variant->product_id]),
|
|
);
|
|
}
|
|
|
|
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();
|
|
|
|
$goodStockBefore = $variant->stock;
|
|
$retailStockBefore = $variant->retail_stock;
|
|
|
|
$variant->decrement('stock', $quantity);
|
|
$variant->increment('retail_stock', $quantity);
|
|
|
|
$this->stockMutationService->record(
|
|
stockable: $variant,
|
|
type: 'out',
|
|
quantity: -$quantity,
|
|
stockBefore: $goodStockBefore,
|
|
stockAfter: $goodStockBefore - $quantity,
|
|
stockQuality: 'good',
|
|
description: $notes ? "Transfer ke stok ecer: {$notes}" : 'Transfer ke stok ecer',
|
|
user: $user,
|
|
);
|
|
|
|
$this->stockMutationService->record(
|
|
stockable: $variant,
|
|
type: 'in',
|
|
quantity: $quantity,
|
|
stockBefore: $retailStockBefore,
|
|
stockAfter: $retailStockBefore + $quantity,
|
|
stockQuality: 'retail',
|
|
description: $notes ? "Transfer dari stok bagus: {$notes}" : 'Transfer dari stok bagus',
|
|
user: $user,
|
|
);
|
|
});
|
|
}
|
|
}
|