Swiss QR-Bill: In Druckablauf OpenDocument/OASIS integrieren
authorCem Aydin <cem.aydin@gmx.ch>
Thu, 23 Dec 2021 21:51:36 +0000 (22:51 +0100)
committerCem Aydin <cem.aydin@gmx.ch>
Thu, 23 Dec 2021 21:51:36 +0000 (22:51 +0100)
- Feature in Mandantenkonfiguration einschaltbar
- Aufruf zum Erzeugen von QR-Code PNG (Steven Schubiger)
- Vorlage hinzugefügt (rev-odt/invoice_qr.odt)
- PNG Bild CH-Kreuz hinzugefügt
- Übersetzungen hinzugefügt, locales Script ausgeführt de/en
- changelog Eintrag

SL/Template/OpenDocument.pm
doc/changelog
image/CH-Kreuz_7mm.png [new file with mode: 0644]
locale/de/all
locale/en/all
templates/print/rev-odt/invoice_qr.odt [new file with mode: 0644]
templates/webpages/client_config/_features.html

index e570321..7a9f952 100644 (file)
@@ -6,15 +6,21 @@ use Archive::Zip;
 use Encode;
 use HTML::Entities;
 use POSIX 'setsid';
+use XML::LibXML;
 
 use SL::Iconv;
 use SL::Template::OpenDocument::Styles;
 
+use SL::DB::BankAccount;
+use SL::Helper::QrBill;
+use SL::Helper::ISO3166;
+
 use Cwd;
 # use File::Copy;
 # use File::Spec;
 # use File::Temp qw(:mktemp);
 use IO::File;
+use List::Util qw(first);
 
 use strict;
 
