From 7c4b49285851d9312d8b00d1724b7d0365ab9764 Mon Sep 17 00:00:00 2001 From: sparkyx Date: Fri, 1 Aug 2025 10:53:20 +0200 Subject: [PATCH] Create FacturX and UBL21 ,Add Country Code and PEPPOL ID --- include/XMLDocument/FacturX.php | 162 ++++++ include/XMLDocument/InvoiceUBL21.php | 545 ++++++++++++++++++ include/XMLDocument/XMLInvoice.php | 256 ++++++++ .../class/noalyss_parameter_folder.class.php | 4 +- include/company.inc.php | 16 +- include/constant.php | 3 +- sql/upgrade.sql | 2 +- 7 files changed, 984 insertions(+), 4 deletions(-) create mode 100644 include/XMLDocument/FacturX.php create mode 100644 include/XMLDocument/InvoiceUBL21.php create mode 100644 include/XMLDocument/XMLInvoice.php diff --git a/include/XMLDocument/FacturX.php b/include/XMLDocument/FacturX.php new file mode 100644 index 000000000..8b5e5b9ff --- /dev/null +++ b/include/XMLDocument/FacturX.php @@ -0,0 +1,162 @@ +cn,$result['customer']['card_id']); + $result['customer']['siren']=$customer->strAttribut(ATTR_DEF_SIREN); + $result['customer']['siret']=$customer->strAttribut(ATTR_DEF_SIRET); + return $result; + } + /** + * @brief check that mandatory info are saved in the DB + * @param $a_error (array) array of errors, empty if nothing found + */ + function check_company_data(&$a_error) { + echo "not implemented"; + return true; + } + /** + * @brief check that mandatory info are saved in the DB for customer + * @param $customer_id (int) card of the customer FICHE.F_ID + * @param $a_error (array) array of errors, empty if nothing found + */ + function check_customer_data($customer_id,&$a_error){ + echo "not implemented"; + return true; + + } + + /** + * @brief create an XML invoice(Factur-X) based on JRN.JR_ID operation + * @parameter $jr_id (int) operation JRN.JR_ID operation + *@return XML String + *@note SIREN or SIRET is mandatory + */ + function make_xml($jr_id) + { + $this->data = $this->build_data($jr_id); + $invoice= new KINU_FX1\CrossIndustryInvoice(KINU_FX1\CrossIndustryInvoice::PROFILE_BASIC_WL); + $invoice->setInvoiceNumber($this->data['id']); + $invoice->setInvoiceType(KINU_FX1\CrossIndustryInvoice::INVOICE_TYPE_COMMERCIAL_INVOICE); + $invoice->setIssueDate(\DateTime::createFromFormat( 'Y-m-d',$this->data['issue_date'])); + + if ( $this->data['due_date'] !="") { + $invoice->setDueDate(\DateTime::createFromFormat( 'Y-m-d',$this->data['due_date'])); + }else { + $due_date=\DateTime::createFromFormat( 'Y-m-d',$this->data['issue_date']); + $due_date->modify('+ 30 days'); + $invoice->setDueDate($due_date); + + } + $supplier=new KINU_FX1\LegalEntity(); + $company = $this->load_noalyss_parameter(); + $supplier->setName($company['MY_NAME']); + $supplier->setSiren($company['SIREN']); + $supplier->setSiret($company['SIRET']); + //$supplier->setSiren('999999'); + $supplier->setVatIdentifier($company['MY_TVA']); + $supplier_addres=new KINU_FX1\Address(); + $supplier_addres->setCityName($company['MY_CITY']) + ->setCountryId($company['MY_COUNTRY_CODE']) + ->setCityName($company['MY_CITY']) + ->setLines($company['MY_STREET']); + $supplier->setAddress($supplier_addres); + $invoice->setPaymentInstruction(null); + $invoice->setPaymentMeansCode(0); + $invoice->setSeller($supplier); + $invoice->setBuyer(new KINU_FX1\LegalEntity); + $buyer=$invoice->getBuyer(); + $buyer->setName($this->data['customer']['name']); + $buyer->setSiren($this->data['customer']['siren']); + $buyer->setSiret($this->data['customer']['siret']); + $buyer->setVatIdentifier($this->data['customer']['customer_id']); + $buyer->setAddress(new KINU_FX1\Address()); + $address=$buyer->getAddress(); + $address->setLines($this->data['customer']['street']) + ->setCityName($this->data['customer']['city']) + ->setZipCode($this->data['customer']['postalzone']) + ->setCountryId($this->data['customer']['country']); + + $invoice->setCurrencyCode('EUR'); + $base=0;$vat=0; + $nb=count($this->data['operation']); + ///@note : Pour l'autoliquidation le total TVA = 0 + for ($i=0;$i < $nb;$i++) { + $base=bcadd($base,$this->data['operation'][$i]['price'],2); + $vat=bcadd($vat,$this->data['operation'][$i]['vat'],2); + $vat=bcsub($vat,$this->data['operation'][$i]['vat_reversed'],2); + } + $tt = bcadd($base,$vat,2); + /** + * @note : Le total de la facture n'est pas toujours le total du. + * il faut alors un "reste" à payer. + * Pas de détail par articles ? + */ + $invoice->setTaxBasisTotalAmount($base); + $invoice->setTaxTotalAmount($vat); + $invoice->setGrandTotalAmount($tt); + $invoice->setDuePayableAmount($tt); + $xml = KINU_FX1\XmlWriter::write($invoice); + return $xml; + } + /** + * @brief create the invoice in the right format + * @param $operation_id (int) JRN.JR_ID + * @return string PDF Invoice including the XML + */ + function create_invoice($operation_id) { + $xml = $this->make_xml($operation_id); + $facturx = new FX_ATGP\Facturx(); + $invoice=$facturx->generateFacturxFromFiles($this->pdf_filename, $xml); + return $invoice; + } + +} \ No newline at end of file diff --git a/include/XMLDocument/InvoiceUBL21.php b/include/XMLDocument/InvoiceUBL21.php new file mode 100644 index 000000000..8d3f5668f --- /dev/null +++ b/include/XMLDocument/InvoiceUBL21.php @@ -0,0 +1,545 @@ + + + [Contenu du PDF encodé en Base64] + + +@endcode + * + */ +class InvoiceUBL21 extends XMLInvoice { + + const EXTRA_PARAMETER = ["INVOICE_EMAIL_COMPANY" + , 'INVOICE_CONTACT_NAME' + , 'COMPANY_LEGAL_ENTITY' + , 'COMPANY_LEGAL_REGISTRATION' + , 'COMPANY_BANK_IBAN' + , 'COMPANY_BANK_BIC' + , 'COMPANY_UBL_ID' + , 'MY_COUNTRY_CODE' + , 'MY_NAME' + , 'MY_STREET' + , 'MY_CITY' + , 'MY_TVA' + ]; + protected $pdf_filename; //!< PDF file to insert into XML + public function get_pdf_filename() { + return $this->pdf_filename; + } + + public function set_pdf_filename($pdf_filename) { + $this->pdf_filename = $pdf_filename; + return $this; + } + + /** + * @brief check that mandatory info are saved in the DB for company (seller) + * @param $a_error (array) array of errors, empty if nothing found + */ + function check_company_data(&$a_error) { + $company = $this->load_noalyss_parameter(); + foreach (InvoiceUBL21::EXTRA_PARAMETER as $item) { + if (!isset($company[$item]) || $company[$item] == '') { + $a_error[]=$item; + } + } + if (count($a_error) == 0) { + return true; + } + return false; + } + /** + * @brief check that mandatory info are saved in the DB for customer + * @param $customer_id (int) card of the customer FICHE.F_ID + * @param $a_error (array) array of errors, empty if nothing found + * @todo : country code au lieu de country !! + */ + function check_customer_data($customer_id,&$a_error){ + $card=new \Fiche($this->cn,$customer_id); + $a_needed=[ATTR_DEF_NAME=>_("Nom") + ,ATTR_DEF_ADRESS=>_("Adresse") + ,ATTR_DEF_POSTCODE=>_("Code postal") + ,ATTR_DEF_CITY=>_("Localité") + ,ATTR_DEF_COUNTRY_CODE=>_("Code pays") + ,ATTR_DEF_NUMTVA=>_("Numéro de TVA") + ]; + + foreach ($a_needed as $item=>$value) { + if (\noalyss_trim($card->strAttribut($item))=="") { + printf (_("ATTENTION donnée manquante dans la fiche client [%s]"),$value); + } + } + if (count($a_error) == 0) { + return true; + } + return false; + } + /** + * @brief transform an operation ($jr_id) into an array, which contains + * needed information for making an e-invoice + * @param $jr_id (int) operation JRN.JR_ID + * @return array with all info7 + * @param type $jr_id + * @see XMLInvoice::build_data + */ + function build_data($jr_id): array { + $result = parent::build_data($jr_id); + /** + * Compute totals VAT and AMOUNT + */ + $nb_operation = count($result['operation']); + + /// block cac:LegalMonetaryTotal + $result['LineExtensionAmount']=0; + $result['TaxExclusiveAmount']=0; + $result['TaxInclusiveAmount']=0; + $result['PayableAmount']=0; + + // block cac:TaxTotal + $result['TaxableAmount']=0; + $result['TaxAmount']=0; + + // array for TaxSubtotal + $VAT_SubTotal=array(); + $idx_subtotal=0; + bcscale(2); + // for each operation + $VAT_SubTotal=array(); + for ($i=0;$i < $nb_operation;$i++) { + $acc_tva=\Acc_TVA::build($this->cn,$result['operation'][$i]['vat_id'] ); + $percent = bcmul($acc_tva->tva_rate,100); + // subtotal for VAT + var_dump($VAT_SubTotal); + $n = \Noalyss\Invoicing\Utility::find_idx($VAT_SubTotal,'percent',$percent); + if ($n == -1 ) { + $n=$idx_subtotal; + $VAT_SubTotal[$idx_subtotal]=array(); + $VAT_SubTotal[$idx_subtotal]['percent']=$percent; + $VAT_SubTotal[$idx_subtotal]['amount']=$VAT_SubTotal[$idx_subtotal]['vat']=0; + $idx_subtotal++; + } + /** + * @todo Pour les intracomm , quel taux utilisé ? 0 ou 21% + */ + $VAT_SubTotal[$n]['amount']=bcadd($VAT_SubTotal[$n]['amount'],$result['operation'][$i]['price']); + $VAT_SubTotal[$n]['vat']=bcadd($VAT_SubTotal[$n]['vat'],$result['operation'][$i]['vat']); + $VAT_SubTotal[$n]['vat']=bcsub($VAT_SubTotal[$n]['vat'],$result['operation'][$i]['vat_reversed']); + $result['TaxableAmount']=bcadd( $result['TaxableAmount'],$result['operation'][$i]['price']); + $result['TaxAmount']=bcadd( $result['TaxAmount'],$result['operation'][$i]['vat']); + $result['TaxAmount']=bcsub( $result['TaxAmount'],$result['operation'][$i]['vat_reversed']); + $result['operation'][$i]['vat_percent']=$percent; + } + $result['subTotalVAT']=$VAT_SubTotal; + $result['LineExtensionAmount']= $result['TaxableAmount']; + $result['TaxExclusiveAmount']= $result['TaxableAmount']; + $result['TaxInclusiveAmount']=bcadd( $result['TaxableAmount'],$result['TaxAmount']); + $result['PayableAmount']=bcadd( $result['TaxableAmount'],$result['TaxAmount']);; + + $this->data=$result; + return $result; + } + /** + * @brief Information customer + */ + function build_customer() + { + + $customer=$this->createElement('cac:AccountingCustomerParty'); + $customer_party=$customer->appendChild($this->createElement('cac:Party')); + ///@todo EndPointID doit être dans les paramètres (voir upgrade.sql) + $customer_party->appendChild($this->createElement('cbc:EndpointID',"ERROR"))->setAttribute('schemeID', 9956); + $party_name=$this->createElement('cac:PartyName'); + $party_name->appendChild($this->createElement("cbc:Name", $this->data['customer']['name'])); + $customer_party->appendChild($party_name); + $postal_address=$customer_party->appendChild($this->createElement('cac:PostalAddress')); + $postal_address->appendChild($this->createElement("cbc:StreetName", $this->data['customer']['street'])); + $postal_address->appendChild($this->createElement("cbc:CityName", $this->data['customer']['city'])); + $postal_address->appendChild($this->createElement("cbc:PostalZone", $this->data['customer']['postalzone'])); + ///@todo customer = countryCode doit être dans les paramètres (voir upgrade.sql) + $country_code ="ERROR"; + $country=$postal_address->appendChild($this->createElement("cac:Country")); + $country->appendChild($this->createElement('cbc:IdentificationCode',$country_code??"ERROR:COUNTRY_CODE")); + $postal_address->appendChild($country); + + // Tax Schem + + + $tax=$this->createElement('cac:PartyTaxScheme'); + $tax->appendChild($this->createElement('cbc:CompanyID',$this->data["customer"]['customer_id'])); + + $tax_scheme=$this->createElement('cac:TaxScheme'); + $tax_scheme->appendChild($this->createElement('cbc:ID',"VAT")); + $tax->appendChild($tax_scheme); + // LegalEntity + $ple=$this->createElement('cac:PartyLegalEntity'); + ///@todo customer = name doit être fiche + $ple->appendChild($this->createElement("cbc:RegistrationName", $this->data['customer']['name']??"ERROR")); + ///@todo customer_id = numéro de TVA doit être dans fiche + $ple->appendChild($this->createElement("cbc:CompanyID", $this->data['customer']['customer_id']??"ERROR")); + + // assemble supplier + $customer_party->appendChild($tax); + $customer_party->appendChild($ple); + $customer->appendChild($customer_party); + return $customer; + } + /** + * @brief Build XML Block for payment + * @code + + 30 + Invoice 2019000005 + + BE54000000000097 + + BPOTBEB1 + + + + * @endcode + */ + function build_paymentInfo() + { + $payment=$this->createElement("cac:PaymentMeans"); + $payment->appendChild($this->createElement('cbc:PaymentMeansCode',30)); + ///@note cbc:PaymentID est la communication lors du paiement + $payment->appendChild($this->createElement('cbc:PaymentID',$this->data["id"])); + $f=$this->createElement ('cac:PayeeFinancialAccount'); + ///@todo customer = IBAN doit être dans les paramètres (voir upgrade.sql) + $f->appendChild($this->createElement("cbc:ID", "ERROR:IBAN")); + $g=$this->createElement("cac:FinancialInstitutionBranch"); + ///@todo customer = BIC doit être dans les paramètres (voir upgrade.sql) + $g->appendChild($this->createElement("cbc:ID", "ERROR:BIC")); + $f->appendChild($g); + + $payment->appendChild($f); + return $payment; + } + /** + * @brief Information supplier + */ + function build_supplier() + { + $company = $this->load_noalyss_parameter(); + + $supplier=$this->createElement('cac:AccountingSupplierParty'); + $supplier_party=$supplier->appendChild($this->createElement('cac:Party')); + $supplier_party->appendChild($this->createElement('cbc:EndpointID',$company['COMPANY_UBL_ID']??"ERROR"))->setAttribute('schemeID', 9956); + $party_name=$this->createElement('cac:PartyName'); + $party_name->appendChild($this->createElement('cbc:Name', $this->data['supplier']['name'])); + $supplier_party->appendChild($party_name); + $postal_address=$supplier_party->appendChild($this->createElement('cac:PostalAddress')); + $postal_address->appendChild($this->createElement("cbc:StreetName", $this->data['supplier']['street'])); + $postal_address->appendChild($this->createElement("cbc:CityName", $this->data['supplier']['city'])); + $postal_address->appendChild($this->createElement("cbc:PostalZone", $this->data['supplier']['postalzone'])); + $country_code = $company['COUNTRY_CODE']; + $country=$postal_address->appendChild($this->createElement("cac:Country")); + $country->appendChild($this->createElement('cbc:IdentificationCode',$country_code??"ERROR")); + $postal_address->appendChild($country); + + // Tax Schem + $tax=$this->createElement('cac:PartyTaxScheme'); + $tax->appendChild($this->createElement('cbc:CompanyID',$this->data["supplier"]['supplier_id'])); + $tax_scheme=$this->createElement('cac:TaxScheme'); + $tax_scheme->appendChild($this->createElement('cbc:ID',"VAT")); + $tax->appendChild($tax_scheme); + // LegalEntity + $ple=$this->createElement('cac:PartyLegalEntity'); + $ple->appendChild($this->createElement("cbc:RegistrationName", $company['COMPANY_LEGAL_REGISTRATION']??"ERROR")); + $ple->appendChild($this->createElement("cbc:CompanyID", $this->data['supplier']['supplier_id']??"ERROR")); + $ple->appendChild($this->createElement("cbc:CompanyLegalForm", $company['COMPANY_LEGAL_ENTITY']??"ERROR")); + $contact=$this->createElement('cac:Contact'); + $contact->appendChild($this->createElement("cbc:Name",$company['INVOICE_CONTACT_NAME']??"ERROR")); + $contact->appendChild($this->createElement("cbc:ElectronicMail",$company['INVOICE_EMAIL_COMPANY']??"ERROR")); + + // assemble supplier + $supplier_party->appendChild($tax); + $supplier_party->appendChild($ple); + $supplier_party->appendChild($contact); + $supplier->appendChild($supplier_party); + + + return $supplier; + } + /** + * @brief TaxTotal Block + * @code + + 516 + + 2400 + 504 + + S + 21 + + VAT + + + + + 200 + 12 + + S + 6 + + VAT + + + + + * @endcode + */ + function build_taxTotal() + { + $taxTotal=$this->createElement("cac:TaxTotal"); + $taxTotal->appendChild($this->createElement('cbc:TaxAmount',$this->data['TaxAmount'])) + ->setAttribute("currencyID","EUR"); + // for subTotal + $subTotal=$this->data['subTotalVAT']; + $nb_sub=count($subTotal); + for ($i=0;$i<$nb_sub;$i++) { + $subTotalXML=$this->createElement("cac:TaxSubtotal"); + $subTotalXML->appendChild($this->createElement('cbc:TaxableAmount',$subTotal[$i]['amount'])) + ->setAttribute("currencyID","EUR"); + $subTotalXML->appendChild($this->createElement('cbc:TaxAmount',$subTotal[$i]['vat'])) + ->setAttribute("currencyID","EUR"); + $taxCategory=$this->createElement("cac:TaxCategory"); + $taxCategory->appendChild($this->createElement("cbc:ID","S")); + $taxCategory->appendChild($this->createElement("cbc:Percent",$subTotal[$i]['percent'])); + $taxScheme=$this->createElement("cac:TaxScheme"); + $taxScheme->appendChild($this->createElement("cbc:ID", "VAT")); + $taxCategory->appendChild($taxScheme); + $subTotalXML->appendChild($taxCategory); + $taxTotal->appendChild($subTotalXML); + } + + + return $taxTotal; + } + /** + * @brief legalMonetaryTotal + * @code + + 2600 + 2600 + 3116 + 3116 + + * @endcode + */ + function build_legalMonetaryTotal() + { + $result=$this->createElement('cac:LegalMonetaryTotal' ); + $result->appendChild($this->createElement("cbc:LineExtensionAmount",$this->data['LineExtensionAmount'])) + ->setAttribute("currencyID","EUR"); + $result->appendChild($this->createElement("cbc:TaxExclusiveAmount",$this->data['TaxExclusiveAmount'])) + ->setAttribute("currencyID","EUR"); + $result->appendChild($this->createElement("cbc:TaxInclusiveAmount",$this->data['TaxInclusiveAmount'])) + ->setAttribute("currencyID","EUR"); + $result->appendChild($this->createElement("cbc:PayableAmount",$this->data['PayableAmount'])) + ->setAttribute("currencyID","EUR"); + return $result; + + } + /** + * @brief cac:InvoiceLine + * @code + + 2 + 10 + 200 + + Good X + + S + 6 + + VAT + + + + + 20 + + + * @endcode + * @parameter $i (int) idx f $this->data->operation[$i] + * + */ + function build_invoiceLine($i) + { + + $result=$this->createElement('cac:InvoiceLine'); + $row=$this->data["operation"][$i]; + $result->appendChild($this->createElement("cbc:ID", $i)); + ///@todo , les unités de quantités devraient être ajoutés à NOALYSS + /// il faut adapter les fiches + $result->appendChild( + $this->createElement("cbc:InvoicedQuantity", $row['quantity'])) + ->setAttribute("unitCode", "EA"); + $result->appendChild($this->createElement("cbc:LineExtensionAmount", $row['price'])) + ->setAttribute("currencyID","EUR"); + $item=$this->createElement("cac:Item"); + $card=new \Fiche($this->cn,$row['card_id']); + $item->appendChild($this->createElement("cbc:Name", $card->strAttribut(ATTR_DEF_NAME))); + $classifiedTaxCat=$this->createElement("cac:ClassifiedTaxCategory"); + ///@todo cbc:ID S = standard rate et que se passe-t'il pour l'autoliquidation ??? + /// Il faut ajouter dans TVA_RATE , un code pour la TVA, + $classifiedTaxCat->appendChild($this->createElement("cbc:ID", "S")); + $classifiedTaxCat->appendChild($this->createElement("cbc:Percent", $row['vat_percent'])); + $tax_scheme=$this->createElement('cac:TaxScheme'); + $tax_scheme->appendChild($this->createElement("cbc:ID", "VAT")); + $classifiedTaxCat->appendChild($tax_scheme); + $item->appendChild($classifiedTaxCat); + $result->appendChild($item); + $price=$result->appendChild($this->createElement("cac:Price")); + $price->appendChild($this->createElement("cbc:PriceAmount", $row['price'])) + ->setAttribute("currencyID","EUR"); + $result->appendChild($price); + + return $result; + + } + /** + * @brief Insert a PDF in the XML +@code + + P01 + InvoicePDF + Facture PDF + + $base64Pdf + + + + + REF_ODT_001 + OpenDocument + Fichier OpenDocument + + [base64-encodage du fichier] + + + + +@endcode + * @return \DOMElement + */ + function build_Invoice():\DOMElement + { + if ( $this->pdf_filename == "") return null; + $result=$this->createElement("AdditionalDocumentReference"); + /** $pdf_filename = 'chemin/vers/votre/fichier.pdf';*/ + + // Lire le fichier PDF + // $pdfContent = file_get_contents($pdfPath); + + // Encoder le PDF en base64 + // $base64Pdf = base64_encode($pdfContent); + + return $result; + } + /** + * @brief create an XML invoice(UBL2.1) based on JRN.JR_ID operation + * @parameter $jr_id (int) operation JRN.JR_ID operation + * @return XML String + */ + function make_xml($jr_id) + { + + + $this->data = $this->build_data($jr_id); + + // HEADER + $root=$this->createElementNS('urn:oasis:names:specification:ubl:schema:xsd:Invoice-2',"Invoice",); + $root->setAttributeNS('http://www.w3.org/2000/xmlns/',"xmlns:cac", "urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"); + $root->setAttributeNS('http://www.w3.org/2000/xmlns/',"xmlns:cbc", "urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"); + $root->appendChild($this->createElement('cbc:CustomizationID',"urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0")); + $root->appendChild($this->createElement('cbc:ProfileID',"urn:fdc:peppol.eu:2017:poacc:billing:01:1.0")); + + $root->appendChild($this->createElement('cbc:ID',$this->data['id'])); + $root->appendChild($this->createElement('cbc:IssueDate',$this->data['issue_date'])); + if ($this->data ['due_date'] != '') { + $root->appendChild($this->createElement('cbc:DueDate',$this->data['due_date'])); + } + $root->appendChild($this->createElement('cbc:InvoiceTypeCode',380)); + $root->appendChild($this->createElement('cbc:DocumentCurrencyCode','EUR')); + + /** + * insert PDF in the XML + */ + $x = $this->build_Invoice(); + if ($x != null ) { + $root->appendChild($x); + } + + // add the supplier + $root->appendChild($this->build_supplier()); + // add the customer + $root->appendChild($this->build_customer()); + // add the payment + $root->appendChild($this->build_paymentInfo()); + // Add cac:TaxTotal + $root->appendChild($this->build_taxTotal()); + + // Add cac:LegalMonetatyTotal + $root->appendChild($this->build_legalMonetaryTotal()); + + // add all the invoiceline + // operation + $nb_operation=count($this->data["operation"]); + for ($i=0;$i < $nb_operation ; $i++) { + $root->appendChild($this->build_invoiceLine($i)); + + } + + $this->append($root); + $this->formatOutput=true; + return $this->saveXML(); + } + /** + * @brief create the invoice in the right format, with PDF if any + * @param $operation_id (int) JRN.JR_ID + * @return string XML Invoice + */ + function create_invoice($operation_id) { + return $this->make_xml($operation_id); + } +} diff --git a/include/XMLDocument/XMLInvoice.php b/include/XMLDocument/XMLInvoice.php new file mode 100644 index 000000000..9c8097930 --- /dev/null +++ b/include/XMLDocument/XMLInvoice.php @@ -0,0 +1,256 @@ + 25.822 + [issue_date] => 2025-06-10 + [due_date] => + [supplier] => Array + ( + [name] => My company sprl + [street] => Allée des Zoulons + [postalzone] => 1080 + [city] => Molenbeek Saint Jean + [country] => BE + [supplier_id] => BE012345678 + [registration_name] => My Company sprl + ) + + [customer] => Array + ( + [name] => This asbl + [street] => + [postalzone] => + [city] => + [country] => + [customer_id] => + [registration_name] => This asbl + ) + + [currency] => 0 + [operation] => Array + ( + [0] => Array + ( + [card_id] => 568 + [quantity] => 1.0000 + [price] => 10.0000 + [vat] => 2.1000 + [vat_id] => 1 + [vat_reversed] => 0.0000 + ) + + [1] => Array + ( + [card_id] => 164 + [quantity] => 5.0000 + [price] => 83.4500 + [vat] => 17.5200 + [vat_id] => 1 + [vat_reversed] => 0.0000 + ) + + [2] => Array + ( + [card_id] => 483 + [quantity] => 1.0000 + [price] => 72.0000 + [vat] => 15.1200 + [vat_id] => 5 + [vat_reversed] => 15.1200 + ) + + ) + +) + + + * @endcode + * + */ +abstract class XMLInvoice extends \DOMDocument +{ + protected $cn; //!< Database conx , current folder + protected $data; //! $data Array data retrieve from DB + function __construct(\Database $conx) + { + parent::__construct("1.0", "UTF-8"); + $this->cn=$conx; + $this->data=[]; + } + /** + * @brief return data + * @return array + */ + public function get_data() { + return $this->data; + } + /** + * @brief returns data + * @param $data (array) + * @return XMLInvoice + */ + public function set_data($data): XMLInvoice { + $this->data = $data; + return $this; + } + + /** + * @brief get Database Connexion + * @param $cn (\Database) + * @return XMLInvoice + */ + + public function get_db_conx():\Database { + return $this->cn; + } + /** + * @brief set Database Connexion + * @param $cn (\Database) + * @return XMLInvoice + */ + public function set_db_conx(\Database $cn): XMLInvoice { + $this->cn = $cn; + return $this; + } + + /** + * @brief transform an operation ($jr_id) into an array, which contains + * needed information for making an e-invoice + * @param $jr_id (int) operation JRN.JR_ID + * @return array with all info7 + + * + */ + function build_data($jr_id):array + { + global $g_parameter; + $operation = new \Acc_Sold($this->cn,$jr_id); + + $operation->get(); + $result=array(); + $result["id"]= $operation->det->jr_pj_number; + $result["issue_date"]=$operation->det->jr_date; + $result["due_date"]=$operation->det->jr_ech; + // supplier + $result['supplier']=array(); + $result['supplier']['name']=$g_parameter->MY_NAME; + $result['supplier']['street']=$g_parameter->MY_STREET; + $result['supplier']['postalzone']=$g_parameter->MY_POSTCODE; + $result['supplier']['city']=$g_parameter->MY_CITY; + $result['supplier']['country']=$g_parameter->MY_COUNTRY; + $result['supplier']['supplier_id']=$g_parameter->MY_TVA; + // official name of the company + $result['supplier']['registration_name']=$g_parameter->MY_NAME; + // official ID , like VAT + $result['supplier']['supplier_id']=str_replace([" ",".","-","/"],"" ,$g_parameter->MY_TVA); + //@todo + //Autre parametre comme email dans Parameter_Extra_SQL + // + // + // == $result['supplier']['supplier_email']=$g_parameter->; + //@todo TESTER S'IL Y A QQ'CHOSE DE VENDU ! + //customer + $customer=new \Fiche($this->cn,$operation->det->array[0]['qs_client']); + $result['customer']=array(); + $result['customer']['card_id']=$operation->det->array[0]['qs_client']; + $result['customer']['name']=$customer->strAttribut(ATTR_DEF_NAME); + $result['customer']['street']=$customer->strAttribut(ATTR_DEF_ADRESS); + $result['customer']['postalzone']=$customer->strAttribut(ATTR_DEF_POSTCODE); + $result['customer']['city']=$customer->strAttribut(ATTR_DEF_CITY); + + // find country_code of this card + + $result['customer']['country']=$customer->strAttribut(ATTR_DEF_COUNTRY); + + $result['customer']['customer_id']=str_replace([" ",".","-","/"],"" ,$customer->strAttribut(ATTR_DEF_NUMTVA)); + // official name of the company + $result['customer']['registration_name']=$customer->strAttribut(ATTR_DEF_NAME); + // official ID , like VAT + $result['customer']['customer_id']=$customer->strAttribut(ATTR_DEF_NUMTVA); + // +++TODO+++ adapt for all currency + // currency must be EURO ! + $result['currency']=$operation->det->currency_id; + // goods and services + $result['operation']=array(); + $nb_operation= count($operation->det->array); + for ($i=0;$i < $nb_operation;$i++) { + $result['operation'][$i]['card_id']=$operation->det->array[$i]['qs_fiche']; + $result['operation'][$i]['quantity']=$operation->det->array[$i]['qs_quantite']; + $result['operation'][$i]['price']=$operation->det->array[$i]['qs_price']; + $result['operation'][$i]['vat']=$operation->det->array[$i]['qs_vat']; + $result['operation'][$i]['vat_id']=$operation->det->array[$i]['qs_vat_code']; + $result['operation'][$i]['vat_reversed']=$operation->det->array[$i]['qs_vat_sided']; + } + print_r($result); + return $result; + + } + /** + * @brief make an array of parameter_extra where pe_code as key and pe_value + * as value + * @return array + */ + function load_noalyss_parameter() + { + $r=$this->cn->get_array(' + select pe_code, pe_value from parameter_extra + union all + select pr_id,pr_value + from parameter'); + + return array_column($r,"pe_value","pe_code"); + } + /** + * @brief create an XML invoice based on JRN.JR_ID operation. + * The PDF file could be added afterward. + * @parameter $jr_id (int) operation JRN.JR_ID operation + */ + abstract function make_xml($jr_id); + /** + * @brief check that mandatory info are saved in the DB for company (seller) + * @param $a_error (array) array of errors, empty if nothing found + */ + abstract function check_company_data(&$a_error) ; + /** + * @brief check that mandatory info are saved in the DB for customer + * @param $customer_id (int) card of the customer FICHE.F_ID + * @param $a_error (array) array of errors, empty if nothing found + */ + abstract function check_customer_data($customer_id,&$a_error) ; + /** + * @brief create the invoice in the right format, with PDF if any + * @param $operation_id (int) JRN.JR_ID + * @return string : XML or PDF format + */ + abstract function create_invoice($operation_id) ; +} diff --git a/include/class/noalyss_parameter_folder.class.php b/include/class/noalyss_parameter_folder.class.php index de5e38aea..18502ed43 100644 --- a/include/class/noalyss_parameter_folder.class.php +++ b/include/class/noalyss_parameter_folder.class.php @@ -49,7 +49,7 @@ class Noalyss_Parameter_Folder var $MY_DEFAULT_ROUND_ERROR_CRED; var $MY_ANC_FILTER; var $MY_CURRENCY; - var $MY_COUNTRY; + var $MY_COUNTRY_CODE; var $MY_REPORT; //!< In Belgium , we need a report on the beginning of the exercice , not in France, @@ -78,6 +78,7 @@ MY_NUMBER= [ {$this->MY_NUMBER }] MY_POSTCODE= [ {$this->MY_POSTCODE }] MY_PHONE= [ {$this->MY_PHONE }] MY_COUNTRY= [ {$this->MY_COUNTRY }] +MY_COUNTRY_CODE= [ {$this->MY_COUNTRY_CODE }] MY_CITY= [ {$this->MY_CITY }] MY_FAX= [ {$this->MY_FAX }] MY_ANALYTIC= [ {$this->MY_ANALYTIC }] @@ -191,6 +192,7 @@ EOF; $this->save('MY_POSTCODE'); $this->save('MY_PHONE'); $this->save('MY_COUNTRY'); + $this->save('MY_COUNTRY_CODE'); $this->save('MY_CITY'); $this->save('MY_FAX'); $this->save('MY_ANALYTIC'); diff --git a/include/company.inc.php b/include/company.inc.php index 4248b1b4b..065fd159f 100644 --- a/include/company.inc.php +++ b/include/company.inc.php @@ -40,6 +40,7 @@ if (isset($_POST['record_company'])) $m->MY_PHONE=$http->post("p_tel"); $m->MY_FAX=$http->post("p_fax"); $m->MY_COUNTRY=$http->post("p_pays"); + $m->MY_COUNTRY_CODE=$http->post("p_country_code"); $m->MY_CHECK_PERIODE=$http->post("p_check_periode"); $m->MY_DATE_SUGGEST=$http->post("p_date_suggest"); $m->MY_ANALYTIC=$http->post("p_compta"); @@ -209,7 +210,7 @@ $all->style=' class="input_text"'; $all->table=1; $all->style=' class="input_text"'; ?> - + input("p_commune", $my->MY_CITY) ?>
@@ -220,6 +221,19 @@ $all->style=' class="input_text"'; ?> input("p_pays", $my->MY_COUNTRY) ?> +
+
+ get_array("select cc_code,format('%s %s',cc_code,cc_name) str_name from country_code_ref order by 2,1"); + $code= array_column($x, "str_name","cc_code"); + $country_code->transform($code); + $country_code->selected=$my->MY_COUNTRY_CODE; + $all->table=1; + $all->style=' class="input_text"'; + ?> + + input("p_country_code" ) ?>