* Copyright (C) 2019-2020 Open-DSI * Copyright (C) 2020 Frédéric France * * 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 . */ /** * \file htdocs/intracommreport/class/intracommreport.class.php * \ingroup Intracomm report * \brief File of class to manage intracomm report */ require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php'; /** * Class to manage intracomm report */ class IntracommReport extends CommonObject { /** * @var string ID to identify managed object */ public $element = 'intracommreport'; /** * @var string Name of table without prefix where object is stored */ public $table_element = 'intracommreport'; /** * @var string Field with ID of parent key if this field has a parent */ public $fk_element = 'fk_intracommreport'; /** * 0 = No test on entity, 1 = Test with field entity, 2 = Test with link by societe * @var int */ public $ismultientitymanaged = 1; public $picto = 'intracommreport'; public $label; // ref ??? public $period; public $declaration; /** * @var string declaration number */ public $declaration_number; public $type_declaration; // deb or des /** * DEB - Product */ const TYPE_DEB = 0; /** * DES - Service */ const TYPE_DES = 1; public static $type = array( 'introduction'=>'Introduction', 'expedition'=>'Expédition' ); /** * Constructor * * @param DoliDB $db Database handle */ public function __construct(DoliDB $db) { $this->db = $db; $this->exporttype = 'deb'; } /** * Function create * * @param User $user User * @param int $notrigger notrigger * @return int */ public function create($user, $notrigger = 0) { return 1; } /** * Function fetch * * @param int $id object ID * @return int */ public function fetch($id) { return 1; } /** * Function delete * * @param int $id object ID * @param User $user User * @param int $notrigger notrigger * @return int */ public function delete($id, $user, $notrigger = 0) { return 1; } /** * Generate XML file * * @param int $mode O for create, R for regenerate (Look always 0 ment toujours 0 within the framework of XML exchanges according to documentation) * @param string $type Declaration type by default - introduction or expedition (always 'expedition' for Des) * @param string $period_reference Period of reference * @return SimpleXMLElement|int */ public function getXML($mode = 'O', $type = 'introduction', $period_reference = '') { global $conf, $mysoc; /**************Construction de quelques variables********************/ $party_id = substr(strtr($mysoc->tva_intra, array(' '=>'')), 0, 4).$mysoc->idprof2; $declarant = substr($mysoc->managers, 0, 14); $id_declaration = self::getDeclarationNumber($this->numero_declaration); /********************************************************************/ /**************Construction du fichier XML***************************/ $e = new SimpleXMLElement(''); $enveloppe = $e->addChild('Envelope'); $enveloppe->addChild('envelopeId', $conf->global->INTRACOMMREPORT_NUM_AGREMENT); $date_time = $enveloppe->addChild('DateTime'); $date_time->addChild('date', date('Y-m-d')); $date_time->addChild('time', date('H:i:s')); $party = $enveloppe->addChild('Party'); $party->addAttribute('partyType', $conf->global->INTRACOMMREPORT_TYPE_ACTEUR); $party->addAttribute('partyRole', $conf->global->INTRACOMMREPORT_ROLE_ACTEUR); $party->addChild('partyId', $party_id); $party->addChild('partyName', $declarant); $enveloppe->addChild('softwareUsed', 'Dolibarr'); $declaration = $enveloppe->addChild('Declaration'); $declaration->addChild('declarationId', $id_declaration); $declaration->addChild('referencePeriod', $period_reference); if ($conf->global->INTRACOMMREPORT_TYPE_ACTEUR === 'PSI') { $psiId = $party_id; } else { $psiId = 'NA'; } $declaration->addChild('PSIId', $psiId); $function = $declaration->addChild('Function'); $functionCode = $function->addChild('functionCode', $mode); $declaration->addChild('declarationTypeCode', $conf->global->{'INTRACOMMREPORT_NIV_OBLIGATION_'.strtoupper($type)}); $declaration->addChild('flowCode', ($type == 'introduction' ? 'A' : 'D')); $declaration->addChild('currencyCode', $conf->global->MAIN_MONNAIE); /********************************************************************/ /**************Ajout des lignes de factures**************************/ $res = $this->addItemsFact($declaration, $type, $period_reference); /********************************************************************/ $this->errors = array_unique($this->errors); if (!empty($res)) { return $e->asXML(); } else { return 0; } } /** * Generate XMLDes file * * @param int $period_year Year of declaration * @param int $period_month Month of declaration * @param string $type_declaration Declaration type by default - introduction or expedition (always 'expedition' for Des) * @return SimpleXMLElement|int */ public function getXMLDes($period_year, $period_month, $type_declaration = 'expedition') { global $mysoc; $e = new SimpleXMLElement(''); $declaration_des = $e->addChild('declaration_des'); $declaration_des->addChild('num_des', self::getDeclarationNumber($this->numero_declaration)); $declaration_des->addChild('num_tvaFr', $mysoc->tva_intra); // /^FR[a-Z0-9]{2}[0-9]{9}$/ // Doit faire 13 caractères $declaration_des->addChild('mois_des', $period_month); $declaration_des->addChild('an_des', $period_year); /**************Ajout des lignes de factures**************************/ $res = $this->addItemsFact($declaration_des, $type_declaration, $period_year.'-'.$period_month, 'des'); /********************************************************************/ $this->errors = array_unique($this->errors); if (!empty($res)) { return $e->asXML(); } else { return 0; } } /** * Add line from invoice * * @param SimpleXMLElement $declaration Reference declaration * @param string $type Declaration type by default - introduction or expedition (always 'expedition' for Des) * @param int $period_reference Reference period * @param string $exporttype deb=DEB, des=DES * @return int <0 if KO, >0 if OK */ public function addItemsFact(&$declaration, $type, $period_reference, $exporttype = 'deb') { global $conf; require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php'; $sql = $this->getSQLFactLines($type, $period_reference, $exporttype); $resql = $this->db->query($sql); if ($resql) { $i = 1; if ($this->db->num_rows($resql) <= 0) { $this->errors[] = 'No data for this period'; return 0; } if ($exporttype == 'deb' && $conf->global->INTRACOMMREPORT_CATEG_FRAISDEPORT > 0) { $categ_fraisdeport = new Categorie($this->db); $categ_fraisdeport->fetch($conf->global->INTRACOMMREPORT_CATEG_FRAISDEPORT); $TLinesFraisDePort = array(); } while ($res = $this->db->fetch_object($resql)) { if ($exporttype == 'des') { $this->addItemXMlDes($declaration, $res, $i); } else { if (empty($res->fk_pays)) { // We don't stop the loop because we want to know all the third parties who don't have an informed country $this->errors[] = 'Country not filled in for the third party '.$res->nom.''; } else { if ($conf->global->INTRACOMMREPORT_CATEG_FRAISDEPORT > 0 && $categ_fraisdeport->containsObject('product', $res->id_prod)) { $TLinesFraisDePort[] = $res; } else { $this->addItemXMl($declaration, $res, $i, ''); } } } $i++; } if (!empty($TLinesFraisDePort)) { $this->addItemFraisDePort($declaration, $TLinesFraisDePort, $type, $categ_fraisdeport, $i); } if (count($this->errors) > 0) { return 0; } } return 1; } /** * Add invoice line * * @param string $type Declaration type by default - introduction or expedition (always 'expedition' for Des) * @param int $period_reference Reference declaration * @param string $exporttype deb=DEB, des=DES * @return string <0 if KO, >0 if OK */ public function getSQLFactLines($type, $period_reference, $exporttype = 'deb') { global $mysoc, $conf; if ($type == 'expedition' || $exporttype == 'des') { $sql = "SELECT f.ref as refinvoice, f.total_ht"; $table = 'facture'; $table_extraf = 'facture_extrafields'; $tabledet = 'facturedet'; $field_link = 'fk_facture'; } else { // Introduction $sql = "SELECT f.ref_supplier as refinvoice, f.total_ht"; $table = 'facture_fourn'; $table_extraf = 'facture_fourn_extrafields'; $tabledet = 'facture_fourn_det'; $field_link = 'fk_facture_fourn'; } $sql .= ", l.fk_product, l.qty , p.weight, p.rowid as id_prod, p.customcode , s.rowid as id_client, s.nom, s.zip, s.fk_pays, s.tva_intra , c.code , ext.mode_transport FROM ".MAIN_DB_PREFIX.$tabledet." l INNER JOIN ".MAIN_DB_PREFIX.$table." f ON (f.rowid = l.".$this->db->escape($field_link).") LEFT JOIN ".MAIN_DB_PREFIX.$table_extraf." ext ON (ext.fk_object = f.rowid) INNER JOIN ".MAIN_DB_PREFIX."product p ON (p.rowid = l.fk_product) INNER JOIN ".MAIN_DB_PREFIX."societe s ON (s.rowid = f.fk_soc) LEFT JOIN ".MAIN_DB_PREFIX."c_country c ON (c.rowid = s.fk_pays) WHERE f.fk_statut > 0 AND l.product_type = ".($exporttype == "des" ? 1 : 0)." AND f.entity = ".((int) $conf->entity)." AND (s.fk_pays <> ".((int) $mysoc->country_id)." OR s.fk_pays IS NULL) AND f.datef BETWEEN '".$this->db->escape($period_reference)."-01' AND '".$this->db->escape($period_reference)."-".date('t')."'"; return $sql; } /** * Add item for DEB * * @param SimpleXMLElement $declaration Reference declaration * @param Resource $res Result of request SQL * @param int $i Line Id * @param string $code_douane_spe Specific customs authorities code * @return void */ public function addItemXMl(&$declaration, &$res, $i, $code_douane_spe = '') { $item = $declaration->addChild('Item'); $item->addChild('itemNumber', $i); $cn8 = $item->addChild('CN8'); if (empty($code_douane_spe)) { $code_douane = $res->customcode; } else { $code_douane = $code_douane_spe; } $cn8->addChild('CN8Code', $code_douane); $item->addChild('MSConsDestCode', $res->code); // code iso pays client $item->addChild('countryOfOriginCode', substr($res->zip, 0, 2)); // code iso pays d'origine $item->addChild('netMass', round($res->weight * $res->qty)); // Poids du produit $item->addChild('quantityInSU', $res->qty); // Quantité de produit dans la ligne $item->addChild('invoicedAmount', round($res->total_ht)); // Montant total ht de la facture (entier attendu) // $item->addChild('invoicedNumber', $res->refinvoice); // Numéro facture if (!empty($res->tva_intra)) { $item->addChild('partnerId', $res->tva_intra); } $item->addChild('statisticalProcedureCode', '11'); $nature_of_transaction = $item->addChild('NatureOfTransaction'); $nature_of_transaction->addChild('natureOfTransactionACode', 1); $nature_of_transaction->addChild('natureOfTransactionBCode', 1); $item->addChild('modeOfTransportCode', $res->mode_transport); $item->addChild('regionCode', substr($res->zip, 0, 2)); } /** * Add item for DES * * @param SimpleXMLElement $declaration Reference declaration * @param Resource $res Result of request SQL * @param int $i Line Id * @return void */ public function addItemXMlDes($declaration, &$res, $i) { $item = $declaration->addChild('ligne_des'); $item->addChild('numlin_des', $i); $item->addChild('valeur', round($res->total_ht)); // Total amount excl. tax of the invoice (whole amount expected) $item->addChild('partner_des', $res->tva_intra); // Represents the foreign customer's VAT number } /** * This function adds an item by retrieving the customs code of the product with the highest amount in the invoice * * @param SimpleXMLElement $declaration Reference declaration * @param array $TLinesFraisDePort Data of shipping costs line * @param string $type Declaration type by default - introduction or expedition (always 'expedition' for Des) * @param Categorie $categ_fraisdeport category of shipping costs * @param int $i Line Id * @return void */ public function addItemFraisDePort(&$declaration, &$TLinesFraisDePort, $type, &$categ_fraisdeport, $i) { global $conf; if ($type == 'expedition') { $table = 'facture'; $tabledet = 'facturedet'; $field_link = 'fk_facture'; $more_sql = 'f.ref'; } else { // Introduction $table = 'facture_fourn'; $tabledet = 'facture_fourn_det'; $field_link = 'fk_facture_fourn'; $more_sql = 'f.ref_supplier'; } foreach ($TLinesFraisDePort as $res) { $sql = "SELECT p.customcode FROM ".MAIN_DB_PREFIX.$tabledet." d INNER JOIN ".MAIN_DB_PREFIX.$table." f ON (f.rowid = d.".$this->db->escape($field_link).") INNER JOIN ".MAIN_DB_PREFIX."product p ON (p.rowid = d.fk_product) WHERE d.fk_product IS NOT NULL AND f.entity = ".((int) $conf->entity)." AND ".$more_sql." = '".$this->db->escape($res->refinvoice)."' AND d.total_ht = ( SELECT MAX(d.total_ht) FROM ".MAIN_DB_PREFIX.$tabledet." d INNER JOIN ".MAIN_DB_PREFIX.$table." f ON (f.rowid = d.".$this->db->escape($field_link).") WHERE d.fk_product IS NOT NULL AND ".$more_sql." = '".$this->db->escape($res->refinvoice)."' AND d.fk_product NOT IN ( SELECT fk_product FROM ".MAIN_DB_PREFIX."categorie_product WHERE fk_categorie = ".((int) $categ_fraisdeport->id)." ) )"; $resql = $this->db->query($sql); $ress = $this->db->fetch_object($resql); $this->addItemXMl($declaration, $res, $i, $ress->customcode); $i++; } } /** * Return next reference of declaration not already used (or last reference) * * @return string free ref or last ref */ public function getNextDeclarationNumber() { $sql = "SELECT MAX(numero_declaration) as max_declaration_number FROM ".MAIN_DB_PREFIX.$this->table_element; $sql .= " WHERE exporttype = '".$this->db->escape($this->exporttype)."'"; $resql = $this->db->query($sql); if ($resql) { $res = $this->db->fetch_object($resql); } return ($res->max_declaration_number + 1); } /** * Verify declaration number. Positive integer of a maximum of 6 characters recommended by the documentation * * @param string $number Number to verify / convert * @return string Number */ public static function getDeclarationNumber($number) { return str_pad($number, 6, 0, STR_PAD_LEFT); } /** * Generate XML file * * @return void */ public function generateXMLFile() { $name = $this->periode.'.xml'; $fname = sys_get_temp_dir().'/'.$name; $f = fopen($fname, 'w+'); fwrite($f, $this->content_xml); fclose($f); header('Content-Description: File Transfer'); header('Content-Type: application/xml'); header('Content-Disposition: attachment; filename="'.$name.'"'); header('Expires: 0'); header('Cache-Control: must-revalidate'); header('Pragma: public'); header('Content-Length: '.filesize($fname)); readfile($fname); exit; } }