Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision 5b44ba35

Von Tamino Steinert vor etwa 1 Jahr hinzugefügt

  • ID 5b44ba351678452baf4fc299244c951e3b124c7c
  • Vorgänger 5e0c45f8
  • Nachfolger 8753c9b3

ZUGFeRD: Verschiebe komplettes Parsen aus ap.pl nach S:C:ZUGFeRD

Unterschiede anzeigen:

SL/Controller/ZUGFeRD.pm
12 12
use SL::SessionFile;
13 13

  
14 14
use XML::LibXML;
15
use List::Util qw(first);
15 16

  
16 17

  
17 18
__PACKAGE__->run_before('check_auth');
......
128 129

  
129 130
  my %res;          # result data structure returned by SL::ZUGFeRD->extract_from_{pdf,xml}()
130 131
  my $parser;       # SL::XMLInvoice object created by SL::ZUGFeRD->extract_from_{pdf,xml}()
131
  my $dom;          # DOM object for parsed XML data
132 132
  my $vendor;       # SL::DB::Vendor object
133 133

  
134
  my $ibanmessage;  # Message to display if vendor's database and invoice IBANs don't match up
135

  
136
  die t8("missing file for action import") unless $file;
137
  die t8("can only parse a pdf or xml file")      unless $file =~ m/^%PDF|<\?xml/;
134
  die t8("missing file for action import")   unless $file;
135
  die t8("can only parse a pdf or xml file") unless $file =~ m/^%PDF|<\?xml/;
138 136

  
139 137
  if ( $::form->{file} =~ m/^%PDF/ ) {
140 138
    %res = %{SL::ZUGFeRD->extract_from_pdf($file)};
......
149 147

  
150 148
  $parser = $res{'invoice_xml'};
151 149

  
152
  # Shouldn't be neccessary with SL::XMLInvoice doing the heavy lifting, but
153
  # let's grab it, just in case.
154
  $dom  = $parser->{dom};
155

  
156 150
  my %metadata = %{$parser->metadata};
157 151
  my @items = @{$parser->items};
158 152

  
153
  my $notes = t8("ZUGFeRD Import. Type: #1", $metadata{'type'});
159 154
  my $iban = $metadata{'iban'};
160 155
  my $invnumber = $metadata{'invnumber'};
161 156

  
......
175 170

  
176 171
  # Check IBAN specified on bill matches the one we've got in
177 172
  # the database for this vendor.
178
  $ibanmessage = $iban ne $vendor->iban ? "Record IBAN $iban doesn't match vendor IBAN " . $vendor->iban : $iban if $iban;
173
 if ($iban) {
174
   $notes .= "\nIBAN: ";
175
   $notes .= $iban ne $vendor->iban ?
176
         t8("Record IBAN #1 doesn't match vendor IBAN #2", $iban, $vendor->iban)
177
       : $iban
178
 }
179 179

  
180 180
  # save the zugferd file to session file for reuse in ap.pl
181 181
  my $session_file = SL::SessionFile->new($file_name, mode => 'w');
......
213 213
    name => $metadata{'currency'},
214 214
    );
215 215

  
216
  my $default_ap_amount_chart = SL::DB::Manager::Chart->find_by(
217
    id => $::instance_conf->get_expense_accno_id
218
  );
219
  # Fallback if there's no default AP amount chart configured
220
  $default_ap_amount_chart ||= SL::DB::Manager::Chart->find_by(charttype => 'A');
221

  
222
  my $active_taxkey = $default_ap_amount_chart->taxkey_id;
223
  my $taxes = SL::DB::Manager::Tax->get_all(
224
    where   => [ chart_categories => {
225
        like => '%' . $default_ap_amount_chart->category . '%'
226
      }],
227
    sort_by => 'taxkey, rate',
228
  );
229
  die t8(
230
    "No tax found for chart #1", $default_ap_amount_chart->displayable_name
231
  ) unless scalar @{$taxes};
232

  
233
  # parse items
234
  my $row = 0;
235
  my %item_form = ();
