Merge branch '250826-e-einvoice' into unstable

* 250826-e-einvoice: (29 commits)
  Update VENDOR : remove useless
  Factur-X norme 3 (May 15th, 2025)
  E-INVOICE : if upload an XML file than the embedded document will be saved separately, if there is no embedded document a standard document with info from XML will be generated
  E-INVOICE , when loading a XML , the file is splitted into a PDF and XML
  XMLInvoiceReader : extract information from XML
  E-INVOICE : corrige PriceAmount
  E-INVOICE : show the XML to download
  Database: upload file function
  E-INVOICE : export XML if exists from ledger_detail_bottom
  E-INVOICE : create standard invoice if not convert
  E-INVOICE: small bugs UBL21
  E-INVOICE: negative amount
  e-invoice : add communication , bank and buyer reference
  e-invoice : display errors if cannot be created
  Typo : space quantity
  PHPUNIT : adapt 12.3
  E-INVOICE: check data before generating E-INVOICE: add missing for Belgium : BuyerReference and Due_Date
  E-INVOICE: add code quantity
  E-INVOICE : add AdditionalDocument
  XMLInvoice adapt currency
  ...
This commit is contained in:
sparkyx 2025-10-06 09:01:38 +02:00
commit 9898434767
169 changed files with 3614 additions and 15659 deletions

View file

@ -0,0 +1,137 @@
<?php
namespace Noalyss\XMLDocument;
/*
* This file is part of NOALYSS.
*
* NOALYSS 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 2 of the License, or
* (at your option) any later version.
*
* NOALYSS 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 NOALYSS; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
// Copyright Author Dany De Bontridder danydb@aevalys.eu 22/10/23
/**
* @file
* @brief Give the error message thanks the code for FacturX and UBL21
*
*/
/**
* @class
* @brief Give the error message thanks the code for FacturX and UBL21
*
property $a_error (double array)
keys :
- general ,
- customer,
- ATTR_DEF_NAME=>'CUST_NAME'
- ATTR_DEF_ADRESS=>'CUST_ADDR'
- ATTR_DEF_POSTCODE=>'CUST_POSTCD'
- ATTR_DEF_CITY=>'CUST_CITY'
- ATTR_DEF_COUNTRY_CODE=>'CUST_CDCOUNTRY'
- ATTR_DEF_NUMTVA=>'CUST_VAT'
- ATTR_DEF_PEPPOLID=>'CUST_PEPPOLID'
- company,
- "INVOICE_EMAIL_COMPANY" company's email
- 'INVOICE_CONTACT_NAME' contact name
- 'COMPANY_LEGAL_ENTITY' legal form of company
- 'COMPANY_LEGAL_REGISTRATION' full name
- 'COMPANY_BANK_IBAN' IBAN bank account
- 'COMPANY_BANK_BIC' BIC bank account
- 'COMPANY_UBL_ID' PEPPOL id
- 'MY_COUNTRY_CODE' country code (normally BE)
- 'MY_NAME' short company name
- 'MY_STREET' address
- 'MY_CITY' address
- 'MY_TVA' VAT number
*
*/
class Error_Message
{
private $a_error;
private $a_message_company;
private $a_message_customer;
/**
* @brief constructo
* @param $a_error (double array)
*/
public function __construct($a_error)
{
$this->a_error = $a_error;
$this->a_message_company = array(
"INVOICE_EMAIL_COMPANY" => _("L'email de la société ")
, 'INVOICE_CONTACT_NAME' => _("Nom du contact")
, 'COMPANY_LEGAL_ENTITY' => _("Type de société (SRL,ASBL,...)")
, 'COMPANY_LEGAL_REGISTRATION' => _("Nom complet de la société")
, 'COMPANY_BANK_IBAN' => _("Compte en banque (IBAN) de la société")
, 'COMPANY_BANK_BIC' => _("Code BIC de compte en banque")
, 'COMPANY_UBL_ID' => _("Identifiant PEPPOL")
, 'MY_COUNTRY_CODE' => _('Code Pays')
, 'MY_NAME' => _("Nom de la société")
, 'MY_STREET' => _("Adresse de la société")
, 'MY_CITY' => _("Ville")
, 'MY_TVA' => _("Numéro de TVA")
// , 'SIREN'=> 'SIREN'
// , 'SIRET'=> 'SIRET'
);
$this->a_message_customer = array(
'name' => _("Nom")
, 'street' => _("Adresse")
, 'postalzone' => _("Code postal")
, 'city' => _("Ville")
, 'country'=>_("Code pays")
, 'customer_id' => _("Numéro de TVA")
, 'endpoint_id' => _('Identifiant PEPPOL')
);
}
public function get_a_error()
{
return $this->a_error;
}
public function set_a_error($a_error)
{
$this->a_error = $a_error;
return $this;
}
/**
* @brief returns the text of an error
* @param $code (string or number)
* @param $type (string) customer , general or company
*/
function get_message_error($code, $type)
{
if ($type == "customer")
{
$a_error = $this->a_error['customer'];
$a_message = $this->a_message_customer;
} else if ($type == "company")
{
$a_error = $this->a_error['company'];
$a_message = $this->a_message_company;
} else
{
throw new \Exception("EM116: unknow type");
}
return $a_message[$code];
}
}

View file

