Feat: Agregada gestión de tablas ISR en settings
- Nueva tabla isr_tables y isr_brackets en BD - Controlador IsrController para CRUD de tablas ISR - Integración con pestaña ISR en settings - Soporte para importación via CSV - Captura manual de brackets
This commit is contained in:
228
app/Http/Controllers/IsrController.php
Normal file
228
app/Http/Controllers/IsrController.php
Normal file
@@ -0,0 +1,228 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\IsrBracket;
|
||||
use App\Models\IsrTable;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class IsrController extends Controller
|
||||
{
|
||||
/**
|
||||
* Lista todas las tablas ISR
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$isrTables = IsrTable::with('brackets')->orderBy('year', 'desc')->get();
|
||||
return view('settings.isr.index', compact('isrTables'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Crea nueva tabla por año
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'year' => 'required|integer|min:2000|max:2100|unique:isr_tables,year',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->route('settings.index', ['tab' => 'isr'])
|
||||
->withErrors($validator)
|
||||
->withInput();
|
||||
}
|
||||
|
||||
$isrTable = IsrTable::create([
|
||||
'year' => $request->input('year'),
|
||||
]);
|
||||
|
||||
return redirect()->route('settings.index', ['tab' => 'isr'])
|
||||
->with('success', 'Tabla ISR para ' . $isrTable->year . ' creada. Ahora agrega los brackets.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Formulario edición de brackets
|
||||
*/
|
||||
public function edit(IsrTable $isrTable)
|
||||
{
|
||||
$isrTable->load('brackets');
|
||||
return view('settings.isr.edit', compact('isrTable'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Guarda brackets manuales
|
||||
*/
|
||||
public function updateBrackets(Request $request, IsrTable $isrTable)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'brackets' => 'required|array|min:1',
|
||||
'brackets.*.lower_limit' => 'required|numeric|min:0',
|
||||
'brackets.*.upper_limit' => 'nullable|numeric|min:0',
|
||||
'brackets.*.fixed_fee' => 'required|numeric|min:0',
|
||||
'brackets.*.rate' => 'required|numeric|min:0|max:100',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->route('settings.isr.edit', $isrTable)
|
||||
->withErrors($validator)
|
||||
->withInput();
|
||||
}
|
||||
|
||||
// Eliminar brackets existentes
|
||||
$isrTable->brackets()->delete();
|
||||
|
||||
// Crear nuevos brackets
|
||||
$bracketsData = $request->input('brackets');
|
||||
$order = 0;
|
||||
|
||||
// Ordenar brackets por lower_limit antes de guardar
|
||||
usort($bracketsData, function ($a, $b) {
|
||||
return floatval($a['lower_limit']) - floatval($b['lower_limit']);
|
||||
});
|
||||
|
||||
foreach ($bracketsData as $bracketData) {
|
||||
$isrTable->brackets()->create([
|
||||
'lower_limit' => floatval($bracketData['lower_limit']),
|
||||
'upper_limit' => isset($bracketData['upper_limit']) && $bracketData['upper_limit'] !== ''
|
||||
? floatval($bracketData['upper_limit'])
|
||||
: null,
|
||||
'fixed_fee' => floatval($bracketData['fixed_fee']),
|
||||
'rate' => floatval($bracketData['rate']),
|
||||
'order' => $order++,
|
||||
]);
|
||||
}
|
||||
|
||||
return redirect()->route('settings.index', ['tab' => 'isr'])
|
||||
->with('success', 'Brackets actualizados correctamente.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Importa CSV
|
||||
*/
|
||||
public function uploadCsv(Request $request, IsrTable $isrTable)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'csv_file' => 'required|file|mimes:csv,txt',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->route('settings.isr.edit', $isrTable)
|
||||
->withErrors($validator)
|
||||
->withInput();
|
||||
}
|
||||
|
||||
$file = $request->file('csv_file');
|
||||
$content = file_get_contents($file->getRealPath());
|
||||
$lines = explode("\n", $content);
|
||||
|
||||
$brackets = [];
|
||||
$firstLine = true;
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
|
||||
// Ignorar primera línea (encabezados vacíos)
|
||||
if ($firstLine) {
|
||||
$firstLine = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (empty($line)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parsear línea CSV
|
||||
$values = str_getcsv($line);
|
||||
|
||||
if (count($values) < 4) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$lowerLimit = $this->parseCsvValue($values[0]);
|
||||
$upperLimit = $this->parseCsvValue($values[1]);
|
||||
$fixedFee = $this->parseCsvValue($values[2]);
|
||||
$rate = $this->parseCsvValue($values[3]);
|
||||
|
||||
// Ignorar si no hay lower_limit válido
|
||||
if ($lowerLimit === null || $lowerLimit < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Default null values to 0
|
||||
$fixedFee = $fixedFee ?? 0;
|
||||
$rate = $rate ?? 0;
|
||||
|
||||
$brackets[] = [
|
||||
'lower_limit' => $lowerLimit,
|
||||
'upper_limit' => $upperLimit,
|
||||
'fixed_fee' => $fixedFee,
|
||||
'rate' => $rate,
|
||||
];
|
||||
}
|
||||
|
||||
// Ordenar brackets por lower_limit
|
||||
usort($brackets, function ($a, $b) {
|
||||
return $a['lower_limit'] - $b['lower_limit'];
|
||||
});
|
||||
|
||||
if (empty($brackets)) {
|
||||
return redirect()->route('settings.index', ['tab' => 'isr'])
|
||||
->with('error', 'No se encontraron brackets válidos en el archivo CSV.');
|
||||
}
|
||||
|
||||
// Eliminar brackets existentes
|
||||
$isrTable->brackets()->delete();
|
||||
|
||||
// Crear nuevos brackets
|
||||
$order = 0;
|
||||
foreach ($brackets as $bracket) {
|
||||
$isrTable->brackets()->create([
|
||||
'lower_limit' => $bracket['lower_limit'],
|
||||
'upper_limit' => $bracket['upper_limit'],
|
||||
'fixed_fee' => $bracket['fixed_fee'],
|
||||
'rate' => $bracket['rate'],
|
||||
'order' => $order++,
|
||||
]);
|
||||
}
|
||||
|
||||
return redirect()->route('settings.index', ['tab' => 'isr'])
|
||||
->with('success', 'Brackets importados correctamente desde el CSV.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Elimina tabla ISR
|
||||
*/
|
||||
public function destroy(IsrTable $isrTable)
|
||||
{
|
||||
$year = $isrTable->year;
|
||||
$isrTable->delete();
|
||||
|
||||
return redirect()->route('settings.index', ['tab' => 'isr'])
|
||||
->with('success', 'Tabla ISR para ' . $year . ' eliminada.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Convierte valor CSV como "3,537.15" a 3537.15
|
||||
*/
|
||||
private function parseCsvValue(string $val): ?float
|
||||
{
|
||||
// Limpiar comillas y espacios
|
||||
$val = trim($val, '"\' ');
|
||||
|
||||
// Manejar "En adelante" como null
|
||||
if (in_array(strtolower($val), ['en adelante', 'en adelante ', 'null', ''])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Eliminar comas de miles
|
||||
$val = str_replace(',', '', $val);
|
||||
|
||||
// Convertir a número
|
||||
$num = floatval($val);
|
||||
|
||||
// Permitir 0 como valor válido, pero null para valores negativos o no numéricos
|
||||
return is_numeric($val) ? $num : null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user