Files
pos_heidiver/app/Filament/Resources/InformeResource.php
T
2026-01-06 15:35:59 -05:00

243 lines
10 KiB
PHP

<?php
namespace App\Filament\Resources;
use App\Filament\Resources\InformeResource\Pages;
use App\Filament\Resources\InformeResource\Widgets;
use App\Models\Venta;
use App\Models\DetalleVenta;
use App\Models\Producto;
use App\Models\Categoria;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
class InformeResource extends Resource
{
protected static ?string $model = Venta::class;
protected static ?string $navigationIcon = 'heroicon-o-chart-bar';
protected static ?string $navigationGroup = 'Reportes';
protected static ?string $modelLabel = 'Informe';
protected static ?string $pluralModelLabel = 'Informes';
protected static ?string $navigationLabel = 'Promedio Ponderado';
public static function canViewAny(): bool
{
return true; // Permitir acceso básico, se puede personalizar según necesidades
}
public static function canCreate(): bool
{
return false; // No se pueden crear informes
}
public static function canEdit($record): bool
{
return false; // No se pueden editar informes
}
public static function canDelete($record): bool
{
return false; // No se pueden eliminar informes
}
public static function table(Table $table): Table
{
return $table
->query(self::getInformeQuery())
->columns([
Tables\Columns\TextColumn::make('producto_nombre')
->label('Producto')
->searchable()
->sortable(),
Tables\Columns\TextColumn::make('categoria_nombre')
->label('Categoría')
->searchable()
->sortable(),
Tables\Columns\TextColumn::make('total_vendido')
->label('Cantidad Vendida')
->alignCenter()
->sortable()
->formatStateUsing(function ($state, $record) {
$unidadMedida = $record->unidad_medida ?? 'unidad';
$cantidadEnUnidad = Producto::convertirDesdeUnidades($state, $unidadMedida);
$nombreUnidad = explode(' ', Producto::getUnidadesMedida()[$unidadMedida])[0];
return "{$cantidadEnUnidad} {$nombreUnidad}";
})
->tooltip(function ($state, $record) {
$unidadMedida = $record->unidad_medida ?? 'unidad';
$cantidadEnUnidad = Producto::convertirDesdeUnidades($state, $unidadMedida);
return "Cantidad en unidades: {$state}\nCantidad en {$unidadMedida}: {$cantidadEnUnidad}";
}),
Tables\Columns\TextColumn::make('precio_compra_unitario')
->label('Precio Compra Actual')
->money('COP')
->alignEnd()
->sortable(),
Tables\Columns\TextColumn::make('promedio_ponderado_compra')
->label('Promedio Ponderado Compra')
->money('COP')
->alignEnd()
->sortable()
->tooltip('Precio promedio de compra ponderado por cantidades compradas'),
Tables\Columns\TextColumn::make('valor_total_compra')
->label('Valor Total Compra')
->money('COP')
->alignEnd()
->sortable(),
Tables\Columns\TextColumn::make('precio_venta_promedio')
->label('Precio Venta Promedio')
->money('COP')
->alignEnd()
->sortable(),
Tables\Columns\TextColumn::make('promedio_ponderado')
->label('Promedio Ponderado')
->money('COP')
->alignEnd()
->sortable()
->tooltip('Precio promedio ponderado por cantidad vendida'),
Tables\Columns\TextColumn::make('margen_beneficio')
->label('Margen %')
->alignCenter()
->sortable()
->getStateUsing(function ($record) {
$precioCompra = $record->precio_compra_unitario;
$precioVenta = $record->precio_venta_promedio;
if ($precioCompra > 0) {
$margen = (($precioVenta - $precioCompra) / $precioCompra) * 100;
return round($margen, 2);
}
return 0;
})
->formatStateUsing(function ($state) {
$color = $state >= 0 ? 'success' : 'danger';
$icon = $state >= 0 ? '📈' : '📉';
return "{$icon} {$state}%";
}),
])
->defaultSort('valor_total_compra', 'desc')
->filters([
Tables\Filters\Filter::make('fecha_rango')
->form([
Forms\Components\DatePicker::make('fecha_desde')
->label('Fecha Desde')
->default(Carbon::now()->startOfMonth()),
Forms\Components\DatePicker::make('fecha_hasta')
->label('Fecha Hasta')
->default(Carbon::now()->endOfMonth()),
])
->query(function (Builder $query, array $data): Builder {
return $query
->when(
$data['fecha_desde'],
fn (Builder $query, $date): Builder => $query->whereDate('ventas.created_at', '>=', $date),
)
->when(
$data['fecha_hasta'],
fn (Builder $query, $date): Builder => $query->whereDate('ventas.created_at', '<=', $date),
);
})
->indicateUsing(function (array $data): array {
$indicators = [];
if ($data['fecha_desde']) {
$indicators['fecha_desde'] = 'Desde: ' . Carbon::parse($data['fecha_desde'])->format('d/m/Y');
}
if ($data['fecha_hasta']) {
$indicators['fecha_hasta'] = 'Hasta: ' . Carbon::parse($data['fecha_hasta'])->format('d/m/Y');
}
return $indicators;
}),
Tables\Filters\SelectFilter::make('categoria')
->options(\App\Models\Categoria::pluck('nombre', 'id'))
->query(function (Builder $query, array $data): Builder {
if (!empty($data['value'])) {
return $query->where('categorias.id', $data['value']);
}
return $query;
})
->searchable()
->preload(),
])
->actions([
// No actions needed for reports
])
->bulkActions([
// No bulk actions for reports
])
->emptyStateHeading('No hay ventas en el período seleccionado')
->emptyStateDescription('Ajuste los filtros de fecha para ver resultados')
->poll('60s'); // Actualizar cada minuto
}
/**
* Query personalizado para el informe de promedio ponderado
*/
protected static function getInformeQuery(): Builder
{
return DetalleVenta::query()
->select([
'productos.id as id', // Filament necesita 'id' como clave del registro
'productos.id as producto_id', // Mantener para compatibilidad
'productos.nombre as producto_nombre',
'productos.precio_compra as precio_compra_unitario',
'productos.unidad_medida',
'categorias.nombre as categoria_nombre',
DB::raw('SUM(detalle_ventas.cantidad) as total_vendido'),
DB::raw('ROUND(AVG(detalle_ventas.precio_unitario), 2) as precio_venta_promedio'),
// Promedio ponderado real: suma de (cantidad * precio) / suma de cantidades
DB::raw('ROUND(SUM(detalle_ventas.cantidad * detalle_ventas.precio_unitario) / SUM(detalle_ventas.cantidad), 2) as promedio_ponderado'),
// Promedio ponderado de compra basado en compras reales
DB::raw('COALESCE(
(SELECT ROUND(SUM(dc.cantidad * dc.precio_unitario) / SUM(dc.cantidad), 2)
FROM detalle_compras dc
INNER JOIN compras c ON dc.compra_id = c.id
WHERE dc.producto_id = productos.id AND c.estado = \'Recibida\'),
CAST(productos.precio_compra AS DECIMAL(10,2))
) as promedio_ponderado_compra'),
DB::raw('COUNT(DISTINCT ventas.id) as numero_ventas'),
DB::raw('MIN(ventas.created_at) as primera_venta'),
DB::raw('MAX(ventas.created_at) as ultima_venta'),
// Agregar el campo calculado para poder ordenar por él con cast explícito
DB::raw('SUM(detalle_ventas.cantidad) * CAST(productos.precio_compra AS DECIMAL(10,2)) as valor_total_compra'),
])
->join('productos', 'detalle_ventas.producto_id', '=', 'productos.id')
->join('categorias', 'productos.categoria_id', '=', 'categorias.id')
->join('ventas', 'detalle_ventas.venta_id', '=', 'ventas.id')
->groupBy([
'productos.id',
'productos.nombre',
'productos.precio_compra',
'productos.unidad_medida',
'categorias.nombre'
])
->havingRaw('SUM(detalle_ventas.cantidad) > 0');
}
public static function getPages(): array
{
return [
'index' => Pages\ListInformes::route('/'),
];
}
}