value; protected static ?string $navigationGroup = 'Production'; public static function form(Form $form): Form { return $form->schema([ Group::make() ->schema([ Section::make([ Grid::make(1) ->schema([ Select::make('order_type') ->required() ->options(OrderType::class) ->searchable(), // Split::make([ Select::make('customer_id') ->required() ->label('Customer') ->options(Customer::all()->pluck('company_name', 'id')) ->searchable(), TextInput::make('customer_po') ->required() ->label('Customer PO'), Split::make([ DatePicker::make('order_date') ->required() ->default(today()), DatePicker::make('due_date') ->required() ->default(today()->add('10 days')), ]), Textarea::make('notes') ->rows(3), ])->columnSpan(1), Grid::make(1) ->schema([ ToggleButtons::make('status') ->required() ->default(OrderStatus::DRAFT->value) ->options(OrderStatus::class) ->inline(), ToggleButtons::make('order_attributes') ->options(OrderAttributes::class) ->multiple() ->inline(), ToggleButtons::make('printed') ->boolean() ->default(false) ->inline(), ToggleButtons::make('pre_production') ->label('Pre-production') ->default(false) ->boolean() ->inline() ->colors([ 'true' => 'info', 'false' => 'info', ]), ])->columnSpan(1), ])->columns(2) ->columnSpan(fn (?Order $record) => $record === null ? 6 : 5), Section::make() ->schema([ Placeholder::make('ID') ->label('Order ID') ->content(fn (Order $record): ?string => $record->internal_po), Placeholder::make('total_service_price') ->label('Total service price') ->content(fn (Order $record): ?string => '$'.number_format($record->total_service_price, 2)), Placeholder::make('created_at') ->label('Created') ->content(fn (Order $record): ?string => $record->created_at?->diffForHumans()), Placeholder::make('updated_at') ->label('Last modified') ->content(fn (Order $record): ?string => $record->updated_at?->diffForHumans()), ]) ->columnSpan(1) ->hidden(fn (?Order $record) => $record === null) ->extraAttributes(['class' => 'h-full']), ]) ->columns(6) ->columnSpan(2), TableRepeater::make('order_products') ->label('Garments') ->schema([ TextInput::make('sku') ->datalist(OrderProduct::all()->unique('sku')->pluck('sku')->toArray()), TextInput::make('product_name') ->datalist(OrderProduct::all()->unique('product_name')->pluck('product_name')->toArray()) ->required(), TextInput::make('color') ->datalist(OrderProduct::all()->unique('color')->pluck('color')->toArray()), Cluster::make([ TextInput::make('xs') ->placeholder('xs') ->rules('numeric'), TextInput::make('s') ->placeholder('s') ->rules('numeric'), TextInput::make('m') ->placeholder('m') ->rules('numeric'), TextInput::make('l') ->placeholder('l') ->rules('numeric'), TextInput::make('xl') ->placeholder('xl') ->rules('numeric'), TextInput::make('2xl') ->placeholder('2xl') ->rules('numeric'), TextInput::make('3xl') ->placeholder('3xl') ->rules('numeric'), TextInput::make('osfa') ->placeholder('osfa') ->rules('numeric'), ]) ->label('Sizes'), ]) ->reorderable() ->cloneable() ->defaultItems(1), Repeater::make('services') ->label('Product Services') ->schema([ Grid::make(19) ->schema([ Select::make('serviceType') ->options(ServiceType::all()->pluck('value', 'id')) ->columnSpan(4) ->placeholder('Select...') ->searchable() ->createOptionForm([ TextInput::make('name') ->label('Code') ->placeholder('Abbreviation here (example: \'Emb\'') ->required(), TextInput::make('value') ->placeholder('Full name here (example: \'Embroidery\'') ->required(), ]) ->createOptionUsing(function (array $data): int { return ServiceType::create($data)->getKey(); }), TextInput::make('serviceFileName') ->datalist(ServiceFile::all()->unique('name')->pluck('name')->toArray()) ->columnSpan(3) ->label('Logo Name'), TextInput::make('placement') ->datalist(ProductService::all()->unique('placement')->pluck('placement')->toArray()) ->columnSpan(3), TextInput::make('serviceFileSetupNumber') ->label('Setup') ->columnSpan(1) ->rules('numeric'), Cluster::make([ TextInput::make('serviceFileWidth') ->prefix('w') ->rules('numeric'), TextInput::make('serviceFileHeight') ->prefix('h') ->rules('numeric'), ]) ->label('Dimensions (inches)') ->columnSpan(4), TextInput::make('amount') ->label('Quantity') ->live() ->prefix('#') ->columnSpan(2) ->rules('numeric'), TextInput::make('amount_price') ->label('Amount') ->prefix('$') ->columnSpan(2) ->rules('numeric'), ]), Grid::make(9) ->schema([ TextInput::make('serviceFileCode') ->label('Code') ->datalist(ServiceFile::all()->unique('code')->pluck('code')->toArray()) ->columnSpan(1) ->placeholder('A1234'), Textarea::make('notes') ->placeholder('Thread colors...') ->columnSpan(8), ]), ]) ->reorderable() ->cloneable() ->columns(4) ->columnSpan(2) ->defaultItems(1), ]); } public static function table(Table $table): Table { return $table ->columns([ Tables\Columns\IconColumn::make('alert') ->getStateUsing(fn ($record) => $record->is_alert_danger || $record->is_alert_warning) ->label('') ->color(fn ($record) => $record->is_alert_danger ? 'danger' : 'warning') ->icon(function ($record) { return $record->is_alert_danger ? 'lucide-calendar-clock' : ($record->rush ? OrderAttributes::rush->getIcon() : null); }) ->size(IconColumnSize::Medium), TextColumn::make('internal_po') ->label('Internal PO') ->fontFamily('mono') ->color('info') ->searchable(query: function (Builder $query, $search) { return $query ->where('internal_po', 'like', "%{$search}%") ->orWhereHas('productServices', function (Builder $query) use ($search) { return $query->where('placement', 'like', "%{$search}%") ->orWhereHas('serviceFile', function (Builder $query) use ($search) { return $query->where('code', 'like', "%{$search}%") ->orWhere('name', 'like', "%{$search}%"); }); }); }) ->sortable(), TextColumn::make('customer.company_name') ->searchable() ->sortable(), TextColumn::make('customer_po') ->label('PO') ->wrap() ->weight('bold') ->color('code') ->searchable() ->sortable() ->extraHeaderAttributes([ 'class' => 'w-full', ]), TextColumn::make('order_date') ->searchable() ->sortable(), TextColumn::make('due_date') ->searchable() ->sortable(), TextColumn::make('status') ->badge() ->searchable() ->sortable(), ]) ->defaultSort('order_date', 'desc') ->filters([ Tables\Filters\Filter::make('order_date') ->form([ DatePicker::make('created_from'), DatePicker::make('created_until'), ]) ->query(function (Builder $query, array $data): Builder { return $query ->when( $data['created_from'], fn (Builder $query, $date): Builder => $query->whereDate('order_date', '>=', $date), ) ->when( $data['created_until'], fn (Builder $query, $date): Builder => $query->whereDate('order_date', '<=', $date), ); }), ], ) ->actions([ Tables\Actions\EditAction::make(), ]) ->bulkActions([ Tables\Actions\BulkAction::make('updateStatus') ->form([ Select::make('status') ->options(OrderStatus::class), ]) ->modalHeading('Change selected orders status') ->modalWidth(MaxWidth::Medium) ->action(function (array $data, Collection $records): void { foreach ($records as $record) { $record->status = $data['status']; $record->save(); } Notification::make() ->title(count($records).' item(s) updated successfully') ->success() ->send(); }) ->icon('lucide-pen') ->color('info') ->deselectRecordsAfterCompletion(), BulkActionGroup::make([ BulkAction::make('Create individual invoices') ->icon(IconEnum::INVOICE->value) ->action(function (Collection $records): void { [$invoiced, $toInvoice] = $records->partition(fn ($record) => $record->invoice); $toInvoice->each(function ($record) { $invoice = Invoice::create([ 'customer_id' => $record->customer->id, 'date' => today(), 'status' => InvoiceStatus::UNPAID->value, ]); $invoice->orders()->save($record); $invoice->calculateTotals(); $record->update(['status' => OrderStatus::INVOICED->value]); }); if ($invoiced->isNotEmpty()) { Notification::make() ->title("{$invoiced->count()} orders are already invoiced") ->warning() ->send(); } if ($toInvoice->isNotEmpty()) { Notification::make() ->title("Successfully created {$toInvoice->count()} invoice(s)") ->success() ->send(); } }), BulkAction::make('Add all to new invoice') ->icon(IconEnum::REPEAT->value) ->action(function (Collection $records): void { if ($records->pluck('customer_id')->unique()->count() !== 1) { Notification::make() ->title('Invalid order combination') ->body('Make sure all orders are from the same customer') ->danger() ->send(); return; } [$invoiced, $validOrders] = $records->partition(fn ($record) => $record->invoice); if ($validOrders->isNotEmpty()) { $invoice = Invoice::create([ 'customer_id' => $records->first()->customer_id, 'date' => today(), 'status' => InvoiceStatus::UNPAID->value, ]); $invoice->orders()->saveMany($validOrders); $invoice->calculateTotals(); // FIXME: Investigate why this is needed. Order::whereIn('id', $validOrders->pluck('id'))->update([ 'status' => OrderStatus::INVOICED->value, ]); } if ($invoiced->isNotEmpty()) { Notification::make() ->title('Some orders are already invoiced') ->body("{$invoiced->count()} orders are already invoiced and will not be added") ->warning() ->send(); } if ($validOrders->isNotEmpty()) { Notification::make() ->title('Invoice created') ->body("{$validOrders->count()} orders have been added to this invoice") ->success() ->send(); } }), ]) ->label('Invoicing') ->hidden(fn () => ! auth()->user()->is_admin), BulkActionGroup::make([ Tables\Actions\DeleteBulkAction::make(), ])->label('Other actions'), ]); } public static function getRelations(): array { return [ ]; } public static function getPages(): array { return [ 'index' => \App\Filament\Admin\Resources\OrderResource\Pages\ListOrders::route('/'), 'create' => \App\Filament\Admin\Resources\OrderResource\Pages\CreateOrder::route('/create'), 'edit' => \App\Filament\Admin\Resources\OrderResource\Pages\EditOrder::route('/{record}/edit'), ]; } }