feat: Enhance WarehouseResource with stock management features, including a repeater for stock entries and improved quantity handling in the WarehouseStock model

This commit is contained in:
Yoga Pangestu 2026-03-07 12:03:12 +07:00
parent c6e7119b94
commit 0a643efbb6
5 changed files with 56 additions and 35 deletions

View File

@ -13,6 +13,8 @@
use App\Models\Warehouse; use App\Models\Warehouse;
use BackedEnum; use BackedEnum;
use Filament\Actions\BulkActionGroup; use Filament\Actions\BulkActionGroup;
use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Resources\Resource; use Filament\Resources\Resource;
use Filament\Schemas\Schema; use Filament\Schemas\Schema;
@ -46,11 +48,47 @@ public static function form(Schema $schema): Schema
return $schema return $schema
->components([ ->components([
TextInput::make('name') TextInput::make('name')
->label('Nama Gudang') ->label('Nama')
->placeholder('Gudang Utama') ->placeholder('Gudang Utama')
->autocomplete(false) ->autocomplete(false)
->required() ->required()
->maxLength(30), ->maxLength(30),
Repeater::make('stocks')
->label('Stok Telur')
->relationship('stocks')
->schema([
Select::make('unit_id')
->label('Satuan')
->relationship('unit', 'name')
->required()
->searchable()
->preload(),
TextInput::make('quantity')
->label('Jumlah')
->placeholder('0')
->autocomplete(false)
->required()
->currencyMask(thousandSeparator: '.', decimalSeparator: ',', precision: 2)
->formatStateUsing(fn ($state) => $state === null ? null : (float) $state)
->dehydrateStateUsing(function ($state) {
if (blank($state)) {
return 0;
}
if (is_numeric($state)) {
return (float) $state;
}
return (float) str($state)
->replace('.', '')
->replace(',', '.')
->toString();
}),
])
->columns(2)
->addActionLabel('Tambah Stok')
->columnSpanFull(),
]) ])
->columns(1); ->columns(1);
} }
@ -60,22 +98,23 @@ public static function table(Table $table): Table
return $table return $table
->columns([ ->columns([
TextColumn::make('name') TextColumn::make('name')
->label('Nama Gudang') ->label('Nama')
->searchable() ->searchable()
->sortable(), ->sortable(),
TextColumn::make('stocks') TextColumn::make('stocks')
->label('Stok Telur') ->label('Stok')
->getStateUsing(function (Warehouse $record) { ->getStateUsing(function (Warehouse $record) {
return $record->stocks() return $record->stocks()
->where('quantity', '>', 0) ->where('quantity', '>', 0)
->with('unit') ->with('unit')
->get() ->get()
->map(fn ($stock) => (float) $stock->quantity.' '.$stock->unit?->alias.($stock->is_broken ? ' (Pecah)' : '')) ->map(fn ($stock) => str_replace('.', ',', (float) $stock->quantity).' '.$stock->unit?->alias)
->join(', ') ?: 'Kosong'; ->toArray();
}) })
->badge() ->listWithLineBreaks()
->color('info'), ->color('info')
->placeholder('Kosong'),
...TimestampColumns::make(), ...TimestampColumns::make(),
]) ])
@ -86,7 +125,7 @@ public static function table(Table $table): Table
]) ])
->recordActions([ ->recordActions([
EditAction::make() EditAction::make()
->modalWidth(Width::Large), ->modalWidth(Width::FourExtraLarge),
DeleteAction::make(), DeleteAction::make(),

View File

@ -11,6 +11,10 @@ class WarehouseStock extends Model
protected $guarded = ['id']; protected $guarded = ['id'];
protected $casts = [
'quantity' => 'decimal:2',
];
public function warehouse() public function warehouse()
{ {
return $this->belongsTo(Warehouse::class); return $this->belongsTo(Warehouse::class);
@ -23,10 +27,13 @@ public function unit()
public static function adjustStock(int $warehouseId, int $unitId, bool $isBroken, float $quantity): void public static function adjustStock(int $warehouseId, int $unitId, bool $isBroken, float $quantity): void
{ {
if ($isBroken) {
return;
}
$stock = self::firstOrCreate([ $stock = self::firstOrCreate([
'warehouse_id' => $warehouseId, 'warehouse_id' => $warehouseId,
'unit_id' => $unitId, 'unit_id' => $unitId,
'is_broken' => $isBroken,
], [ ], [
'quantity' => 0, 'quantity' => 0,
]); ]);

View File

@ -12,11 +12,10 @@ public function up(): void
$table->id(); $table->id();
$table->foreignId('warehouse_id')->constrained()->cascadeOnDelete(); $table->foreignId('warehouse_id')->constrained()->cascadeOnDelete();
$table->foreignId('unit_id')->constrained()->cascadeOnDelete(); $table->foreignId('unit_id')->constrained()->cascadeOnDelete();
$table->boolean('is_broken')->default(false);
$table->decimal('quantity', 12, 2)->default(0); $table->decimal('quantity', 12, 2)->default(0);
$table->timestamps(); $table->timestamps();
$table->unique(['warehouse_id', 'unit_id', 'is_broken'], 'warehouse_unit_condition_unique'); $table->unique(['warehouse_id', 'unit_id'], 'warehouse_unit_unique');
}); });
} }

View File

@ -1,24 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('egg_collections', function (Blueprint $table) {
$table->decimal('total_eggs', 12, 2)->default(0)->change();
$table->decimal('total_broken_eggs', 12, 2)->default(0)->change();
});
}
public function down(): void
{
Schema::table('egg_collections', function (Blueprint $table) {
$table->unsignedInteger('total_eggs')->default(0)->change();
$table->unsignedInteger('total_broken_eggs')->default(0)->change();
});
}
};