@ -19,15 +19,25 @@ namespace Noalyss\XMLDocument;
*/
// Copyright Author Dany De Bontridder danydb@aevalys.eu 22/10/23
use \Kinulab\Facturx\CrossIndustryInvoice as KINU_FX1;
use \Atgp\FacturX as FX_ATGP;
use horstoeko\zugferd\codelists\ZugferdCountryCodes;
use horstoeko\zugferd\codelists\ZugferdCurrencyCodes;
use horstoeko\zugferd\codelists\ZugferdElectronicAddressScheme;
use horstoeko\zugferd\codelists\ZugferdInvoiceType;
use horstoeko\zugferd\codelists\ZugferdReferenceCodeQualifiers;
use horstoeko\zugferd\codelists\ZugferdUnitCodes;
use horstoeko\zugferd\codelists\ZugferdVatCategoryCodes;
use horstoeko\zugferd\codelists\ZugferdVatTypeCodes;
use horstoeko\zugferd\ZugferdDocumentBuilder;
use horstoeko\zugferd\ZugferdProfiles;
/**
* @file
* @brief answer to an inplace object
*/
class FacturX extends XMLInvoice
{
const EXTRA_PARAMETER = ["INVOICE_EMAIL_COMPANY"
const EXTRA_PARAMETER = [
"INVOICE_EMAIL_COMPANY"
, 'INVOICE_CONTACT_NAME'
, 'COMPANY_LEGAL_ENTITY'
, 'COMPANY_LEGAL_REGISTRATION'
@ -40,38 +50,54 @@ class FacturX extends XMLInvoice
, 'MY_CITY'
, 'MY_COUNTRY_CODE'
, 'MY_TVA'
,'SIREN'
,'SIRET'
// ,'SIREN'
// ,'SIRET'
];
protected $pdf_filename;
function build_data($jr_id): array {
$result = parent::build_data($jr_id);
$customer=new \Fiche($this->cn,$result['customer']['card_id']);
$result['customer']['siren']=$customer->get_attribute(ATTR_DEF_SIREN);
$result['customer']['siret']=$customer->get_attribute(ATTR_DEF_SIRET);
return $result;
$this->data=parent::build_data($jr_id);
return $this->data;
}
/**
* @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;
function check_company_data()
{
$a_error=array();
$company = $this->load_noalyss_parameter();
foreach (FacturX::EXTRA_PARAMETER as $item) {
if (!isset($company[$item]) || trim($company[$item]) == '') {
$a_error[]=$item;
}
}
return $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
*/
function check_customer_data($customer_id,&$a_error){
echo "not implemented";
return true;
function check_customer_data($customer_id){
$a_error=array();
$a_needed=[ATTR_DEF_NAME=>'name'
,ATTR_DEF_ADRESS=>'street'
,ATTR_DEF_POSTCODE=>'postalzone'
,ATTR_DEF_CITY=>'city'
,ATTR_DEF_COUNTRY_CODE=>'country'
,ATTR_DEF_NUMTVA=>'customer_id'
,ATTR_DEF_PEPPOLID=>'endpoint_id'
];
foreach ($a_needed as $item=>$value) {
if ( $this->data['customer'][$value]=="") {
$a_error[]=$value;
}
}
return $a_error;
}
/**
@ -83,53 +109,81 @@ class FacturX extends XMLInvoice
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']);
// var_dump($this->data);
$documentBuilder = ZugferdDocumentBuilder::createNew(ZugferdProfiles::PROFILE_XRECHNUNG_3);
$documentBuilder->setDocumentInformation(
$this->data['id']
,"380"
,\DateTime::createFromFormat( 'Y-m-d',$this->data["issue_date"])
, $this->data['currency']
);
$invoice->setCurrencyCode('EUR');
$documentBuilder->addDocumentPaymentTerm(
sprintf("IBAN %s",$company['COMPANY_BANK_IBAN'])
,\DateTime::createFromFormat( 'Y-m-d',$this->data["due_date"])
, $this->data['info']['communication']
);
//------------------------------------------------
// SELLER
//------------------------------------------------
$documentBuilder->setDocumentSeller($company['MY_NAME'], );
$documentBuilder->addDocumentSellerGlobalId($company['SIREN'], '0009');
$documentBuilder->addDocumentSellerTaxNumber($company['MY_TVA']);
$documentBuilder->addDocumentSellerVATRegistrationNumber($company['MY_TVA']);
$documentBuilder->setDocumentSellerAddress(
$company['MY_STREET']
, '', ''
, $company['MY_POSTCODE']
, $company['MY_CITY']
,$company['MY_COUNTRY_CODE']);
$documentBuilder->setDocumentSellerCommunication(ZugferdElectronicAddressScheme::UNECE3155_EM
, $company["INVOICE_EMAIL_COMPANY"]);
//------------------------------------------------
// BUYER
//------------------------------------------------
$documentBuilder->setDocumentBuyer($this->data['customer']['name'], $this->data['customer']['customer_id']);
$documentBuilder->setDocumentBuyerAddress(
$this->data['customer']['street']
, ''
, ''
, $this->data['customer']['postalzone']
, $this->data['customer']['city']
, $this->data['customer']['country']
);
// $documentBuilder->setDocumentBuyerContact('H. Meier', 'Einkauf', '+49-333-4444444', '+49-333-5555555', 'hm@kunde.de');
// $documentBuilder->setDocumentBuyerCommunication(ZugferdElectronicAddressScheme::UNECE3155_EM, 'purchase@kunde.de');
$documentBuilder->setDocumentBuyerOrderReferencedDocument($this->data['info']['order']);
//------------------------------------------------
// Item & total
//------------------------------------------------
$base=0;$vat=0;
$nb=count($this->data['operation']);
///@note : Pour l'autoliquidation le total TVA = 0
for ($i=0;$i < $nb;$i++) {
$documentBuilder->addNewPosition($i+1);
$documentBuilder->setDocumentPositionProductDetails($this->data['operation'][$i]['qcode']
,$this->data['operation'][$i]['name']
,$this->data['operation'][$i]['description']
);
$documentBuilder->setDocumentPositionNetPrice($this->data['operation'][$i]['price']);
$documentBuilder->setDocumentPositionQuantity($this->data['operation'][$i]['quantity']
,$this->data['operation'][$i]['code_quantity']
);
$documentBuilder->addDocumentPositionTax(
$this->data['operation'][$i]['vat_code']
, ZugferdVatTypeCodes::VALUE_ADDED_TAX
, bcmul($this->data['operation'][$i]['vat_rate'],100,2)
);
$documentBuilder->setDocumentPositionLineSummation($this->data['operation'][$i]['price']);
$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);
@ -140,12 +194,40 @@ class FacturX extends XMLInvoice
* 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;
///@TODO DNY : ajouter les TVA par types ( addDocumentTax)
/// ainsi que la Somme des totaux (setDocumentSummation)
$subTotal=$this->data['subTotalVAT'];
$nb_sub=count($subTotal);
for ($i=0;$i<$nb_sub;$i++)
{
$documentBuilder->addDocumentTax(
$subTotal[$i]["vat_code"]
, ZugferdVatTypeCodes::VALUE_ADDED_TAX
,sprintf("%.2f",$subTotal[$i]['amount'])
, sprintf("%.2f",$subTotal[$i]['vat'])
, sprintf("%.2f",$subTotal[$i]['percent'])
);
}
$documentBuilder->setDocumentSummation(
sprintf("%.2f",$this->data['TaxInclusiveAmount'])
, sprintf("%.2f",$this->data['PayableAmount'])
, sprintf("%.2f",$this->data['TaxExclusiveAmount'])
, 0.0
, 0.0
, sprintf("%.2f",$this->data['LineExtensionAmount'])
, sprintf("%.2f",(bcsub($this->data['TaxInclusiveAmount'],
$this->data['TaxExclusiveAmount'],
2)
)
)
, 0
);
return $documentBuilder;
}
/**
* @brief create the invoice in the right format
@ -153,10 +235,14 @@ class FacturX extends XMLInvoice
* @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;
$documentBuilder= $this->make_xml($operation_id);
$invoice = \horstoeko\zugferd\ZugferdDocumentPdfBuilder::fromPdfFile($documentBuilder, $this->pdf_filename);
$invoice->generateDocument();
$invoice->saveDocument($this->pdf_filename."-new.pdf");
return $invoice->downloadString();
}
}

View file

@ -23,12 +23,14 @@ namespace Noalyss\XMLDocument;
/**
* @file
* @brief answer to an inplace object
* @brief UBL2.1 Belgique
* - $pdf_filename PDF file to insert into XML, it is the file on the filesystem
*/
/**
* @class
* @brief UBL2.1 Belgique
* @note Doit contenir le PDF
* @note Doit contenir le PDF.
* - $pdf_filename PDF file to insert into XML, it is the file on the filesystem
@code
<cac:Attachment>
<cbc:EmbeddedDocumentBinaryObject mimeCode="application/pdf" filename="facture.pdf" encodingCode="Base64">
@ -52,32 +54,37 @@ class InvoiceUBL21 extends XMLInvoice {
, 'MY_STREET'
, 'MY_CITY'
, 'MY_TVA'
, 'INVOICE_EMAIL_COMPANY'
];
protected $pdf_filename; //!< PDF file to insert into XML
protected $pdf_filename; //!< PDF file to insert into XML,
// it is the file on the filesystem
public function get_pdf_filename() {
return $this->pdf_filename;
}
public function set_pdf_filename($pdf_filename) {
$this->pdf_filename = $pdf_filename;
return $this;
/**
* @brief display_error display a warning with all error
*/
function display_error()
{
$a_error=$this->verify();
include NOALYSS_TEMPLATE."/invoiceUBL21-display_error.php";
}
/**
* @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) {
function check_company_data() {
// var $a_error (array) contains the errors for the company
$a_error=array();
$company = $this->load_noalyss_parameter();
foreach (InvoiceUBL21::EXTRA_PARAMETER as $item) {
if (!isset($company[$item]) || $company[$item] == '') {
if (!isset($company[$item]) || trim($company[$item]) == '') {
$a_error[]=$item;
}
}
if (count($a_error) == 0) {
return true;
}
return false;
return $a_error;
}
/**
* @brief check that mandatory info are saved in the DB for customer
@ -85,26 +92,26 @@ class InvoiceUBL21 extends XMLInvoice {
* @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")
function check_customer_data($customer_id){
$a_error=array();
$a_needed=[ATTR_DEF_NAME=>'name'
,ATTR_DEF_ADRESS=>'street'
,ATTR_DEF_POSTCODE=>'postalzone'
,ATTR_DEF_CITY=>'city'
,ATTR_DEF_COUNTRY_CODE=>'country'
,ATTR_DEF_NUMTVA=>'customer_id'
,ATTR_DEF_PEPPOLID=>'endpoint_id'
];
foreach ($a_needed as $item=>$value) {
if (\noalyss_trim($card->get_attribute($item))=="") {
printf (_("ATTENTION donnée manquante dans la fiche client [%s]"),$value);
}
if ( $this->data['customer'][$value]=="") {
$a_error[]=$value;
}
}
if (count($a_error) == 0) {
return true;
}
return false;
return $a_error;
}
/**
* @brief transform an operation ($jr_id) into an array, which contains
* needed information for making an e-invoice
@ -113,61 +120,11 @@ class InvoiceUBL21 extends XMLInvoice {
* @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']);
function build_data($jr_id): array
{
/// 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;
$this->data=parent::build_data($jr_id);
return $this->data;
}
/**
* @brief Information customer
@ -178,7 +135,8 @@ class InvoiceUBL21 extends XMLInvoice {
$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);
$customer_party->appendChild($this->createElement('cbc:EndpointID',$this->data['customer']['endpoint_id']))
->setAttribute('schemeID', 9925);
$party_name=$this->createElement('cac:PartyName');
$party_name->appendChild($this->createElement("cbc:Name", $this->data['customer']['name']));
$customer_party->appendChild($party_name);
@ -187,9 +145,10 @@ class InvoiceUBL21 extends XMLInvoice {
$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_code =$this->data['customer']['country'];
$country=$postal_address->appendChild($this->createElement("cac:Country"));
$country->appendChild($this->createElement('cbc:IdentificationCode',$country_code??"ERROR:COUNTRY_CODE"));
$country->appendChild($this->createElement('cbc:IdentificationCode',$country_code));
$postal_address->appendChild($country);
// Tax Schem
@ -231,16 +190,16 @@ class InvoiceUBL21 extends XMLInvoice {
*/
function build_paymentInfo()
{
$company = $this->load_noalyss_parameter();
$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"]));
$payment->appendChild($this->createElement('cbc:PaymentID',$this->data["info"]['communication']));
$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"));
$f->appendChild($this->createElement("cbc:ID",$company['COMPANY_BANK_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"));
$g->appendChild($this->createElement("cbc:ID", $company['COMPANY_BANK_BIC']));
$f->appendChild($g);
$payment->appendChild($f);
@ -255,7 +214,7 @@ class InvoiceUBL21 extends XMLInvoice {
$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);
$supplier_party->appendChild($this->createElement('cbc:EndpointID',$company['COMPANY_UBL_ID']))->setAttribute('schemeID', 9925);
$party_name=$this->createElement('cac:PartyName');
$party_name->appendChild($this->createElement('cbc:Name', $this->data['supplier']['name']));
$supplier_party->appendChild($party_name);
@ -325,20 +284,24 @@ class InvoiceUBL21 extends XMLInvoice {
function build_taxTotal()
{
$taxTotal=$this->createElement("cac:TaxTotal");
$taxTotal->appendChild($this->createElement('cbc:TaxAmount',$this->data['TaxAmount']))
->setAttribute("currencyID","EUR");
$taxTotal->appendChild($this->createElement('cbc:TaxAmount',sprintf("%.2f",$this->data['TaxAmount'])))
->setAttribute("currencyID",$this->data['currency']);
// 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");
$subTotalXML->appendChild($this->createElement('cbc:TaxableAmount',sprintf("%.2f",$subTotal[$i]['amount'])))
->setAttribute("currencyID", $this->data['currency']);
$subTotalXML->appendChild($this->createElement('cbc:TaxAmount',sprintf("%.2f",$subTotal[$i]['vat'])))
->setAttribute("currencyID",$this->data['currency']);
$taxCategory=$this->createElement("cac:TaxCategory");
$taxCategory->appendChild($this->createElement("cbc:ID","S"));
$taxCategory->appendChild($this->createElement("cbc:Percent",$subTotal[$i]['percent']));
/**
* @TODO DNY
* Pas toujours S !?
*/
//$taxCategory->appendChild($this->createElement("cbc:ID",$subTotal[$i]['vat_code']));
$taxCategory->appendChild($this->createElement("cbc:Percent",sprintf("%.2f",$subTotal[$i]['percent'])));
$taxScheme=$this->createElement("cac:TaxScheme");
$taxScheme->appendChild($this->createElement("cbc:ID", "VAT"));
$taxCategory->appendChild($taxScheme);
@ -363,14 +326,14 @@ class InvoiceUBL21 extends XMLInvoice {
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");
$result->appendChild($this->createElement("cbc:LineExtensionAmount",sprintf("%.2f",$this->data['LineExtensionAmount'])))
->setAttribute("currencyID",$this->data['currency']);
$result->appendChild($this->createElement("cbc:TaxExclusiveAmount",sprintf("%.2f",$this->data['TaxExclusiveAmount'])))
->setAttribute("currencyID",$this->data['currency']);
$result->appendChild($this->createElement("cbc:TaxInclusiveAmount",sprintf("%.2f",$this->data['TaxInclusiveAmount'])))
->setAttribute("currencyID", $this->data['currency'] );
$result->appendChild($this->createElement("cbc:PayableAmount",sprintf("%.2f",$this->data['PayableAmount'])))
->setAttribute("currencyID", $this->data['currency'] );
return $result;
}
@ -404,30 +367,35 @@ class InvoiceUBL21 extends XMLInvoice {
$result=$this->createElement('cac:InvoiceLine');
$row=$this->data["operation"][$i];
$amount=sprintf("%.2f",$row['price']);
$result->appendChild($this->createElement("cbc:ID", $i));
///@todo , les unités de quantités devraient être ajoutés à NOALYSS
/// il faut adapter les fiches
$amount=sprintf("%.2f",$row['price']);
$result->appendChild(
$this->createElement("cbc:InvoicedQuantity", $row['quantity']))
->setAttribute("unitCode", "EA");
$result->appendChild($this->createElement("cbc:LineExtensionAmount", $row['price']))
->setAttribute("currencyID","EUR");
$this->createElement("cbc:InvoicedQuantity", sprintf("%.2f",$row['quantity'])))
->setAttribute("unitCode", $row["code_quantity"]);
$result->appendChild($this->createElement("cbc:LineExtensionAmount", $amount))
->setAttribute("currencyID",$this->data['currency']);
// ITEM
$item=$this->createElement("cac:Item");
$card=new \Fiche($this->cn,$row['card_id']);
$item->appendChild($this->createElement("cbc:Name", $card->get_attribute(ATTR_DEF_NAME)));
$item->appendChild($this->createElement("cbc:Description",$row['name']));
$item->appendChild($this->createElement("cbc:Name", $row['qcode']));
$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']));
//cbc:ID S = standard rate
/// see TVA_RATE.TVA_PEPPOL_CODE & C0TVA
$classifiedTaxCat->appendChild($this->createElement("cbc:ID", $row['vat_code']));
$classifiedTaxCat->appendChild($this->createElement("cbc:Percent", sprintf("%.2f",$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");
$price->appendChild($this->createElement("cbc:PriceAmount",sprintf("%.2f",abs($row['price_unit']))))
->setAttribute("currencyID",$this->data['currency']);
$result->appendChild($price);
return $result;
@ -435,10 +403,10 @@ class InvoiceUBL21 extends XMLInvoice {
}
/**
* @brief Insert a PDF in the XML
* the document type is not due for BELGIUM
@code
<cac:AdditionalDocumentReference>
<cbc:ID>P01</cbc:ID>
<cbc:DocumentType>InvoicePDF</cbc:DocumentType>
<cbc:DocumentDescription>Facture PDF</cbc:DocumentDescription>
<cac:Attachment>
<cbc:EmbeddedDocumentBinaryObject
@ -449,7 +417,6 @@ class InvoiceUBL21 extends XMLInvoice {
<!-- OU -->
<cac:AdditionalDocumentReference>
<cbc:ID>REF_ODT_001</cbc:ID>
<cbc:DocumentType>OpenDocument</cbc:DocumentType>
<cbc:DocumentDescription>Fichier OpenDocument</cbc:DocumentDescription>
<cac:Attachment>
<cbc:EmbeddedDocumentBinaryObject
@ -465,14 +432,31 @@ class InvoiceUBL21 extends XMLInvoice {
function build_Invoice():\DOMElement
{
if ( $this->pdf_filename == "") return null;
$result=$this->createElement("AdditionalDocumentReference");
/** $pdf_filename = 'chemin/vers/votre/fichier.pdf';*/
static $i=0;
$i++;
if ( $this->pdf_filename == null ) {
return null;
}
// Lire le fichier PDF
// $pdfContent = file_get_contents($pdfPath);
$pdfContent = file_get_contents( $this->pdf_filename );
// Encoder le PDF en base64
// $base64Pdf = base64_encode($pdfContent);
$result=$this->createElement("cac:AdditionalDocumentReference");
$id=$this->createElement("cbc:ID",$i);
$document_description=$this->createElement("cbc:DocumentDescription"
, $this->data['description']);
// PDF in base64
$base64Pdf = base64_encode($pdfContent);
$embeddedDocument=$this->createElement("cbc:EmbeddedDocumentBinaryObject",$base64Pdf);
$embeddedDocument->setAttribute("mimeCode", "application/pdf");
$embeddedDocument->setAttribute("filename", "facture.pdf");
$attachment=$this->createElement("cac:Attachment");
$attachment->appendChild($embeddedDocument);
$result->appendChild($id);
$result->appendChild($document_description);
$result->appendChild($attachment);
return $result;
}
@ -496,12 +480,14 @@ class InvoiceUBL21 extends XMLInvoice {
$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']));
if ($this->data ['due_date'] == '')
{
$this->data ['due_date']=$this->data['issue_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'));
$root->appendChild($this->createElement('cbc:DocumentCurrencyCode',$this->data['currency']));
$root->appendChild($this->createElement('cbc:BuyerReference',$this->data['info']['order']));
/**
* insert PDF in the XML
*/

View file

@ -1,7 +1,7 @@
<?php
namespace Noalyss\XMLDocument;
use Noalyss\Utility;
//use Noalyss\Utility;
/*
* This file is part of NOALYSS.
*
@ -57,6 +57,9 @@ use Noalyss\Utility;
)
[currency] => 0
[info] => Array
[order] = order reference
[communication] = communication added to the invoice
[operation] => Array
(
[0] => Array
@ -67,6 +70,8 @@ use Noalyss\Utility;
[vat] => 2.1000
[vat_id] => 1
[vat_reversed] => 0.0000
[code_quantity]=> EA
[vat_code]=> Code VAT for PEPPOL (S,K,...)
)
[1] => Array
@ -77,6 +82,8 @@ use Noalyss\Utility;
[vat] => 17.5200
[vat_id] => 1
[vat_reversed] => 0.0000
[code_quantity]=> EA
[vat_code]=> Code VAT for PEPPOL (S,K,...)
)
[2] => Array
@ -87,6 +94,8 @@ use Noalyss\Utility;
[vat] => 15.1200
[vat_id] => 5
[vat_reversed] => 15.1200
[code_quantity]=> EA
[vat_code]=> Code VAT for PEPPOL (S,K,...)
)
)
@ -100,7 +109,8 @@ use Noalyss\Utility;
abstract class XMLInvoice extends \DOMDocument
{
protected $cn; //!< Database conx , current folder
protected $data; //! $data Array data retrieve from DB
protected $data; //! $data (Array) data retrieve from DB
protected $jr_id; //! $jr_id (int) is JRN.JR_ID
function __construct(\Database $conx)
{
parent::__construct("1.0", "UTF-8");
@ -154,71 +164,149 @@ abstract class XMLInvoice extends \DOMDocument
function build_data($jr_id):array
{
global $g_parameter;
$this->jr_id=$jr_id;
$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;
$result["due_date"]=($operation->det->jr_ech=="")?$operation->det->jr_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 !
$result['supplier']=$this->fill_supplier();
//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->get_attribute(ATTR_DEF_NAME);
$result['customer']['street']=$customer->get_attribute(ATTR_DEF_ADRESS);
$result['customer']['postalzone']=$customer->get_attribute(ATTR_DEF_POSTCODE);
$result['customer']['city']=$customer->get_attribute(ATTR_DEF_CITY);
$result['customer']=$this->fill_customer($operation->det->array[0]['qs_client']);
// find country_code of this card
// currency
$result['currency']=$this->cn->get_value("select cr_code_iso from currency where id=$1"
,array($operation->det->currency_id));
$result['customer']['country']=$customer->get_attribute(ATTR_DEF_COUNTRY);
// document description
$result['description']=$operation->det->jr_comment;
$result['customer']['customer_id']=str_replace([" ",".","-","/"],"" ,$customer->get_attribute(ATTR_DEF_NUMTVA));
// official name of the company
$result['customer']['registration_name']=$customer->get_attribute(ATTR_DEF_NAME);
// official ID , like VAT
$result['customer']['customer_id']=$customer->get_attribute(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'];
$card=new \Fiche($this->cn,$operation->det->array[$i]['qs_fiche']);
$result['operation'][$i]['qcode']=$card->get_attribute(ATTR_DEF_QUICKCODE);
$result['operation'][$i]['name']=$card->get_attribute(ATTR_DEF_NAME);
$result['operation'][$i]['description']=$card->get_attribute(9);
// get the type of unity, if not found then it will be EA
$x= $card->get_attribute(ATTR_DEF_QUANTITY_TYPE,0);
$result['operation'][$i]['code_quantity']=($x===false||$x=="")?"EA":$x;
// $operation->det->currency_id == 0 default currency of the folder
if ($operation->det->currency_id == 0 ) {
$result['operation'][$i]['price']=$operation->det->array[$i]['qs_price'];
$result['operation'][$i]['price_unit']=$operation->det->array[$i]['qs_unit'];
$result['operation'][$i]['vat']=$operation->det->array[$i]['qs_vat'];
} else {
$result['operation'][$i]['price']=$operation->det->array[$i]['oc_amount'];
$result['operation'][$i]['price_unit']=bcdiv(
$operation->det->array[$i]['oc_amount'],
$operation->det->array[$i]['qs_quantite'],
2);
$result['operation'][$i]['vat']=$operation->det->array[$i]['oc_vat_amount'];
}
$result['operation'][$i]['vat_id']=$operation->det->array[$i]['qs_vat_code'];
// // tva code for PEPPOL
$x=$this->cn->get_row("select tva_peppol_code,tva_rate from tva_rate where tva_id=$1"
,[ $result['operation'][$i]['vat_id']]);
$result['operation'][$i]['vat_code']=($x['tva_peppol_code']=="")?"S":$x['tva_peppol_code'];
$result['operation'][$i]['vat_rate']=$x['tva_rate'];
$result['operation'][$i]['vat_reversed']=$operation->det->array[$i]['qs_vat_sided'];
}
print_r($result);
//------------------------------------------------
// retrieve order and comment
//------------------------------------------------
$a_row=$this->cn->get_array("select id_type,ji_value from jrn_info where jr_id=$1"
,[$jr_id]);
$nb_row = count($a_row);
$result['info']=[];
$result['info']['order']='NA';
$result['info']['communication']='';
for($i=0;$i<$nb_row;$i++) {
switch ($a_row[$i]['id_type']) {
case 'BON_COMMANDE':
$result['info']['order']=$a_row[$i]['ji_value'];
break;
case 'OTHER':
$result['info']['communication']=$a_row[$i]['ji_value'];
break;
}
}
$result['info']['communication']=($result['info']['communication']=="")?$result['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,2);
$idx=sprintf("%s - %s",$percent,$result['operation'][$i]['vat_code'] );
// subtotal for VAT
$n = find_idx($VAT_SubTotal,'idx',$idx);
if ($n == -1 ) {
$n=$idx_subtotal;
$VAT_SubTotal[$idx_subtotal]=array();
$VAT_SubTotal[$idx_subtotal]['idx']=$idx;
$VAT_SubTotal[$idx_subtotal]['vat_code']=$result['operation'][$i]['vat_code'] ;
$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']);
return $result;
}
/**
* @brief make an array of parameter_extra where pe_code as key and pe_value
* as value
* @return array
* @return array keys : pe_code,pe_value
*/
function load_noalyss_parameter()
{
@ -238,19 +326,218 @@ abstract class XMLInvoice extends \DOMDocument
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) ;
abstract function check_company_data() ;
/**
* @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) ;
abstract function check_customer_data($customer_id) ;
/**
* @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) ;
/**
* @brief display_error display a warning with all error
*/
public function display_error()
{
$a_error=$this->verify();
include NOALYSS_TEMPLATE."/xmlinvoice-display_error.php";
}
/**
* @brief check that the VAT is using a PEPPOL Code
*/
function check_VAT()
{
$a_error=array();
$nb_operation=count($this->data['operation']);
for ($i=0;$i <$nb_operation;$i++)
{
if ( $this->data['operation'][$i]['vat_code'] == "" ) {
$card=new \Fiche(
$this->cn
,$this->data['operation'][$i]['card_id']
);
$tva= \Acc_Tva::build($this->cn, $this->data['operation'][$i]['vat_id']);
$a_error[]=sprintf(_("%s : %s code TVA pour PEPPOL non configuré code TVA [ %s %s ]")
, $i
, $card->get_quick_code()
,$tva->tva_id
,$tva->tva_code
);
}
}
return $a_error;
}
/**
* @brief thanks MY_INVOICE_FORMAT , create the corresponding object
* - UBL21BEL => InvoiceUBL21
* - FacturX => FACTURXFR
* @returns null MY_INVOICE_FORMAT is BASIC
*/
static function build_xmlinvoice(\Database $conx) {
global $g_parameter;
if ($g_parameter->MY_INVOICE_FORMAT == 'UBL21BEL') {
return new \Noalyss\XMLDocument\InvoiceUBL21($conx);
}
if ($g_parameter->MY_INVOICE_FORMAT == 'FACTURXFR') {
return new \Noalyss\XMLDocument\FacturX($conx);
}
return null;
}
/**
* @brief check that all the data are correct
* @returns null : no errors, string separated with comma of error code
* @see get_message_error
*/
public function verify()
{
// verify all VAT
///@var $a_error : array of error_code see check_company_error
$a_error = array();
$a_error['general'] = [];
$a_error['operation']=[];
// verify that all needed data in PARAMETER are valid
$a_error['company'] = $this->check_company_data();
$a_error['customer'] = $this->check_customer_data($this->data['customer']['card_id']);
return $a_error;
}
/**
* @brief retrieve data from customer and return it into an array
* @param $card_id (int) FICHE.F_ID
* @return array keys :
* - name
* - ,street
* - ,postalzone
* - ,city
* - ,country
* - ,customer_id => VAT Number
* - , registration_name,
* - card_id
*/
function fill_customer($card_id):array
{
$customer =new \Fiche($this->cn,$card_id);
$result=array();
$result['card_id']=$card_id;
$result['name']=$customer->get_attribute(ATTR_DEF_NAME,0);
$result['street']=$customer->get_attribute(ATTR_DEF_ADRESS,0);
$result['postalzone']=$customer->get_attribute(ATTR_DEF_POSTCODE,0);
$result['city']=$customer->get_attribute(ATTR_DEF_CITY,0);
// find country_code of this card
$result['country']=$customer->get_attribute(ATTR_DEF_COUNTRY_CODE,0);
// official ID , like VAT
$result['customer_id']=str_replace([" ",".","-","/"],"" ,$customer->get_attribute(ATTR_DEF_NUMTVA,0));
// official name of the company
$result['registration_name']=$customer->get_attribute(ATTR_DEF_NAME,0);
$result['endpoint_id']=$customer->get_attribute(ATTR_DEF_PEPPOLID,0);
return $result;
}
/**
* @brief complete $this->data from $g_parameter (global variable) for
* Noalyss_Folder_Parameter
* @return array keys :
* - name
* - ,street
* - ,postalzone
* - ,city
* - ,country
* - supplier_id => VAT Number
* - registration_name,
*
*/
function fill_supplier():array
{
$a_parameter=$this->load_noalyss_parameter();
$result=array();
$result['name']=$a_parameter['MY_NAME'];
$result['street']=$a_parameter['MY_STREET'];
$result['postalzone']=$a_parameter['MY_POSTCODE'];
$result['city']=$a_parameter['MY_CITY'];
$result['country']=$a_parameter['MY_COUNTRY'];
// official name of the company
$result['registration_name']=$a_parameter['MY_NAME'];
// official ID , like VAT
$result['supplier_id']=str_replace([" ",".","-","/"],"" ,$a_parameter['MY_TVA']);
$result['COUNTRY_CODE']=$a_parameter['COUNTRY_CODE']??"";
$result['COMPANY_LEGAL_REGISTRATION']=$a_parameter['COMPANY_LEGAL_REGISTRATION']??"";
$result['COMPANY_LEGAL_ENTITY']=$a_parameter['COMPANY_LEGAL_ENTITY']??"";
$result['INVOICE_CONTACT_NAME']=$a_parameter['INVOICE_CONTACT_NAME']??"";
$result['INVOICE_EMAIL_COMPANY']=$a_parameter['INVOICE_EMAIL_COMPANY']??"";
$result['COMPANY_UBL_ID']=$a_parameter['COMPANY_UBL_ID']??"";
return $result;
}
/**
* @brief build operation from array
* key :
* - [e_march0] => Quick code of the item
- [e_march0_label] => Label of item
- [e_march0_price] => Unit Price
- [e_quant0] => Quantity
- [htva_march0] => Price w/0 VAT
- [e_march0_tva_id] => Code VAT
- [e_march0_tva_amount] => Amount VAT
- [tva_march0] => Amount VAT (duplicate -> to remove)
- [tvac_march0] => Total Amount Tax included
* @param type $a_array
* @return type
*/
function fill_operation_from_array($a_array)
{
$result=array();
$http=new \HttpInput();
$http->set_array($a_array);
$nb_item=$http->get_value("nb_item");
for ($i=0;$i<$nb_item;$i++)
{
if ( $http->get_value("e_march{$i}_tva_id") == "")
{
continue;
}
$operation=array();
$card=\Fiche::from_qcode($this->cn,trim($http->get_value("e_march{$i}")));
$operation['card_id']=$card->id;
$operation['quantity']=$http->get_value("e_quant{$i}");
$operation['price']=$http->get_value("e_march{$i}_price");
$operation['vat']=$http->get_value("tvac_march{$i}");
$tva= \Acc_Tva::build($this->cn, $http->get_value("e_march{$i}_tva_id"));
$operation['vat_id']=$tva->tva_id;
$operation['vat_reversed']=($tva->tva_both_side==1)?$operation['vat']:0;
$operation['vat_code']=$tva->tva_peppol_code;
$operation['code_quantity']=$card->get_attribute(ATTR_DEF_QUANTITY_TYPE,0);
$operation['code_quantity']=($operation['code_quantity']=="")?"EA":$operation['code_quantity'];
$result[$i]=$operation;
}
return $result;
}
/**
* @brief set the PDF
* @param $pdf_filename (string) full path to the PDF
* @return $this
* @throws \Exception if the filename doesn't exist
*/
public function set_pdf_filename($pdf_filename) {
if ( !file_exists($pdf_filename)) {
throw new \Exception("AD65 $pdf_filename doesn't not exist");
}
$this->pdf_filename = $pdf_filename;
return $this;
}
}

View file

@ -0,0 +1,540 @@
<?php
namespace Noalyss\XMLDocument;
/*
* This file is part of NOALYSS.
*
* NOALYSS 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 2 of the License, or
* (at your option) any later version.
*
* NOALYSS 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 NOALYSS; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
// Copyright Author Dany De Bontridder danydb@aevalys.eu 22/10/23
/**
* @file
* @brief extract information from UBL21
*
*/
/**
* @file
* @brief Get information from an XML
* Exception code :
* - 55 : XML Invalid
* - 62 : filename don't exist
*/
class XMLInvoice_Reader
{
protected \DOMDocument $domDocument;
protected readonly \DOMXPath $xpath;
public function __construct(\DOMDocument $domDocument)
{
$this->domDocument = $domDocument;
$this->xpath = new \DOMXPath($this->domDocument);
}
/**
*
* @return \DOMDocument
*/
public function get_domDocument(): \DOMDocument
{
return $this->domDocument;
}
public function get_xpath(): \DOMXPath
{
return $this->xpath;
}
public function set_domDocument($domDocument)
{
$this->domDocument = $domDocument;
return $this;
}
/**
* @brief Build an XMLInvoice_Reader object from an XML string
* @param $string (string) XML
* @return \Noalyss\XMLDocument\XMLInvoice_Reader
* @throws \Exception if xml not valid
*/
static function build_from_string($string)
{
$dm = new \DOMDocument();
if ($dm->loadXML($string) != false)
{
return new XMLInvoice_Reader($dm);
} else
{
throw new \Exception("XR55: not a valid XML");
}
}
/**
* @brief Build an XMLInvoice_Reader object from an XML file
* @param $filename (string) file and path to the file
* @return \Noalyss\XMLDocument\XMLInvoice_Reader
* @throws \Exception if xml not valid
*/
static function build_from_file($filename)
{
if (!file_exists($filename))
{
throw new \Exception("XR62: file not found $filename", 62);
}
$dm = new \DOMDocument();
if ($dm->load($filename) != false)
{
return new XMLInvoice_Reader($dm);
} else
{
throw new \Exception("XR55: not a valid XML", 55);
}
}
/**
* @brief execute a XPATH query of the DOMDocument and return the result
* or null if nothing found
* @param $query (string) valid XPath Query
* @return null or DOMNodeList or DOMElement
*/
public function get_node($query)
{
if ($this->xpath->query($query)->length == 0)
{
return null;
}
return $this->xpath->query($query);
}
/**
* @brief returns the embedded document in an array (keys : filecontent (BYTES),mimecode , filename)
* or false if there is no document
* @return bool|array keys : filecontent (BYTES),mimecode , filename)
* or false if there is no document
* @throws \Exception if there are several documents
*
*/
public function get_embedded_document()
{
if (($node = $this->get_node("//cac:AdditionalDocumentReference")) == false)
{
return false;
}
if ($node->length > 1)
{
throw new \Exception("XR110 Too many documents found", 110);
}
///@var DOMNodeList $data //cac:AdditionalDocumentReference[1]/cac:Attachment[1]/cbc:EmbeddedDocumentBinaryObject[1]
if (
($data = $this->get_node("//cac:AdditionalDocumentReference[1]/cac:Attachment[1]/cbc:EmbeddedDocumentBinaryObject[1]")) == null
)
{
throw new \Exception("X116 Document corrupted", 116);
}
$filecontent = base64_decode($data->item(0)->nodeValue);
$mimecode = $data->item(0)->getAttribute("mimeCode");
$filename = $data->item(0)->getAttribute("filename");
return array("filecontent" => $filecontent,
"mimecode" => $mimecode,
"filename" => $filename);
}
/**
* @brief get the node value
* @param $query (string) valid XPath query
* @param $ix (int) the item number
* @return string
*/
function get_node_value($query, $ix = 0)
{
return $this->get_node($query)?->item($ix)?->nodeValue;
}
/**
* @brief get the customer info from XML
* @return array
*/
function get_customer(): array
{
$result = [];
$result['ID'] = $this->get_node_value("//cac:AccountingCustomerParty[1]/cac:Party[1]/cbc:EndpointID[1]");
$result['name'] = $this->get_node_value('//cac:AccountingCustomerParty[1]/cac:Party[1]/cac:PartyName[1]/cbc:Name[1]');
$result['street'] = $this->get_node_value('//cac:AccountingCustomerParty[1]/cac:Party[1]/cac:PostalAddress[1]/cbc:StreetName[1]');
$result['city'] = $this->get_node_value('//cac:AccountingCustomerParty[1]/cac:Party[1]/cac:PostalAddress[1]/cbc:CityName[1]');
$result['postcode'] = $this->get_node_value('//cac:AccountingCustomerParty[1]/cac:Party[1]/cac:PostalAddress[1]/cbc:PostalZone[1]');
$result['country_code'] = $this->get_node_value("//cac:AccountingCustomerParty[1]/cac:Party[1]/cac:PostalAddress[1]/cac:Country[1]/cbc:IdentificationCode[1]");
$result['company_id'] = $this->get_node_value("//cac:AccountingCustomerParty[1]/cac:Party[1]/cac:PartyTaxScheme[1]/cbc:CompanyID[1]");
return $result;
}
/**
* @brief get the supplier info from XML
* @return array
*/
function get_supplier(): array
{
$result = [];
$result['ID'] = $this->get_node_value("//cac:AccountingSupplierParty[1]/cac:Party[1]/cbc:EndpointID[1]");
$result['name'] = $this->get_node_value("//cac:AccountingSupplierParty[1]/cac:Party[1]/cac:PartyName[1]/cbc:Name[1]");
$result['street'] = $this->get_node_value("//cac:AccountingSupplierParty[1]/cac:Party[1]/cac:PostalAddress[1]/cbc:StreetName[1]");
$result['city'] = $this->get_node_value("//cac:AccountingSupplierParty[1]/cac:Party[1]/cac:PostalAddress[1]/cbc:CityName[1]");
$result['postcode'] = $this->get_node_value("//cac:AccountingSupplierParty[1]/cac:Party[1]/cac:PostalAddress[1]/cbc:PostalZone[1]");
$result['country_code'] = $this->get_node_value("//cac:AccountingSupplierParty[1]/cac:Party[1]/cac:PostalAddress[1]/cac:Country[1]/cbc:IdentificationCode[1]");
$result['company_id'] = $this->get_node_value("//cac:AccountingSupplierParty[1]/cac:Party[1]/cac:PartyTaxScheme[1]/cbc:CompanyID[1]");
return $result;
}
/**
* @brief get the Taxes info from XML
* @return array
*/
function get_taxes(): array
{
$result = [];
$node = $this->get_node("//cac:TaxTotal/cac:TaxSubtotal");
for ($e = 0; $e < $node->length; $e++)
{
$row = [];
$xml = simplexml_import_dom($node->item($e));
// /Invoice/cac:TaxTotal[1]/cac:TaxSubtotal[1]/cbc:TaxableAmount[1]
$row ['taxable_amount'] = $xml->xpath("//cbc:TaxableAmount")[$e] . "";
$row ['tax'] = $xml->xpath("//cbc:TaxAmount")[$e] . "";
$row ['tax_id'] = $xml->xpath("//cac:TaxCategory/cbc:ID")[$e] . "";
$row ['tax_percent'] = $xml->xpath("//cac:TaxCategory/cbc:Percent")[$e] . "";
// $row ['name'] =$xml->xpath("//cac:InvoiceLine/cac:Item/cbc:Name")[$e]."<br>";
/**
* @TODODNY
* Implémenter les allowances
*/
$result[] = $row;
}
return $result;
}
/**
* @brief retrieve InvoiceLines
* @TODO XMLInvoice_Reader->get_invoiceLine * Implémenter les allowances
*/
function get_invoiceLine(): array
{
$result = [];
$node = $this->get_node("//cac:InvoiceLine");
for ($e = 0; $e < $node->length; $e++)
{
$row = [];
$xml = simplexml_import_dom($node->item($e));
//var_dump($xml->asXML());
$row ['quantity'] = $this->get_node_value("//cbc:InvoicedQuantity", $e);
$row ['amount'] = $this->get_node_value("//cbc:LineExtensionAmount", $e);
$row ['description'] = $this->get_node_value("//cac:Item/cbc:Description", $e);
$row ['name'] = $this->get_node_value("//cac:InvoiceLine/cac:Item/cbc:Name", $e);
$row ['unit_price'] = $this->get_node_value("//cac:InvoiceLine/cac:Price/cbc:PriceAmount", $e);
$row ['tva_id'] = $this->get_node_value("//cac:InvoiceLine/cac:Item/cac:ClassifiedTaxCategory/cbc:ID", $e);
$row ['tva_percent'] = $this->get_node_value("//cac:InvoiceLine/cac:Item/cac:ClassifiedTaxCategory/cbc:Percent", $e);
$result[] = $row;
}
return $result;
}
/**
* @brief get the Payment Means info from XML
* @return array
*/
function get_payment_mean(): array
{
$result = [];
$result['payment_code'] = $this->get_node_value("//cac:PaymentMeans[1]/cbc:PaymentMeansCode[1]");
$result['label'] = $this->get_node_value("//cac:PaymentMeans[1]/cbc:PaymentID[1]");
$result['iban'] = $this->get_node_value("//cac:PaymentMeans[1]/cac:PayeeFinancialAccount[1]/cbc:ID[1]");
$result['bic'] = $this->get_node_value("//cac:PaymentMeans[1]/cac:PayeeFinancialAccount[1]/cac:FinancialInstitutionBranch[1]/cbc:ID[1]");
$result['note'] = [];
$note = $this->get_node("//cac:PaymentTerms/cbc:Note");
if ($note != null)
{
$a = $note->length;
for ($i = 0; $i < $a; $i++)
{
$result['note'][] = $note->item(0)->nodeValue;
}
}
/**
<cac:PaymentTerms>
<cbc:Note> In geval van betaling binnen 14 dagen is 2% (52.00) betalingskorting van
toepassing en het te betalen bedrag = 3053.68
En cas de paiement dans les 14 jours, l'escompte conditionnel de 2% (52.00) est appliqué et le montant payable = 3053.68
In case of payment within 14 days, 2% (52.00) conditional cash/payment discount applies and the payable amount = 3053.68
</cbc:Note>
</cac:PaymentTerms>
*/
return $result;
}
/**
* @brief get the node value from simpleXML
* @param $xml (\SimpleXMLElement) fragment of XML dom
* @param $query (string) XPath query
* @return string
*/
protected function get_simple_xml_value(\SimpleXMLElement $xml, $query): string
{
$x = $xml->xpath($query);
$result = (count($x) == 0) ? "" : $x[0];
return (string) $result;
}
/**
* @brief get the amount summary info from XML
* @return array
*/
function get_amount_summary(): array
{
$result = [];
$node = $this->get_node("//cac:LegalMonetaryTotal");
$xml = simplexml_import_dom($node->item(0));
$result['LineExtensionAmount'] = $this->get_simple_xml_value($xml, "cbc:LineExtensionAmount");
$result['TaxExclusiveAmount'] = $this->get_simple_xml_value($xml, "cbc:TaxExclusiveAmount");
$result['TaxInclusiveAmount'] = $this->get_simple_xml_value($xml, "cbc:TaxInclusiveAmount");
$result['PayableAmount'] = $this->get_simple_xml_value($xml, "cbc:PayableAmount");
$result['AllowanceTotalAmount'] = $this->get_simple_xml_value($xml, "cbc:AllowanceTotalAmount");
$result['AllowanceTotalAmount'] = ($result['AllowanceTotalAmount'] == "") ? 0 : $result['AllowanceTotalAmount'];
$result['ChargeTotalAmount'] = $this->get_simple_xml_value($xml, "cbc:ChargeTotalAmount");
$result['ChargeTotalAmount'] = ($result['ChargeTotalAmount'] == "") ? 0 : $result['ChargeTotalAmount'];
return $result;
}
/**
* @brief get the customer info from XML
* @return array
*/
/**
*
@code
<cac:AllowanceCharge>
<cbc:ChargeIndicator>false</cbc:ChargeIndicator>
<cbc:AllowanceChargeReasonCode>64</cbc:AllowanceChargeReasonCode>
<cbc:AllowanceChargeReason>Conditional cash/payment discount | Korting contant | Escompte Conditionnel 2%</cbc:AllowanceChargeReason>
<cbc:Amount currencyID="EUR">4.00</cbc:Amount>
<cac:TaxCategory>
<cbc:ID>S</cbc:ID>
<cbc:Percent>6.00</cbc:Percent>
<cac:TaxScheme>
<cbc:ID>VAT</cbc:ID>
</cac:TaxScheme>
</cac:TaxCategory>
</cac:AllowanceCharge>
@encode
*/
function get_allowance(): array
{
$result = [];
return $result;
}
/**
*
* @return array
* @TODODNY
* Implémenter les allowances
*/
function get_info(): array
{
$result = [];
$result['id'] = $this->get_node_value('cbc:ID');
$result['IssueDate'] = $this->get_node_value('cbc:IssueDate');
$result['DueDate'] = $this->get_node_value('cbc:DueDate');
$result['InvoiceTypeCode'] = $this->get_node_value('cbc:InvoiceTypeCode');
$result['DocumentCurrencyCode'] = $this->get_node_value('cbc:DocumentCurrencyCode');
$result['BuyerReference'] = $this->get_node_value('cbc:BuyerReference');
$result['ActualDeliveryDate'] = $this->get_node_value('//cac:Delivery[1]/cbc:ActualDeliveryDate[1]');
/*
<cac:Delivery>
<cbc:ActualDeliveryDate>2018-07-01</cbc:ActualDeliveryDate>
</cac:Delivery>
*/
return $result;
}
/**
* @brief make a PDF with the information from XMLInvoice
* @param \Database $cn
* @returns \PDF
*/
public function to_pdf(\Database $cn): \PDF
{
$pdf = new \PDF($cn);
$result = $this->get_info();
$pdf->setDossierInfo(_(" id ")." ".$result['id']);
$pdf->AliasNbPages();
$pdf->setAuthor("Noalyss");
$pdf->AddPage();
// 180 mm large
$pdf->setFont("DejaVu", "B", 12);
$pdf->write_cell(60, 4, _("Information facture"));
$pdf->line_new(10);
$pdf->setFont("DejaVu", "", 7);
$pdf->write(4, sprintf(_("Document ID %s"), $result['id']));
$pdf->ln();
$pdf->write(4, sprintf(_("Date facture %s"), $result['IssueDate']));
$pdf->ln();
$pdf->write(4, sprintf(_("Date échéance %s"), $result['DueDate']));
$pdf->ln();
$pdf->write(4, sprintf(_("Code facture %s"), $result['InvoiceTypeCode']));
$pdf->ln();
$pdf->write(4, sprintf(_("Devise docuemnt %s"), $result['DocumentCurrencyCode']));
$pdf->ln();
$pdf->write(4, sprintf(_("Référence client %s"), $result['BuyerReference']));
$pdf->ln();
$pdf->write(4, sprintf(_("Date Livraison %s"), $result['ActualDeliveryDate']));
$pdf->ln(10);
$pdf->setFont("DejaVu", "B", 12);
$pdf->write_cell(60, 4, _("Fournisseur"));
$pdf->line_new(12);
$supplier = $this->get_supplier();
$pdf->setFont("DejaVu", "", 7);
$pdf->write_cell(60, 4, $supplier['name']);
$pdf->write_cell(60, 4, $supplier['company_id']);
$pdf->write_cell(60, 4, $supplier['ID']);
$pdf->line_new();
$pdf->write_cell(60, 4, $supplier['street']);
$pdf->write_cell(30, 4, $supplier['postcode']);
$pdf->write_cell(60, 4, $supplier['city']);
$pdf->write_cell(20, 4, $supplier['country_code']);
$pdf->line_new(10);
$customer = $this->get_customer();
$pdf->setFont("DejaVu", "B", 12);
$pdf->write_cell(60, 4, _("Client"));
$pdf->line_new(10);
$pdf->setFont("DejaVu", "", 7);
$pdf->write_cell(60, 4, $customer['name']);
$pdf->write_cell(60, 4, $customer['company_id']);
$pdf->write_cell(60, 4, $customer['ID']);
$pdf->line_new();
$pdf->write_cell(60, 4, $customer['street']);
$pdf->write_cell(40, 4, $customer['postcode']);
$pdf->write_cell(60, 4, $customer['city']);
$pdf->write_cell(20, 4, $customer['country_code']);
$pdf->line_new(10);
$pdf->setFont("DejaVu", "B", 12);
$pdf->write_cell(60, 4, _("Articles"));
$pdf->line_new(10);
$pdf->setFont("DejaVu", "", 7);
$result = $this->get_invoiceLine();
$pdf->write_cell(50, 4, _("Code"), border: 'B');
$pdf->write_cell(50, 4, _("Description"), border: 'B');
$pdf->write_cell(30, 4, _("TVA"), border: 'B', align: 'R');
$pdf->write_cell(25, 4, _("Quantité"), border: 'B', align: 'R');
$pdf->write_cell(25, 4, _("Montant HT"), border: 'B', align: 'R');
$pdf->line_new();
$nb_inline = count($result);
for ($i = 0; $i < $nb_inline; $i++)
{
$pdf->write_cell(50, 4, $result[$i]['name']);
$pdf->write_cell(50, 4, $result[$i]['description']);
$pdf->write_cell(25, 4, $result[$i]['tva_percent'], align: 'R');
$pdf->write_cell(5, 4, $result[$i]['tva_id']);
$pdf->write_cell(25, 4, $result[$i]['quantity'], align: 'R');
$pdf->write_cell(25, 4, $result[$i]['amount'], align: 'R');
$pdf->line_new();
}
$pdf->line_new(10);
$pdf->setFont("DejaVu", "B", 12);
$pdf->write_cell(60, 4, _("Totaux"));
$pdf->line_new(10);
$pdf->setFont("DejaVu", "", 7);
$result = $this->get_amount_summary();
// @TODO DNY : Qu'est-ce que LineExtension Amount ??
$pdf->write_cell(50, 4, _("Base taxe"));
$pdf->write_cell(50, 4, $result['LineExtensionAmount']);
$pdf->line_new();
$pdf->write_cell(50, 4, _("Total Hors Taxe"));
$pdf->write_cell(50, 4, $result['TaxExclusiveAmount']);
$pdf->line_new();
$pdf->write_cell(50, 4, _("Total avec Taxe"));
$pdf->write_cell(50, 4, $result['TaxInclusiveAmount']);
$pdf->line_new();
$pdf->write_cell(50, 4, _("Total à payer"));
$pdf->write_cell(50, 4, $result['PayableAmount']);
$pdf->line_new();
$pdf->write_cell(50, 4, _("Total réduction"));
$pdf->write_cell(50, 4, $result['AllowanceTotalAmount']);
$pdf->line_new();
$pdf->write_cell(50, 4, _("Total charge"));
$pdf->write_cell(50, 4, $result['ChargeTotalAmount']);
$pdf->line_new();
$pdf->line_new(10);
$pdf->setFont("DejaVu", "B", 12);
$pdf->write_cell(60, 4, _("TVA"));
$pdf->line_new(10);
$pdf->setFont("DejaVu", "", 7);
$result = $this->get_taxes();
$pdf->write_cell(25, 4, _("% Taxe"), align: 'R', border: 'B');
$pdf->write_cell(5, 4, _("Code taxe"), border: 'B');
$pdf->write_cell(50, 4, _("Base"), align: 'R', border: 'B');
$pdf->write_cell(50, 4, _("Taxe"), align: 'R', border: 'B');
$pdf->line_new();
$nb_inline = count($result);
for ($i = 0; $i < $nb_inline; $i++)
{
$pdf->write_cell(25, 4, $result[$i]['tax_percent'], align: 'R');
$pdf->write_cell(5, 4, $result[$i]['tax_id']);
$pdf->write_cell(50, 4, $result[$i]['taxable_amount'], align: 'R');
$pdf->write_cell(50, 4, $result[$i]['tax'], align: 'R');
$pdf->line_new();
}
$pdf->line_new(10);
$pdf->setFont("DejaVu", "B", 12);
$pdf->write_cell(60, 4, _("Paiement"));
$pdf->line_new(10);
$pdf->setFont("DejaVu", "", 7);
$result = $this->get_payment_mean();
$pdf->write_cell(50, 4, _("Code"));
$pdf->write_cell(50, 4, $result['payment_code']);
$pdf->line_new();
$pdf->write_cell(50, 4, _("IBAN"));
$pdf->write_cell(50, 4, $result['iban']);
$pdf->line_new();
$pdf->write_cell(50, 4, _("BIC"));
$pdf->write_cell(50, 4, $result['bic']);
$pdf->line_new();
$pdf->write_cell(50, 4, _("Communication"));
$pdf->write_cell(50, 4, $result['label']);
$pdf->line_new();
$nb_note = count($result['note']);
for ($i = 0; $i < $nb_note; $i++)
{
$pdf->write_cell(40, 4, _("note") . " " . $i);
$pdf->write_multi(140, 4, str_replace(["\n","\r","\t"]," ",$result['note'][$i]));
$pdf->line_new();
}
return $pdf;
}
}

View file

@ -256,6 +256,11 @@ switch ($action) {
$filename = mb_substr($obj->det->jr_pj_name, 0, 60);
}
echo HtmlInput::show_receipt_document($jr_id, h($filename));
// if using the XML Belgian format, add a tab for showing it
$acc_document=new Acc_Document($cn,$jr_id);
echo '<span style="margin-left:5rem">'.
$acc_document->link_download_xml()
.'</span>';
echo $x;
echo '<p id="receipt_info_id" style="display:inline" ></p>';
echo '</div>';
@ -268,9 +273,8 @@ switch ($action) {
case 'loadfile':
if ($access == 'W' && isset ($_FILES)) {
$cn->start();
// remove the file
$grpt = $cn->get_value('select jr_grpt_id from jrn where jr_id=$1', array($jr_id));
$cn->save_receipt($grpt);
$acc_document=new \Acc_Document($cn,$jr_id);
$acc_document->save_receipt();
$cn->commit();
// Show a link to the new file
$op->get();
@ -290,7 +294,13 @@ switch ($action) {
$filename = $obj->det->jr_pj_name;
echo HtmlInput::show_receipt_document($jr_id, h($filename));
echo $x;
// if using the XML Belgian format, add a tab for showing it
$acc_document=new Acc_Document($cn,$jr_id);
echo '<span style="margin-left:5rem">'.
$acc_document->link_download_xml()
.'</span>';
echo '<p id="receipt_info_id" style="display:inline" ></p>';
echo '</div>';
echo '</div>';
echo '</body></html>';
}
@ -325,12 +335,21 @@ switch ($action) {
$old_oid = $r['jr_pj'];
if (strlen($old_oid) != 0) {
// check if this pj is used somewhere else
$c = $cn->count_sql("select * from jrn where jr_pj=" . $old_oid);
$c = $cn->get_value("select count(*) from jrn where jr_pj=$1",
[$old_oid]);
if ($c == 1)
$cn->lo_unlink($old_oid);
}
$cn->exec_sql("update jrn set jr_pj=null, jr_pj_name=null, " .
"jr_pj_type=null where jr_id=$1", array($jr_id));
if ( ($oid_xml = $cn->get_value("select jr_document_xml from jrn where jr_id = $1",[$jr_id])) != "")
{
$cn->exec_sql("update jrn set jr_document_xml=null where jr_id=$1", array($jr_id));
$cn->lo_unlink($oid_xml);
}
}
}
echo '</div>';

View file

@ -0,0 +1,387 @@
<?php
/*
* This file is part of NOALYSS.
*
* NOALYSS 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 2 of the License, or
* (at your option) any later version.
*
* NOALYSS 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 NOALYSS; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
// Copyright Author Dany De Bontridder danydb@aevalys.eu 28/08/25
use Noalyss\XMLDocument\XMLInvoice_Reader;
/**
* @file
* @brief Document used in accountancy : invoice , credit note, ...It is
* a specialization of Document used in Follow-UP
*/
/**
* @class
* @brief Document used in accountancy : invoice , credit note, ... It is
* a specialization of Document used in Follow-UP.
* property :
* - d_id JRN.JR_ID
* - d_name name Receipt number
- d_description Comment of the operation
- d_mimetype mimetype of the document JRN.JR_PJ_TYPE
- d_filename filename JRN.JR_PJ_NAME
- document_xml oid of the XML invoice (including PDF) JRN.JR_DOCUMENT_XML
- d_lob = JRN.JR_PJ
*
*/
class Acc_Document extends Document {
private $document_xml; ///< $document_xml (oid) XML document e-invoice
public function get_document_xml() {
return $this->document_xml;
}
public function set_document_xml($document_xml) {
$this->document_xml = $document_xml;
return $this;
}
/**
* @brief constructor
* @param $cn \Database
* @param $jr_id (int) JRN.JRID will be in d_id
*/
function __construct($cn, $jr_id=0)
{
$this->db=$cn;
$this->set_id($jr_id);
// counter for MARCH_NEXT
$this->counter=0;
}
/**
* @brief set_id fill up d_filename, d_mimetype,d_lob,d_description,jr_pj_number
*/
function set_id($jr_id) {
$this->d_id=$jr_id;
if ( $jr_id == 0 ){
return $this;
}
$row = $this->db->get_row("select jr_comment
,jr_pj
,jr_pj_name
,jr_pj_type
,jr_pj_number
,jr_document_xml
from jrn
where
jr_id=$1", [$this->d_id]);
if ( empty ($row)) {
return $this;
}
$this->d_name=$row['jr_pj_number'];
$this->d_description=$row['jr_comment'];
$this->d_mimetype=$row['jr_pj_type'];
$this->d_filename=$row['jr_pj_name'];
$this->d_lob=$row['jr_pj'];
$this->document_xml=$row['jr_document_xml'];
return $this;
}
/**
* @brief save the file into DB, will create a large object if there
* is no document to replace. It will change the d_filename, d_mimetype
*
* @param $d_filename (string) full path to the file to load into DB
*
* @returns false if d_id = 0 or the file doesn't exist, true for success
*/
function update($filename) {
if ($this->d_id == 0) return false;
if ( ! file_exists($filename)) return false;
$this->db->start();
$this->d_mimetype= mime_content_type($filename);
$this->d_filename= basename($filename);
if ( $this->d_lob == "") {
$this->db->lo_unlink($this->d_lob);
}
$this->d_lob=$this->db->lo_import($filename);
$this->db->exec_sql(
"update jrn set jr_pj=$1,jr_pj_name=$2,jr_pj_type=$3
where jr_id=$4",
[$this->d_lob,$this->d_filename,$this->d_mimetype,$this->d_id]
);
$this->db->commit();
return true;
}
/**
* @brief for FACTURX we replace the PDF save in DB by this one, always
* a PDF (since it is a FACTURX document)
*/
function replace_receipt($new_oid)
{
if ($this->d_lob != "")
{
$this->db->lo_unlink($this->d_lob);
}
$this->d_lob=$new_oid;
$this->db->exec_sql("
update jrn
set
jr_pj = $1
,jr_pj_name =$2
,jr_pj_type =$3
where jr_id=$4
",
[$this->d_lob
,$this->d_filename
,$this->d_mimetype
,$this->d_id]
);
}
/**
* @brief save the Large Object $oid in the column JRN.JR_DOCUMENT_XML
* @param $oid( OID) PostgreSQL Object ID
*/
function update_document_xml($oid){
$this->db->exec_sql("update jrn set jr_document_xml=$1 where
jr_id=$2",[
$oid,
$this->d_id
]);
}
/**
* @brief create the invoice and saved it as attachment to the
* operation,
* @param $p_array is normally the $_POST
@verbatim
Array
(
[ledger_type] => VEN
[gDossier] => x
[nb_item] => 1 (number of item used to numerate e_marchX, e_quantX ,...)
[p_jrn] => 2
[p_jrn_predef] => 2
[jrn_type] => VEN or ACH
[e_date] => 10.06.2025 (date)
[e_ech] => limite date
[e_client] => CLIENT
[e_pj] => 25.827
[e_pj_suggest] => 25.827
[e_comm] => E-INVOICE
[p_currency_code] => 0
[p_currency_rate] => 1 (rate if 1 for EURO)
[jrn_note_input] =>
[e_march0] => 7DVINV
[e_march0_label] => Label of the operation
[e_march0_price] => 10.0000
[e_quant0] => 1.0000
[htva_march0] => 10
[e_march0_tva_id] => 1
[e_march0_tva_amount] => 2.1
[tva_march0] => 2.1
[tvac_march0] => 12.1
...
[mp_date] => (dd.mm.yyyy date of payment)
[acompte] => 0 (amount to deduce as advance payment)
[e_comm_paiement] => (string : comment of the payment)
[e_mp] => 0 (method of payment it is the XX in e_mp_qcode_XX)
[e_mp_qcode_16] => Banque 1
[e_mp_qcode_17] => Banque 2
[view_invoice] => Enregistrer
[gen_doc] => int DOCUMENT_MODELE.MD_ID , document template to use
)
* @endverbatim
* @todo rewrite code : remove extract and +SQL value
* @returns void
*/
function create_document($internal, $p_array) {
$this->f_id = $p_array['e_client'];
// var md_id (int) DOCUMENT_MODELE.MD_ID
$this->md_id = $p_array['gen_doc'];
// var ag_id == 0 fake follow-up
$this->ag_id = 0;
// var e_pj (string) receipt nb
$p_array['e_pj'] = $this->db->get_value("select jr_pj_number from jrn where jr_id=$1"
, [$this->d_id]);
$filename = "";
// generate the document and set d_lob,d_mimetype,
$this->generate($p_array, $p_array['e_pj']);
// Move the document to accountancy (table JRN),
$this->moveDocumentACC($internal);
// Update the comment with invoice number, if the comment is empty
if (!isset($p_array['e_comm']) || noalyss_strlentrim($p_array['e_comm']) == 0) {
$sql = "update jrn set jr_comment=' document " . $this->d_number . "' where jr_internal=$1";
$this->db->exec_sql($sql, [$internal]);
}
}
/**
* @brief export the file to the file system and complet $this->d_mimetype, d_filename and
* @param $destination_file (string) full path to document
* @return bool false for failure and string (the full path_name) for success
*/
function export_file($destination_file) {
if (empty($this->d_filename)) {
return false;
}
$this->db->start();
if ($this->db->lo_export($this->d_lob, $destination_file) == false) {
record_log("ACD122. cannot export");
$this->db->commit();
return false;
}
$this->db->commit();
return $destination_file;
}
/**
* \brief Save a "piece justificative" , the name must be a receipt. If it
* is a XML document, split it into 2 parts : PDF and XML
* the PDF be stored in JR_PJ and the XML into JR_DOCUMENT_XML
*
*
* \return $oid of the lob file if success null if a error occurs
*
*/
function save_receipt()
{
$this->db->start();
/**
* pj is the $_FILES key
*/
$a_file= $this->db->upload('pj',only_oid:false);
if ($a_file == false) {
return false;
}
$oid=$a_file['oid'];
// Remove old document if any
$old_oid = $this->db->get_value("select jr_pj from jrn where jr_id=$1"
,[$this->d_id]);
if ( $old_oid != "")
{
$this->db->lo_unlink( $old_oid);
}
// if there is a e-invoice in XML
if ( $_FILES['pj']['type'] == 'text/xml'
|| $_FILES['pj']['type'] == 'application/xml'
)
{
// save the XML
$this->db->exec_sql("update jrn set jr_document_xml = $1
where
jr_id=$2",
[$oid,$this->d_id]);
$xmlreader= XMLInvoice_Reader::build_from_file($a_file['filename']);
//@var $embedded_file (array) keys = filecontent: binary data
//,mimecode mimetype and filename (string)
try
{
$embedded_file=$xmlreader->get_embedded_document();
if ($embedded_file == false)
{
$embedded_file=array();
// create a PDF with standard information
$pdf=$xmlreader->to_pdf($this->db);
$file_oid=$this->db->lo_write($pdf->Output("S"));
$embedded_file['filename']="invoice.pdf";
$embedded_file['mimecode']="application/pdf";
}
else
{
$file_oid=$this->db->lo_write($embedded_file['filecontent']);
if ( $file_oid == false )
{
// create a PDF with standard information
$pdf=$xmlreader->to_pdf($this->db);
$file_oid=$this->db->lo_write($pdf->Output("S"));
}
}
//@var $file_oid OID of the large object saved in DB
$this->d_name=$embedded_file['filename'];
$this->d_description=$embedded_file['filename'];
$this->d_lob=$file_oid;
$this->d_mimetype=$embedded_file['mimecode'];
// save extracted document into DB
$this->db->exec_sql("update jrn set jr_pj=$1 , jr_pj_name=$2,
jr_pj_type=$3 where jr_id=$4",
array(
$this->d_lob
, $this->d_name
, $this->d_description
, $this->d_id
)
);
return $file_oid;
} catch (\Exception $e ) {
\record_log($e);
// if exception is not too many document or document corrupted
// then rethrow the exception
if ( !in_array(e->getCode(),[110,116]) )
{
throw new \Exception("X281 ",281,$e);
}
}
}
// save new document
$this->db->exec_sql("update jrn set jr_pj=$1 , jr_pj_name=$2,
jr_pj_type=$3 where jr_id=$4",
array(
$oid
, $_FILES['pj']['name']
, $_FILES['pj']['type']
, $this->d_id
)
);
$this->d_name=$_FILES['pj']['name'];
$this->d_description=$_FILES['pj']['name'];
$this->d_lob=$oid;
$this->d_mimetype=$_FILES['pj']['type'];
$this->db->commit();
return $oid;
}
/**
* @brief return a string with a link download XML or an empty string
* if there is no XML to download
*/
function link_download_xml():string
{
$xml_oid=$this->db->get_value("select jr_document_xml from jrn where jr_id=$1",
[$this->d_id]);
if ($xml_oid == "") { return "";}
$url= "export.php?".http_build_query(
[
"gDossier"=>\Dossier::id(),
"jr_id"=>$this->d_id,
"act"=>'RAW:xml-invoice'
]);
$r = sprintf('<a class="mtitle line" href="%s">',$url);
$r .= _("XML")
.'<i class="icon-download">'
.'</i>'
.'</a>';
return $r;
}
}

View file

@ -1610,6 +1610,7 @@ class Acc_Ledger extends jrn_def_sql
$acc_end->currency_rate=$currency_rate;
$acc_end->currency_rate_ref=$currency_rate_ref->get_rate();
// @var $jr_id (int) JRN.JR_ID
$jr_id=$acc_end->insert_jrn();
$this->jr_id=$jr_id;
@ -1651,7 +1652,8 @@ class Acc_Ledger extends jrn_def_sql
*/
if (isset($_FILES["pj"]))
{
$this->db->save_receipt($seq);
$acc_document=new Acc_Document($this->db, $jr_id);
$acc_document->save_receipt();
}
/*----------------------------------------------
* Save the note
@ -1973,31 +1975,22 @@ class Acc_Ledger extends jrn_def_sql
}
/**
* @brief create the invoice and saved it as attachment to the
* operation,
* @brief alias for Acc_Document->create_document
* @param $internal is the internal code
* @param $p_array is normally the $_POST
* @todo rewrite code : remove extract and +SQL value
* \return a string
@see Acc_Document::create_document
* @return a string
*/
function create_document($internal, $p_array)
{
$doc=new Document($this->db);
$doc->f_id=$p_array['e_client'];
$doc->md_id=$p_array['gen_doc'];
$doc->ag_id=0;
$p_array['e_pj']=$this->pj;
$filename="";
$doc->Generate($p_array, $p_array['e_pj']);
// Move the document to the jrn
$doc->moveDocumentPj($internal);
// Update the comment with invoice number, if the comment is empty
if (!isset($p_array['e_comm'])||noalyss_strlentrim($p_array['e_comm'])==0)
{
$sql="update jrn set jr_comment=' document ".$doc->d_number."' where jr_internal=$1";
$this->db->exec_sql($sql,[$internal]);
$id=$this->db->get_value('select jr_id from jrn where jr_internal=$1',
[$internal]);
if ( $id == "") {
return;
}
return h($doc->d_name.' ('.$doc->d_filename.')');
$acc_document=new Acc_Document($this->db,$id);
$acc_document->create_document($internal,$p_array);
return h($acc_document->d_name.' ('.$acc_document->d_filename.')');
}
/**

View file

@ -983,6 +983,7 @@ class Acc_Ledger_Fin extends Acc_Ledger
throw new Exception (_("Erreur de balance"),EXC_BALANCE);
// $acc_operation->update_receipt();
$this->jr_id=&$jr_id;
$this->db->exec_sql('update jrn set jr_pj_number=$1 where jr_id=$2', array($acc_operation->pj, $jr_id));
$internal=$this->compute_internal_code($seq);
@ -1073,21 +1074,18 @@ class Acc_Ledger_Fin extends Acc_Ledger
$class=($i%2==0)?' class="even" ':' class="odd" ';
$ret.=tr($row, $class);
if ($i==0)
if ($i==0 && isset($_FILES['pj']) )
{
// first record we upload the files and
// keep variable to update other row of jrn
if (isset($_FILES['pj']))
$oid=$this->db->save_receipt($seq);
$acc_document=new Acc_Document($this->db,$jr_id);
$oid=$acc_document->save_receipt();
}
else
elseif ($oid != 0 )
{
if ($oid!=0)
{
$this->db->exec_sql("update jrn set jr_pj=$1 , jr_pj_name=$2,
jr_pj_type=$3 where jr_grpt_id=$4",
array($oid, $_FILES['pj']['name'], $_FILES['pj']['type'], $seq));
}
$this->db->exec_sql("update jrn set jr_pj=$1 , jr_pj_name=$2,
jr_pj_type=$3 where jr_grpt_id=$4",
array($oid, $_FILES['pj']['name'], $_FILES['pj']['type'], $seq));
}
} // for nbitem
// increment pj

View file

@ -29,9 +29,86 @@ require_once NOALYSS_INCLUDE.'/lib/ac_common.php';
/*!
* \class Acc_Ledger_Purchase
* \brief Handle the ledger of purchase,
*
*
** @brief : input, confirm and save new operations in edger of purchase
the $_POST data is an array with these keys
@code
Array
(
// =====================
// ANALYTIC PART
// =====================
[pa_id] => Array
(
[0] => 1
)
[op] => Array
(
[0] => 0
)
[amount_t0] => 10
[hplan] => Array
(
[0] => Array
(
[0] => -1
)
)
[val] => Array
(
[0] => Array
(
[0] => 10
)
)
// =====================
// SALES DATA
// =====================
[e_client] => QuickCode supplier
[nb_item] => number of items (lines of invoice)
[p_jrn] => JRN_DEF.JRN_DEF_ID id of the ledger
[jrn_note_input] => Note JRN_NOTE.N_TEXT
[mt] => 1759130008.8134
[p_currency_rate] => Currency Rate
[p_currency_code] => Currency Code
[e_comm] => Description of invoice
[e_date] => date invoice
[e_ech] => limit date
[e_pj] => Receipt number
[e_pj_suggest] => suggested receipt number
[e_mp] => payment means (
[jrn_type] => Type of ledger (always ACH)
//---------------------------------------------
// For each invoice line
//---------------------------------------------
[e_march0] => QuickCode of the item
[e_march0_label] => label
[e_march0_price] => unit price
[e_march0_tva_id] => VAT ID
[e_march0_tva_amount] => amount of VAT
[e_quant0] => quantity of item
//========================
// MISC
//========================
[repo] => 1 (repository)
[gen_invoice] => on (it is asked to generate an invoice
[gen_doc] => Document template id
[bon_comm] => JRN_INFO.
[other_info] = JRN_INFO.>
[opd_name] => Name of operation template
[od_description] => Description of operation template
[reverse_date] => if reverse is asked
[ext_label] => Label for revese operation
[jr_optype] => Type of operation NOR:Normal,, EXT; reverse, ..
)
@endcode
*/
class Acc_Ledger_Purchase extends Acc_Ledger
{
@ -978,7 +1055,9 @@ class Acc_Ledger_Purchase extends Acc_Ledger
if ( isset ($_FILES))
{
if ( sizeof($_FILES) != 0 )
$this->db->save_receipt($seq);
$acc_document=new \Acc_Document($this->db, $this->jr_id);
$acc_document->save_receipt();
$this->doc=HtmlInput::show_receipt_document($this->jr_id,h($_FILES['pj']['name']));
}
$str_file="";
/* Generate an document and save it into the database (Note de frais only)

View file

@ -28,9 +28,87 @@ require_once NOALYSS_INCLUDE.'/lib/user_common.php';
require_once NOALYSS_INCLUDE.'/lib/ac_common.php';
/*!
* \brief Handle the ledger of sold,
*
* @exception throw an exception is something is wrong
* @class Acc_Ledger_Sale
* @brief : input, confirm and save new operations in edger of sales
the $_POST data is an array with these keys
@code
Array
(
// =====================
// ANALYTIC PART
// =====================
[pa_id] => Array
(
[0] => 1
)
[op] => Array
(
[0] => 0
)
[amount_t0] => 10
[hplan] => Array
(
[0] => Array
(
[0] => -1
)
)
[val] => Array
(
[0] => Array
(
[0] => 10
)
)
// =====================
// SALES DATA
// =====================
[e_client] => QuickCode customer
[nb_item] => number of items (lines of invoice)
[p_jrn] => JRN_DEF.JRN_DEF_ID id of the ledger
[jrn_note_input] => Note JRN_NOTE.N_TEXT
[mt] => 1759130008.8134
[p_currency_rate] => Currency Rate
[p_currency_code] => Currency Code
[e_comm] => Description of invoice
[e_date] => date invoice
[e_ech] => limit date
[e_pj] => Receipt number
[e_pj_suggest] => suggested receipt number
[e_mp] => payment means (
[jrn_type] => Type of ledger (always VEN)
//---------------------------------------------
// For each invoice line
//---------------------------------------------
[e_march0] => QuickCode of the item
[e_march0_label] => label
[e_march0_price] => unit price
[e_march0_tva_id] => VAT ID
[e_march0_tva_amount] => amount of VAT
[e_quant0] => quantity of item
//========================
// MISC
//========================
[repo] => 1 (repository)
[gen_invoice] => on (it is asked to generate an invoice
[gen_doc] => Document template id
[bon_comm] => JRN_INFO.
[other_info] = JRN_INFO.>
[opd_name] => Name of operation template
[od_description] => Description of operation template
[reverse_date] => if reverse is asked
[ext_label] => Label for revese operation
[jr_optype] => Type of operation NOR:Normal,, EXT; reverse, ..
)
@endcode
*/
class Acc_Ledger_Sale extends Acc_Ledger {
@ -698,17 +776,7 @@ class Acc_Ledger_Sale extends Acc_Ledger {
where j_id in (select j_id from jrnx where j_grpt=$2)'
, array($internal, $seq));
/* Save the attachment or generate doc */
if (isset($_FILES['pj'])) {
if (noalyss_strlentrim($_FILES['pj']['name']) != 0)
$this->db->save_receipt($seq);
else
/* Generate an invoice and save it into the database */
if (isset($_POST['gen_invoice'])) {
$file = $this->create_document($internal, $p_array);
$this->doc=HtmlInput::show_receipt_document($this->jr_id,h($file));
}
}
//----------------------------------------
// Save the payer
//----------------------------------------
@ -932,6 +1000,7 @@ class Acc_Ledger_Sale extends Acc_Ledger {
$r.='<td>' . _('Numéro Pièce') .$span.'</td><td>'. hb($this->pj) . '</td>';
}
}
$e_comm=($e_comm == "")?_('Facture')." $e_pj":$e_comm;
$r.='</tr>';
$r.='<tr>';
$r.='<td> ' . _('Date') . '</td><td> ' . hb($e_date) . '</td>';
@ -954,7 +1023,7 @@ class Acc_Ledger_Sale extends Acc_Ledger {
$r.='</tr>';
$r.='<tr>';
$r.='<td> ' . _('Client') . '</td><td> ' . hb($e_client . ':' . $client_name) . '</td>';
$r.='<td> ' . _('Client') . '</td><td> ' . HtmlInput::card_detail($e_client).":".hb( $client_name) . '</td>';
$r.='</tr>';
$r.='</table>';
$r.='<pre>'._('Note').' '.h($p_array['jrn_note_input']).'</pre>';
@ -1296,7 +1365,8 @@ EOF;
return $r;
}
/*!\brief the function extra info allows to
/*!
* \brief the function extra info allows to
* - add a attachment
* - generate an invoice
* - insert extra info
@ -1304,31 +1374,35 @@ EOF;
*/
public function extra_info() {
$r = '<div id="facturation_div_id" style="height:185px;height:10rem">';
$r = '<div id="facturation_div_id" style="display:flex;height:185px;height:10rem">';
// check for upload piece
$file = new IFile();
$file->table = 0;
$file->setAlertOnSize(true);
$r.='<p class="decale">';
// add a receipt
$r.=_("Ajoutez une pièce justificative ");
$r.=$file->input("pj", "");
if ($this->db->count_sql("select md_id,md_name from document_modele where md_affect='VEN' ") > 0) {
$r.=_('ou générer une facture') . ' <input type="checkbox" name="gen_invoice" CHECKED>';
// We propose to generate the invoice and some template
$doc_gen = new ISelect();
$doc_gen->name = "gen_doc";
$doc_gen->value = $this->db->make_array(
"select md_id,md_name " .
" from document_modele where md_affect='VEN' order by 2");
$r.=$doc_gen->input() . '<br>';
}
//------------------------------------------------
// Propose to generate an invoice
//------------------------------------------------
$r.=_('ou générer une facture') . ' <input type="checkbox" name="gen_invoice" CHECKED>';
// We propose to generate the invoice and some template
$doc_gen = new ISelect();
$doc_gen->name = "gen_doc";
$doc_gen->value = $this->db->make_array(
"select md_id,md_name " .
" from document_modele where md_affect='VEN' ".
" union select -2,'"._("0 - Facture PDF Standard")."' ".
" order by 2");
$r.=$doc_gen->input() . '<br>';
$r.='<br>';
$obj = new IText();
$r.=_('Numero de bon de commande : ') . $obj->input('bon_comm') . '<br>';
$r.=_('Communication ou autre information : ') . $obj->input('other_info') . '<br>';
$r.=_('Numero de bon de commande') . $obj->input('bon_comm') . '<br>';
$r.=_('Communication') . $obj->input('other_info') . '<br>';
$r.='</p>';
$r.='</div>';
return $r;

View file

@ -91,9 +91,9 @@ class Acc_Operation
jr_id {$this->jr_id}
jr_optype {$this->jr_optype}
amount {$this->amount}
currency_rate {$this->amount}
currency_rate_ref {$this->amount}
currency_id {$this->amount}
currency_rate {$this->currency_rate}
currency_rate_ref {$this->currency_rate_ref}
currency_id {$this->currency_id}
]
EOF;
return $r;
@ -1031,11 +1031,43 @@ EOF;
/**
* @class Acc_Detail
* @brief Contains the detail of an operation Acc_Operation
* propery :
* - $det
- jr_id PKfrom table JRN
- jr_def_id id of the ledger (FK to - _DEF- _DEF_ID)from table JRN
- jr_montant AMOUNT of the operationfrom table JRN
- jr_comment COMMENT from table JRN
- jr_date DATEfrom table JRN
- jr_grpt_id CODE to group - X rowsfrom table JRN
- jr_internal INTERNAL CODEfrom table JRN
- jr_tech_date DATE OF CHANGEfrom table JRN
- jr_tech_per FK TO PARAM_PERIODEP_IDfrom table JRN
- jrn_ech from table JRN
- jr_ech DATE LIMIT OF PAYMENTfrom table JRN
- jr_rapt from table JRN
- jr_echfrom table JRN
- jr_validfrom table JRN
- jr_opid from table JRN
- jr_c_opidfrom table JRN
- jr_pj OID OF THE DOCUMENTfrom table JRN
- jr_pj_name NAME OF THE DOCUMENTfrom table JRN
- jr_pj_typefrom table JRN
- jr_pj_number RECEIPT NBfrom table JRN
- jr_mt INTERNAL CODEfrom table JRN
- jr_raptfrom table JRN
- jr_date_paid DATE OF PAYMENTfrom table JRN
- jr_optype TYPE OF OPERATION NOR = NORMAL OPE=OPENING EXT=EXTOURNEfrom table JRN
- currency_id FK TO CURRENCYIDfrom table JRN
- currency_rate amountfrom table JRN
- currency_rate_ref amount in CURRENT_HISTORYCH_VALUEfrom table JRN
* - note from table JRN_NOTE
- $jr_id JRN.JR_ID
- $info
*/
class Acc_Detail extends Acc_Operation
{
public $det;
public $jr_id;
public $det;//!< Object with columns from JRN
public $jr_id;//! $jr_id (int) JRN.JR_ID
public $info;
function __construct($p_cn,$p_jrid=0)
@ -1050,10 +1082,34 @@ class Acc_Detail extends Acc_Operation
*/
function get()
{
$sql="SELECT jr_id, jr_def_id, jr_montant, jr_comment, jr_date, jr_grpt_id,
jr_internal, jr_tech_date, jr_tech_per, jrn_ech, jr_ech, jr_rapt,jr_ech,
jr_valid, jr_opid, jr_c_opid, jr_pj, jr_pj_name, jr_pj_type,
jr_pj_number, jr_mt,jr_rapt,jr_date_paid,jr_optype,currency_id,currency_rate,currency_rate_ref
$sql="SELECT jr_id
, jr_def_id
, jr_montant
, jr_comment
, jr_date
, jr_grpt_id
, jr_internal
, jr_tech_date
, jr_tech_per
, jrn_ech
, jr_ech
, jr_rapt
,jr_ech
, jr_valid
, jr_opid
, jr_c_opid
, jr_pj
, jr_pj_name
, jr_pj_type,
jr_pj_number
, jr_mt
,jr_rapt
,jr_date_paid
,jr_optype
,currency_id
,currency_rate
,currency_rate_ref
,jr_document_xml
FROM jrn where jr_id=$1";
$array=$this->db->get_array($sql,array($this->jr_id));
if ( count($array) == 0 ) throw new Exception('Aucune ligne trouvée');
@ -1363,4 +1419,5 @@ class Acc_Fin extends Acc_Detail
return $array;
}
}

View file

@ -30,7 +30,9 @@
"label"=>"tva_label",
"rate"=>"tva_rate",
"comment"=>"tva_comment",
"account"=>"tva_poste");
"account"=>"tva_poste"
tva_peppol_code
* );
*/
class Acc_Tva
@ -42,7 +44,9 @@ class Acc_Tva
"account"=>"tva_poste",
"both_side"=>'tva_both_side',
'tva_reverse_account'=>'tva_reverse_account',
'tva_code'=>'tva_code');
'tva_code'=>'tva_code',
"tva_peppol_code"=>"tva_peppol_code"
);
public $tva_id,
$tva_label,
$tva_rate,
@ -50,16 +54,18 @@ class Acc_Tva
$tva_poste,
$tva_both_side,
$tva_code,
$tva_reverse_account;
$tva_reverse_account,
$tva_peppol_code
;
private $cn; //!< Database connection
private Tva_Rate_SQL $tva_rate_sql;
function __construct ($p_init,$p_tva_id=-1)
function __construct (Database $cn,$p_tva_id=-1)
{
$this->cn=$p_init;
$this->tva_rate_sql=new Tva_Rate_SQL($p_init,$p_tva_id);
$this->cn=$cn;
$this->tva_rate_sql=new Tva_Rate_SQL($cn,$p_tva_id);
$this->tva_id=$p_tva_id;
$this->tva_label=&$this->tva_rate_sql->tva_label;
$this->tva_rate=&$this->tva_rate_sql->tva_rate;
@ -68,6 +74,7 @@ class Acc_Tva
$this->tva_both_side=&$this->tva_rate_sql->tva_both_side;
$this->tva_code=&$this->tva_rate_sql->tva_code;
$this->tva_reverse_account=&$this->tva_rate_sql->tva_reverse_account;
$this->tva_peppol_code=&$this->tva_rate_sql->tva_peppol_code;
}
/**

View file

@ -699,4 +699,18 @@ class Card_Property
}
return null;
}
/**
* @brief returns the property value of a card without creating a card
* @param \Database $conx
* @param $card_id (int) FICHE.F_ID
* @param $property_id (int) ad_value
* @return string or false if nothing was found
*/
static function get_attribute(\Database $conx, $card_id,$property_id)
{
$r=$conx->get_value("select ad_value from fiche_detail where
ad_id = $1 and f_id=$2",[$property_id,$card_id]);
if ( $conx->count() == 0) { return false;}
return $r;
}
}

View file

@ -81,34 +81,6 @@ class Database extends DatabaseCore
. ",dbname = ".$this->get_dbname()
. "]";
}
/***
* \brief Save a "piece justificative" , the name must be pj
*
* \param $seq jr_grpt_id
* \return $oid of the lob file if success
* null if a error occurs
*
*/
function save_receipt($seq)
{
$oid = $this->upload('pj');
if ($oid == false) {
return false;
}
// Remove old document
$ret = $this->exec_sql("select jr_pj from jrn where jr_grpt_id=$seq");
if (pg_num_rows($ret) != 0) {
$r = pg_fetch_array($ret, 0);
$old_oid = $r['jr_pj'];
if (strlen($old_oid??"") != 0)
$this->lo_unlink( $old_oid);
}
// Load new document
$this->exec_sql("update jrn set jr_pj=$1 , jr_pj_name=$2,
jr_pj_type=$3 where jr_grpt_id=$4",
array($oid, $_FILES['pj']['name'], $_FILES['pj']['type'], $seq));
return $oid;
}
/**
* \brief Get version of a database, the content of the

View file

@ -19,10 +19,14 @@
*/
// Copyright Author Dany De Bontridder danydb@aevalys.eu
/*! \file
/*!
* \file
* \brief Class Document corresponds to the table document
*/
/*! \brief Class Document corresponds to the table document
/*!
* \class
* \brief
* Class Document corresponds to the table DOCUMENT
*/
class Document
@ -38,7 +42,7 @@ class Document
var $d_number; /*!< $d_number number of the document */
var $md_id; /*!< $md_id document's template */
var $f_id; /*!< fiche.f_id */
private $counter; /*!< counter for the items ( goods ) */
protected $counter; /*!< counter for the items ( goods ) */
var $d_name; /*!< document name */
var $md_type ; /*!< Type of document */
/*!
@ -54,6 +58,24 @@ class Document
// counter for MARCH_NEXT
$this->counter=0;
}
function __toString(): string
{
return "Document[db=" . $this->db
. ", d_id=" . $this->d_id
. ", ag_id=" . $this->ag_id
. ", d_mimetype=" . $this->d_mimetype
. ", d_filename=" . $this->d_filename
. ", d_lob=" . $this->d_lob
. ", d_description=" . $this->d_description
. ", d_number=" . $this->d_number
. ", md_id=" . $this->md_id
. ", f_id=" . $this->f_id
. ", counter=" . $this->counter
. ", d_name=" . $this->d_name
. ", md_type=" . $this->md_type
. "]";
}
/**
* @brief insert a minimal document and set the d_id
@ -105,16 +127,20 @@ class Document
/*!
* \brief Generate the document, Call $this-\>replace to replace
* tag by value
* @param p_array contains the data normally it is the $_POST
* @param $p_filename contains the new filename
* @param p_array contains the data normally it is the $_POST (see Acc_Ledger_Sale or
* Acc_Ledger_Purchase)
* @see Acc_Ledger_Sale
* @see Acc_Ledger_Purchase
* @param $p_filename contains the new filename, if not given the filename will be generated
* \return an string : the url where the generated doc can be found, the name
* of the file and his mimetype
*/
function generate($p_array, $p_filename="")
{
try {
// create a temp directory in /tmp to unpack file and to parse it
///@var $dirname (string) temp directory in /tmp to unpack file and to parse it
$dirname=tempnam($_ENV['TMP'], 'doc_');
if ($dirname == false) {
throw new Exception ('DC117 cannot create tmp file',5000);
@ -124,6 +150,31 @@ class Document
if ( mkdir($dirname) == false ) {
throw new Exception ("DC121 cannot create $dirname directory",5000);
}
/**
* md_id == -2 is the standard PDF invoice, you don't parse or compute
* it
*/
if ( $this->md_id == -2)
{
$file_to_parse=str_replace(
array('/', '*', '<', '>', ';', ',', '\\', '.', ':', '(', ')', ' ', '[', ']')
, "-"
, "inv-std-".$p_array['e_pj'].".pdf");
$this->d_number=$this->db->get_next_seq("seq_doc_type_stdinv");
$this->d_filename=$file_to_parse;
$this->d_mimetype="application/pdf";
$this->d_name=$file_to_parse;
$standard_invoice=new \Noalyss\Invoice_PDF($this->db,$dirname,$file_to_parse);
$standard_invoice->set_data($p_array);
$standard_invoice->export();
$this->saveGenerated($dirname.DIRECTORY_SEPARATOR.$file_to_parse);
// Invoice
$href=http_build_query(array('gDossier'=>Dossier::id(), "d_id"=>$this->d_id, 'act'=>'RAW:document'));
$ret='<A class="mtitle" HREF="export.php?'.$href.'">'._('Document').'</A>';
return $ret;
}
// Retrieve the lob and save it into $dirname
$this->db->start();
$dm_info="select md_name,md_type,md_lob,md_filename,md_mimetype
@ -145,7 +196,8 @@ class Document
record_log(sprintf('DOCUMENT.GENERATE.D1 , export failed %s %s',$dirname, $filename));
throw new Exception(sprintf(_("Export a échoué pour %s"), $filename));
}
// $type (letter) type of document : OOo for openoffice otherwise n , with OOo the file
// is a ZIP XML
$type="n";
// if the doc is a OOo, we need to unzip it first
// and the name of the file to change is always content.xml
@ -247,7 +299,7 @@ class Document
{
if (mkdir($temp_dir)==false)
{
$msg=sprintf("D221."._("Ne peut pas créer le répertoire %s", $temp_dir));
$msg=sprintf("D221."._("Ne peut pas créer le répertoire %s"), $temp_dir);
record_log("D221".$msg);
throw new Exception($msg);
}
@ -332,10 +384,9 @@ class Document
}
/*!
* \brief Save the generated Document
* \brief insert the generated Document into the database, update the $this->d_id
* that is the PK of document. and load the PDF into the database.
* \param $p_file is the generated file
*
*
* \return 0 if no error otherwise 1
*/
@ -716,22 +767,22 @@ class Document
$p_tag=noalyss_str_replace('=', '', $p_tag);
$r="Tag inconnu";
static $aComment=NULL;
static $counter_comment=1; /* <! counter for the comment , skip the first one which is the descrition */
static $counter_comment=1; /*<! counter for the comment , skip the first one which is the descrition */
static $aRelatedAction=NULL;
static $counter_related_action=0; /* <! counter for the related action */
static $counter_related_action=0; /*<! counter for the related action */
static $aRelatedOperation=NULL;
static $counter_related_operation=0; /* <! counter for the related operation */
static $counter_related_operation=0; /*<! counter for the related operation */
static $aFileAttached=NULL;
static $counter_file=0; /* <! counter for the file */
static $counter_file=0; /*<! counter for the file */
static $aOtherCard=NULL;
static $counter_other_card=0; /* <! counter for the other card */
static $counter_other_card=0; /*<! counter for the other card */
static $aTag=NULL;
static $counter_tag=0; /* <! counter for the tags */
static $counter_tag=0; /*<! counter for the tags */
static $aParameterExtra=NULL; // Extra parameter for the company
switch ($p_tag)
@ -1769,14 +1820,11 @@ class Document
}
/*!
* \brief Move a document from the table document into the concerned row
* \brief Move a document from the table document into the concerned operation
* the document is not copied : it is only a link
*
* \param $p_internal internal code
*
*/
function moveDocumentPj($p_internal)
function moveDocumentACC($p_internal)
{
$sql="update jrn set jr_pj=$1,jr_pj_name=$2,jr_pj_type=$3 where jr_internal=$4";
@ -1836,7 +1884,7 @@ class Document
}
/**
* replace a pattern with a value in the buffer , handle the change for OOo type file and amount
* @brief replace a pattern with a value in the buffer , handle the change for OOo type file and amount
*
* @param string $p_buffer
* @param string $_pattern
@ -1890,14 +1938,14 @@ class Document
function export_file($p_destination_file)
{
if ($this->d_id==0) {
return;
return false;
}
$this->db->start();
$ret=$this->db->exec_sql(
"select d_id,d_lob,d_filename,d_mimetype from document where d_id=$1", [$this->d_id]);
if (Database::num_row($ret)==0)
{
return;
return false;
}
$row=Database::fetch_array($ret, 0);
//the document is saved into file $tmp
@ -1916,15 +1964,14 @@ class Document
}
/**
* @brief transform the current Document to a PDF, returns the full path of the PDF from the TMP folder
* if the file IS a pdf , then export it and return the path to the file.
*
* @todo replace use of unoconv with a PHP lib to convert into PDF
* @return string full path to the PDF file
*/
function transform2pdf()
{
if (GENERATE_PDF == 'NO' ) {
\record_log(__FILE__."D1857 PDF not available");
throw new \Exception("Cannot not transform to PDF",5000);
}
// Extract from public.document
// Extract from public.document
$dirname=tempnam($_ENV['TMP'],"document");
if ( $dirname == false ) {
@ -1935,6 +1982,16 @@ class Document
if ( mkdir($dirname) == false ) {
throw new Exception("D1868.cannot create tmp directory",5000);
}
if ( $this->d_mimetype == "application/pdf") {
$destination_file=$dirname."/".$this->d_filename;
$this->export_file($destination_file);
return $dirname."/".$destination_file;
return;
}
if (GENERATE_PDF == 'NO' ) {
\record_log(__FILE__."D1857 PDF not available");
throw new \Exception("Cannot not transform to PDF",5000);
}
$destination_file=$dirname."/".$this->d_filename;
$this->export_file($destination_file);

View file

@ -329,7 +329,7 @@ class Fiche
* @param int $p_ad_id AD_ID from attr_def.ad_id
* @param int $p_return 1 return NOTFOUND otherwise an empty string
* @see constant.php
* @return string
* @return string
* @note reread data from database and so it reset previous unsaved change
*/
function get_attribute($p_ad_id,$p_return=1)
@ -1345,16 +1345,17 @@ class Fiche
bcscale(4);
$gDossier=dossier::id();
$p_search=sql_string($p_search);
$script=$_SERVER['PHP_SELF'];
$script=$_SERVER['PHP_SELF']??"";
// Creation of the nav bar
// Get the max numberRow
$filter_amount='';
global $g_user;
$filter_year=" j_tech_per in (select p_id from parm_periode ".
"where p_exercice='".$g_user->get_exercice()."')";
if ( $p_amount) $filter_amount=' and f_id in (select f_id from jrnx where '.$filter_year.')';
if ($p_amount) {
$filter_amount = ' and f_id in (select f_id from jrnx where ' . $filter_year . ')';
}
$all_tiers=$this->count_by_modele($this->fiche_def_ref,"",$p_sql.$filter_amount);
// Get offset and page variable
@ -1495,21 +1496,23 @@ class Fiche
return $this->fiche_def;
}
/*!
***************************************************
* \brief Check if a fiche is used by a jrn
* \brief Check if a card can be used and then belong tp a specific ledger, it the card
*
* return 1 if the fiche is in the range otherwise 0, the quick_code
* or the id must be set
*
*
* \param $p_jrn journal_id
* \param $p_type : deb or cred default empty
* \param $jrn_def_id journal_id (JRN.JRN_DEF_ID)
* \param $side : deb or cred , default empty = both
*
* \return 1 if the fiche is in the range otherwise < 1
* -1 the card doesn't exist
* -2 the ledger has no card to check
* \return 1 if the card belongs to the ledger,
* 0 the card doesn't belong,
* -1 the card doesn't exist,
* -2 the ledger has no card to check,
* -3 there is no category of card for this ledger
*
*/
function belong_ledger($p_jrn,$p_type="")
function belong_ledger($jrn_def_id,$side="")
{
// check if we have a quick_code or a f_id
if (($this->quick_code==null || $this->quick_code == "" )
@ -1519,41 +1522,38 @@ class Fiche
}
//retrieve the quick_code
if ( $this->quick_code=="")
$this->quick_code=$this->get_quick_code();
if ( $this->quick_code==null)
return -1;
if ( $this->id == 0 )
if ( $this->get_by_qcode(null,false) == 1)
return -1;
$get="";
if ( $p_type == 'deb' )
{
$get='jrn_def_fiche_deb';
}elseif ( $p_type == 'cred' )
{
$get='jrn_def_fiche_cred';
if ($this->quick_code == "") {
$this->quick_code = $this->get_quick_code();
}
if ( $get != "" )
if ($this->quick_code == null) {
return -1;
}
if ($this->id == 0 && $this->get_by_qcode(null, false) == 1) {
return -1;
}
if ( $side == 'deb' )
{
$Res=$this->cn->exec_sql("select $get as fiche from jrn_def where jrn_def_id=$p_jrn");
$Res=$this->cn->exec_sql("select jrn_def_fiche_deb as fiche from jrn_def where jrn_def_id=$1",[$jrn_def_id]);
}elseif ( $side == 'cred' )
{
$Res=$this->cn->exec_sql("select jrn_def_fiche_cred as fiche from jrn_def where jrn_def_id=$1",[$jrn_def_id]);
}
else
{
// Get all the fiche type (deb and cred)
$Res=$this->cn->exec_sql(" select jrn_def_fiche_cred as fiche
from jrn_def where jrn_def_id=$p_jrn
from jrn_def where jrn_def_id=$1
union
select jrn_def_fiche_deb
from jrn_def where jrn_def_id=$p_jrn"
from jrn_def where jrn_def_id=$1",
[$jrn_def_id]
);
}
$Max=Database::num_row($Res);
if ( $Max==0)
if ( Database::num_row($Res)==0)
{
return -2;
}
@ -1582,11 +1582,11 @@ class Fiche
fd_id in (".$str_list.") and f_id= ".$this->id;
$Res=$this->cn->exec_sql($sql);
$Max=Database::num_row($Res);
if ($Max==0 )
if (Database::num_row($Res) == 0) {
return 0;
else
return 1;
}
return 1;
}
/*!
* \brief get all the card from a categorie
@ -1968,12 +1968,12 @@ class Fiche
}
/**
* @brief create a card from a qcode and returns a card
* @param string $p_qcode qcode of the card
* @param $cn Database cnx
* @param $p_qcode (string) qcode of the card
*/
static function from_qcode($p_qcode)
static function from_qcode(Database $cn,string $p_qcode)
{
$cn=Dossier::connect();
$card=new Card($cn);
$card=new Fiche($cn);
$card->get_by_qcode($p_qcode);
return $card;
}

View file

@ -0,0 +1,332 @@
<?php
namespace Noalyss;
/*
* This file is part of NOALYSS.
*
* NOALYSS 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 2 of the License, or
* (at your option) any later version.
*
* NOALYSS 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 NOALYSS; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
// Copyright Author Dany De Bontridder danydb@aevalys.eu 22/10/23
/**
* @file
* @brief create a standard invoice
*/
/**
* @class Invoice PDF
* @brief create a standard invoice
*/
class Invoice_PDF extends \PDF
{
private $data; //!< $data (array) see Acc_Ledger_Purchases
function __construct(
\Database $cn
, private $dirname //!< folder where to save file
, private $filename //!< filename to use
)
{
parent::__construct($cn);
}
public function get_dirname()
{
return $this->dirname;
}
public function get_filename()
{
return $this->filename;
}
public function set_dirname($dirname)
{
$this->dirname = $dirname;
return $this;
}
public function set_filename($filename)
{
$this->filename = $filename;
return $this;
}
function set_data($array)
{
$this->data = $array;
return $this;
}
function get_data()
{
return $this->data;
}
function footer()
{
//Position at 1 cm from bottom
$this->SetY(-10);
//Arial italic 8
$this->SetFont('Arial', '', 8);
//Page number
parent::Cell(0, 8, " Page " . $this->PageNo() . '/{nb}', 0, 0, 'C');
parent::Ln(3);
}
function header()
{
global $g_parameter;
$this->setY(15);
$this->SetFont('DejaVu', '', 6);
$colsize = 90;
$this->write_multi($colsize, 3, $g_parameter->MY_NAME);
$this->write_multi($colsize, 3, $this->data['e_date'], border: '', align: 'R');
$this->line_new();
$this->write_multi($colsize, 3,
sprintf("%s %s "
, $g_parameter->MY_STREET
, $g_parameter->MY_NUMBER));
$this->line_new();
$this->write_multi($colsize, 3, $g_parameter->MY_POSTCODE
. " " . $g_parameter->MY_CITY
. " " . $g_parameter->MY_COUNTRY
);
$this->line_new();
$this->write_multi($colsize, 3, $g_parameter->MY_TVA);
$this->line_new();
$email_company = $this->cn->get_value("select pe_value from parameter_extra where pe_code=$1",
['INVOICE_EMAIL_COMPANY']);
$site = $this->cn->get_value("select pe_value from parameter_extra where pe_code=$1",
['WEB_COMPANY']);
// for FRANCE , the SIREN and SIRET must be given
$siren = $this->cn->get_value("select pe_value from parameter_extra where pe_code=$1",
['SIREN']);
$siret = $this->cn->get_value("select pe_value from parameter_extra where pe_code=$1",
['SIRET']);
$iban = $this->cn->get_value("select pe_value from parameter_extra where pe_code=$1",
['COMPANY_BANK_IBAN']);
$bic = $this->cn->get_value("select pe_value from parameter_extra where pe_code=$1",
['COMPANY_BANK_BIC']);
if ($siret != "")
{
$this->write_multi($colsize, 3, "SIRET $siret");
$this->line_new();
}
if ($siren != "")
{
$this->write_multi($colsize, 3, "SIREN $siren");
$this->line_new();
}
if ($iban != "")
{
$this->write_multi($colsize, 3, "IBAN $iban BIC $bic");
$this->line_new();
}
if ($g_parameter->MY_PHONE != "")
{
$this->write_multi($colsize, 3, sprintf(_("Tel %s "),
$g_parameter->MY_PHONE
));
$this->line_new();
}
if ($email_company != "")
{
$this->write_multi($colsize, 3, sprintf(_("email %s "),
$email_company));
$this->line_new();
}
if ($site != "")
{
$this->write_multi($colsize, 3, sprintf(_("site %s"),
$site
));
$this->line_new();
}
$this->setFont("DejaVu", 'B', 14);
$this->write_multi(40, 10, "");
$this->write_multi(100, 10, _("Facture") . " " . $this->data['e_pj'], border: 1, align: 'C');
$this->write_multi(40, 10, "");
$this->line_new(10);
$this->ln(5);
}
//!
//@brief make the invoice
function export()
{
$this->SetAuthor('NOALYSS');
$this->AliasNbPages();
$this->AddPage();
$this->SetAutoPageBreak(true, $this->bMargin*1);
$this->setTitle($this->filename, true);
// $customer (Fiche) retrieve card of the customer
$customer = new \Fiche($this->cn);
$customer->get_by_qcode(trim($this->data['e_client']));
$this->setFont("DejaVu", '', 7);
$this->write_cell(50, 4, "");
$this->write_cell(60, 4, sprintf(_("Echéance %s"), $this->data['e_ech']));
$this->line_new();
$this->write_cell(50, 4, "");
$this->write_cell(100, 4, _("Client"), 'B', 'R');
$this->line_new();
$this->write_cell(50, 4, "");
$this->write_cell(110, 4, $customer->get_attribute(ATTR_DEF_NAME)
. " " . $customer->get_attribute(ATTR_DEF_FIRST_NAME, 0));
$this->line_new();
$this->write_cell(50, 4, "");
$this->write_cell(110, 4, $customer->get_attribute(ATTR_DEF_ADRESS, 0));
$this->line_new();
$this->write_cell(50, 4, "");
$this->write_cell(110, 4, $customer->get_attribute(ATTR_DEF_POSTCODE, 0)
. " " . $customer->get_attribute(ATTR_DEF_CITY, 0)
);
$this->line_new();
$this->write_cell(50, 4, "");
$this->write_cell(110, 4, $customer->get_attribute(ATTR_DEF_COUNTRY, 0));
$this->line_new();
$this->write_cell(50, 4, "");
$this->write_cell(110, 4, $customer->get_attribute(ATTR_DEF_NUMTVA, 0));
$this->line_new();
$a_tva_amount = [];
$a_tva_code = [];
$col = array(
"quick_code" => 30,
"label" => 80,
"quantity" => 25,
"price" => 25,
"vat_code" => 20
);
$this->SetFont("DejaVu", "B", 12);
$this->write_multi(50, 20, "");
$this->write_multi(50, 20, _("Détails"));
$this->line_new();
$this->SetFont("DejaVuCond", "", 7);
$currency = new \Acc_Currency($this->cn, $this->data['p_currency_code']);
$this->write_multi(60, 4, sprintf(_("Les montants sont en %s taux %s")
, $currency->get_code()
, $this->data['p_currency_rate']));
$this->line_new(4);
if ($this->data["bon_comm"] != "")
{
$this->write_multi(120, 4, sprintf(_("Bon de commande / référence %s")
, $this->data["bon_comm"]));
$this->line_new(4);
}
$this->line_new(4);
$this->SetFont("DejaVu", "", 7);
$this->write_multi($col['quick_code'], 4, _("Article"), 1);
$this->write_multi($col['label'], 4, _("Description"), 1);
$this->write_multi($col['quantity'], 4, _("Quantité"), 1);
$this->write_multi($col['price'], 4, _("Prix"), 1);
$this->write_multi($col['vat_code'], 4, _("TVA"), 1);
$this->line_new();
///@var $tot_amount (float) total amount without VAT
///@var $tot_vat (float) total VAT
///@var $line (int) line printed
$tot_amount = $tot_vat = $line =0;
for ($i = 0; $i < $this->data['nb_item']; $i++)
{
$item = new \Fiche($this->cn);
if (!isset($this->data['e_march' . $i]) || $this->data['e_march' . $i] == "")
{
continue;
}
$line++;
$item->get_by_qcode(trim($this->data['e_march' . $i]));
$fill = $this->is_fill($line);
$this->write_multi($col['quick_code'], 4, $item->get_attribute(ATTR_DEF_QUICKCODE),'','',$fill);
$this->write_multi($col['label'], 4, $this->data['e_march' . $i . '_label'],fill:$fill);
$this->write_multi($col['quantity'], 4, nbm($this->data['e_quant' . $i]), '', 'R',fill:$fill);
$this->write_multi($col['price'], 4, nbm($this->data['e_march' . $i . '_price']), '', 'R',fill:$fill);
$this->write_multi($col['vat_code'], 4, $this->data['e_march' . $i . '_tva_id'], '', 'C',fill:$fill);
$x = $this->data['e_march' . $i . '_tva_id'];
if (!isset($a_tva_amount[$x]))
{
$a_tva_amount[$x] = 0;
}
$a_tva_amount[$x] = bcadd($a_tva_amount[$x], $this->data["e_march" . $i . "_tva_amount"], 2);
$tot_amount = bcadd($tot_amount
, bcmul($this->data['e_march' . $i . '_price']
, $this->data['e_quant' . $i]
, 2
)
, 2);
$tot_vat = bcadd($tot_vat
, $this->data['e_march' . $i . '_tva_amount']
, 2);
$this->line_new(4);
if ($this->GetY()>250) {
$this->AddPage();
}
}
$this->line_new(10);
$this->SetFont("DejaVu", "B", 9);
$this->write_multi(30, 4, _("TVA"));
$this->line_new(5);
$this->SetFont("DejaVu", "", 7);
foreach ($a_tva_amount as $tva_id => $tva_amount)
{
$tva = \Acc_Tva::build($this->cn, $tva_id);
$this->write_multi(20, 4, "");
$this->write_multi(80, 4, $tva->tva_id
. " / " . $tva->tva_code
. " / " . $tva->tva_label
. " / " . $tva->tva_rate * 100
);
$this->write_multi(50, 4, $tva_amount);
$this->line_new();
}
$this->ln(20);
$this->SetFont("DejaVu", "B", 9);
$this->write_multi(30, 4, _("TOTAUX"));
$this->line_new();
$this->SetFont("DejaVu", "", 7);
$this->write_multi(60, 4, _("Total Hors TVA "));
$this->write_multi(60, 4, nbm($tot_amount), '', 'R');
$this->line_new();
$this->write_multi(60, 4, _("Total TVA "));
$this->write_multi(60, 4, nbm($tot_vat), '', 'R');
$this->line_new();
$this->write_multi(60, 4, _("Total "));
$this->write_multi(60, 4, nbm(bcadd($tot_amount, $tot_vat, 2),2), '', 'R');
$this->line_new();
$iban = $this->cn->get_value("select pe_value from parameter_extra where pe_code=$1",
['COMPANY_BANK_IBAN']);
if ($this->data['e_ech'] != "" && $iban != "")
{
$info = ($this->data["other_info"] == "") ? $this->data["e_pj"] : $this->data["other_info"];
$bic = $this->cn->get_value("select pe_value from parameter_extra where pe_code=$1",
['COMPANY_BANK_IBAN']);
$this->write_multi(150, 4,
sprintf(_("Paiement avant le %s sur le compte %s (BIC %s) avec comme message %s"),
$this->data['e_ech']
, $iban
, $bic
, $this->data["other_info"]
)
);
$this->line_new();
}
$this->Output($this->dirname . DIRECTORY_SEPARATOR . $this->filename, "F");
}
}

View file

@ -227,10 +227,10 @@ select sum(signed_amount) delta,sum(debit) debit,sum(credit) credit from saldo_d
$ledger->save($oe_data);
$oe_result=_("Détail opération");
$oe_result.=sprintf('<a class="detail" style="display:inline" href="javascript:modifyOperation(%d,%d)">%s</a><hr>',
$ledger->jr_id, dossier::id(), $ledger->internal);
$ledger->jr_id, dossier::id(), $ledger->jr_internal);
$cn->exec_sql("update operation_exercice set oe_transfer_date=to_timestamp($1,'DD.MM.YY HH24:MI') , jr_internal=$2 where oe_id=$3",
[date('d.m.Y H:i'),$ledger->internal,$this->operation_exercice_sql->oe_id]);
[date('d.m.Y H:i'),$ledger->jr_internal,$this->operation_exercice_sql->oe_id]);
$cn->commit();
return true;

View file

@ -191,13 +191,13 @@ class Tva_Rate_MTable extends Manage_Table_SQL
$text->selected=$value;
$text->transform(array(
null=>_('-')
,"S"=>_('Taux standard')
,'AE'=>_('Autoliquidate mais pas INTRACOMM.')
,'Z'=>_("TVA à 0%")
,'K'=>_('Autoliquidation INTRACOMM.')
,'G'=>_('TVA exempt pour export hors Europe')
,'O'=>_('TVA Hors périmètre application')
,'E'=>_('Exempté de TVA')
,"S"=>_('S Taux standard')
,'AE'=>_('AE Autoliquidate mais pas INTRACOMM.')
,'Z'=>_("Z TVA à 0%")
,'K'=>_('K Autoliquidation INTRACOMM.')
,'G'=>_('G TVA exempt pour export hors Europe')
,'O'=>_('O TVA Hors périmètre application')
,'E'=>_('E Exempté de TVA')
));
echo $text->input();
}elseif ($key == "tva_id") {

View file

@ -92,7 +92,7 @@ if (!isset($_POST['summary']) && !isset($_POST['save'])) {
echo _("Détail opération");
echo " ";
printf('<a class="detail" style="display:inline" href="javascript:modifyOperation(%d,%d)">%s</a><hr>',
$jr_id, dossier::id(), $ledger->internal);
$jr_id, dossier::id(), $ledger->jr_internal);
echo '</div>';
// show feedback

View file

@ -33,7 +33,8 @@ $http=new HttpInput();
$strac=$http->request('ac');
$ac="ac=".$strac;
$p_msg="";
$post_jrn=$http->post("p_jrn", "string","");
//@var $post_jrn (int) Ledger id JRN_DEF.JRN_DEF_ID
$post_jrn=$http->post("p_jrn", "number","");
//----------------------------------------------------------------------
// Encode a new invoice
// empty form for encoding
@ -65,6 +66,9 @@ if ( isset ($_POST['view_invoice'] ) )
$p_msg=$e->getMessage();
$correct=1;
}
//------------------------------------------------
// Confirm before saving
//------------------------------------------------
// if correct is not set it means it is correct
if ( ! isset($correct))
{
@ -79,6 +83,28 @@ if ( isset ($_POST['view_invoice'] ) )
echo '<form class="print" enctype="multipart/form-data" method="post">';
echo dossier::hidden();
echo $Ledger->confirm($_POST );
//----------------------------------------------------
// Check that INVOICE can be generated
// for e-invoice only
//----------------------------------------------------
if ($g_parameter->MY_INVOICE_FORMAT != 'BASIC')
{
$xmldocument= \Noalyss\XMLDocument\XMLInvoice::build_xmlinvoice($cn);
$array=[];
$array['supplier']=$xmldocument->fill_supplier();
$customer=Fiche::from_qcode($cn,trim($http->post("e_client")));
$array['customer']=$xmldocument->fill_customer($customer->id);
$array['operation']=$xmldocument->fill_operation_from_array($_POST);
$array['due_date']=$http->post("e_ech");
if ( $array['due_date'] == '')
{
$array['due_date']=$http->post("e_date");
}
$xmldocument->set_data($array);
$xmldocument->display_error();
}
echo HtmlInput::hidden('ac',$strac);
$Ledger->input_extra_info();
echo HtmlInput::submit("record", _("Enregistrement"), 'onClick="return verify_ca(\'\');"');
@ -121,10 +147,113 @@ if ( isset($_POST['record']) )
else
echo '<div class="content">';
$Ledger=new Acc_Ledger_Sale($cn,$_POST['p_jrn']);
$Ledger=new Acc_Ledger_Sale($cn,$post_jrn);
try {
$internal=$Ledger->insert($_POST);
// var $receipt (string) contains the name of the file name of
// the invoice (document created), if empty there
// is no invoice
$receipt='';
//-------------------------------------------------------
// Generate a XLM invoice
// if a document has been created create the XML file
//-------------------------------------------------------
///@var $flag_invoice (int) error for invoice generating.
/// 0 = nothing no invoice created
/// 1 = cannot create e-invoice
/// 2 = create e-invoice requested
$flag_invoice=0;
/* Save the attachment or generate doc */
if (isset($_FILES['pj']) && noalyss_strlentrim($_FILES['pj']['name']) != 0)
{
$acc_document=new Acc_Document($cn,$Ledger->jr_id);
$acc_document->save_receipt();
$receipt= HtmlInput::show_receipt_document($Ledger->jr_id
,h($_FILES['pj']['name']));
}
else
/* Generate an invoice and save it into the database */
if (isset($_POST['gen_invoice']))
{
//@var $invoice_template (int) get the invoice number DOCUMENT_MODELE.MD_ID
$invoice_template=$http->post("gen_doc","number");
// generate an invoice
$file = $Ledger->create_document($internal, $_POST);
$receipt= HtmlInput::show_receipt_document($Ledger->jr_id
,h($file));
$acc_document=new Acc_Document($cn,$Ledger->jr_id);
/**
* @todo si Client non belge ou pas de de tva alors pas de e-facture
*/
if ($g_parameter->MY_INVOICE_FORMAT != 'BASIC' && ! empty($acc_document->d_filename ))
{
$flag_invoice=2;
$xmldocument= \Noalyss\XMLDocument\XMLInvoice::build_xmlinvoice($cn);
$xmldocument->build_data($Ledger->jr_id);
$code_error = $xmldocument->verify() ;
// check that all the sub arrays are empty
if ( ! empty( array_filter($code_error,function($a){ if (!empty($a)) return true; })))
{
$xmldocument->display_error();
$flag_invoice=1;
}
}
//------------------------------------------------
// flag_invoice == 2 , generate an e-invoice
//------------------------------------------------
if ( $flag_invoice == 2 )
{
$pdf_filename=$acc_document->d_filename;
if ( $acc_document->d_mimetype != 'application/pdf')
{
$pdf_filename=$acc_document->transform2pdf();
// save PDF In db
$acc_document->update($pdf_filename);
}else{
$pdf_filename=$_ENV['TMP']."/".$pdf_filename;
$acc_document->export_file($pdf_filename);
}
// make the PDF
$xmldocument->set_pdf_filename($pdf_filename);
// make the XML + PDF
$xml=$xmldocument->create_invoice($Ledger->jr_id);
if (DEBUGNOALYSS > 1) {
$mt=date ('ymd-Hi').'+'.$Ledger->jr_id;
$uniq= $_ENV['TMP']. DIRECTORY_SEPARATOR."$mt-e-invoice.xml";
file_put_contents($uniq, $xml);
chmod ($uniq,774);
echo \Noalyss\Dbg::echo_file("file save $uniq");
}
// FOR BELGIUM : XML and PDF will be store separately
// save XML string into the DB
$oid=$cn->lo_write($xml);
echo \Noalyss\Dbg::echo_var(1, "oid is $oid");
if ($oid == false) {
throw new Exception ('CV177 : cannot import e-invoice');
}
if ( $g_parameter->MY_INVOICE_FORMAT == 'UBL21BEL')
{
$acc_document->update_document_xml($oid);
$receipt= HtmlInput::show_receipt_document($Ledger->jr_id,$acc_document->d_filename)
. $acc_document->link_download_xml();
}elseif ($g_parameter->MY_INVOICE_FORMAT=='FACTURXFR')
{
$acc_document->replace_receipt($oid);
$receipt= HtmlInput::show_receipt_document($Ledger->jr_id,$acc_document->d_filename);
}
}
}
}
catch (\Exception $e) {
if ( $e->getCode()==EXC_BALANCE)
@ -144,14 +273,17 @@ if ( isset($_POST['record']) )
}
/* Show button */
echo '<h1> Enregistrement </h1>';
echo '<h1>'._("Enregistré").'</h1>';
if ($flag_invoice == 1) {
echo_warning(_("Impossible de générer facture électronique") );
$xmldocument->display_error();
}
echo $Ledger->confirm($_POST,true);
/* Show link for Invoice */
if (isset ($Ledger->doc) )
if ($receipt != "")
{
echo '<h2 class="h-section">'._('Document').' </h2>';
echo $Ledger->doc;
echo $receipt;
}

View file

@ -224,8 +224,8 @@ define("ATTR_DEF_BQ_NO", 3);
define("ATTR_DEF_BQ_NAME", 4);
define("ATTR_DEF_PRIX_ACHAT", 7);
define("ATTR_DEF_PRIX_VENTE", 6);
define("ATTR_DEF_TVA", 2);
define("ATTR_DEF_NUMTVA", 13);
define("ATTR_DEF_TVA", 2); // usable VAT for goods and services
define("ATTR_DEF_NUMTVA", 13); // number of VAT
define("ATTR_DEF_ADRESS", 14);
define("ATTR_DEF_POSTCODE", 15);
define("ATTR_DEF_COUNTRY", 16);
@ -427,7 +427,13 @@ function noalyss_class_autoloader($class)
'noalyss\file_cache'=>"lib/file_cache.class.php",
"pdfland"=>"class/pdf_land.class.php",
"noalyss\widget\widget"=>"widget/widget.php",
"noalyss\otp"=>"lib/otp.class.php"
"noalyss\otp"=>"lib/otp.class.php",
'noalyss\xmldocument\xmlinvoice'=>'XMLDocument/XMLInvoice.php',
'noalyss\xmldocument\facturx'=>'XMLDocument/FacturX.php',
'noalyss\xmldocument\invoiceubl21'=>'XMLDocument/InvoiceUBL21.php',
'noalyss\xmldocument\error_message'=>'XMLDocument/Error_Message.php',
"noalyss\invoice_pdf"=>"class/invoice_pdf.class.php",
'noalyss\xmldocument\xmlinvoice_reader'=>'XMLDocument/xmlinvoice_reader.class.php'
);
if (isset ($aClass[$class])) {
require_once NOALYSS_INCLUDE . "/" . $aClass[$class];

View file

@ -32,7 +32,7 @@ try
}
catch (Exception $exc)
{
error_log($exc->getTraceAsString());
record_log($exc);
return;
}
$cn=Dossier::connect();

View file

@ -0,0 +1,122 @@
<?php
/*
* This file is part of NOALYSS.
*
* NOALYSS 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 2 of the License, or
* (at your option) any later version.
*
* NOALYSS 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 NOALYSS; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
// Copyright Author Dany De Bontridder danydb@aevalys.eu 22/10/23
/**
* @file
* @brief export the XML invoice from JRN.JR_DOCUMENT_XML
*/
if ( ! defined ('ALLOWED')) die (_('Non autorisé'));
$http=new HttpInput();
try
{
$jr_id=$http->get('jr_id',"number");
}
catch (Exception $exc)
{
record_log($exc);
return;
}
$cn=Dossier::connect();
$r=$cn->exec_sql("select jr_def_id from jrn where jr_id=$1",array($jr_id));
if ( Database::num_row($r) == 0 )
{
echo_error("Invalid operation id jr_id=$jr_id");
exit;
}
$a=Database::fetch_array($r,0);
$jrn=$a['jr_def_id'];
global $g_user;
if ($g_user->check_jrn($jrn) == 'X' )
{
/* Cannot Access */
NoAccess();
exit -1;
}
$ret=$cn->exec_sql("select jr_pj_name,jr_pj_number ,jr_document_xml from jrn where jr_id=$1",
array($jr_id));
if ( Database::num_row ($ret) == 0 )
return;
$row=Database::fetch_array($ret,0);
if ( $row['jr_document_xml']==null )
{
ini_set('zlib.output_compression','Off');
header("Pragma: public");
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: must-revalidate");
header('Content-type: '.'text/plain');
header('Content-Disposition: attachment;filename=vide.txt',FALSE);
header("Accept-Ranges: bytes");
echo "******************";
echo _("Fichier effacé");
echo "******************";
exit();
}
$tmp=tempnam($_ENV['TMP'],'document_');
$new_name=$row['jr_pj_name'];
$receipt_number=clean_filename($row['jr_pj_number']);
$receipt_number=noalyss_str_replace('.','-',$receipt_number);
if ( ! empty($receipt_number) && strpos($new_name,$receipt_number) === false ) {
$new_name=$receipt_number.'-'.$new_name;
}
// replace extension by xml (normally a PDF)
//@var $pos_ext (int) where is the last dot
$pos_ext=strrpos($new_name,'.');
if ( $pos_ext == 0)
{
// there is no extension
$new_name.='.xml';
}else {
$new_name=substr_replace($new_name,'.xml',$pos_ext);
}
$cn->start();
$cn->lo_export($row['jr_document_xml'],$tmp);
$cn->commit();
ini_set('zlib.output_compression','Off');
header("Pragma: public");
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: must-revalidate");
header('Content-type: application/xml');
header('Content-Disposition: attachment;filename="'.$new_name.'"',FALSE);
header("Accept-Ranges: bytes");
$file=fopen($tmp,'r');
while ( !feof ($file) )
echo fread($file,8192);
fclose($file);
unlink ($tmp);

View file

@ -1877,3 +1877,20 @@ function guidv4($data = null) {
// Output the 36 character UUID.
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}
/**
* @brief retrieve the index for the key percent, returns -1 if nothing found
* @param $array (array) SubTotal
* @param $key (string) name of the key
* @param $value (string) value to look for
* @return int
*/
function find_idx($array,$key,$value) {
if ( count($array) == 0 ) { return -1; }
$nb_array=count($array);
for($i=0;$i <$nb_array;$i++) {
if ($array[$i][$key] == $value) {
return $i;
}
}
return -1;
}

View file

@ -718,27 +718,39 @@ class DatabaseCore
}
/***
* \brief Save a document into the database , it just puts the file in the database
* \brief Save one or several documents into the database , it just puts the file in the database
* and returns the corresponding OID , the mimetype , size ... of the document
* must be set in the calling function.
*
* \param name of the variable in $_FILES
* \param $only_oid (bool) (default :true) false : return filename and oid in an array , true only OID,
* \return $oid of the lob file if success
* false if a error occurs or if there is no file to upload
* array(oid, filename) if $only_oid is true
*
*/
function upload($p_name)
function upload($p_name,$only_oid = false)
{
//var $a : 0 we're in a transaction, 1 we are not in a transaction
$a=0;
if ( $this->status() !== PGSQL_TRANSACTION_INTRANS ) {
$a=1;
$this->start();
}
/* there is no file to upload */
if ($_FILES[$p_name]["error"] == UPLOAD_ERR_NO_FILE) {
\record_log("DC759: error upload file".var_export($_FILES, true));
if ( $a==1) { $this->rollback(); }
return false;
}
$new_name = tempnam($_ENV['TMP'], $p_name);
if ($_FILES[$p_name]["error"] > 0) {
print_r($_FILES);
echo_error(__FILE__ . ":" . __LINE__ . "Error: " . $_FILES[$p_name]["error"]);
\record_log("DC740: error upload file".var_export($_FILES, true));
if ( $a==1) { $this->rollback(); }
return false;
}
if (strlen($_FILES[$p_name]['tmp_name']) != 0) {
@ -746,19 +758,101 @@ class DatabaseCore
// echo "Image saved";
$oid = pg_lo_import($this->db, $new_name);
if ($oid == false) {
echo_error(__FILE__, __LINE__, "cannot upload document");
\record_log("DC747: error upload file".var_export($_FILES, true). "SQL MESSAGE". pg_last_error($this->db));
$this->rollback();
return false;
}
return $oid;
if ( $a == 1 ) { $this->commit(); }
if ($only_oid ){
return $oid;
}else {
return ["oid"=>$oid,'filename'=>$new_name];
}
} else {
echo "<H1>Error</H1>";
\record_log("DC754: move_uploaded fails".var_export($_FILES, true));
$this->rollback();
return false;
}
}
\record_log("DC576: Files error names empty".var_export($_FILES, true));
if ( $a == 1) { $this->commit(); }
return false;
}
/**
* @brief large_object writee: create a Large object if oid is not given
* with data content in a binaray
* @param $binary_data (raw data) binary
* @returns $oid of the LO, false if it fails
*/
function lo_write($binary_data)
{
//var $a : 0 where in a transaction, 1 we are not in a transaction
$a=0;
if ( $this->status() !== PGSQL_TRANSACTION_INTRANS ) {
$a=1;
$this->start();
}
$oid= pg_lo_create($this->db);
if ( ($handle=pg_lo_open($this->db,$oid,"w")) == false ) { return false ;}
pg_lo_write($handle, $binary_data);
pg_lo_close($handle);
if ( $a==1) { $this->commit(); }
return $oid;
}
/**
* @brief read a Large object with data content in a binary
* @param $oid (int8) oid of the large object
* @returns $binary_data (raw data) binary
*/
function lo_read($oid)
{
//var $a : 0 where in a transaction, 1 we are not in a transaction
$a=0;
if ( $this->status() !== PGSQL_TRANSACTION_INTRANS ) {
$a=1;
$this->start();
}
$handle=pg_lo_open($this->db,$oid,"r");
if ( $handle == false ) { return false ;}
// set position end of the LO
pg_lo_seek($handle, 0, PGSQL_SEEK_END);
// get the size
$size= pg_lo_tell($handle);
// set position to start
pg_lo_seek($handle, 0, PGSQL_SEEK_SET);
// read the comùplete LOB
$binary_data = pg_lo_read($handle,$size );
pg_lo_close($handle);
if ( $a==1) { $this->commit(); }
return $binary_data;
}
/**
* @brief replace a Large object with data content in a binary
* @param $oid (int8) oid of the large object
* @param $binary_data (raw data) binary
* @returns $oid of the LO, false if it fails
*/
function lo_replace($binary_data, $oid) {
$a = 0;
if ($this->status() !== PGSQL_TRANSACTION_INTRANS) {
$a = 1;
$this->start();
}
$handle = pg_lo_open($this->db, $oid, "w");
if ( $handle == false ) { return false ;}
pg_lo_truncate($handle, 0);
pg_lo_write($handle, $binary_data);
pg_lo_close($handle);
if ($a == 1) {
$this->commit();
}
return $oid;
}
/**
* \brief wrapper for the function pg_num_rows
@ -823,7 +917,7 @@ class DatabaseCore
/**
* \brief wrapper for the function pg_lo_unlink
* \param $p_oid is the of oid
* \return return the result of the operation
* \return return the result of the operation : false == fails
*/
function lo_unlink($p_oid)

View file

@ -9,7 +9,7 @@
* @brief show the common parts of operation details
*
* Variables : $div = popup or box (det[0-9]
*
*@var $obj = Acc_Operation
*/
bcscale(2);
\Noalyss\Dbg::echo_file(__FILE__);
@ -44,13 +44,18 @@ $a_tab['linked_operation_div']=array('id'=>'linked_operation_div'.$div,'label'=>
$a_tab['document_operation_div']=array('id'=>'document_operation_div'.$div,'label'=>_('Document').'('.$nb_document.')','display'=>'block');
$a_tab['linked_action_div']=array('id'=>'linked_action_div'.$div,'label'=>_('Actions Gestion').'('.count($a_followup).')','display'=>'none');
$a_tab['analytic_div']=array('id'=>'analytic_div'.$div,'label'=>_('Comptabilité Analytique'),'display'=>'none');
//var $g_parameter \Noalyss_Parameter_Folder
global $g_parameter;
// show tabs
if ( $div != "popup") :
$a_tab['document_operation_div']['display']='block';
$tabs=array_column($a_tab,"id");
?>
<input type="hidden" id="<?=$div?>tab" value="<?=join(",",$tabs)?>">
<ul class="tabs">
<?php foreach ($a_tab as $idx=>$a_value): ?>
<?php
@ -58,7 +63,7 @@ if ( $div != "popup") :
?>
<li class="<?php echo $class?>">
<?php $div_tab_id=$a_value['id'];?>
<a href="javascript:void(0)" onclick="unselect_other_tab(this.parentNode.parentNode);var tab=Array('writing_div<?php echo $div?>','info_operation_div<?php echo $div?>','linked_operation_div<?php echo $div?>','document_operation_div<?php echo $div?>','linked_action_div<?php echo $div?>','analytic_div<?php echo $div?>');this.parentNode.className='tabs_selected' ;show_tabs(tab,'<?php echo $div_tab_id; ?>');"><?php echo _($a_value['label'])?></a>
<a href="javascript:void(0)" onclick="unselect_other_tab(this.parentNode.parentNode);this.parentNode.className='tabs_selected' ;show_tabs($F('<?=$div?>tab').split(','),'<?php echo $div_tab_id; ?>');"><?php echo _($a_value['label'])?></a>
</li>
<?php endforeach; ?>
</ul>
@ -317,9 +322,7 @@ require_once NOALYSS_TEMPLATE.'/ledger_detail_file.php';
</span>
<?php endif;?>
</div>
<hr>
<?php
<?php
echo '<p style="text-align:center">';
if ( $div != 'popup' ) {
@ -408,4 +411,4 @@ echo '</form>';
}else {
echo '</p>';
}
?>

View file

@ -0,0 +1,124 @@
<?php
/*
* This file is part of NOALYSS.
*
* NOALYSS 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 2 of the License, or
* (at your option) any later version.
*
* NOALYSS 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 NOALYSS; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
// Copyright Author Dany De Bontridder danydb@aevalys.eu 22/10/23
/**
* @file
* @brief display errors for generating e-invoices, called from
* invoiceUBL21-display-error.php
*/
///@var $a_vat_error (array) contains error for VAT
$a_vat_error=$this->check_VAT();
///@var $total_error (int) total of errors found in e-invoice
$total_error=count ($a_error['general'])
+ count($a_error['operation'])
+ count($a_error['customer'])
+ count($a_error['company'])
+count($a_vat_error);
if ( $total_error == 0 ) :
return;
endif;
$error_message=new \Noalyss\XMLDocument\Error_Message($a_error);
?>
<button onclick="$('invoice_error_popover').show();return false" class="button bt-error "><i class="icon-attention"></i> <?=_("Erreurs Facture électronique {$total_error}")?></button>
<div style="display:none" id="invoice_error_popover">
<?php
echo HtmlInput::title_box(_("Erreurs"), "invoice_error_popover","hide");
//----------------------------------------------------------------------------
// company
//----------------------------------------------------------------------------
$nb_error=count($a_error['company']);
for ($i=0;$i<$nb_error;$i++):
?>
<?php if ($i == 0 ):?>
<h3><?=_("Société")?></h3>
<p class="text-muted">
<?=_("A corriger dans COMPANY")?>
</p>
<ol>
<?php endif;?>
<li class="notice-item">
<?=$error_message->get_message_error(code:$a_error['company'][$i],type:'company')?>
</li>
<?php
endfor;
if ( $nb_error!=0) print '</ol>';
?>
<?php
//----------------------------------------------------------------------------
// Customer
//----------------------------------------------------------------------------
$nb_error=count($a_error['customer']);
for ($i=0;$i<$nb_error;$i++):
?>
<?php if ($i == 0) :?>
<h3><?=_("Client")?></h3>
<p class="text-muted">
<?=_("A corriger dans la fiche")?>
</p>
<p>
<?php
$card=new \Fiche ($this->cn,$this->data['customer']['card_id']);
echo \HtmlInput::card_detail($card->get_attribute(ATTR_DEF_QUICKCODE)
,$card->get_attribute(ATTR_DEF_NAME));
?>
</p>
<ol>
<?php endif;?>
<li class="notice-item">
<?=$error_message->get_message_error(code:$a_error['customer'][$i],type:'customer')?>
</li>
<?php
endfor;
if ( $nb_error!=0) print '</ol>';
?>
<?php
//----------------------------------------------------------------------------
// Item VAT
//----------------------------------------------------------------------------
$a_vat_error=$this->check_VAT();
$nb_error=count($a_vat_error);
for ($i=0;$i<$nb_error;$i++):
?>
<?php if ($i == 0) :?>
<h3><?=_("TVA")?></h3>
<p class="text-muted">
<?=_("A corriger dans la configuration TVA (C0TVA)")?>
</p>
<ol>
<?php endif;?>
<li class="notice-item">
<?=$a_vat_error[$i]?>
</li>
<?php
endfor;
if ( $nb_error!=0) print '</ol>';
?>
<button onclick="$('invoice_error_popover').hide();return false" class="button"><?=_("Fermer")?></button>
</div>