ZUGFeRD: Zahlungsinfos ergänzt: Kontonummer, Typ=Einzug/Überweisung
authorMoritz Bunkus <m.bunkus@linet-services.de>
Fri, 6 Mar 2020 14:11:15 +0000 (15:11 +0100)
committerMoritz Bunkus <m.bunkus@linet-services.de>
Fri, 6 Mar 2020 14:11:15 +0000 (15:11 +0100)
SL/Controller/SimpleSystemSetting.pm
SL/DB/Helper/ZUGFeRD.pm
SL/DB/MetaSetup/BankAccount.pm
locale/de/all
sql/Pg-upgrade2/bank_account_flag_for_zugferd_usage.sql [new file with mode: 0644]
templates/webpages/simple_system_setting/_bank_account_form.html

index 2e559a3..4fec19a 100644 (file)
@@ -35,6 +35,7 @@ my %supported_types = (
       { method => 'bank',                                      title => t8('Bank'), },
       { method => 'bank_code',                                 title => t8('Bank code'), },
       { method => 'bic',                                       title => t8('BIC'), },
+      {                                                        title => t8('Use for ZUGFeRD'), formatter => sub { $_[0]->use_for_zugferd ? t8('yes') : t8('no') } },
       { method => 'reconciliation_starting_date_as_date',      title => t8('Date'),    align => 'right' },
       { method => 'reconciliation_starting_balance_as_number', title => t8('Balance'), align => 'right' },
     ],
index 3ad825c..0511256 100644 (file)
@@ -6,6 +6,7 @@ use utf8;
 use parent qw(Exporter);
 our @EXPORT = qw(create_zugferd_data create_zugferd_xmp_data);
 
+use SL::DB::BankAccount;
 use SL::DB::GenericTranslation;
 use SL::DB::Tax;
 use SL::DB::TaxKey;
@@ -176,6 +177,28 @@ sub _line_item {
   # <ram:IncludedSupplyChainTradeLineItem>
 }
 
