This commit is contained in:
Lizandro Guarnizo
2026-01-18 11:21:19 -05:00
parent 18c43fe06f
commit 33688d297c
21 changed files with 958 additions and 14 deletions
@@ -16,7 +16,8 @@ use Filament\Forms\Components\{
TextInput,
Select,
DatePicker,
Section
Section,
View
};
use Filament\Tables\Columns\{
TextColumn,
@@ -64,6 +65,13 @@ class OrdenProduccionResource extends Resource
->required(),
])
->columns(2),
Section::make('Proceso')
->schema([
View::make('filament.orden_produccion.timeline')
->columnSpanFull(),
])
->columnSpanFull(),
]);
}
@@ -130,7 +138,7 @@ class OrdenProduccionResource extends Resource
public static function getRelations(): array
{
return [
//
RelationManagers\TrasladosRelationManager::class,
];
}
@@ -0,0 +1,71 @@
<?php
namespace App\Filament\Resources\OrdenProduccionResource\RelationManagers;
use App\Models\TrasladoPrenda;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables;
use Filament\Tables\Columns\TextColumn;
use Filament\Forms\Form;
use Filament\Forms;
class TrasladosRelationManager extends RelationManager
{
protected static string $relationship = 'traslados';
protected static ?string $recordTitleAttribute = 'id';
public function form(Form $form): Form
{
return $form->schema([
Forms\Components\TextInput::make('cantidad_enviada')->numeric(),
Forms\Components\TextInput::make('cantidad_recibida')->numeric(),
Forms\Components\TextInput::make('residual')->numeric()->disabled(),
Forms\Components\Select::make('origen')->options([
'confeccion' => 'Confección',
'tintoreria' => 'Tintorería',
'acabados' => 'Acabados',
'bodega' => 'Bodega',
]),
Forms\Components\Select::make('destino')->options([
'confeccion' => 'Confección',
'tintoreria' => 'Tintorería',
'acabados' => 'Acabados',
'bodega' => 'Bodega',
]),
Forms\Components\Textarea::make('notas')->columnSpanFull(),
]);
}
public function table(Tables\Table $table): Tables\Table
{
return $table
->columns([
TextColumn::make('id')->label('ID'),
TextColumn::make('origen'),
TextColumn::make('destino'),
TextColumn::make('cantidad_enviada'),
TextColumn::make('cantidad_recibida'),
TextColumn::make('prendas_defectuosas'),
TextColumn::make('reparaciones'),
TextColumn::make('saldos'),
TextColumn::make('residual'),
TextColumn::make('estado'),
TextColumn::make('fecha_envio')->dateTime(),
TextColumn::make('fecha_recepcion')->dateTime(),
])
->filters([
//
])
->headerActions([
Tables\Actions\CreateAction::make(),
])
->actions([
Tables\Actions\EditAction::make(),
Tables\Actions\DeleteAction::make(),
])
->bulkActions([
Tables\Actions\DeleteBulkAction::make(),
]);
}
}
@@ -0,0 +1,106 @@
<?php
namespace App\Filament\Resources;
use App\Filament\Resources\TrasladoPrendaResource\Pages;
use App\Filament\Resources\TrasladoPrendaResource\RelationManagers;
use App\Models\TrasladoPrenda;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\DateTimePicker;
use Filament\Tables\Columns\TextColumn;
class TrasladoPrendaResource extends Resource
{
protected static ?string $model = TrasladoPrenda::class;
protected static ?string $navigationGroup = 'Gestión';
protected static ?string $navigationIcon = 'heroicon-o-switch-horizontal';
public static function form(Form $form): Form
{
return $form
->schema([
Select::make('origen')->options([
'confeccion' => 'Confección',
'tintoreria' => 'Tintorería',
'acabados' => 'Acabados',
'bodega' => 'Bodega',
])->required(),
Select::make('destino')->options([
'confeccion' => 'Confección',
'tintoreria' => 'Tintorería',
'acabados' => 'Acabados',
'bodega' => 'Bodega',
])->required(),
TextInput::make('cantidad_enviada')->numeric()->required(),
TextInput::make('cantidad_recibida')->numeric()->nullable(),
TextInput::make('prendas_defectuosas')->numeric()->nullable(),
TextInput::make('reparaciones')->numeric()->nullable(),
TextInput::make('saldos')->numeric()->nullable(),
TextInput::make('residual')->numeric()->disabled()->dehydrated(false),
DateTimePicker::make('fecha_envio')->nullable(),
DateTimePicker::make('fecha_recepcion')->nullable(),
Select::make('estado')->options([
'pendiente' => 'Pendiente',
'realizado' => 'Realizado',
'recibido' => 'Recibido',
])->default('pendiente'),
Textarea::make('notas')->columnSpanFull(),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('id')->sortable(),
TextColumn::make('origen'),
TextColumn::make('destino'),
TextColumn::make('cantidad_enviada'),
TextColumn::make('cantidad_recibida'),
TextColumn::make('prendas_defectuosas'),
TextColumn::make('reparaciones'),
TextColumn::make('saldos'),
TextColumn::make('residual'),
TextColumn::make('estado'),
TextColumn::make('fecha_envio')->dateTime(),
TextColumn::make('fecha_recepcion')->dateTime(),
TextColumn::make('referencia_type')->label('Referencia'),
])
->filters([
//
])
->actions([
Tables\Actions\EditAction::make(),
Tables\Actions\DeleteAction::make(),
])
->bulkActions([
Tables\Actions\DeleteBulkAction::make(),
]);
}
public static function getRelations(): array
{
return [
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListTrasladoPrendas::route('/'),
'create' => Pages\CreateTrasladoPrenda::route('/create'),
'edit' => Pages\EditTrasladoPrenda::route('/{record}/edit'),
];
}
}
@@ -0,0 +1,11 @@
<?php
namespace App\Filament\Resources\TrasladoPrendaResource\Pages;
use App\Filament\Resources\TrasladoPrendaResource;
use Filament\Resources\Pages\CreateRecord;
class CreateTrasladoPrenda extends CreateRecord
{
protected static string $resource = TrasladoPrendaResource::class;
}
@@ -0,0 +1,11 @@
<?php
namespace App\Filament\Resources\TrasladoPrendaResource\Pages;
use App\Filament\Resources\TrasladoPrendaResource;
use Filament\Resources\Pages\EditRecord;
class EditTrasladoPrenda extends EditRecord
{
protected static string $resource = TrasladoPrendaResource::class;
}
@@ -0,0 +1,11 @@
<?php
namespace App\Filament\Resources\TrasladoPrendaResource\Pages;
use App\Filament\Resources\TrasladoPrendaResource;
use Filament\Resources\Pages\ListRecords;
class ListTrasladoPrendas extends ListRecords
{
protected static string $resource = TrasladoPrendaResource::class;
}
@@ -0,0 +1,31 @@
<?php
namespace App\Http\Controllers\Filament;
use App\Http\Controllers\Controller;
use App\Models\TrasladoPrenda;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
class TrasladoPrendaController extends Controller
{
use AuthorizesRequests;
public function markReceived(Request $request, TrasladoPrenda $traslado)
{
$data = $request->validate([
'cantidad_recibida' => ['nullable', 'integer', 'min:0'],
'prendas_defectuosas' => ['nullable', 'integer', 'min:0'],
'reparaciones' => ['nullable', 'integer', 'min:0'],
'saldos' => ['nullable', 'integer', 'min:0'],
]);
$userId = Auth::id();
$traslado->markAsReceived($data, $userId);
return redirect()->back()->with('success', 'Traslado marcado como recibido');
}
}
+27 -3
View File
@@ -50,11 +50,35 @@ class Confeccion extends Model
}
});
// Cuando se completa la confección → avanzar OP
static::updated(function ($confeccion) {
// Crear traslado cuando la confección tenga recepción (en creación y en actualización)
$createTrasladoFn = function ($confeccion) {
if ($confeccion->cantidad_recibida !== null) {
// Evitar duplicados
$exists = \App\Models\TrasladoPrenda::where('referencia_type', self::class)
->where('referencia_id', $confeccion->id)
->exists();
if (! $exists) {
$destino = 'tintoreria';
\App\Models\TrasladoPrenda::crearDesdeReferencia(
$confeccion,
'confeccion',
$destino
);
// Marcar el paso como terminado y avanzar la OP incluso si fue parcial
$confeccion->ordenProduccion?->avanzarEstado();
}
}
// Mantener el comportamiento anterior: si está completo, también avanzamos (por seguridad)
if ($confeccion->estado === 'completo') {
$confeccion->ordenProduccion?->avanzarEstado();
}
});
};
static::updated($createTrasladoFn);
static::saved($createTrasladoFn);
}
}
+24
View File
@@ -25,6 +25,11 @@ class OrdenProduccion extends Model
return $this->belongsTo(Tela::class);
}
public function traslados()
{
return $this->hasMany(\App\Models\TrasladoPrenda::class);
}
public function avanzarEstado(): void
{
$flujo = [
@@ -41,6 +46,25 @@ class OrdenProduccion extends Model
$this->update([
'estado' => $flujo[$actual + 1],
]);
// Si la orden llega a finalizada, crear entrada en inventario con lo que haya ingresado
if ($this->estado === 'finalizada') {
// Sumar traslados cuyo destino sea 'bodega'
$cantidad = \App\Models\TrasladoPrenda::where('orden_produccion_id', $this->id)
->where('destino', 'bodega')
->sum('cantidad_recibida');
// Crear inventario solo si hay traslados a bodega con cantidad
if ($cantidad > 0 && !\App\Models\InventarioPrenda::where('orden_produccion_id', $this->id)->exists()) {
\App\Models\InventarioPrenda::create([
'orden_produccion_id' => $this->id,
'cantidad_terminada' => $cantidad,
'cantidad_disponible' => $cantidad,
'fecha_ingreso' => now(),
'estado' => 'en_bodega',
]);
}
}
}
}
+25 -3
View File
@@ -32,8 +32,27 @@ class ProcesoAcabado extends Model
/* Lógica de negocio */
protected static function booted()
{
// Cuando se recibe el proceso
static::updated(function ($acabado) {
$createTrasladoFn = function ($acabado) {
if ($acabado->cantidad_recibida !== null) {
// Evitar duplicados
$exists = \App\Models\TrasladoPrenda::where('referencia_type', self::class)
->where('referencia_id', $acabado->id)
->exists();
if (! $exists) {
$destino = 'bodega';
\App\Models\TrasladoPrenda::crearDesdeReferencia(
$acabado,
'acabados',
$destino
);
// Avanzar la OP al cerrar este proceso
$acabado->ordenProduccion?->avanzarEstado();
}
}
if (
$acabado->fecha_recepcion &&
$acabado->cantidad_recibida !== null
@@ -49,6 +68,9 @@ class ProcesoAcabado extends Model
]);
}
}
});
};
static::updated($createTrasladoFn);
static::saved($createTrasladoFn);
}
}
+31 -3
View File
@@ -39,14 +39,42 @@ class Tintoreria extends Model
}
});
// Cuando vuelve de tintorería → avanzar OP
static::updated(function ($tintoreria) {
$createTrasladoFn = function ($tintoreria) {
if ($tintoreria->cantidad_recibida !== null) {
// Evitar duplicados
$exists = \App\Models\TrasladoPrenda::where('referencia_type', self::class)
->where('referencia_id', $tintoreria->id)
->exists();
if (! $exists) {
// Mapear "perdidas" a prendas_defectuosas en el traslado
$payload = [
'prendas_defectuosas' => $tintoreria->perdidas ?? 0,
];
$destino = 'acabados';
\App\Models\TrasladoPrenda::crearDesdeReferencia(
$tintoreria,
'tintoreria',
$destino,
$payload
);
// Avanzar la OP al cerrar el paso
$tintoreria->ordenProduccion?->avanzarEstado();
}
}
if (
$tintoreria->fecha_recepcion &&
$tintoreria->cantidad_recibida !== null
) {
$tintoreria->ordenProduccion?->avanzarEstado();
}
});
};
static::updated($createTrasladoFn);
static::saved($createTrasladoFn);
}
}
+141
View File
@@ -0,0 +1,141 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class TrasladoPrenda extends Model
{
protected $table = 'traslados_prenda';
protected $fillable = [
'orden_produccion_id',
'referencia_type',
'referencia_id',
'origen',
'destino',
'cantidad_enviada',
'cantidad_recibida',
'prendas_defectuosas',
'reparaciones',
'saldos',
'residual',
'fecha_envio',
'fecha_recepcion',
'estado',
'ingresado_por',
'notas',
];
protected $casts = [
'fecha_envio' => 'datetime',
'fecha_recepcion' => 'datetime',
];
public function ordenProduccion()
{
return $this->belongsTo(OrdenProduccion::class);
}
public function referencia()
{
return $this->morphTo();
}
public function ingresadoPor()
{
return $this->belongsTo(User::class, 'ingresado_por');
}
public static function crearDesdeReferencia(Model $referencia, string $origen, string $destino, array $payload = []) : self
{
$defaults = [
'orden_produccion_id' => $referencia->orden_produccion_id,
'referencia_type' => get_class($referencia),
'referencia_id' => $referencia->id,
'origen' => $origen,
'destino' => $destino,
'cantidad_enviada' => $payload['cantidad_enviada'] ?? ($referencia->cantidad_enviada ?? 0),
'cantidad_recibida' => $payload['cantidad_recibida'] ?? ($referencia->cantidad_recibida ?? null),
'prendas_defectuosas' => $payload['prendas_defectuosas'] ?? ($referencia->prendas_defectuosas ?? 0),
'reparaciones' => $payload['reparaciones'] ?? ($referencia->reparaciones ?? 0),
'saldos' => $payload['saldos'] ?? ($referencia->saldos ?? 0),
'fecha_envio' => $payload['fecha_envio'] ?? ($referencia->fecha_envio ?? null),
'fecha_recepcion' => $payload['fecha_recepcion'] ?? ($referencia->fecha_recepcion ?? null),
'estado' => 'recibido',
];
$cantidad_enviada = (int) $defaults['cantidad_enviada'];
$cantidad_recibida = (int) ($defaults['cantidad_recibida'] ?? 0);
$prendas_defectuosas = (int) ($defaults['prendas_defectuosas'] ?? 0);
$reparaciones = (int) ($defaults['reparaciones'] ?? 0);
$saldos = (int) ($defaults['saldos'] ?? 0);
$defaults['residual'] = $cantidad_enviada - ($cantidad_recibida + $prendas_defectuosas + $reparaciones + $saldos);
$traslado = self::create($defaults);
// Si el traslado es a bodega y la OP ya está finalizada, crear inventario (si no existe)
if ($traslado->destino === 'bodega') {
$traslado->maybeCreateInventory();
}
return $traslado;
}
public function markAsReceived(array $data = [], $userId = null)
{
// Actualizar campos si se envían
$this->cantidad_recibida = $data['cantidad_recibida'] ?? $this->cantidad_recibida;
$this->prendas_defectuosas = $data['prendas_defectuosas'] ?? $this->prendas_defectuosas ?? 0;
$this->reparaciones = $data['reparaciones'] ?? $this->reparaciones ?? 0;
$this->saldos = $data['saldos'] ?? $this->saldos ?? 0;
$this->fecha_recepcion = $data['fecha_recepcion'] ?? now();
$this->estado = 'recibido';
// Si cantidad_recibida no se proporcionó, inferirla
if ($this->cantidad_recibida === null) {
$this->cantidad_recibida = (int) $this->cantidad_enviada - ((int) $this->prendas_defectuosas + (int) $this->reparaciones + (int) $this->saldos);
if ($this->cantidad_recibida < 0) {
$this->cantidad_recibida = 0;
}
}
if ($userId) {
$this->ingresado_por = $userId;
}
$this->residual = (int) $this->cantidad_enviada - ((int) $this->cantidad_recibida + (int) $this->prendas_defectuosas + (int) $this->reparaciones + (int) $this->saldos);
$this->save();
// Si destino bodega, intentar crear inventario
if ($this->destino === 'bodega') {
$this->maybeCreateInventory();
}
return $this;
}
protected function maybeCreateInventory()
{
$op = $this->ordenProduccion;
if (! $op) return;
if ($op->estado === 'finalizada' && !\App\Models\InventarioPrenda::where('orden_produccion_id', $op->id)->exists()) {
$cantidad = self::where('orden_produccion_id', $op->id)
->where('destino', 'bodega')
->sum('cantidad_recibida');
if ($cantidad > 0) {
\App\Models\InventarioPrenda::create([
'orden_produccion_id' => $op->id,
'cantidad_terminada' => $cantidad,
'cantidad_disponible' => $cantidad,
'fecha_ingreso' => now(),
'estado' => 'en_bodega',
]);
}
}
}
}