parfum/app/Traits/StockTransfer/WithStockTransferStock.php
Yoga Pangestu dffc9f9e72 feat: add stock transfer management functionality
- Implemented stock transfer creation and management with Livewire components.
- Created StockTransfer and StockTransferItem models with necessary relationships.
- Added migration files for stock_transfers and stock_transfer_items tables.
- Developed traits for handling stock transfer items (add, delete, update).
- Integrated stock transfer logging in StockActivityLogService.
- Updated ViewServiceProvider to include stock transfer permissions and routes.
- Created frontend views for stock transfer form and index with appropriate UI components.
- Added role permissions for stock transfer actions in RolePermissionSeeder.
2026-01-09 21:41:49 +07:00

170 lines
5.9 KiB
PHP

<?php
namespace App\Traits\StockTransfer;
use App\Models\Bottle;
use App\Models\Perfume;
use App\Models\Product;
use App\Models\StockTransfer;
use App\Services\StockActivityLogService;
trait WithStockTransferStock
{
public function transferStockBetweenOutlets($sourceOutlet, $destinationOutlet, $item): void
{
$quantity = $item->quantity;
switch ($item->transferable_type) {
case Perfume::class:
$relation = 'perfumes';
break;
case Product::class:
$relation = 'products';
break;
case Bottle::class:
$relation = 'bottles';
break;
default:
return;
}
// Decrease source outlet stock
$sourceItem = $sourceOutlet->{$relation}()
->where("{$relation}.id", $item->transferable_id)
->lockForUpdate()
->withPivot('stock')
->first();
$sourcePreviousStock = 0;
if ($sourceItem) {
$sourcePreviousStock = $sourceItem->pivot->stock ?? 0;
}
if ($sourcePreviousStock < $quantity) {
throw new \Exception("Stok {$item->transferable->name} di outlet asal tidak mencukupi. Tersisa {$sourcePreviousStock}, diminta {$quantity}.");
}
$sourceNewStock = $sourcePreviousStock - $quantity;
$sourceOutlet->{$relation}()->updateExistingPivot($item->transferable_id, [
'stock' => $sourceNewStock,
]);
// Increase destination outlet stock
$destinationItem = $destinationOutlet->{$relation}()
->where("{$relation}.id", $item->transferable_id)
->lockForUpdate()
->withPivot('stock')
->first();
if ($destinationItem) {
$destinationPreviousStock = $destinationItem->pivot->stock ?? 0;
$destinationOutlet->{$relation}()->updateExistingPivot($item->transferable_id, [
'stock' => $destinationPreviousStock + $quantity,
]);
} else {
$destinationPreviousStock = 0;
$destinationOutlet->{$relation}()->attach($item->transferable_id, [
'stock' => $quantity,
]);
}
$destinationNewStock = $destinationPreviousStock + $quantity;
$stockTransfer = $item->stockTransfer ?? StockTransfer::find($item->stock_transfer_id);
if ($stockTransfer) {
StockActivityLogService::transfer(
stockTransfer: $stockTransfer,
product: $item->transferable,
quantity: $quantity,
sourcePreviousStock: $sourcePreviousStock,
sourceNewStock: $sourceNewStock,
destinationPreviousStock: $destinationPreviousStock,
destinationNewStock: $destinationNewStock
);
}
}
public function reverseStockTransferBetweenOutlets($sourceOutlet, $destinationOutlet, $item): void
{
$quantity = $item->quantity;
switch ($item->transferable_type) {
case Perfume::class:
$relation = 'perfumes';
break;
case Product::class:
$relation = 'products';
break;
case Bottle::class:
$relation = 'bottles';
break;
default:
return;
}
// Return items to source outlet
$sourcePivot = $sourceOutlet->{$relation}()
->where("{$relation}.id", $item->transferable_id)
->first();
$currentSourceStock = $sourcePivot?->pivot?->stock ?? 0;
$newSourceStock = $currentSourceStock + $quantity;
if ($sourcePivot) {
$sourceOutlet->{$relation}()->updateExistingPivot($item->transferable_id, [
'stock' => $newSourceStock,
]);
} else {
$sourceOutlet->{$relation}()->attach($item->transferable_id, [
'stock' => $quantity,
]);
}
$stockTransfer = $item->stockTransfer ?? StockTransfer::find($item->stock_transfer_id);
if ($stockTransfer) {
StockActivityLogService::logOutlet(
logName: StockActivityLogService::LOG_TRANSFER,
event: 'increase',
description: "Stock Transfer #{$stockTransfer->hash_id} Deleted: Returned {$quantity} stock to source outlet.",
performedOn: $item->transferable,
outlet: $sourceOutlet,
quantityChange: $quantity,
previousStock: $currentSourceStock,
newStock: $newSourceStock,
extraProperties: ['stock_transfer_id' => $stockTransfer->id]
);
}
// Remove items from destination outlet
$destinationPivot = $destinationOutlet->{$relation}()
->where("{$relation}.id", $item->transferable_id)
->first();
$currentDestinationStock = $destinationPivot?->pivot?->stock ?? 0;
$newDestinationStock = max(0, $currentDestinationStock - $quantity);
if ($destinationPivot) {
$destinationOutlet->{$relation}()->updateExistingPivot($item->transferable_id, [
'stock' => $newDestinationStock,
]);
}
if ($stockTransfer) {
StockActivityLogService::logOutlet(
logName: StockActivityLogService::LOG_TRANSFER,
event: 'decrease',
description: "Stock Transfer #{$stockTransfer->hash_id} Deleted: Removed {$quantity} stock from destination outlet.",
performedOn: $item->transferable,
outlet: $destinationOutlet,
quantityChange: -1 * abs($quantity),
previousStock: $currentDestinationStock,
newStock: $newDestinationStock,
extraProperties: ['stock_transfer_id' => $stockTransfer->id]
);
}
}
}