Browse Source

NEW Support the Swiss QR-Code

Laurent Destailleur 3 years ago
parent
commit
233d712e39

+ 1 - 0
ChangeLog

@@ -13,6 +13,7 @@ NEW: PHP 8.1 compatibility
 NEW: Support for recurring purchase invoices.
 NEW: #20292 Include German public holidays
 NEW: Can show ZATCA QRCode on PDFs
+NEW: Can show Swiss QR Code on PDFs
 NEW: #17123 added ExtraFields for Stock Mouvement
 NEW: #20609 : new massaction to assign a sale representatives on a selection of thirdparties
 NEW: #20653 edit discount pourcentage for all lines in one shot

+ 6 - 0
dev/resources/iso-normes/barcodes/QR code for invoices.txt → dev/resources/iso-normes/qr-bar-codes/QR code for invoices.txt

@@ -20,3 +20,9 @@ https://www.pwc.com/m1/en/services/tax/me-tax-legal-news/2021/saudi-arabia-guide
 https://www.tecklenborgh.com/post/ksa-zatca-publishes-guide-on-how-to-develop-a-fatoora-compliant-qr-code
 
 Method to encode/decode ZATCA string is available in test/phpunit/BarcodeTest.php 
+
+
+* FOR QR-Bill in switzerland
+----------------------------
+Syntax of QR Code https://www.swiss-qr-invoice.org/fr/
+Syntax of complentary field named "structured information of invoice S1": https://www.swiss-qr-invoice.org/downloads/qr-bill-s1-syntax-fr.pdf

+ 0 - 0
dev/resources/iso-normes/barcodes/barcode_EAN13.txt → dev/resources/iso-normes/qr-bar-codes/barcode_EAN13.txt


+ 16 - 0
htdocs/admin/pdf_other.php

@@ -62,6 +62,11 @@ if ($action == 'update') {
 	}
 	if (GETPOSTISSET('INVOICE_ADD_ZATCA_QR_CODE')) {
 		dolibarr_set_const($db, "INVOICE_ADD_ZATCA_QR_CODE", GETPOST("INVOICE_ADD_ZATCA_QR_CODE", 'int'), 'chaine', 0, '', $conf->entity);
+		dolibarr_del_const($db, "INVOICE_ADD_SWISS_QR_CODE", $conf->entity);
+	}
+	if (GETPOSTISSET('INVOICE_ADD_SWISS_QR_CODE')) {
+		dolibarr_set_const($db, "INVOICE_ADD_SWISS_QR_CODE", GETPOST("INVOICE_ADD_SWISS_QR_CODE", 'int'), 'chaine', 0, '', $conf->entity);
+		dolibarr_del_const($db, "INVOICE_ADD_ZATCA_QR_CODE", $conf->entity);
 	}
 
 	setEventMessages($langs->trans("SetupSaved"), null, 'mesgs');
@@ -138,6 +143,17 @@ if (isModEnabled('facture')) {
 	}
 	print '</td></tr>';
 
