packages/xml-parser/src/Xml/Parser/InvoiceParser.php
<?php
/**
* Created by PhpStorm.
* User: Giansalex
* Date: 05/10/2017
* Time: 08:14
*/
declare(strict_types=1);
namespace Greenter\Xml\Parser;
use DateTime;
use DOMDocument;
use DOMElement;
use DOMNode;
use DOMNodeList;
use DOMXPath;
use Greenter\Model\Client\Client;
use Greenter\Model\Company\Address;
use Greenter\Model\Company\Company;
use Greenter\Model\DocumentInterface;
use Greenter\Model\Sale\Detraction;
use Greenter\Model\Sale\Document;
use Greenter\Model\Sale\Invoice;
use Greenter\Model\Sale\Legend;
use Greenter\Model\Sale\Prepayment;
use Greenter\Model\Sale\SaleDetail;
use Greenter\Model\Sale\SalePerception;
use Greenter\Parser\DocumentParserInterface;
/**
* Class InvoiceParser.
* @package Greenter\Xml\Parser
*/
class InvoiceParser implements DocumentParserInterface
{
/**
* @param mixed $value
* @return DocumentInterface
*/
public function parse($value): ?DocumentInterface
{
$xpt = $this->getXpath($value);
$inv = new Invoice();
$docFac = explode('-', $this->defValue($xpt->query('/xt:Invoice/cbc:ID')));
$inv->setSerie($docFac[0])
->setCorrelativo($docFac[1])
->setTipoDoc($this->defValue($xpt->query('/xt:Invoice/cbc:InvoiceTypeCode')))
->setTipoMoneda($this->defValue($xpt->query('/xt:Invoice/cbc:DocumentCurrencyCode')))
->setFechaEmision(new DateTime($this->defValue($xpt->query('/xt:Invoice/cbc:IssueDate'))))
->setCompany($this->getCompany($xpt))
->setClient($this->getClient($xpt));
$extensions = $xpt->query('/xt:Invoice/ext:UBLExtensions')->item(0);
$additional = $xpt->query('//sac:AdditionalInformation', $extensions)->item(0);
$this->loadTotals($inv, $xpt, $additional);
$this->loadTributos($inv, $xpt);
$monetaryTotal = $xpt->query('/xt:Invoice/cac:LegalMonetaryTotal')->item(0);
$inv->setTipoOperacion($this->defValue($xpt->query('sac:SUNATTransaction/cbc:ID', $additional)))
->setSumDsctoGlobal((float)$this->defValue($xpt->query('cbc:AllowanceTotalAmount', $monetaryTotal), '0'))
->setTotalAnticipos((float)$this->defValue($xpt->query('cbc:PrepaidAmount', $monetaryTotal), '0'))
->setAnticipos(iterator_to_array($this->getPrepayments($xpt)))
->setMtoOtrosTributos((float)$this->defValue($xpt->query('cbc:ChargeTotalAmount', $monetaryTotal), '0'))
->setMtoImpVenta((float)$this->defValue($xpt->query('cbc:PayableAmount', $monetaryTotal), '0'))
->setDetails(iterator_to_array($this->getDetails($xpt)))
->setLegends(iterator_to_array($this->getLegends($xpt, $additional)));
$this->loadExtras($xpt, $inv);
return $inv;
}
private function getXpath($value)
{
$doc = $value;
if (!($value instanceof DOMDocument)) {
$doc = new DOMDocument();
@$doc->loadXML($value);
}
$rootNamespace = $doc->documentElement->namespaceURI;
$xpt = new DOMXPath($doc);
$xpt->registerNamespace('xt', $rootNamespace);
return $xpt;
}
private function defValue(DOMNodeList $nodeList, string $default = '')
{
if ($nodeList->length == 0) {
return $default;
}
return $nodeList->item(0)->nodeValue;
}
private function defNodeAttribute(DOMNodeList $nodeList, string $attribute, string $default = '')
{
if ($nodeList->length == 0) {
return $default;
}
$node = $nodeList->item(0);
if (!$node instanceof DOMElement) {
return $default;
}
return $node->getAttribute($attribute);
}
private function loadTotals(Invoice $inv, DOMXPath $xpt, DOMNode $node = null)
{
if (empty($node)) {
return;
}
$totals = $xpt->query('sac:AdditionalMonetaryTotal', $node);
foreach ($totals as $total) {
/**@var $total DOMElement*/
$id = trim($this->defValue($xpt->query('cbc:ID', $total)));
$val = (float)$this->defValue($xpt->query('cbc:PayableAmount', $total), '0');
switch ($id) {
case '1001':
$inv->setMtoOperGravadas($val);
break;
case '1002':
$inv->setMtoOperInafectas($val);
break;
case '1003':
$inv->setMtoOperExoneradas($val);
break;
case '1004':
$inv->setMtoOperGratuitas($val);
break;
case '2001':
$inv->setPerception((new SalePerception())
->setCodReg($this->defNodeAttribute($xpt->query('cbc:ID', $total), 'schemeID'))
->setMto($val)
->setMtoBase((float)$this->defValue($xpt->query('sac:ReferenceAmount', $total), '0'))
->setMtoTotal((float)$this->defValue($xpt->query('sac:TotalAmount', $total), '0')));
break;
case '2003':
$inv->setDetraccion((new Detraction())
->setMount($val)
->setPercent((float)$this->defValue($xpt->query('cbc:Percent', $total), '0'))
->setValueRef((float)$this->defValue($xpt->query('sac:ReferenceAmount', $total), '0')));
break;
case '2005':
$inv->setMtoDescuentos($val);
break;
}
}
}
private function loadTributos(Invoice $inv, DOMXPath $xpt)
{
$taxs = $xpt->query('/xt:Invoice/cac:TaxTotal');
foreach ($taxs as $tax) {
$name = $this->defValue($xpt->query('cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', $tax));
$val = (float)$this->defValue($xpt->query('cbc:TaxAmount', $tax), '0');
switch ($name) {
case 'IGV':
$inv->setMtoIGV($val);
break;
case 'ISC':
$inv->setMtoISC($val);
break;
case 'OTROS':
$inv->setSumOtrosCargos($val);
break;
}
}
}
private function getPrepayments(DOMXPath $xpt)
{
$nodes = $xpt->query('/xt:Invoice/cac:PrepaidPayment');
if ($nodes->length == 0) {
return;
}
foreach ($nodes as $node) {
yield (new Prepayment())
->setTotal((float)$this->defValue($xpt->query('cbc:PaidAmount', $node), '0'))
->setTipoDocRel($this->defNodeAttribute($xpt->query('cbc:ID', $node), 'schemeID'))
->setNroDocRel($this->defValue($xpt->query('cbc:ID', $node)));
}
}
private function getLegends(DOMXPath $xpt, DOMNode $node = null)
{
if (empty($node)) {
return;
}
$legends = $xpt->query('sac:AdditionalProperty', $node);
foreach ($legends as $legend) {
/**@var $legend DOMElement*/
yield (new Legend())
->setCode($this->defValue($xpt->query('cbc:ID', $legend)))
->setValue($this->defValue($xpt->query('cbc:Value', $legend)));
}
}
private function getClient(DOMXPath $xp)
{
$node = $xp->query('/xt:Invoice/cac:AccountingCustomerParty')->item(0);
$cl = new Client();
$cl->setNumDoc($this->defValue($xp->query('cbc:CustomerAssignedAccountID', $node)))
->setTipoDoc($this->defValue($xp->query('cbc:AdditionalAccountID', $node)))
->setRznSocial($this->defValue($xp->query('cac:Party/cac:PartyLegalEntity/cbc:RegistrationName', $node)))
->setAddress($this->getAddress($xp, $node));
return $cl;
}
private function getCompany(DOMXPath $xp)
{
$node = $xp->query('/xt:Invoice/cac:AccountingSupplierParty')->item(0);
$cl = new Company();
$cl->setRuc($this->defValue($xp->query('cbc:CustomerAssignedAccountID', $node)))
->setNombreComercial($this->defValue($xp->query('cac:Party/cac:PartyName/cbc:Name', $node)))
->setRazonSocial($this->defValue($xp->query('cac:Party/cac:PartyLegalEntity/cbc:RegistrationName', $node)))
->setAddress($this->getAddress($xp, $node));
return $cl;
}
private function loadExtras(DOMXPath $xpt, Invoice $inv)
{
$inv->setCompra($this->defValue($xpt->query('/xt:Invoice/cac:OrderReference/cbc:ID')));
$fecVen = $this->defValue($xpt->query('/xt:Invoice/cac:PaymentMeans/cbc:PaymentDueDate'));
if (!empty($fecVen)) {
$inv->setFecVencimiento(new DateTime($fecVen));
}
$inv->setGuias(iterator_to_array($this->getGuias($xpt)));
}
private function getGuias(DOMXPath $xpt)
{
$guias = $xpt->query('/xt:Invoice/cac:DespatchDocumentReference');
if ($guias->length == 0) {
return;
}
foreach ($guias as $guia) {
$item = new Document();
$item->setTipoDoc($this->defValue($xpt->query('cbc:DocumentTypeCode', $guia)));
$item->setNroDoc($this->defValue($xpt->query('cbc:ID', $guia)));
yield $item;
}
}
/**
* @param DOMXPath $xp
* @param DOMNode $node
* @return Address|null
*/
private function getAddress(DOMXPath $xp, $node)
{
$nAd = $xp->query('cac:Party/cac:PostalAddress', $node);
if ($nAd->length > 0) {
$address = $nAd->item(0);
return (new Address())
->setDireccion($this->defValue($xp->query('cbc:StreetName', $address)))
->setDepartamento($this->defValue($xp->query('cbc:CityName', $address)))
->setProvincia($this->defValue($xp->query('cbc:CountrySubentity', $address)))
->setDistrito($this->defValue($xp->query('cbc:District', $address)))
->setUbigueo($this->defValue($xp->query('cbc:ID', $address)));
}
return null;
}
private function getDetails(DOMXPath $xpt)
{
$nodes = $xpt->query('/xt:Invoice/cac:InvoiceLine');
foreach ($nodes as $node) {
$det = new SaleDetail();
$det->setCantidad((float)$this->defValue($xpt->query('cbc:InvoicedQuantity', $node), '0'))
->setUnidad($this->defNodeAttribute($xpt->query('cbc:InvoicedQuantity', $node), 'unitCode'))
->setMtoValorVenta((float)$this->defValue($xpt->query('cbc:LineExtensionAmount', $node)))
->setMtoValorUnitario((float)$this->defValue($xpt->query('cac:Price/cbc:PriceAmount', $node)))
->setDescripcion($this->defValue($xpt->query('cac:Item/cbc:Description', $node)))
->setCodProducto($this->defValue($xpt->query('cac:Item/cac:SellersItemIdentification/cbc:ID', $node)))
->setCodProdSunat($this->defValue($xpt->query('cac:Item/cac:CommodityClassification/cbc:ItemClassificationCode', $node)));
$this->loadTaxDetail($det, $xpt, $node);
$this->loadDescuentosDetail($det, $xpt, $node);
$this->loadPricesDetail($det, $xpt, $node);
yield $det;
}
}
private function loadTaxDetail(SaleDetail $detail, DOMXPath $xpt, DOMNode $detailNode)
{
$taxs = $xpt->query('cac:TaxTotal', $detailNode);
foreach ($taxs as $tax) {
$name = $this->defValue($xpt->query('cac:TaxSubtotal/cac:TaxCategory/cac:TaxScheme/cbc:Name', $tax));
$val = (float)$this->defValue($xpt->query('cbc:TaxAmount', $tax), '0');
switch ($name) {
case 'IGV':
$detail->setIgv($val)
->setTipAfeIgv($this->defValue($xpt->query('cac:TaxSubtotal/cac:TaxCategory/cbc:TaxExemptionReasonCode', $tax)));
break;
case 'ISC':
$detail->setIsc($val)
->setTipSisIsc($this->defValue($xpt->query('cac:TaxSubtotal/cac:TaxCategory/cbc:TierRange', $tax)));
break;
}
}
}
private function loadDescuentosDetail(SaleDetail $detail, DOMXPath $xpt, DOMNode $detailNode)
{
$descs = $xpt->query('cac:AllowanceCharge', $detailNode);
foreach ($descs as $desc) {
$charge = $this->defValue($xpt->query('cbc:ChargeIndicator', $desc));
$charge = trim($charge);
if ($charge == 'false') {
$val = (float)$this->defValue($xpt->query('cbc:Amount', $desc), '0');
$detail->setDescuento($val);
}
}
}
private function loadPricesDetail(SaleDetail $detail, DOMXPath $xpt, DOMNode $detailNode)
{
$prices = $xpt->query('cac:PricingReference', $detailNode);
foreach ($prices as $price) {
$code = $this->defValue($xpt->query('cac:AlternativeConditionPrice/cbc:PriceTypeCode', $price));
$value = (float)$this->defValue($xpt->query('cac:AlternativeConditionPrice/cbc:PriceAmount', $price), '0');
$code = trim($code);
switch ($code) {
case '01':
$detail->setMtoPrecioUnitario($value);
break;
case '02':
$detail->setMtoValorGratuito($value);
break;
}
}
}
}