@@ -346,11 +352,20 @@ sub parse_block {
 sub parse {
   $main::lxdebug->enter_sub();
   my $self = $_[0];
+
   local *OUT = $_[1];
   my $form = $self->{"form"};
 
   close(OUT);
 
+  my $qr_image_path;
+  if ($::instance_conf->get_create_qrbill_invoices) {
+    # the biller account information, biller address and the reference number,
+    # are needed in the template aswell as in the qr-code generation, therefore
+    # assemble these and add to $::form
+    $qr_image_path = $self->generate_qr_code;
+  }
+
   my $file_name;
   if ($form->{"IN"} =~ m|^/|) {
     $file_name = $form->{"IN"};
@@ -420,6 +435,28 @@ sub parse {
     $zip->contents("styles.xml", Encode::encode('utf-8-strict', $new_styles));
   }
 
+  if ($::instance_conf->get_create_qrbill_invoices) {
+    # get placeholder path from odt XML
+    my $qr_placeholder_path;
+    my $dom = XML::LibXML->load_xml(string => $contents);
+    my @nodelist = $dom->getElementsByTagName("draw:frame");
+    for my $node (@nodelist) {
+      my $attr = $node->getAttribute('draw:name');
+      if ($attr eq 'QRCodePlaceholder') {
+        my @children = $node->getChildrenByTagName('draw:image');
+        $qr_placeholder_path = $children[0]->getAttribute('xlink:href');
+      }
+    }
+    if (!defined($qr_placeholder_path)) {
+      $::form->error($::locale->text('QR-Code placeholder image: QRCodePlaceholder not found in template.'));
+    }
+    # replace QR-Code Placeholder Image in zip file (odt) with generated one
+    $zip->updateMember(
+     $qr_placeholder_path,
+     $qr_image_path
+    );
+  }
+
   $zip->writeToFileNamed($form->{"tmpfile"}, 1);
 
   my $res = 1;
@@ -431,6 +468,260 @@ sub parse {
   return $res;
 }
 
+sub get_qrbill_account {
+  $main::lxdebug->enter_sub();
+  my ($self) = @_;
+
+  my $qr_account;
+
+  my $bank_accounts     = SL::DB::Manager::BankAccount->get_all;
+  $qr_account = scalar(@{ $bank_accounts }) == 1 ?
+    $bank_accounts->[0] :
+    first { $_->use_for_qrbill } @{ $bank_accounts };
+
+  if (!$qr_account) {
+    $::form->error($::locale->text('No bank account flagged for QRBill usage was found.'));
+  }
+
+  $main::lxdebug->leave_sub();
+  return $qr_account;
+}
+
+sub remove_letters_prefix {
+  my $s = $_[0];
+  $s =~ s/^[a-zA-Z]+//;
+  return $s;
+}
+
+sub check_digits_and_max_length {
+  my $s = $_[0];
+  my $length = $_[1];
+
+  return 0 if (!($s =~ /^\d*$/) || length($s) > $length);
+  return 1;
+}
+
+sub calculate_check_digit {
+  # calculate ESR check digit using algorithm: "modulo 10, recursive"
+  my $ref_number_str = $_[0];
+
+  my @m = (0, 9, 4, 6, 8, 2, 7, 1, 3, 5);
+  my $carry = 0;
+
+  my @ref_number_split = map int($_), split(//, $ref_number_str);
+
+  for my $v (@ref_number_split) {
+    $carry = @m[($carry + $v) % 10];
+  }
+
+  return (10 - $carry) % 10;
+}
+
+sub assemble_ref_number {
+  $main::lxdebug->enter_sub();
+
+  my $bank_id = $_[0];
+  my $customer_number = $_[1];
+  my $order_number = $_[2] // "0";
+  my $invoice_number = $_[3] // "0";
+
+  # check values (analog to checks in makro)
+  # - bank_id
+  #     input: 6 digits, only numbers
+  #     output: 6 digits, only numbers
+  if (!($bank_id =~ /^\d*$/) || length($bank_id) != 6) {
+    $::form->error($::locale->text('Bank account id number invalid. Must be 6 digits.'));
+  }
+
+  # - customer_number
+  #     input: prefix (letters) + up to 6 digits (numbers)
+  #     output: prefix removed, 6 digits, filled with leading zeros
+  $customer_number = remove_letters_prefix($customer_number);
+  if (!check_digits_and_max_length($customer_number, 6)) {
+    $::form->error($::locale->text('Customer number invalid. Must be less then or equal to 6 digits after prefix.'));
+  }
+  # fill with zeros
+  $customer_number = sprintf "%06d", $customer_number;
+
+  # - order_number
+  #     input: prefix (letters) + up to 7 digits, may be zero
+  #     output: prefix removed, 7 digits, filled with leading zeros
+  $order_number = remove_letters_prefix($order_number);
+  if (!check_digits_and_max_length($order_number, 7)) {
+    $::form->error($::locale->text('Order number invalid. Must be less then or equal to 7 digits after prefix.'));
+  }
+  # fill with zeros
+  $order_number = sprintf "%07d", $order_number;
+
+  # - invoice_number
+  #     input: prefix (letters) + up to 7 digits, may be zero
+  #     output: prefix removed, 7 digits, filled with leading zeros
+  $invoice_number = remove_letters_prefix($invoice_number);
+  if (!check_digits_and_max_length($invoice_number, 7)) {
+    $::form->error($::locale->text('Invoice number invalid. Must be less then or equal to 7 digits after prefix.'));
+  }
+  # fill with zeros
+  $invoice_number = sprintf "%07d", $invoice_number;
+
+  # assemble ref. number
+  my $ref_number = $bank_id . $customer_number . $order_number . $invoice_number;
+
+  # calculate check digit
+  my $ref_number_cpl = $ref_number . calculate_check_digit($ref_number);
+
+  $main::lxdebug->leave_sub();
+  return $ref_number_cpl;
+}
+
+sub get_ref_number_formatted {
+  $main::lxdebug->enter_sub();
+
+  my $ref_number = $_[0];
+
+  # create ref. number in format:
+  # 'XX XXXXX XXXXX XXXXX XXXXX XXXXX' (2 digits + 5 x 5 digits)
+  my $ref_number_spaced = substr($ref_number, 0, 2) . ' ' .
+                          substr($ref_number, 2, 5) . ' ' .
+                          substr($ref_number, 7, 5) . ' ' .
+                          substr($ref_number, 12, 5) . ' ' .
+                          substr($ref_number, 17, 5) . ' ' .
+                          substr($ref_number, 22, 5);
+
+  $main::lxdebug->leave_sub();
+  return $ref_number_spaced;
+}
+
+sub get_iban_formatted {
+  $main::lxdebug->enter_sub();
+
+  my $iban = $_[0];
+
+  # create iban number in format:
+  # 'XXXX XXXX XXXX XXXX XXXX X' (5 x 4 + 1digits)
+  my $iban_spaced = substr($iban, 0, 4) . ' ' .
+                    substr($iban, 4, 4) . ' ' .
+                    substr($iban, 8, 4) . ' ' .
+                    substr($iban, 12, 4) . ' ' .
+                    substr($iban, 16, 4) . ' ' .
+                    substr($iban, 20, 1);
+
+  $main::lxdebug->leave_sub();
+  return $iban_spaced;
+}
+
+sub get_amount_formatted {
+  $main::lxdebug->enter_sub();
+
+  unless ($_[0] =~ /^\d+\.\d{2}$/) {
+    $::form->error($::locale->text('Amount has wrong format.'));
+  }
+
+  local $_ = shift;
+  $_ = reverse split //;
+  m/^\d{2}\./g;
+  s/\G(\d{3})(?=\d)/$1 /g;
+
+  $main::lxdebug->leave_sub();
+  return scalar reverse split //;
+}
+
+sub generate_qr_code {
+  $main::lxdebug->enter_sub();
+  my $self = $_[0];
+  my $form = $self->{"form"};
+
+  # assemble data for QR-Code
+
+  # get qr-account data
+  my $qr_account = $self->get_qrbill_account();
+
+  my %biller_information = (
+    'iban' => $qr_account->{'iban'}
+  );
+
+  my $biller_countrycode = SL::Helper::ISO3166::map_name_to_alpha_2_code(
+    $::instance_conf->get_address_country()
+  );
+  if (!$biller_countrycode) {
+    $::form->error($::locale->text('Error mapping biller countrycode.'));
+  }
+  my %biller_data = (
+    'address_type' => 'K',
+    'company' => $::instance_conf->get_company(),
+    'address_row1' => $::instance_conf->get_address_street1(),
+    'address_row2' => $::instance_conf->get_address_zipcode() . ' ' . $::instance_conf->get_address_city(),
+    'countrycode' => $biller_countrycode,
+  );
+
+  my %payment_information = (
+    'amount' => sprintf("%.2f", $form->parse_amount(\%::myconfig, $form->{'total'})),
+    'currency' => $form->{'currency'},
+  );
+
+  my $customer_countrycode = SL::Helper::ISO3166::map_name_to_alpha_2_code($form->{'country'});
+  if (!$customer_countrycode) {
+    $::form->error($::locale->text('Error mapping customer countrycode.'));
+  }
+  my %invoice_recipient_data = (
+    'address_type' => 'K',
+    'name' => $form->{'name'},
+    'address_row1' => $form->{'street'},
+    'address_row2' => $form->{'zipcode'} . ' ' . $form->{'city'},
+    'countrycode' => $customer_countrycode,
+  );
+
+  # generate ref.-no. with check digit
+  my $ref_number = assemble_ref_number(
+    $qr_account->{'bank_account_id'},
+    $form->{'customernumber'},
+    $form->{'ordnumber'},
+    $form->{'invnumber'},
+  );
+
+  my %ref_nr_data = (
+    'type' => 'QRR',
+    'ref_number' => $ref_number,
+  );
+
+  # set into form for template processing
+  $form->{'biller_information'} = \%biller_information;
+  $form->{'biller_data'} = \%biller_data;
+  $form->{'ref_number'} = $ref_number;
+
+  # get ref. number/iban formatted with spaces
+  $form->{'ref_number_formatted'} = get_ref_number_formatted($ref_number);
+  $form->{'iban_formatted'} = get_iban_formatted($qr_account->{'iban'});
+
+  # format amount for template
+  $form->{'amount_formatted'} = get_amount_formatted(
+    sprintf(
+      "%.2f",
+      $form->parse_amount(\%::myconfig, $form->{'total'})
+    )
+  );
+
+  # set outfile
+  my $outfile = $form->{"tmpdir"} . '/' . 'qr-code.png';
+
+  # generate QR-Code Image
+  eval {
+   my $qr_image = SL::Helper::QrBill->new(
+     \%biller_information,
+     \%biller_data,
+     \%payment_information,
+     \%invoice_recipient_data,
+     \%ref_nr_data,
+   );
+   $qr_image->generate($outfile);
+  } or do {
+   local $_ = $@; chomp; my $error = $_;
+   $::form->error($::locale->text('QR-Image generation failed: ' . $error));
+  };
+
+  $main::lxdebug->leave_sub();
+  return $outfile;
+}
+
 sub is_xvfb_running {
   $main::lxdebug->enter_sub();
 
index a06a2a8..8d2b19b 100644 (file)
@@ -15,11 +15,12 @@ von kivitendo.
 
 Mittelgroße neue Features:
 
-In Kundenstammdaten können nun abweichende Rechnungsadressen analog zu
-Lieferadressen verwaltet werden. Diese können in Verkaufsbelegen
-ausgewählt werden. Sie stehen den Druckvorlagen als eigene Variablen
-zur Verfügung.
-
+- In Kundenstammdaten können nun abweichende Rechnungsadressen analog zu
+  Lieferadressen verwaltet werden. Diese können in Verkaufsbelegen
+  ausgewählt werden. Sie stehen den Druckvorlagen als eigene Variablen
+  zur Verfügung.
+- Unterstützung für Schweizer QR-Rechnung mit OpenDocument Vorlagen.
+  Variante: QR-IBAN mit QR-Referenz
 
 Kleinere neue Features und Detailverbesserungen:
 
diff --git a/image/CH-Kreuz_7mm.png b/image/CH-Kreuz_7mm.png
new file mode 100644 (file)
index 0000000..41d3c5f
Binary files /dev/null and b/image/CH-Kreuz_7mm.png differ
index 7e4c709..7918844 100755 (executable)
@@ -311,6 +311,7 @@ $self->{texts} = {
   'Amount BT'                   => 'Betrag Bank',
   'Amount Due'                  => 'Betrag fällig',
   'Amount and net amount are calculated by kivitendo. "verify_amount" and "verify_netamount" can be used for sanity checks.' => 'Betrag und Nettobetrag werden von kivitendo berechnet. "verify_amount" und "verify_netamount" können für Plausibilitätsprüfungen angegeben werden.',
+  'Amount has wrong format.'    => 'Betrag hat falsches Format.',
   'Amount less skonto'          => 'Betrag abzgl. Skonto',
   'Amount payable'              => 'Noch zu bezahlender Betrag',
   'Amount payable less discount' => 'Noch zu bezahlender Betrag abzüglich Skonto',
@@ -429,6 +430,7 @@ $self->{texts} = {
   'Balances'                    => 'Salden',
   'Balancing'                   => 'Bilanzierung',
   'Bank'                        => 'Bank',
+  'Bank Account Id Number (Swiss)' => 'Bankkonto Identifikationsnummer (Schweiz)',
   'Bank Code'                   => 'BLZ',
   'Bank Code (long)'            => 'Bankleitzahl (BLZ)',
   'Bank Code Number'            => 'Bankleitzahl',
@@ -438,6 +440,7 @@ $self->{texts} = {
   'Bank Transaction'            => 'Bankkonto',
   'Bank Transaction is in a closed period.' => 'Die Bankbewegung befindet sich innerhalb eines geschlossenen Zeitraums.',
   'Bank account'                => 'Bankkonto',
+  'Bank account id number invalid. Must be 6 digits.' => 'Bank Identifikationsnummer ungültig. (6-stellig)',
   'Bank accounts'               => 'Bankkonten',
   'Bank code'                   => 'Bankleitzahl',
   'Bank code of the goal/source' => 'Bankleitzahl von Ziel- oder Quellkonto',
@@ -812,6 +815,7 @@ $self->{texts} = {
   'Create one from the context menu by right-clicking on this text.' => 'Erstellen Sie einen aus dem Kontextmenü, indem Sie auf diesen Text rechtsklicken.',
   'Create order'                => 'Auftrag erstellen',
   'Create sales invoices with Factur-X/ZUGFeRD data' => 'Verkaufsrechnungen mit Factur-X-/ZUGFeRD-Daten erzeugen',
+  'Create sales invoices with Swiss QR-Bill' => 'Verkaufsrechnungen mit Schweizer QR-Rechnung erzeugen',
   'Create tables'               => 'Tabellen anlegen',
   'Create with profile \'Factur-X 1.0.05/ZUGFeRD 2.1.1 extended\'' => 'Mit Profil »Factur-X 1.0.05/ZUGFeRD 2.1.1 extended«',
   'Create with profile \'Factur-X 1.0.05/ZUGFeRD 2.1.1 extended\' (test mode)' => 'Mit Profil »Factur-X 1.0.05/ZUGFeRD 2.1.1 extended« (Test-Modus)',
@@ -886,6 +890,7 @@ $self->{texts} = {
   'Customer missing!'           => 'Kundenname fehlt!',
   'Customer must not be empty.' => 'Kunden darf nicht leer sein.',
   'Customer not found'          => 'Kunde nicht gefunden',
+  'Customer number invalid. Must be less then or equal to 6 digits after prefix.' => 'Kundennummer ungültig. (kleiner/gleich 6 Stellen nach Prefix)',
   'Customer of assigned order must match customer.' => 'Kunde des zugeordneten Auftrags muss mit dem gewählten Kunden übereinstimmen.',
   'Customer of assigned project must match customer.' => 'Kunde des zugeordneten Projekts muss mit dem gewählten Kunden übereinstimmen.',
   'Customer saved'              => 'Kunde gespeichert',
@@ -1356,6 +1361,8 @@ $self->{texts} = {
   'Error in position #1: You must either assign no stock at all or the full quantity of #2 #3.' => 'Fehler in Position #1: Sie müssen einer Position entweder gar keinen Lagereingang oder die vollständige im Lieferschein vermerkte Menge von #2 #3 zuweisen.',
   'Error in position #1: You must either assign no transfer at all or the full quantity of #2 #3.' => 'Fehler in Position #1: Sie müssen einer Position entweder gar keinen Lagerausgang oder die vollständige im Lieferschein vermerkte Menge von #2 #3 zuweisen.',
   'Error in row #1: The quantity you entered is bigger than the stocked quantity.' => 'Fehler in Zeile #1: Die angegebene Menge ist größer als die vorhandene Menge.',
+  'Error mapping biller countrycode.' => 'Fehler beim Erzeugen des Ländercodes für Rechnungssteller.',
+  'Error mapping customer countrycode.' => 'Fehler beim Erzeugen des Ländercodes für Kunden.',
   'Error message from the database driver:' => 'Fehlermeldung des Datenbanktreibers:',
   'Error message from the database: #1' => 'Fehlermeldung der Datenbank: #1',
   'Error message from the webshop api:' => 'Fehlermeldung der Webshop Api',
@@ -1714,6 +1721,7 @@ $self->{texts} = {
   'If enabled a warning will be shown in sales delivery orders on workflow to invoices if positions are not stocked out.' => 'Falls aktiviert, wird eine Warnung beim Workflow von Verkaufslieferscheinen zu Rechnungen ausgegeben, wenn die Positionen noch nicht ausgelagert sind.',
   'If enabled only those projects that are assigned to the currently selected customer are offered for selection in sales records.' => 'Wenn eingeschaltet, so werden in Verkaufsbelegen nur diejenigen Projekte zur Auswahl angeboten, die dem aktuell ausgewählten Kunden zugewiesen wurden.',
   'If enabled purchase and sales records cannot be saved if no transaction description has been entered.' => 'Wenn angeschaltet, so können Einkaufs- und Verkaufsbelege nicht gespeichert werden, solange keine Vorgangsbezeichnung eingegeben wurde.',
+  'If enabled sales invoices created using OpenDocument/OASIS format will include data for Swiss QR-Bill creation.' => 'Falls aktiviert, enthalten Rechnungen im OpenDocument/OASIS Format, Daten zur Schweizer QR-Rechnung.',
   'If enabled the record links view starts always from the sales order including all sublevels' => 'Falls aktiv, werden die verknüpften Belege immer vom Verkaufsauftrag inkl. aller darunterliegenden Belege angezeigt',
   'If item not found, allow creation of new item' => 'Falls Artikel nicht gefunden, erlaube Erfassen eines Neuen',
   'If left empty the default sender from the kivitendo configuration will be used (key \'email_from\' in section \'periodic_invoices\'; current value: #1).' => 'Falls leer, so wird der Standardabsender aus der kivitendo-Konfiguration genutzt (Schlüssel »email_from« in Abschnitt »periodic_invoices«; aktueller Wert: #1).',
@@ -1857,6 +1865,7 @@ $self->{texts} = {
   'Invoice for fees'            => 'Rechnung über Gebühren',
   'Invoice has already been storno\'d!' => 'Diese Rechnung wurde bereits storniert.',
   'Invoice number'              => 'Rechnungsnummer',
+  'Invoice number invalid. Must be less then or equal to 7 digits after prefix.' => 'Rechnungsnummer ungültig. (kleiner/gleich 7 Stellen nach Prefix)',
   'Invoice to:'                 => 'Rechnung an:',
   'Invoice total'               => 'Die Rechnungssumme',
   'Invoice total less discount' => 'Rechnungssumme abzüglich Skonto',
@@ -2163,6 +2172,7 @@ $self->{texts} = {
   'No bank account chosen!'     => 'Kein Bankkonto ausgewählt!',
   'No bank account configured for bank code/BIC #1, account number/IBAN #2.' => 'Kein Bankkonto für BLZ/BIC #1, Kontonummer/IBAN #2 konfiguriert.',
   'No bank account flagged for Factur-X/ZUGFeRD usage was found.' => 'Es wurde kein Bankkonto gefunden, das für Nutzung mit Factur-X/ZUGFeRD markiert ist.',
+  'No bank account flagged for QRBill usage was found.' => 'Kein Bankkonto markiert für QR-Rechnung gefunden.',
   'No bank information has been entered in this customer\'s master data entry. You cannot create bank collections unless you enter bank information.' => 'Für diesen Kunden wurden in seinen Stammdaten keine Kontodaten hinterlegt. Solange dies nicht geschehen ist, können Sie keine Überweisungen für den Lieferanten anlegen.',
   'No bank information has been entered in this vendor\'s master data entry. You cannot create bank transfers unless you enter bank information.' => 'Für diesen Lieferanten wurden in seinen Stammdaten keine Kontodaten hinterlegt. Solange dies nicht geschehen ist, können Sie keine Überweisungen für den Lieferanten anlegen.',
   'No bins have been added to this warehouse yet.' => 'Es wurden zu diesem Lager noch keine Lagerplätze angelegt.',
@@ -2339,6 +2349,7 @@ $self->{texts} = {
   'Order amount'                => 'Auftragswert',
   'Order deleted!'              => 'Auftrag gelöscht!',
   'Order item search'           => 'Auftragsartikelsuche',
+  'Order number invalid. Must be less then or equal to 7 digits after prefix.' => 'Auftragsnummer ungültig. (kleiner/gleich 7 Stellen nach Prefix)',
   'Order probability'           => 'Auftragswahrscheinlichkeit',
   'Order probability & expected billing date' => 'Auftragswahrscheinlichkeit & vorrauss. Abrechnungsdatum',
   'Order value periodicity'     => 'Auftragswert basiert auf Periodizität',
@@ -2695,6 +2706,8 @@ $self->{texts} = {
   'Purpose'                     => 'Verwendungszweck',
   'Purpose (if field names purpose, purpose1, purpose2 ... exist they will all combined into the field "purpose")' => 'Verwendungszweck (wenn die Spalten purpose, purpose1, purpose2 ... existieren werden diese zum Feld "purpose" zusammengefügt)',
   'Purpose/Reference'           => 'Verwendungszweck und Referenz',
+  'QR-Code placeholder image: QRCodePlaceholder not found in template.' => 'QR-Code Platzhalter Bild: QRCodePlaceholder nicht gefunden.',
+  'QR-Image generation failed: ' => 'QR-Code Erzeugung fehlgeschlagen: ',
   'QUEUED'                      => 'In Warteschlange',
   'Qty'                         => 'Menge',
   'Qty according to delivery order' => 'Menge laut Lieferschein',
@@ -4056,6 +4069,7 @@ $self->{texts} = {
   'Use default booking group because wanted is missing' => 'Fehlende Buchungsgruppe, deshalb Standardbuchungsgruppe',
   'Use existing templates'      => 'Vorhandene Druckvorlagen verwenden',
   'Use for Factur-X/ZUGFeRD'    => 'Nutzung mit Factur-X/ZUGFeRD',
+  'Use for Swiss QR-Bill'       => 'Nutzung mit Schweizer QR-Rechnung',
   'Use master default bin for Default Transfer, if no default bin for the part is configured' => 'Standardlagerplatz für Ein- / Auslagern über Standard-Lagerplatz, falls für die Ware kein expliziter Lagerplatz konfiguriert ist',
   'Use settings from client configuration' => 'Einstellungen aus Mandantenkonfiguration folgen',
   'Use text field for department of contacts' => 'Textfeld für Abteilungen von Ansprechpersonen verwenden',
index 3d05e6d..dc99bfc 100644 (file)
@@ -311,6 +311,7 @@ $self->{texts} = {
   'Amount BT'                   => '',
   'Amount Due'                  => '',
   'Amount and net amount are calculated by kivitendo. "verify_amount" and "verify_netamount" can be used for sanity checks.' => '',
+  'Amount has wrong format.'    => '',
   'Amount less skonto'          => '',
   'Amount payable'              => '',
   'Amount payable less discount' => '',
@@ -429,6 +430,7 @@ $self->{texts} = {
   'Balances'                    => '',
   'Balancing'                   => '',
   'Bank'                        => '',
+  'Bank Account Id Number (Swiss)' => '',
   'Bank Code'                   => '',
   'Bank Code (long)'            => '',
   'Bank Code Number'            => '',
@@ -438,6 +440,7 @@ $self->{texts} = {
   'Bank Transaction'            => '',
   'Bank Transaction is in a closed period.' => '',
   'Bank account'                => '',
+  'Bank account id number invalid. Must be 6 digits.' => '',
   'Bank accounts'               => '',
   'Bank code'                   => '',
   'Bank code of the goal/source' => '',
@@ -812,6 +815,7 @@ $self->{texts} = {
   'Create one from the context menu by right-clicking on this text.' => '',
   'Create order'                => '',
   'Create sales invoices with Factur-X/ZUGFeRD data' => '',
+  'Create sales invoices with Swiss QR-Bill' => '',
   'Create tables'               => '',
   'Create with profile \'Factur-X 1.0.05/ZUGFeRD 2.1.1 extended\'' => '',
   'Create with profile \'Factur-X 1.0.05/ZUGFeRD 2.1.1 extended\' (test mode)' => '',
@@ -886,6 +890,7 @@ $self->{texts} = {
   'Customer missing!'           => '',
   'Customer must not be empty.' => '',
   'Customer not found'          => '',
+  'Customer number invalid. Must be less then or equal to 6 digits after prefix.' => '',
   'Customer of assigned order must match customer.' => '',
   'Customer of assigned project must match customer.' => '',
   'Customer saved'              => '',
@@ -1356,6 +1361,8 @@ $self->{texts} = {
   'Error in position #1: You must either assign no stock at all or the full quantity of #2 #3.' => '',
   'Error in position #1: You must either assign no transfer at all or the full quantity of #2 #3.' => '',
   'Error in row #1: The quantity you entered is bigger than the stocked quantity.' => '',
+  'Error mapping biller countrycode.' => '',
+  'Error mapping customer countrycode.' => '',
   'Error message from the database driver:' => '',
   'Error message from the database: #1' => '',
   'Error message from the webshop api:' => '',
@@ -1714,6 +1721,7 @@ $self->{texts} = {
   'If enabled a warning will be shown in sales delivery orders on workflow to invoices if positions are not stocked out.' => '',
   'If enabled only those projects that are assigned to the currently selected customer are offered for selection in sales records.' => '',
   'If enabled purchase and sales records cannot be saved if no transaction description has been entered.' => '',
+  'If enabled sales invoices created using OpenDocument/OASIS format will include data for Swiss QR-Bill creation.' => '',
   'If enabled the record links view starts always from the sales order including all sublevels' => '',
   'If item not found, allow creation of new item' => '',
   'If left empty the default sender from the kivitendo configuration will be used (key \'email_from\' in section \'periodic_invoices\'; current value: #1).' => '',
@@ -1857,6 +1865,7 @@ $self->{texts} = {
   'Invoice for fees'            => '',
   'Invoice has already been storno\'d!' => '',
   'Invoice number'              => '',
+  'Invoice number invalid. Must be less then or equal to 7 digits after prefix.' => '',
   'Invoice to:'                 => '',
   'Invoice total'               => '',
   'Invoice total less discount' => '',
@@ -2163,6 +2172,7 @@ $self->{texts} = {
   'No bank account chosen!'     => '',
   'No bank account configured for bank code/BIC #1, account number/IBAN #2.' => '',
   'No bank account flagged for Factur-X/ZUGFeRD usage was found.' => '',
+  'No bank account flagged for QRBill usage was found.' => '',
   'No bank information has been entered in this customer\'s master data entry. You cannot create bank collections unless you enter bank information.' => '',
   'No bank information has been entered in this vendor\'s master data entry. You cannot create bank transfers unless you enter bank information.' => '',
   'No bins have been added to this warehouse yet.' => '',
@@ -2339,6 +2349,7 @@ $self->{texts} = {
   'Order amount'                => '',
   'Order deleted!'              => '',
   'Order item search'           => '',
+  'Order number invalid. Must be less then or equal to 7 digits after prefix.' => '',
   'Order probability'           => '',
   'Order probability & expected billing date' => '',
   'Order value periodicity'     => '',
@@ -2695,6 +2706,8 @@ $self->{texts} = {
   'Purpose'                     => '',
   'Purpose (if field names purpose, purpose1, purpose2 ... exist they will all combined into the field "purpose")' => '',
   'Purpose/Reference'           => '',
+  'QR-Code placeholder image: QRCodePlaceholder not found in template.' => '',
+  'QR-Image generation failed: ' => '',
   'QUEUED'                      => '',
   'Qty'                         => '',
   'Qty according to delivery order' => '',
@@ -4055,6 +4068,7 @@ $self->{texts} = {
   'Use default booking group because wanted is missing' => '',
   'Use existing templates'      => '',
   'Use for Factur-X/ZUGFeRD'    => '',
+  'Use for Swiss QR-Bill'       => '',
   'Use master default bin for Default Transfer, if no default bin for the part is configured' => '',
   'Use settings from client configuration' => '',
   'Use text field for department of contacts' => '',
diff --git a/templates/print/rev-odt/invoice_qr.odt b/templates/print/rev-odt/invoice_qr.odt
new file mode 100644 (file)
index 0000000..2b0688a
Binary files /dev/null and b/templates/print/rev-odt/invoice_qr.odt differ
index 1585eba..46d8a37 100644 (file)
      [% LxERP.t8("If the test mode is enabled, the Factur-X/ZUGFeRD invoices will be flagged so that they're only fit to be used for testing purposes.") %]
    </td>
   </tr>
+  <tr>
+   <td align="right">[% LxERP.t8("Create sales invoices with Swiss QR-Bill") %]</td>
+   <td>[% L.yes_no_tag("defaults.create_qrbill_invoices", SELF.defaults.create_qrbill_invoices) %]</td>
+   <td>[% LxERP.t8("If enabled sales invoices created using OpenDocument/OASIS format will include data for Swiss QR-Bill creation.") %]</td>
+  </tr>
 
   <tr><td class="listheading" colspan="4">[% LxERP.t8("E-mail") %]</td></tr>