Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Los cambios notables de cada lanzamiento serán documentados en este archivo.
- #276 CI: tests unitarios separados de los de integración (SUNAT beta ya no bloquea PRs).
- Corregir parámetros implícitamente nullable (deprecación PHP 8.4).
- Registrar filtros y funciones Twig del reporte con instancias (compatibilidad con Psalm 5.26).
- #258 Retención/Percepción: agregar `Payment::numero` (número de cuota) usado en `cac:Payment/cbc:ID`, validar un solo pago/cobro por detalle de retención y percepción (XSD SUNAT) y leer número/fecha de pago por nodo en los parsers.

## 5.0.0 - 2023-05-01
- #206 Agregar xml para nueva Guia de Remisión.
Expand Down
29 changes: 29 additions & 0 deletions packages/core/src/Core/Model/Retention/Payment.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@
*/
class Payment
{
/**
* Número de pago (cuota) asignado a la factura.
*
* Si no se especifica, se utiliza la posición del pago dentro del detalle.
*
* @var int|null
*/
private $numero;

/**
* Moneda de pago (igual a la moneda del documento de referencia).
*
Expand All @@ -38,6 +47,26 @@ class Payment
*/
private $fecha;

/**
* @return int|null
*/
public function getNumero(): ?int
{
return $this->numero;
}

/**
* @param int|null $numero
*
* @return Payment
*/
public function setNumero(?int $numero): Payment
{
$this->numero = $numero;

return $this;
}

/**
* @return string
*/
Expand Down
1 change: 1 addition & 0 deletions packages/validator/src/Validator/Loader/PaymentLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class PaymentLoader implements LoaderMetadataInterface
{
public function load(ClassMetadata $metadata)
{
$metadata->addPropertyConstraint('numero', new Assert\GreaterThan(0));
$metadata->addPropertyConstraints('moneda', [
new Assert\NotBlank(),
new Assert\Length(['min' => 3, 'max' => 3]),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ public function load(ClassMetadata $metadata)
]);
$metadata->addPropertyConstraints('cobros', [
new Assert\NotBlank(),
// XSD SUNAT: cac:Payment maxOccurs=1 por SUNATPerceptionDocumentReference,
// para varios cobros de una factura agregar un detalle por cada cobro.
new Assert\Count(['max' => 1]),
new Assert\Valid(),
]);
$metadata->addPropertyConstraint('impCobrar', new Assert\NotNull());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ public function load(ClassMetadata $metadata)
]);
$metadata->addPropertyConstraints('pagos', [
new Assert\NotBlank(),
// XSD SUNAT: cac:Payment maxOccurs=1 por SUNATRetentionDocumentReference,
// para varios pagos de una factura agregar un detalle por cada pago.
new Assert\Count(['max' => 1]),
new Assert\Valid(),
]);
$metadata->addPropertyConstraint('impPagar', new Assert\NotNull());
Expand Down
29 changes: 28 additions & 1 deletion packages/validator/tests/Validator/CePerceptionValidatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,32 @@ public function testValidatePerceptionInvalidIndExcepcional()
$this->assertEquals('indExcepcional', $errors->get(0)->getPropertyPath());
}

public function testValidatePerceptionMultiplesCobrosPorDetalle()
{
$perception = $this->getPerception();
$detail = $perception->getDetails()[0];
$cobro = (new Payment())
->setNumero(2)
->setMoneda('PEN')
->setFecha(new DateTime())
->setImporte(100);
$detail->setCobros(array_merge($detail->getCobros(), [$cobro]));

$errors = $this->getValidator()->validate($perception);

$this->assertEquals(1, $errors->count());
}

public function testValidatePerceptionNumeroCobroInvalido()
{
$perception = $this->getPerception();
$perception->getDetails()[0]->getCobros()[0]->setNumero(0);

$errors = $this->getValidator()->validate($perception);

$this->assertEquals(1, $errors->count());
}

/**
* @return Perception
*/
Expand Down Expand Up @@ -97,7 +123,8 @@ private function getPerception()
private function getExtras()
{
$pay = new Payment();
$pay->setMoneda('PEN')
$pay->setNumero(1)
->setMoneda('PEN')
->setFecha(new DateTime())
->setImporte(100);

Expand Down
29 changes: 28 additions & 1 deletion packages/validator/tests/Validator/CeRetentionValidatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,32 @@ public function testValidateRetention()
$this->assertEquals(0,$errors->count());
}

public function testValidateRetentionMultiplePagosPorDetalle()
{
$retention = $this->getRetention();
$detail = $retention->getDetails()[0];
$pay = (new Payment())
->setNumero(2)
->setMoneda('PEN')
->setFecha(new \DateTime())
->setImporte(100);
$detail->setPagos(array_merge($detail->getPagos(), [$pay]));

$errors = $this->getValidator()->validate($retention);

$this->assertEquals(1, $errors->count());
}

