Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision 16dcabdf

Von Bernd Bleßmann vor mehr als 1 Jahr hinzugefügt

  • ID 16dcabdf324312e6d3a38c3b505d2ed254161fa9
  • Vorgänger 79629ddd
  • Nachfolger 74b53ff2

CSV-Import Kreditorenbuchungen

Kopie und Anpassung vom Debitorenbuchungsimport

Unterschiede anzeigen:

SL/Controller/CsvImport.pm
23 23
use SL::Controller::CsvImport::Order;
24 24
use SL::Controller::CsvImport::DeliveryOrder;
25 25
use SL::Controller::CsvImport::ARTransaction;
26
use SL::Controller::CsvImport::APTransaction;
26 27
use SL::JSON;
27 28
use SL::Controller::CsvImport::BankTransaction;
28 29
use SL::BackgroundJob::CsvImport;
......
312 313
sub check_type {
313 314
  my ($self) = @_;
314 315

  
315
  die "Invalid CSV import type" if none { $_ eq $::form->{profile}->{type} } qw(parts inventories customers_vendors billing_addresses addresses contacts projects orders delivery_orders bank_transactions ar_transactions);
316
  die "Invalid CSV import type" if none { $_ eq $::form->{profile}->{type} } qw(parts inventories customers_vendors billing_addresses addresses contacts projects orders delivery_orders bank_transactions ar_transactions ap_transactions);
316 317
  $self->type($::form->{profile}->{type});
317 318
}
318 319

  
......
363 364
            : $self->type eq 'delivery_orders'   ? $::locale->text('CSV import: delivery orders')
364 365
            : $self->type eq 'bank_transactions' ? $::locale->text('CSV import: bank transactions')
365 366
            : $self->type eq 'ar_transactions'   ? $::locale->text('CSV import: ar transactions')
367
            : $self->type eq 'ap_transactions'   ? $::locale->text('CSV import: ap transactions')
366 368
            : die;
367 369

  
368
  if ( any { $_ eq $self->{type} } qw(customers_vendors orders delivery_orders ar_transactions) ) {
370
  if ( any { $_ eq $self->{type} } qw(customers_vendors orders delivery_orders ar_transactions ap_transactions) ) {
369 371
    $self->all_taxzones(SL::DB::Manager::TaxZone->get_all_sorted(query => [ obsolete => 0 ]));
370 372
  };
371 373

  
......
514 516
    $::form->{settings}->{sellprice_adjustment} = $::form->parse_amount(\%::myconfig, $::form->{settings}->{sellprice_adjustment});
515 517
  }
