Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision 746da637

Von Cem Aydin vor 12 Monaten hinzugefügt

  • ID 746da63786aafdffa3876c22c17de6f87b13b0fc
  • Vorgänger 5210125b
  • Nachfolger b1f188d9

Schweizer Banküberweisung via XML implementiert

Analog zu "Überweisung via SEPA XML"

Das Feature kann in der Mandantenkonfiguration ein-/ausgeschaltet
werden. Wenn eingeschaltet, wird anstatt des SEPA export ein Export
gemäss dem schweizer Standard erstellt. Dementsprechend werden die
Texte im Menü und in den Templates ersetzt, und im Code mittels
Weiche auf den schweizer Export umgeschaltet.

Gemäss Standard:
Swiss Payment Standards
Customer Credit Transfer Initiation (pain.001)

Unterschiede anzeigen:

bin/mozilla/sepa.pl
14 14
use SL::ReportGenerator;
15 15
use SL::SEPA;
16 16
use SL::SEPA::XML;
17
use SL::SEPA::SwissXML;
18
use SL::Helper::QrBillParser;
19
use SL::Helper::ISO3166;
20

  
21
use SL::Helper::QrBillFunctions qw(
22
  get_street_name_from_address_line
23
  get_building_number_from_address_line
24
);
17 25

  
18 26
require "bin/mozilla/common.pl";
19 27
require "bin/mozilla/reportgenerator.pl";
......
26 34
  my $vc            = $form->{vc} eq 'customer' ? 'customer' : 'vendor';
27 35
  my $vc_no         = $form->{vc} eq 'customer' ? $::locale->text('VN') : $::locale->text('CN');
28 36

  
29
  $form->{title}    = $vc eq 'customer' ? $::locale->text('Prepare bank collection via SEPA XML') : $locale->text('Prepare bank transfer via SEPA XML');
37
  my $swiss_export = $::instance_conf->get_sepa_swiss_xml_export;
38

  
39
  $form->{title}    = $vc eq 'customer' ?
40
                        $::locale->text('Prepare bank collection via SEPA XML') :
41
                        $swiss_export ?
42
                          $locale->text('Prepare bank transfer via swiss XML') :
43
                          $locale->text('Prepare bank transfer via SEPA XML');
30 44

  
31 45
  my $bank_accounts = SL::DB::Manager::BankAccount->get_all_sorted( query => [ obsolete => 0 ] );
32 46

  
......
66 80
    $invoice->{reference_prefix_vc}  = ' '  . $prefix_vc_number unless $prefix_vc_number =~ m/^ /;
67 81
  }
68 82

  
83
  # for swiss export override database check because of different cases
84
  if ($swiss_export) {
85
    foreach my $invoice (@{ $invoices }) {
86
      # determine invoice type
87
      if ($invoice->{qrbill_data}) {
88
        $invoice->{type} = 'QRBILL';
89

  
90
        # vendor iban comes from qrbill data
91
        # no further checks needed
92
        $invoice->{vc_bank_info_ok} = 1;
93

  
94
      } elsif ($invoice->{vc_iban} =~ m/^(CH|LI)/) {
95
        $invoice->{type} = 'DOMESTIC';
96

  
97
        # vendor iban is needed
98
        $invoice->{vc_bank_info_ok} = $invoice->{vc_iban} ? 1 : 0;
99

  
100
      } else {
101
        $invoice->{type} = 'SEPA';
102

  
103
        # vendor iban and bic are needed
104
        $invoice->{vc_bank_info_ok} = $invoice->{vc_iban} && $invoice->{vc_bic} ? 1 : 0
105
      }
106
    }
107
  }
108

  
69 109
  setup_sepa_add_transfer_action_bar();
70 110

  
71 111
  $form->header();
......
86 126
  my $myconfig      = \%main::myconfig;
87 127
  my $vc            = $form->{vc} eq 'customer' ? 'customer' : 'vendor';
88 128

  
89
  $form->{title}    = $vc eq 'customer' ? $::locale->text('Create bank collection via SEPA XML') : $locale->text('Create bank transfer via SEPA XML');
129
  my $swiss_export = $::instance_conf->get_sepa_swiss_xml_export;
130

  
131
  $form->{title}    = $vc eq 'customer' ?
132
                        $::locale->text('Create bank collection via SEPA XML') :
133
                        $swiss_export ?
134
                          $locale->text('Create bank transfer via swiss XML') :
135
                          $locale->text('Create bank transfer via SEPA XML');
