replace(',', '.') ->toString(); }; $unmaskMoney = function ($state) { if (blank($state)) { return 0; } if (is_int($state) || is_float($state)) { return $state; } return (float) str($state) ->replace('.', '') ->replace(',', '.') ->toString(); }; $recalculateTotal = function (callable $get, callable $set) use ($unmaskMoney, $unmaskQty): void { $quantity = $unmaskQty($get('egg_quantity')); $unitPrice = $unmaskMoney($get('unit_price')); $discount = $unmaskMoney($get('discount')); $subtotal = $quantity * $unitPrice; $totalAmount = max(0, $subtotal - $discount); $set('total_amount', number_format($totalAmount, 0, ',', '.')); }; return $schema ->components([ Grid::make(3) ->schema([ DatePicker::make('order_date') ->label('Tanggal Order') ->default(now()) ->required() ->native(false) ->displayFormat('l, d F Y'), Select::make('warehouse_id') ->label('Ambil Dari Gudang') ->relationship('warehouse', 'name') ->searchable() ->preload() ->native(false) ->required() ->live() ->default(fn () => Warehouse::first()?->id), Select::make('customer_id') ->label('Pelanggan') ->relationship('customer', 'name') ->searchable() ->preload() ->native(false), ]) ->columnSpanFull(), Grid::make(3) ->schema([ TextInput::make('egg_quantity') ->label('Jumlah Telur') ->placeholder('0') ->autocomplete(false) ->helperText('Jumlah per satuan.') ->required() ->live() ->afterStateUpdated(function ($state, callable $get, callable $set) use ($recalculateTotal): void { $recalculateTotal($get, $set); }) ->rules([ fn (callable $get, $record) => function (string $attribute, $value, $fail) use ($get, $record) { $warehouseId = $get('warehouse_id'); $unitId = $get('unit_id'); $quantity = (float) str_replace(',', '.', $value); if (! $warehouseId || ! $unitId || $quantity <= 0) { return; } $available = WarehouseStock::getQuantity($warehouseId, $unitId); // If we are editing, we add back the current quantity if ($record && $record->warehouse_id == $warehouseId && $record->unit_id == $unitId) { $available += (float) $record->egg_quantity; } if ($available < $quantity) { $unitAlias = Unit::find($unitId)?->alias ?? 'satuan'; $fail('Stok tidak mencukupi untuk unit ini di gudang tersebut. Tersedia: '.str_replace('.', ',', (float) $available)." {$unitAlias}"); } }, ]) ->currencyMask(thousandSeparator: '.', decimalSeparator: ',', precision: 2) ->formatStateUsing(fn ($state) => $state === null ? null : (float) $state) ->dehydrateStateUsing($unmaskQty), TextInput::make('unit_price') ->label('Harga per Satuan') ->placeholder('0') ->autocomplete(false) ->prefix('Rp') ->required() ->live() ->currencyMask(thousandSeparator: '.', decimalSeparator: ',', precision: 0) ->dehydrateStateUsing($unmaskMoney) ->afterStateUpdated(function ($state, callable $get, callable $set) use ($recalculateTotal): void { $recalculateTotal($get, $set); }), Select::make('unit_id') ->label('Satuan') ->relationship('unit', 'alias') ->searchable() ->preload() ->native(false) ->required() ->live(), ]) ->columnSpanFull(), TextInput::make('discount') ->label('Diskon') ->placeholder('0') ->autocomplete(false) ->default(0) ->prefix('Rp') ->live() ->afterStateUpdated(function ($state, callable $get, callable $set) use ($recalculateTotal): void { $recalculateTotal($get, $set); }) ->currencyMask(thousandSeparator: '.', decimalSeparator: ',', precision: 0) ->dehydrateStateUsing($unmaskMoney), TextInput::make('total_amount') ->label('Total') ->placeholder('0') ->readOnly() ->prefix('Rp') ->required() ->formatStateUsing(fn ($state) => number_format(($state ?? 0), 0, ',', '.')) ->dehydrateStateUsing($unmaskMoney), Textarea::make('notes') ->label('Catatan') ->placeholder('...') ->autocomplete(false) ->columnSpanFull(), ]) ->columns(2); } public static function table(Table $table): Table { return $table ->columns([ TextColumn::make('order_date') ->label('Tanggal Order') ->date('l, d F Y') ->sortable(), TextColumn::make('customer.name') ->label('Pelanggan') ->searchable() ->sortable(), TextColumn::make('warehouse.name') ->label('Gudang') ->searchable() ->sortable(), TextColumn::make('egg_quantity') ->label('Jumlah') ->getStateUsing(function ($record) { return str_replace('.', ',', (float) $record->egg_quantity)." {$record->unit?->alias}"; }) ->sortable(), TextColumn::make('unit_price') ->label('Harga per Satuan') ->money('IDR', decimalPlaces: 0) ->sortable(), TextColumn::make('discount') ->label('Diskon') ->money('IDR', decimalPlaces: 0) ->sortable() ->default(0), TextColumn::make('total_amount') ->label('Total') ->money('IDR', decimalPlaces: 0) ->sortable(), TextColumn::make('notes') ->label('Catatan') ->sortable() ->searchable(), ...TimestampColumns::make(), ]) ->filters([ TrashedFilter::make() ->native(false) ->visible(fn (): bool => auth()->user()->hasRole(RoleEnum::DEVELOPER)), ]) ->recordActions([ EditAction::make() ->label('Ubah'), DeleteAction::make(), ForceDeleteAction::make(), RestoreAction::make(), ]) ->toolbarActions([ BulkActionGroup::make([ ...DefaultBulkActions::make('Order'), ]), ]) ->emptyStateIcon(Heroicon::ShoppingCart) ->emptyStateDescription('Setelah Anda membuat data pertama, maka akan muncul disini.') ->defaultSort('created_at', 'desc') ->deferFilters(false); } public static function getPages(): array { return [ 'index' => ManageOrders::route('/'), ]; } public static function getRecordRouteBindingEloquentQuery(): Builder { return parent::getRecordRouteBindingEloquentQuery() ->withoutGlobalScopes([ SoftDeletingScope::class, ]); } }