310 lines
14 KiB
PHP
310 lines
14 KiB
PHP
<?php
|
||
|
||
namespace App\Filament\Resources;
|
||
|
||
use App\Filament\Resources\TintoreriaResource\Pages;
|
||
use App\Filament\Resources\TintoreriaResource\RelationManagers;
|
||
use App\Models\Tintoreria;
|
||
use Filament\Forms;
|
||
use Filament\Forms\Form;
|
||
use Filament\Resources\Resource;
|
||
use Filament\Tables;
|
||
use Filament\Tables\Table;
|
||
use Illuminate\Database\Eloquent\Builder;
|
||
use Illuminate\Database\Eloquent\SoftDeletingScope;
|
||
use Filament\Forms\Components\{
|
||
Select,
|
||
TextInput,
|
||
DatePicker,
|
||
Textarea,
|
||
Section
|
||
};
|
||
use Filament\Tables\Columns\{
|
||
TextColumn,
|
||
BadgeColumn
|
||
};
|
||
|
||
class TintoreriaResource extends Resource
|
||
{
|
||
protected static ?string $model = Tintoreria::class;
|
||
|
||
protected static ?string $navigationGroup = 'Gestión';
|
||
protected static ?int $navigationSort = 5;
|
||
protected static ?string $navigationIcon = 'heroicon-o-eye-dropper';
|
||
|
||
public static function form(Form $form): Form
|
||
{
|
||
return $form->schema([
|
||
Section::make('Datos iniciales')
|
||
->schema([
|
||
Select::make('proveedor_id')
|
||
->label('Tintorería')
|
||
->relationship(
|
||
'proveedor',
|
||
'nombre',
|
||
fn($query) => $query->where('categoria', 'tintoreria')
|
||
)
|
||
->searchable()
|
||
->preload()
|
||
->required(),
|
||
|
||
Select::make('orden_produccion_id')
|
||
->label('Orden de Producción')
|
||
->options(fn () => \App\Models\OrdenProduccion::all()->mapWithKeys(function ($op) {
|
||
// Verificar que ambos procesos estén completos
|
||
$verificacion = $op->verificarProcesosCompletos();
|
||
|
||
if (!$verificacion['completo']) {
|
||
// Si no están completos, no mostrar o mostrar con advertencia
|
||
return [];
|
||
}
|
||
|
||
// Calcular cantidad disponible (mínimo entre ojales y prensillas terminados - ya enviadas)
|
||
$disponible = $op->getCantidadDisponibleParaTintoreria();
|
||
|
||
if ($disponible > 0) {
|
||
return [$op->id => "{$op->numero_orden} (Disponible: {$disponible}) [O:{$verificacion['ojales_terminados']} P:{$verificacion['prensillas_terminadas']}]"];
|
||
}
|
||
return [];
|
||
})->filter())
|
||
->searchable()
|
||
->preload()
|
||
->reactive()
|
||
->required()
|
||
->afterStateUpdated(function ($state, $set) {
|
||
if ($state) {
|
||
$orden = \App\Models\OrdenProduccion::find($state);
|
||
|
||
if ($orden) {
|
||
$disponible = $orden->getCantidadDisponibleParaTintoreria();
|
||
$verificacion = $orden->verificarProcesosCompletos();
|
||
|
||
$set('cantidad_enviada', $disponible ?: 0);
|
||
$set('fecha_envio', now()->toDateString());
|
||
$set('prenda_nombre_info', $orden->prenda_modelo ?? 'Sin especificar');
|
||
|
||
// Información adicional sobre los procesos
|
||
$set('info_procesos', "Ojales: {$verificacion['ojales_terminados']} | Prensillas: {$verificacion['prensillas_terminadas']}");
|
||
}
|
||
} else {
|
||
$set('prenda_nombre_info', null);
|
||
$set('info_procesos', null);
|
||
}
|
||
})
|
||
->helperText('Solo se muestran órdenes con AMBOS procesos (ojales Y prensillas) terminados'),
|
||
|
||
TextInput::make('prenda_nombre_info')
|
||
->label('Prenda')
|
||
->disabled()
|
||
->dehydrated(false)
|
||
->visible(fn ($get) => $get('orden_produccion_id') !== null)
|
||
->helperText('Nombre de la prenda de la orden de producción seleccionada'),
|
||
|
||
TextInput::make('info_procesos')
|
||
->label('Estado de Procesos')
|
||
->disabled()
|
||
->dehydrated(false)
|
||
->visible(fn ($get) => $get('orden_produccion_id') !== null)
|
||
->helperText('Cantidad terminada en cada proceso'),
|
||
|
||
TextInput::make('tipo_proceso')
|
||
->label('Tipo de proceso')
|
||
->required(),
|
||
|
||
DatePicker::make('fecha_envio')
|
||
->required()
|
||
->default(now()),
|
||
|
||
TextInput::make('cantidad_enviada')
|
||
->numeric()
|
||
->required()
|
||
->reactive()
|
||
->helperText(function ($get) {
|
||
$opId = $get('orden_produccion_id');
|
||
if (!$opId) return 'Selecciona una OP primero';
|
||
|
||
// Calcular disponible desde ojales/prensillas terminados
|
||
$ojalesTerminados = \App\Models\Ojal::where('orden_produccion_id', $opId)
|
||
->where('terminada', true)
|
||
->sum('cantidad_enviada');
|
||
|
||
// Restar lo que ya se envió a tintorería
|
||
$yaEnTintoreria = \App\Models\Tintoreria::where('orden_produccion_id', $opId)
|
||
->sum('cantidad_enviada');
|
||
|
||
$disponible = $ojalesTerminados - $yaEnTintoreria;
|
||
|
||
return 'Sugerido: ' . max(0, $disponible);
|
||
}),
|
||
|
||
TextInput::make('valor_por_prenda')
|
||
->label('Valor por prenda')
|
||
->numeric()
|
||
->prefix('$')
|
||
->default(0)
|
||
->required()
|
||
->helperText('Valor a pagar por cada prenda procesada'),
|
||
|
||
TextInput::make('total_pagar')
|
||
->label('Total a pagar')
|
||
->numeric()
|
||
->prefix('$')
|
||
->readOnly()
|
||
->default(0)
|
||
->helperText('Se calcula automáticamente: (cantidad recibida × valor por prenda) - descuentos por cobros'),
|
||
])
|
||
->columns(2),
|
||
|
||
Section::make('Recepciones')
|
||
->description('Resumen de recepciones. Para registrar recepciones múltiples use el botón "Agregar recepción" en la cabecera o la relación "Recepciones" abajo.')
|
||
->schema([
|
||
Forms\Components\Placeholder::make('recepcion_summary')
|
||
->label('Resumen')
|
||
->content(function ($get) {
|
||
$id = $get('id');
|
||
if (! $id) return 'Sin recepciones registradas';
|
||
|
||
$tintoreria = \App\Models\Tintoreria::find($id);
|
||
if (!$tintoreria) return 'Sin datos';
|
||
|
||
$enviada = (int) ($tintoreria->cantidad_enviada ?? 0);
|
||
$recepciones = (int) $tintoreria->recepciones()->sum(\DB::raw('cantidad - COALESCE(prendas_defectuosas, 0)'));
|
||
$reprocesos = (int) $tintoreria->ajustes()->where('tipo', 'reproceso')->sum('cantidad');
|
||
$cobros = (int) $tintoreria->cobros()->sum('cantidad');
|
||
$cobrosValor = (float) $tintoreria->cobros()->sum('valor');
|
||
|
||
$recibidoNeto = $recepciones - $reprocesos;
|
||
$disponibleSiguienteProceso = $recepciones - $reprocesos - $cobros;
|
||
$faltantes = max(0, $enviada - $recepciones + $reprocesos);
|
||
|
||
$valorPorPrenda = (float) ($tintoreria->valor_por_prenda ?? 0);
|
||
$cantidadBase = $tintoreria->cantidad_recibida ?? $enviada;
|
||
$valorTotal = $cantidadBase * $valorPorPrenda;
|
||
$valorAPagar = max(0, $valorTotal - $cobrosValor);
|
||
|
||
return "╯━━━ PRENDAS ━━━
|
||
📦 Enviadas: {$enviada}
|
||
|
||
✅ Recibidas: {$recepciones}
|
||
🔧 En reproceso: {$reprocesos}
|
||
📊 Neto recibido: {$recibidoNeto}
|
||
👉 Disponible siguiente proceso: {$disponibleSiguienteProceso}
|
||
|
||
💰 Con problemas (cobradas): {$cobros} prendas
|
||
⚠️ Pendientes: {$faltantes}
|
||
|
||
━━━ PAGO ━━━
|
||
💵 Valor total: $" . number_format($valorTotal, 0) . "
|
||
💸 Descuento cobros: $" . number_format($cobrosValor, 0) . "
|
||
✅ A pagar: $" . number_format($valorAPagar, 0);
|
||
}),
|
||
|
||
Forms\Components\Placeholder::make('instrucciones')
|
||
->label('Acciones')
|
||
->content('Use el botón "Agregar recepción" en la cabecera para registrar una recepción rápida. También puede gestionar todas las recepciones en la sección relacionada al final de la página.'),
|
||
])
|
||
->columns(1)
|
||
->collapsed(),
|
||
]);
|
||
}
|
||
|
||
public static function table(Table $table): Table
|
||
{
|
||
return $table
|
||
->columns([
|
||
TextColumn::make('ordenProduccion.numero_orden')
|
||
->label('OP')
|
||
->searchable(),
|
||
|
||
TextColumn::make('proveedor.nombre')
|
||
->label('Tintorería')
|
||
->searchable(),
|
||
|
||
TextColumn::make('tipo_proceso'),
|
||
|
||
TextColumn::make('cantidad_enviada'),
|
||
|
||
TextColumn::make('recibidas_neto')
|
||
->label('Cantidad recibida')
|
||
->getStateUsing(function ($record) {
|
||
$recepciones = (int) $record->recepciones()->sum(\DB::raw('cantidad - COALESCE(prendas_defectuosas, 0)'));
|
||
$reprocesos = (int) $record->ajustes()->where('tipo', 'reproceso')->sum('cantidad');
|
||
$cobros = (int) $record->cobros()->sum('cantidad');
|
||
return $recepciones - $reprocesos - $cobros;
|
||
})
|
||
->sortable(query: function ($query, $direction) {
|
||
return $query->withSum('recepciones as recepciones_sum', \DB::raw('cantidad - COALESCE(prendas_defectuosas, 0)'))
|
||
->orderBy('recepciones_sum', $direction);
|
||
}),
|
||
|
||
TextColumn::make('faltantes')
|
||
->label('Faltan')
|
||
->sortable(),
|
||
|
||
\Filament\Tables\Columns\BadgeColumn::make('estado_recepcion')
|
||
->label('Estado')
|
||
->formatStateUsing(function ($state, $record) {
|
||
$faltan = $record->faltantes ?? 0;
|
||
|
||
if ($faltan > 0) {
|
||
return 'Parcial';
|
||
}
|
||
|
||
return 'Completado';
|
||
})
|
||
->colors([
|
||
'warning' => 'Parcial',
|
||
'success' => 'Completado',
|
||
]),
|
||
|
||
TextColumn::make('cobros_total')
|
||
->label('Cobros')
|
||
->money('COP'),
|
||
|
||
TextColumn::make('total_pagar')
|
||
->label('Total a pagar')
|
||
->money('COP')
|
||
->sortable(),
|
||
|
||
TextColumn::make('ultima_recepcion')
|
||
->label('Fecha recepción')
|
||
->getStateUsing(fn($record) => optional($record->recepciones()->latest('fecha_recepcion')->first())->fecha_recepcion)
|
||
->date(),
|
||
])
|
||
->defaultSort('created_at', 'desc')
|
||
->filters([
|
||
Tables\Filters\SelectFilter::make('orden_produccion_id')
|
||
->label('Orden de Producción')
|
||
->relationship('ordenProduccion', 'numero_orden')
|
||
->searchable()
|
||
->preload(),
|
||
])
|
||
->actions([
|
||
Tables\Actions\EditAction::make(),
|
||
])
|
||
->bulkActions([
|
||
Tables\Actions\BulkActionGroup::make([
|
||
Tables\Actions\DeleteBulkAction::make(),
|
||
]),
|
||
]);
|
||
}
|
||
|
||
public static function getRelations(): array
|
||
{
|
||
return [
|
||
RelationManagers\RecepcionesRelationManager::class,
|
||
RelationManagers\CobrosRelationManager::class,
|
||
RelationManagers\AjustesRelationManager::class,
|
||
];
|
||
}
|
||
|
||
public static function getPages(): array
|
||
{
|
||
return [
|
||
'index' => Pages\ListTintorerias::route('/'),
|
||
'create' => Pages\CreateTintoreria::route('/create'),
|
||
'edit' => Pages\EditTintoreria::route('/{record}/edit'),
|
||
];
|
||
}
|
||
}
|