+sub _specified_trade_settlement_payment_means {
+  my ($self, %params) = @_;
+
+  #     <ram:SpecifiedTradeSettlementPaymentMeans>
+  $params{xml}->startTag('ram:SpecifiedTradeSettlementPaymentMeans');
+  $params{xml}->dataElement('ram:TypeCode', $self->direct_debit ? 59 : 58); # 59 = SEPA direct debit, 58 = SEPA credit transfer
+
+  if ($self->direct_debit) {
+    $params{xml}->startTag('ram:PayerPartyDebtorFinancialAccount');
+    $params{xml}->dataElement('ram:IBANID', $self->customer->iban);
+    $params{xml}->endTag;
+
+  } else {
+    $params{xml}->startTag('ram:PayeePartyCreditorFinancialAccount');
+    $params{xml}->dataElement('ram:IBANID', $params{bank_account}->iban);
+    $params{xml}->endTag;
+  }
+
+  $params{xml}->endTag;
+  #     </ram:SpecifiedTradeSettlementPaymentMeans>
+}
+
 sub _taxes {
   my ($self, %params) = @_;
 
@@ -520,6 +543,7 @@ sub _applicable_header_trade_settlement {
   $params{xml}->startTag("ram:ApplicableHeaderTradeSettlement");
   $params{xml}->dataElement("ram:InvoiceCurrencyCode", _u8(SL::Helper::ISO4217::map_currency_name_to_code($self->currency->name) // 'EUR'));
 
+  _specified_trade_settlement_payment_means($self, %params);
   _taxes($self, %params);
   _payment_terms($self, %params);
   _totals($self, %params);
@@ -546,6 +570,7 @@ sub _supply_chain_trade_transaction {
 sub _validate_data {
   my ($self) = @_;
 
+  my %result;
   my $prefix = $::locale->text('The ZUGFeRD invoice data cannot be generated because the data validation failed.') . ' ';
 
   if (!$::instance_conf->get_co_ustid) {
@@ -572,43 +597,54 @@ sub _validate_data {
   if ($failed_unit) {
     SL::X::ZUGFeRDValidation->throw(message => $prefix . $::locale->text('One of the units used (#1) cannot be mapped to a known unit code from the UN/ECE Recommendation 20 list.', $failed_unit));
   }
+
+  if ($self->direct_debit) {
+    if (!$self->customer->iban) {
+      SL::X::ZUGFeRDValidation->throw(message => $prefix . $::locale->text('The customer\'s bank account number (IBAN) is missing.'));
+    }
+
+  } else {
+    my $bank_accounts     = SL::DB::Manager::BankAccount->get_all;
+    $result{bank_account} = scalar(@{ $bank_accounts }) == 1 ? $bank_accounts->[0] : first { $_->use_for_zugferd } @{ $bank_accounts };
+
+    if (!$result{bank_account}) {
+      SL::X::ZUGFeRDValidation->throw(message => $prefix . $::locale->text('No bank account flagged for ZUGFeRD usage was found.'));
+    }
+  }
+
+  return %result;
 }
 
 sub create_zugferd_data {
-  my ($self) = @_;
+  my ($self)        = @_;
 
-  _validate_data($self);
+  my $output        = '';
 
-  my %ptc_data = $self->calculate_prices_and_taxes;
-  my $output   = '';
-  my $xml      = XML::Writer->new(
-    OUTPUT      => \$output,
-    DATA_MODE   => 1,
-    DATA_INDENT => 2,
-    ENCODING    => 'utf-8',
+  my %params        = _validate_data($self);
+  $params{ptc_data} = { $self->calculate_prices_and_taxes };
+  $params{xml}      = XML::Writer->new(
+    OUTPUT          => \$output,
+    DATA_MODE       => 1,
+    DATA_INDENT     => 2,
+    ENCODING        => 'utf-8',
   );
 
-  my %params = (
-    ptc_data => \%ptc_data,
-    xml      => $xml,
-  );
-
-  $xml->xmlDecl();
+  $params{xml}->xmlDecl();
 
   # <rsm:CrossIndustryInvoice>
-  $xml->startTag("rsm:CrossIndustryInvoice",
-                 "xmlns:a"   => "urn:un:unece:uncefact:data:standard:QualifiedDataType:100",
-                 "xmlns:rsm" => "urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100",
-                 "xmlns:qdt" => "urn:un:unece:uncefact:data:standard:QualifiedDataType:10",
-                 "xmlns:ram" => "urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100",
-                 "xmlns:xs"  => "http://www.w3.org/2001/XMLSchema",
-                 "xmlns:udt" => "urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100");
+  $params{xml}->startTag("rsm:CrossIndustryInvoice",
+                         "xmlns:a"   => "urn:un:unece:uncefact:data:standard:QualifiedDataType:100",
+                         "xmlns:rsm" => "urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100",
+                         "xmlns:qdt" => "urn:un:unece:uncefact:data:standard:QualifiedDataType:10",
+                         "xmlns:ram" => "urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100",
+                         "xmlns:xs"  => "http://www.w3.org/2001/XMLSchema",
+                         "xmlns:udt" => "urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100");
 
   _exchanged_document_context($self, %params);
   _exchanged_document($self, %params);
   _supply_chain_trade_transaction($self, %params);
 
-  $xml->endTag;
+  $params{xml}->endTag;
   # </rsm:CrossIndustryInvoice>
 
   return $output;
index b72a170..cd3ffce 100644 (file)
@@ -21,6 +21,7 @@ __PACKAGE__->meta->columns(
   reconciliation_starting_balance => { type => 'numeric', precision => 15, scale => 5 },
   reconciliation_starting_date    => { type => 'date' },
   sortkey                         => { type => 'integer', not_null => 1 },
+  use_for_zugferd                 => { type => 'boolean', not_null => 1 },
 );
 
 __PACKAGE__->meta->primary_key_columns([ 'id' ]);
index 43959b9..72f7aa9 100755 (executable)
@@ -2005,6 +2005,7 @@ $self->{texts} = {
   'No assembly has been selected yet.' => 'Es wurde noch kein Erzeugnis ausgewahlt.',
   'No background job has been created yet.' => 'Es wurden noch keine Hintergrund-Jobs angelegt.',
   'No bank account chosen!'     => 'Kein Bankkonto ausgewählt!',
+  'No bank account flagged for ZUGFeRD usage was found.' => 'Es wurde kein Bankkonto gefunden, das für Nutzung mit ZUGFeRD markiert ist.',
   '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.',
@@ -3272,6 +3273,7 @@ $self->{texts} = {
   'The custom variable has been saved.' => 'Die benutzerdefinierte Variable wurde gespeichert.',
   'The custom variable is in use and cannot be deleted.' => 'Die benutzerdefinierte Variable ist in Benutzung und kann nicht gelöscht werden.',
   'The customer name is missing.' => 'Der Kundenname fehlt.',
+  'The customer\'s bank account number (IBAN) is missing.' => 'Die Kontonummer (IBAN) des Kunden fehlt.',
   'The database for user management and authentication does not exist. You can create let kivitendo create it with the following parameters:' => 'Die Datenbank für die Benutzeranmeldung existiert nicht. Sie können Sie von kivitendo automatisch mit den folgenden Parametern anlegen lassen:',
   'The database host is missing.' => 'Der Datenbankhost fehlt.',
   'The database name is missing.' => 'Der Datenbankname fehlt.',
@@ -3808,6 +3810,7 @@ $self->{texts} = {
   'Use default warehouse for assembly transfer' => 'Zum Fertigen Standardlager des Bestandteils verwenden',
   'Use existing templates'      => 'Vorhandene Druckvorlagen verwenden',
   'Use fill up when calculating shipped quantities?' => 'Sollen nicht verlinkte Positionen abgeglichen werden?',
+  'Use for ZUGFeRD'             => 'Nutzung mit ZUGFeRD',
   'Use linked items'            => 'Verknüpfte Positionen verwenden',
   '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',
diff --git a/sql/Pg-upgrade2/bank_account_flag_for_zugferd_usage.sql b/sql/Pg-upgrade2/bank_account_flag_for_zugferd_usage.sql
new file mode 100644 (file)
index 0000000..d62d7ee
--- /dev/null
@@ -0,0 +1,14 @@
+-- @tag: bank_account_flag_for_zugferd_usage
+-- @description: Bankkonto für die Nutzung mit ZUGFeRD markieren
+-- @depends: release_3_5_5
+ALTER TABLE bank_accounts
+ADD COLUMN use_for_zugferd BOOLEAN;
+
+UPDATE bank_accounts
+SET use_for_zugferd = (
+  SELECT COUNT(*)
+  FROM bank_accounts
+) = 1;
+
+ALTER TABLE bank_accounts
+ALTER COLUMN use_for_zugferd SET NOT NULL;
index f3da6cc..21e58fc 100644 (file)
   <th align="right">[% LxERP.t8('Chart') %]</th>
   <td>[% P.chart.picker('object.chart_id', SELF.object.chart_id, type='AR_paid,AP_paid', category='A,L,Q', choose=1, style=style, "data-validate"="required", "data-title"=LxERP.t8("Chart")) %]</td>
  </tr>
+ <tr>
+  <th align="right">[% LxERP.t8('Use for ZUGFeRD') %]</th>
+  <td>[% L.checkbox_tag('object.use_for_zugferd', checked = SELF.object.use_for_zugferd, for_submit=1) %]</td>
+ </tr>
  <tr>
   <th align="right">[% LxERP.t8('Obsolete') %]</th>
   <td>[% L.checkbox_tag('object.obsolete', checked = SELF.object.obsolete, for_submit=1) %]</td>