diff --git a/class/actions_autoverifactu.class.php b/class/actions_autoverifactu.class.php index 3197b13..698985d 100644 --- a/class/actions_autoverifactu.class.php +++ b/class/actions_autoverifactu.class.php @@ -228,13 +228,24 @@ public function printUnderHeaderPDFline($parameters, &$pdfhandler) { global $mysoc; + + + // 2. Si el objeto no lo tiene, lo buscamos en el request o configuración + + + + $object = $parameters['object']; + + $modelpdf = $object->model_pdf; + if ( $object->element === 'facture' && $object->status > Facture::STATUS_DRAFT && $object->type <= Facture::TYPE_DEPOSIT - && autoverifactuEnabled() + && autoverifactuEnabled() + && $modelpdf !== "Autoverifactu" ) { $pdf = &$parameters['pdf']; @@ -251,48 +262,42 @@ public function printUnderHeaderPDFline($parameters, &$pdfhandler) 'fecha' => date('d-m-Y', $object->date), 'importe' => number_format($object->total_ttc, 2, '.', ''), )); + //El código «QR» deberá tener un tamaño entre 30x30 y 40x40 milímetros y seguir las especificaciones de la norma ISO/IEC 18004:2015 + //A este respecto, se deben mantener como mínimo 2 milímetros de espacio vacío (en blanco) alrededor de los cuatro lados del código «QR», recomendándose que sean 6 milímetros. + //La presentación del código «QR» incluirá también un texto que siempre deberá ir precediéndolo: «QR tributario:», y que se situará encima del propio código «QR» + // (preferiblemente centrado con respecto a este), de manera que sirva para identificarlo y distinguirlo de otros posibles códigos «QR» que pudiera contener la factura para otros cometidos. + + + $pdf->setTopMargin($pdfhandler->tab_top -5); + $pdf->MultiCell(30, 10, 'QR tributario:', 0, 'C', 0, 1); $pdf->write2DBarcode( $base_url . $endpoint . '?' . $query, 'QRCODE,M', $pdfhandler->marge_gauche, - $pdfhandler->tab_top - 5, - 25, - 25, + $pdfhandler->tab_top-1 , + 32, + 32, array( 'border' => false, - 'padding' => 0, + 'padding' => 2, 'fgcolor' => array(25, 25, 25), - 'bgcolor' => false, + 'bgcolor' => array(255, 255, 255), //margen color blanco con padding 2mm 'module_width' => 1, 'module_height' => 1, ), - 25, + 30, ); - $pdf->setTopMargin($pdfhandler->tab_top + 21); - $pdf->MultiCell(25, 5, 'Veri*Factu', 0, 'C', 0, 1); + $pdf->setTopMargin($pdfhandler->tab_top + 32); + $pdf->MultiCell(30, 10, 'VERI*FACTU', 0, 'C', 0, 1); - $this->results = array('extra_under_address_shift' => 27); + $this->results = array('extra_under_address_shift' => 40); } return 0; } - /** - * Execute action after PDF (document) creation - * - * @param array $parameters Array of parameters - * @param CommonDocGenerator $pdfhandler PDF builder handler - * @param string $action 'add', 'update', 'view' - * @return int Return integer <0 if KO, - * =0 if OK but we want to process standard actions too, - * >0 if OK and we want to replace standard actions. - */ - public function afterPDFCreation($parameters, &$pdfhandler, &$action) - { - return 0; - } /** * Execute action on card page buttons render. If it is a facture page, diff --git a/core/modules/facture/doc/pdf_Autoverifactu.modules.php b/core/modules/facture/doc/pdf_Autoverifactu.modules.php new file mode 100644 index 0000000..3400076 --- /dev/null +++ b/core/modules/facture/doc/pdf_Autoverifactu.modules.php @@ -0,0 +1,2351 @@ + + * Copyright (C) 2005-2012 Regis Houssin + * Copyright (C) 2008 Raphael Bertrand + * Copyright (C) 2010-2014 Juanjo Menent + * Copyright (C) 2012 Christophe Battarel + * Copyright (C) 2012 Cédric Salvador + * Copyright (C) 2012-2014 Raphaël Doursenaud + * Copyright (C) 2015 Marcos García + * Copyright (C) 2017-2018 Ferran Marcet + * Copyright (C) 2018-2024 Frédéric France + * Copyright (C) 2022 Anthony Berton + * Copyright (C) 2022 Charlene Benke + * Copyright (C) 2024 MDW + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * or see https://www.gnu.org/ + */ + +/** + * \file htdocs/core/modules/facture/doc/pdf_Verifactu.modules.php + * \ingroup invoice + * \brief File of class to generate customers invoices from Verifactu model + */ + +require_once DOL_DOCUMENT_ROOT.'/core/modules/facture/modules_facture.php'; +require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/pdf.lib.php'; + +require_once dirname(__DIR__ , 4). '/lib/validation.lib.php'; +require_once dirname(__DIR__,4) . '/lib/autoverifactu.lib.php'; + + +//!importante +/* + + +7. Ubicación del QR en la factura +El QR debe ubicarse al principio de la factura, preferentemente en: + +Parte superior izquierda si la factura es en formato vertical. +Lado izquierdo superior si la factura es en formato horizontal. +Solo en la primera página, en caso de facturas con múltiples páginas. +Esta disposición está alineada con la Ley General Tributaria (artículo 29.2.j), promoviendo la transparencia y el control fiscal. + +8. Tamaño del QR en las facturas +Para garantizar la legibilidad del código QR en cualquier factura, su tamaño debe estar entre: + +30x30 mm como mínimo +40x40 mm como máximo +Este rango asegura que el QR pueda ser escaneado correctamente por cualquier dispositivo. + + */ + + + + + + +/** + * Class to generate the customer invoice PDF with template Verifactu + */ +class pdf_Autoverifactu extends ModelePDFFactures +{ + /** + * @var DoliDB Database handler + */ + public $db; + + /** + * @var string model name + */ + public $name; + + /** + * @var string model description (short text) + */ + public $description; + + /** + * @var int Save the name of generated file as the main doc when generating a doc with this template + */ + public $update_main_doc_field; + + /** + * @var string document type + */ + public $type; + + /** + * Dolibarr version of the loaded document + * @var string + */ + public $version = 'dolibarr'; + + /** + * @var bool Situation invoice type + */ + public $situationinvoice; + + /** + * @var float X position for the situation progress column + */ + public $posxprogress; + + /** + * @var int Category of operation + */ + public $categoryOfOperation = -1; // unknown by default + + + /** + * Constructor + * + * @param DoliDB $db Database handler + */ + public function __construct($db) + { + global $conf, $langs, $mysoc; + + // Translations + $langs->loadLangs(array("main", "bills")); + + $this->db = $db; + $this->name = "Autoverifactu"; + $this->description = "Factura PDF plantilla Autoverifactu. Una plantilla compatible con el QR de Verifactu"; + $this->update_main_doc_field = 1; // Save the name of generated file as the main doc when generating a doc with this template + + // Dimension page + $this->type = 'pdf'; + $formatarray = pdf_getFormat(); + $this->page_largeur = $formatarray['width']; + $this->page_hauteur = $formatarray['height']; + $this->format = array($this->page_largeur, $this->page_hauteur); + $this->marge_gauche = getDolGlobalInt('MAIN_PDF_MARGIN_LEFT', 10); + $this->marge_droite = getDolGlobalInt('MAIN_PDF_MARGIN_RIGHT', 10); + $this->marge_haute = getDolGlobalInt('MAIN_PDF_MARGIN_TOP', 10); + $this->marge_basse = getDolGlobalInt('MAIN_PDF_MARGIN_BOTTOM', 10); + + $this->option_logo = 1; // Display logo + $this->option_tva = 1; // Manage the vat option FACTURE_TVAOPTION + $this->option_modereg = 1; // Display payment mode + $this->option_condreg = 1; // Display payment terms + $this->option_multilang = 1; // Available in several languages + $this->option_escompte = 1; // Displays if there has been a discount + $this->option_credit_note = 1; // Support credit notes + $this->option_freetext = 1; // Support add of a personalised text + $this->option_draft_watermark = 1; // Support add of a watermark on drafts + $this->watermark = ''; + + // Get source company + $this->emetteur = $mysoc; + if (empty($this->emetteur->country_code)) { + $this->emetteur->country_code = substr($langs->defaultlang, -2); // By default, if was not defined + } + + // Define position of columns + $this->posxdesc = $this->marge_gauche + 1; + if (getDolGlobalInt('PRODUCT_USE_UNITS')) { + $this->posxtva = 101; + $this->posxup = 118; + $this->posxqty = 135; + $this->posxunit = 151; + } else { + $this->posxtva = 106; + $this->posxup = 122; + $this->posxqty = 145; + $this->posxunit = 162; + } + $this->posxprogress = 151; // Only displayed for situation invoices + $this->posxdiscount = 162; + $this->posxprogress = 174; + $this->postotalht = 174; + if (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT') || getDolGlobalString('MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT_COLUMN')) { + $this->posxtva = $this->posxup; + } + $this->posxpicture = $this->posxtva - getDolGlobalInt('MAIN_DOCUMENTS_WITH_PICTURE_WIDTH', 20); // width of images + if ($this->page_largeur < 210) { // To work with US executive format + $this->posxpicture -= 20; + $this->posxtva -= 20; + $this->posxup -= 20; + $this->posxqty -= 20; + $this->posxunit -= 20; + $this->posxdiscount -= 20; + $this->posxprogress -= 20; + $this->postotalht -= 20; + } + + $this->tva = array(); + $this->tva_array = array(); + $this->localtax1 = array(); + $this->localtax2 = array(); + $this->atleastoneratenotnull = 0; + $this->atleastonediscount = 0; + $this->situationinvoice = false; + } + + + // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps + /** + * Function to build pdf onto disk + * + * @param Facture $object Object to generate + * @param Translate $outputlangs Lang output object + * @param string $srctemplatepath Full path of source filename for generator using a template file + * @param int $hidedetails Do not show line details + * @param int $hidedesc Do not show desc + * @param int $hideref Do not show ref + * @return int 1=OK, 0=KO + */ + public function write_file($object, $outputlangs, $srctemplatepath = '', $hidedetails = 0, $hidedesc = 0, $hideref = 0) + { + // phpcs:enable + global $user, $langs, $conf, $mysoc, $hookmanager, $nblines; + + dol_syslog("write_file outputlangs->defaultlang=".(is_object($outputlangs) ? $outputlangs->defaultlang : 'null')); + + if (!is_object($outputlangs)) { + $outputlangs = $langs; + } + // For backward compatibility with FPDF, force output charset to ISO, because FPDF expect text to be encoded in ISO + if (getDolGlobalString('MAIN_USE_FPDF')) { + $outputlangs->charset_output = 'ISO-8859-1'; + } + + // Load translation files required by the page + $outputlangs->loadLangs(array("main", "bills", "products", "dict", "companies")); + + // Show Draft Watermark + if ($object->statut == $object::STATUS_DRAFT && (getDolGlobalString('FACTURE_DRAFT_WATERMARK'))) { + $this->watermark = getDolGlobalString('FACTURE_DRAFT_WATERMARK'); + } + + global $outputlangsbis; + $outputlangsbis = null; + if (getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE') && $outputlangs->defaultlang != getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE')) { + $outputlangsbis = new Translate('', $conf); + $outputlangsbis->setDefaultLang(getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE')); + $outputlangsbis->loadLangs(array("main", "bills", "products", "dict", "companies")); + } + + $nblines = count($object->lines); + + // Loop on each lines to detect if there is at least one image to show + $realpatharray = array(); + if (getDolGlobalString('MAIN_GENERATE_INVOICES_WITH_PICTURE')) { + for ($i = 0; $i < $nblines; $i++) { + if (empty($object->lines[$i]->fk_product)) { + continue; + } + + $objphoto = new Product($this->db); + $objphoto->fetch($object->lines[$i]->fk_product); + + $pdir = get_exdir($object->lines[$i]->fk_product, 2, 0, 0, $objphoto, 'product').$object->lines[$i]->fk_product."/photos/"; + $dir = $conf->product->dir_output.'/'.$pdir; + + $realpath = ''; + foreach ($objphoto->liste_photos($dir, 1) as $key => $obj) { + $filename = $obj['photo']; + //if ($obj['photo_vignette']) $filename='thumbs/'.$obj['photo_vignette']; + $realpath = $dir.$filename; + break; + } + + if ($realpath) { + $realpatharray[$i] = $realpath; + } + } + } + if (count($realpatharray) == 0) { + $this->posxpicture = $this->posxtva; + } + + if ($conf->facture->dir_output) { + $object->fetch_thirdparty(); + + $deja_regle = $object->getSommePaiement((isModEnabled("multicurrency") && $object->multicurrency_tx != 1) ? 1 : 0); + $amount_credit_notes_included = $object->getSumCreditNotesUsed((isModEnabled("multicurrency") && $object->multicurrency_tx != 1) ? 1 : 0); + $amount_deposits_included = $object->getSumDepositsUsed((isModEnabled("multicurrency") && $object->multicurrency_tx != 1) ? 1 : 0); + + // Definition of $dir and $file + if ($object->specimen) { + $dir = empty($conf->facture->multidir_output[$object->entity]) ? $conf->facture->dir_output : $conf->facture->multidir_output[$object->entity]; + $file = $dir."/SPECIMEN.pdf"; + } else { + $objectref = dol_sanitizeFileName($object->ref); + $dir = (empty($conf->facture->multidir_output[$object->entity]) ? $conf->facture->dir_output : $conf->facture->multidir_output[$object->entity])."/".$objectref; + $file = $dir."/".$objectref.".pdf"; + } + if (!file_exists($dir)) { + if (dol_mkdir($dir) < 0) { + $this->error = $langs->transnoentities("ErrorCanNotCreateDir", $dir); + return 0; + } + } + + if (file_exists($dir)) { + // Add pdfgeneration hook + if (!is_object($hookmanager)) { + include_once DOL_DOCUMENT_ROOT.'/core/class/hookmanager.class.php'; + $hookmanager = new HookManager($this->db); + } + $hookmanager->initHooks(array('pdfgeneration')); + $parameters = array('file' => $file, 'object' => $object, 'outputlangs' => $outputlangs); + global $action; + $reshook = $hookmanager->executeHooks('beforePDFCreation', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks + + // Set nblines with the new facture lines content after hook + $nblines = count($object->lines); + $nbpayments = count($object->getListOfPayments()); + + // Create pdf instance + $pdf = pdf_getInstance($this->format); + $default_font_size = pdf_getPDFFontSize($outputlangs); // Must be after pdf_getInstance + $pdf->SetAutoPageBreak(1, 0); + + $heightforinfotot = 50 + (4 * $nbpayments); // Height reserved to output the info and total part and payment part + if ($heightforinfotot > 220) { + $heightforinfotot = 220; + } + $heightforfreetext = (isset($conf->global->MAIN_PDF_FREETEXT_HEIGHT) ? $conf->global->MAIN_PDF_FREETEXT_HEIGHT : 5); // Height reserved to output the free text on last page + $heightforfooter = $this->marge_basse + 8; // Height reserved to output the footer (value include bottom margin) + if (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_SHOW_FOOT_DETAILS')) { + $heightforfooter += 6; + } + + $heightforqrinvoice_firstpage = 0; + if (getDolGlobalString('INVOICE_ADD_SWISS_QR_CODE') == 'bottom') { + $heightforqrinvoice_firstpage = $this->getHeightForQRInvoice(1, $object, $langs); + if ($heightforqrinvoice_firstpage > 0) { + // Shrink infotot to a base 30 + $heightforinfotot = 30 + (4 * $nbpayments); // Height reserved to output the info and total part and payment part + } + } + + if (class_exists('TCPDF')) { + $pdf->setPrintHeader(false); + $pdf->setPrintFooter(false); + } + $pdf->SetFont(pdf_getPDFFont($outputlangs)); + + // Set path to the background PDF File + if (getDolGlobalString('MAIN_ADD_PDF_BACKGROUND')) { + $logodir = $conf->mycompany->dir_output; + if (!empty($conf->mycompany->multidir_output[$object->entity])) { + $logodir = $conf->mycompany->multidir_output[$object->entity]; + } + $pagecount = $pdf->setSourceFile($logodir.'/' . getDolGlobalString('MAIN_ADD_PDF_BACKGROUND')); + $tplidx = $pdf->importPage(1); + } + + $pdf->Open(); + $pagenb = 0; + $pdf->SetDrawColor(128, 128, 128); + + $pdf->SetTitle($outputlangs->convToOutputCharset($object->ref)); + $pdf->SetSubject($outputlangs->transnoentities("PdfInvoiceTitle")); + $pdf->SetCreator("Dolibarr ".DOL_VERSION); + $pdf->SetAuthor($mysoc->name.($user->id > 0 ? ' - '.$outputlangs->convToOutputCharset($user->getFullName($outputlangs)) : '')); + $pdf->SetKeyWords($outputlangs->convToOutputCharset($object->ref)." ".$outputlangs->transnoentities("PdfInvoiceTitle")." ".$outputlangs->convToOutputCharset($object->thirdparty->name)); + if (getDolGlobalString('MAIN_DISABLE_PDF_COMPRESSION')) { + $pdf->SetCompression(false); + } + + // Set certificate + $cert = empty($user->conf->CERTIFICATE_CRT) ? '' : $user->conf->CERTIFICATE_CRT; + $certprivate = empty($user->conf->CERTIFICATE_CRT_PRIVATE) ? '' : $user->conf->CERTIFICATE_CRT_PRIVATE; + // If user has no certificate, we try to take the company one + if (!$cert) { + $cert = !getDolGlobalString('CERTIFICATE_CRT') ? '' : $conf->global->CERTIFICATE_CRT; + } + if (!$certprivate) { + $certprivate = !getDolGlobalString('CERTIFICATE_CRT_PRIVATE') ? '' : $conf->global->CERTIFICATE_CRT_PRIVATE; + } + // If a certificate is found + if ($cert) { + $info = array( + 'Name' => $this->emetteur->name, + 'Location' => getCountry($this->emetteur->country_code, 0), + 'Reason' => 'INVOICE', + 'ContactInfo' => $this->emetteur->email + ); + $pdf->setSignature($cert, $certprivate, $this->emetteur->name, '', 2, $info); + } + + // @phan-suppress-next-line PhanPluginSuspiciousParamOrder + $pdf->SetMargins($this->marge_gauche, $this->marge_haute, $this->marge_droite); // Left, Top, Right + + // Set $this->atleastonediscount if you have at least one discount + // and determine category of operation + $categoryOfOperation = 0; + $nbProduct = 0; + $nbService = 0; + for ($i = 0; $i < $nblines; $i++) { + if ($object->lines[$i]->remise_percent) { + $this->atleastonediscount++; + } + + // determine category of operation + if ($categoryOfOperation < 2) { + $lineProductType = $object->lines[$i]->product_type; + if ($lineProductType == Product::TYPE_PRODUCT) { + $nbProduct++; + } elseif ($lineProductType == Product::TYPE_SERVICE) { + $nbService++; + } + if ($nbProduct > 0 && $nbService > 0) { + // mixed products and services + $categoryOfOperation = 2; + } + } + } + // determine category of operation + if ($categoryOfOperation <= 0) { + // only services + if ($nbProduct == 0 && $nbService > 0) { + $categoryOfOperation = 1; + } + } + $this->categoryOfOperation = $categoryOfOperation; + if (empty($this->atleastonediscount)) { // retrieve space not used by discount + $delta = ($this->posxprogress - $this->posxdiscount); + $this->posxpicture += $delta; + $this->posxtva += $delta; + $this->posxup += $delta; + $this->posxqty += $delta; + $this->posxunit += $delta; + $this->posxdiscount += $delta; + // post of fields after are not modified, stay at same position + } + + $progress_width = 0; + // Situation invoice handling + if ($object->situation_cycle_ref && !getDolGlobalString('MAIN_PDF_HIDE_SITUATION')) { + $this->situationinvoice = true; + $progress_width = 10; + $this->posxpicture -= $progress_width; + $this->posxtva -= $progress_width; + $this->posxup -= $progress_width; + $this->posxqty -= $progress_width; + $this->posxunit -= $progress_width; + $this->posxdiscount -= $progress_width; + $this->posxprogress -= $progress_width; + } + + // New page + $pdf->AddPage(); + if (!empty($tplidx)) { + $pdf->useTemplate($tplidx); + } + $pagenb++; + + // Output header (logo, ref and address blocks). This is first call for first page. + $top_shift = $this->_pagehead($pdf, $object, 1, $outputlangs); + $pdf->SetFont('', '', $default_font_size - 1); + $pdf->MultiCell(0, 3, ''); // Set interline to 3 + $pdf->SetTextColor(0, 0, 0); + + // $pdf->GetY() here can't be used. It is bottom of the second address box but first one may be higher + + // $tab_top is y where we must continue content (90 = 42 + 48: 42 is height of logo and ref, 48 is address blocks) + $tab_top = 90 + $top_shift; // top_shift is an addition for linked objects or addons (0 in most cases) + $tab_top_newpage = (!getDolGlobalInt('MAIN_PDF_DONOTREPEAT_HEAD') ? 42 + $top_shift : 10); + + // You can add more thing under header here, if you increase $extra_under_address_shift too. + $extra_under_address_shift = 0; + $qrcodestring = ''; + if (getDolGlobalString('INVOICE_ADD_ZATCA_QR_CODE')) { + $qrcodestring = $object->buildZATCAQRString(); + } elseif (getDolGlobalString('INVOICE_ADD_SWISS_QR_CODE') == '1' && (empty($object->mode_reglement_code) || $object->mode_reglement_code == 'VIR')) { + if ($object->fk_account > 0 || $object->fk_bank > 0 || getDolGlobalInt('FACTURE_RIB_NUMBER')) { + $qrcodestring = $object->buildSwitzerlandQRString(); + } + } elseif (getDolGlobalString('INVOICE_ADD_EPC_QR_CODE') == '1' && (empty($object->mode_reglement_code) || $object->mode_reglement_code == 'VIR')) { + if ($object->fk_account > 0 || $object->fk_bank > 0 || getDolGlobalInt('FACTURE_RIB_NUMBER')) { + $qrcodestring = $object->buildEPCQrCodeString(); + } + } + + if ($qrcodestring) { + $qrcodecolor = array('25', '25', '25'); + // set style for QR-code + $styleQr = array( + 'border' => false, + 'padding' => 0, + 'fgcolor' => $qrcodecolor, + 'bgcolor' => false, //array(255,255,255) + 'module_width' => 1, // width of a single module in points + 'module_height' => 1 // height of a single module in points + ); + $pdf->write2DBarcode($qrcodestring, 'QRCODE,M', $this->marge_gauche, $tab_top - 5, 25, 25, $styleQr, 'N'); + + if (getDolGlobalString('INVOICE_ADD_EPC_QR_CODE') == '1' && (empty($object->mode_reglement_code) || $object->mode_reglement_code == 'VIR')) { + if ($object->fk_account > 0 || $object->fk_bank > 0 || getDolGlobalInt('FACTURE_RIB_NUMBER')) { + $pdf->SetXY($this->marge_gauche + 30, $pdf->GetY() - 15); + $pdf->SetFont('', '', $default_font_size - 4); + $pdf->MultiCell(40, 3, $langs->transnoentitiesnoconv("INVOICE_ADD_EPC_QR_CODEPay"), 0, 'L', 0); + } + } + + $extra_under_address_shift += 25; + } + + // Call hook printUnderHeaderPDFline + $parameters = array( + 'object' => $object, + 'i' => $i, + 'pdf' => &$pdf, + 'outputlangs' => $outputlangs, + 'hidedetails' => $hidedetails + ); + $reshook = $hookmanager->executeHooks('printUnderHeaderPDFline', $parameters, $this); // Note that $object may have been modified by hook + if (!empty($hookmanager->resArray['extra_under_address_shift'])) { + $extra_under_address_shift += $hookmanager->resArray['extra_under_address_shift']; + } + + $tab_top += $extra_under_address_shift; + $tab_top_newpage += 0; + + // Incoterm + $height_incoterms = 0; + if (isModEnabled('incoterm')) { + $desc_incoterms = $object->getIncotermsForPDF(); + if ($desc_incoterms) { + $tab_top -= 2; + + $pdf->SetFont('', '', $default_font_size - 1); + $pdf->writeHTMLCell(190, 3, $this->posxdesc - 1, $tab_top - 1, dol_htmlentitiesbr($desc_incoterms), 0, 1); + $nexY = $pdf->GetY(); + $height_incoterms = $nexY - $tab_top; + + // Rect takes a length in 3rd parameter + $pdf->SetDrawColor(192, 192, 192); + $pdf->Rect($this->marge_gauche, $tab_top - 1, $this->page_largeur - $this->marge_gauche - $this->marge_droite, $height_incoterms + 1); + + $tab_top = $nexY + 6; + } + } + + // Display notes + $notetoshow = empty($object->note_public) ? '' : $object->note_public; + if (getDolGlobalString('MAIN_ADD_SALE_REP_SIGNATURE_IN_NOTE')) { + // Get first sale rep + if (is_object($object->thirdparty)) { + $salereparray = $object->thirdparty->getSalesRepresentatives($user); + $salerepobj = new User($this->db); + $salerepobj->fetch($salereparray[0]['id']); + if (!empty($salerepobj->signature)) { + $notetoshow = dol_concatdesc($notetoshow, $salerepobj->signature); + } + } + } + // Extrafields in note + $extranote = $this->getExtrafieldsInHtml($object, $outputlangs); + if (!empty($extranote)) { + $notetoshow = dol_concatdesc($notetoshow, $extranote); + } + if ($notetoshow) { + $tab_top -= 2; + + $substitutionarray = pdf_getSubstitutionArray($outputlangs, null, $object); + complete_substitutions_array($substitutionarray, $outputlangs, $object); + + $notetoshow = make_substitutions($notetoshow, $substitutionarray, $outputlangs); + $notetoshow = convertBackOfficeMediasLinksToPublicLinks($notetoshow); + + $pdf->SetFont('', '', $default_font_size - 1); + $pdf->writeHTMLCell(190, 3, $this->posxdesc - 1, $tab_top - 1, dol_htmlentitiesbr($notetoshow), 0, 1); + $nexY = $pdf->GetY(); + $height_note = $nexY - $tab_top; + + // Rect takes a length in 3rd parameter + $pdf->SetDrawColor(192, 192, 192); + $pdf->Rect($this->marge_gauche, $tab_top - 1, $this->page_largeur - $this->marge_gauche - $this->marge_droite, $height_note + 1); + + $tab_top = $nexY + 6; + } + + $iniY = $tab_top + 7; + $curY = $tab_top + 7; + $nexY = $tab_top + 7; + + // Loop on each lines + for ($i = 0; $i < $nblines; $i++) { + $curY = $nexY; + $pdf->SetFont('', '', $default_font_size - 1); // Into loop to work with multipage + $pdf->SetTextColor(0, 0, 0); + + // Define size of image if we need it + $imglinesize = array(); + if (!empty($realpatharray[$i])) { + $imglinesize = pdf_getSizeForImage($realpatharray[$i]); + } + + $pdf->setTopMargin($tab_top_newpage); + $page_bottom_margin = $heightforfooter + $heightforfreetext + $heightforinfotot + $this->getHeightForQRInvoice($pdf->getPage(), $object, $langs); + $pdf->setPageOrientation('', 1, $page_bottom_margin); + $pageposbefore = $pdf->getPage(); + + $showpricebeforepagebreak = 1; + $posYAfterImage = 0; + $posYAfterDescription = 0; + + // We start with Photo of product line + if (isset($imglinesize['width']) && isset($imglinesize['height']) && ($curY + $imglinesize['height']) > ($this->page_hauteur - $page_bottom_margin)) { // If photo too high, we moved completely on new page + $pdf->AddPage('', '', true); + if (!empty($tplidx)) { + $pdf->useTemplate($tplidx); + } + if (!getDolGlobalInt('MAIN_PDF_DONOTREPEAT_HEAD')) { + $top_shift = $this->_pagehead($pdf, $object, 0, $outputlangs); + $tab_top_newpage = (!getDolGlobalInt('MAIN_PDF_DONOTREPEAT_HEAD') ? 42 + $top_shift : 10); + } + $pdf->setPage($pageposbefore + 1); + + $curY = $tab_top_newpage; + + // Allows data in the first page if description is long enough to break in multiples pages + if (getDolGlobalString('MAIN_PDF_DATA_ON_FIRST_PAGE')) { + $showpricebeforepagebreak = 1; + } else { + $showpricebeforepagebreak = 0; + } + } + + if (isset($imglinesize['width']) && isset($imglinesize['height'])) { + $curX = $this->posxpicture - 1; + $pdf->Image($realpatharray[$i], $curX + (($this->posxtva - $this->posxpicture - $imglinesize['width']) / 2), $curY, $imglinesize['width'], $imglinesize['height'], '', '', '', 2, 300); // Use 300 dpi + // $pdf->Image does not increase value return by getY, so we save it manually + $posYAfterImage = $curY + $imglinesize['height']; + } + + // Description of product line + $curX = $this->posxdesc - 1; + + $pdf->startTransaction(); + pdf_writelinedesc($pdf, $object, $i, $outputlangs, $this->posxpicture - $curX - $progress_width, 3, $curX, $curY, $hideref, $hidedesc); + $pageposafter = $pdf->getPage(); + if ($pageposafter > $pageposbefore) { // There is a pagebreak + $pdf->rollbackTransaction(true); + $pageposafter = $pageposbefore; + //print $pageposafter.'-'.$pageposbefore;exit; + $pdf->setPageOrientation('', 1, $heightforfooter); // The only function to edit the bottom margin of current page to set it. + pdf_writelinedesc($pdf, $object, $i, $outputlangs, $this->posxpicture - $curX - $progress_width, 3, $curX, $curY, $hideref, $hidedesc); + $pageposafter = $pdf->getPage(); + $posyafter = $pdf->GetY(); + //var_dump($posyafter); var_dump(($this->page_hauteur - ($heightforfooter+$heightforfreetext+$heightforinfotot))); exit; + if ($posyafter > ($this->page_hauteur - $page_bottom_margin)) { // There is no space left for total+free text + if ($i == ($nblines - 1)) { // No more lines, and no space left to show total, so we create a new page + $pdf->AddPage('', '', true); + if (!empty($tplidx)) { + $pdf->useTemplate($tplidx); + } + if (!getDolGlobalInt('MAIN_PDF_DONOTREPEAT_HEAD')) { + $top_shift = $this->_pagehead($pdf, $object, 0, $outputlangs); + $tab_top_newpage = (!getDolGlobalInt('MAIN_PDF_DONOTREPEAT_HEAD') ? 42 + $top_shift : 10); + } + $pdf->setPage($pageposafter + 1); + } + } else { + // We found a page break + + // Allows data in the first page if description is long enough to break in multiples pages + if (getDolGlobalString('MAIN_PDF_DATA_ON_FIRST_PAGE')) { + $showpricebeforepagebreak = 1; + } else { + $showpricebeforepagebreak = 0; + } + } + } else { // No pagebreak + $pdf->commitTransaction(); + } + $posYAfterDescription = $pdf->GetY(); + + $nexY = $pdf->GetY(); + $pageposafter = $pdf->getPage(); + $pdf->setPage($pageposbefore); + $pdf->setTopMargin($this->marge_haute); + $pdf->setPageOrientation('', 1, 0); // The only function to edit the bottom margin of current page to set it. + + // We suppose that a too long description or photo were moved completely on next page + if ($pageposafter > $pageposbefore && empty($showpricebeforepagebreak)) { + $pdf->setPage($pageposafter); + $curY = $tab_top_newpage; + } + + $pdf->SetFont('', '', $default_font_size - 1); // We reposition the default font + + // VAT Rate + if (!getDolGlobalString('MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT') && !getDolGlobalString('MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT_COLUMN')) { + $vat_rate = pdf_getlinevatrate($object, $i, $outputlangs, $hidedetails); + $pdf->SetXY($this->posxtva - 5, $curY); + $pdf->MultiCell($this->posxup - $this->posxtva + 4, 3, $vat_rate, 0, 'R'); + } + + // Unit price before discount + $up_excl_tax = pdf_getlineupexcltax($object, $i, $outputlangs, $hidedetails); + $pdf->SetXY($this->posxup, $curY); + $pdf->MultiCell($this->posxqty - $this->posxup - 0.8, 3, $up_excl_tax, 0, 'R', 0); + + // Quantity + $qty = pdf_getlineqty($object, $i, $outputlangs, $hidedetails); + $pdf->SetXY($this->posxqty, $curY); + $pdf->MultiCell($this->posxunit - $this->posxqty - 0.8, 4, $qty, 0, 'R'); // Enough for 6 chars + + // Unit + if (getDolGlobalInt('PRODUCT_USE_UNITS')) { + $unit = pdf_getlineunit($object, $i, $outputlangs, $hidedetails); + $pdf->SetXY($this->posxunit, $curY); + $pdf->MultiCell($this->posxdiscount - $this->posxunit - 0.8, 4, $unit, 0, 'L'); + } + + // Discount on line + if ($object->lines[$i]->remise_percent) { + $pdf->SetXY($this->posxdiscount - 2, $curY); + $remise_percent = pdf_getlineremisepercent($object, $i, $outputlangs, $hidedetails); + $pdf->MultiCell($this->posxprogress - $this->posxdiscount + 2, 3, $remise_percent, 0, 'R'); + } + + // Situation progress + if ($this->situationinvoice) { + $progress = pdf_getlineprogress($object, $i, $outputlangs, $hidedetails); + $pdf->SetXY($this->posxprogress, $curY); + $pdf->MultiCell($this->postotalht - $this->posxprogress + 1, 3, $progress, 0, 'R'); + } + + // Total HT line + $total_excl_tax = pdf_getlinetotalexcltax($object, $i, $outputlangs, $hidedetails); + $pdf->SetXY($this->postotalht, $curY); + $pdf->MultiCell($this->page_largeur - $this->marge_droite - $this->postotalht, 3, $total_excl_tax, 0, 'R', 0); + + + $sign = 1; + if (isset($object->type) && $object->type == 2 && getDolGlobalString('INVOICE_POSITIVE_CREDIT_NOTE')) { + $sign = -1; + } + // Collection of totals by value of VAT in $this->tva["taux"]=total_tva + $prev_progress = $object->lines[$i]->get_prev_progress($object->id); + if ($prev_progress > 0 && !empty($object->lines[$i]->situation_percent)) { // Compute progress from previous situation + if (isModEnabled("multicurrency") && $object->multicurrency_tx != 1) { + $tvaligne = $sign * $object->lines[$i]->multicurrency_total_tva * ($object->lines[$i]->situation_percent - $prev_progress) / $object->lines[$i]->situation_percent; + } else { + $tvaligne = $sign * $object->lines[$i]->total_tva * ($object->lines[$i]->situation_percent - $prev_progress) / $object->lines[$i]->situation_percent; + } + } else { + if (isModEnabled("multicurrency") && $object->multicurrency_tx != 1) { + $tvaligne = $sign * $object->lines[$i]->multicurrency_total_tva; + } else { + $tvaligne = $sign * $object->lines[$i]->total_tva; + } + } + + $localtax1ligne = $object->lines[$i]->total_localtax1; + $localtax2ligne = $object->lines[$i]->total_localtax2; + $localtax1_rate = $object->lines[$i]->localtax1_tx; + $localtax2_rate = $object->lines[$i]->localtax2_tx; + $localtax1_type = $object->lines[$i]->localtax1_type; + $localtax2_type = $object->lines[$i]->localtax2_type; + + // TODO remise_percent is an obsolete field for object parent + /*if ($object->remise_percent) { + $tvaligne -= ($tvaligne * $object->remise_percent) / 100; + } + if ($object->remise_percent) { + $localtax1ligne -= ($localtax1ligne * $object->remise_percent) / 100; + } + if ($object->remise_percent) { + $localtax2ligne -= ($localtax2ligne * $object->remise_percent) / 100; + }*/ + + $vatrate = (string) $object->lines[$i]->tva_tx; + + // Retrieve type from database for backward compatibility with old records + if ((!isset($localtax1_type) || $localtax1_type == '' || !isset($localtax2_type) || $localtax2_type == '') // if tax type not defined + && (!empty($localtax1_rate) || !empty($localtax2_rate))) { // and there is local tax + $localtaxtmp_array = getLocalTaxesFromRate($vatrate, 0, $object->thirdparty, $mysoc); + $localtax1_type = isset($localtaxtmp_array[0]) ? $localtaxtmp_array[0] : ''; + $localtax2_type = isset($localtaxtmp_array[2]) ? $localtaxtmp_array[2] : ''; + } + + // retrieve global local tax + if ($localtax1_type && $localtax1ligne != 0) { + if (empty($this->localtax1[$localtax1_type][$localtax1_rate])) { + $this->localtax1[$localtax1_type][$localtax1_rate] = $localtax1ligne; + } else { + $this->localtax1[$localtax1_type][$localtax1_rate] += $localtax1ligne; + } + } + if ($localtax2_type && $localtax2ligne != 0) { + if (empty($this->localtax2[$localtax2_type][$localtax2_rate])) { + $this->localtax2[$localtax2_type][$localtax2_rate] = $localtax2ligne; + } else { + $this->localtax2[$localtax2_type][$localtax2_rate] += $localtax2ligne; + } + } + + if (($object->lines[$i]->info_bits & 0x01) == 0x01) { + $vatrate .= '*'; + } + + // Fill $this->tva and $this->tva_array + if (!isset($this->tva[$vatrate])) { + $this->tva[$vatrate] = 0; + } + $this->tva[$vatrate] += $tvaligne; // ->tva is abandoned, we use now ->tva_array that is more complete + $vatcode = $object->lines[$i]->vat_src_code; + if (empty($this->tva_array[$vatrate.($vatcode ? ' ('.$vatcode.')' : '')]['amount'])) { + $this->tva_array[$vatrate.($vatcode ? ' ('.$vatcode.')' : '')]['amount'] = 0; + } + $this->tva_array[$vatrate.($vatcode ? ' ('.$vatcode.')' : '')] = array('vatrate' => $vatrate, 'vatcode' => $vatcode, 'amount' => $this->tva_array[$vatrate.($vatcode ? ' ('.$vatcode.')' : '')]['amount'] + $tvaligne); + + if ($posYAfterImage > $posYAfterDescription) { + $nexY = $posYAfterImage; + } + + // Add line + if (getDolGlobalString('MAIN_PDF_DASH_BETWEEN_LINES') && $i < ($nblines - 1)) { + $pdf->setPage($pageposafter); + $pdf->SetLineStyle(array('dash' => '1,1', 'color' => array(80, 80, 80))); + //$pdf->SetDrawColor(190,190,200); + $pdf->line($this->marge_gauche, $nexY + 1, $this->page_largeur - $this->marge_droite, $nexY + 1); + $pdf->SetLineStyle(array('dash' => 0)); + } + + $nexY += 2; // Add space between lines + + // Detect if some page were added automatically and output _tableau for past pages + while ($pagenb < $pageposafter) { + $pdf->setPage($pagenb); + if ($pagenb == 1) { + $this->_tableau($pdf, $tab_top, $this->page_hauteur - $tab_top - $heightforfooter - $heightforqrinvoice_firstpage, 0, $outputlangs, 0, 1, $object->multicurrency_code); + } else { + $this->_tableau($pdf, $tab_top_newpage, $this->page_hauteur - $tab_top_newpage - $heightforfooter, 0, $outputlangs, 1, 1, $object->multicurrency_code); + } + $this->_pagefoot($pdf, $object, $outputlangs, 1, $this->getHeightForQRInvoice($pagenb, $object, $langs)); + $pagenb++; + $pdf->setPage($pagenb); + $pdf->setPageOrientation('', 1, 0); // The only function to edit the bottom margin of current page to set it. + if (!getDolGlobalInt('MAIN_PDF_DONOTREPEAT_HEAD')) { + $top_shift = $this->_pagehead($pdf, $object, 0, $outputlangs); + $tab_top_newpage = (!getDolGlobalInt('MAIN_PDF_DONOTREPEAT_HEAD') ? 42 + $top_shift : 10); + } + if (!empty($tplidx)) { + $pdf->useTemplate($tplidx); + } + } + if (isset($object->lines[$i + 1]->pagebreak) && $object->lines[$i + 1]->pagebreak) { + if ($pagenb == 1) { + $this->_tableau($pdf, $tab_top, $this->page_hauteur - $tab_top - $heightforfooter - $heightforqrinvoice_firstpage, 0, $outputlangs, 0, 1, $object->multicurrency_code); + } else { + $this->_tableau($pdf, $tab_top_newpage, $this->page_hauteur - $tab_top_newpage - $heightforfooter, 0, $outputlangs, 1, 1, $object->multicurrency_code); + } + $this->_pagefoot($pdf, $object, $outputlangs, 1, $this->getHeightForQRInvoice($pagenb, $object, $langs)); + // New page + $pdf->AddPage(); + if (!empty($tplidx)) { + $pdf->useTemplate($tplidx); + } + $pagenb++; + if (!getDolGlobalInt('MAIN_PDF_DONOTREPEAT_HEAD')) { + $top_shift = $this->_pagehead($pdf, $object, 0, $outputlangs); + $tab_top_newpage = (!getDolGlobalInt('MAIN_PDF_DONOTREPEAT_HEAD') ? 42 + $top_shift : 10); + } + } + } + + // Show square + if ($pagenb == 1) { + $this->_tableau($pdf, $tab_top, $this->page_hauteur - $tab_top - $heightforinfotot - $heightforfreetext - $heightforfooter - $heightforqrinvoice_firstpage, 0, $outputlangs, 0, 0, $object->multicurrency_code); + $bottomlasttab = $this->page_hauteur - $heightforinfotot - $heightforfreetext - $heightforfooter - $heightforqrinvoice_firstpage + 1; + } else { + $this->_tableau($pdf, $tab_top_newpage, $this->page_hauteur - $tab_top_newpage - $heightforinfotot - $heightforfreetext - $heightforfooter, 0, $outputlangs, 1, 0, $object->multicurrency_code); + $bottomlasttab = $this->page_hauteur - $heightforinfotot - $heightforfreetext - $heightforfooter + 1; + } + dol_syslog("bottomlasttab=".$bottomlasttab." this->page_hauteur=".$this->page_hauteur." heightforinfotot=".$heightforinfotot." heightforfreetext=".$heightforfreetext." heightforfooter=".$heightforfooter); + + // Display info area + $posy = $this->_tableau_info($pdf, $object, $bottomlasttab, $outputlangs, $outputlangsbis); + + // Display total area + $posy = $this->_tableau_tot($pdf, $object, $deja_regle, $bottomlasttab, $outputlangs, $outputlangsbis); + + // Display Payments area + if (($deja_regle || $amount_credit_notes_included || $amount_deposits_included) && !getDolGlobalString('INVOICE_NO_PAYMENT_DETAILS')) { + $posy = $this->_tableau_versements($pdf, $object, $posy, $outputlangs, $heightforfooter); + } + + // Pagefoot + $this->_pagefoot($pdf, $object, $outputlangs, 0, $this->getHeightForQRInvoice($pdf->getPage(), $object, $langs)); + if (method_exists($pdf, 'AliasNbPages')) { + $pdf->AliasNbPages(); + } + + if (getDolGlobalString('INVOICE_ADD_SWISS_QR_CODE') == 'bottom') { + $result = $this->addBottomQRInvoice($pdf, $object, $outputlangs); + if (!$result) { + $pdf->Close(); + return 0; + } + } + $pdf->Close(); + + $pdf->Output($file, 'F'); + + // Add pdfgeneration hook + $hookmanager->initHooks(array('pdfgeneration')); + $parameters = array('file' => $file, 'object' => $object, 'outputlangs' => $outputlangs); + global $action; + $reshook = $hookmanager->executeHooks('afterPDFCreation', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks + if ($reshook < 0) { + $this->error = $hookmanager->error; + $this->errors = $hookmanager->errors; + } + + dolChmod($file); + + $this->result = array('fullpath' => $file); + + return 1; // No error + } else { + $this->error = $langs->transnoentities("ErrorCanNotCreateDir", $dir); + return 0; + } + } else { + $this->error = $langs->transnoentities("ErrorConstantNotDefined", "FAC_OUTPUTDIR"); + return 0; + } + } + + + // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps + // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore + /** + * Show payments table + * + * @param TCPDF $pdf Object PDF + * @param Facture $object Object invoice + * @param int $posy Position y in PDF + * @param Translate $outputlangs Object langs for output + * @param int $heightforfooter Height for footer + * @return int Return integer <0 if KO, >0 if OK + */ + protected function _tableau_versements(&$pdf, $object, $posy, $outputlangs, $heightforfooter = 0) + { + // phpcs:enable + + $sign = 1; + if ($object->type == 2 && getDolGlobalString('INVOICE_POSITIVE_CREDIT_NOTE')) { + $sign = -1; + } + + $current_page = $pdf->getPage(); + $tab3_posx = 120; + $tab3_top = $posy + 8; + $tab3_width = 80; + $tab3_height = 4; + if ($this->page_largeur < 210) { // To work with US executive format + $tab3_posx -= 15; + } + + $default_font_size = pdf_getPDFFontSize($outputlangs); + + $this->_tableau_versements_header($pdf, $object, $outputlangs, $default_font_size, $tab3_posx, $tab3_top, $tab3_width, $tab3_height); + + $y = 0; + + $pdf->SetFont('', '', $default_font_size - 4); + + + // Loop on each discount available (deposits and credit notes and excess of payment included) + $sql = "SELECT re.rowid, re.amount_ht, re.multicurrency_amount_ht, re.amount_tva, re.multicurrency_amount_tva, re.amount_ttc, re.multicurrency_amount_ttc,"; + $sql .= " re.description, re.fk_facture_source,"; + $sql .= " f.type, f.datef"; + $sql .= " FROM ".MAIN_DB_PREFIX."societe_remise_except as re, ".MAIN_DB_PREFIX."facture as f"; + $sql .= " WHERE re.fk_facture_source = f.rowid AND re.fk_facture = ".((int) $object->id); + $resql = $this->db->query($sql); + if ($resql) { + $num = $this->db->num_rows($resql); + $i = 0; + $invoice = new Facture($this->db); + while ($i < $num) { + $y += 3; + if ($tab3_top + $y >= ($this->page_hauteur - $heightforfooter)) { + $y = 0; + $current_page++; + $pdf->AddPage('', '', true); + /*if (!empty($tplidx)) { + $pdf->useTemplate($tplidx); + }*/ + if (!getDolGlobalInt('MAIN_PDF_DONOTREPEAT_HEAD')) { + $top_shift = $this->_pagehead($pdf, $object, 0, $outputlangs); + $tab_top_newpage = (!getDolGlobalInt('MAIN_PDF_DONOTREPEAT_HEAD') ? 42 + $top_shift : 10); + } + $pdf->setPage($current_page); + $this->_tableau_versements_header($pdf, $object, $outputlangs, $default_font_size, $tab3_posx, $tab3_top + $y - 3, $tab3_width, $tab3_height); + } + + $obj = $this->db->fetch_object($resql); + + if ($obj->type == 2) { + $text = $outputlangs->transnoentities("CreditNote"); + } elseif ($obj->type == 3) { + $text = $outputlangs->transnoentities("Deposit"); + } elseif ($obj->type == 0) { + $text = $outputlangs->transnoentities("ExcessReceived"); + } else { + $text = $outputlangs->transnoentities("UnknownType"); + } + + $invoice->fetch($obj->fk_facture_source); + + $pdf->SetXY($tab3_posx, $tab3_top + $y); + $pdf->MultiCell(20, 3, dol_print_date($this->db->jdate($obj->datef), 'day', false, $outputlangs, true), 0, 'L', 0); + $pdf->SetXY($tab3_posx + 21, $tab3_top + $y); + $pdf->MultiCell(20, 3, price((isModEnabled("multicurrency") && $object->multicurrency_tx != 1) ? $obj->multicurrency_amount_ttc : $obj->amount_ttc, 0, $outputlangs), 0, 'L', 0); + $pdf->SetXY($tab3_posx + 40, $tab3_top + $y); + $pdf->MultiCell(20, 3, $text, 0, 'L', 0); + $pdf->SetXY($tab3_posx + 58, $tab3_top + $y); + $pdf->MultiCell(20, 3, $invoice->ref, 0, 'L', 0); + + $pdf->line($tab3_posx, $tab3_top + $y + 3, $tab3_posx + $tab3_width, $tab3_top + $y + 3); + + $i++; + } + } else { + $this->error = $this->db->lasterror(); + return -1; + } + + // Loop on each payment + // TODO Call getListOfPaymentsgetListOfPayments instead of hard coded sql + $sql = "SELECT p.datep as date, p.fk_paiement, p.num_paiement as num, pf.amount as amount, pf.multicurrency_amount,"; + $sql .= " cp.code"; + $sql .= " FROM ".MAIN_DB_PREFIX."paiement_facture as pf, ".MAIN_DB_PREFIX."paiement as p"; + $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_paiement as cp ON p.fk_paiement = cp.id"; + $sql .= " WHERE pf.fk_paiement = p.rowid AND pf.fk_facture = ".((int) $object->id); + //$sql.= " WHERE pf.fk_paiement = p.rowid AND pf.fk_facture = 1"; + $sql .= " ORDER BY p.datep"; + + $resql = $this->db->query($sql); + if ($resql) { + $num = $this->db->num_rows($resql); + $i = 0; + $y += 3; + $maxY = $y; + while ($i < $num) { + if ($tab3_top + $y >= ($this->page_hauteur - $heightforfooter)) { + $y = 0; + $current_page++; + $pdf->AddPage('', '', true); + /*if (!empty($tplidx)) { + $pdf->useTemplate($tplidx); + }*/ + if (!getDolGlobalInt('MAIN_PDF_DONOTREPEAT_HEAD')) { + $top_shift = $this->_pagehead($pdf, $object, 0, $outputlangs); + $tab_top_newpage = (!getDolGlobalInt('MAIN_PDF_DONOTREPEAT_HEAD') ? 42 + $top_shift : 10); + } + $pdf->setPage($current_page); + $this->_tableau_versements_header($pdf, $object, $outputlangs, $default_font_size, $tab3_posx, $tab3_top + $y - 3, $tab3_width, $tab3_height); + } + + $row = $this->db->fetch_object($resql); + + $pdf->SetXY($tab3_posx, $tab3_top + $y); + $pdf->MultiCell(20, 3, dol_print_date($this->db->jdate($row->date), 'day', false, $outputlangs, true), 0, 'L', 0); + $pdf->SetXY($tab3_posx + 21, $tab3_top + $y); + $pdf->MultiCell(20, 3, price($sign * ((isModEnabled("multicurrency") && $object->multicurrency_tx != 1) ? $row->multicurrency_amount : $row->amount), 0, $outputlangs), 0, 'L', 0); + $pdf->SetXY($tab3_posx + 40, $tab3_top + $y); + $oper = $outputlangs->transnoentitiesnoconv("PaymentTypeShort".$row->code); + + $pdf->MultiCell(20, 3, $oper, 0, 'L', 0); + $maxY = max($pdf->GetY() - $tab3_top - 3, $maxY); + $pdf->SetXY($tab3_posx + 58, $tab3_top + $y); + $pdf->MultiCell(30, 3, $row->num, 0, 'L', 0); + $y = $maxY = max($pdf->GetY() - $tab3_top - 3, $maxY); + $pdf->line($tab3_posx, $tab3_top + $y + 3, $tab3_posx + $tab3_width, $tab3_top + $y + 3); + $y += 3; + $i++; + } + + return $tab3_top + $y + 3; + } else { + $this->error = $this->db->lasterror(); + return -1; + } + } + + // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps + // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore + /** + * Function _tableau_versements_header + * + * @param TCPDF $pdf Object PDF + * @param Facture $object Object invoice + * @param Translate $outputlangs Object langs for output + * @param int $default_font_size Font size + * @param int $tab3_posx pos x + * @param int $tab3_top pos y + * @param int $tab3_width width + * @param int $tab3_height height + * @return void + */ + protected function _tableau_versements_header($pdf, $object, $outputlangs, $default_font_size, $tab3_posx, $tab3_top, $tab3_width, $tab3_height) + { + // phpcs:enable + $title = $outputlangs->transnoentities("PaymentsAlreadyDone"); + if ($object->type == 2) { + $title = $outputlangs->transnoentities("PaymentsBackAlreadyDone"); + } + + $pdf->SetFont('', '', $default_font_size - 3); + $pdf->SetXY($tab3_posx, $tab3_top - 4); + $pdf->MultiCell(60, 3, $title, 0, 'L', 0); + + $pdf->line($tab3_posx, $tab3_top, $tab3_posx + $tab3_width, $tab3_top); + + $pdf->SetFont('', '', $default_font_size - 4); + $pdf->SetXY($tab3_posx, $tab3_top); + $pdf->MultiCell(20, 3, $outputlangs->transnoentities("Payment"), 0, 'L', 0); + $pdf->SetXY($tab3_posx + 21, $tab3_top); + $pdf->MultiCell(20, 3, $outputlangs->transnoentities("Amount"), 0, 'L', 0); + $pdf->SetXY($tab3_posx + 40, $tab3_top); + $pdf->MultiCell(20, 3, $outputlangs->transnoentities("Type"), 0, 'L', 0); + $pdf->SetXY($tab3_posx + 58, $tab3_top); + $pdf->MultiCell(20, 3, $outputlangs->transnoentities("Num"), 0, 'L', 0); + + $pdf->line($tab3_posx, $tab3_top - 1 + $tab3_height, $tab3_posx + $tab3_width, $tab3_top - 1 + $tab3_height); + } + + // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps + // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore + /** + * Show miscellaneous information (payment mode, payment term, ...) + * + * @param TCPDF $pdf Object PDF + * @param Facture $object Object to show + * @param int $posy Y + * @param Translate $outputlangs Langs object + * @param Translate $outputlangsbis Object lang for output bis + * @return int Pos y + */ + protected function _tableau_info(&$pdf, $object, $posy, $outputlangs, $outputlangsbis) + { + // phpcs:enable + global $conf, $mysoc, $hookmanager; + + $default_font_size = pdf_getPDFFontSize($outputlangs); + + $pdf->SetFont('', '', $default_font_size - 1); + + // If France, show VAT mention if not applicable + if ($this->emetteur->country_code == 'FR' && empty($mysoc->tva_assuj)) { + $pdf->SetFont('', 'B', $default_font_size - 2); + $pdf->SetXY($this->marge_gauche, $posy); + if ($mysoc->forme_juridique_code == 92) { + $pdf->MultiCell(100, 3, $outputlangs->transnoentities("VATIsNotUsedForInvoiceAsso"), 0, 'L', 0); + } else { + $pdf->MultiCell(100, 3, $outputlangs->transnoentities("VATIsNotUsedForInvoice"), 0, 'L', 0); + } + + $posy = $pdf->GetY() + 4; + } + + $posxval = 52; + $posxend = 110; // End of x for text on left side + if ($this->page_largeur < 210) { // To work with US executive format + $posxend -= 10; + } + + // Show payments conditions + if ($object->type != 2 && ($object->cond_reglement_code || $object->cond_reglement)) { + $pdf->SetFont('', 'B', $default_font_size - 2); + $pdf->SetXY($this->marge_gauche, $posy); + $titre = $outputlangs->transnoentities("PaymentConditions").':'; + $pdf->MultiCell(43, 4, $titre, 0, 'L'); + + $pdf->SetFont('', '', $default_font_size - 2); + $pdf->SetXY($posxval, $posy); + $lib_condition_paiement = ($outputlangs->transnoentities("PaymentCondition".$object->cond_reglement_code) != 'PaymentCondition'.$object->cond_reglement_code) ? $outputlangs->transnoentities("PaymentCondition".$object->cond_reglement_code) : $outputlangs->convToOutputCharset($object->cond_reglement_doc ? $object->cond_reglement_doc : $object->cond_reglement_label); + $lib_condition_paiement = str_replace('\n', "\n", $lib_condition_paiement); + $pdf->MultiCell(67, 4, $lib_condition_paiement, 0, 'L'); + + $posy = $pdf->GetY() + 3; // We need spaces for 2 lines payment conditions + } + + // Show category of operations + if (getDolGlobalInt('INVOICE_CATEGORY_OF_OPERATION') == 2 && $this->categoryOfOperation >= 0) { + $pdf->SetFont('', 'B', $default_font_size - 2); + $pdf->SetXY($this->marge_gauche, $posy); + $categoryOfOperationTitle = $outputlangs->transnoentities("MentionCategoryOfOperations").' : '; + $pdf->MultiCell($posxval - $this->marge_gauche, 4, $categoryOfOperationTitle, 0, 'L'); + + $pdf->SetFont('', '', $default_font_size - 2); + $pdf->SetXY($posxval, $posy); + $categoryOfOperationLabel = $outputlangs->transnoentities("MentionCategoryOfOperations" . $this->categoryOfOperation); + $pdf->MultiCell($posxend - $posxval, 4, $categoryOfOperationLabel, 0, 'L'); + + $posy = $pdf->GetY() + 3; // for 2 lines + } + + if ($object->type != 2) { + // Check a payment mode is defined + if (empty($object->mode_reglement_code) + && !getDolGlobalInt('FACTURE_CHQ_NUMBER') + && !getDolGlobalInt('FACTURE_RIB_NUMBER')) { + $this->error = $outputlangs->transnoentities("ErrorNoPaiementModeConfigured"); + } elseif (($object->mode_reglement_code == 'CHQ' && !getDolGlobalInt('FACTURE_CHQ_NUMBER') && empty($object->fk_account) && empty($object->fk_bank)) + || ($object->mode_reglement_code == 'VIR' && !getDolGlobalInt('FACTURE_RIB_NUMBER') && empty($object->fk_account) && empty($object->fk_bank))) { + // Avoid having any valid PDF with setup that is not complete + $outputlangs->load("errors"); + + $pdf->SetXY($this->marge_gauche, $posy); + $pdf->SetTextColor(200, 0, 0); + $pdf->SetFont('', 'B', $default_font_size - 2); + $this->error = $outputlangs->transnoentities("ErrorPaymentModeDefinedToWithoutSetup", $object->mode_reglement_code); + $pdf->MultiCell(80, 3, $this->error, 0, 'L', 0); + $pdf->SetTextColor(0, 0, 0); + + $posy = $pdf->GetY() + 1; + } + + // Show payment mode + if (!empty($object->mode_reglement_code) + && $object->mode_reglement_code != 'CHQ' + && $object->mode_reglement_code != 'VIR') { + $pdf->SetFont('', 'B', $default_font_size - 2); + $pdf->SetXY($this->marge_gauche, $posy); + $titre = $outputlangs->transnoentities("PaymentMode").':'; + $pdf->MultiCell(80, 5, $titre, 0, 'L'); + + $pdf->SetFont('', '', $default_font_size - 2); + $pdf->SetXY($posxval, $posy); + $lib_mode_reg = $outputlangs->transnoentities("PaymentType".$object->mode_reglement_code) != 'PaymentType'.$object->mode_reglement_code ? $outputlangs->transnoentities("PaymentType".$object->mode_reglement_code) : $outputlangs->convToOutputCharset($object->mode_reglement); + //#21654: add account number used for the debit + if ($object->mode_reglement_code == "PRE") { + require_once DOL_DOCUMENT_ROOT.'/societe/class/companybankaccount.class.php'; + $bac = new CompanyBankAccount($this->db); + // @phan-suppress-next-line PhanPluginSuspiciousParamPosition + $bac->fetch(0, '', $object->thirdparty->id); + $iban = $bac->iban.(($bac->iban && $bac->bic) ? ' / ' : '').$bac->bic; + $lib_mode_reg .= ' '.$outputlangs->trans("PaymentTypePREdetails", dol_trunc($iban, 6, 'right', 'UTF-8', 1)); + } + $pdf->MultiCell(80, 5, $lib_mode_reg, 0, 'L'); + + $posy = $pdf->GetY(); + } + + // Show if Option VAT debit option is on also if transmitter is french + // Decret n°2099-1299 2022-10-07 + // French mention : "Option pour le paiement de la taxe d'après les débits" + if ($this->emetteur->country_code == 'FR') { + if (getDolGlobalInt('TAX_MODE') == 1) { + $pdf->SetXY($this->marge_gauche, $posy); + $pdf->writeHTMLCell(80, 5, '', '', $outputlangs->transnoentities("MentionVATDebitOptionIsOn"), 0, 1); + + $posy = $pdf->GetY() + 1; + } + } + + // Show online payment link + if (empty($object->mode_reglement_code) || $object->mode_reglement_code == 'CB' || $object->mode_reglement_code == 'VAD') { + $useonlinepayment = 0; + if (getDolGlobalString('PDF_SHOW_LINK_TO_ONLINE_PAYMENT')) { + // Show online payment link + // The list can be complete by the hook 'doValidatePayment' executed inside getValidOnlinePaymentMethods() + include_once DOL_DOCUMENT_ROOT.'/core/lib/payments.lib.php'; + $validpaymentmethod = getValidOnlinePaymentMethods(''); + $useonlinepayment = count($validpaymentmethod); + } + + if ($object->statut != Facture::STATUS_DRAFT && $useonlinepayment) { + require_once DOL_DOCUMENT_ROOT.'/core/lib/payments.lib.php'; + global $langs; + + $langs->loadLangs(array('payment', 'paybox', 'stripe')); + $servicename = $langs->transnoentities('Online'); + $paiement_url = getOnlinePaymentUrl('', 'invoice', $object->ref, '', '', ''); + $linktopay = $langs->trans("ToOfferALinkForOnlinePayment", $servicename).' '.$outputlangs->transnoentities("ClickHere").''; + + $pdf->SetXY($this->marge_gauche, $posy); + $pdf->writeHTMLCell(80, 5, '', '', dol_htmlentitiesbr($linktopay), 0, 1); + + $posy = $pdf->GetY() + 1; + } + } + + // Show payment mode CHQ + if (empty($object->mode_reglement_code) || $object->mode_reglement_code == 'CHQ') { + // If payment mode unregulated or payment mode forced to CHQ + if (getDolGlobalInt('FACTURE_CHQ_NUMBER')) { + $diffsizetitle = (!getDolGlobalString('PDF_DIFFSIZE_TITLE') ? 3 : $conf->global->PDF_DIFFSIZE_TITLE); + + if (getDolGlobalInt('FACTURE_CHQ_NUMBER') > 0) { + $account = new Account($this->db); + $account->fetch(getDolGlobalInt('FACTURE_CHQ_NUMBER')); + + $pdf->SetXY($this->marge_gauche, $posy); + $pdf->SetFont('', 'B', $default_font_size - $diffsizetitle); + $pdf->MultiCell(100, 3, $outputlangs->transnoentities('PaymentByChequeOrderedTo', $account->owner_name), 0, 'L', 0); + $posy = $pdf->GetY() + 1; + + if (!getDolGlobalString('MAIN_PDF_HIDE_CHQ_ADDRESS')) { + $pdf->SetXY($this->marge_gauche, $posy); + $pdf->SetFont('', '', $default_font_size - $diffsizetitle); + $pdf->MultiCell(100, 3, $outputlangs->convToOutputCharset($account->owner_address), 0, 'L', 0); + $posy = $pdf->GetY() + 2; + } + } + if ($conf->global->FACTURE_CHQ_NUMBER == -1) { + $pdf->SetXY($this->marge_gauche, $posy); + $pdf->SetFont('', 'B', $default_font_size - $diffsizetitle); + $pdf->MultiCell(100, 3, $outputlangs->transnoentities('PaymentByChequeOrderedTo', $this->emetteur->name), 0, 'L', 0); + $posy = $pdf->GetY() + 1; + + if (!getDolGlobalString('MAIN_PDF_HIDE_CHQ_ADDRESS')) { + $pdf->SetXY($this->marge_gauche, $posy); + $pdf->SetFont('', '', $default_font_size - $diffsizetitle); + $pdf->MultiCell(100, 3, $outputlangs->convToOutputCharset($this->emetteur->getFullAddress()), 0, 'L', 0); + $posy = $pdf->GetY() + 2; + } + } + } + } + + // If payment mode not forced or forced to VIR, show payment with BAN + if (empty($object->mode_reglement_code) || $object->mode_reglement_code == 'VIR') { + if ($object->fk_account > 0 || $object->fk_bank > 0 || getDolGlobalInt('FACTURE_RIB_NUMBER')) { + $bankid = ($object->fk_account <= 0 ? $conf->global->FACTURE_RIB_NUMBER : $object->fk_account); + if ($object->fk_bank > 0) { + $bankid = $object->fk_bank; // For backward compatibility when object->fk_account is forced with object->fk_bank + } + $account = new Account($this->db); + $account->fetch($bankid); + + $curx = $this->marge_gauche; + $cury = $posy; + + $posy = pdf_bank($pdf, $outputlangs, $curx, $cury, $account, 0, $default_font_size); + + $posy += 2; + } + } + } + + return $posy; + } + + + // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps + // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore + /** + * Show total to pay + * + * @param TCPDF $pdf Object PDF + * @param Facture $object Object invoice + * @param int $deja_regle Amount already paid (in the currency of invoice) + * @param int $posy Position depart + * @param Translate $outputlangs Object langs + * @param Translate $outputlangsbis Object lang for output bis + * @return int Position pour suite + */ + protected function _tableau_tot(&$pdf, $object, $deja_regle, $posy, $outputlangs, $outputlangsbis) + { + // phpcs:enable + global $conf, $mysoc, $hookmanager; + + $sign = 1; + if ($object->type == 2 && getDolGlobalString('INVOICE_POSITIVE_CREDIT_NOTE')) { + $sign = -1; + } + + $default_font_size = pdf_getPDFFontSize($outputlangs); + + $outputlangsbis = null; + if (getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE') && $outputlangs->defaultlang != getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE')) { + $outputlangsbis = new Translate('', $conf); + $outputlangsbis->setDefaultLang(getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE')); + $outputlangsbis->loadLangs(array("main", "dict", "companies", "bills", "products", "propal")); + $default_font_size--; + } + + $tab2_top = $posy; + $tab2_hl = 4; + $pdf->SetFont('', '', $default_font_size - 1); + + // Total table + $col1x = 120; + $col2x = 170; + if ($this->page_largeur < 210) { // To work with US executive format + $col1x -= 15; + $col2x -= 10; + } + $largcol2 = ($this->page_largeur - $this->marge_droite - $col2x); + + $useborder = 0; + $index = 0; + + // Total HT + $pdf->SetFillColor(255, 255, 255); + $pdf->SetXY($col1x, $tab2_top); + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities(!getDolGlobalString('MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT') ? "TotalHT" : "Total").(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transnoentities(!getDolGlobalString('MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT') ? "TotalHT" : "Total") : ''), 0, 'L', 1); + + $total_ht = ((isModEnabled("multicurrency") && isset($object->multicurrency_tx) && $object->multicurrency_tx != 1) ? $object->multicurrency_total_ht : $object->total_ht); + $pdf->SetXY($col2x, $tab2_top); + $pdf->MultiCell($largcol2, $tab2_hl, price($sign * ($total_ht + (!empty($object->remise) ? $object->remise : 0)), 0, $outputlangs), 0, 'R', 1); + + // Show VAT by rates and total + $pdf->SetFillColor(248, 248, 248); + + $total_ttc = (isModEnabled("multicurrency") && $object->multicurrency_tx != 1) ? $object->multicurrency_total_ttc : $object->total_ttc; + + $this->atleastoneratenotnull = 0; + if (!getDolGlobalString('MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT')) { + $tvaisnull = ((!empty($this->tva) && count($this->tva) == 1 && isset($this->tva['0.000']) && is_float($this->tva['0.000'])) ? true : false); + if (getDolGlobalString('MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT_IFNULL') && $tvaisnull) { + // Nothing to do + } else { + //Local tax 1 before VAT + foreach ($this->localtax1 as $localtax_type => $localtax_rate) { + if (in_array((string) $localtax_type, array('1', '3', '5'))) { + continue; + } + + foreach ($localtax_rate as $tvakey => $tvaval) { + if ($tvakey != 0) { // On affiche pas taux 0 + //$this->atleastoneratenotnull++; + + $index++; + $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index); + + $tvacompl = ''; + if (preg_match('/\*/', (string) $tvakey)) { + $tvakey = str_replace('*', '', (string) $tvakey); + $tvacompl = " (".$outputlangs->transnoentities("NonPercuRecuperable").")"; + } + + $totalvat = $outputlangs->transcountrynoentities("TotalLT1", $mysoc->country_code).(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transcountrynoentities("TotalLT1", $mysoc->country_code) : ''); + $totalvat .= ' '; + + if (getDolGlobalString('PDF_LOCALTAX1_LABEL_IS_CODE_OR_RATE') == 'nocodenorate') { + $totalvat .= $tvacompl; + } else { + $totalvat .= vatrate(abs($tvakey), 1).$tvacompl; + } + + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $totalvat, 0, 'L', 1); + + $total_localtax = ((isModEnabled("multicurrency") && isset($object->multicurrency_tx) && $object->multicurrency_tx != 1) ? price2num($tvaval * $object->multicurrency_tx, 'MT') : $tvaval); + + $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($largcol2, $tab2_hl, price($total_localtax, 0, $outputlangs), 0, 'R', 1); + } + } + } + + //Local tax 2 before VAT + foreach ($this->localtax2 as $localtax_type => $localtax_rate) { + if (in_array((string) $localtax_type, array('1', '3', '5'))) { + continue; + } + + foreach ($localtax_rate as $tvakey => $tvaval) { + if ($tvakey != 0) { // On affiche pas taux 0 + //$this->atleastoneratenotnull++; + + $index++; + $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index); + + $tvacompl = ''; + if (preg_match('/\*/', (string) $tvakey)) { + $tvakey = str_replace('*', '', (string) $tvakey); + $tvacompl = " (".$outputlangs->transnoentities("NonPercuRecuperable").")"; + } + $totalvat = $outputlangs->transcountrynoentities("TotalLT2", $mysoc->country_code).(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transcountrynoentities("TotalLT2", $mysoc->country_code) : ''); + $totalvat .= ' '; + + if (getDolGlobalString('PDF_LOCALTAX2_LABEL_IS_CODE_OR_RATE') == 'nocodenorate') { + $totalvat .= $tvacompl; + } else { + $totalvat .= vatrate(abs($tvakey), 1).$tvacompl; + } + + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $totalvat, 0, 'L', 1); + + $total_localtax = ((isModEnabled("multicurrency") && isset($object->multicurrency_tx) && $object->multicurrency_tx != 1) ? price2num($tvaval * $object->multicurrency_tx, 'MT') : $tvaval); + + $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($largcol2, $tab2_hl, price($total_localtax, 0, $outputlangs), 0, 'R', 1); + } + } + } + + // VAT + foreach ($this->tva_array as $tvakey => $tvaval) { + if ($tvakey != 0) { // On affiche pas taux 0 + $this->atleastoneratenotnull++; + + $index++; + $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index); + + $tvacompl = ''; + if (preg_match('/\*/', $tvakey)) { + $tvakey = str_replace('*', '', $tvakey); + $tvacompl = " (".$outputlangs->transnoentities("NonPercuRecuperable").")"; + } + $totalvat = $outputlangs->transcountrynoentities("TotalVAT", $mysoc->country_code).(is_object($outputlangsbis) ? ' / '.$outputlangsbis->transcountrynoentities("TotalVAT", $mysoc->country_code) : ''); + $totalvat .= ' '; + if (getDolGlobalString('PDF_VAT_LABEL_IS_CODE_OR_RATE') == 'rateonly') { + $totalvat .= vatrate($tvaval['vatrate'], 1).$tvacompl; + } elseif (getDolGlobalString('PDF_VAT_LABEL_IS_CODE_OR_RATE') == 'codeonly') { + $totalvat .= $tvaval['vatcode'].$tvacompl; + } elseif (getDolGlobalString('PDF_VAT_LABEL_IS_CODE_OR_RATE') == 'nocodenorate') { + $totalvat .= $tvacompl; + } else { + $totalvat .= vatrate($tvaval['vatrate'], 1).($tvaval['vatcode'] ? ' ('.$tvaval['vatcode'].')' : '').$tvacompl; + } + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $totalvat, 0, 'L', 1); + + $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($largcol2, $tab2_hl, price(price2num($tvaval['amount'], 'MT'), 0, $outputlangs), 0, 'R', 1); + } + } + + //Local tax 1 after VAT + foreach ($this->localtax1 as $localtax_type => $localtax_rate) { + if (in_array((string) $localtax_type, array('2', '4', '6'))) { + continue; + } + + foreach ($localtax_rate as $tvakey => $tvaval) { + if ($tvakey != 0) { // On affiche pas taux 0 + //$this->atleastoneratenotnull++; + + $index++; + $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index); + + $tvacompl = ''; + if (preg_match('/\*/', (string) $tvakey)) { + $tvakey = str_replace('*', '', (string) $tvakey); + $tvacompl = " (".$outputlangs->transnoentities("NonPercuRecuperable").")"; + } + $totalvat = $outputlangs->transcountrynoentities("TotalLT1", $mysoc->country_code).' '; + + if (getDolGlobalString('PDF_LOCALTAX1_LABEL_IS_CODE_OR_RATE') == 'nocodenorate') { + $totalvat .= $tvacompl; + } else { + $totalvat .= vatrate(abs($tvakey), 1).$tvacompl; + } + + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $totalvat, 0, 'L', 1); + + $total_localtax = ((isModEnabled("multicurrency") && isset($object->multicurrency_tx) && $object->multicurrency_tx != 1) ? price2num($tvaval * $object->multicurrency_tx, 'MT') : $tvaval); + + $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($largcol2, $tab2_hl, price($total_localtax, 0, $outputlangs), 0, 'R', 1); + } + } + } + + //Local tax 2 after VAT + foreach ($this->localtax2 as $localtax_type => $localtax_rate) { + if (in_array((string) $localtax_type, array('2', '4', '6'))) { + continue; + } + + foreach ($localtax_rate as $tvakey => $tvaval) { + //$this->atleastoneratenotnull++; + + $index++; + $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index); + + $tvacompl = ''; + if (preg_match('/\*/', (string) $tvakey)) { + $tvakey = str_replace('*', '', (string) $tvakey); + $tvacompl = " (".$outputlangs->transnoentities("NonPercuRecuperable").")"; + } + $totalvat = $outputlangs->transcountrynoentities("TotalLT2", $mysoc->country_code).' '; + + if (getDolGlobalString('PDF_LOCALTAX2_LABEL_IS_CODE_OR_RATE') == 'nocodenorate') { + $totalvat .= $tvacompl; + } else { + $totalvat .= vatrate(abs($tvakey), 1).$tvacompl; + } + + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $totalvat, 0, 'L', 1); + + $total_localtax = ((isModEnabled("multicurrency") && isset($object->multicurrency_tx) && $object->multicurrency_tx != 1) ? price2num($tvaval * $object->multicurrency_tx, 'MT') : $tvaval); + + $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($largcol2, $tab2_hl, price($total_localtax, 0, $outputlangs), 0, 'R', 1); + } + } + + // Revenue stamp + if (price2num($object->revenuestamp, 'MT') != 0) { + $index++; + $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities("RevenueStamp"), $useborder, 'L', 1); + + $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($largcol2, $tab2_hl, price($sign * $object->revenuestamp), $useborder, 'R', 1); + } + + // Total TTC + $index++; + $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index); + $pdf->SetTextColor(0, 0, 60); + $pdf->SetFillColor(224, 224, 224); + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities("TotalTTC"), $useborder, 'L', 1); + + $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($largcol2, $tab2_hl, price($sign * $total_ttc, 0, $outputlangs), $useborder, 'R', 1); + + // Retained warranty + if ($object->displayRetainedWarranty()) { + $pdf->SetTextColor(40, 40, 40); + $pdf->SetFillColor(255, 255, 255); + + $retainedWarranty = $object->getRetainedWarrantyAmount(); + $billedWithRetainedWarranty = $object->total_ttc - $retainedWarranty; + + // Billed - retained warranty + $index++; + $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities("ToPayOn", dol_print_date($object->date_lim_reglement, 'day')), $useborder, 'L', 1); + + $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($largcol2, $tab2_hl, price($billedWithRetainedWarranty), $useborder, 'R', 1); + + // retained warranty + $index++; + $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index); + + $retainedWarrantyToPayOn = $outputlangs->transnoentities("RetainedWarranty").' ('.$object->retained_warranty.'%)'; + $retainedWarrantyToPayOn .= !empty($object->retained_warranty_date_limit) ? ' '.$outputlangs->transnoentities("toPayOn", dol_print_date($object->retained_warranty_date_limit, 'day')) : ''; + + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $retainedWarrantyToPayOn, $useborder, 'L', 1); + $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($largcol2, $tab2_hl, price($retainedWarranty), $useborder, 'R', 1); + } + } + } + + $pdf->SetTextColor(0, 0, 0); + $creditnoteamount = $object->getSumCreditNotesUsed((isModEnabled("multicurrency") && $object->multicurrency_tx != 1) ? 1 : 0); // Warning, this also include excess received + $depositsamount = $object->getSumDepositsUsed((isModEnabled("multicurrency") && $object->multicurrency_tx != 1) ? 1 : 0); + //print "x".$creditnoteamount."-".$depositsamount;exit; + $resteapayer = price2num($total_ttc - $deja_regle - $creditnoteamount - $depositsamount, 'MT'); + if (!empty($object->paye)) { + $resteapayer = 0; + } + + if (($deja_regle > 0 || $creditnoteamount > 0 || $depositsamount > 0) && !getDolGlobalString('INVOICE_NO_PAYMENT_DETAILS')) { + // Already paid + Deposits + $index++; + $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities("Paid"), 0, 'L', 0); + $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($largcol2, $tab2_hl, price($deja_regle + $depositsamount, 0, $outputlangs), 0, 'R', 0); + + // Credit note + if ($creditnoteamount) { + $labeltouse = ($outputlangs->transnoentities("CreditNotesOrExcessReceived") != "CreditNotesOrExcessReceived") ? $outputlangs->transnoentities("CreditNotesOrExcessReceived") : $outputlangs->transnoentities("CreditNotes"); + $index++; + $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $labeltouse, 0, 'L', 0); + $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($largcol2, $tab2_hl, price($creditnoteamount, 0, $outputlangs), 0, 'R', 0); + } + + // Escompte + if ($object->close_code == Facture::CLOSECODE_DISCOUNTVAT) { + $index++; + $pdf->SetFillColor(255, 255, 255); + + $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities("EscompteOfferedShort"), $useborder, 'L', 1); + $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($largcol2, $tab2_hl, price($object->total_ttc - $deja_regle - $creditnoteamount - $depositsamount, 0, $outputlangs), $useborder, 'R', 1); + + $resteapayer = 0; + } + + $index++; + $pdf->SetTextColor(0, 0, 60); + $pdf->SetFillColor(224, 224, 224); + $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities("RemainderToPay"), $useborder, 'L', 1); + $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($largcol2, $tab2_hl, price($resteapayer, 0, $outputlangs), $useborder, 'R', 1); + + $pdf->SetFont('', '', $default_font_size - 1); + $pdf->SetTextColor(0, 0, 0); + } + + $index++; + + if (getDolGlobalString("BILL_TEXT_TOTAL_FOOTER")) { + $index++; + $index++; + $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index); + $pdf->MultiCell($col2x - $col1x, $tab2_hl, $conf->global->BILL_TEXT_TOTAL_FOOTER, 0, 'L', 0); + } + + return ($tab2_top + ($tab2_hl * $index)); + } + + // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore + /** + * Show table for lines + * + * @param TCPDF $pdf Object PDF + * @param float|int $tab_top Top position of table + * @param float|int $tab_height Height of table (rectangle) + * @param int $nexY Y (not used) + * @param Translate $outputlangs Langs object + * @param int $hidetop 1=Hide top bar of array and title, 0=Hide nothing, -1=Hide only title + * @param int $hidebottom Hide bottom bar of array + * @param string $currency Currency code + * @return void + */ + protected function _tableau(&$pdf, $tab_top, $tab_height, $nexY, $outputlangs, $hidetop = 0, $hidebottom = 0, $currency = '') + { + global $conf; + + // Force to disable hidetop and hidebottom + $hidebottom = 0; + if ($hidetop) { + $hidetop = -1; + } + + $currency = !empty($currency) ? $currency : $conf->currency; + $default_font_size = pdf_getPDFFontSize($outputlangs); + + // Amount in (at tab_top - 1) + $pdf->SetTextColor(0, 0, 0); + $pdf->SetFont('', '', $default_font_size - 2); + + if (empty($hidetop)) { + // Show category of operations + if (getDolGlobalInt('INVOICE_CATEGORY_OF_OPERATION') == 1 && $this->categoryOfOperation >= 0) { + $categoryOfOperations = $outputlangs->transnoentities("MentionCategoryOfOperations") . ' : ' . $outputlangs->transnoentities("MentionCategoryOfOperations" . $this->categoryOfOperation); + $pdf->SetXY($this->marge_gauche, $tab_top - 4); + $pdf->MultiCell(($pdf->GetStringWidth($categoryOfOperations)) + 4, 2, $categoryOfOperations); + } + + $titre = $outputlangs->transnoentities("AmountInCurrency", $outputlangs->transnoentitiesnoconv("Currency".$currency)); + $pdf->SetXY($this->page_largeur - $this->marge_droite - ($pdf->GetStringWidth($titre) + 3), $tab_top - 4); + $pdf->MultiCell(($pdf->GetStringWidth($titre) + 3), 2, $titre); + + //$conf->global->MAIN_PDF_TITLE_BACKGROUND_COLOR='230,230,230'; + if (getDolGlobalString('MAIN_PDF_TITLE_BACKGROUND_COLOR')) { + $pdf->Rect($this->marge_gauche, $tab_top, $this->page_largeur - $this->marge_droite - $this->marge_gauche, 5, 'F', null, explode(',', getDolGlobalString('MAIN_PDF_TITLE_BACKGROUND_COLOR'))); + } + } + + $pdf->SetDrawColor(128, 128, 128); + $pdf->SetFont('', '', $default_font_size - 1); + + // Output Rect + $this->printRect($pdf, $this->marge_gauche, $tab_top, $this->page_largeur - $this->marge_gauche - $this->marge_droite, $tab_height, $hidetop, $hidebottom); // Rect takes a length in 3rd parameter and 4th parameter + + if (empty($hidetop)) { + $pdf->line($this->marge_gauche, $tab_top + 5, $this->page_largeur - $this->marge_droite, $tab_top + 5); // line takes a position y in 2nd parameter and 4th parameter + + $pdf->SetXY($this->posxdesc - 1, $tab_top + 1); + $pdf->MultiCell(108, 2, $outputlangs->transnoentities("Designation"), '', 'L'); + } + + if (getDolGlobalString('MAIN_GENERATE_INVOICES_WITH_PICTURE')) { + $pdf->line($this->posxpicture - 1, $tab_top, $this->posxpicture - 1, $tab_top + $tab_height); + if (empty($hidetop)) { + //$pdf->SetXY($this->posxpicture-1, $tab_top+1); + //$pdf->MultiCell($this->posxtva-$this->posxpicture-1,2, $outputlangs->transnoentities("Photo"),'','C'); + } + } + + if (!getDolGlobalString('MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT') && !getDolGlobalString('MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT_COLUMN')) { + $pdf->line($this->posxtva - 1, $tab_top, $this->posxtva - 1, $tab_top + $tab_height); + if (empty($hidetop)) { + $pdf->SetXY($this->posxtva - 3, $tab_top + 1); + $pdf->MultiCell($this->posxup - $this->posxtva + 3, 2, $outputlangs->transnoentities("VAT"), '', 'C'); + } + } + + $pdf->line($this->posxup - 1, $tab_top, $this->posxup - 1, $tab_top + $tab_height); + if (empty($hidetop)) { + $pdf->SetXY($this->posxup - 1, $tab_top + 1); + $pdf->MultiCell($this->posxqty - $this->posxup - 1, 2, $outputlangs->transnoentities("PriceUHT"), '', 'C'); + } + + $pdf->line($this->posxqty - 1, $tab_top, $this->posxqty - 1, $tab_top + $tab_height); + if (empty($hidetop)) { + $pdf->SetXY($this->posxqty - 1, $tab_top + 1); + $pdf->MultiCell($this->posxunit - $this->posxqty - 1, 2, $outputlangs->transnoentities("Qty"), '', 'C'); + } + + if (getDolGlobalInt('PRODUCT_USE_UNITS')) { + $pdf->line($this->posxunit - 1, $tab_top, $this->posxunit - 1, $tab_top + $tab_height); + if (empty($hidetop)) { + $pdf->SetXY($this->posxunit - 1, $tab_top + 1); + $pdf->MultiCell($this->posxdiscount - $this->posxunit - 1, 2, $outputlangs->transnoentities("Unit"), '', 'C'); + } + } + + if ($this->atleastonediscount) { + $pdf->line($this->posxdiscount - 1, $tab_top, $this->posxdiscount - 1, $tab_top + $tab_height); + if (empty($hidetop)) { + $pdf->SetXY($this->posxdiscount - 1, $tab_top + 1); + $pdf->MultiCell($this->posxprogress - $this->posxdiscount + 1, 2, $outputlangs->transnoentities("ReductionShort"), '', 'C'); + } + } + + if ($this->situationinvoice) { + $pdf->line($this->posxprogress - 1, $tab_top, $this->posxprogress - 1, $tab_top + $tab_height); + if (empty($hidetop)) { + $pdf->SetXY($this->posxprogress, $tab_top + 1); + $pdf->MultiCell($this->postotalht - $this->posxprogress, 2, $outputlangs->transnoentities("ProgressShort"), '', 'C'); + } + } + + $pdf->line($this->postotalht, $tab_top, $this->postotalht, $tab_top + $tab_height); + if (empty($hidetop)) { + $pdf->SetXY($this->postotalht - 1, $tab_top + 1); + $pdf->MultiCell(30, 2, $outputlangs->transnoentities("TotalHTShort"), '', 'C'); + } + } + + // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore + /** + * Show top header of page. + * + * @param TCPDF $pdf Object PDF + * @param Facture $object Object to show + * @param int $showaddress 0=no, 1=yes + * @param Translate $outputlangs Object lang for output + * @param Translate $outputlangsbis Object lang for output bis + * @return float|int Return topshift value + */ + protected function _pagehead(&$pdf, $object, $showaddress, $outputlangs, $outputlangsbis = null) + { + // phpcs:enable + global $conf, $langs; + + $ltrdirection = 'L'; + if ($outputlangs->trans("DIRECTION") == 'rtl') { + $ltrdirection = 'R'; + } + + // Load traductions files required by page + $outputlangs->loadLangs(array("main", "bills", "propal", "companies")); + + $default_font_size = pdf_getPDFFontSize($outputlangs); + + pdf_pagehead($pdf, $outputlangs, $this->page_hauteur); + + + + + /* 7. Ubicación del QR en la factura + + + Si se utiliza un formato de orientación vertical para organizar y disponer el contenido de la factura, el código «QR» + se situará arriba de esta, próximo al margen superior, preferiblemente centrado respecto a los márgenes izquierdo y + derecho de la factura.La presentación del código «QR» incluirá también un texto que siempre deberá ir precediéndolo: + «QR tributario:», y que se situará encima del propio código «QR» (preferiblemente centrado con respecto a este), + de manera que sirva para identificarlo y distinguirlo de otros posibles códigos «QR» que pudiera contener + la factura para otros cometidos. + Además, en el caso de facturas expedidas por sistemas que emiten facturas verificables, + justo debajo del código «QR» deberá aparecer la frase «Factura verificable en la sede electrónica de la AEAT» + o «VERI*FACTU», preferiblemente centrada con respecto al código «QR». Si no cabe toda la frase en una sola línea, + podrán utilizarse varias líneas hasta completarla.Tanto el texto que siempre debe preceder al código «QR», como, en su caso, + la frase que habrán de incluir los sistemas «VERI*FACTU» deberán tener un tipo de letra y tamaño legibles, siempre iguales o + superiores a los del resto de datos de la factura. + + 8. Tamaño del QR en las facturas + + 1. El código «QR» deberá tener un tamaño entre 30x30 y 40x40 milímetros y seguir las especificaciones de la norma + ISO/IEC 18004:2015.Este rango asegura que el QR pueda ser escaneado correctamente por cualquier dispositivo. + + A este respecto, se deben mantener como mínimo 2 milímetros de espacio vacío (en blanco) alrededor de los cuatro lados + del código «QR», recomendándose que sean 6 milímetros.La presentación del código «QR» incluirá también un texto que + siempre deberá ir precediéndolo: «QR tributario:»,Además, en el caso de facturas expedidas por sistemas que emiten + facturas verificables, justo debajo del código «QR» deberá aparecer la frase «Factura verificable en la sede + electrónica de la AEAT» o «VERI*FACTU», preferiblemente centrada con respecto al código «QR». + */ + if ( + $object->element === 'facture' + && $object->status > Facture::STATUS_DRAFT + && $object->type <= Facture::TYPE_DEPOSIT + && autoverifactuEnabled() + ) { + global $mysoc; + + $testMode = (bool) getDolGlobalString('AUTOVERIFACTU_TEST_MODE'); + $base_url = $testMode ? VERIFACTU_TEST_VERIFICACION_BASE_URL : VERIFACTU_BASE_URL; + $endpoint = '/wlpl/TIKE-CONT/ValidarQR'; + + $query = http_build_query(array( + 'nif' => $mysoc->idprof1, + 'numserie' => $object->ref, + 'fecha' => date('d-m-Y', $object->date), + 'importe' => number_format($object->total_ttc, 2, '.', ''), + )); + + $qr_width = 36; + $qr_x = ($this->page_largeur - $qr_width) / 2; + $qr_y = 8; + + $pdf->SetXY($qr_x+1,3); + $pdf->SetFont('', '', $default_font_size ); + + $pdf->MultiCell(30, 10, 'QR tributario:', 0, 'C', 0, 1); + + $pdf->write2DBarcode( + $base_url . $endpoint . '?' . $query, + 'QRCODE,M', + $qr_x, + $qr_y, + $qr_width, + $qr_width, + array( + 'border' => false, + 'padding' => 6, + 'fgcolor' => array(25, 25, 25), + 'bgcolor' => array(255, 255, 255), + 'module_width' => 1, + 'module_height' => 1, + ), + 25, + ); + + $pdf->SetXY($qr_x+1, $qr_y + $qr_width ); + $pdf->MultiCell(30, 10, 'VERI*FACTU', 0, 'C', 0, 1); + } + + $pdf->SetTextColor(0, 0, 60); + $pdf->SetFont('', 'B', $default_font_size + 2); + + $w = 110; + + $posy = $this->marge_haute; + $posx = $this->page_largeur - $this->marge_droite - $w; + + $pdf->SetXY($this->marge_gauche, $posy); + + // Logo + if (!getDolGlobalInt('PDF_DISABLE_MYCOMPANY_LOGO')) { + if ($this->emetteur->logo) { + $logodir = $conf->mycompany->dir_output; + if (!empty($conf->mycompany->multidir_output[$object->entity])) { + $logodir = $conf->mycompany->multidir_output[$object->entity]; + } + if (!getDolGlobalInt('MAIN_PDF_USE_LARGE_LOGO')) { + $logo = $logodir.'/logos/thumbs/'.$this->emetteur->logo_small; + } else { + $logo = $logodir.'/logos/'.$this->emetteur->logo; + } + if (is_readable($logo)) { + + $pdf->Image($logo, $this->marge_gauche, $posy, 0, $height); // width=0 (auto) + } else { + $pdf->SetTextColor(200, 0, 0); + $pdf->SetFont('', 'B', $default_font_size - 2); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("ErrorLogoFileNotFound", $logo), 0, 'L'); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("ErrorGoToGlobalSetup"), 0, 'L'); + } + } else { + $text = $this->emetteur->name; + $pdf->MultiCell($w, 4, $outputlangs->convToOutputCharset($text), 0, $ltrdirection); + } + } + + $pdf->SetFont('', 'B', $default_font_size + 3); + $pdf->SetXY($posx, $posy); + $pdf->SetTextColor(0, 0, 60); + $title = $outputlangs->transnoentities("PdfInvoiceTitle"); + if ($object->type == 1) { + $title = $outputlangs->transnoentities("InvoiceReplacement"); + } + if ($object->type == 2) { + $title = $outputlangs->transnoentities("InvoiceAvoir"); + } + if ($object->type == 3) { + $title = $outputlangs->transnoentities("InvoiceDeposit"); + } + if ($object->type == 4) { + $title = $outputlangs->transnoentities("InvoiceProForma"); + } + if ($this->situationinvoice) { + $langs->loadLangs(array("other")); + $title = $outputlangs->transnoentities("PDFInvoiceSituation") . " " . $outputlangs->transnoentities("NumberingShort") . $object->situation_counter . " -"; + } + + + if (getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE') && is_object($outputlangsbis)) { + $title .= ' - '; + if ($object->type == 0) { + if ($this->situationinvoice) { + $title .= $outputlangsbis->transnoentities("PDFInvoiceSituation"); + } + $title .= $outputlangsbis->transnoentities("PdfInvoiceTitle"); + } elseif ($object->type == 1) { + $title .= $outputlangsbis->transnoentities("InvoiceReplacement"); + } elseif ($object->type == 2) { + $title .= $outputlangsbis->transnoentities("InvoiceAvoir"); + } elseif ($object->type == 3) { + $title .= $outputlangsbis->transnoentities("InvoiceDeposit"); + } elseif ($object->type == 4) { + $title .= $outputlangsbis->transnoentities("InvoiceProForma"); + } + } + + + + + + + + + + $title .= ' '.$outputlangs->convToOutputCharset($object->ref); + if ($object->statut == $object::STATUS_DRAFT) { + $pdf->SetTextColor(128, 0, 0); + $title .= ' - '.$outputlangs->transnoentities("NotValidated"); + } + + $pdf->MultiCell($w, 3, $title, '', 'R'); + + $pdf->SetFont('', 'B', $default_font_size); + + /* + $posy += 5; + $pdf->SetXY($posx, $posy); + $pdf->SetTextColor(0, 0, 60); + $textref = $outputlangs->transnoentities("Ref")." : ".$outputlangs->convToOutputCharset($object->ref); + if ($object->statut == $object::STATUS_DRAFT) { + $pdf->SetTextColor(128, 0, 0); + $textref .= ' - '.$outputlangs->transnoentities("NotValidated"); + } + $pdf->MultiCell($w, 4, $textref, '', 'R');*/ + + $posy += 3; + $pdf->SetFont('', '', $default_font_size - 2); + + if ($object->ref_customer) { + $posy += 4; + $pdf->SetXY($posx, $posy); + $pdf->SetTextColor(0, 0, 60); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("RefCustomer")." : ".$outputlangs->convToOutputCharset($object->ref_customer), '', 'R'); + } + + if (getDolGlobalString('PDF_SHOW_PROJECT_TITLE')) { + $object->fetch_projet(); + if (!empty($object->project->ref)) { + $posy += 3; + $pdf->SetXY($posx, $posy); + $pdf->SetTextColor(0, 0, 60); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("Project")." : ".(empty($object->project->title) ? '' : $object->project->title), '', 'R'); + } + } + + if (getDolGlobalString('PDF_SHOW_PROJECT')) { + $object->fetch_projet(); + if (!empty($object->project->ref)) { + $outputlangs->load("projects"); + $posy += 3; + $pdf->SetXY($posx, $posy); + $pdf->SetTextColor(0, 0, 60); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("RefProject")." : ".(empty($object->project->ref) ? '' : $object->project->ref), '', 'R'); + } + } + + $objectidnext = $object->getIdReplacingInvoice('validated'); + if ($object->type == 0 && $objectidnext) { + $objectreplacing = new Facture($this->db); + $objectreplacing->fetch($objectidnext); + + $posy += 3; + $pdf->SetXY($posx, $posy); + $pdf->SetTextColor(0, 0, 60); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("ReplacementByInvoice").' : '.$outputlangs->convToOutputCharset($objectreplacing->ref), '', 'R'); + } + if ($object->type == 1) { + $objectreplaced = new Facture($this->db); + $objectreplaced->fetch($object->fk_facture_source); + + $posy += 4; + $pdf->SetXY($posx, $posy); + $pdf->SetTextColor(0, 0, 60); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("ReplacementInvoice").' : '.$outputlangs->convToOutputCharset($objectreplaced->ref), '', 'R'); + } + if ($object->type == 2 && !empty($object->fk_facture_source)) { + $objectreplaced = new Facture($this->db); + $objectreplaced->fetch($object->fk_facture_source); + + $posy += 3; + $pdf->SetXY($posx, $posy); + $pdf->SetTextColor(0, 0, 60); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("CorrectionInvoice").' : '.$outputlangs->convToOutputCharset($objectreplaced->ref), '', 'R'); + } + + $posy += 4; + $pdf->SetXY($posx, $posy); + $pdf->SetTextColor(0, 0, 60); + $title = $outputlangs->transnoentities("DateInvoice"); + if (getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE') && is_object($outputlangsbis)) { + $title .= ' - '.$outputlangsbis->transnoentities("DateInvoice"); + } + $pdf->MultiCell($w, 3, $title." : ".dol_print_date($object->date, "day", false, $outputlangs, true), '', 'R'); + + if (getDolGlobalString('INVOICE_POINTOFTAX_DATE')) { + $posy += 4; + $pdf->SetXY($posx, $posy); + $pdf->SetTextColor(0, 0, 60); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("DatePointOfTax")." : ".dol_print_date($object->date_pointoftax, "day", false, $outputlangs), '', 'R'); + } + + if ($object->type != 2) { + $posy += 3; + $pdf->SetXY($posx, $posy); + $pdf->SetTextColor(0, 0, 60); + $title = $outputlangs->transnoentities("DateDue"); + if (getDolGlobalString('PDF_USE_ALSO_LANGUAGE_CODE') && is_object($outputlangsbis)) { + $title .= ' - '.$outputlangsbis->transnoentities("DateDue"); + } + $pdf->MultiCell($w, 3, $title." : ".dol_print_date($object->date_lim_reglement, "day", false, $outputlangs, true), '', 'R'); + } + + if (!getDolGlobalString('MAIN_PDF_HIDE_CUSTOMER_CODE') && $object->thirdparty->code_client) { + $posy += 3; + $pdf->SetXY($posx, $posy); + $pdf->SetTextColor(0, 0, 60); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("CustomerCode")." : ".$outputlangs->transnoentities($object->thirdparty->code_client), '', 'R'); + } + + // Get contact + if (getDolGlobalString('DOC_SHOW_FIRST_SALES_REP')) { + $arrayidcontact = $object->getIdContact('internal', 'SALESREPFOLL'); + if (count($arrayidcontact) > 0) { + $usertmp = new User($this->db); + $usertmp->fetch($arrayidcontact[0]); + $posy += 4; + $pdf->SetXY($posx, $posy); + $pdf->SetTextColor(0, 0, 60); + $pdf->MultiCell($w, 3, $outputlangs->transnoentities("SalesRepresentative")." : ".$usertmp->getFullName($langs), '', 'R'); + } + } + + $posy += 1; + + $top_shift = 14; + // Show list of linked objects + $current_y = $pdf->getY(); + $posy = pdf_writeLinkedObjects($pdf, $object, $outputlangs, $posx, $posy, $w, 3, 'R', $default_font_size); + if ($current_y < $pdf->getY()) { + $top_shift = $pdf->getY() - $current_y; + } + + if ($showaddress) { + // Sender properties + $carac_emetteur = ''; + // Add internal contact of object if defined + $arrayidcontact = $object->getIdContact('internal', 'BILLING'); + if (count($arrayidcontact) > 0) { + $object->fetch_user($arrayidcontact[0]); + $labelbeforecontactname = ($outputlangs->transnoentities("FromContactName") != 'FromContactName' ? $outputlangs->transnoentities("FromContactName") : $outputlangs->transnoentities("Name")); + $carac_emetteur .= ($carac_emetteur ? "\n" : '').$labelbeforecontactname." ".$outputlangs->convToOutputCharset($object->user->getFullName($outputlangs)); + $carac_emetteur .= "\n"; + } + + $carac_emetteur .= pdf_build_address($outputlangs, $this->emetteur, $object->thirdparty, '', 0, 'source', $object); + + // Show sender + $posy = getDolGlobalString('MAIN_PDF_USE_ISO_LOCATION') ? 40 : 42; + $posy += $top_shift; + $posx = $this->marge_gauche; + if (getDolGlobalString('MAIN_INVERT_SENDER_RECIPIENT')) { + $posx = $this->page_largeur - $this->marge_droite - 80; + } + + $hautcadre = getDolGlobalString('MAIN_PDF_USE_ISO_LOCATION') ? 38 : 40; + $widthrecbox = getDolGlobalString('MAIN_PDF_USE_ISO_LOCATION') ? 92 : 82; + + + // Show sender frame + if (!getDolGlobalString('MAIN_PDF_NO_SENDER_FRAME')) { + $pdf->SetTextColor(0, 0, 0); + $pdf->SetFont('', '', $default_font_size - 2); + $pdf->SetXY($posx, $posy - 5); + $pdf->MultiCell($widthrecbox, 5, $outputlangs->transnoentities("BillFrom"), 0, $ltrdirection); + $pdf->SetXY($posx, $posy); + $pdf->SetFillColor(230, 230, 230); + $pdf->MultiCell($widthrecbox, $hautcadre, "", 0, 'R', 1); + $pdf->SetTextColor(0, 0, 60); + } + + // Show sender name + if (!getDolGlobalString('MAIN_PDF_HIDE_SENDER_NAME')) { + $pdf->SetXY($posx + 2, $posy + 3); + $pdf->SetFont('', 'B', $default_font_size); + $pdf->MultiCell($widthrecbox - 2, 4, $outputlangs->convToOutputCharset($this->emetteur->name), 0, $ltrdirection); + $posy = $pdf->getY(); + } + + // Show sender information + $pdf->SetXY($posx + 2, $posy); + $pdf->SetFont('', '', $default_font_size - 1); + $pdf->MultiCell($widthrecbox - 2, 4, $carac_emetteur, 0, $ltrdirection); + + + // If BILLING contact defined on invoice, we use it + $usecontact = false; + $arrayidcontact = $object->getIdContact('external', 'BILLING'); + if (count($arrayidcontact) > 0) { + $usecontact = true; + $result = $object->fetch_contact($arrayidcontact[0]); + } + + // Recipient name + if ($usecontact && ($object->contact->socid != $object->thirdparty->id && (!isset($conf->global->MAIN_USE_COMPANY_NAME_OF_CONTACT) || getDolGlobalString('MAIN_USE_COMPANY_NAME_OF_CONTACT')))) { + $thirdparty = $object->contact; + } else { + $thirdparty = $object->thirdparty; + } + + $carac_client_name = pdfBuildThirdpartyName($thirdparty, $outputlangs); + + $mode = 'target'; + $carac_client = pdf_build_address($outputlangs, $this->emetteur, $object->thirdparty, ($usecontact ? $object->contact : ''), $usecontact, $mode, $object); + + // Show recipient + $widthrecbox = getDolGlobalString('MAIN_PDF_USE_ISO_LOCATION') ? 92 : 100; + if ($this->page_largeur < 210) { + $widthrecbox = 84; // To work with US executive format + } + $posy = getDolGlobalString('MAIN_PDF_USE_ISO_LOCATION') ? 40 : 42; + $posy += $top_shift; + $posx = $this->page_largeur - $this->marge_droite - $widthrecbox; + if (getDolGlobalString('MAIN_INVERT_SENDER_RECIPIENT')) { + $posx = $this->marge_gauche; + } + + // Show recipient frame + if (!getDolGlobalString('MAIN_PDF_NO_RECIPENT_FRAME')) { + $pdf->SetTextColor(0, 0, 0); + $pdf->SetFont('', '', $default_font_size - 2); + $pdf->SetXY($posx + 2, $posy - 5); + $pdf->MultiCell($widthrecbox - 2, 5, $outputlangs->transnoentities("BillTo"), 0, $ltrdirection); + $pdf->Rect($posx, $posy, $widthrecbox, $hautcadre); + } + + // Show recipient name + $pdf->SetXY($posx + 2, $posy + 3); + $pdf->SetFont('', 'B', $default_font_size); + // @phan-suppress-next-line PhanPluginSuspiciousParamOrder + $pdf->MultiCell($widthrecbox - 2, 2, $carac_client_name, 0, $ltrdirection); + + $posy = $pdf->getY(); + + // Show recipient information + $pdf->SetFont('', '', $default_font_size - 1); + $pdf->SetXY($posx + 2, $posy); + // @phan-suppress-next-line PhanPluginSuspiciousParamOrder + $pdf->MultiCell($widthrecbox - 2, 4, $carac_client, 0, $ltrdirection); + + // Show shipping address + if (getDolGlobalInt('INVOICE_SHOW_SHIPPING_ADDRESS')) { + $idaddressshipping = $object->getIdContact('external', 'SHIPPING'); + + if (!empty($idaddressshipping)) { + $contactshipping = $object->fetch_Contact($idaddressshipping[0]); + $companystatic = new Societe($this->db); + $companystatic->fetch($object->contact->fk_soc); + $carac_client_name_shipping = pdfBuildThirdpartyName($object->contact, $outputlangs); + $carac_client_shipping = pdf_build_address($outputlangs, $this->emetteur, $companystatic, $object->contact, $usecontact, 'target', $object); + } else { + $carac_client_name_shipping = pdfBuildThirdpartyName($object->thirdparty, $outputlangs); + $carac_client_shipping = pdf_build_address($outputlangs, $this->emetteur, $object->thirdparty, '', 0, 'target', $object); + } + if (!empty($carac_client_shipping)) { + $posy += $hautcadre; + + $hautcadre -= 10; // Height for the shipping address does not need to be as high as main box + + // Show shipping frame + $pdf->SetXY($posx + 2, $posy - 5); + $pdf->SetFont('', '', $default_font_size - 2); + $pdf->MultiCell($widthrecbox, '', $outputlangs->transnoentities('ShippingTo'), 0, 'L', 0); + $pdf->Rect($posx, $posy, $widthrecbox, $hautcadre); + + // Show shipping name + $pdf->SetXY($posx + 2, $posy + 3); + $pdf->SetFont('', 'B', $default_font_size); + $pdf->MultiCell($widthrecbox - 2, 2, $carac_client_name_shipping, '', 'L'); + + $posy = $pdf->getY(); + + // Show shipping information + $pdf->SetXY($posx + 2, $posy); + $pdf->SetFont('', '', $default_font_size - 1); + $pdf->MultiCell($widthrecbox - 2, 2, $carac_client_shipping, '', 'L'); + + $top_shift += $hautcadre + 1; + } + } + } + + $pdf->SetTextColor(0, 0, 0); + + return $top_shift; + } + + // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore + /** + * Show footer of page. Need this->emetteur object + * @param TCPDF $pdf PDF + * @param Facture $object Object to show + * @param Translate $outputlangs Object lang for output + * @param int $hidefreetext 1=Hide free text + * @param int $heightforqrinvoice Height for QR invoices + * @return int Return height of bottom margin including footer text + */ + protected function _pagefoot(&$pdf, $object, $outputlangs, $hidefreetext = 0, $heightforqrinvoice = 0) + { + + $showdetails = getDolGlobalInt('MAIN_GENERATE_DOCUMENTS_SHOW_FOOT_DETAILS', 0); + + + $text="En cumplimiento de la normativa de Protección de Datos le informamos que el RESPONSABLE del tratamiento de los mismos es OYR SOLUTIONS S.L. +gestionados con la FINALIDAD de expedir un presupuesto o la correspondiente factura por la compra de un presto o la prestación de alguno de nuestros servicios. La LEGITIMACIÓN se basa en la existencia de una relación comercial de compra de productos o contractual para la prestación de un servicio. Serán DESTINATARIOS de sus datos aquellas organizaciones o entidades directamente relacionadas con el Responsable, así como las Administraciones Públicas con competencia en la materia. El plazo de CONSERVACIÓN de sus datos será hasta el fin de la prestación del servicio, y una vez finalizado, hasta que se solicite su supresión por parte del interesado. Entre sus DERECHOS se encuentra el de acceder, rectificar y suprimir, así como el de oposición, limitación y portabilidad. + +Si tiene alguna pregunta relacionada con el cumplimiento del RGPD o la protección de datos, póngase en contacto con nuestro Responsable de Protección de Datos en el correo security@oyr.es"; + + return pdf_pagefoot($pdf, $outputlangs, 'INVOICE_FREE_TEXT', $this->emetteur, $heightforqrinvoice + $this->marge_basse, $this->marge_gauche, $this->page_hauteur, $object, $showdetails, $hidefreetext, $this->page_largeur, $this->watermark); + } +} diff --git a/core/modules/modAutoverifactu.class.php b/core/modules/modAutoverifactu.class.php index 060cedd..b960e89 100644 --- a/core/modules/modAutoverifactu.class.php +++ b/core/modules/modAutoverifactu.class.php @@ -106,7 +106,7 @@ public function __construct($db) // Set this to 1 if module has its own barcode directory (core/modules/barcode) 'barcode' => 0, // Set this to 1 if module has its own models directory (core/modules/xxx) - 'models' => 0, + 'models' => 1, // Set this to 1 if module has its own printing directory (core/modules/printing) 'printing' => 0, // Set this to 1 if module has its own theme directory (theme) diff --git a/core/triggers/interface_10_modAutoverifactu_AutoverifactuFreezeInvoices.class.php b/core/triggers/interface_10_modAutoverifactu_AutoverifactuFreezeInvoices.class.php index fb30dca..f2a4653 100644 --- a/core/triggers/interface_10_modAutoverifactu_AutoverifactuFreezeInvoices.class.php +++ b/core/triggers/interface_10_modAutoverifactu_AutoverifactuFreezeInvoices.class.php @@ -212,7 +212,12 @@ public function runTrigger($action, $object, $user, $langs, $conf) $result = autoverifactuRegisterInvoice($object, $action); if ($result < 0) { - $this->errors[] = $langs->trans('RecordCreationFail'); + if (!empty($object->errors)) { + $this->errors = array_merge($this->errors, (array) $object->errors); + }else{ + $this->errors[] = $langs->trans('RecordCreationFail'); + } + } return $result; diff --git a/lib/validate.lib.php b/lib/validate.lib.php deleted file mode 100644 index cad9f3b..0000000 --- a/lib/validate.lib.php +++ /dev/null @@ -1,357 +0,0 @@ -format('d-m-Y') === $date; -} -/** -* Valida el tipo de especificación de factura -* -* @param string $type especificación de factura (L2). -* -* @return boolean 1 correct or 0 incorrect -*/ -function autoverifactuValidateVerifactuInvoice($type){ - return ( - $type ==="F1" || - $type ==="F2" || - $type ==="F3" || - $type ==="R1" || - $type ==="R2" || - $type ==="R3" || - $type ==="R4" || - $type ==="R5" ); -} - -/** -* Valida el tipo de especificación de factura rectificativa -* -* @param string $type especificación de factura (L2). -* @param boolean obliagtorio. -* -* @return boolean 1 correct or 0 incorrect -*/ -function autoverifactuValidateVerifactuInvoiceRectificative ($type,$requerido){ - if(!$requerido && $type ===''){ - return 1; - } - return ( - $type ==="R1" || - $type ==="R2" || - $type ==="R3" || - $type ==="R4" || - $type ==="R5" ); -} -/** -* Valida de tipo alfanumerico. -* -* @param string cadena facture. -* @param int numero de caracteres -* -* @return boolean 1 correct or 0 incorrect -*/ -function autoverifactuValidateAlphaNumber($string, $length) { - $actualLength = mb_strlen($string, 'UTF-8'); - if ($actualLength > (int)$length || $actualLength === 0) { - return 0; - } - $pattern = "/^[a-zA-Z0-9ñÑáéíóúÁÉÍÓÚüÜ ]+$/u"; - if (!preg_match($pattern, $string)) { - return 0; - } - return 1; -} - -/** -* Valida de tipo alfanumerico + guion para la ref. -* -* @param string cadena facture. -* @param int numero de caracteres -* -* @return boolean 1 correct or 0 incorrect -*/ -function autoverifactuValidateAlphaNumberScript($string, $length) { - $actualLength = mb_strlen($string, 'UTF-8'); - if ($actualLength > (int)$length || $actualLength === 0) { - return 0; - } - $pattern = "/^[a-zA-Z0-9ñÑáéíóúÁÉÍÓÚüÜ\- ]+$/u"; - if (!preg_match($pattern, $string)) { - return 0; - } - return 1; -} - -/** -* Valida de tipo number decimal (numberCount,numberDecimal). -*@param float number validate -* @param int numberCount epresenta el número total de dígitos -*@param int numberDecimal Representa cuántos de esos numberCount dígitos están reservados para la parte decimal -* @return boolean 1 correct or 0 incorrect -*/ -function autoverifactuValidateNumber($number,$numberCount,$numberDecimal){ - if (!is_numeric($number)) { - return 0; - } - $absoluteNumber = ltrim($number, '-'); - $parts = explode('.', $absoluteNumber); - $integers = $parts[0]; - $decimals = isset($parts[1]) ? $parts[1] : ''; - $maxIntegersAllowed = $numberCount - $numberDecimal; - $actualIntegersCount = strlen($integers); - $actualDecimalsCount = strlen($decimals); - if ($actualIntegersCount > $maxIntegersAllowed) { - return 0; - } - if ($actualDecimalsCount > $numberDecimal) { - return 0; - } - if (($actualIntegersCount + $actualDecimalsCount) > $numberCount) { - return 0; - } - return 1; -} - -/** - * Verifica que el tipo de impuesto tiene un valor correcto - * @param string tipo de impuesto - * @param boolean obligatorio. - * @return boolean 1 correct or 0 incorrect -*/ -function autoverifactuValidateTaxType($taxType){ - return ($taxType==='01' || - $taxType==='02' || - $taxType==='03' || - $taxType==='05' - ); -} - -/** - * Verifica que el tipo de regimen tiene un valor correcto - * @param string tipo de regimen - * @return boolean 1 correct or 0 incorrect -*/ -function autoverifactuValidateRegimeTypeIva($regimeType){ - return ($regimeType==='01' || - $regimeType==='02' || - $regimeType==='03' || - $regimeType==='04' || - $regimeType==='05' || - $regimeType==='06' || - $regimeType==='07' || - $regimeType==='08' || - $regimeType==='09' || - $regimeType==='10' || - $regimeType==='11' || - $regimeType==='14' || - $regimeType==='15' || - $regimeType==='17' || - $regimeType==='18' || - $regimeType==='19' || - $regimeType==='20' - ); -} - -/** - * Verifica que el tipo de regimen tiene un valor correcto - * @param string tipo de regimen - * @return boolean 1 correct or 0 incorrect -*/ -function autoverifactuValidateRegimeTypeOther($regimeType){ - return ($regimeType==='01' || - $regimeType==='02' || - $regimeType==='03' || - $regimeType==='04' || - $regimeType==='05' || - $regimeType==='06' || - $regimeType==='07' || - $regimeType==='08' || - $regimeType==='09' || - $regimeType==='10' || - $regimeType==='11' || - $regimeType==='14' || - $regimeType==='15' || - $regimeType==='17' || - $regimeType==='18' || - $regimeType==='19' - ); -} - -/** - * Verifica que el tipo de operacion tiene un valor correcto - * @param string tipo de operacion - * @return boolean 1 correct or 0 incorrect -*/ -function autoverifactuValidateOperationType($operationType){ - return ($operationType==='S1' || - $operationType==='S2' || - $operationType==='N1' || - $operationType==='N2' || - $operationType==='validate' - ); -} - -/** - * Verifica que el codigo de error tiene un valor correcto - * @param string tipo codigo de error - * @return boolean 1 correct or 0 incorrect -*/ -function autoverifactuValidateExemptionCode($errorCode){ - return ($errorCode==='E1' || - $errorCode==='E2' || - $errorCode==='E3' || - $errorCode==='E4' || - $errorCode==='E5' || - $errorCode==='E6'|| - $errorCode==='0' - ); -} - -/** -* Valida si el nif es correcto y corresponde con el nombre -* atraves de la API de la Agencia Tributaria. -* -* @param string $nif nif -* @param string $name nombre -* -* @return boolean 1 correct or 0 incorrect -*/ -function autoverifactuValidateNifName($nif,$name) -{ - - $envelope = autoverifactuSoapEnvelopeNIF( - $nif, - $name - ); - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, 'https://www1.agenciatributaria.gob.es/wlpl/BURT-JDIT/ws/VNifV2SOAP'); - - - curl_setopt($ch, CURLOPT_POST, 1); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); - curl_setopt($ch, CURLOPT_FAILONERROR, 1); - - curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'P12'); - $certPath = DOL_DATA_ROOT . '/' . getDolGlobalString('AUTOVERIFACTU_CERT'); - curl_setopt($ch, CURLOPT_SSLCERT, $certPath); - $certPass = getDolGlobalString('AUTOVERIFACTU_PASSWORD'); - curl_setopt($ch, CURLOPT_SSLCERTPASSWD, $certPass); - - curl_setopt( - $ch, - CURLOPT_HTTPHEADER, - array( - 'Content-Type: text/xml', - 'User-Agent: Mozilla/5.0 (compatible; Módulo Auto-Veri*Factu de Dolibarr/0.0.1', - ), - ); - - // --- INSERTA ESTO PARA VER LA RESPUESTA --- - curl_setopt($ch, CURLOPT_VERBOSE, true); // Activa modo detallado - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // Asegura que devuelve la respuesta - - - curl_setopt($ch, CURLOPT_POSTFIELDS, $envelope); - - $res = curl_exec($ch); - - error_log('# RESPUESTA VALIDACIÓN NIF Y NOMBRE'); - curl_close($ch); - // var_dump(htmlspecialchars($res)); - - - try { - - $xml = new SimpleXMLElement($res); - $xml->registerXPathNamespace('valida', 'http://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/burt/jdit/ws/VNifV2Sal.xsd'); - $resultado = $xml->xpath('//valida:Resultado'); - - - if($resultado[0]=='IDENTIFICADO'){ - return 1; - }else if($resultado[0]=='NO PROCESADO'){ - return 0; - // return "Se excede del número de contribuyentes a identificar"; - }else if((string) $resultado[0]=='IDENTIFICADO-REVOCADO'){ - return 1; - //return 0; - //Si el contribuyente se identifica con el NIF aportado, y está en estado baja - }else if((string) $resultado[0][0]=='IDENTIFICADO-BAJA'){ - return 0; - //Si el contribuyente se identifica con el NIF aportado y está en estado baja por revocación del NIF. - }else{ - return 0; - //return "El nombre del usuario no concuerda con el DNI"; - } - - } catch (Exception $e) { - return "Error al procesar el XML: " . $e->getMessage(); - } -} - -/** -* funcion "privada" que genera el xml de la validacion nif -* -* @param string $nif nif -* @param string $name nombre -* -* @return string xml de la validacion nif -*/ -function autoverifactuSoapEnvelopeNIF($nif,$name){ - - $xml = new DOMDocument('1.0', 'UTF-8'); - - // 1. Creamos el Envelope - $envelope = $xml->createElement('soapenv:Envelope'); - - // 2. CORRECCIÓN: Definir cada namespace por separado - $envelope->setAttribute('xmlns:soapenv', 'http://schemas.xmlsoap.org/soap/envelope/'); - $envelope->setAttribute('xmlns:vnif', 'http://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/burt/jdit/ws/VNifV2Ent.xsd'); - - // 3. Estructura normal - $headerEl = $xml->createElement('soapenv:Header'); - $envelope->appendChild($headerEl); - - $body = $xml->createElement('soapenv:Body'); - $root = $xml->createElement('vnif:VNifV1Ent'); - $regContriEl = $xml->createElement('vnif:Contribuyente'); - // Asegúrate de que $nif esté en mayúsculas - $regNifEl = $xml->createElement('vnif:Nif', strtoupper($nif)); - $regContriEl->appendChild($regNifEl); - - $regNameEl = $xml->createElement('vnif:Nombre', $name); - $regContriEl->appendChild($regNameEl); - - $root->appendChild($regContriEl); - $body->appendChild($root); - $envelope->appendChild($body); - $xml->appendChild($envelope); - - // Usamos saveXML() sobre todo el documento para que incluya la cabecera XML si es necesario - return $xml->saveXML(); -} \ No newline at end of file diff --git a/lib/validation.lib.php b/lib/validation.lib.php index a3159f4..dc2f214 100644 --- a/lib/validation.lib.php +++ b/lib/validation.lib.php @@ -33,7 +33,7 @@ require_once DOL_DOCUMENT_ROOT . '/societe/class/societe.class.php'; require_once __DIR__ . '/verifactu.lib.php'; -require_once __DIR__ . '/validate.lib.php'; + @@ -287,6 +287,8 @@ function autoverifactuRecordFromLog($blockedlog, $recordType = 'alta') $line->tva_tx = $linedata->tva_tx; $line->total_ht = $linedata->total_ht; $line->total_tva = $linedata->total_tva; + $line->localtax1_tx = $linedata->localtax1_tx; + $line->total_localtax1 = $linedata->total_localtax1; $line->array_options["options_verifactu_Tax_Type"] = "validate"; $lines[] = $line; } @@ -384,24 +386,26 @@ function autoverifactuEnabled() * Performs record data validation. * * @param stdClass $record Target record. - * + * @param string &$errorMsg Parameter by reference to store the error message. * @return int 0 if validatio fail, 1 if succeed */ -function autoverifactuValidateRecord($record) +function autoverifactuValidateRecord($record, &$errorMsg = '') { //validacion de todos los datos de la factura - $isCorrect=autoverifactuValidateValuesRecord($record); + $isCorrect=autoverifactuValidateValuesRecord($record,$errorMsg); if(!$isCorrect){ return 0; } //validamos que el total de la factura coincida con el total calculado if($record->factureTotalAmount!==$record->factureTtc){ + $errorMsg="AUTOVERIFACTU_ERROR_ValidateTotalAmountANDFactureTtc"; return 0; } if (!isset($record->breakdown, $record->totalTaxAmount, $record->totalAmount)) { + $errorMsg="AUTOVERIFACTU_ERROR_ValidateExistBreakdownTotalTaxAmountTotalAmount"; return 0; } @@ -410,33 +414,41 @@ function autoverifactuValidateRecord($record) && count($record->recipients) ) { // If is simplified, it should not have recipients. + $errorMsg="AUTOVERIFACTU_ERROR_ValidateIsSimplifiedNotRecipients"; return 0; } $isCorrective = preg_match('/R[0-5]/', $record->invoiceType); if ($isCorrective && !$record->correctiveType) { + $errorMsg="AUTOVERIFACTU_ERROR_ValidateCorrectiveIsNotCorrectiveType"; return 0; } elseif (!$isCorrective && $record->correctiveType) { + $errorMsg="AUTOVERIFACTU_ERROR_ValidateIsNotCorrectiveCorrectiveType"; return 0; } elseif (!$isCorrective && count($record->correctedInvoices)) { + $errorMsg="AUTOVERIFACTU_ERROR_ValidateIsNotCorrectiveCountCorrectiveInvoices"; return 0; } if ($record->correctiveType === 'S') { // If its corrective by diferrence it should have base and tax amounts. if (!$record->correctedBaseAmount || !$record->correctedTaxAmount) { + $errorMsg="AUTOVERIFACTU_ERROR_ValidateIsCorrectiveDiferenceBAseTaxAmounts"; return 0; } } else { // If is corrective by substitution, it shouldn't. if ($record->correctedBaseAmount || $record->correctedTaxAmount) { + $errorMsg="AUTOVERIFACTU_ERROR_ValidateIsCorrectiveSustitutionTaxAmountBaseAmount"; return 0; } } if ($record->invoiceType === 'F3' && count($record->replacedInvoices)) { + $errorMsg="AUTOVERIFACTU_ERROR_ValidateIsF3ReplaceInvoices"; return 0; } elseif ($record->invoiceType !== 'F3' && count($record->replacedInvoices)) { + $errorMsg="AUTOVERIFACTU_ERROR_ValidateIsNotF3ReplaceInvoices"; return 0; } @@ -444,6 +456,7 @@ function autoverifactuValidateRecord($record) $expectedBase = 0; foreach ($record->breakdown as $details) { if (!isset($details->taxAmount, $details->baseAmount, $details->taxRate)) { + $errorMsg="AUTOVERIFACTU_ERROR_ValidateTaxAmountBaseAmountTaxRate"; return 0; } @@ -458,6 +471,7 @@ function autoverifactuValidateRecord($record) } if (!$validTaxAmount) { + $errorMsg="AUTOVERIFACTU_ERROR_ValidateTaxAmount"; return 0; } @@ -478,7 +492,12 @@ function autoverifactuValidateRecord($record) } } - return (int) $isTotalValid; + if(!(int) $isTotalValid){ + $errorMsg="AUTOVERIFACTU_ERROR_ValidateTotalValid"; + return 0; + } + + return 1; } /** @@ -522,21 +541,23 @@ function autoverifactuIsPosInvoice($invoice) return $invoice->module_source === 'takepos'; } -/* Validates the values ​​of an invoice record. +/* Validates the values ​​of an invoice record * * @param stdClass $record Target record. * * @return int 0 if validatio fail, 1 if succeed */ -function autoverifactuValidateValuesRecord($record){ +function autoverifactuValidateValuesRecord($record, &$errorMsg = '' ){ $isValidType =autoverifactuValidateTypeInvoice($record->type); if(!$isValidType){ + $errorMsg="AUTOVERIFACTU_ERROR_RecordType"; return 0; } $isValidDateOperation=autoverifactuValidateDate($record->dateOperation,false); if(!$isValidDateOperation){ + $errorMsg="AUTOVERIFACTU_ERROR_ValidateDateOperation"; return 0; } @@ -544,42 +565,42 @@ function autoverifactuValidateValuesRecord($record){ $isValidVerifactuInvoice=autoverifactuValidateVerifactuInvoice($record->invoiceType); if(!$isValidVerifactuInvoice){ + $errorMsg="AUTOVERIFACTU_ERROR_ValidateInvoiceType"; return 0; } $isValidDescription=autoverifactuValidateAlphaNumber($record->description,500); if(!$isValidDescription){ + $errorMsg="AUTOVERIFACTU_ERROR_ValidateDescription"; return 0; } $isValidRef=autoverifactuValidateAlphaNumberScript($record->invoiceId->invoiceNumber,60); if(!$isValidRef){ + $errorMsg="AUTOVERIFACTU_ERROR_ValidateInvoiceNumber"; return 0; } - $isValidTotalAmount=autoverifactuValidateNumber($record->factureTtc,12,2); + $isValidTotalAmount=autoverifactuValidateNumber($record->factureTotalAmount,12,2); if(!$isValidTotalAmount){ + $errorMsg="AUTOVERIFACTU_ERROR_ValidateFactureTotalAmount"; return 0; } $isValidTtc=autoverifactuValidateNumber($record->factureTtc,12,2); if(!$isValidTtc){ + $errorMsg="AUTOVERIFACTU_ERROR_ValidateFactureTtc"; return 0; } - for ($i=0; $i < count($record->recipients); $i++) { - $isValidNif=autoverifactuValidateNifName($record->recipients[$i]->nif,$record->recipients[$i]->name); - - if(!$isValidNif){ - return 0; - } - } + if($record->correctiveType){ $isValidCorrectiveType=autoverifactuValidateVerifactuInvoiceRectificative($record->correctiveType,false); if(!$isValidCorrectiveType){ + $errorMsg="AUTOVERIFACTU_ERROR_ValidateCorrectiveType"; return 0; } } @@ -591,12 +612,14 @@ function autoverifactuValidateValuesRecord($record){ if($record->correctedBaseAmount){ $isValidCorrectedBaseAmount=autoverifactuValidateNumber($record->correctedBaseAmount,12,2); if(!$isValidCorrectedBaseAmount){ + $errorMsg="AUTOVERIFACTU_ERROR_ValidateCorrectedBaseAmount"; return 0; } } if($record->correctedTaxAmount){ $isValidCorrectedTaxAmount=autoverifactuValidateNumber($record->correctedTaxAmount,12,2); if(!$isValidCorrectedTaxAmount){ + $errorMsg="AUTOVERIFACTU_ERROR_ValidateCorrectedTaxAmount"; return 0; } } @@ -611,60 +634,72 @@ function autoverifactuValidateValuesRecord($record){ $isValidTotalTaxType=autoverifactuValidateTaxType($record->breakdown[0]->taxType); if(!$isValidTotalTaxType){ + $errorMsg="AUTOVERIFACTU_ERROR_ValidateTaxType"; return 0; } if($record->breakdown[0]->taxType === '01'){ $isValidRegimeType=autoverifactuValidateRegimeTypeIva($record->breakdown[0]->regimeType); if(!$isValidRegimeType){ + $errorMsg="AUTOVERIFACTU_ERROR_ValidateRegimeType"; return 0; } }else{ $isValidRegimeType=autoverifactuValidateRegimeTypeOther($record->breakdown[0]->regimeType); if(!$isValidRegimeType){ + $errorMsg="AUTOVERIFACTU_ERROR_ValidateRegimeType"; return 0; } } $isValidOperationType=autoverifactuValidateOperationType($record->breakdown[0]->operationType); if(!$isValidOperationType){ + $errorMsg="AUTOVERIFACTU_ERROR_ValidateOperationType"; return 0; } $isValidTotalTaxRate=autoverifactuValidateNumber($record->breakdown[0]->taxRate,4,2); if(!$isValidTotalTaxRate){ + $errorMsg="AUTOVERIFACTU_ERROR_ValidateTaxRate"; return 0; } $isValidBaseAmount=autoverifactuValidateNumber($record->breakdown[0]->baseAmount,12,2); if(!$isValidBaseAmount){ + $errorMsg="AUTOVERIFACTU_ERROR_ValidateBaseAmount"; return 0; } $isValidTaxAmount=autoverifactuValidateNumber($record->breakdown[0]->taxAmount,12,2); if(!$isValidTaxAmount){ + $errorMsg="AUTOVERIFACTU_ERROR_ValidateTaxAmount"; return 0; } - if(isset($record->breakdown[0]->exemptionCode)){ - $isValidTexemptionCode=autoverifactuValidateExemptionCode($record->breakdown[0]->exemptionCode,12,2); + if(isset($record->breakdown[0]->exeptionCode)){ + + $isValidTExeptionCode=autoverifactuValidateexeptionCode($record->breakdown[0]->exeptionCode,12,2); + if(!$isValidTExeptionCode){ + $errorMsg="AUTOVERIFACTU_ERROR_ValidateExeptionCode"; + return 0; + } } - return 1; - if(!$isValidTexemptionCode){ - return 0; - } - + + + return 1; } $isValidTotalTaxAmount=autoverifactuValidateNumber($record->totalTaxAmount,12,2); if(!$isValidTotalTaxAmount){ + $errorMsg="AUTOVERIFACTU_ERROR_ValidateTotalTaxAmount"; return 0; } $isValidTotalAmount=autoverifactuValidateNumber($record->totalAmount,12,2); if(!$isValidTotalAmount){ + $errorMsg="AUTOVERIFACTU_ERROR_ValidateTotalAmount"; return 0; } @@ -673,24 +708,239 @@ function autoverifactuValidateValuesRecord($record){ } +/* +* Funciones auxiliares para validación de datos de factura. +*/ /** - * Validates the issuer data for a Veri*Factu record. - * - * @param array $issuer The issuer data to validate. - * - * @return int 1 if valid, 0 otherwise. - */ -function autoverifactuValidateIssuer($issuer){ - if (!$issuer || !$issuer['idprof1'] || !$issuer['name']) { - return 0; +* Valida si es alta o anulacion. +* +* @param string $type facture. +* +* @return boolean 1 correct or 0 incorrect +*/ +function autoverifactuValidateTypeInvoice($type){ + return ($type === 'alta' || $type=== 'anulacion'); +} +/** +* Valida si es una fecha +* +* @param string $date +* @param boolean en caso de true tiene que ser una fecha en caso de false puede +* ser una fecha o estar "" o null +* @return boolean 1 correct or 0 incorrect +*/ +function autoverifactuValidateDate($date,$require){ + if(!$require && $date===""){ + return 1; } + $d = DateTime::createFromFormat('d-m-y', $date); + return $d && $d->format('d-m-Y') === $date; +} +/** +* Valida el tipo de especificación de factura +* +* @param string $type especificación de factura (L2). +* +* @return boolean 1 correct or 0 incorrect +*/ +function autoverifactuValidateVerifactuInvoice($type){ + return ( + $type ==="F1" || + $type ==="F2" || + $type ==="F3" || + $type ==="R1" || + $type ==="R2" || + $type ==="R3" || + $type ==="R4" || + $type ==="R5" ); +} - $isValidNifName=autoverifactuValidateNifName($issuer['idprof1'],$issuer['name'] ); +/** +* Valida el tipo de especificación de factura rectificativa +* +* @param string $type especificación de factura (L2). +* @param boolean obliagtorio. +* +* @return boolean 1 correct or 0 incorrect +*/ +function autoverifactuValidateVerifactuInvoiceRectificative ($type,$requerido){ + if(!$requerido && $type ===''){ + return 1; + } + return ( + $type ==="R1" || + $type ==="R2" || + $type ==="R3" || + $type ==="R4" || + $type ==="R5" ); +} +/** +* Valida de tipo alfanumerico. +* +* @param string cadena facture. +* @param int numero de caracteres +* +* @return boolean 1 correct or 0 incorrect +*/ +function autoverifactuValidateAlphaNumber($string, $length) { + $actualLength = mb_strlen($string, 'UTF-8'); + if ($actualLength > (int)$length || $actualLength === 0) { + return 0; + } + $pattern = "/^[a-zA-Z0-9ñÑáéíóúÁÉÍÓÚüÜ ]+$/u"; + if (!preg_match($pattern, $string)) { + return 0; + } + return 1; +} - if(!$isValidNifName){ +/** +* Valida de tipo alfanumerico + guion para la ref. +* +* @param string cadena facture. +* @param int numero de caracteres +* +* @return boolean 1 correct or 0 incorrect +*/ +function autoverifactuValidateAlphaNumberScript($string, $length) { + $actualLength = mb_strlen($string, 'UTF-8'); + if ($actualLength > (int)$length || $actualLength === 0) { return 0; } + $pattern = "/^[a-zA-Z0-9ñÑáéíóúÁÉÍÓÚüÜ\- ]+$/u"; + if (!preg_match($pattern, $string)) { + return 0; + } + return 1; +} +/** +* Valida de tipo number decimal (numberCount,numberDecimal). +*@param float number validate +* @param int numberCount epresenta el número total de dígitos +*@param int numberDecimal Representa cuántos de esos numberCount dígitos están reservados para la parte decimal +* @return boolean 1 correct or 0 incorrect +*/ +function autoverifactuValidateNumber($number,$numberCount,$numberDecimal){ + if (!is_numeric($number)) { + return 0; + } + $absoluteNumber = ltrim($number, '-'); + $parts = explode('.', $absoluteNumber); + $integers = $parts[0]; + $decimals = isset($parts[1]) ? $parts[1] : ''; + $maxIntegersAllowed = $numberCount - $numberDecimal; + $actualIntegersCount = strlen($integers); + $actualDecimalsCount = strlen($decimals); + if ($actualIntegersCount > $maxIntegersAllowed) { + return 0; + } + if ($actualDecimalsCount > $numberDecimal) { + return 0; + } + if (($actualIntegersCount + $actualDecimalsCount) > $numberCount) { + return 0; + } return 1; -} \ No newline at end of file +} + +/** + * Verifica que el tipo de impuesto tiene un valor correcto + * @param string tipo de impuesto + * @param boolean obligatorio. + * @return boolean 1 correct or 0 incorrect +*/ +function autoverifactuValidateTaxType($taxType){ + return ($taxType==='01' || + $taxType==='02' || + $taxType==='03' || + $taxType==='05' + ); +} + +/** + * Verifica que el tipo de regimen tiene un valor correcto + * @param string tipo de regimen + * @return boolean 1 correct or 0 incorrect +*/ +function autoverifactuValidateRegimeTypeIva($regimeType){ + return ($regimeType==='01' || + $regimeType==='02' || + $regimeType==='03' || + $regimeType==='04' || + $regimeType==='05' || + $regimeType==='06' || + $regimeType==='07' || + $regimeType==='08' || + $regimeType==='09' || + $regimeType==='10' || + $regimeType==='11' || + $regimeType==='14' || + $regimeType==='15' || + $regimeType==='17' || + $regimeType==='18' || + $regimeType==='19' || + $regimeType==='20' + ); +} + +/** + * Verifica que el tipo de regimen tiene un valor correcto + * @param string tipo de regimen + * @return boolean 1 correct or 0 incorrect +*/ +function autoverifactuValidateRegimeTypeOther($regimeType){ + return ($regimeType==='01' || + $regimeType==='02' || + $regimeType==='03' || + $regimeType==='04' || + $regimeType==='05' || + $regimeType==='06' || + $regimeType==='07' || + $regimeType==='08' || + $regimeType==='09' || + $regimeType==='10' || + $regimeType==='11' || + $regimeType==='14' || + $regimeType==='15' || + $regimeType==='17' || + $regimeType==='18' || + $regimeType==='19' + ); +} + +/** + * Verifica que el tipo de operacion tiene un valor correcto + * @param string tipo de operacion + * @return boolean 1 correct or 0 incorrect +*/ +function autoverifactuValidateOperationType($operationType){ + return ($operationType==='S1' || + $operationType==='S2' || + $operationType==='N1' || + $operationType==='N2' || + $operationType==='validate' + ); +} + +/** + * Verifica que el codigo de error tiene un valor correcto + * @param string tipo codigo de error + * @return boolean 1 correct or 0 incorrect +*/ +function autoverifactuValidateexeptionCode($errorCode){ + + return ($errorCode==='E1' || + $errorCode==='E2' || + $errorCode==='E3' || + $errorCode==='E4' || + $errorCode==='E5' || + $errorCode==='E6'|| + $errorCode==='0' //en caso de que no haya exención + + ); +} + + + diff --git a/lib/verifactu.lib.php b/lib/verifactu.lib.php index 7268832..4f8a66f 100644 --- a/lib/verifactu.lib.php +++ b/lib/verifactu.lib.php @@ -26,7 +26,7 @@ require_once DOL_DOCUMENT_ROOT . '/core/lib/admin.lib.php'; require_once DOL_DOCUMENT_ROOT . '/compta/facture/class/facture.class.php'; -require_once __DIR__ . '/validation.lib.php'; + /* Veri*Factu API URLs */ @@ -203,6 +203,9 @@ function autoverifactuRegisterInvoice($invoice, $action) } dol_syslog('Error on verifactu request ' . print_r($e, true), LOG_ERR); + + $invoice->errors[] = $e->getMessage(); + return -1; } @@ -222,9 +225,11 @@ function autoverifactuRegisterInvoice($invoice, $action) */ function autoverifactuSendInvoice($invoice, $action, &$xml) { + global $langs; + $langs->load('autoverifactu@autoverifactu'); - if (!autoverifactuSystemCheck()) { + $invoice->errors[] ="VerifactuErrorSytemChecks"; dol_syslog('Veri*Factu bridge does not pass system checks'); return; } @@ -232,6 +237,7 @@ function autoverifactuSendInvoice($invoice, $action, &$xml) $enabled = getDolGlobalString('AUTOVERIFACTU_ENABLED') == '1'; if (!$enabled) { + $invoice->errors[] ="VerifactuErrorNotEnabled"; dol_syslog('Veri*Factu bridge is not enabled'); return; } @@ -244,12 +250,14 @@ function autoverifactuSendInvoice($invoice, $action, &$xml) . $invoice->id . 'is already registered', ); - + $invoice->errors[] ="VerifactuErrorSkipInvoiceRegistration"; return; } $record = autoverifactuInvoiceToRecord($invoice, $recordType); + if (!$record) { + throw new Exception('Inconsistent invoice data'); } @@ -269,7 +277,6 @@ function autoverifactuSendInvoice($invoice, $action, &$xml) 'Verifactu record registry interception on "autoverifactuRecord" for invoice #' . $invoice->id, ); - return $reshook; } @@ -280,11 +287,7 @@ function autoverifactuSendInvoice($invoice, $action, &$xml) 'idprof1' => $mysoc->idprof1, ); - $issuerIsValid = autoverifactuValidateIssuer($issuer); - if (!$issuerIsValid) { - throw new Exception('Inconsistent issuer data'); - } $envelope = autoverifactuSoapEnvelope( $record, $issuer @@ -324,7 +327,6 @@ function autoverifactuSendInvoice($invoice, $action, &$xml) $error = curl_error($ch); $code = curl_errno($ch); curl_close($ch); - throw new Exception('cURL error: ' . $error, $code); } else { $xml = $envelope; @@ -345,10 +347,26 @@ function autoverifactuSendInvoice($invoice, $action, &$xml) $status = $doc->getElementsByTagName('EstadoRegistro')[0]; if ($status->nodeValue === 'Incorrecto') { + + //mostrar el motivo dol_syslog('# REJECTED SOAP ENVELOPE', LOG_DEBUG); dol_syslog($envelope, LOG_DEBUG); - throw new Exception($res, 400); + echo "



"; + // var_dump(htmlspecialchars($res)); + $errCodeList=$doc->getElementsByTagName('CodigoErrorRegistro'); + $errDescriptionList=$doc->getElementsByTagName('DescripcionErrorRegistro'); + $errCode = ($errCodeList->length > 0) ? $errCodeList->item(0)->nodeValue : 'No code'; + $errDescription = ($errDescriptionList->length > 0) ? $errDescriptionList->item(0)->nodeValue : 'No description'; + var_dump($errCode); + if($langs->trans($errCode)!==$errCode){ + //por si queremos poner nosotros la explicación del del codigo del error, sino mostramos la dada + $errDescription=$langs->trans($errCode); + } + + throw new Exception("AEAT Informa: ERROR DE VALIDACIÓN " .$errCode. "

".$errDescription, 500); } elseif ($status->nodeValue === 'AceptadoConErrores') { + + //mostrar los errores $errCode = $doc->getElementsByTagName('CodigoErrorRegistro')[0] ?? null; $errMessage = $doc->getElementsByTagName('DescripcionErrorRegistro')[0] ?? null; @@ -658,10 +676,12 @@ function autoverifactuInvoiceToRecord($invoice, $recordType = 'alta') if (!empty($reshook)) { return $reshook; } - - if (autoverifactuValidateRecord($record)) { + $errorMsg = ''; + if (autoverifactuValidateRecord($record,$errorMsg)) { return $record; + }else{ + throw new Exception($errorMsg ?: 'Inconsistent invoice data'); } } @@ -796,15 +816,15 @@ function autoverifactuRecordToXML($record, $xml = null) $dEl->appendChild($xml->createElement('sum1:ClaveRegimen', $details->regimeType)); } // Si el código de exención es E1, E2, E3, E4, E5 o E6, se indicará el código de exención. - if(in_array($details->exemptionCode, array('E1', 'E2','E3','E4','E5','E6'), true)){ - $dEl->appendChild($xml->createElement('sum1:OperacionExenta', $details->exemptionCode)); + if( isset($details->exeptionCode) && in_array($details->exeptionCode, array('E1', 'E2','E3','E4','E5','E6'), true)){ + $dEl->appendChild($xml->createElement('sum1:OperacionExenta', $details->exeptionCode)); } //se indicará la calificación de la operación en caso de no existir código de exención. - if(!in_array($details->exemptionCode, array('E1', 'E2','E3','E4','E5','E6'), true)){ + if( !isset($details->exeptionCode) || !in_array($details->exeptionCode, array('E1', 'E2','E3','E4','E5','E6'), true)){ $dEl->appendChild($xml->createElement('sum1:CalificacionOperacion', $details->operationType)); } // Se indicará el tipo impositivo, si no existe código de exención o la calificación de la operación no es N1. - if(!(in_array($details->exemptionCode, array('E1', 'E2','E3','E4','E5','E6'), true) || $details->operationType === 'N1' )){ + if( !isset($details->exeptionCode) || !(in_array($details->exeptionCode, array('E1', 'E2','E3','E4','E5','E6'), true) || $details->operationType === 'N1' )){ $dEl->appendChild($xml->createElement('sum1:TipoImpositivo', $details->taxRate)); } @@ -812,7 +832,7 @@ function autoverifactuRecordToXML($record, $xml = null) $dEl->appendChild($xml->createElement('sum1:BaseImponibleOimporteNoSujeto', $details->baseAmount)); // Se indicará el cantidad impositiva, si no existe código de exención o la calificación de la operación no es N1. - if(!(in_array($details->exemptionCode, array('E1', 'E2','E3','E4','E5','E6'), true) || $details->operationType === 'N1' ) ){ + if(!isset($details->exeptionCode) || !(in_array($details->exeptionCode, array('E1', 'E2','E3','E4','E5','E6'), true) || $details->operationType === 'N1' ) ){ $dEl->appendChild($xml->createElement('sum1:CuotaRepercutida', $details->taxAmount)); } // Se indicará el recargo de equivalencia y el tipo en caso de existir @@ -857,7 +877,6 @@ function autoverifactuRecordToXML($record, $xml = null) $systemEl = $xml->createElement('sum1:SistemaInformatico'); $recordEl->appendChild($systemEl); - $systemEl->appendChild($xml->createElement('sum1:NombreRazon', htmlspecialchars($record->system->vendorName))); $systemEl->appendChild($xml->createElement('sum1:NIF', htmlspecialchars($record->system->vendorNif))); $systemEl->appendChild($xml->createElement('sum1:NombreSistemaInformatico', htmlspecialchars($record->system->name))); @@ -919,10 +938,20 @@ function autoverifactuLinesToBreakdown($invoice) $details->taxRate = number_format((float) $line->tva_tx, 2, '.', ''); $details->baseAmount = number_format((float) $line->total_ht, 2, '.', ''); $details->taxAmount = number_format((float) $line->total_tva, 2, '.', ''); - $details->exemptionCode=$line->array_options["options_verifactu_Tax_Exception"]; + $details->exeptionCode=$line->array_options["options_verifactu_Tax_Exception"]; if( $details->regimeType =="18"){ $details->tax_type=$line->localtax1_type; + /* + ? No se si validar que el recargo de eguivalencia en este tercero esta activo, + ? ya que estamos eligiendo la opcion de recargo de equivalencia con Operación Sujeta y No exenta + if (empty($invoice->thirdparty)) { + $invoice->fetch_thirdparty(); + } + $sujeto_a_re = $invoice->thirdparty->localtax1_assuj; + if($sujeto_a_re == 0){ + retornar error + }*/ $details->EquivalenceSurchargeType =number_format((float) $line->localtax1_tx, 2, '.', '') ; $details->EquivalenceSurcharge=number_format((float) $line->total_localtax1, 2, '.', ''); } @@ -956,6 +985,8 @@ function autoverifactuLinesToBreakdown($invoice) $details->taxRate = number_format((float) $line->tva_tx, 2, '.', ''); $details->baseAmount = number_format((float) $line->total_ht, 2, '.', ''); $details->taxAmount = number_format((float) $line->total_tva, 2, '.', ''); + $details->EquivalenceSurchargeType =number_format((float) $line->localtax1_tx, 2, '.', '') ; + $details->EquivalenceSurcharge=number_format((float) $line->total_localtax1, 2, '.', ''); $breakdown[] = $details; break; }