Skip to content

Commit

Permalink
Merge pull request #7 from eclipxe13/catalogs-cfdi_40
Browse files Browse the repository at this point in the history
Catalogs CFDI 4.0
  • Loading branch information
eclipxe13 authored Mar 5, 2022
2 parents c090bb4 + 5bce27f commit e5cdcfa
Show file tree
Hide file tree
Showing 86 changed files with 7,327 additions and 73 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ php bin/sat-catalogos-update update-origins catalogs/
php bin/sat-catalogos-update update-database catalogs/ catalogs/catalogos.sqlite3
```

## About the catalogs included

Read the file [*Catálogos SAT*](docs/Catalogos.md) for more information about the catalogs included (in spanish).

## PHP Support

This tool is compatible with last [PHP supported version](https://www.php.net/supported-versions.php).
Expand Down
10 changes: 10 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# phpcfdi/sat-catalogos-populate Changelog

## Version 2.3.0 2022-03-05

Add CFDI 4.0 catalogs.

- Add *Origin* as a scrap from SAT website, saving the file as `cfdi_40.xsl`.
- Add *Source importer* from `cfdi_40.xsl`. It includes 25 catalogs.
- Importers create tables with prefix `cfdi_40_*`.
- Update `docs/Catalogos.md` with CFDI 4.0 catalogs.
- Refactor how to know what lines to skip when join two CVS files.

## Version 2.2.1 2022-03-04

Include `Nóminas - Estados` origin to `dump-origins` command.
Expand Down
135 changes: 84 additions & 51 deletions docs/Catalogos.md

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions docs/Pruebas.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ estos archivos y correr los tests. De esta forma podremos notar un cambio import

- `tests/_files/cce/*.csv`: Archivos que salen de los catálogos de Comercio Exterior
- `tests/_files/cfdi/*.csv`: Archivos que salen de los catálogos de CFDI 3.3
- `tests/_files/cfdi40/*.csv`: Archivos que salen de los catálogos de CFDI 4.0
- `tests/_files/nomina/*.csv`: Archivos que salen de los catálogos de Nómina 1.2
- `tests/_files/rep/*.csv`: Archivos que salen de los catálogos de recibo electrónico de pagos

Expand All @@ -42,19 +43,22 @@ php bin/sat-catalogos-update update-origins build/temp/origins.xml

rm -rf tests/_files/sources
rm -rf tests/_files/cfdi
rm -rf tests/_files/cfdi40
rm -rf tests/_files/nomina
rm -rf tests/_files/rep
rm -rf tests/_files/cce

mkdir -p tests/_files/sources
mkdir -p tests/_files/cfdi
mkdir -p tests/_files/cfdi40
mkdir -p tests/_files/nomina
mkdir -p tests/_files/rep
mkdir -p tests/_files/cce

cp build/temp/*.xls tests/_files/sources

php tests/convert-xls-to-csv-folder.php tests/_files/sources/catCFDI.xls tests/_files/cfdi
php tests/convert-xls-to-csv-folder.php tests/_files/sources/cfdi_40.xls tests/_files/cfdi40
php tests/convert-xls-to-csv-folder.php tests/_files/sources/catNomina.xls tests/_files/nomina
php tests/convert-xls-to-csv-folder.php tests/_files/sources/catPagos.xls tests/_files/rep
php tests/convert-xls-to-csv-folder.php tests/_files/sources/c_ClavePedimento.xls tests/_files/cce
Expand Down
8 changes: 7 additions & 1 deletion src/Commands/DumpOrigins.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,13 @@ public function run(): int
'CFDI',
'http://omawww.sat.gob.mx/tramitesyservicios/Paginas/anexo_20_version3-3.htm',
'catCFDI.xls',
'Catálogos CFDI Versión 3.3(xls)',
'Catálogos CFDI Versión 3.3*',
),
new ScrapingOrigin(
'CFDI',
'http://omawww.sat.gob.mx/tramitesyservicios/Paginas/anexo_20_version3-3.htm',
'cfdi_40.xls',
'Catálogos CFDI Versión 4.0*',
),
new ConstantOrigin('Nóminas', "{$common}/catNomina.xls"),
new ConstantOrigin('Nóminas - Estados', "{$common}/C_Estado.xls", null, 'nominas_estados.xls'),
Expand Down
47 changes: 26 additions & 21 deletions src/Converters/CsvFolderJoinFiles.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ function ($path): array {
/**
* @param array{destination: string, index: int} $first
* @param array{destination: string, index: int} $second
* @return int
*/
private function compareFiles(array $first, array $second): int
{
Expand All @@ -97,42 +98,46 @@ public function writeLines(string $source, string $destination, int $skipFirstLi

public function findLinesToSkip(string $firstPath, string $secondPath): int
{
$lines = 0;
$first = new SplFileObject($firstPath, 'r');
$second = new SplFileObject($secondPath, 'r');
while ($this->splCurrentLinesAreEqual($first, $second)) {
$first->next();
$second->next();
$lines = $lines + 1;

$firstTenLines = $this->splReadTenLines($first);
$secondTenLines = $this->splReadTenLines($second);

for ($i = 9; $i > 0; $i--) {
$firstValue = $firstTenLines[$i] ?? null;
$secondValue = $secondTenLines[$i] ?? null;
if (null !== $firstValue && null !== $secondValue && $firstValue === $secondValue) {
return $i + 1;
}
}
return $lines;
return 0;
}

/**
* @param Iterator<mixed> $first
* @param Iterator<mixed> $second
* @param Iterator<string> $iterator
* @return array<int, string>
*/
private function splCurrentLinesAreEqual(Iterator $first, Iterator $second): bool
private function splReadTenLines(Iterator $iterator): array
{
if (! $first->valid() || ! $second->valid()) {
return false;
$result = [];
$counter = 0;
foreach ($iterator as $line) {
$result[] = $this->splCurrentLinesNormalizeValue($line);
$counter = $counter + 1;
if (10 === $counter) {
break;
}
}
$firstValue = $this->splCurrentLinesAreEqualNormalizeValue($first->current());
$secondValue = $this->splCurrentLinesAreEqualNormalizeValue($second->current());
return ($firstValue === $secondValue);
return $result;
}

private function splCurrentLinesAreEqualNormalizeValue(mixed $current): mixed
private function splCurrentLinesNormalizeValue(string $current): string
{
if (! is_string($current)) {
return $current;
}
return trim(implode(',', array_map('trim', explode(',', rtrim($current, ',')))));
}

/**
* @param string[] $searchterms
*/
/** @param string[] $searchterms */
public function lastLineContains(string $filename, array $searchterms): bool
{
$lastline = $this->obtainFileLastLine($filename);
Expand Down
43 changes: 43 additions & 0 deletions src/Importers/Cfdi40/Injectors/Aduanas.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);

namespace PhpCfdi\SatCatalogosPopulate\Importers\Cfdi40\Injectors;

use PhpCfdi\SatCatalogosPopulate\AbstractCsvInjector;
use PhpCfdi\SatCatalogosPopulate\Database\DataFields;
use PhpCfdi\SatCatalogosPopulate\Database\DataTable;
use PhpCfdi\SatCatalogosPopulate\Database\DateDataField;
use PhpCfdi\SatCatalogosPopulate\Database\PaddingDataField;
use PhpCfdi\SatCatalogosPopulate\Database\TextDataField;
use PhpCfdi\SatCatalogosPopulate\InjectorInterface;
use PhpCfdi\SatCatalogosPopulate\Utils\CsvFile;
use RuntimeException;

use function PhpCfdi\SatCatalogosPopulate\Utils\array_rtrim;

class Aduanas extends AbstractCsvInjector implements InjectorInterface
{
public function checkHeaders(CsvFile $csv): void
{
$csv->move(3);
$expected = ['c_Aduana', 'Descripción', 'Fecha inicio de vigencia', 'Fecha fin de vigencia'];
$headers = array_rtrim($csv->readLine());

if ($expected !== $headers) {
throw new RuntimeException("The headers did not match on file {$this->sourceFile()}");
}

$csv->next();
}

public function dataTable(): DataTable
{
return new DataTable('cfdi_40_aduanas', new DataFields([
new PaddingDataField('id', '0', 2),
new TextDataField('texto'),
new DateDataField('vigencia_desde'),
new DateDataField('vigencia_hasta'),
]));
}
}
54 changes: 54 additions & 0 deletions src/Importers/Cfdi40/Injectors/ClavesUnidades.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

declare(strict_types=1);

namespace PhpCfdi\SatCatalogosPopulate\Importers\Cfdi40\Injectors;

use PhpCfdi\SatCatalogosPopulate\AbstractCsvInjector;
use PhpCfdi\SatCatalogosPopulate\Database\DataFields;
use PhpCfdi\SatCatalogosPopulate\Database\DataTable;
use PhpCfdi\SatCatalogosPopulate\Database\DateDataField;
use PhpCfdi\SatCatalogosPopulate\Database\PaddingDataField;
use PhpCfdi\SatCatalogosPopulate\Database\TextDataField;
use PhpCfdi\SatCatalogosPopulate\InjectorInterface;
use PhpCfdi\SatCatalogosPopulate\Utils\CsvFile;
use RuntimeException;

use function PhpCfdi\SatCatalogosPopulate\Utils\array_rtrim;

class ClavesUnidades extends AbstractCsvInjector implements InjectorInterface
{
public function checkHeaders(CsvFile $csv): void
{
$csv->move(3);
$expected = [
'c_ClaveUnidad',
'Nombre',
'Descripción',
'Nota',
'Fecha de inicio de vigencia',
'Fecha de fin de vigencia',
'Símbolo',
];
$headers = array_rtrim($csv->readLine());

if ($expected !== $headers) {
throw new RuntimeException("The headers did not match on file {$this->sourceFile()}");
}

$csv->next();
}

public function dataTable(): DataTable
{
return new DataTable('cfdi_40_claves_unidades', new DataFields([
new PaddingDataField('id', '0', 2),
new TextDataField('texto'),
new TextDataField('descripcion'),
new TextDataField('notas'),
new DateDataField('vigencia_desde'),
new DateDataField('vigencia_hasta'),
new TextDataField('simbolo'),
]));
}
}
95 changes: 95 additions & 0 deletions src/Importers/Cfdi40/Injectors/CodigosPostales.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php

declare(strict_types=1);

namespace PhpCfdi\SatCatalogosPopulate\Importers\Cfdi40\Injectors;

use Generator;
use PhpCfdi\SatCatalogosPopulate\AbstractCsvInjector;
use PhpCfdi\SatCatalogosPopulate\Database\BoolDataField;
use PhpCfdi\SatCatalogosPopulate\Database\DataFields;
use PhpCfdi\SatCatalogosPopulate\Database\DataTable;
use PhpCfdi\SatCatalogosPopulate\Database\DateDataField;
use PhpCfdi\SatCatalogosPopulate\Database\TextDataField;
use PhpCfdi\SatCatalogosPopulate\Utils\CsvFile;
use RuntimeException;

class CodigosPostales extends AbstractCsvInjector
{
public function checkHeaders(CsvFile $csv): void
{
$csv->move(3);
$expectedLines = [
[
'c_CodigoPostal',
'c_Estado',
'c_Municipio',
'c_Localidad',
'Estímulo Franja Fronteriza',
'Fecha inicio de vigencia',
'Fecha fin de vigencia',
'Referencias del Huso Horario',
],
[
'',
'',
'',
'',
'',
'',
'',
'Descripción del Huso Horario',
'Mes_Inicio_Horario_Verano',
'Día_Inicio_Horario_Verano',
'Día_Inicio_Horario_Verano',
'Diferencia_Horaria_Verano',
'Mes_Inicio_Horario_Invierno',
'Día_Inicio_Horario_Invierno',
'Día_Inicio_Horario_Invierno',
'Diferencia_Horaria_Invierno',
],
];
foreach ($expectedLines as $line => $expected) {
$line = $line + 1;
$headers = array_map(fn ($line): string => trim((string) $line), $csv->readLine());
if ($expected !== $headers) {
throw new RuntimeException("The headers did not match on file {$this->sourceFile()} line {$line}");
}
$csv->next();
}
}

public function dataTable(): DataTable
{
return new DataTable('cfdi_40_codigos_postales', new DataFields([
new TextDataField('id'),
new TextDataField('estado'),
new TextDataField('municipio'),
new TextDataField('localidad'),
new BoolDataField('estimulo_frontera', ['1']),
new DateDataField('vigencia_desde'),
new DateDataField('vigencia_hasta'),
new TextDataField('huso_descripcion'),
new TextDataField('huso_verano_mes_inicio'),
new TextDataField('huso_verano_dia_inicio'),
new TextDataField('huso_verano_hora_inicio'),
new TextDataField('huso_verano_diferencia'),
new TextDataField('huso_invierno_mes_inicio'),
new TextDataField('huso_invierno_dia_inicio'),
new TextDataField('huso_invierno_hora_inicio'),
new TextDataField('huso_invierno_diferencia'),
]));
}

/** @inheritdoc */
protected function readLinesFromCsv(CsvFile $csv): Generator
{
foreach ($csv->readLines() as $line) {
if ('00000' === $line[0]) {
continue;
}

yield $line;
}
}
}
38 changes: 38 additions & 0 deletions src/Importers/Cfdi40/Injectors/Colonias.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace PhpCfdi\SatCatalogosPopulate\Importers\Cfdi40\Injectors;

use PhpCfdi\SatCatalogosPopulate\AbstractCsvInjector;
use PhpCfdi\SatCatalogosPopulate\Database\DataFields;
use PhpCfdi\SatCatalogosPopulate\Database\DataTable;
use PhpCfdi\SatCatalogosPopulate\Database\PaddingDataField;
use PhpCfdi\SatCatalogosPopulate\Database\TextDataField;
use PhpCfdi\SatCatalogosPopulate\Utils\CsvFile;
use RuntimeException;

class Colonias extends AbstractCsvInjector
{
public function checkHeaders(CsvFile $csv): void
{
$csv->move(3);
$expected = ['c_Colonia', 'c_CodigoPostal', 'Nombre del asentamiento'];
$headers = $csv->readLine();

if ($expected !== $headers) {
throw new RuntimeException("The headers did not match on file {$this->sourceFile()}");
}

$csv->next();
}

public function dataTable(): DataTable
{
return new DataTable('cfdi_40_colonias', new DataFields([
new PaddingDataField('colonia', '0', 4),
new PaddingDataField('codigo_postal', '0', 5),
new TextDataField('texto'),
]), ['colonia', 'codigo_postal']);
}
}
Loading

0 comments on commit e5cdcfa

Please sign in to comment.