516 518

  
517
  if ($self->type eq 'orders' or $self->{type} eq 'ar_transactions') {
519
  if ( any { $_ eq $self->{type} } qw(orders ar_transactions ap_transactions) ) {
518 520
    $::form->{settings}->{max_amount_diff} = $::form->parse_amount(\%::myconfig, $::form->{settings}->{max_amount_diff});
519 521
  }
520 522

  
......
735 737
       : $self->{type} eq 'delivery_orders'   ? SL::Controller::CsvImport::DeliveryOrder->new(@args)
736 738
       : $self->{type} eq 'bank_transactions' ? SL::Controller::CsvImport::BankTransaction->new(@args)
737 739
       : $self->{type} eq 'ar_transactions'   ? SL::Controller::CsvImport::ARTransaction->new(@args)
740
       : $self->{type} eq 'ap_transactions'   ? SL::Controller::CsvImport::APTransaction->new(@args)
738 741
       :                                        die "Program logic error";
739 742
}
740 743

  
SL/Controller/CsvImport/APTransaction.pm
1
package SL::Controller::CsvImport::APTransaction;
2

  
3
use strict;
4

  
5
use List::MoreUtils qw(any);
6

  
7
use SL::Helper::Csv;
8
use SL::Controller::CsvImport::Helper::Consistency;
9
use SL::DB::PurchaseInvoice;
10
use SL::DB::AccTransaction;
11
use SL::DB::Department;
12
use SL::DB::Project;
13
use SL::DB::TaxZone;
14
use SL::DB::Chart;
15
use SL::TransNumber;
16
use DateTime;
17

  
18
use parent qw(SL::Controller::CsvImport::BaseMulti);
19

  
20
use Rose::Object::MakeMethods::Generic
21
(
22
 'scalar --get_set_init' => [ qw(settings charts_by taxkeys_by) ],
23
);
24

  
25

  
26
sub init_class {
27
  my ($self) = @_;
28
  $self->class(['SL::DB::PurchaseInvoice', 'SL::DB::AccTransaction']);
29
}
30

  
31
sub set_profile_defaults {
32
  my ($self) = @_;
33

  
34
  $self->controller->profile->_set_defaults(
35
                       ap_column          => $::locale->text('Invoice'),
36
                       transaction_column => $::locale->text('AccTransaction'),
37
                       max_amount_diff    => 0.02,
38
                      );
39
};
40

  
41

  
42
sub init_settings {
43
  my ($self) = @_;
44

  
45
  return { map { ( $_ => $self->controller->profile->get($_) ) } qw(ap_column transaction_column max_amount_diff) };
46
}
47

  
48
sub init_profile {
49
  my ($self) = @_;
50

  
51
  my $profile = $self->SUPER::init_profile;
52

  
53
  # SUPER::init_profile sets row_ident to the translated class name
54
  # overwrite it with the user specified settings
55
  foreach my $p (@{ $profile }) {
56
    if ($p->{class} eq 'SL::DB::PurchaseInvoice') {
57
      $p->{row_ident} = $self->_ap_column;
58
    }
59
    if ($p->{class} eq 'SL::DB::AccTransaction') {
60
      $p->{row_ident} = $self->_transaction_column;
61
    }
62
  }
63

  
64
  foreach my $p (@{ $profile }) {
65
    my $prof = $p->{profile};
66
    if ($p->{row_ident} eq $self->_ap_column) {
67
      # no need to handle
68
      delete @{$prof}{qw(amount cp_id datepaid delivery_term_id gldate invoice language_id netamount paid shipvia storno storno_id taxzone taxzone_id type)};
69
    }
70
    if ($p->{row_ident} eq $self->_transaction_column) {
71
      # no need to handle
72
      delete @{$prof}{qw(acc_trans_id cb_transaction chart_link cleared fx_transaction gldate memo ob_transaction source tax_id description trans_id transdate)};
73
    }
74
  }
75

  
76
  return $profile;
77
}
78

  
79

  
80
sub setup_displayable_columns {
81
  my ($self) = @_;
82

  
83
  $self->SUPER::setup_displayable_columns;
84

  
85
  $self->add_displayable_columns($self->_ap_column,
86
                                 { name => 'datatype',                description => $self->_ap_column . ' [1]'                                },
87
                                 { name => 'currency_id',             description => $::locale->text('Currency (database ID)')                 },
88
                                 { name => 'currency',                description => $::locale->text('Currency')                               },
89
                                 { name => 'deliverydate',            description => $::locale->text('Delivery Date')                          },
90
                                 { name => 'department_id',           description => $::locale->text('Department (database ID)')               },
91
                                 { name => 'department',              description => $::locale->text('Department (description)')               },
92
                                 { name => 'direct_debit',            description => $::locale->text('direct debit')                           },
93
                                 { name => 'duedate',                 description => $::locale->text('Due Date')                               },
94
                                 { name => 'employee_id',             description => $::locale->text('Employee (database ID)')                 },
95
                                 { name => 'exchangerate',            description => $::locale->text('Exchangerate')                           },
96
                                 { name => 'globalproject_id',        description => $::locale->text('Document Project (database ID)')         },
97
                                 { name => 'globalprojectnumber',     description => $::locale->text('Document Project (number)')              },
98
                                 { name => 'globalproject',           description => $::locale->text('Document Project (description)')         },
99
                                 { name => 'intnotes',                description => $::locale->text('Internal Notes')                         },
100
                                 { name => 'invnumber',               description => $::locale->text('Invoice Number')                         },
101
                                 { name => 'is_sepa_blocked',         description => $::locale->text('Bank transfer via SEPA is blocked')      },
102
                                 { name => 'notes',                   description => $::locale->text('Notes')                                  },
103
                                 { name => 'orddate',                 description => $::locale->text('Order Date')                             },
104
                                 { name => 'ordnumber',               description => $::locale->text('Order Number')                           },
105
                                 { name => 'payment_id',              description => $::locale->text('Payment terms (database ID)')            },
106
                                 { name => 'payment',                 description => $::locale->text('Payment terms (name)')                   },
107
                                 { name => 'quonumber',               description => $::locale->text('Quotation Number')                       },
108
                                 { name => 'quodate',                 description => $::locale->text('Quotation Date')                         },
109
                                 { name => 'tax_point',               description => $::locale->text('Tax point')                              },
110
                                 { name => 'taxincluded',             description => $::locale->text('Tax Included')                           },
111
                                 { name => 'transaction_description', description => $::locale->text('Transaction description')                },
112
                                 { name => 'transdate',               description => $::locale->text('Invoice Date')                           },
113
                                 { name => 'vendor',                  description => $::locale->text('Vendor (name)')                          },
114
                                 { name => 'vendornumber',            description => $::locale->text('Vendor Number')                          },
115
                                 { name => 'vendor_gln',              description => $::locale->text('Vendor GLN')                             },
116
                                 { name => 'vendor_id',               description => $::locale->text('Vendor (database ID)')                   },
117
                                 { name => 'verify_amount',           description => $::locale->text('Amount (for verification)') . ' [2]'     },
118
                                 { name => 'verify_netamount',        description => $::locale->text('Net amount (for verification)') . ' [2]' },
119
                                 { name => 'apchart',                 description => $::locale->text('Payable account (account number)')       },
120
  );
121

  
122
  $self->add_displayable_columns($self->_transaction_column,
123
                                 { name => 'datatype',      description => $self->_transaction_column . ' [1]'             },
124
                                 { name => 'amount',        description => $::locale->text('Amount')                       },
125
                                 { name => 'chart_id',      description => $::locale->text('Account number (database ID)') },
126
                                 { name => 'project_id',    description => $::locale->text('Project (database ID)')        },
127
                                 { name => 'project',       description => $::locale->text('Project (description)')        },
128
                                 { name => 'projectnumber', description => $::locale->text('Project (number)')             },
129
                                 { name => 'taxkey',        description => $::locale->text('Taxkey')                       },
130
                                 { name => 'accno',         description => $::locale->text('Account number')               },
131
                                );
132
}
133

  
134
sub init_taxkeys_by {
135
  my ($self) = @_;
136

  
137
  my $all_taxes = SL::DB::Manager::Tax->get_all;
138
  return { map { $_->taxkey => $_->id } @{ $all_taxes } };
139
}
140

  
141

  
142
sub init_charts_by {
143
  my ($self) = @_;
144

  
145
  my $all_charts = SL::DB::Manager::Chart->get_all;
146
  return { map { my $col = $_; ( $col => { map { ( $_->$col => $_ ) } @{ $all_charts } } ) } qw(id accno) };
147
}
148

  
149
sub check_objects {
150
  my ($self) = @_;
151

  
152
  $self->controller->track_progress(phase => 'building data', progress => 0);
153

  
154
  my $i = 0;
155
  my $num_data = scalar @{ $self->controller->data };
156
  my $invoice_entry;
157

  
158
  foreach my $entry (@{ $self->controller->data }) {
159
    $self->controller->track_progress(progress => $i/$num_data * 100) if $i % 100 == 0;
160

  
161
    if ($entry->{raw_data}->{datatype} eq $self->_ap_column) {
162
      $self->handle_invoice($entry);
163
      $invoice_entry = $entry;
164
    } elsif ($entry->{raw_data}->{datatype} eq $self->_transaction_column ) {
165
      die "Cannot process transaction row without an invoice row" if !$invoice_entry;
166
      $self->handle_transaction($entry, $invoice_entry);
167
    } else {
168
      die "unknown datatype";
169
    };
170

  
171
  } continue {
172
    $i++;
173
  } # finished data parsing
174

  
175
  $self->add_transactions_to_ap(); # go through all data entries again, adding payable entry to ap lines while calculating amount and netamount
176

  
177
  foreach my $entry (@{ $self->controller->data }) {
178
    next unless ($entry->{raw_data}->{datatype} eq $self->_ap_column);
179
    $self->check_verify_amounts($entry->{object});
180
  };
181

  
182
  foreach my $entry (@{ $self->controller->data }) {
183
    next unless ($entry->{raw_data}->{datatype} eq $self->_ap_column);
184
    unless ( $entry->{object}->validate_acc_trans ) {
185
      push @{ $entry->{errors} }, $::locale->text('Error: ap transaction doesn\'t validate');
186
    };
187
  };
188

  
189
  # add info columns that aren't directly part of the object to be imported
190
  # but are always determined or should always be shown because they are mandatory
191
  $self->add_info_columns($self->_ap_column,
192
                          { header => $::locale->text('Vendor'),              method => 'vc_name'   },
193
                          { header => $::locale->text('Payable account'),     method => 'apchart'   },
194
                          { header => $::locale->text('Amount'),              method => 'amount'    },
195
                          { header => $::locale->text('Net amount'),          method => 'netamount' },
196
                          { header => $::locale->text('Tax zone'),            method => 'taxzone'   });
197

  
198
  # Adding info_header this way only works, if the first invoice $self->controller->data->[0]
199

  
200
  # Todo: access via ->[0] ok? Better: search first order column and use this
201
  $self->add_info_columns($self->_ap_column, { header => $::locale->text('Department'),    method => 'department' }) if $self->controller->data->[0]->{info_data}->{department} or $self->controller->data->[0]->{raw_data}->{department};
202

  
203
  $self->add_info_columns($self->_ap_column, { header => $::locale->text('Project Number'), method => 'globalprojectnumber' }) if $self->controller->data->[0]->{info_data}->{globalprojectnumber};
204

  
205
  $self->add_columns($self->_ap_column,
206
                     map { "${_}_id" } grep { exists $self->controller->data->[0]->{raw_data}->{$_} } qw(payment department globalproject taxzone currency));
207
  $self->add_columns($self->_ap_column, 'globalproject_id') if exists $self->controller->data->[0]->{raw_data}->{globalprojectnumber};
208
  $self->add_columns($self->_ap_column, 'notes')            if exists $self->controller->data->[0]->{raw_data}->{notes};
209

  
210
  # Todo: access via ->[1] ok? Better: search first item column and use this
211
  $self->add_info_columns($self->_transaction_column, { header => $::locale->text('Chart'), method => 'accno' });
212
  $self->add_columns($self->_transaction_column, 'amount');
213

  
214
  $self->add_info_columns($self->_transaction_column, { header => $::locale->text('Project Number'), method => 'projectnumber' }) if $self->controller->data->[1]->{info_data}->{projectnumber};
215

  
216
  # If invoice has errors, add error for acc_trans items
217
  # If acc_trans item has an error, add an error to the invoice item
218
  my $ap_entry;
219
  foreach my $entry (@{ $self->controller->data }) {
220
    # Search first order
221
    if ($entry->{raw_data}->{datatype} eq $self->_ap_column) {
222
      $ap_entry = $entry;
223
    } elsif ( defined $ap_entry
224
              && $entry->{raw_data}->{datatype} eq $self->_transaction_column
225
              && scalar @{ $ap_entry->{errors} } > 0 ) {
226
      push @{ $entry->{errors} }, $::locale->text('Error: invalid ap row for this transaction');
227
    } elsif ( defined $ap_entry
228
              && $entry->{raw_data}->{datatype} eq $self->_transaction_column
229
              && scalar @{ $entry->{errors} } > 0 ) {
230
      push @{ $ap_entry->{errors} }, $::locale->text('Error: invalid acc transactions for this ap row');
231
    }
232
  }
233
}
234

  
235
sub handle_invoice {
236

  
237
  my ($self, $entry) = @_;
238

  
239
  my $object = $entry->{object};
240

  
241
  $object->transactions( [] ); # initialise transactions for ap object so methods work on unsaved transactions
242

  
243
  my $vc_obj;
244
  if (any { $entry->{raw_data}->{$_} } qw(vendor vendornumber vendor_gln vendor_id)) {
245
    $self->check_vc($entry, 'vendor_id');
246
    $vc_obj = SL::DB::Vendor->new(id => $object->vendor_id)->load if $object->vendor_id;
247
  } else {
248
    push @{ $entry->{errors} }, $::locale->text('Error: Vendor missing');
249
  }
250

  
251
  $self->check_apchart($entry); # checks for payable account
252
  $self->check_payment($entry); # currency default from vendor used below
253
  $self->check_department($entry);
254
  $self->check_taxincluded($entry);
255
  $self->check_project($entry, global => 1);
256
  $self->check_taxzone($entry); # taxzone default from customer used below
257
  $self->check_currency($entry); # currency default from customer used below
258
  $self->handle_employee($entry);
259

  
260
  if ($vc_obj ) {
261
    # copy defaults from customer if not specified in import file
262
    foreach (qw(payment_id language_id taxzone_id currency_id)) {
263
      $object->$_($vc_obj->$_) unless $object->$_;
264
    }
265
  }
266
}
267

  
268
sub check_taxkey {
269
  my ($self, $entry, $invoice_entry, $chart) = @_;
270

  
271
  die "check_taxkey needs chart object as an argument" unless ref($chart) eq 'SL::DB::Chart';
272
  # problem: taxkey is not unique in table tax, normally one of those entries is chosen directly from a dropdown
273
  # so we check if the chart has an active taxkey, and if it matches the taxkey from the import, use the active taxkey
274
  # if the chart doesn't have an active taxkey, use the first entry from Tax that matches the taxkey
275

  
276
  my $object         = $entry->{object};
277
  my $invoice_object = $invoice_entry->{object};
278

  
279
  unless ( defined $entry->{raw_data}->{taxkey} ) {
280
    push @{ $entry->{errors} }, $::locale->text('Error: taxkey missing'); # don't just assume 0, force taxkey in import
281
    return 0;
282
  };
283

  
284
  my $tax = $chart->get_active_taxkey($invoice_object->deliverydate // $invoice_object->transdate // DateTime->today_local)->tax;
285
  if ( $entry->{raw_data}->{taxkey} != $tax->taxkey ) {
286
   # assume there is only one tax entry with that taxkey, can't guess
287
    $tax = SL::DB::Manager::Tax->get_first( where => [ taxkey => $entry->{raw_data}->{taxkey} ]);
288
  };
289

  
290
  unless ( $tax ) {
291
    push @{ $entry->{errors} }, $::locale->text('Error: invalid taxkey');
292
    return 0;
293
  };
294

  
295
  $object->taxkey($tax->taxkey);
296
  $object->tax_id($tax->id);
297
  return 1;
298
};
299

  
300
sub handle_transaction {
301
  my ($self, $entry, $invoice_entry) = @_;
302

  
303
  # Prepare acc_trans data. amount is dealt with in add_transactions_to_ap
304

  
305
  my $object = $entry->{object};
306

  
307
  $self->check_project($entry, global => 0);
308
  if ( $self->check_chart($entry) ) {
309
    my $chart_obj = SL::DB::Manager::Chart->find_by(id => $object->chart_id);
310

  
311
    unless ( $chart_obj->link =~ /AP_amount/ ) {
312
      push @{ $entry->{errors} }, $::locale->text('Error: chart isn\'t an ap_amount chart');
313
      return 0;
314
    };
315

  
316
    if ( $self->check_taxkey($entry, $invoice_entry, $chart_obj) ) {
317
      # do nothing, taxkey was assigned, just continue
318
    } else {
319
      # missing taxkey, don't do anything
320
      return 0;
321
    };
322
  } else {
323
    return 0;
324
  };
325

  
326
  # check whether taxkey and automatic taxkey match
327
  # die sprintf("taxkeys don't match: %s not equal default taxkey for chart %s: %s", $object->taxkey, $chart_obj->accno, $active_tax_for_chart->tax->taxkey) unless $object->taxkey == $active_tax_for_chart->tax->taxkey;
328

  
329
  die "no taxkey for transaction object" unless $object->taxkey or $object->taxkey == 0;
330

  
331
}
332

  
333
sub check_chart {
334
  my ($self, $entry) = @_;
335

  
336
  my $object = $entry->{object};
337

  
338
  if (any { $entry->{raw_data}->{$_} } qw(accno chart_id)) {
339

  
340
    # Check whether or not chart ID is valid.
341
    if ($object->chart_id && !$self->charts_by->{id}->{ $object->chart_id }) {
342
      push @{ $entry->{errors} }, $::locale->text('Error: invalid chart_id');
343
      return 0;
344
    }
345

  
346
    # Map number to ID if given.
347
    if (!$object->chart_id && $entry->{raw_data}->{accno}) {
348
      my $chart = $self->charts_by->{accno}->{ $entry->{raw_data}->{accno} };
349
      if (!$chart) {
350
        push @{ $entry->{errors} }, $::locale->text('Error: invalid chart (accno)');
351
        return 0;
352
      }
353

  
354
      $object->chart_id($chart->id);
355
    }
356

  
357
    # Map description to ID if given.
358
    if (!$object->chart_id && $entry->{raw_data}->{description}) {
359
      my $chart = $self->charts_by->{description}->{ $entry->{raw_data}->{description} };
360
      if (!$chart) {
361
        push @{ $entry->{errors} }, $::locale->text('Error: invalid chart');
362
        return 0;
363
      }
364

  
365
      $object->chart_id($chart->id);
366
    }
367

  
368
    if ($object->chart_id) {
369
      # add account number to preview
370
      $entry->{info_data}->{accno} = $self->charts_by->{id}->{ $object->chart_id }->accno;
371
    } else {
372
      push @{ $entry->{errors} }, $::locale->text('Error: chart not found');
373
      return 0;
374
    }
375
  } else {
376
    push @{ $entry->{errors} }, $::locale->text('Error: chart missing');
377
    return 0;
378
  }
379

  
380
  return 1;
381
}
382

  
383
sub check_apchart {
384
  my ($self, $entry) = @_;
385

  
386
  my $chart;
387

  
388
  if ( $entry->{raw_data}->{apchart} ) {
389
    my $apchart = $entry->{raw_data}->{apchart};
390
    $chart = SL::DB::Manager::Chart->find_by(accno => $apchart);
391
    unless ($chart) {
392
      push @{ $entry->{errors} }, $::locale->text("Error: can't find ap chart with accno #1", $apchart);
393
      return 0;
394
    }
395
  } elsif ( $::instance_conf->get_ap_chart_id ) {
396
    $chart = SL::DB::Manager::Chart->find_by(id => $::instance_conf->get_ap_chart_id);
397
  } else {
398
    push @{ $entry->{errors} }, $::locale->text("Error: neither apchart passed, no default payable chart configured");
399
    return 0;
400
  };
401

  
402
  unless ($chart->link eq 'AP') {
403
    push @{ $entry->{errors} }, $::locale->text('Error: apchart isn\'t an AP chart');
404
    return 0;
405
  };
406

  
407
  $entry->{info_data}->{apchart} = $chart->accno;
408
  $entry->{object}->{apchart} = $chart;
409
  return 1;
410
};
411

  
412
sub check_taxincluded {
413
  my ($self, $entry) = @_;
414

  
415
  my $object = $entry->{object};
416

  
417
  if ( $entry->{raw_data}->{taxincluded} ) {
418
    if ( $entry->{raw_data}->{taxincluded} eq 'f' or $entry->{raw_data}->{taxincluded} eq '0' ) {
419
      $object->taxincluded('0');
420
    } elsif ( $entry->{raw_data}->{taxincluded} eq 't' or $entry->{raw_data}->{taxincluded} eq '1' ) {
421
      $object->taxincluded('1');
422
    } else {
423
      push @{ $entry->{errors} }, $::locale->text('Error: taxincluded has to be t or f');
424
      return 0;
425
    };
426
  } else {
427
    push @{ $entry->{errors} }, $::locale->text('Error: taxincluded wasn\'t set');
428
    return 0;
429
  };
430
  return 1;
431
};
432

  
433
sub check_verify_amounts {
434
  my ($self) = @_;
435

  
436
  # If amounts are given, show calculated amounts as info and given amounts (verify_xxx).
437
  # And throw an error if the differences are too big.
438
  my @to_verify = ( { column      => 'amount',
439
                      raw_column  => 'verify_amount',
440
                      info_header => 'Calc. Amount',
441
                      info_method => 'calc_amount',
442
                      err_msg     => $::locale->text('Amounts differ too much'),
443
                    },
444
                    { column      => 'netamount',
445
                      raw_column  => 'verify_netamount',
446
                      info_header => 'Calc. Net amount',
447
                      info_method => 'calc_netamount',
448
                      err_msg     => $::locale->text('Net amounts differ too much'),
449
                    } );
450

  
451
  foreach my $tv (@to_verify) {
452
    if (exists $self->controller->data->[0]->{raw_data}->{ $tv->{raw_column} }) {
453
      $self->add_raw_data_columns($self->_ap_column, $tv->{raw_column});
454
      $self->add_info_columns($self->_ap_column,
455
                              { header => $::locale->text($tv->{info_header}), method => $tv->{info_method} });
456
    }
457

  
458
    # check differences
459
    foreach my $entry (@{ $self->controller->data }) {
460
      if ( @{ $entry->{errors} } ) {
461
        push @{ $entry->{errors} }, $::locale->text($tv->{err_msg});
462
        return 0;
463
      };
464

  
465
      if ($entry->{raw_data}->{datatype} eq $self->_ap_column) {
466
        next if !$entry->{raw_data}->{ $tv->{raw_column} };
467
        my $parsed_value = $::form->parse_amount(\%::myconfig, $entry->{raw_data}->{ $tv->{raw_column} });
468
        # round $abs_diff, otherwise it might trigger for 0.020000000000021
469
        my $abs_diff = $::form->round_amount(abs($entry->{object}->${ \$tv->{column} } - $parsed_value),2);
470
        if ( $abs_diff > $self->settings->{'max_amount_diff'}) {
471
          push @{ $entry->{errors} }, $::locale->text($tv->{err_msg});
472
        }
473
      }
474
    }
475
  }
476
};
477

  
478
sub add_transactions_to_ap {
479
  my ($self) = @_;
480

  
481
  # go through all verified ap and acc_trans rows in import, adding acc_trans objects to ap objects
482

  
483
  my $ap_entry;  # the current ap row
484

  
485
  foreach my $entry (@{ $self->controller->data }) {
486
    # when we reach an ap_column for the first time, don't do anything, just store in $ap_entry
487
    # when we reach an ap_column for the second time, save it
488
    if ($entry->{raw_data}->{datatype} eq $self->_ap_column) {
489
      if ( $ap_entry && $ap_entry->{object} ) { # won't trigger the first time, finishes the last object
490
        if ( $ap_entry->{object}->{apchart} && $ap_entry->{object}->{apchart}->isa('SL::DB::Chart') ) {
491
          $ap_entry->{object}->recalculate_amounts; # determine and set amount and netamount for ap
492
          $ap_entry->{object}->create_ap_row(chart => $ap_entry->{object}->{apchart});
493
          $ap_entry->{info_data}->{amount}    = $ap_entry->{object}->amount;
494
          $ap_entry->{info_data}->{netamount} = $ap_entry->{object}->netamount;
495
        } else {
496
          push @{ $entry->{errors} }, $::locale->text("ap_chart isn't a valid chart");
497
        };
498
      };
499
      $ap_entry = $entry; # remember as last ap_entry
500

  
501
    } elsif ( defined $ap_entry && $entry->{raw_data}->{datatype} eq $self->_transaction_column ) {
502
      push @{ $entry->{errors} }, $::locale->text('no tax_id in acc_trans') if !defined $entry->{object}->tax_id;
503
      next if @{ $entry->{errors} };
504

  
505
      my $acc_trans_objects = $ap_entry->{object}->add_ap_amount_row(
506
        amount      => $entry->{object}->amount,
507
        chart       => SL::DB::Manager::Chart->find_by(id => $entry->{object}->chart_id), # add_ap_amount takes chart obj. as argument
508
        tax_id      => $entry->{object}->tax_id,
509
        project_id  => $entry->{object}->project_id,
510
        debug       => 0,
511
      );
512

  
513
    } else {
514
      die "This should never happen\n";
515
    };
516
  }
517

  
518
  # finish the final object
519
  if ( $ap_entry->{object} ) {
520
    if ( $ap_entry->{object}->{apchart} && $ap_entry->{object}->{apchart}->isa('SL::DB::Chart') ) {
521
      $ap_entry->{object}->recalculate_amounts;
522
      $ap_entry->{info_data}->{amount}    = $ap_entry->{object}->amount;
523
      $ap_entry->{info_data}->{netamount} = $ap_entry->{object}->netamount;
524

  
525
      $ap_entry->{object}->create_ap_row(chart => $ap_entry->{object}->{apchart});
526
    } else {
527
      push @{ $ap_entry->{errors} }, $::locale->text("The payable chart isn't a valid chart.");
528
      return 0;
529
    };
530
  } else {
531
    die "There was no final ap_entry object";
532
  };
533
}
534

  
535
sub save_objects {
536
  my ($self, %params) = @_;
537

  
538
  # save all the Invoice objects
539
  my $objects_to_save;
540
  foreach my $entry (@{ $self->controller->data }) {
541
    # only push the invoice objects that don't have an error
542
    next if $entry->{raw_data}->{datatype} ne $self->_ap_column;
543
    next if @{ $entry->{errors} };
544

  
545
    die unless $entry->{object}->validate_acc_trans;
546

  
547
    push @{ $objects_to_save }, $entry;
548
  }
549

  
550
  $self->SUPER::save_objects(data => $objects_to_save);
551
}
552

  
553
sub _ap_column {
554
  $_[0]->settings->{'ap_column'}
555
}
556

  
557
sub _transaction_column {
558
  $_[0]->settings->{'transaction_column'}
559
}
560

  
561
1;
locale/de/all
90 90
  'AP Transaction (abbreviation)' => 'K',
91 91
  'AP Transaction Storno (one letter abbreviation)' => 'S',
92 92
  'AP Transaction with Storno (abbreviation)' => 'K(S)',
93
  'AP Transaction/AccTrans Item row names' => 'Namen der Rechnungs/Buchungszeilen',
93 94
  'AP Transactions'             => 'Kreditorenbuchungen',
94 95
  'AP template suggestions'     => 'Vorschlag Kreditorenbuchung',
95 96
  'AP transaction \'#1\' posted (ID: #2)' => 'Kreditorenbuchung \'#1\' verbucht (Buchungsnummer: #2)',
......
588 589
  'CSV export'                  => 'CSV-Export',
589 590
  'CSV export -- options'       => 'CSV-Export -- Optionen',
590 591
  'CSV import: additional billing addresses' => 'CSV-Import: zusätzliche Rechnungsadressen',
592
  'CSV import: ap transactions' => 'CSV Import: Kreditorenbuchungen',
591 593
  'CSV import: ar transactions' => 'CSV Import: Debitorenbuchungen',
592 594
  'CSV import: bank transactions' => 'CSV Import: Bankbewegungen',
593 595
  'CSV import: contacts'        => 'CSV-Import: Ansprechpersonen',
......
1559 1561
  'Error: This is not a sales reclamation.' => 'Fehler: Dies ist keine Verkaufreklamaiton.',
1560 1562
  'Error: Transfer would result in a negative target quantity.' => 'Fehler: Lagerbewegung würde zu einer negativen Zielmenge führen.',
1561 1563
  'Error: Unit missing or invalid' => 'Fehler: Einheit fehlt oder ungültig',
1564
  'Error: Vendor missing'       => 'Fehler: Lieferant fehlt',
1562 1565
  'Error: Warehouse not found'  => 'Fehler: Lager nicht gefunden',
1563 1566
  'Error: amount and netamount need to be numeric' => 'Fehler: amount und netamount müssen numerisch sein',
1567
  'Error: ap transaction doesn\'t validate' => 'Fehler: die Kreditorenbuchung ist nicht korrekt',
1568
  'Error: apchart isn\'t an AP chart' => 'Fehler: das Verbindlichkeitskonto ist nicht als Verbindlichkeitskonto definiert (link = AP)',
1564 1569
  'Error: ar transaction doesn\'t validate' => 'Fehler: die Debitorenbuchung ist nicht korrekt',
1565 1570
  'Error: archart isn\'t an AR chart' => 'Fehler: das Forderungskonto ist nicht als Forderungskonto definiert (link = AR)',
1566 1571
  'Error: booking group missing or invalid' => 'Fehler: Buchungsgruppe fehlt oder ungültig',
1572
  'Error: can\'t find ap chart with accno #1' => 'Fehler: kein Verbindlichkeitskonto mit Kontonummer #1',
1567 1573
  'Error: can\'t find ar chart with accno #1' => 'Fehler: kein Forderungskonto mit Kontonummer #1',
1574
  'Error: chart isn\'t an ap_amount chart' => 'Fehler: Konto ist kein Aufwandkonto',
1568 1575
  'Error: chart isn\'t an ar_amount chart' => 'Fehler: Konto ist kein Erlöskonto',
1569 1576
  'Error: chart missing'        => 'Fehler: Konto fehlt',
1570 1577
  'Error: chart not found'      => 'Fehler: Konto nicht gefunden',
1578
  'Error: invalid acc transactions for this ap row' => 'Fehler: ungültige Buchungszeilen für diese Rechnungzeile',
1571 1579
  'Error: invalid acc transactions for this ar row' => 'Fehler: ungültige Buchungszeilen für diese Rechnungzeile',
1580
  'Error: invalid ap row for this transaction' => 'Fehler: ungültige Rechnungszeile für diese Buchungszeile',
1572 1581
  'Error: invalid ar row for this transaction' => 'Ungültige Rechnungszeile für diese Buchungszeile',
1573 1582
  'Error: invalid chart'        => 'Fehler: ungültiges Konto',
1574 1583
  'Error: invalid chart (accno)' => 'Fehler: ungültiges Konto (accno)',
......
1578 1587
  'Error: local bank account id doesn\'t match local bank account number' => 'Fehler: Bankkonto-ID stimmt nicht mit Kontonummer überein',
1579 1588
  'Error: local bank account id doesn\'t match local bank code' => 'Fehler: Bankkonto-ID stimmt nicht mit BLZ überein',
1580 1589
  'Error: need amount and netamount' => 'Fehler: amount und netamount werden benötigt',
1590
  'Error: neither apchart passed, no default payable chart configured' => 'Fehler: Verbindlichkeitskonto (archart) fehlt, kein Standardforderungskonto definiert',
1581 1591
  'Error: neither archart passed, no default receivables chart configured' => 'Fehler: Forderungskonto (archart) fehlt, kein Standardforderungskonto definiert',
1582 1592
  'Error: taxincluded has to be t or f' => 'Fehler: Steuer im Preis inbegriffen muß t oder f sein',
1583 1593
  'Error: taxincluded wasn\'t set' => 'Fehler: Steuer im Preis inbegriffen nicht gesetzt (taxincluded)',
......
2687 2697
  'Paste template'              => 'Vorlage einfügen',
2688 2698
  'Path'                        => 'Pfad',
2689 2699
  'Payable account'             => 'Verbindlichkeitskonto',
2700
  'Payable account (account number)' => 'Verbindlichkeitskonto (Kontonummer)',
2690 2701
  'Payables'                    => 'Verbindlichkeiten',
2691 2702
  'Payment'                     => 'Zahlungsausgang',
2692 2703
  'Payment / Delivery Options'  => 'Zahlungs- und Lieferoptionen',
......
3969 3980
  'The password is too short (minimum length: #1).' => 'Das Password ist zu kurz (minimale Länge: #1).',
3970 3981
  'The password is weak (e.g. it can be found in a dictionary).' => 'Das Passwort ist schwach (z.B. wenn es in einem Wörterbuch steht).',
3971 3982
  'The path is missing.'        => 'Pfad fehlt',
3983
  'The payable chart isn\'t a valid chart.' => 'Das Verbindlichkeitskonto ist kein gültiges Konto.',
3972 3984
  'The payment term has been created.' => 'Die Zahlungsbedingungen wurden angelegt.',
3973 3985
  'The payment term has been deleted.' => 'Die Zahlungsbedingungen wurden gelöscht.',
3974 3986
  'The payment term has been saved.' => 'Die Zahlungsbedingungen wurden gespeichert.',
......
4712 4724
  'always'                      => 'immer',
4713 4725
  'and'                         => 'und',
4714 4726
  'ap_aging_list'               => 'liste_offene_verbindlichkeiten',
4727
  'ap_chart isn\'t a valid chart' => 'Das Verbindlichkeitskonto ist kein gültiges Konto',
4715 4728
  'ar_aging_list'               => 'liste_offene_forderungen',
4716 4729
  'ar_chart isn\'t a valid chart' => 'Das Forderungskonto ist kein gültiges Konto.',
4717 4730
  'article_list'                => 'article_list',
locale/en/all
90 90
  'AP Transaction (abbreviation)' => '',
91 91
  'AP Transaction Storno (one letter abbreviation)' => '',
92 92
  'AP Transaction with Storno (abbreviation)' => '',
93
  'AP Transaction/AccTrans Item row names' => '',
93 94
  'AP Transactions'             => 'Purchase Transactions',
94 95
  'AP template suggestions'     => '',
95 96
  'AP transaction \'#1\' posted (ID: #2)' => '',
......
588 589
  'CSV export'                  => '',
589 590
  'CSV export -- options'       => '',
590 591
  'CSV import: additional billing addresses' => '',
592
  'CSV import: ap transactions' => '',
591 593
  'CSV import: ar transactions' => '',
592 594
  'CSV import: bank transactions' => '',
593 595
  'CSV import: contacts'        => '',
......
1559 1561
  'Error: This is not a sales reclamation.' => '',
1560 1562
  'Error: Transfer would result in a negative target quantity.' => '',
1561 1563
  'Error: Unit missing or invalid' => '',
1564
  'Error: Vendor missing'       => '',
1562 1565
  'Error: Warehouse not found'  => '',
1563 1566
  'Error: amount and netamount need to be numeric' => '',
1567
  'Error: ap transaction doesn\'t validate' => '',
1568
  'Error: apchart isn\'t an AP chart' => '',
1564 1569
  'Error: ar transaction doesn\'t validate' => '',
1565 1570
  'Error: archart isn\'t an AR chart' => '',
1566 1571
  'Error: booking group missing or invalid' => '',
1572
  'Error: can\'t find ap chart with accno #1' => '',
1567 1573
  'Error: can\'t find ar chart with accno #1' => '',
1574
  'Error: chart isn\'t an ap_amount chart' => '',
1568 1575
  'Error: chart isn\'t an ar_amount chart' => '',
1569 1576
  'Error: chart missing'        => '',
1570 1577
  'Error: chart not found'      => '',
1578
  'Error: invalid acc transactions for this ap row' => '',
1571 1579
  'Error: invalid acc transactions for this ar row' => '',
1580
  'Error: invalid ap row for this transaction' => '',
1572 1581
  'Error: invalid ar row for this transaction' => '',
1573 1582
  'Error: invalid chart'        => '',
1574 1583
  'Error: invalid chart (accno)' => '',
......
1578 1587
  'Error: local bank account id doesn\'t match local bank account number' => '',
1579 1588
  'Error: local bank account id doesn\'t match local bank code' => '',
1580 1589
  'Error: need amount and netamount' => '',
1590
  'Error: neither apchart passed, no default payable chart configured' => '',
1581 1591
  'Error: neither archart passed, no default receivables chart configured' => '',
1582 1592
  'Error: taxincluded has to be t or f' => '',
1583 1593
  'Error: taxincluded wasn\'t set' => '',
......
2686 2696
  'Paste template'              => '',
2687 2697
  'Path'                        => '',
2688 2698
  'Payable account'             => '',
2699
  'Payable account (account number)' => '',
2689 2700
  'Payables'                    => '',
2690 2701
  'Payment'                     => '',
2691 2702
  'Payment / Delivery Options'  => '',
......
3967 3978
  'The password is too short (minimum length: #1).' => '',
3968 3979
  'The password is weak (e.g. it can be found in a dictionary).' => '',
3969 3980
  'The path is missing.'        => '',
3981
  'The payable chart isn\'t a valid chart.' => '',
3970 3982
  'The payment term has been created.' => '',
3971 3983
  'The payment term has been deleted.' => '',
3972 3984
  'The payment term has been saved.' => '',
......
4710 4722
  'always'                      => '',
4711 4723
  'and'                         => '',
4712 4724
  'ap_aging_list'               => '',
4725
  'ap_chart isn\'t a valid chart' => '',
4713 4726
  'ar_aging_list'               => '',
4714 4727
  'ar_chart isn\'t a valid chart' => '',
4715 4728
  'article_list'                => '',
menus/user/00-erp.yaml
1475 1475
  params:
1476 1476
    action: CsvImport/new
1477 1477
    profile.type: ar_transactions
1478
- parent: system_import_csv
1479
  id: system_import_csv_ap_transactions
1480
  name: AP Transactions
1481
  order: 820
1482
  params:
1483
    action: CsvImport/new
1484
    profile.type: ap_transactions
1478 1485
- parent: system
1479 1486
  id: system_templates
1480 1487
  name: Templates
templates/design40_webpages/csv_import/_form_aptransactions.html
1
[% USE LxERP %]
2
[% USE L %]
3
<tr>
4
 <th align="right">[%- LxERP.t8('AP Transaction/AccTrans Item row names') %]:</th>
5
 <td colspan="10">
6
  [% L.input_tag('settings.ap_column', SELF.profile.get('ap_column'), size => "10") %]
7
  [% L.input_tag('settings.transaction_column',  SELF.profile.get('transaction_column'),  size => "20") %]
8
 </td>
9

  
10
<tr>
11
 <th align="right">[%- LxERP.t8('Maximal amount difference') %]:</th>
12
 <td colspan="10">
13
  [% L.input_tag('settings.max_amount_diff', LxERP.format_amount(SELF.profile.get('max_amount_diff')), size => "5") %]
14
 </td>
15
</tr>
templates/design40_webpages/csv_import/form.html
156 156
      [% INCLUDE 'csv_import/_form_delivery_orders.html' %]
157 157
    [% ELSIF SELF.type == 'ar_transactions' %]
158 158
      [% INCLUDE 'csv_import/_form_artransactions.html' %]
159
    [% ELSIF SELF.type == 'ap_transactions' %]
160
      [% INCLUDE 'csv_import/_form_aptransactions.html' %]
159 161
    [% ELSIF SELF.type == 'bank_transactions' %]
160 162
      [% INCLUDE 'csv_import/_form_banktransactions.html' %]
161 163
    [% END %]
templates/webpages/csv_import/_form_aptransactions.html
1
[% USE LxERP %]
2
[% USE L %]
3
<tr>
4
 <th align="right">[%- LxERP.t8('AP Transaction/AccTrans Item row names') %]:</th>
5
 <td colspan="10">
6
  [% L.input_tag('settings.ap_column', SELF.profile.get('ap_column'), size => "10") %]
7
  [% L.input_tag('settings.transaction_column',  SELF.profile.get('transaction_column'),  size => "20") %]
8
 </td>
9

  
10
<tr>
11
 <th align="right">[%- LxERP.t8('Maximal amount difference') %]:</th>
12
 <td colspan="10">
13
  [% L.input_tag('settings.max_amount_diff', LxERP.format_amount(SELF.profile.get('max_amount_diff')), size => "5") %]
14
 </td>
15
</tr>
templates/webpages/csv_import/form.html
280 280
 [%- INCLUDE 'csv_import/_form_delivery_orders.html' %]
281 281
[%- ELSIF SELF.type == 'ar_transactions' %]
282 282
 [%- INCLUDE 'csv_import/_form_artransactions.html' %]
283
[%- ELSIF SELF.type == 'ap_transactions' %]
284
 [%- INCLUDE 'csv_import/_form_aptransactions.html' %]
283 285
[%- ELSIF SELF.type == 'bank_transactions' %]
284 286
 [%- INCLUDE 'csv_import/_form_banktransactions.html' %]
285 287
[%- END %]

Auch abrufbar als: Unified diff