Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision ffe592ca

Von Tamino Steinert vor etwa 1 Jahr hinzugefügt

  • ID ffe592caa678432509bbd480f4372b5dd628fd5f
  • Vorgänger 273ec927
  • Nachfolger 7bbb4f9a

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
113 113
sub load_zugferd {
114 114
  $::auth->assert('ap_transactions');
115 115

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

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

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

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

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

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

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

  
148

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  
212 131
sub load_record_template {
locale/de/all
2444 2444
  'No Order items fetched'      => 'Keine Auftragspositionen gefunden',
2445 2445
  'No Shopdescription'          => 'Keine Shop-Artikelbeschreibung',
2446 2446
  'No Shopimages'               => 'Keine Shop-Bilder',
2447
  '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.',
2447 2448
  'No Vendor'                   => 'Kein Lieferant',
2448 2449
  'No Vendor was found matching the search parameters.' => 'Zu dem Suchbegriff wurde kein Händler gefunden',
2449 2450
  'No account selected. Please select an account.' => 'Kein Konto ausgewählt. Bitte Konto auswählen.',
......
2523 2524
  'No such job #1 in the database.' => 'Hintergrund-Job #1 existiert nicht mehr.',
2524 2525
  'No summary account'          => 'Kein Sammelkonto',
2525 2526
  'No superuser credentials were entered.' => 'Es wurden keine Super-Benutzer-Anmeldedaten eingegeben.',
2527
  'No tax found for chart #1'   => 'Keine Steuer für Konto #1 gefunden',
2526 2528
  'No template has been selected yet.' => 'Es wurde noch keine Vorlage ausgewählt.',
2527 2529
  'No text blocks have been created for this position.' => 'Für diese Position wurden noch keine Textblöcke angelegt.',
2528 2530
  'No text has been entered yet.' => 'Es wurde noch kein Text eingegeben.',
......
2834 2836
  'Pictures for search parts'   => 'Bilder für Warensuche',
2835 2837
  'Please Check the bank information for each customer:' => 'Bitte überprüfen Sie die Bankinformationen der Kunden:',
2836 2838
  'Please Check the bank information for each vendor:' => 'Bitte überprüfen Sie die Kontoinformationen der Lieferanten:',
2839
  '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',
2837 2840
  'Please ask your administrator to create warehouses and bins.' => 'Bitten Sie Ihren Administrator, dass er Lager und Lagerplätze anlegt.',
2838 2841
  'Please change the partnumber of the following parts and run the update again:' => 'Bitte ändern Sie daher die Artikelnummer folgender Artikel:',
2839 2842
  'Please choose a part.'       => 'Bitte wählen Sie einen Artikel aus.',
......
3154 3157
  'Reconcile'                   => 'Abgleichen',
3155 3158
  'Reconciliation'              => 'Kontenabgleich',
3156 3159
  'Reconciliation with bank'    => 'Kontenabgleich mit Bank',
3160
  'Record IBAN #1 doesn\'t match vendor IBAN #2' => 'Beleg IBAN #1 stimmt nicht mit Lieferanten IBAN #2 überein',
3157 3161
  'Record Type'                 => 'Belegtyp',
3158 3162
  'Record Vendor Invoice'       => 'Einkaufsrechnung erfassen',
3159 3163
  'Record in'                   => 'Buchen auf',
......
4846 4850
  'Your import is being processed.' => 'Ihr Import wird verarbeitet',
4847 4851
  'Your target quantity will be added to the stocked quantity.' => 'Ihre gezählte Zielmenge wird zum Lagerbestand hinzugezählt.',
4848 4852
  'ZIPcode'                     => 'PLZ',
4853
  'ZUGFeRD Import. Type: #1'    => 'ZUGFeRD Import. Typ: #1',
4849 4854
  'Zeitraum'                    => 'Zeitraum',
4850 4855
  'Zero amount posting!'        => 'Buchung ohne Wert',
4851 4856
  'Zip'                         => 'PLZ',
locale/en/all
2432 2432
  'Next run at'                 => '',
2433 2433
  'No'                          => '',
2434 2434
  'No 1:n or n:1 relation'      => '',
2435
  'No AP Record Template for vendor #1 found, please add one' => '',
2435 2436
  'No AP template was found.'   => '',
2436 2437
  'No Billing and ship to address, for Order Number #1 with ID Billing #2 and ID Shipping #3' => '',
2437 2438
  'No Company Address given'    => '',
......
2522 2523
  'No such job #1 in the database.' => '',
2523 2524
  'No summary account'          => '',
2524 2525
  'No superuser credentials were entered.' => '',
2526
  'No tax found for chart #1'   => '',
2525 2527
  'No template has been selected yet.' => '',
2526 2528
  'No text blocks have been created for this position.' => '',
2527 2529
  'No text has been entered yet.' => '',
......
3153 3155
  'Reconcile'                   => '',
3154 3156
  'Reconciliation'              => '',
3155 3157
  'Reconciliation with bank'    => '',
3158
  'Record IBAN #1 doesn\'t match vendor IBAN #2' => '',
3156 3159
  'Record Type'                 => '',
3157 3160
  'Record Vendor Invoice'       => '',
3158 3161
  'Record in'                   => '',
......
4844 4847
  'Your import is being processed.' => '',
4845 4848
  'Your target quantity will be added to the stocked quantity.' => '',
4846 4849
  'ZIPcode'                     => '',
4850
  'ZUGFeRD Import. Type: #1'    => '',
4847 4851
  'Zeitraum'                    => '',
4848 4852
  'Zero amount posting!'        => '',
4849 4853
  'Zip'                         => '',

Auch abrufbar als: Unified diff