Skip to content

Commit

Permalink
Merge pull request #18 from driftking301/version2
Browse files Browse the repository at this point in the history
Completed ConceptoImpuestos and Tests
  • Loading branch information
eclipxe13 committed Nov 10, 2017
2 parents 55e099d + ad1e26b commit 0af3ac1
Show file tree
Hide file tree
Showing 2 changed files with 265 additions and 46 deletions.
140 changes: 125 additions & 15 deletions src/CfdiUtils/Validate/Cfdi33/Standard/ConceptoImpuestos.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,142 @@
* ConceptoImpuestos.php
*
* Valida que:
* - CONCEPIMPC01: Si se utiliza el nodo impuestos en un concepto entonces se deben incluir traslados o retenciones
* - CONCEPIMPC01: El nodo impuestos de un concepto debe incluir traslados y/o retenciones (CFDI33152)
* - CONCEPIMPC02: Los traslados de los impuestos de un concepto deben tener una base y ser mayor a cero (CFDI33154)
* - CONCEPIMPC03: No se debe registrar la tasa o cuota ni el importe cuando
* el tipo de factor de traslado es exento (CFDI33157)
* - CONCEPIMPC04: Se debe registrar la tasa o cuota y el importe cuando
* el tipo de factor de traslado es tasa o cuota (CFDI33158)
* - CONCEPIMPC05: Las retenciones de los impuestos de un concepto deben tener una base y ser mayor a cero (CFDI33154)
* - CONCEPIMPC06: Las retenciones de los impuestos de un concepto deben tener
* un tipo de factor diferente de exento (CFDI33166)
*/
class ConceptoImpuestos extends AbstractDiscoverableVersion33
{
public function validate(NodeInterface $comprobante, Asserts $asserts)
private function registerAsserts(Asserts $asserts)
{
$asserts->put(
'CONCEPIMPC01',
'Si se utiliza el nodo impuestos en un concepto entonces se deben incluir traslados y retenciones',
Status::when($this->allConceptosImpuestosHasTrasladosOrRetenciones($comprobante))
);
$assertDescriptions = [
'CONCEPIMPC01' => 'El nodo impuestos de un concepto debe incluir traslados y/o retenciones (CFDI33152)',
'CONCEPIMPC02' => 'Los traslados de los impuestos de un concepto deben tener una base y ser mayor a cero'
. ' (CFDI33154)',
'CONCEPIMPC03' => 'No se debe registrar la tasa o cuota ni el importe cuando el tipo de factor de traslado'
. ' es exento (CFDI33157)',
'CONCEPIMPC04' => 'Se debe registrar la tasa o cuota y el importe cuando el tipo de factor de traslado'
. ' es tasa o cuota (CFDI33158)',
'CONCEPIMPC05' => 'Las retenciones de los impuestos de un concepto deben tener una base y ser mayor a cero'
. '(CFDI33154)',
'CONCEPIMPC06' => ' Las retenciones de los impuestos de un concepto deben tener un tipo de factor diferente'
. ' de exento (CFDI33166)',
];
foreach ($assertDescriptions as $code => $title) {
$asserts->put($code, $title);
}
}

public function allConceptosImpuestosHasTrasladosOrRetenciones(NodeInterface $comprobante): bool
public function validate(NodeInterface $comprobante, Asserts $asserts)
{
foreach ($comprobante->searchNodes('cfdi:Conceptos', 'cfdi:Concepto') as $concepto) {
$impuestos = $concepto->searchNode('cfdi:Impuestos');
if (null === $impuestos) {
continue;
$this->registerAsserts($asserts);

$status01 = Status::ok();
$status02 = Status::ok();
$status03 = Status::ok();
$status04 = Status::ok();
$status05 = Status::ok();
$status06 = Status::ok();

foreach ($comprobante->searchNodes('cfdi:Conceptos', 'cfdi:Concepto') as $i => $concepto) {
if ($status01->isOk() && ! $this->conceptoImpuestosHasTrasladosOrRetenciones($concepto)) {
$status01 = Status::error();
$asserts->get('CONCEPIMPC01')->setExplanation(sprintf('Concepto #%d', $i));
}
if ($impuestos->searchNodes('cfdi:Traslados', 'cfdi:Traslado')->count()
|| $impuestos->searchNodes('cfdi:Retenciones', 'cfdi:Retencion')->count()) {
continue;

$traslados = $concepto->searchNodes('cfdi:Impuestos', 'cfdi:Traslados', 'cfdi:Traslado');
foreach ($traslados as $k => $traslado) {
if ($status02->isOk() && ! $this->impuestoHasBaseGreaterThanZero($traslado)) {
$status02 = Status::error();
$asserts->get('CONCEPIMPC02')->setExplanation(sprintf('Concepto #%d, Traslado #%d', $i, $k));
}
if ($status03->isOk() && ! $this->trasladoHasTipoFactorExento($traslado)) {
$status03 = Status::error();
$asserts->get('CONCEPIMPC03')->setExplanation(sprintf('Concepto #%d, Traslado #%d', $i, $k));
}
if ($status04->isOk() && ! $this->trasladoHasTipoFactorTasaOCuota($traslado)) {
$status04 = Status::error();
$asserts->get('CONCEPIMPC04')->setExplanation(sprintf('Concepto #%d, Traslado #%d', $i, $k));
}
}

$retenciones = $concepto->searchNodes('cfdi:Impuestos', 'cfdi:Retenciones', 'cfdi:Retencion');
foreach ($retenciones as $k => $retencion) {
if ($status05->isOk() && ! $this->impuestoHasBaseGreaterThanZero($retencion)) {
$status05 = Status::error();
$asserts->get('CONCEPIMPC05')->setExplanation(sprintf('Concepto #%d, Retención #%d', $i, $k));
}
if ($status06->isOk() && 'Exento' === $retencion['TipoFactor']) {
$status06 = Status::error();
$asserts->get('CONCEPIMPC06')->setExplanation(sprintf('Concepto #%d, Retención #%d', $i, $k));
}
}
}

$asserts->putStatus('CONCEPIMPC01', $status01);
$asserts->putStatus('CONCEPIMPC02', $status02);
$asserts->putStatus('CONCEPIMPC03', $status03);
$asserts->putStatus('CONCEPIMPC04', $status04);
$asserts->putStatus('CONCEPIMPC05', $status05);
$asserts->putStatus('CONCEPIMPC06', $status06);
}

private function conceptoImpuestosHasTrasladosOrRetenciones(NodeInterface $concepto): bool
{
$impuestos = $concepto->searchNode('cfdi:Impuestos');
if (null === $impuestos) {
return true;
}
if ($impuestos->searchNodes('cfdi:Traslados', 'cfdi:Traslado')->count()
|| $impuestos->searchNodes('cfdi:Retenciones', 'cfdi:Retencion')->count()) {
return true;
}
return false;
}

private function impuestoHasBaseGreaterThanZero(NodeInterface $impuesto): bool
{
if (! isset($impuesto['Base'])) {
return false;
}
if (! is_numeric($impuesto['Base'])) {
return false;
}
if ((float) $impuesto['Base'] < 0.000001) {
return false;
}
return true;
}

private function trasladoHasTipoFactorExento(NodeInterface $traslado): bool
{
if ('Exento' === $traslado['TipoFactor']) {
if (isset($traslado['TasaOCuota'])) {
return false;
}
if (isset($traslado['Importe'])) {
return false;
}
}
return true;
}

private function trasladoHasTipoFactorTasaOCuota(NodeInterface $traslado): bool
{
if ('Tasa' === $traslado['TipoFactor'] || 'Cuota' === $traslado['TipoFactor']) {
if ('' === $traslado['TasaOCuota']) {
return false;
}
if ('' === $traslado['Importe']) {
return false;
}
}
return true;
}
}
171 changes: 140 additions & 31 deletions tests/CfdiUtilsTests/Validate/Cfdi33/Standard/ConceptoImpuestosTest.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php
namespace CfdiUtilsTests\Validate\Cfdi33\Standard;

use CfdiUtils\Nodes\Node;
use CfdiUtils\Elements\Cfdi33\Comprobante;
use CfdiUtils\Validate\Cfdi33\Standard\ConceptoImpuestos;
use CfdiUtils\Validate\Status;
use CfdiUtilsTests\Validate\ValidateTestCase;
Expand All @@ -17,41 +17,150 @@ protected function setUp()
$this->validator = new ConceptoImpuestos();
}

public function testValidCase()
{
$this->comprobante->addChild(
new Node('cfdi:Conceptos', [], [
new Node('cfdi:Concepto'),
new Node('cfdi:Concepto', [], [
new Node('cfdi:Impuestos', [], [
new Node('cfdi:Traslados', [], [
new Node('cfdi:Traslado'),
]),
new Node('cfdi:Retenciones', [], [
new Node('cfdi:Retencion'),
]),
]),
]),
])
);
public function testInvalidCaseNoRetencionOrTraslado()
{
$comprobante = $this->validComprobante();
$comprobante->addConcepto()->getImpuestos();
$this->runValidate();
$this->assertStatusEqualsCode(Status::ok(), 'CONCEPIMPC01');
$this->assertStatusEqualsCode(Status::error(), 'CONCEPIMPC01');
}

public function testInvalidCaseNoRetencionOrTraslado()
public function providerInvalidBaseTraslado()
{
$this->comprobante->addChild(
new Node('cfdi:Conceptos', [], [
new Node('cfdi:Concepto'),
new Node('cfdi:Concepto', [], [
new Node('cfdi:Impuestos', [], [
new Node('cfdi:Traslados', [], []),
new Node('cfdi:Retenciones', [], []),
]),
]),
])
return[
['0'],
['0.0000001'],
['-1'],
['foo'],
['0.0.0.0'],
];
}

/**
* @param $base
* @dataProvider providerInvalidBaseTraslado
*/
public function testTrasladoHasBaseGreaterThanZeroInvalidCase($base)
{
$comprobante = $this->validComprobante();
$comprobante->addConcepto()->addTraslado(['Base' => $base]);
$this->runValidate();
$this->assertStatusEqualsCode(Status::error(), 'CONCEPIMPC02');
}

public function providerTrasladoTipoFactorExento()
{
return[
['1', '1'],
[null, '1'],
['1', null],
];
}

/**
* @param $tasaOCuota
* @param $importe
* @dataProvider providerTrasladoTipoFactorExento
*/
public function testTrasladosTipoFactorInvalidCase($tasaOCuota, $importe)
{
$comprobante = $this->validComprobante();
$comprobante->addConcepto()->addTraslado([
'TipoFactor' => 'Exento',
'TasaOCuota' => $tasaOCuota,
'Importe' => $importe,
]);
$this->runValidate();
$this->assertStatusEqualsCode(Status::error(), 'CONCEPIMPC03');
}

public function providerTrasladosTipoFactorTasaOCuotaInvalidCase()
{
return $this->providerFullJoin(
[['Tasa'], ['Cuota']],
[[''], [null]],
[[''], [null]]
);
}

/**
* @param $tipoFactor
* @param $tasaOCuota
* @param $importe
* @dataProvider providerTrasladosTipoFactorTasaOCuotaInvalidCase
*/
public function testTrasladosTipoFactorTasaOCuotaInvalidCase($tipoFactor, $tasaOCuota, $importe)
{
$comprobante = $this->validComprobante();
$comprobante->addConcepto()->addTraslado([
'TipoFactor' => $tipoFactor,
'TasaOCuota' => $tasaOCuota,
'Importe' => $importe,
]);
$this->runValidate();
$this->assertStatusEqualsCode(Status::error(), 'CONCEPIMPC01');
$this->assertStatusEqualsCode(Status::error(), 'CONCEPIMPC04');
}

public function providerInvalidBaseRetencion()
{
return[
['0'],
['0.0000001'],
['-1'],
['foo'],
['0.0.0.0'],
];
}

/**
* @param $base
* @dataProvider providerInvalidBaseTraslado
*/
public function testRetencionesHasBaseGreaterThanZeroInvalidCase($base)
{
$comprobante = $this->validComprobante();
$comprobante->addConcepto()->addRetencion(['Base' => $base]);
$this->runValidate();
$this->assertStatusEqualsCode(Status::error(), 'CONCEPIMPC05');
}

public function testInvalidCaseRetencionTipoFactorExento()
{
$comprobante = $this->validComprobante();
$comprobante->addConcepto()->addRetencion(['TipoFactor' => 'Exento']);
$this->runValidate();
$this->assertStatusEqualsCode(Status::error(), 'CONCEPIMPC06');
}

public function testValidComprobante()
{
$this->validComprobante();
$this->runValidate();
$this->assertFalse($this->asserts->hasErrors());
}

private function validComprobante(): Comprobante
{
/** @var Comprobante $comprobante */
$comprobante = $this->comprobante;
$comprobante->addConcepto();
$comprobante->addConcepto()->multiTraslado([
'TipoFactor' => 'Exento',
'Base' => '123.45',
], [
'Base' => '123.45',
'TipoFactor' => 'Tasa',
'TasaOCuota' => '0.160000',
'Importe' => '19.75',
])->multiRetencion([
'Base' => '0.000001',
'TipoFactor' => 'Tasa',
'TasaOCuota' => '0.02',
'Importe' => '1.23',
], [
'Base' => '123.45',
'TipoFactor' => 'Cuota',
]);
return $comprobante;
}
}

0 comments on commit 0af3ac1

Please sign in to comment.