+	print '<tr class="oddeven"><td>';
+	print $form->textwithpicto($langs->trans("INVOICE_ADD_SWISS_QR_CODE"), '');
+	print '</td><td>';
+	if ($conf->use_javascript_ajax) {
+		print ajax_constantonoff('INVOICE_ADD_SWISS_QR_CODE');
+	} else {
+		$arrval = array('0' => $langs->trans("No"), '1' => $langs->trans("Yes"));
+		print $form->selectarray("INVOICE_ADD_SWISS_QR_CODE", $arrval, $conf->global->INVOICE_ADD_SWISS_QR_CODE);
+	}
+	print '</td></tr>';
+
 	/*
 	  print '<tr class="oddeven"><td>'.$langs->trans("MAIN_PDF_PROPAL_USE_ELECTRONIC_SIGNING").'</td><td>';
 	  if ($conf->use_javascript_ajax) {

+ 88 - 0
htdocs/core/class/commoninvoice.class.php

@@ -900,6 +900,94 @@ abstract class CommonInvoice extends CommonObject
 
 		return $s;
 	}
+
+
+	/**
+	 * Build string for QR-Bill (Switzerland)
+	 *
+	 * @return	string			String for Switzerland QR Code if QR-Bill
+	 */
+	public function buildSwitzerlandQRString()
+	{
+		global $conf, $mysoc;
+
+		$tmplang = new Translate('', $conf);
+		$tmplang->setDefaultLang('en_US');
+		$tmplang->load("main");
+
+		$pricewithtaxstring = price2num($this->total_ttc, 2, 1);
+		$pricetaxstring = price2num($this->total_tva, 2, 1);
+
+		$complementaryinfo = '';
+		/*
+		 Example: //S1/10/10201409/11/190512/20/1400.000-53/30/106017086/31/180508/32/7.7/40/2:10;0:30
+		 /10/ Numéro de facture – 10201409
+		 /11/ Date de facture – 12.05.2019
+		 /20/ Référence client – 1400.000-53
+		 /30/ Numéro IDE pour la TVA – CHE-106.017.086 TVA
+		 /31/ Date de la prestation pour la comptabilisation de la TVA – 08.05.2018
+		 /32/ Taux de TVA sur le montant total de la facture – 7.7%
+		 /40/ Conditions – 2% d’escompte à 10 jours, paiement net à 30 jours
+		 */
+		$datestring = dol_print_date($this->date, '%y%m%d');
+		//$pricewithtaxstring = price($this->total_ttc, 0, $tmplang, 0, -1, 2);
+		//$pricetaxstring = price($this->total_tva, 0, $tmplang, 0, -1, 2);
+		$complementaryinfo = '//S1/10/'.str_replace('/', '', $this->ref).'/11/'.$datestring;
+		if ($this->ref_client) {
+			$complementaryinfo .= '/20/'.$this->ref_client;
+		}
+		if ($this->thirdparty->vat_number) {
+			$complementaryinfo .= '/30/'.$this->thirdparty->vat_number;
+		}
+
+		// Header
+		$s .= "SPC\n";
+		$s .= "0200\n";
+		$s .= "1\n";
+		if ($this->fk_account > 0) {
+			// Bank BAN if country is LI or CH
+			// TODO Add
+		} else {
+			$s .= "\n";
+		}
+		// Seller
+		$s .= "S";
+		$s .= dol_trunc($mysoc->name, 70, 'right', 'UTF-8', 1)."\n";
+		$s .= dol_trunc($mysoc->address, 70, 'right', 'UTF-8', 1)."\n";
+		$s .= dol_trunc($mysoc->zip, 16, 'right', 'UTF-8', 1)."\n";
+		$s .= dol_trunc($mysoc->town, 35, 'right', 'UTF-8', 1)."\n";
+		$s .= dol_trunc($mysoc->country_code, 2, 'right', 'UTF-8', 1)."\n";
+		// Final seller
+		$s .= "\n";
+		$s .= "\n";
+		$s .= "\n";
+		$s .= "\n";
+		$s .= "\n";
+		$s .= "\n";
+		$s .= "\n";
+		// Amount of payment (to do?)
+		$s .= price($pricewithtaxstring, 0, 'none', 0, 0, 2)."\n";
+		$s .= $this->currency_code."\n";
+		// Buyer
+		$s .= "S";
+		$s .= dol_trunc($this->thirdparty->name, 70, 'right', 'UTF-8', 1)."\n";
+		$s .= dol_trunc($this->thirdparty->address, 70, 'right', 'UTF-8', 1)."\n";
+		$s .= dol_trunc($this->thirdparty->zip, 16, 'right', 'UTF-8', 1)."\n";
+		$s .= dol_trunc($this->thirdparty->town, 35, 'right', 'UTF-8', 1)."\n";
+		$s .= dol_trunc($this->thirdparty->country_code, 2, 'right', 'UTF-8', 1)."\n";
+		// ID of payment
+		$s .= "NON\n";			// NON or QRR
+		$s .= "\n";				// QR Code if previous field is QRR
+		if ($complementaryinfo) {
+			$s .= $complementaryinfo."\n";
+		} else {
+			$s .= "\n";
+		}
+		$s .= "EPD\n";
+		$s .= "\n";
+		//var_dump($s);exit;
+		return $s;
+	}
 }
 
 

+ 1 - 1
htdocs/core/class/commonobject.class.php

@@ -278,7 +278,7 @@ abstract class CommonObject
 	public $country_id;
 
 	/**
-	 * @var string
+	 * @var string		The ISO country code on 2 chars.
 	 * @see getFullAddress(), isInEEC(), country
 	 */
 	public $country_code;

+ 25 - 19
htdocs/core/lib/functions.lib.php

@@ -5467,7 +5467,7 @@ function vatrate($rate, $addpercent = false, $info_bits = 0, $usestarfornpr = 0)
  *
  *		@param	float				$amount			Amount to format
  *		@param	integer				$form			Type of format, HTML or not (not by default)
- *		@param	Translate|string	$outlangs		Object langs for output
+ *		@param	Translate|string	$outlangs		Object langs for output. '' use default lang. 'none' use international separators.
  *		@param	int					$trunc			1=Truncate if there is more decimals than MAIN_MAX_DECIMALS_SHOWN (default), 0=Does not truncate. Deprecated because amount are rounded (to unit or total amount accurancy) before beeing inserted into database or after a computation, so this parameter should be useless.
  *		@param	int					$rounding		Minimum number of decimal to show. If 0, no change, if -1, we use min($conf->global->MAIN_MAX_DECIMALS_UNIT,$conf->global->MAIN_MAX_DECIMALS_TOT)
  *		@param	int					$forcerounding	Force the number of decimal to forcerounding decimal (-1=do not force)
