This commit is contained in:
Lizandro Guarnizo
2026-02-04 16:35:34 -05:00
parent cb7bc1229b
commit f37b2fe8d0
9 changed files with 598 additions and 105 deletions
+164 -69
View File
@@ -101,78 +101,118 @@ class OjalPresillaResource extends Resource
->visible(fn ($get) => $get('orden_produccion_id') !== null)
->helperText('Nombre de la prenda de la orden de producción seleccionada'),
TextInput::make('tipo')
->label('Tipo')
->placeholder('Ej: Ojal, Prensilla, Botón, etc.')
Select::make('tipo')
->label('Tipo de Proceso')
->options([
'Ojal' => 'Ojal',
'Prensilla' => 'Prensilla',
])
->required()
->helperText('Especifica el tipo de accesorio'),
->reactive()
->afterStateUpdated(function ($state, $set, $get) {
$opId = $get('orden_produccion_id');
if (!$opId) return;
$op = \App\Models\OrdenProduccion::find($opId);
if (!$op) return;
// Calcular disponibilidad según el tipo
$cantidadRecibida = $op->confecciones->sum('cantidad_recibida');
$arreglos = \App\Models\Ajuste::where('referencia_type', \App\Models\Confeccion::class)
->whereHas('referencia', function($q) use ($opId) {
$q->where('orden_produccion_id', $opId);
})->where('tipo', 'arreglo')->sum('cantidad');
$cobros = \App\Models\Cobro::where('referencia_type', \App\Models\Confeccion::class)
->whereHas('referencia', function($q) use ($opId) {
$q->where('orden_produccion_id', $opId);
})->sum('cantidad');
$cantidadConfecciones = $cantidadRecibida - $arreglos - $cobros;
if ($state === 'Ojal') {
// Para ojales: disponible desde confección
$yaEnviado = \App\Models\Ojal::where('orden_produccion_id', $opId)
->sum('cantidad_enviada');
$disponible = $cantidadConfecciones - $yaEnviado;
$set('cantidad_enviada', max(0, $disponible));
} else if ($state === 'Prensilla') {
// Para prensillas: solo lo que ya terminó en ojales
$ojalesTerminados = \App\Models\Ojal::where('orden_produccion_id', $opId)
->where('terminada', true)
->sum('cantidad_enviada');
$yaEnviado = \App\Models\Prensilla::where('orden_produccion_id', $opId)
->sum('cantidad_enviada');
$disponible = $ojalesTerminados - $yaEnviado;
$set('cantidad_enviada', max(0, $disponible));
}
})
->helperText(fn ($get) => $get('tipo') === 'Prensilla'
? '⚠️ Solo se pueden enviar prendas que hayan terminado el proceso de Ojales'
: 'Primer proceso: las prendas vienen desde Confección'),
Select::make('proveedor_id')
->label('Proveedor/Usuario Principal')
->options(fn () => \App\Models\Proveedor::where('categoria', 'ojales_prensillas')
->orWhere('categoria', 'empleado')
->pluck('nombre', 'id'))
->searchable()
->preload()
->nullable()
->helperText('Principal responsable del proceso'),
Select::make('usuario_asignado_id')
->label('Usuario Asignado (Operario)')
->relationship('usuarioAsignado', 'name')
->searchable()
->preload()
->nullable()
->helperText('Usuario del sistema responsable'),
TextInput::make('cantidad_enviada')
->label('Cantidad total disponible')
->label('Cantidad')
->numeric()
->readOnly()
->helperText(function ($state) {
if (!$state) return 'Selecciona una OP primero';
return "Máximo disponible: {$state} unidades";
})
->live(),
Repeater::make('distribuciones')
->label('Distribución entre proveedores')
->schema([
Select::make('proveedor_id')
->label('Proveedor')
->options(fn () => \App\Models\Proveedor::where('categoria', 'empleado')->pluck('nombre', 'id'))
->searchable()
->required(),
TextInput::make('cantidad')
->label('Cantidad')
->numeric()
->required()
->minValue(1)
->live()
->afterStateUpdated(function ($state, $set, $get) {
// Validar que la suma no exceda el total
$total = (int) ($get('../../cantidad_enviada') ?? 0);
$distribuciones = $get('../../distribuciones') ?? [];
$suma = 0;
foreach ($distribuciones as $dist) {
$suma += (int) ($dist['cantidad'] ?? 0);
}
if ($suma > $total) {
throw \Illuminate\Validation\ValidationException::withMessages([
'cantidad' => "La suma de las distribuciones ({$suma}) excede el total disponible ({$total})."
]);
}
}),
])
->defaultItems(1)
->addActionLabel('Agregar proveedor')
->required()
->minValue(1)
->reactive()
->helperText(function ($get) {
$total = (int) ($get('cantidad_enviada') ?? 0);
$distribuciones = $get('distribuciones') ?? [];
$suma = 0;
foreach ($distribuciones as $dist) {
$suma += (int) ($dist['cantidad'] ?? 0);
}
$restante = $total - $suma;
$opId = $get('orden_produccion_id');
$tipo = $get('tipo');
if ($restante < 0) {
return "⚠️ Excede el total por " . abs($restante) . " unidades";
} elseif ($restante > 0) {
return "Faltan {$restante} unidades por distribuir";
if (!$opId || !$tipo) return 'Selecciona una OP y tipo primero';
$op = \App\Models\OrdenProduccion::find($opId);
if (!$op) return '';
if ($tipo === 'Ojal') {
$cantidadRecibida = $op->confecciones->sum('cantidad_recibida');
$arreglos = \App\Models\Ajuste::where('referencia_type', \App\Models\Confeccion::class)
->whereHas('referencia', function($q) use ($opId) {
$q->where('orden_produccion_id', $opId);
})->where('tipo', 'arreglo')->sum('cantidad');
$cobros = \App\Models\Cobro::where('referencia_type', \App\Models\Confeccion::class)
->whereHas('referencia', function($q) use ($opId) {
$q->where('orden_produccion_id', $opId);
})->sum('cantidad');
$cantidadConfecciones = $cantidadRecibida - $arreglos - $cobros;
$yaEnviado = \App\Models\Ojal::where('orden_produccion_id', $opId)->sum('cantidad_enviada');
$disponible = $cantidadConfecciones - $yaEnviado;
return "Disponible desde Confección: {$disponible}";
} else {
return "✓ Total distribuido correctamente";
$ojalesTerminados = \App\Models\Ojal::where('orden_produccion_id', $opId)
->where('terminada', true)->sum('cantidad_enviada');
$yaEnviado = \App\Models\Prensilla::where('orden_produccion_id', $opId)->sum('cantidad_enviada');
$disponible = $ojalesTerminados - $yaEnviado;
return "Disponible desde Ojales terminados: {$disponible}";
}
})
->live(),
}),
Checkbox::make('terminada')
->label('Marcar como terminada')
->helperText('Al marcar terminada, se enviará automáticamente a tintorería')
->live(),
->label('Marcar como terminado')
->helperText(fn ($get) => $get('tipo') === 'Prensilla'
? '✓ Al terminar Prensillas, las prendas quedarán disponibles para Tintorería'
: '✓ Al terminar Ojales, las prendas quedarán disponibles para Prensillas')
->reactive(),
]);
}
@@ -181,31 +221,73 @@ class OjalPresillaResource extends Resource
return $table
->columns([
Tables\Columns\TextColumn::make('tipo')
->label('Tipo')
->label('Proceso')
->searchable()
->sortable()
->badge()
->color('info'),
->color(fn ($state) => $state === 'Ojal' ? 'info' : 'warning'),
Tables\Columns\TextColumn::make('ordenProduccion.numero_orden')
->label('OP')
->searchable()
->sortable(),
Tables\Columns\TextColumn::make('ordenProduccion.prenda_modelo')
->label('Prenda')
->searchable()
->limit(30)
->toggleable(),
Tables\Columns\TextColumn::make('cantidad_enviada')
->label('Cantidad')
->alignCenter()
->sortable(),
Tables\Columns\TextColumn::make('proveedor.nombre')
->label('Proveedor')
->searchable()
->toggleable()
->default('Sin asignar'),
Tables\Columns\TextColumn::make('usuarioAsignado.name')
->label('Operario')
->searchable()
->toggleable()
->default('-'),
Tables\Columns\IconColumn::make('terminada')
->boolean()
->label('Terminada')
->label('Terminado')
->alignCenter()
->sortable(),
Tables\Columns\BadgeColumn::make('estado_flujo')
->label('Estado en Flujo')
->getStateUsing(function ($record) {
if ($record->tipo === 'Ojal') {
if ($record->terminada) {
// Verificar si ya se creó el proceso de prensillas
$prensillasCreadas = \App\Models\Prensilla::where('orden_produccion_id', $record->orden_produccion_id)
->where('cantidad_enviada', '>', 0)
->exists();
return $prensillasCreadas ? 'En Prensillas' : 'Listo para Prensillas';
}
return 'En Proceso';
} else {
// Es Prensilla
if ($record->terminada) {
return 'Listo para Tintorería';
}
return 'En Proceso';
}
})
->colors([
'success' => fn ($state) => in_array($state, ['Listo para Prensillas', 'Listo para Tintorería']),
'primary' => 'En Prensillas',
'warning' => 'En Proceso',
]),
Tables\Columns\TextColumn::make('fecha_envio')
->date()
->label('Fecha envío')
@@ -219,13 +301,26 @@ class OjalPresillaResource extends Resource
])
->filters([
SelectFilter::make('tipo')
->label('Tipo'),
->label('Tipo de Proceso')
->options([
'Ojal' => 'Ojal',
'Prensilla' => 'Prensilla',
]),
SelectFilter::make('terminada')
->label('Estado')
->options([
'0' => 'Pendiente',
'1' => 'Terminada',
])
'0' => 'En Proceso',
'1' => 'Terminado',
]),
SelectFilter::make('proveedor_id')
->label('Proveedor')
->relationship('proveedor', 'nombre'),
SelectFilter::make('usuario_asignado_id')
->label('Operario')
->relationship('usuarioAsignado', 'name'),
])
->actions([
Tables\Actions\EditAction::make(),
@@ -0,0 +1,86 @@
<?php
namespace App\Filament\Resources\OrdenProduccionResource\Pages;
use App\Filament\Resources\OrdenProduccionResource;
use App\Models\OrdenProduccion;
use Filament\Resources\Pages\Page;
use Filament\Infolists\Infolist;
use Filament\Infolists\Components\{Section, TextEntry, Grid};
class VerFlujoOjalesPresillas extends Page
{
protected static string $resource = OrdenProduccionResource::class;
protected static string $view = 'filament.resources.orden-produccion-resource.pages.ver-flujo-ojales-presillas';
protected static ?string $title = 'Flujo de Ojales y Prensillas';
public OrdenProduccion $record;
public function mount(int | string $record): void
{
$this->record = OrdenProduccion::findOrFail($record);
}
public function getFlujoData(): array
{
$op = $this->record;
// Datos de Confección
$cantidadRecibidaConfeccion = $op->confecciones()
->whereNotNull('fecha_recepcion')
->sum('cantidad_recibida');
// Datos de Ojales
$ojales = $op->ojales;
$ojalesData = $ojales->map(function ($ojal) {
return [
'cantidad' => $ojal->cantidad_enviada,
'proveedor' => $ojal->proveedor?->nombre ?? 'Sin proveedor',
'operario' => $ojal->usuarioAsignado?->name ?? '-',
'terminado' => $ojal->terminada,
'fecha' => $ojal->fecha_envio?->format('d/m/Y') ?? '-',
];
});
$ojalesTerminados = $ojales->where('terminada', true)->sum('cantidad_enviada');
$ojalesPendientes = $ojales->where('terminada', false)->sum('cantidad_enviada');
// Datos de Prensillas
$prensillas = $op->prensillas;
$prensillasData = $prensillas->map(function ($prensilla) {
return [
'cantidad' => $prensilla->cantidad_enviada,
'proveedor' => $prensilla->proveedor?->nombre ?? 'Sin proveedor',
'operario' => $prensilla->usuarioAsignado?->name ?? '-',
'terminado' => $prensilla->terminada,
'fecha' => $prensilla->fecha_envio?->format('d/m/Y') ?? '-',
];
});
$prensillasTerminadas = $prensillas->where('terminada', true)->sum('cantidad_enviada');
$prensillasPendientes = $prensillas->where('terminada', false)->sum('cantidad_enviada');
// Verificación de ambos procesos
$verificacion = $op->verificarProcesosCompletos();
return [
'confeccion_recibida' => $cantidadRecibidaConfeccion,
'ojales' => [
'total' => $ojales->sum('cantidad_enviada'),
'terminados' => $ojalesTerminados,
'pendientes' => $ojalesPendientes,
'registros' => $ojalesData,
],
'prensillas' => [
'total' => $prensillas->sum('cantidad_enviada'),
'terminadas' => $prensillasTerminadas,
'pendientes' => $prensillasPendientes,
'registros' => $prensillasData,
],
'verificacion' => $verificacion,
'disponible_tintoreria' => $op->getCantidadDisponibleParaTintoreria(),
];
}
}
+31 -30
View File
@@ -50,24 +50,20 @@ class TintoreriaResource extends Resource
Select::make('orden_produccion_id')
->label('Orden de Producción')
->options(fn () => \App\Models\OrdenProduccion::whereHas('ojales', function ($query) {
$query->where('terminada', true);
})->get()->mapWithKeys(function ($op) {
// Calcular cantidad disponible de ojales terminados
$cantidadOjalesTerminados = \App\Models\Ojal::where('orden_produccion_id', $op->id)
->where('terminada', true)
->sum('cantidad_enviada');
->options(fn () => \App\Models\OrdenProduccion::all()->mapWithKeys(function ($op) {
// Verificar que ambos procesos estén completos
$verificacion = $op->verificarProcesosCompletos();
// Calcular cantidad ya enviada a tintorería
$enviadasEnTint = \App\Models\Tintoreria::where('orden_produccion_id', $op->id)
->whereNull('fecha_recepcion')
->sum('cantidad_enviada');
if (!$verificacion['completo']) {
// Si no están completos, no mostrar o mostrar con advertencia
return [];
}
// Calcular disponible
$disponible = $cantidadOjalesTerminados - $enviadasEnTint;
// 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})"];
return [$op->id => "{$op->numero_orden} (Disponible: {$disponible}) [O:{$verificacion['ojales_terminados']} P:{$verificacion['prensillas_terminadas']}]"];
}
return [];
})->filter())
@@ -77,27 +73,25 @@ class TintoreriaResource extends Resource
->required()
->afterStateUpdated(function ($state, $set) {
if ($state) {
// Calcular cantidad disponible de ojales terminados
$cantidadOjalesTerminados = \App\Models\Ojal::where('orden_produccion_id', $state)
->where('terminada', true)
->sum('cantidad_enviada');
$enviadasEnTint = \App\Models\Tintoreria::where('orden_produccion_id', $state)
->whereNull('fecha_recepcion')
->sum('cantidad_enviada');
$pending = max(0, (int)$cantidadOjalesTerminados - (int)$enviadasEnTint);
$set('cantidad_enviada', $pending ?: 0);
$set('fecha_envio', now()->toDateString());
$orden = \App\Models\OrdenProduccion::find($state);
$set('prenda_nombre_info', $orden->prenda_modelo ?? 'Sin especificar');
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 ojales/prensillas terminados'),
->helperText('Solo se muestran órdenes con AMBOS procesos (ojales Y prensillas) terminados'),
TextInput::make('prenda_nombre_info')
->label('Prenda')
@@ -106,6 +100,13 @@ class TintoreriaResource extends Resource
->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(),
@@ -43,7 +43,7 @@ class PrendasPendientesWidget extends BaseWidget
->label('Pend. Confección')
->alignCenter()
->state(function (OrdenProduccion $record): int {
// Recibidas en confección - Enviadas a ojales
// Recibidas en confección - Enviadas a ojales y prensillas
$recibidas = $record->confecciones()
->whereNotNull('fecha_recepcion')
->sum('cantidad_recibida');
@@ -62,8 +62,9 @@ class PrendasPendientesWidget extends BaseWidget
$disponiblesConfeccion = $recibidas - $arreglos - $cobros;
$enviadasOjales = $record->ojales()->sum('cantidad_enviada');
$enviadasPrensillas = $record->prensillas()->sum('cantidad_enviada');
return max(0, $disponiblesConfeccion - $enviadasOjales);
return max(0, $disponiblesConfeccion - $enviadasOjales - $enviadasPrensillas);
})
->color(fn ($state) => $state > 0 ? 'warning' : 'gray')
->weight(fn ($state) => $state > 0 ? 'bold' : 'normal'),
@@ -71,17 +72,51 @@ class PrendasPendientesWidget extends BaseWidget
Tables\Columns\TextColumn::make('pendiente_ojales')
->label('Pend. Ojales')
->alignCenter()
->state(function (OrdenProduccion $record): int {
->state(function (OrdenProduccion $record): string {
// Enviadas a ojales - Terminadas en ojales
$enviadas = $record->ojales()->sum('cantidad_enviada');
$terminadas = $record->ojales()
->where('terminada', true)
->sum('cantidad_enviada');
$pendientes = max(0, $enviadas - $terminadas);
return max(0, $enviadas - $terminadas);
return "{$terminadas}/{$enviadas}";
})
->color(fn ($state) => $state > 0 ? 'warning' : 'gray')
->weight(fn ($state) => $state > 0 ? 'bold' : 'normal'),
->color(function (OrdenProduccion $record) {
$enviadas = $record->ojales()->sum('cantidad_enviada');
$terminadas = $record->ojales()->where('terminada', true)->sum('cantidad_enviada');
return $terminadas < $enviadas ? 'warning' : ($terminadas > 0 ? 'success' : 'gray');
})
->weight(function (OrdenProduccion $record) {
$enviadas = $record->ojales()->sum('cantidad_enviada');
$terminadas = $record->ojales()->where('terminada', true)->sum('cantidad_enviada');
return $terminadas < $enviadas ? 'bold' : 'normal';
})
->tooltip('Terminadas / Total Enviadas'),
Tables\Columns\TextColumn::make('pendiente_prensillas')
->label('Pend. Prensillas')
->alignCenter()
->state(function (OrdenProduccion $record): string {
$enviadas = $record->prensillas()->sum('cantidad_enviada');
$terminadas = $record->prensillas()
->where('terminada', true)
->sum('cantidad_enviada');
$pendientes = max(0, $enviadas - $terminadas);
return "{$terminadas}/{$enviadas}";
})
->color(function (OrdenProduccion $record) {
$enviadas = $record->prensillas()->sum('cantidad_enviada');
$terminadas = $record->prensillas()->where('terminada', true)->sum('cantidad_enviada');
return $terminadas < $enviadas ? 'warning' : ($terminadas > 0 ? 'success' : 'gray');
})
->weight(function (OrdenProduccion $record) {
$enviadas = $record->prensillas()->sum('cantidad_enviada');
$terminadas = $record->prensillas()->where('terminada', true)->sum('cantidad_enviada');
return $terminadas < $enviadas ? 'bold' : 'normal';
})
->tooltip('Terminadas / Total Enviadas'),
Tables\Columns\TextColumn::make('pendiente_tintoreria')
->label('Pend. Tintorería')
+6
View File
@@ -10,6 +10,7 @@ class Ojal extends Model
protected $fillable = [
'proveedor_id',
'usuario_asignado_id',
'orden_produccion_id',
'tipo',
'fecha_envio',
@@ -30,6 +31,11 @@ class Ojal extends Model
return $this->belongsTo(Proveedor::class);
}
public function usuarioAsignado()
{
return $this->belongsTo(\App\Models\User::class, 'usuario_asignado_id');
}
public function ordenProduccion()
{
return $this->belongsTo(OrdenProduccion::class);
+51
View File
@@ -83,7 +83,58 @@ class OrdenProduccion extends Model
return $this->belongsTo(\App\Models\Producto::class, 'producto_id');
}
/**
* Verifica si ambos procesos (ojales y prensillas) están completos para una cantidad específica
*
* @param int $cantidad Cantidad de prendas a verificar
* @return array ['completo' => bool, 'ojales_terminados' => int, 'prensillas_terminadas' => int, 'minimo' => int]
*/
public function verificarProcesosCom
pletos(int $cantidad = null): array
{
$cantidadVerificar = $cantidad ?? $this->cantidad_total;
// Cantidad de ojales terminados
$ojalesTerminados = $this->ojales()
->where('terminada', true)
->sum('cantidad_enviada');
// Cantidad de prensillas terminadas
$prensillasTerminadas = $this->prensillas()
->where('terminada', true)
->sum('cantidad_enviada');
// El mínimo entre ambos procesos
$minimoCompleto = min($ojalesTerminados, $prensillasTerminadas);
// Están completos si el mínimo cubre la cantidad requerida
$completo = $minimoCompleto >= $cantidadVerificar;
return [
'completo' => $completo,
'ojales_terminados' => $ojalesTerminados,
'prensillas_terminadas' => $prensillasTerminadas,
'minimo_completo' => $minimoCompleto,
'cantidad_requerida' => $cantidadVerificar,
'faltante_ojales' => max(0, $cantidadVerificar - $ojalesTerminados),
'faltante_prensillas' => max(0, $cantidadVerificar - $prensillasTerminadas),
];
}
/**
* Obtiene la cantidad disponible para enviar a tintorería
* (solo las prendas que tienen ambos procesos completos)
*/
public function getCantidadDisponibleParaTintoreria(): int
{
$verificacion = $this->verificarProcesosCompletos();
// Ya enviadas a tintorería
$enviadasTintoreria = $this->tintorerias()->sum('cantidad_enviada');
return max(0, $verificacion['minimo_completo'] - $enviadasTintoreria);
}
protected static function booted()
{
+6
View File
@@ -8,6 +8,7 @@ class Prensilla extends Model
{
protected $fillable = [
'proveedor_id',
'usuario_asignado_id',
'orden_produccion_id',
'tipo',
'fecha_envio',
@@ -28,6 +29,11 @@ class Prensilla extends Model
return $this->belongsTo(Proveedor::class);
}
public function usuarioAsignado()
{
return $this->belongsTo(\App\Models\User::class, 'usuario_asignado_id');
}
public function ordenProduccion()
{
return $this->belongsTo(OrdenProduccion::class);