Release inicial 1.0.0
This commit is contained in:
178
Services/CsvDatabaseService.php
Normal file
178
Services/CsvDatabaseService.php
Normal file
@ -0,0 +1,178 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\SatCatalogs\Services;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class CsvDatabaseService
|
||||
{
|
||||
/**
|
||||
* Carga datos desde CSV a una tabla de la base de datos.
|
||||
*/
|
||||
public static function importCsvToTable(string $csvFilePath, string $tableName, array $columns, bool $updateExisting = false)
|
||||
{
|
||||
if (!file_exists($csvFilePath)) {
|
||||
return ["error" => "Archivo CSV no encontrado: {$csvFilePath}"];
|
||||
}
|
||||
|
||||
$handle = fopen($csvFilePath, 'r');
|
||||
$batchSize = 5000;
|
||||
$data = [];
|
||||
$rowCount = 0;
|
||||
$insertedCount = 0;
|
||||
|
||||
while (($row = fgetcsv($handle, 1000, ";")) !== false) {
|
||||
$record = [];
|
||||
|
||||
foreach ($columns as $index => $column) {
|
||||
$value = $row[$index] ?? null;
|
||||
|
||||
// Aplicar CAST según el tipo de dato
|
||||
if ($value === '') {
|
||||
$record[$column] = null;
|
||||
|
||||
} elseif (self::isDateColumn($column)) {
|
||||
$record[$column] = self::formatDate($value);
|
||||
|
||||
} elseif (is_numeric($value)) {
|
||||
$record[$column] = strpos($value, '.') !== false ? (float) $value : (int) $value;
|
||||
|
||||
} else {
|
||||
$record[$column] = trim($value);
|
||||
}
|
||||
}
|
||||
|
||||
$data[] = $record;
|
||||
$rowCount++;
|
||||
|
||||
if (count($data) >= $batchSize) {
|
||||
self::insertOrUpdate($tableName, $data, $updateExisting);
|
||||
|
||||
$insertedCount += count($data);
|
||||
$data = [];
|
||||
}
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
|
||||
if (!empty($data)) {
|
||||
self::insertOrUpdate($tableName, $data, $updateExisting);
|
||||
|
||||
$insertedCount += count($data);
|
||||
}
|
||||
|
||||
return [
|
||||
"inserted" => $insertedCount,
|
||||
"total_rows" => $rowCount
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserta o actualiza datos en la tabla.
|
||||
*/
|
||||
private static function insertOrUpdate(string $tableName, array $data, bool $updateExisting)
|
||||
{
|
||||
if (empty($data)) {
|
||||
echo "⚠️ Datos vacíos para {$tableName}\n";
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($data as &$record) {
|
||||
foreach ($record as $key => $value) {
|
||||
if ($value === '?') { // Si hay un '?', lo convertimos en NULL
|
||||
$record[$key] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($record);
|
||||
|
||||
try {
|
||||
$primaryKeys = self::getPrimaryKeys($tableName);
|
||||
$updateFields = array_diff(array_keys($data[0]), $primaryKeys);
|
||||
|
||||
if ($updateExisting && !empty($updateFields)) {
|
||||
DB::table($tableName)->upsert($data, $primaryKeys, $updateFields);
|
||||
|
||||
echo "✅ Upsert ejecutado en {$tableName} (" . count($data) . " filas)\n";
|
||||
|
||||
} else {
|
||||
DB::table($tableName)->insert($data);
|
||||
|
||||
echo "✅ Insert ejecutado en {$tableName} (" . count($data) . " filas)\n";
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error("🚨 Error en {$tableName}: " . $e->getMessage());
|
||||
echo "❌ ERROR en {$tableName}: {$e->getMessage()}\n";
|
||||
|
||||
die();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* **Detecta si una columna es de tipo fecha.**
|
||||
*/
|
||||
private static function isDateColumn(string $columnName): bool
|
||||
{
|
||||
$dateFields = ['fecha_inicio_vigencia', 'fecha_fin_vigencia'];
|
||||
|
||||
return in_array($columnName, $dateFields);
|
||||
}
|
||||
|
||||
/**
|
||||
* **Convierte fechas de diferentes formatos a `YYYY-MM-DD`.**
|
||||
*/
|
||||
private static function formatDate($value)
|
||||
{
|
||||
if (!$value || strtolower($value) === 'null') {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
if (is_numeric($value) && $value > 10000) {
|
||||
return Carbon::instance(\PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($value))->format('Y-m-d');
|
||||
}
|
||||
|
||||
if (preg_match('/^\d{2}\/\d{2}\/\d{4}$/', $value)) {
|
||||
return Carbon::createFromFormat('d/m/Y', $value)->format('Y-m-d');
|
||||
}
|
||||
|
||||
return Carbon::parse($value)->format('Y-m-d');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* **Obtiene las claves primarias de la tabla.**
|
||||
*/
|
||||
private static function getPrimaryKeys(string $tableName): array
|
||||
{
|
||||
$primaryKeys = [
|
||||
'sat_forma_pago' => ['c_forma_pago'],
|
||||
'sat_moneda' => ['c_moneda'],
|
||||
'sat_codigo_postal' => ['c_codigo_postal'],
|
||||
'sat_regimen_fiscal' => ['c_regimen_fiscal'],
|
||||
'sat_pais' => ['c_pais'],
|
||||
'sat_uso_cfdi' => ['c_uso_cfdi'],
|
||||
'sat_clave_prod_serv' => ['c_clave_prod_serv'],
|
||||
'sat_clave_unidad' => ['c_clave_unidad'],
|
||||
'sat_aduana' => ['c_aduana'],
|
||||
'sat_colonia' => ['c_colonia', 'c_codigo_postal'],
|
||||
'sat_estado' => ['c_estado', 'c_pais'],
|
||||
'sat_localidad' => ['c_localidad', 'c_estado'],
|
||||
'sat_municipio' => ['c_municipio', 'c_estado'],
|
||||
'sat_banco' => ['c_banco'],
|
||||
'sat_deduccion' => ['c_deduccion'],
|
||||
'sat_percepcion' => ['c_percepcion'],
|
||||
'sat_regimen_contratacion' => ['c_regimen_contratacion'],
|
||||
];
|
||||
|
||||
return $primaryKeys[$tableName] ?? ['id'];
|
||||
}
|
||||
}
|
171
Services/CsvGeneratorService.php
Normal file
171
Services/CsvGeneratorService.php
Normal file
@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\SatCatalogs\Services;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Xls as XlsReader;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date as ExcelDate;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class CsvGeneratorService
|
||||
{
|
||||
private static array $dateColumnsMap = [
|
||||
'c_formapago' => [12, 13],
|
||||
'c_moneda' => [4, 5],
|
||||
'c_codigopostal' => [5, 6],
|
||||
'c_regimenfiscal' => [4, 5],
|
||||
'c_usocfdi' => [4, 5],
|
||||
'c_claveprodserv' => [5, 6],
|
||||
'c_claveunidad' => [4, 5],
|
||||
'c_aduana' => [2, 3],
|
||||
'c_estado' => [3],
|
||||
'c_localidad' => [3],
|
||||
'c_municipio' => [3],
|
||||
];
|
||||
|
||||
/**
|
||||
* Convierte hojas de un XLSX o XLS a un CSV optimizado.
|
||||
*/
|
||||
public static function convertToCsv(string $filePath, $sheetNames, string $csvName, int $skipRows = 7)
|
||||
{
|
||||
$extension = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
|
||||
$reader = ($extension === 'xls') ? new XlsReader() : new XlsxReader();
|
||||
|
||||
try {
|
||||
$reader->setReadDataOnly(true);
|
||||
$reader->setLoadSheetsOnly($sheetNames);
|
||||
$spreadsheet = $reader->load($filePath);
|
||||
} catch (\PhpOffice\PhpSpreadsheet\Reader\Exception $e) {
|
||||
die("❌ Error al cargar hojas: " . implode(", ", (array) $sheetNames) . "\n");
|
||||
}
|
||||
|
||||
$csvPath = database_path("seeders/sat_cache/{$csvName}.csv");
|
||||
$handle = fopen($csvPath, 'w');
|
||||
|
||||
if (!$handle) {
|
||||
return ["error" => "No se pudo crear el archivo CSV."];
|
||||
}
|
||||
|
||||
foreach ((array) $sheetNames as $sheetName) {
|
||||
if (!$spreadsheet->sheetNameExists($sheetName)) {
|
||||
echo "⚠️ Hoja no encontrada: {$sheetName}, saltando...\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
$sheet = $spreadsheet->getSheetByName($sheetName);
|
||||
if (!$sheet) continue;
|
||||
|
||||
$dateColumns = self::$dateColumnsMap[$csvName] ?? []; // Índices de columnas de fechas
|
||||
|
||||
foreach ($sheet->getRowIterator($skipRows + 1) as $row) {
|
||||
$cells = [];
|
||||
$colIndex = 0;
|
||||
|
||||
foreach ($row->getCellIterator() as $cell) {
|
||||
$value = trim((string) $cell->getValue());
|
||||
|
||||
// 🛑 Omitir filas con "Continúa en..."
|
||||
if (str_starts_with($value, 'Continúa en ')) {
|
||||
echo "⚠️ Fila omitida: {$value}\n";
|
||||
continue 2; // Salta toda la fila
|
||||
}
|
||||
|
||||
// 🔥 Convertir "?" y valores vacíos a NULL
|
||||
if ($value === '?' || $value === '') {
|
||||
$value = '';
|
||||
}
|
||||
|
||||
// 📅 Si es fecha de Excel, convertir a YYYY-MM-DD
|
||||
if (in_array($colIndex, $dateColumns) && self::isExcelDate($value)) {
|
||||
$value = self::convertExcelDate($value);
|
||||
}
|
||||
|
||||
// 🔹 Escapar valores que contienen punto y coma, saltos de línea o comillas
|
||||
$value = self::escapeCsvValue($value);
|
||||
|
||||
$cells[] = $value;
|
||||
$colIndex++;
|
||||
}
|
||||
|
||||
if (self::isEmptyRow($cells)) {
|
||||
echo "⚠️ Fila vacía detectada y omitida\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
fputcsv($handle, $cells, ';'); // 💡 Asegura que los valores se escapen correctamente
|
||||
}
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
$spreadsheet->disconnectWorksheets();
|
||||
unset($spreadsheet);
|
||||
|
||||
return $csvPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* **Detecta si el valor es un número de fecha de Excel**
|
||||
*/
|
||||
private static function isExcelDate($value): bool
|
||||
{
|
||||
return is_numeric($value) && $value > 10000;
|
||||
}
|
||||
|
||||
/**
|
||||
* **Convierte un número de fecha de Excel a formato YYYY-MM-DD**
|
||||
*/
|
||||
private static function convertExcelDate($value): string
|
||||
{
|
||||
try {
|
||||
return Carbon::instance(ExcelDate::excelToDateTimeObject($value))->format('Y-m-d');
|
||||
} catch (\Exception $e) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* **Escapa valores con punto y coma, comillas o saltos de línea en CSV**
|
||||
*/
|
||||
private static function escapeCsvValue($value): string
|
||||
{
|
||||
// Si el valor contiene punto y coma, salto de línea o comillas dobles, se escapa
|
||||
if (strpbrk($value, ";\"\n")) {
|
||||
$value = '"' . str_replace('"', '""', $value) . '"'; // Duplicar comillas internas
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* **Verifica si una fila está vacía o contiene valores inválidos.**
|
||||
*/
|
||||
private static function isEmptyRow(array $cells): bool
|
||||
{
|
||||
$empty = empty(array_filter($cells, fn($cell) => $cell !== ''));
|
||||
|
||||
if ($empty) {
|
||||
echo "⚠️ Fila detectada como vacía y omitida\n";
|
||||
}
|
||||
|
||||
return $empty;
|
||||
}
|
||||
|
||||
/**
|
||||
* **Cuenta las filas de un archivo CSV.**
|
||||
*/
|
||||
public static function countCsvRows(string $csvPath): int
|
||||
{
|
||||
if (!file_exists($csvPath)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$file = fopen($csvPath, 'r');
|
||||
$count = 0;
|
||||
|
||||
while (fgetcsv($file, 0, ';') !== false) {
|
||||
$count++;
|
||||
}
|
||||
|
||||
fclose($file);
|
||||
return $count;
|
||||
}
|
||||
}
|
416
Services/SatCatalogsService.php
Normal file
416
Services/SatCatalogsService.php
Normal file
@ -0,0 +1,416 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\SatCatalogs\Services;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class SatCatalogService
|
||||
{
|
||||
protected $catalogs = [
|
||||
'banco' => [
|
||||
'table' => 'sat_banco',
|
||||
'key' => 'c_banco',
|
||||
'value' => 'descripcion AS item',
|
||||
'order_by' => 'descripcion',
|
||||
'search_columns' => ['descripcion'],
|
||||
'use_status' => true,
|
||||
'limit' => 10
|
||||
],
|
||||
'clave_prod_serv' => [
|
||||
'table' => 'sat_clave_prod_serv',
|
||||
'key' => 'c_clave_prod_serv',
|
||||
'value' => "CONCAT_WS(' - ', c_clave_prod_serv , descripcion) as item",
|
||||
'search_type' => 'MATCH',
|
||||
'search_columns' => ['c_clave_prod_serv_text', 'descripcion'],
|
||||
],
|
||||
'clave_unidad' => [
|
||||
'table' => 'sat_clave_unidad',
|
||||
'key' => 'c_clave_unidad',
|
||||
'value' => "CONCAT_WS(' - ', c_clave_unidad , nombre) as item",
|
||||
'search_columns' => ['c_clave_unidad', 'nombre'],
|
||||
'limit' => 20
|
||||
],
|
||||
'deduccion' => [
|
||||
'table' => 'sat_deduccion',
|
||||
'key' => 'c_deduccion',
|
||||
'value' => "CONCAT_WS(' - ', c_deduccion , descripcion) as item",
|
||||
'search_columns' => ['c_deduccion', 'descripcion'],
|
||||
'limit' => 20
|
||||
],
|
||||
'forma_pago' => [
|
||||
'table' => 'sat_forma_pago',
|
||||
'key' => 'c_forma_pago',
|
||||
'value' => "CONCAT_WS(' - ', c_forma_pago , descripcion) as item",
|
||||
'search_columns' => ['c_forma_pago', 'descripcion'],
|
||||
'limit' => 10
|
||||
],
|
||||
'moneda' => [
|
||||
'table' => 'sat_moneda',
|
||||
'key' => 'c_moneda',
|
||||
'value' => "CONCAT_WS(' - ', c_moneda , descripcion) as item",
|
||||
'order_by' => 'descripcion',
|
||||
'search_columns' => ['c_moneda', 'descripcion'],
|
||||
'limit' => 10
|
||||
],
|
||||
'pais' => [
|
||||
'table' => 'sat_pais',
|
||||
'key' => 'c_pais',
|
||||
'value' => 'descripcion AS item',
|
||||
'order_by' => 'descripcion',
|
||||
'search_columns' => ['c_pais', 'descripcion'],
|
||||
'limit' => 10
|
||||
],
|
||||
|
||||
'estado' => [
|
||||
'table' => 'sat_estado',
|
||||
'key' => 'c_estado',
|
||||
'value' => 'nombre_del_estado AS item',
|
||||
'order_by' => 'nombre_del_estado',
|
||||
'extra_conditions' => ['c_pais'],
|
||||
'search_columns' => ['nombre_del_estado'],
|
||||
],
|
||||
'municipio' => [
|
||||
'table' => 'sat_municipio',
|
||||
'key' => 'c_municipio',
|
||||
'value' => 'descripcion AS item',
|
||||
'order_by' => 'descripcion',
|
||||
'extra_conditions' => ['c_estado'],
|
||||
'search_columns' => ['descripcion'],
|
||||
],
|
||||
'localidad' => [
|
||||
'table' => 'sat_localidad',
|
||||
'key' => 'c_localidad',
|
||||
'value' => 'descripcion AS item',
|
||||
'order_by' => 'descripcion',
|
||||
'extra_conditions' => ['c_estado'],
|
||||
'search_columns' => ['descripcion'],
|
||||
],
|
||||
'colonia' => [
|
||||
'table' => 'sat_colonia',
|
||||
'joins' => [
|
||||
[
|
||||
'table' => 'sat_codigo_postal',
|
||||
'first' => 'sat_colonia.c_codigo_postal',
|
||||
'second' => 'sat_codigo_postal.c_codigo_postal',
|
||||
],
|
||||
],
|
||||
'key' => 'sat_colonia.c_colonia',
|
||||
'value' => 'sat_colonia.nombre_del_asentamiento AS item',
|
||||
'columns' => [
|
||||
'sat_colonia.c_codigo_postal',
|
||||
],
|
||||
'order_by' => 'sat_colonia.nombre_del_asentamiento',
|
||||
'extra_conditions' => ['sat_codigo_postal.c_codigo_postal', 'sat_codigo_postal.c_estado', 'sat_codigo_postal.c_municipio', 'sat_colonia.c_colonia'],
|
||||
'search_columns' => ['sat_colonia.nombre_del_asentamiento'],
|
||||
],
|
||||
'codigo_postal' => [
|
||||
'table' => 'sat_codigo_postal',
|
||||
'joins' => [
|
||||
[
|
||||
'table' => 'sat_localidad',
|
||||
'first' => 'sat_codigo_postal.c_localidad',
|
||||
'second' => 'sat_localidad.c_localidad',
|
||||
'and' => ['sat_codigo_postal.c_estado = sat_localidad.c_estado'],
|
||||
'type' => 'leftJoin',
|
||||
],
|
||||
[
|
||||
'table' => 'sat_municipio',
|
||||
'first' => 'sat_codigo_postal.c_municipio',
|
||||
'second' => 'sat_municipio.c_municipio',
|
||||
'and' => ['sat_codigo_postal.c_estado = sat_municipio.c_estado'],
|
||||
'type' => 'leftJoin',
|
||||
],
|
||||
[
|
||||
'table' => 'sat_estado',
|
||||
'first' => 'sat_codigo_postal.c_estado',
|
||||
'second' => 'sat_estado.c_estado',
|
||||
'type' => 'leftJoin',
|
||||
],
|
||||
],
|
||||
'key' => 'sat_codigo_postal.c_codigo_postal',
|
||||
'columns' => [
|
||||
'sat_codigo_postal.c_estado',
|
||||
'sat_estado.nombre_del_estado as estado',
|
||||
'sat_codigo_postal.c_localidad',
|
||||
'sat_localidad.descripcion as localidad',
|
||||
'sat_codigo_postal.c_municipio',
|
||||
'sat_municipio.descripcion as municipio',
|
||||
],
|
||||
'search_columns' => ['sat_codigo_postal.c_codigo_postal'],
|
||||
],
|
||||
];
|
||||
|
||||
protected $fixedCatalogs = [
|
||||
'exportacion' => [
|
||||
1 => 'No aplica',
|
||||
2 => 'Definitiva con clave A1',
|
||||
3 => 'Temporal',
|
||||
4 => 'Definitiva con clave distinta a A1 o cuando no existe enajenación en términos del CFF'
|
||||
],
|
||||
'horas_extra' => [
|
||||
'01' => 'Dobles',
|
||||
'02' => 'Triples',
|
||||
'03' => 'Simples'
|
||||
],
|
||||
'impuestos' => [
|
||||
1 => 'ISR',
|
||||
2 => 'IVA',
|
||||
3 => 'IEPS'
|
||||
],
|
||||
'incapacidad' => [
|
||||
1 => 'Riesgo de trabajo',
|
||||
2 => 'Enfermedad en general',
|
||||
3 => 'Maternidad',
|
||||
4 => 'Licencia por cuidados médicos de hijos diagnosticados con cáncer'
|
||||
],
|
||||
'jornada' => [
|
||||
'01' => 'Diurna',
|
||||
'02' => 'Nocturna',
|
||||
'03' => 'Mixta',
|
||||
'04' => 'Por hora',
|
||||
'05' => 'Reducida',
|
||||
'06' => 'Continuada',
|
||||
'07' => 'Partida',
|
||||
'08' => 'Por turnos',
|
||||
'99' => 'Otra Jornada'
|
||||
],
|
||||
'metodo_pago' => [
|
||||
'PUE' => 'Pago en una sola exhibición',
|
||||
'PPD' => 'Pago en parcialidades o diferido'
|
||||
],
|
||||
'nomina' => [
|
||||
'O' => 'Nómina ordinaria',
|
||||
'E' => 'Nómina extraordinaria'
|
||||
],
|
||||
'objeto_imp' => [
|
||||
1 => 'No objeto de impuesto.',
|
||||
2 => 'Sí objeto de impuesto.',
|
||||
3 => 'Sí objeto del impuesto y no obligado al desglose.',
|
||||
4 => 'Sí objeto del impuesto y no causa impuesto.',
|
||||
5 => 'Sí objeto del impuesto, IVA crédito PODEBI.'
|
||||
],
|
||||
'origen_recurso' => [
|
||||
'IP' => 'Ingresos propios',
|
||||
'IF' => 'Ingresos federales',
|
||||
'IM' => 'Ingresos mixtos'
|
||||
],
|
||||
'otro_pago' => [
|
||||
1 => 'Reintegro de ISR pagado en exceso',
|
||||
2 => 'Subsidio para el empleo',
|
||||
3 => 'Viáticos',
|
||||
4 => 'Aplicación de saldo a favor por compensación anual',
|
||||
5 => 'Reintegro de ISR retenido en exceso de ejercicio anterior',
|
||||
6 => 'Alimentos en bienes',
|
||||
7 => 'ISR ajustado por subsidio',
|
||||
8 => 'Subsidio efectivamente entregado que no correspondía',
|
||||
999 => 'Pagos distintos a los listados'
|
||||
],
|
||||
'periodicidad' => [
|
||||
1 => 'Diario',
|
||||
2 => 'Semanal',
|
||||
3 => 'Quincenal',
|
||||
4 => 'Mensual',
|
||||
5 => 'Bimestral'
|
||||
],
|
||||
'periodicidad_pago' => [
|
||||
1 => 'Diario',
|
||||
2 => 'Semanal',
|
||||
3 => 'Catorcenal',
|
||||
4 => 'Quincenal',
|
||||
5 => 'Mensual',
|
||||
6 => 'Bimestral',
|
||||
7 => 'Unidad obra',
|
||||
8 => 'Comisión',
|
||||
9 => 'Precio alzado',
|
||||
10 => 'Decenal',
|
||||
99 => 'Otra Periodicidad'
|
||||
],
|
||||
'riesgo_puesto' => [
|
||||
'1' => 'Clase I',
|
||||
'2' => 'Clase II',
|
||||
'3' => 'Clase III',
|
||||
'4' => 'Clase IV',
|
||||
'5' => 'Clase V'
|
||||
],
|
||||
'tipo_comprobante' => [
|
||||
'I' => 'Ingreso',
|
||||
'E' => 'Egreso',
|
||||
'T' => 'Traslado',
|
||||
'N' => 'Nómina',
|
||||
'P' => 'Pago'
|
||||
],
|
||||
'tipo_factor' => [
|
||||
1 => 'Tasa',
|
||||
2 => 'Cuota',
|
||||
3 => 'Exento'
|
||||
],
|
||||
'tipo_relacion' => [
|
||||
1 => 'Nota de crédito de los documentos relacionados',
|
||||
2 => 'Nota de débito de los documentos relacionados',
|
||||
3 => 'Devolución de mercancía sobre facturas o traslados previos',
|
||||
4 => 'Sustitución de los CFDI previos',
|
||||
5 => 'Traslados de mercancías facturados previamente',
|
||||
6 => 'Factura generada por los traslados previos',
|
||||
7 => 'CFDI por aplicación de anticipo'
|
||||
]
|
||||
];
|
||||
|
||||
|
||||
public function searchCatalog(string $catalog, string $searchTerm = '', array $options = []): array
|
||||
{
|
||||
// 1. Validar si es un catálogo fijo o uno definido en $this->catalogs
|
||||
if (isset($this->fixedCatalogs[$catalog])) {
|
||||
return $this->fixedCatalogs[$catalog];
|
||||
}
|
||||
|
||||
if (!isset($this->catalogs[$catalog])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$config = $this->catalogs[$catalog];
|
||||
|
||||
// 2. Construye Query Builder base
|
||||
$query = DB::table($config['table']);
|
||||
|
||||
// 3. Aplica joins
|
||||
if (!empty($config['joins'])) {
|
||||
foreach ($config['joins'] as $join) {
|
||||
$type = $join['type'] ?? 'join';
|
||||
$query->{$type}($join['table'], function($joinObj) use ($join) {
|
||||
$joinObj->on($join['first'], '=', $join['second']);
|
||||
// Soporte para AND en ON, si está definidio
|
||||
if (!empty($join['and'])) {
|
||||
foreach ((array) $join['and'] as $andCondition) {
|
||||
// 'sat_codigo_postal.c_estado = sat_localidad.c_estado'
|
||||
$parts = explode('=', $andCondition);
|
||||
if (count($parts) === 2) {
|
||||
$left = trim($parts[0]);
|
||||
$right = trim($parts[1]);
|
||||
$joinObj->whereRaw("$left = $right");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Construir la lista de columnas a seleccionar
|
||||
$selectFields = [];
|
||||
|
||||
// - Si hay "columns", añádelas
|
||||
if (!empty($config['columns'])) {
|
||||
foreach ($config['columns'] as $col) {
|
||||
$selectFields[] = DB::raw($col);
|
||||
}
|
||||
}
|
||||
|
||||
// - Si también tienes "key" y "value" (por ejemplo para select2),
|
||||
// añádelos (si no están ya en columns).
|
||||
if (!empty($config['key'])) {
|
||||
$selectFields[] = DB::raw($config['key']);
|
||||
}
|
||||
|
||||
if (!empty($config['value'])) {
|
||||
$selectFields[] = DB::raw($config['value']);
|
||||
}
|
||||
|
||||
// - Si al final no hay nada, por fallback selecciona todo (o lanza un error)
|
||||
if (empty($selectFields)) {
|
||||
$query->select('*');
|
||||
|
||||
} else {
|
||||
$query->select($selectFields);
|
||||
}
|
||||
|
||||
// 5. Filtrado por status si aplica
|
||||
if (($config['use_status'] ?? false) === true) {
|
||||
$status = isset($options['status'])
|
||||
? $options['status']
|
||||
: (isset($config['status']) ? $config['status']: null);
|
||||
|
||||
if ($status !== null) {
|
||||
$query->where('status', $status);
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Filtrar según extra_conditions (ahora puede incluir columnas de la tabla unida)
|
||||
if (isset($config['extra_conditions'])) {
|
||||
foreach ($config['extra_conditions'] as $field) {
|
||||
if (array_key_exists($field, $options)) {
|
||||
$query->where($field, $options[$field]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 7. Búsqueda
|
||||
if (!empty($searchTerm) && !empty($config['search_columns'])) {
|
||||
if (($config['search_type'] ?? 'LIKE') === 'MATCH') {
|
||||
// Ejemplo: MATCH..AGAINST
|
||||
$cols = implode(',', $config['search_columns']);
|
||||
|
||||
$query->whereRaw("MATCH ($cols) AGAINST (? IN BOOLEAN MODE)", [$searchTerm]);
|
||||
|
||||
} else {
|
||||
// Búsqueda por LIKE
|
||||
$query->where(function ($subQ) use ($config, $searchTerm) {
|
||||
foreach ($config['search_columns'] as $col) {
|
||||
$subQ->orWhere($col, 'LIKE', "%{$searchTerm}%");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 8. Ordenar resultados
|
||||
$orderBy = $options['order_by'] ?? $config['order_by'] ?? $config['key'];
|
||||
$orderDir = $options['order_dir'] ?? $config['order_dir'] ?? 'asc';
|
||||
$query->orderBy($orderBy, $orderDir);
|
||||
|
||||
// 9. Limitar
|
||||
$limit = array_key_exists('limit', $options) ? $options['limit'] : ($config['limit'] ?? 50);
|
||||
|
||||
if ($limit !== null) {
|
||||
$query->limit($limit);
|
||||
}
|
||||
|
||||
|
||||
// Para ver la sentencia SQL (con placeholders ?)
|
||||
// dump($query->toSql()); dd($query->getBindings());
|
||||
|
||||
|
||||
// 10. Revisar modo de respuesta
|
||||
$rawMode = ($config['rawMode'] ?? false) || ($options['rawMode'] ?? false);
|
||||
$firstRow = ($config['firstRow'] ?? false) || ($options['firstRow'] ?? false);
|
||||
$select2Mode = $options['select2Mode'] ?? false;
|
||||
|
||||
// (a) Modo raw -> devolvemos tal cual
|
||||
if ($rawMode) {
|
||||
if($firstRow){
|
||||
return (array) $query->first();
|
||||
}
|
||||
|
||||
return $query->get()->toArray();
|
||||
}
|
||||
|
||||
$shortKey = Str::afterLast($config['key'], '.');
|
||||
|
||||
// (b) Devuelve en formato "select2" o en un array
|
||||
if ($select2Mode) {
|
||||
$response = [];
|
||||
|
||||
foreach ($query->get() as $row) {
|
||||
$response[] = [
|
||||
'id' => $row->{$shortKey},
|
||||
'text' => $row->item,
|
||||
];
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
||||
// (c) Por defecto, regresa "pluck" id => texto
|
||||
return $query->pluck('item', $shortKey)->toArray();
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user