Release inicial 1.0.0
This commit is contained in:
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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user