@@ -5490,25 +5490,31 @@ function price($amount, $form = 0, $outlangs = '', $trunc = 1, $rounding = -1, $
 	}
 	$nbdecimal = $rounding;
 
-	// Output separators by default (french)
-	$dec = ',';
-	$thousand = ' ';
-
-	// If $outlangs not forced, we use use language
-	if (!is_object($outlangs)) {
-		$outlangs = $langs;
-	}
-
-	if ($outlangs->transnoentitiesnoconv("SeparatorDecimal") != "SeparatorDecimal") {
-		$dec = $outlangs->transnoentitiesnoconv("SeparatorDecimal");
-	}
-	if ($outlangs->transnoentitiesnoconv("SeparatorThousand") != "SeparatorThousand") {
-		$thousand = $outlangs->transnoentitiesnoconv("SeparatorThousand");
-	}
-	if ($thousand == 'None') {
+	if ($outlangs === 'none') {
+		// Use international separators
+		$dec = '.';
 		$thousand = '';
-	} elseif ($thousand == 'Space') {
+	} else {
+		// Output separators by default (french)
+		$dec = ',';
 		$thousand = ' ';
+
+		// If $outlangs not forced, we use use language
+		if (!is_object($outlangs)) {
+			$outlangs = $langs;
+		}
+
+		if ($outlangs->transnoentitiesnoconv("SeparatorDecimal") != "SeparatorDecimal") {
+			$dec = $outlangs->transnoentitiesnoconv("SeparatorDecimal");
+		}
+		if ($outlangs->transnoentitiesnoconv("SeparatorThousand") != "SeparatorThousand") {
+			$thousand = $outlangs->transnoentitiesnoconv("SeparatorThousand");
+		}
+		if ($thousand == 'None') {
+			$thousand = '';
+		} elseif ($thousand == 'Space') {
+			$thousand = ' ';
+		}
 	}
 	//print "outlangs=".$outlangs->defaultlang." amount=".$amount." html=".$form." trunc=".$trunc." nbdecimal=".$nbdecimal." dec='".$dec."' thousand='".$thousand."'<br>";
 
@@ -5547,7 +5553,7 @@ function price($amount, $form = 0, $outlangs = '', $trunc = 1, $rounding = -1, $
 	}
 	// Add symbol of currency if requested
 	$cursymbolbefore = $cursymbolafter = '';
-	if ($currency_code) {
+	if ($currency_code && is_object($outlangs)) {
 		if ($currency_code == 'auto') {
 			$currency_code = $conf->currency;
 		}

+ 5 - 0
htdocs/core/modules/facture/doc/pdf_crabe.modules.php

@@ -452,8 +452,13 @@ class pdf_crabe extends ModelePDFFactures
 
 				// You can add more thing under header here, if you increase $extra_under_address_shift too.
 				$extra_under_address_shift = 0;
+				$qrcodestring = '';
 				if (! empty($conf->global->INVOICE_ADD_ZATCA_QR_CODE)) {
 					$qrcodestring = $object->buildZATCAQRString();
+				} elseif (! empty($conf->global->INVOICE_ADD_SWISS_QR_CODE)) {
+					$qrcodestring = $object->buildSwitzerlandQRString();
+				}
+				if ($qrcodestring) {
 					$qrcodecolor = array('25', '25', '25');
 					// set style for QR-code
 					$styleQr = array(

+ 5 - 0
htdocs/core/modules/facture/doc/pdf_sponge.modules.php

@@ -435,8 +435,13 @@ class pdf_sponge extends ModelePDFFactures
 
 				// You can add more thing under header here, if you increase $extra_under_address_shift too.
 				$extra_under_address_shift = 0;
+				$qrcodestring = '';
 				if (! empty($conf->global->INVOICE_ADD_ZATCA_QR_CODE)) {
 					$qrcodestring = $object->buildZATCAQRString();
+				} elseif (! empty($conf->global->INVOICE_ADD_SWISS_QR_CODE)) {
+					$qrcodestring = $object->buildSwitzerlandQRString();
+				}
+				if ($qrcodestring) {
 					$qrcodecolor = array('25', '25', '25');
 					// set style for QR-code
 					$styleQr = array(

+ 1 - 0
htdocs/langs/en_US/admin.lang

@@ -2266,5 +2266,6 @@ IconOnlyTextOnHover=Icon only - Text of icon appears under icon on mouse hover t
 IconOnly=Icon only - Text on tooltip only
 INVOICE_ADD_ZATCA_QR_CODE=Show the ZATCA QR code on invoices
 INVOICE_ADD_ZATCA_QR_CODEMore=Some Arabic countries need this QR Code on their invoices
+INVOICE_ADD_SWISS_QR_CODE=Show the swiss QR-Bill code on invoices
 UrlSocialNetworksDesc=Url link of social network. Use {socialid} for the variable part that contains the social network ID.
 IfThisCategoryIsChildOfAnother=If this category is a child of another one