public function testValidateRetentionNumeroPagoInvalido()
{
$retention = $this->getRetention();
$retention->getDetails()[0]->getPagos()[0]->setNumero(0);

$errors = $this->getValidator()->validate($retention);

$this->assertEquals(1, $errors->count());
}

/**
* @return Retention
*/
Expand Down Expand Up @@ -77,7 +103,8 @@ private function getRetention()
private function getExtras()
{
$pay = new Payment();
$pay->setMoneda('PEN')
$pay->setNumero(1)
->setMoneda('PEN')
->setFecha(new \DateTime())
->setImporte(100);

Expand Down
6 changes: 4 additions & 2 deletions packages/xml-parser/src/Xml/Parser/PerceptionParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -172,10 +172,12 @@ private function getPayments(DOMNode $node)
$pays = $xml->getNodes('cac:Payment', $node);
foreach ($pays as $pay) {
$temp = $xml->getNode('cbc:PaidAmount', $pay);
$numero = $xml->getValue('cbc:ID', $pay);
$payment = new Payment();
$payment->setMoneda($temp->getAttribute('currencyID'))
$payment->setNumero($numero === null || $numero === '' ? null : (int)$numero)
->setMoneda($temp->getAttribute('currencyID'))
->setImporte((float)$temp->nodeValue)
->setFecha(new DateTime($xml->getValue('cbc:PaidDate')));
->setFecha(new DateTime($xml->getValue('cbc:PaidDate', $pay)));

yield $payment;
}
Expand Down
6 changes: 4 additions & 2 deletions packages/xml-parser/src/Xml/Parser/RetentionParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,12 @@ private function getPayments(DOMNode $node)
$pays = $xml->getNodes('cac:Payment', $node);
foreach ($pays as $pay) {
$temp = $xml->getNode('cbc:PaidAmount', $pay);
$numero = $xml->getValue('cbc:ID', $pay);
$payment = new Payment();
$payment->setMoneda($temp->getAttribute('currencyID'))
$payment->setNumero($numero === null || $numero === '' ? null : (int)$numero)
->setMoneda($temp->getAttribute('currencyID'))
->setImporte((float)$temp->nodeValue)
->setFecha(new DateTime($xml->getValue('cbc:PaidDate')));
->setFecha(new DateTime($xml->getValue('cbc:PaidDate', $pay)));

yield $payment;
}
Expand Down
4 changes: 4 additions & 0 deletions packages/xml-parser/tests/Xml/Parser/PerceptionParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ public function testParseDoc($filename)
$this->assertNotEmpty($detail->getNumDoc());
$this->assertNotNull($detail->getFechaEmision());
$this->assertGreaterThan(0, count($detail->getCobros()));
foreach ($detail->getCobros() as $cobro) {
$this->assertGreaterThan(0, $cobro->getNumero());
$this->assertNotNull($cobro->getFecha());
}
$this->assertTrue(is_float($detail->getImpTotal()));
$this->assertTrue(is_float($detail->getImpPercibido()));
$this->assertTrue(is_float($detail->getImpCobrar()));
Expand Down
4 changes: 4 additions & 0 deletions packages/xml-parser/tests/Xml/Parser/RetentionParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ public function testParseDoc($filename)
$this->assertNotEmpty($detail->getNumDoc());
$this->assertNotNull($detail->getFechaEmision());
$this->assertTrue(is_array($detail->getPagos()));
foreach ($detail->getPagos() as $pago) {
$this->assertGreaterThan(0, $pago->getNumero());
$this->assertNotNull($pago->getFecha());
}
$this->assertTrue(is_float($detail->getImpTotal()));
$this->assertTrue(is_float($detail->getImpRetenido()));
$this->assertTrue(is_float($detail->getImpPagar()));
Expand Down
2 changes: 1 addition & 1 deletion packages/xml/src/Xml/Templates/perception.xml.twig
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
{% if det.cobros %}
{% for cob in det.cobros %}
<cac:Payment>
<cbc:ID>{{ loop.index }}</cbc:ID>
<cbc:ID>{{ cob.numero|default(loop.index) }}</cbc:ID>
<cbc:PaidAmount currencyID="{{ cob.moneda }}">{{ cob.importe|n_format }}</cbc:PaidAmount>
<cbc:PaidDate>{{ cob.fecha|date('Y-m-d') }}</cbc:PaidDate>
</cac:Payment>
Expand Down
2 changes: 1 addition & 1 deletion packages/xml/src/Xml/Templates/retention.xml.twig
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
{% if det.pagos %}
{% for pay in det.pagos %}
<cac:Payment>
<cbc:ID>{{ loop.index }}</cbc:ID>
<cbc:ID>{{ pay.numero|default(loop.index) }}</cbc:ID>
<cbc:PaidAmount currencyID="{{ pay.moneda }}">{{ pay.importe|n_format }}</cbc:PaidAmount>
<cbc:PaidDate>{{ pay.fecha|date('Y-m-d') }}</cbc:PaidDate>
</cac:Payment>
Expand Down