135 lines
4.6 KiB
PHP
135 lines
4.6 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\Studio\Stock\StockOpname;
|
|
|
|
use App\Enums\StockOpnameStatus;
|
|
use App\Models\StockOpname;
|
|
use App\Services\StockActivityLogService;
|
|
use App\Traits\Components\WithConfirmation;
|
|
use App\Traits\Components\WithToast;
|
|
use App\Traits\Utilities\WithRequestNotification;
|
|
use Illuminate\Contracts\View\View;
|
|
use Illuminate\Support\Facades\Artisan;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Livewire\Attributes\Title;
|
|
use Livewire\Component;
|
|
|
|
#[Title('Stock Opname')]
|
|
class Index extends Component
|
|
{
|
|
use WithConfirmation, WithRequestNotification, WithToast;
|
|
|
|
public function generate(): void
|
|
{
|
|
Artisan::call('stock-opname:generate');
|
|
|
|
$periodMonth = now()->format('Y-m');
|
|
|
|
$this->toast('Stock opname untuk bulan '.formatDateLocalized($periodMonth, 'F Y').' berhasil digenerate.', 'Berhasil');
|
|
}
|
|
|
|
public function requestApproval(StockOpname $stockOpname): void
|
|
{
|
|
$stockOpname->update(['status' => StockOpnameStatus::PENDING_APPROVAL]);
|
|
|
|
$this->toast('Permohonan persetujuan berhasil diajukan.', 'Berhasil');
|
|
|
|
$this->redirectRoute('studio.stock.stock_opname.index', navigate: true);
|
|
}
|
|
|
|
public function approveApproval(StockOpname $stockOpname): void
|
|
{
|
|
DB::transaction(function () use ($stockOpname) {
|
|
|
|
$stockOpname->update(['status' => StockOpnameStatus::COMPLETED]);
|
|
|
|
$outlet = $stockOpname->outlet;
|
|
|
|
foreach ($stockOpname->items as $item) {
|
|
if (is_null($item->qty_physical)) {
|
|
continue;
|
|
}
|
|
|
|
$itemable = $item->itemable;
|
|
|
|
if (! $itemable) {
|
|
continue;
|
|
}
|
|
|
|
$morphType = class_basename($itemable);
|
|
|
|
$relationMap = [
|
|
'Perfume' => 'perfumes',
|
|
'Product' => 'products',
|
|
'Bottle' => 'bottles',
|
|
];
|
|
|
|
if (! isset($relationMap[$morphType])) {
|
|
continue;
|
|
}
|
|
|
|
$relationName = $relationMap[$morphType];
|
|
|
|
$currentPivot = $outlet->$relationName()
|
|
->where($itemable->getTable().'.id', $itemable->id)
|
|
->first();
|
|
|
|
// Current stock is the stock of the item in the outlet at the time the stock opname was created
|
|
$currentStock = $currentPivot?->pivot?->stock ?? 0;
|
|
|
|
// Calculate the difference between the physical stock and the system stock AT THE TIME the stock opname was created
|
|
// then apply the difference to the current stock
|
|
// Example: qty_system=100, qty_physical=90, transaction 20 occurred
|
|
// currentStock=80, difference=-10, newStock=80+(-10)=70 ✓
|
|
$difference = $item->qty_physical - $item->qty_system;
|
|
$newStock = max(0, $currentStock + $difference);
|
|
|
|
$outlet->$relationName()->syncWithoutDetaching([
|
|
$itemable->id => ['stock' => $newStock],
|
|
]);
|
|
|
|
StockActivityLogService::stockOpname(
|
|
stockOpname: $stockOpname,
|
|
product: $itemable,
|
|
quantityChange: $newStock - $currentStock,
|
|
previousStock: $currentStock,
|
|
newStock: $newStock,
|
|
note: $stockOpname->note
|
|
);
|
|
}
|
|
});
|
|
|
|
$this->toast('Stock opname berhasil disetujui dan stok outlet disesuaikan.', 'Berhasil');
|
|
|
|
$this->redirectRoute('studio.stock.stock_opname.index', navigate: true);
|
|
}
|
|
|
|
public function render(): View
|
|
{
|
|
$stockOpname = StockOpname::with(['outlet', 'items'])
|
|
->where('period_month', now()->format('Y-m'))
|
|
->get()
|
|
->map(function ($stockOpname) {
|
|
$total = $stockOpname->items()->count();
|
|
|
|
$filled = $stockOpname->items()
|
|
->whereNotNull('qty_physical')
|
|
->count();
|
|
|
|
$percent = $total > 0 ? floor(($filled / $total) * 100) : 0;
|
|
|
|
$stockOpname->total_item = $total;
|
|
$stockOpname->progress_filled = $filled;
|
|
$stockOpname->progress_percent = $percent;
|
|
$stockOpname->period_month = formatDateLocalized($stockOpname->period_month, 'F Y');
|
|
|
|
return $stockOpname;
|
|
});
|
|
|
|
return view('livewire.studio.stock.stock-opname.index', [
|
|
'pageTitle' => 'Stock Opname',
|
|
'stockOpname' => $stockOpname,
|
|
]);
|
|
}
|
|
}
|