90 136

  
91 137
  my $bank_accounts = SL::DB::Manager::BankAccount->get_all_sorted( query => [ obsolete => 0 ] );
92 138
  if (!scalar @{ $bank_accounts }) {
......
149 195

  
150 196
  my ($vc_bank_info);
151 197
  my $error_message;
152

  
153 198
  my @bank_columns    = qw(iban bic);
154
  push @bank_columns, qw(mandator_id mandate_date_of_signature) if $vc eq 'customer';
155 199

  
156
  if ($form->{confirmation}) {
157
    $vc_bank_info = { map { $_->{id} => $_ } @{ $form->{vc_bank_info} || [] } };
200
  # separate validation for swiss export
201
  if (!$swiss_export) {
202
    push @bank_columns, qw(mandator_id mandate_date_of_signature) if $vc eq 'customer';
158 203

  
159
    foreach my $info (values %{ $vc_bank_info }) {
160
      if (any { !$info->{$_} } @bank_columns) {
161
        $error_message = $locale->text('The bank information must not be empty.');
162
        last;
204
    if ($form->{confirmation}) {
205
      $vc_bank_info = { map { $_->{id} => $_ } @{ $form->{vc_bank_info} || [] } };
206

  
207
      foreach my $info (values %{ $vc_bank_info }) {
208
        if (any { !$info->{$_} } @bank_columns) {
209
          $error_message = $locale->text('The bank information must not be empty.');
210
          last;
211
        }
163 212
      }
164 213
    }
214
  } else {
215
    ($error_message, $vc_bank_info) = validate_vendors_swiss_export(\@bank_transfers);
165 216
  }
166 217

  
167 218
  if ($error_message || !$form->{confirmation}) {
168
    my @vc_ids                 = uniq map { $_->{vc_id} } @bank_transfers;
169
    $vc_bank_info            ||= CT->get_bank_info('vc' => $vc,
170
                                                   'id' => \@vc_ids);
171
    my @vc_bank_info           = sort { lc $a->{name} cmp lc $b->{name} } values %{ $vc_bank_info };
219
    if (!$swiss_export) {
220
      my @vc_ids = uniq map { $_->{vc_id} } @bank_transfers;
221

  
222
      $vc_bank_info ||= CT->get_bank_info('vc' => $vc, 'id' => \@vc_ids);
223
    }
224

  
225
    my @vc_bank_info = sort { lc $a->{name} cmp lc $b->{name} } values %{ $vc_bank_info };
172 226

  
173 227
    setup_sepa_create_transfer_action_bar(is_vendor => $vc eq 'vendor');
174 228

  
......
198 252
                                     'vc'             => $vc);
199 253

  
200 254
    $form->header();
201
    print $form->parse_html_template('sepa/bank_transfer_created', { 'id' => $id, 'vc' => $vc });
255
    print $form->parse_html_template('sepa/bank_transfer_created',
256
                                      {
257
                                        'id' => $id,
258
                                        'vc' => $vc,
259
                                      });
202 260
  }
203 261

  
204 262
  $main::lxdebug->leave_sub();
205 263
}
206 264

  
265
sub validate_vendors_swiss_export {
266
  my ($bank_transfers) = @_;
267

  
268
  my $form          = $main::form;
269
  my $locale        = $main::locale;
270
  my $myconfig      = \%main::myconfig;
271

  
272
  # determine unique vendor types
273
  my %unique_vendor_types;
274
  for my $bt (@$bank_transfers) {
275
    my $uid = "$bt->{vc_id}_$bt->{type}";
276
    $unique_vendor_types{$uid} = {
277
      vc_id => $bt->{vc_id},
278
      type => $bt->{type}
279
    } unless defined $unique_vendor_types{$uid};
280
  }
281

  
282
  # get bank info for unique vendor types
283
  my $vendors = $form->{vc_bank_info} ?
284
    { map { $_->{id} => $_ } @{ $form->{vc_bank_info} } } :
285
    CT->get_bank_info('vc' => 'vendor', 'id' => [ map { $_->{vc_id} } values %unique_vendor_types ]);
286

  
287
  # combine bank info with unique vendor types
288
  for my $unique_vendor (values %unique_vendor_types) {
289
    $vendors->{$unique_vendor->{vc_id}}->{type} = $unique_vendor->{type};
290
  }
291

  
292
  # validate bank info for unique vendor types
293
  my $error_message;
294
  for my $vendor (values %$vendors) {
295
    #my $bank_info = $unique_vendor->{bank_info};
296
    next if ($vendor->{type} eq 'QRBILL');
297
    if ($vendor->{type} eq 'DOMESTIC' && !$vendor->{iban}) {
298
        $error_message = $locale->text('The bank information must not be empty.');
299
        last;
300
    } elsif ($vendor->{type} eq 'SEPA' && (!$vendor->{iban} || !$vendor->{bic})) {
301
        $error_message = $locale->text('The bank information must not be empty.');
302
        last;
303
    }
304
  }
305

  
306
  return $error_message, $vendors;
307
}
308

  
207 309
sub bank_transfer_search {
208 310
  $main::lxdebug->enter_sub();
209 311

  
......
267 369
    'closed'      => { 'text' => $locale->text('Closed'), },
268 370
    num_invoices  => { 'text' => $locale->text('Number of invoices'), },
269 371
    sum_amounts   => { 'text' => $locale->text('Sum of all amounts'), },
270
    message_ids   => { 'text' => $locale->text('SEPA message IDs'), },
372
    message_ids   => { 'text' => !$::instance_conf->get_sepa_swiss_xml_export ? $locale->text('SEPA message IDs') : $locale->text('Message IDs'), },
271 373
  );
272 374

  
273 375
  my @columns = qw(selected id export_date employee executed closed num_invoices sum_amounts message_ids);
......
390 492
    has_executed              => $has_executed,
391 493
  );
392 494

  
393
  $form->{title}    = $locale->text('View SEPA export');
495
  $form->{title}    = !$::instance_conf->get_sepa_swiss_xml_export ?
496
                        $locale->text('View SEPA export') :
497
                        $locale->text('View bank transfer');
394 498
  $form->header();
395 499
  print $form->parse_html_template('sepa/bank_transfer_edit',
396 500
                                   { ids                       => \@ids,
......
514 618
  my $vc       = $form->{vc} eq 'customer' ? 'customer' : 'vendor';
515 619
  my $defaults = SL::DB::Default->get;
516 620

  
621
  my $swiss_export = $::instance_conf->get_sepa_swiss_xml_export;
622

  
517 623
  if (!$defaults->company) {
518 624
    $form->show_generic_error($locale->text('You have to enter a company name in the client configuration.'));
519 625
  }
......
547 653

  
548 654
  my $message_id = strftime('MSG%Y%m%d%H%M%S', localtime) . sprintf('%06d', $$);
549 655

  
550
  my $sepa_xml   = SL::SEPA::XML->new('company'     => $defaults->company,
551
                                      'creditor_id' => $defaults->sepa_creditor_id,
552
                                      'src_charset' => 'UTF-8',
553
                                      'message_id'  => $message_id,
554
                                      'grouped'     => 1,
555
                                      'collection'  => $vc eq 'customer',
556
    );
656
  my $sepa_xml;
657
  my %sepa_xml_params = (
658
    'company'     => $defaults->company,
659
    'creditor_id' => $defaults->sepa_creditor_id,
660
    'src_charset' => 'UTF-8',
661
    'message_id'  => $message_id,
662
    'grouped'     => 1,
663
    'collection'  => $vc eq 'customer',
664
  );
665
  if (!$swiss_export) {
666
      $sepa_xml = SL::SEPA::XML->new(%sepa_xml_params);
667
  } else {
668
      $sepa_xml = SL::SEPA::SwissXML->new(%sepa_xml_params);
669
  }
557 670

  
558 671
  foreach my $item (@items) {
559 672
    my $requested_execution_date;
......
573 686
      }
574 687
    }
575 688

  
576
    $sepa_xml->add_transaction({ 'src_iban'       => $item->{our_iban},
577
                                 'src_bic'        => $item->{our_bic},
578
                                 'dst_iban'       => $item->{vc_iban},
579
                                 'dst_bic'        => $item->{vc_bic},
580
                                 'company'        => $item->{vc_name},
581
                                 'company_number' => $item->{vc_number},
582
                                 'amount'         => $item->{amount},
583
                                 'reference'      => $item->{reference},
584
                                 'mandator_id'    => $mandator_id,
585
                                 'reference_date' => $item->{reference_date},
586
                                 'execution_date' => $requested_execution_date,
587
                                 'end_to_end_id'  => $item->{end_to_end_id},
588
                                 'date_of_signature' => $item->{mandate_date_of_signature}, });
689
    my $transaction_data = {
690
      'src_iban'       => $item->{our_iban},
691
      'src_bic'        => $item->{our_bic},
692
      'dst_iban'       => $item->{vc_iban},
693
      'dst_bic'        => $item->{vc_bic},
694
      'company'        => $item->{vc_name},
695
      'company_number' => $item->{vc_number},
696
      'amount'         => $item->{amount},
697
      'reference'      => $item->{reference},
698
      'mandator_id'    => $mandator_id,
699
      'reference_date' => $item->{reference_date},
700
      'execution_date' => $requested_execution_date,
701
      'end_to_end_id'  => $item->{end_to_end_id},
702
      'date_of_signature' => $item->{mandate_date_of_signature},
703
    };
704

  
705
    # set data for swiss xml export
706
    if ($swiss_export) {
707

  
708
      $transaction_data->{currency} = $item->{currency_name};
709
      $transaction_data->{is_qrbill} = 0;
710
      $transaction_data->{is_sepa_payment} = 0;
711

  
712
      if ($item->{qrbill_data}) {
713
        my $qr_obj = SL::Helper::QrBillParser->new($item->{qrbill_data});
714
        # check if valid qr-bill
715
        if (!$qr_obj->is_valid) {
716
          $form->show_generic_error($locale->text('QR bill data invalid.'));
717
        }
718
        $transaction_data->{is_qrbill} = 1;
719

  
720
        # set qr reference type
721
        # setting these according to example pain_001_Example_PT_D_QRR_SCOR.xml
722
        # 'QRR'
723
        if ($qr_obj->{payment_reference}->{reference_type} eq 'QRR') {
724
          $transaction_data->{end_to_end_id} = 'ENDTOENDID-QRR';
725
        }
726
        # 'SCOR'
727
        elsif ($qr_obj->{payment_reference}->{reference_type} eq 'SCOR') {
728
          $transaction_data->{end_to_end_id} = 'ENDTOENDID-SCOR';
729
        }
730
        # 'NON'
731
        # (using the default end_to_end_id given above)
732

  
733
        # data for remittance information
734
        # this contains the reference for 'QRR' or 'SCOR'
735
        $transaction_data->{reference} = $qr_obj->{payment_reference}->{reference};
736
        # this can be used in any case
737
        $transaction_data->{unstructured_message} = $qr_obj->{additional_information}->{unstructured_message};
738

  
739
        # set currency and amount
740
        $transaction_data->{currency} = $qr_obj->{payment_amount_information}->{currency};
741
        $transaction_data->{amount} = $qr_obj->{payment_amount_information}->{amount};
742

  
743
        # set creditor name and address from qr data
744
        $transaction_data->{creditor_name} = $qr_obj->{creditor}->{name};
745
        $transaction_data->{creditor_street_name} = $qr_obj->get_creditor_street_name;
746
        $transaction_data->{creditor_building_number} = $qr_obj->get_creditor_building_number;
747
        $transaction_data->{creditor_postal_code} = $qr_obj->get_creditor_post_code;
748
        $transaction_data->{creditor_town_name} = $qr_obj->get_creditor_town_name;
749
        $transaction_data->{creditor_country} = $qr_obj->{creditor}->{country};
750

  
751
        # set creditor iban
752
        $transaction_data->{dst_iban} = $qr_obj->{creditor_information}->{iban};
753
      } else {
754
        # if no qr-bill data is given, we want to set the creditor address from the vc data
755
        # this is not needed for the creditor name as it is set above
756

  
757
        $transaction_data->{has_creditor_address} = 0;
758

  
759
        if ($item->{vc_zipcode} && $item->{vc_city} && $item->{vc_country}) {
760
          $transaction_data->{has_creditor_address} = 1;
761

  
762
          $transaction_data->{creditor_street_name} = get_street_name_from_address_line($item->{vc_street});
763
          $transaction_data->{creditor_building_number} = get_building_number_from_address_line($item->{vc_building_number});
764

  
765
          $transaction_data->{creditor_postal_code} = $item->{vc_zipcode};
766
          $transaction_data->{creditor_town_name} = $item->{vc_city};
767
          # use ISO 3166-1 alpha-2 country code
768
          $transaction_data->{creditor_country} = SL::Helper::ISO3166::map_name_to_alpha_2_code($item->{vc_country});
769
        }
770

  
771
        # if the Iban does not start with 'CH' or 'LI' we assume it is a SEPA payment
772
        if ($item->{vc_iban} !~ /^(CH|LI)/) {
773
          $transaction_data->{is_sepa_payment} = 1;
774

  
775
          # check if currency is EUR
776
          if ($transaction_data->{currency} ne 'EUR') {
777
            $form->show_generic_error($locale->text('SEPA payments must be in EUR.'));
778
          }
779

  
780
          # check if destination BIC is set
781
          if (!$transaction_data->{dst_bic}) {
782
            $form->show_generic_error($locale->text('SEPA payments require a destination BIC.'));
783
          }
784
          # TODO: set reference (unstructured or SCOR)
785
          # -> where should the data come from?
786
        }
787
      }
788
    }
789

  
790
    $sepa_xml->add_transaction($transaction_data);
589 791
  }
590 792

  
591 793
  # Store the message ID used in each of the entries in order to
......
599 801

  
600 802
  my $xml = $sepa_xml->to_xml();
601 803

  
602
  print $cgi->header('-type'                => 'application/octet-stream',
603
                     '-content-disposition' => 'attachment; filename="SEPA_' . $message_id . ($vc eq 'customer' ? '.cdd' : '.cct') . '"',
604
                     '-content-length'      => length $xml);
804
  my $header;
805
  if ($swiss_export) {
806
    # I had to use encode for the length here, otherwise the xml output was cut off
807
    $header = {
808
      '-type' => 'text/xml; charset=utf-8',
809
      '-content-disposition' => 'attachment; filename="SWISS_' . $message_id . '.xml' . '"',
810
      '-content-length' => length Encode::encode('utf-8', $xml),
811
    };
812
  } else {
813
    $header = {
814
      '-type' => 'application/octet-stream',
815
      '-content-disposition' => 'attachment; filename="SEPA_' . $message_id . ($vc eq 'customer' ? '.cdd' : '.cct') . '"',
816
      '-content-length' => length $xml,
817
    };
818
  }
819

  
820
  print $cgi->header($header);
605 821
  print $xml;
606 822

  
607 823
  $main::lxdebug->leave_sub();
......
711 927
      combobox => [
712 928
        action => [ t8('Actions') ],
713 929
        action => [
714
          t8('SEPA XML download'),
930
          !$::instance_conf->get_sepa_swiss_xml_export ? t8('SEPA XML download') : t8('Swiss XML download'),
715 931
          submit => [ '#form', { action => 'bank_transfer_download_sepa_xml' } ],
716 932
          checks => [ [ 'kivi.check_if_entries_selected', '[name="ids[]"]' ] ],
717 933
        ],
......
724 940
          t8('Mark as closed'),
725 941
          submit => [ '#form', { action => 'bank_transfer_mark_as_closed' } ],
726 942
          checks => [ [ 'kivi.check_if_entries_selected', '[name="ids[]"]' ] ],
727
          confirm => [ $params{is_vendor} ? t8('Do you really want to close the selected SEPA exports? No payment will be recorded for bank transfers that haven\'t been marked as executed yet.')
728
                                          : t8('Do you really want to close the selected SEPA exports? No payment will be recorded for bank collections that haven\'t been marked as executed yet.') ],
943
          confirm => [ !$::instance_conf->get_sepa_swiss_xml_export ?
944
                          $params{is_vendor} ? t8('Do you really want to close the selected SEPA exports? No payment will be recorded for bank transfers that haven\'t been marked as executed yet.')
945
                                             : t8('Do you really want to close the selected SEPA exports? No payment will be recorded for bank collections that haven\'t been marked as executed yet.')
946
                                             : t8('Do you really want to close the selected Swiss XML exports? No payment will be recorded for bank transfers that haven\'t been marked as executed yet.') ],
729 947
        ],
730 948
        action => [
731
          t8('Undo SEPA exports'),
949
          !$::instance_conf->get_sepa_swiss_xml_export ? t8('Undo SEPA exports') : t8('Undo Swiss XML exports'),
732 950
          submit => [ '#form', { action => 'bank_transfer_undo_sepa_xml' } ],
733 951
          checks => [ [ 'kivi.check_if_entries_selected', '[name="ids[]"]' ] ],
734
          confirm => [ t8('Do you really want to undo the selected SEPA exports? You have to reassign the export again.') ],
952
          confirm => [ !$::instance_conf->get_sepa_swiss_xml_export ?
953
            t8('Do you really want to undo the selected SEPA exports? You have to reassign the export again.') :
954
            t8('Do you really want to undo the selected Swiss XML exports? You have to reassign the export again.') ],
735 955
        ],
736 956
      ], # end of combobox "Actions"
737 957
    );

Auch abrufbar als: Unified diff