Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision cdb079eb

Von Cem Aydin vor etwa 2 Monaten hinzugefügt

  • ID cdb079ebb32c7c8130312cddd39f6c1d39cc6599
  • Vorgänger 3abd8072
  • Nachfolger be93d983

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';
203

  
204
    if ($form->{confirmation}) {
205
      $vc_bank_info = { map { $_->{id} => $_ } @{ $form->{vc_bank_info} || [] } };
158 206

  
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;
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
    next if ($vendor->{type} eq 'QRBILL');
296
    if ($vendor->{type} eq 'DOMESTIC' && !$vendor->{iban}) {
297
      $error_message = $locale->text('The bank information must not be empty.');
298
      last;
299
    } elsif ($vendor->{type} eq 'SEPA' && (!$vendor->{iban} || !$vendor->{bic})) {
300
      $error_message = $locale->text('The bank information must not be empty.');
301
      last;
302
    }
303
  }
304

  
305
  return $error_message, $vendors;
306
}
307

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

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

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

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

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

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

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

  
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
    );
655
  my $sepa_xml;
656
  my %sepa_xml_params = (
657
    'company'     => $defaults->company,
658
    'creditor_id' => $defaults->sepa_creditor_id,
659
    'src_charset' => 'UTF-8',
660
    'message_id'  => $message_id,
661
    'grouped'     => 1,
662
    'collection'  => $vc eq 'customer',
663
  );
664
  if (!$swiss_export) {
665
      $sepa_xml = SL::SEPA::XML->new(%sepa_xml_params);
666
  } else {
667
      $sepa_xml = SL::SEPA::SwissXML->new(%sepa_xml_params);
668
  }
557 669

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

  
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}, });
688
    my $transaction_data = {
689
      'src_iban'       => $item->{our_iban},
690
      'src_bic'        => $item->{our_bic},
691
      'dst_iban'       => $item->{vc_iban},
692
      'dst_bic'        => $item->{vc_bic},
693
      'company'        => $item->{vc_name},
694
      'company_number' => $item->{vc_number},
695
      'amount'         => $item->{amount},
696
      'reference'      => $item->{reference},
697
      'mandator_id'    => $mandator_id,
698
      'reference_date' => $item->{reference_date},
699
      'execution_date' => $requested_execution_date,
700
      'end_to_end_id'  => $item->{end_to_end_id},
701
      'date_of_signature' => $item->{mandate_date_of_signature},
702
    };
703

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  
591 792
  # Store the message ID used in each of the entries in order to
......
599 800

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

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

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

  
607 822
  $main::lxdebug->leave_sub();
......
711 926
      combobox => [
712 927
        action => [ t8('Actions') ],
713 928
        action => [
714
          t8('SEPA XML download'),
929
          !$::instance_conf->get_sepa_swiss_xml_export ? t8('SEPA XML download') : t8('Swiss XML download'),
715 930
          submit => [ '#form', { action => 'bank_transfer_download_sepa_xml' } ],
716 931
          checks => [ [ 'kivi.check_if_entries_selected', '[name="ids[]"]' ] ],
717 932
        ],
......
724 939
          t8('Mark as closed'),
725 940
          submit => [ '#form', { action => 'bank_transfer_mark_as_closed' } ],
726 941
          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.') ],
942
          confirm => [ !$::instance_conf->get_sepa_swiss_xml_export ?
943
                          $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.')
944
                                             : 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.')
945
                                             : 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 946
        ],
730 947
        action => [
731
          t8('Undo SEPA exports'),
948
          !$::instance_conf->get_sepa_swiss_xml_export ? t8('Undo SEPA exports') : t8('Undo Swiss XML exports'),
732 949
          submit => [ '#form', { action => 'bank_transfer_undo_sepa_xml' } ],
733 950
          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.') ],
951
          confirm => [ !$::instance_conf->get_sepa_swiss_xml_export ?
952
            t8('Do you really want to undo the selected SEPA exports? You have to reassign the export again.') :
953
            t8('Do you really want to undo the selected Swiss XML exports? You have to reassign the export again.') ],
735 954
        ],
736 955
      ], # end of combobox "Actions"
737 956
    );

Auch abrufbar als: Unified diff