can('ver variantes'); } protected static ?string $model = ProductVariant::class; protected static ?string $navigationIcon = 'heroicon-o-tag'; public static function form(Form $form): Form { return $form ->schema([ Forms\Components\Select::make('producto_id') ->relationship('producto', 'nombre') ->searchable() ->required() ->reactive() ->afterStateUpdated(fn($set, $get, $component) => self::updateSkuAndBarcode($set, $get, $component)), Forms\Components\Select::make('color_id') ->relationship('color', 'name') ->searchable() ->required() ->reactive() ->afterStateUpdated(fn($set, $get, $component) => self::updateSkuAndBarcode($set, $get, $component)), Forms\Components\Select::make('size_id') ->relationship('size', 'name') ->searchable() ->required() ->reactive() ->afterStateUpdated(fn($set, $get, $component) => self::updateSkuAndBarcode($set, $get, $component)), Forms\Components\TextInput::make('sku') ->label('SKU') ->required() ->maxLength(50) ->dehydrated(), // Se guarda en la base de datos TextInput::make('barcode') ->label('Código de Barras') ->required() ->length(13) ->dehydrated() ->suffixAction( Action::make('imprimirQr') ->icon('heroicon-o-printer') ->color('primary') ->hidden(fn($record) => is_null($record)) ->action(fn($state) => redirect()->route('imprimir.barcode', ['barcode' => $state])) ->hidden(fn($record) => is_null($record)) ), Forms\Components\Section::make('Asignación de Bodega') ->schema([ Forms\Components\Select::make('bodega_id') ->label('Bodega Inicial') ->options(\App\Models\Bodega::pluck('nombre', 'id')) ->required() ->searchable() ->preload() ->default(function () { // Intentar obtener la bodega principal como default $bodegaPrincipal = \App\Models\Bodega::where('nombre', 'Principal')->first(); return $bodegaPrincipal?->id; }) ->helperText('Seleccione la bodega donde se asignará el stock inicial de esta variante'), Forms\Components\TextInput::make('stock_inicial') ->label('Stock Inicial') ->numeric() ->required() ->default(0) ->minValue(0) ->helperText('Stock que se asignará en la bodega seleccionada'), Forms\Components\Placeholder::make('info_bodega') ->label('Información') ->content('La variante se creará con el stock especificado en la bodega seleccionada. Podrá distribuir a otras bodegas posteriormente.') ]) ->columns(2) ->hiddenOn('edit'), // Solo mostrar en creación ]); } protected static function updateSkuAndBarcode($set, $get, $component) { $producto = $get('producto_id') ? \App\Models\Producto::find($get('producto_id')) : null; $color = $get('color_id') ? \App\Models\Color::find($get('color_id')) : null; $size = $get('size_id') ? \App\Models\Size::find($get('size_id')) : null; $exists = ProductVariant::where('producto_id', $get('producto_id')) ->where('color_id', $get('color_id')) ->where('size_id', $get('size_id')) ->exists(); if ($exists) { // Mostrar notificación en Filament Notification::make() ->title('Error') ->body('Esta combinación de producto, color y talla ya existe.') ->danger() ->send(); } // Generar SKU basado en el producto, color y talla $baseSku = strtoupper(substr($producto?->nombre ?? 'XX', 0, 2)) . '-' . strtoupper(substr($color?->name ?? 'XX', 0, 2)) . '-' . strtoupper(substr($size?->name ?? 'XX', 0, 2)); // Asegurar SKU único $sku = $baseSku; $counter = 1; while (ProductVariant::where('sku', $sku)->exists()) { $sku = $baseSku . '-' . $counter; $counter++; } // Obtener datos para el código de barras $countryCode = '57'; // Código de país (puedes cambiarlo) $categoryId = $producto?->categoria_id ?? 00; $productId = $producto?->id ?? 00000; $variantId = ProductVariant::where('producto_id',$productId)->count() ?? 0; $variantId += 1; // Generar código de barras estructurado $eanService = app(\App\Services\EAN13Service::class); $barcode = $eanService->generateUniqueBarcode($countryCode, $categoryId, $productId, $variantId); // Asignar valores al formulario $set('sku', $sku); $set('barcode', $barcode); } public static function table(Table $table): Table { return $table ->columns([ Tables\Columns\TextColumn::make('producto.nombre') ->numeric() ->sortable(), Tables\Columns\TextColumn::make('color.name') ->numeric() ->sortable(), Tables\Columns\TextColumn::make('size.name') ->numeric() ->sortable(), Tables\Columns\TextColumn::make('stock_total') ->label('Stock Total') ->getStateUsing(function (ProductVariant $record): string { $stockTotal = $record->getStockEfectivo(); $bodegas = $record->bodegas()->count(); return "{$stockTotal} ({$bodegas} bodegas)"; }) ->tooltip('Stock total distribuido en todas las bodegas') ->sortable(query: function ($query, $direction) { return $query->withSum('bodegas as stock_total', 'variante_bodega.stock') ->orderBy('stock_total', $direction); }), Tables\Columns\TextColumn::make('sku') ->label('SKU') ->searchable(), Tables\Columns\TextColumn::make('barcode') ->action(fn($record) => redirect()->route('imprimir.barcode', ['barcode' => $record->barcode])) ->tooltip('Imprimir código de barras'), Tables\Columns\TextColumn::make('created_at') ->dateTime() ->sortable() ->toggleable(isToggledHiddenByDefault: true), Tables\Columns\TextColumn::make('updated_at') ->dateTime() ->sortable() ->toggleable(isToggledHiddenByDefault: true), ]) ->paginated(true) //->paginationPageOptions([50]) ->defaultSort('created_at', 'desc') ->filters([ SelectFilter::make('producto_id') ->label('Producto') ->relationship('producto', 'nombre') // Relación con el modelo producto ->preload() // Precargar opcionesπ ->searchable(), // Permitir búsqueda en el filtro ]) ->actions([ Tables\Actions\EditAction::make(), ]) ->bulkActions([ Tables\Actions\BulkActionGroup::make([ Tables\Actions\DeleteBulkAction::make(), ]), ]); } public static function getRelations(): array { return [ RelationManagers\BodegasRelationManager::class, ]; } public static function getPages(): array { return [ 'index' => Pages\ListProductVariants::route('/'), 'create' => Pages\CreateProductVariant::route('/create'), 'edit' => Pages\EditProductVariant::route('/{record}/edit'), ]; } }