236
  foreach my $i (@items) {
237
    $row++;
238

  
239
    my %item = %{$i};
240

  
241
    my $net_total = $::form->format_amount(\%::myconfig, $item{'subtotal'}, 2);
242

  
243
    my $tax_rate = $item{'tax_rate'};
244
    $tax_rate /= 100 if $tax_rate > 1; # XML data is usually in percent
245

  
246
    my $tax   = first { $tax_rate              == $_->rate } @{ $taxes };
247
    $tax    //= first { $active_taxkey->tax_id == $_->id }   @{ $taxes };
248
    $tax    //= $taxes->[0];
249

  
250
    $item_form{"AP_amount_chart_id_${row}"}          = $default_ap_amount_chart->id;
251
    $item_form{"previous_AP_amount_chart_id_${row}"} = $default_ap_amount_chart->id;
252
    $item_form{"amount_${row}"}                      = $net_total;
253
    $item_form{"taxchart_${row}"}                    = $tax->id . '--' . $tax->rate;
254
  }
255
  $item_form{rowcount} = $row;
256

  
216 257
  $self->redirect_to(
217
    controller                           => 'ap.pl',
218
    action                               => 'load_zugferd',
219
    'form_defaults.no_payment_bookings'  => 0,
220
    'form_defaults.paid_1_suggestion'    => $::form->format_amount(\%::myconfig, $metadata{'total'}, 2),
221
    'form_defaults.invnumber'            => $invnumber,
222
    'form_defaults.AP_chart_id'          => $ap_chart_id,
223
    'form_defaults.currency'             => $currency->name,
224
    'form_defaults.duedate'              => $metadata{'duedate'},
225
    'form_defaults.transdate'            => $metadata{'transdate'},
226
    'form_defaults.notes'                => "ZUGFeRD Import. Type: $metadata{'type'}\nIBAN: " . $ibanmessage,
227
    'form_defaults.taxincluded'          => 0,
228
    'form_defaults.direct_debit'         => $metadata{'direct_debit'},
229
    'form_defaults.vendor'               => $vendor->name,
230
    'form_defaults.vendor_id'            => $vendor->id,
231
    'form_defaults.zugferd_session_file' => $file_name,
258
    controller           => 'ap.pl',
259
    action               => 'load_zugferd',
260
    no_payment_bookings  => 0,
261
    paid_1_suggestion    => $::form->format_amount(\%::myconfig, $metadata{'total'}, 2),
262
    invnumber            => $invnumber,
263
    AP_chart_id          => $ap_chart_id,
264
    currency             => $currency->name,
265
    duedate              => $metadata{'duedate'},
266
    transdate            => $metadata{'transdate'},
267
    notes                => $notes,
268
    taxincluded          => 0,
269
    direct_debit         => $metadata{'direct_debit'},
270
    vendor               => $vendor->name,
271
    vendor_id            => $vendor->id,
272
    zugferd_session_file => $file_name,
273
    %item_form,
232 274
  );
