feat: add stock adjustment functionality for warehouse items via a modal form.
This commit is contained in:
parent
5b8f89f693
commit
6477684540
@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Forms\Studio\Master\Warehouse;
|
||||
|
||||
use App\Models\Warehouse;
|
||||
use App\Services\StockActivityLogService;
|
||||
use Livewire\Form;
|
||||
|
||||
class AdjustmentStockForm extends Form
|
||||
{
|
||||
public string $itemId = '';
|
||||
|
||||
public string $type = '';
|
||||
|
||||
public string $name = '';
|
||||
|
||||
public string $oldStock = '0';
|
||||
|
||||
public string $newStock = '0';
|
||||
|
||||
public ?string $note = null;
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'newStock' => 'required',
|
||||
'note' => 'nullable',
|
||||
];
|
||||
}
|
||||
|
||||
public function validationAttributes(): array
|
||||
{
|
||||
return [
|
||||
'newStock' => 'stok baru',
|
||||
'note' => 'keterangan',
|
||||
];
|
||||
}
|
||||
|
||||
public function setAdjustment(string $itemId, string $type, string $name, int $stock): void
|
||||
{
|
||||
$this->itemId = $itemId;
|
||||
$this->type = $type;
|
||||
$this->name = $name;
|
||||
$this->oldStock = formatCurrencyNumber($stock);
|
||||
$this->newStock = formatCurrencyNumber($stock);
|
||||
$this->note = null;
|
||||
}
|
||||
|
||||
public function save(Warehouse $warehouse): void
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
$newStockValue = (int) str_replace(['.', ','], '', (string) $this->newStock);
|
||||
$oldStockValue = (int) str_replace(['.', ','], '', (string) $this->oldStock);
|
||||
|
||||
$typeId = explode('_', $this->itemId);
|
||||
$type = $typeId[0];
|
||||
$id = $typeId[1];
|
||||
|
||||
$relation = match ($type) {
|
||||
'perfume' => 'perfumes',
|
||||
'product' => 'products',
|
||||
'bottle' => 'bottles',
|
||||
default => null,
|
||||
};
|
||||
|
||||
if (! $relation) {
|
||||
return;
|
||||
}
|
||||
|
||||
$item = $warehouse->{$relation}()->find($id);
|
||||
|
||||
if (! $item) {
|
||||
return;
|
||||
}
|
||||
|
||||
$warehouse->{$relation}()->updateExistingPivot($id, [
|
||||
'stock' => $newStockValue,
|
||||
]);
|
||||
|
||||
// Log the activity
|
||||
StockActivityLogService::logWarehouse(
|
||||
logName: 'adjustment',
|
||||
event: 'update',
|
||||
description: "Manual adjustment for {$this->name}: changed from {$oldStockValue} to {$newStockValue}. Note: ".($this->note ?? '-'),
|
||||
performedOn: $item,
|
||||
warehouse: $warehouse,
|
||||
quantityChange: $newStockValue - $oldStockValue,
|
||||
previousStock: $oldStockValue,
|
||||
newStock: $newStockValue,
|
||||
extraProperties: [
|
||||
'note' => $this->note,
|
||||
'adjustment_type' => 'manual',
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -2,16 +2,26 @@
|
||||
|
||||
namespace App\Livewire\Studio\Master\Warehouse;
|
||||
|
||||
use App\Livewire\Forms\Studio\Master\Warehouse\AdjustmentStockForm;
|
||||
use App\Models\Warehouse;
|
||||
use App\Traits\Authorization\WithAuthorization;
|
||||
use App\Traits\Components\WithCloseModal;
|
||||
use App\Traits\Components\WithConfirmation;
|
||||
use App\Traits\Components\WithToast;
|
||||
use App\Traits\Notification\WithSubscribeNotification;
|
||||
use App\Traits\Utilities\WithUpdatedData;
|
||||
use Flux\Flux;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Livewire\Attributes\On;
|
||||
use Livewire\Attributes\Title;
|
||||
use Livewire\Component;
|
||||
|
||||
#[Title('Detail Gudang')]
|
||||
class Show extends Component
|
||||
{
|
||||
use WithAuthorization;
|
||||
use WithAuthorization, WithCloseModal, WithConfirmation, WithSubscribeNotification, WithToast, WithUpdatedData;
|
||||
|
||||
public AdjustmentStockForm $form;
|
||||
|
||||
public Warehouse $warehouse;
|
||||
|
||||
@ -19,6 +29,10 @@ class Show extends Component
|
||||
|
||||
public array $type = [];
|
||||
|
||||
public string $method = 'saveAdjustment';
|
||||
|
||||
public string $modalTitle = '';
|
||||
|
||||
public function mount(Warehouse $warehouse): void
|
||||
{
|
||||
$this->warehouse = $warehouse;
|
||||
@ -121,6 +135,29 @@ public function getItemsProperty()
|
||||
return $perfumes->concat($products)->concat($bottles);
|
||||
}
|
||||
|
||||
#[On('modal:open')]
|
||||
public function openModal(string $method, string $modalTitle, string $itemId, string $type, string $name, int $stock): void
|
||||
{
|
||||
$this->resetValidation();
|
||||
$this->resetErrorBag();
|
||||
|
||||
$this->method = $method;
|
||||
$this->modalTitle = $modalTitle;
|
||||
|
||||
$this->form->setAdjustment($itemId, $type, $name, $stock);
|
||||
}
|
||||
|
||||
public function saveAdjustment(): void
|
||||
{
|
||||
$this->canOrAbort('manage adjustment');
|
||||
|
||||
$this->form->save($this->warehouse);
|
||||
|
||||
$this->toast('Stok berhasil diperbarui.');
|
||||
|
||||
Flux::modals()->close();
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('livewire.studio.master.warehouse.show', [
|
||||
|
||||
@ -46,9 +46,9 @@
|
||||
<flux:table.column>Tipe</flux:table.column>
|
||||
<flux:table.column>Item</flux:table.column>
|
||||
<flux:table.column>Stok</flux:table.column>
|
||||
@can('audit stock warehouse')
|
||||
@canAny(['manage adjustment', 'audit stock warehouse'])
|
||||
<flux:table.column>Aksi</flux:table.column>
|
||||
@endcan
|
||||
@endcanAny
|
||||
</flux:table.columns>
|
||||
|
||||
|
||||
@ -57,22 +57,35 @@
|
||||
<flux:table.row :key="$item['id']">
|
||||
<flux:table.cell>
|
||||
<flux:badge size="sm"
|
||||
:color="$item['type'] === 'Parfum' ? 'fuchsia' : ($item['type'] === 'Produk' ? 'cyan' :
|
||||
:color="$item['type'] === 'Parfum' ? 'fuchsia' : ($item['type'] === 'Produk' ?
|
||||
'cyan' :
|
||||
'zinc')">
|
||||
{{ $item['type'] }}</flux:badge>
|
||||
</flux:table.cell>
|
||||
<flux:table.cell class="font-medium">{{ $item['name'] }}</flux:table.cell>
|
||||
<flux:table.cell>{{ number_format($item['stock'], 0, ',', '.') }}</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
@can('audit stock warehouse')
|
||||
<flux:tooltip content="Audit">
|
||||
<flux:button size="sm" variant="primary" icon="clipboard-document-list"
|
||||
href="{{ route('studio.master.warehouse.' . $item['type_key'] . '.audit', ['warehouse' => $warehouse->hash, $item['type_key'] => $item['hash_id']]) }}"
|
||||
wire:navigate>
|
||||
</flux:button>
|
||||
</flux:tooltip>
|
||||
@endcan
|
||||
</flux:table.cell>
|
||||
@canAny(['manage adjustment', 'audit stock warehouse'])
|
||||
<flux:table.cell>
|
||||
@can('audit stock warehouse')
|
||||
<flux:tooltip content="Audit">
|
||||
<flux:button size="sm" variant="primary" icon="clipboard-document-list"
|
||||
href="{{ route('studio.master.warehouse.' . $item['type_key'] . '.audit', ['warehouse' => $warehouse->hash, $item['type_key'] => $item['hash_id']]) }}"
|
||||
wire:navigate>
|
||||
</flux:button>
|
||||
</flux:tooltip>
|
||||
@endcan
|
||||
@can('manage adjustment')
|
||||
<flux:modal.trigger name="form-modal">
|
||||
<flux:tooltip content="Ubah">
|
||||
<flux:button variant="primary" color="yellow" icon="pencil-square"
|
||||
size="sm"
|
||||
wire:click="$dispatch('modal:open', { method: 'saveAdjustment', modalTitle: 'Ubah Stok', itemId: '{{ $item['id'] }}', type: '{{ $item['type'] }}', name: '{{ $item['name'] }}', stock: {{ $item['stock'] }} })">
|
||||
</flux:button>
|
||||
</flux:tooltip>
|
||||
</flux:modal.trigger>
|
||||
@endcan
|
||||
</flux:table.cell>
|
||||
@endcanAny
|
||||
</flux:table.row>
|
||||
@empty
|
||||
<flux:table.row>
|
||||
@ -85,4 +98,41 @@
|
||||
</flux:table>
|
||||
</flux:card>
|
||||
</div>
|
||||
|
||||
<flux:modal name="form-modal" class="w-[95%] max-w-sm md:max-w-xl mx-auto" @close="closeModal('form-modal')">
|
||||
<div class="p-4 space-y-6">
|
||||
<div>
|
||||
<flux:heading size="lg">{{ $modalTitle }}</flux:heading>
|
||||
<flux:subheading>Sesuaikan stok untuk {{ $form?->name ?? '-' }} ({{ $form?->type ?? '-' }})
|
||||
</flux:subheading>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<flux:field>
|
||||
<flux:label>Stok Saat Ini</flux:label>
|
||||
<flux:input wire:model="form.oldStock" disabled />
|
||||
</flux:field>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>Stok Baru <span class="text-red-500 ms-1">*</span></flux:label>
|
||||
<flux:input placeholder="Masukkan stok" x-mask:dynamic="$money($input, ',')"
|
||||
wire:model="form.newStock" autocomplete="off" />
|
||||
<flux:error name="form.newStock" />
|
||||
</flux:field>
|
||||
</div>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>Keterangan</flux:label>
|
||||
<flux:textarea placeholder="Contoh: Koreksi stok fisik, Rusak, dsb." wire:model="form.note" />
|
||||
<flux:error name="form.note" />
|
||||
</flux:field>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<flux:spacer />
|
||||
<flux:button variant="primary" wire:click="{{ $method }}">
|
||||
Simpan
|
||||
</flux:button>
|
||||
</div>
|
||||
</div>
|
||||
</flux:modal>
|
||||
</flux:main>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user