233 275

  
234 276
}
bin/mozilla/ap.pl
114 114
sub load_zugferd {
115 115
  $::auth->assert('ap_transactions');
116 116

  
117
  my $data;    # buffer for holding file contents
118

  
119
  my $form_defaults = $::form->{form_defaults};
120
  my $file = SL::SessionFile->new($form_defaults->{zugferd_session_file}, mode => '<');
121
  my $file_name = $file->file_name;
122

  
123
  $::form->{$_} = $form_defaults->{$_} for keys %{ $form_defaults // {} };
117
  my $file_name = $::form->{zugferd_session_file};
124 118

  
125 119
  # Defaults
126
  $::form->{title}            = "Add";
127
  $::form->{paidaccounts}     = 1;
128

  
129
  $file->open('<');
130
  if ( ! defined($file->fh->read($data, -s $file->fh)) ) {
131
      SL::Helper::Flash::flash_later('error',
132
        t8('Could not open ZUGFeRD file for reading: #1', $!));
133
  } else {
134

  
135
      my %res;          # result data structure returned by SL::ZUGFeRD->extract_from_{pdf,xml}()
136
      my $parser;       # SL::XMLInvoice object created by SL::ZUGFeRD->extract_from_{pdf,xml}()
137
      my $template_ap;  # SL::DB::RecordTemplate object
138
      my $vendor;       # SL::DB::Vendor object
139
      my %metadata;     # structured data extracted from XML payload
140
      my @items;        # list of invoice items
141
      my $default_ap_amount_chart;
142

  
143
      if ( $data =~ m/^%PDF/ ) {
144
        %res = %{SL::ZUGFeRD->extract_from_pdf($data)};
145
      } else {
146
        %res = %{SL::ZUGFeRD->extract_from_xml($data)};
147
      }
148

  
149

  
150
      $parser = $res{'invoice_xml'};
151
      %metadata = %{$parser->metadata};
152
      @items = @{$parser->items};
153

  
154
      $default_ap_amount_chart = SL::DB::Manager::Chart->find_by(id => $::instance_conf->get_expense_accno_id);
155

  
156
      # Fallback if there's no default AP amount chart configured
157
      unless ( $default_ap_amount_chart ) {
158
        $default_ap_amount_chart = SL::DB::Manager::Chart->find_by(charttype => 'A');
159
      }
160

  
161
      my $row = 0;
162
      foreach my $i (@items) {
163
        $row++;
164

  
165
        my %item = %{$i};
120
  $::form->{title}      ||= "Add";
121
  $::form->{paidaccounts} = 1 if undef $::form->{paidaccounts};
166 122

  
167
        my $net_total = $::form->format_amount(\%::myconfig, $item{'subtotal'}, 2);
168
        my $desc = $item{'description'};
169
        my $tax_rate = $item{'tax_rate'} / 100; # XML data is usually in percent
170

  
171
        my $active_taxkey = $default_ap_amount_chart->taxkey_id;
172
        my $taxes         = SL::DB::Manager::Tax->get_all(
173
          where   => [ chart_categories => { like => '%' . $default_ap_amount_chart->category . '%' }],
174
          sort_by => 'taxkey, rate',
175
        );
176

  
177
        my $tax   = first { $tax_rate          == $_->rate } @{ $taxes };
178
        $tax    //= first { $active_taxkey->tax_id == $_->id } @{ $taxes };
179
        $tax    //= $taxes->[0];
180

  
181
        # If we really can't find any tax definition (a simple rounding error may
182
        # be sufficient for that to happen), grab the first tax fitting the default
183
        # AP amount chart, just like the AP form would do it for manual entry.
184
        if ( scalar @{$taxes} == 0 ) {
185
          $taxes = SL::DB::Manager::Tax->get_all(
186
            where   => [ chart_categories => { like => '%' . $default_ap_amount_chart->category . '%' } ],
187
          );
188
        }
189

  
190
        if (!$tax) {
191
          $row--;
192
          next;
193
        }
194

  
195
        $::form->{"AP_amount_chart_id_${row}"}          = $default_ap_amount_chart->id;
196
        $::form->{"previous_AP_amount_chart_id_${row}"} = $default_ap_amount_chart->id;
197
        $::form->{"amount_${row}"}                      = $net_total;
198
        $::form->{"taxchart_${row}"}                    = $tax->id . '--' . $tax->rate;
199
      }
200

  
201
      flash('info', $::locale->text("The ZUGFeRD/Factur-X invoice '#1' has been loaded.", $file_name));
202

  
203
      $::form->{form_validity_token} = SL::DB::ValidityToken->create(scope => SL::DB::ValidityToken::SCOPE_PURCHASE_INVOICE_POST())->token;
204
      $::form->{rowcount}         = $row;
205

  
206
      update(
207
        keep_rows_without_amount => 1,
208
        dont_add_new_row         => 1,
209
      );
210
    }
123
  $::form->{form_validity_token} = SL::DB::ValidityToken->create(scope => SL::DB::ValidityToken::SCOPE_PURCHASE_INVOICE_POST())->token;
124
  flash('info', $::locale->text(
125
      "The ZUGFeRD/Factur-X invoice '#1' has been loaded.", $file_name));
126
  update(
127
    keep_rows_without_amount => 1,
128
    dont_add_new_row         => 1,
129
  );
211 130
}
212 131

  
213 132
sub load_record_template {
locale/de/all
848 848
  'Could not load this vendor'  => 'Konnte diesen Lieferanten nicht laden',
849 849
  'Could not open ZUGFeRD file for reading: #1' => 'Die ZUGFeRD Datei konnte nicht zum Lesen geöffnet werden: #1',
850 850
  'Could not parse PDF embedded attachment #1: #2' => 'Konnte PDF-Anhang #1 nicht verarbeiten: #2',
851
  'Could not parse XML Invoice: unknown root node name (#1) (supported: (#2))' => 'Konnte XML-Rechnung nicht verabeiten: unbekanntes Wurzelelement (unterstützt: #2)',
851
  'Could not parse XML Invoice: unknown XML invoice type\nsupported: #1' => 'Konnte XML Rechnung nicht parsen: unbekannter XML Rechnungstyp\nunterstützt: #1',
852 852
  'Could not print dunning.'    => 'Die Mahnungen konnten nicht gedruckt werden.',
853 853
  'Could not reconcile chosen elements!' => 'Die gewählten Elemente konnten nicht ausgeglichen werden!',
854 854
  'Could not spawn ghostscript.' => 'Die Anwendung "ghostscript" konnte nicht gestartet werden.',
......
1716 1716
  'Factur-X/ZUGFeRD invoice'    => 'Factur-X-/ZUGFeRD-Rechnung',
1717 1717
  'Factur-X/ZUGFeRD notes for each invoice' => 'Factur-X-/ZUGFeRD-Notizen für jede Rechnung',
1718 1718
  'Factur-X/ZUGFeRD settings'   => 'Factur-X-/ZUGFeRD-Einstellungen',
1719
  'Faktur-X/ZUGFeRD/XRechnung import #1, #2' => 'Faktur-X/ZUGFeRD/XRechnung-Import #1, #2',
1720 1719
  'Fax'                         => 'Fax',
1721 1720
  'Features'                    => 'Features',
1722 1721
  'Feb'                         => 'Feb',
......
2453 2452
  'No Order items fetched'      => 'Keine Auftragspositionen gefunden',
2454 2453
  'No Shopdescription'          => 'Keine Shop-Artikelbeschreibung',
2455 2454
  'No Shopimages'               => 'Keine Shop-Bilder',
2455
  'No VAT Info for this Factur-X/ZUGFeRD invoice, please ask your vendor to add this for his Factur-X/ZUGFeRD data.' => 'Keine USt-Info für diese Factur-X/ZUGFeRD Rechnung, bitte fragen Sie Ihren Lieferanten, diese für seine Factur-X/ZUGFeRD Daten hinzuzufügen.',
2456 2456
  'No Vendor'                   => 'Kein Lieferant',
2457 2457
  'No Vendor was found matching the search parameters.' => 'Zu dem Suchbegriff wurde kein Händler gefunden',
2458 2458
  'No account selected. Please select an account.' => 'Kein Konto ausgewählt. Bitte Konto auswählen.',
......
2536 2536
  'No such job #1 in the database.' => 'Hintergrund-Job #1 existiert nicht mehr.',
2537 2537
  'No summary account'          => 'Kein Sammelkonto',
2538 2538
  'No superuser credentials were entered.' => 'Es wurden keine Super-Benutzer-Anmeldedaten eingegeben.',
2539
  'No tax found for chart #1'   => 'Keine Steuer für Konto #1 gefunden',
2539 2540
  'No template has been selected yet.' => 'Es wurde noch keine Vorlage ausgewählt.',
2540 2541
  'No text blocks have been created for this position.' => 'Für diese Position wurden noch keine Textblöcke angelegt.',
2541 2542
  'No text has been entered yet.' => 'Es wurde noch kein Text eingegeben.',
......
2847 2848
  'Pictures for search parts'   => 'Bilder für Warensuche',
2848 2849
  'Please Check the bank information for each customer:' => 'Bitte überprüfen Sie die Bankinformationen der Kunden:',
2849 2850
  'Please Check the bank information for each vendor:' => 'Bitte überprüfen Sie die Kontoinformationen der Lieferanten:',
2851
  'Please add a valid VAT-ID for this vendor: #1' => 'Bitte fügen Sie eine gültige USt-ID für diesen Lieferanten hinzu: #1',
2850 2852
  'Please ask your administrator to create warehouses and bins.' => 'Bitten Sie Ihren Administrator, dass er Lager und Lagerplätze anlegt.',
2851 2853
  'Please change the partnumber of the following parts and run the update again:' => 'Bitte ändern Sie daher die Artikelnummer folgender Artikel:',
2852 2854
  'Please choose a part.'       => 'Bitte wählen Sie einen Artikel aus.',
......
3165 3167
  'Reconcile'                   => 'Abgleichen',
3166 3168
  'Reconciliation'              => 'Kontenabgleich',
3167 3169
  'Reconciliation with bank'    => 'Kontenabgleich mit Bank',
3170
  'Record IBAN #1 doesn\'t match vendor IBAN #2' => 'Beleg IBAN #1 stimmt nicht mit Lieferanten IBAN #2 überein',
3168 3171
  'Record Type'                 => 'Belegtyp',
3169 3172
  'Record Vendor Invoice'       => 'Einkaufsrechnung erfassen',
3170 3173
  'Record in'                   => 'Buchen auf',
......
3911 3914
  'The WebDAV feature has been used.' => 'Das WebDAV-Feature wurde benutzt.',
3912 3915
  'The XMP metadata does not declare the Factur-X/ZUGFeRD data.' => 'Die XMP-Metadaten enthalten keine Factur-X-/ZUGFeRD-Deklaration.',
3913 3916
  'The ZUGFeRD invoice data cannot be generated because the data validation failed.' => 'Die ZUGFeRD-Rechnungsdaten können nicht erzeugt werden, da die Validierung fehlschlug.',
3917
  'The ZUGFeRD/Factur-X invoice \'#1\' has been loaded.' => 'Die ZUGFeRD/Factur-X Rechnung \'#1\' wurde geladen.',
3914 3918
  'The abbreviation is missing.' => 'Abkürzung fehlt',
3915 3919
  'The access rights a user has within a client instance is still governed by his group membership.' => 'Welche Zugriffsrechte ein Benutzer innerhalb eines Mandanten hat, wird weiterhin über Gruppenmitgliedschaften geregelt.',
3916 3920
  'The access rights have been saved.' => 'Die Zugriffsrechte wurden gespeichert.',
......
4860 4864
  'Your import is being processed.' => 'Ihr Import wird verarbeitet',
4861 4865
  'Your target quantity will be added to the stocked quantity.' => 'Ihre gezählte Zielmenge wird zum Lagerbestand hinzugezählt.',
4862 4866
  'ZIPcode'                     => 'PLZ',
4867
  'ZUGFeRD Import. Type: #1'    => 'ZUGFeRD Import. Typ: #1',
4863 4868
  'Zeitraum'                    => 'Zeitraum',
4864 4869
  'Zero amount posting!'        => 'Buchung ohne Wert',
4865 4870
  'Zip'                         => 'PLZ',
locale/en/all
673 673
  'Cannot post transaction with a debit and credit entry for the same account!' => '',
674 674
  'Cannot post transaction!'    => '',
675 675
  'Cannot process payment for a closed period!' => '',
676
  'Cannot process this invoice: neither VAT ID nor tax ID present.' => '',
676 677
  'Cannot remove files!'        => '',
677 678
  'Cannot revert a versioned copy.' => '',
678 679
  'Cannot safely book imported bank transactions due to lax posting settings for payments' => '',
......
846 847
  'Could not load this draft'   => '',
847 848
  'Could not load this vendor'  => '',
848 849
  'Could not open ZUGFeRD file for reading: #1' => '',
850
  'Could not parse PDF embedded attachment #1: #2' => '',
851
  'Could not parse XML Invoice: unknown XML invoice type\nsupported: #1' => '',
849 852
  'Could not print dunning.'    => '',
850 853
  'Could not reconcile chosen elements!' => '',
851 854
  'Could not spawn ghostscript.' => '',
......
2436 2439
  'Next run at'                 => '',
2437 2440
  'No'                          => '',
2438 2441
  'No 1:n or n:1 relation'      => '',
2439
  'No AP Record Template for this vendor found, please add one' => '',
2440 2442
  'No AP Record Template for vendor #1 found, please add one' => '',
2441 2443
  'No AP template was found.'   => '',
2442 2444
  'No Billing and ship to address, for Order Number #1 with ID Billing #2 and ID Shipping #3' => '',
......
2533 2535
  'No such job #1 in the database.' => '',
2534 2536
  'No summary account'          => '',
2535 2537
  'No superuser credentials were entered.' => '',
2538
  'No tax found for chart #1'   => '',
2536 2539
  'No template has been selected yet.' => '',
2537 2540
  'No text blocks have been created for this position.' => '',
2538 2541
  'No text has been entered yet.' => '',
......
2738 2741
  'Paid'                        => '',
2739 2742
  'Paid amount'                 => '',
2740 2743
  'Parameter module must be given.' => '',
2744
  'Parsing the XML data failed: #1' => '',
2741 2745
  'Parsing the XMP metadata failed.' => '',
2742 2746
  'Part'                        => '',
2743 2747
  'Part "#1" has chargenumber or best before date set. So it cannot be transfered automatically.' => '',
......
3162 3166
  'Reconcile'                   => '',
3163 3167
  'Reconciliation'              => '',
3164 3168
  'Reconciliation with bank'    => '',
3169
  'Record IBAN #1 doesn\'t match vendor IBAN #2' => '',
3165 3170
  'Record Type'                 => '',
3166 3171
  'Record Vendor Invoice'       => '',
3167 3172
  'Record in'                   => '',
......
3880 3885
  'The AR transaction #1 has been deleted.' => '',
3881 3886
  'The Bins in Inventory were only a information text field.' => '',
3882 3887
  'The Bins in master data were only a information text field.' => '',
3883
  'The Factur-X/ZUGFeRD XML invoice was not found.' => '',
3884 3888
  'The Factur-X/ZUGFeRD notes have been saved.' => '',
3885 3889
  'The Factur-X/ZUGFeRD version used is not supported.' => '',
3886 3890
  'The GL transaction #1 has been deleted.' => '',
......
3908 3912
  'The WebDAV feature has been used.' => '',
3909 3913
  'The XMP metadata does not declare the Factur-X/ZUGFeRD data.' => '',
3910 3914
  'The ZUGFeRD invoice data cannot be generated because the data validation failed.' => '',
3915
  'The ZUGFeRD/Factur-X invoice \'#1\' has been loaded.' => '',
3911 3916
  'The abbreviation is missing.' => '',
3912 3917
  'The access rights a user has within a client instance is still governed by his group membership.' => '',
3913 3918
  'The access rights have been saved.' => '',
......
4690 4695
  'Vendor saved'                => '',
4691 4696
  'Vendor saved!'               => '',
4692 4697
  'Vendor type'                 => '',
4698
  'Vendor with VAT ID (#1) and/or tax ID (#2) not found. Please check if the vendor #3 exists and whether it has the correct tax ID/VAT ID.' => '',
4693 4699
  'Vendors'                     => '',
4694 4700
  'Vendors: VAT ID / taxnumber unique' => '',
4695 4701
  'Verrechnungseinheit'         => '',
......
4856 4862
  'Your import is being processed.' => '',
4857 4863
  'Your target quantity will be added to the stocked quantity.' => '',
4858 4864
  'ZIPcode'                     => '',
4865
  'ZUGFeRD Import. Type: #1'    => '',
4859 4866
  'Zeitraum'                    => '',
4860 4867
  'Zero amount posting!'        => '',
4861 4868
  'Zip'                         => '',
......
4904 4911
  'by'                          => '',
4905 4912
  'can not allocate #1 units of #2, missing #3 units' => '',
4906 4913
  'can not allocate enough resources for production' => '',
4907
  'can only parse a pdf file'   => '',
4914
  'can only parse a pdf or xml file' => '',
4908 4915
  'cash'                        => '',
4909 4916
  'chargenumber #1'             => '',
4910 4917
  'chart_of_accounts'           => '',

Auch abrufbar als: Unified diff