]> wagnertech.de Git - mfinanz.git/blob - SL/Controller/CsvImport/APTransaction.pm
date error in mapping
[mfinanz.git] / 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     dont_save_anything_on_errors => 0,
39   );
40 };
41
42
43 sub init_settings {
44   my ($self) = @_;
45
46   return { map { ( $_ => $self->controller->profile->get($_) ) } qw(ap_column transaction_column max_amount_diff dont_save_anything_on_errors) };
47 }
48
49 sub init_profile {
50   my ($self) = @_;
51
52   my $profile = $self->SUPER::init_profile;
53
54   # SUPER::init_profile sets row_ident to the translated class name
55   # overwrite it with the user specified settings
56   foreach my $p (@{ $profile }) {
57     if ($p->{class} eq 'SL::DB::PurchaseInvoice') {
58       $p->{row_ident} = $self->_ap_column;
59     }
60     if ($p->{class} eq 'SL::DB::AccTransaction') {
61       $p->{row_ident} = $self->_transaction_column;
62     }
63   }
64
65   foreach my $p (@{ $profile }) {
66     my $prof = $p->{profile};
67     if ($p->{row_ident} eq $self->_ap_column) {
68       # no need to handle
69       delete @{$prof}{qw(amount cp_id datepaid delivery_term_id gldate invoice language_id netamount paid shipvia storno storno_id taxzone taxzone_id type)};
70     }
71     if ($p->{row_ident} eq $self->_transaction_column) {
72       # no need to handle
73       delete @{$prof}{qw(acc_trans_id cb_transaction chart_link cleared fx_transaction gldate memo ob_transaction source tax_id description trans_id transdate)};
74     }
75   }
76
77   return $profile;
78 }
79
80 sub init_existing_objects {
81   my ($self) = @_;
82
83   # only use objects of main class (the first one)
84   #eval "require " . $self->class->[0];
85   $self->existing_objects($self->manager_class->[0]->get_all);
86 }
87
88 sub get_duplicate_check_fields {
89   return {
90     vendor_and_invnumber => {
91       label     => $::locale->text('Vendor and Invoice Number'),
92       default   => 1,
93       std_check => 1,
94       maker     => sub {
95         my ($object, $worker) = @_;
96         return if ref $object ne $worker->class->[0];
97         return '__' . $object->vendor_id . '__' . $object->invnumber . '__';
98       },
99     },
100   };
101 }
102
103 sub check_std_duplicates {
104   my $self = shift;
105
106   my $duplicates = {};
107
108   my $all_fields = $self->get_duplicate_check_fields();
109
110   foreach my $key (keys(%{ $all_fields })) {
111     if ( $self->controller->profile->get('duplicates_'. $key) && (!exists($all_fields->{$key}->{std_check}) || $all_fields->{$key}->{std_check} )  ) {
112       $duplicates->{$key} = {};
113     }
114   }
115
116   my @duplicates_keys = keys(%{ $duplicates });
117
118   if ( !scalar(@duplicates_keys) ) {
119     return;
120   }
121
122   if ( $self->controller->profile->get('duplicates') eq 'check_db' ) {
123     foreach my $object (@{ $self->existing_objects }) {
124       foreach my $key (@duplicates_keys) {
125         my $value = exists($all_fields->{$key}->{maker}) ? $all_fields->{$key}->{maker}->($object, $self) : $object->$key;
126         $duplicates->{$key}->{$value} = 'db';
127       }
128     }
129   }
130
131   # only check main class (the first one)
132   foreach my $entry (@{ $self->controller->data }) {
133     my $object = $entry->{object};
134
135     next if ref $object ne $self->class->[0];
136     next if scalar @{ $entry->{errors} };
137
138     foreach my $key (@duplicates_keys) {
139       my $value = exists($all_fields->{$key}->{maker}) ? $all_fields->{$key}->{maker}->($object, $self) : $object->$key;
140
141       if ( exists($duplicates->{$key}->{$value}) ) {
142         push(@{ $entry->{errors} }, $duplicates->{$key}->{$value} eq 'db' ? $::locale->text('Duplicate in database') : $::locale->text('Duplicate in CSV file'));
143         last;
144       } else {
145         $duplicates->{$key}->{$value} = 'csv';
146       }
147     }
148   }
149 }
150
151 sub setup_displayable_columns {
152   my ($self) = @_;
153
154   $self->SUPER::setup_displayable_columns;
155
156   $self->add_displayable_columns($self->_ap_column,
157                                  { name => 'datatype',                description => $self->_ap_column . ' [1]'                                },
158                                  { name => 'currency_id',             description => $::locale->text('Currency (database ID)')                 },
159                                  { name => 'currency',                description => $::locale->text('Currency')                               },
160                                  { name => 'deliverydate',            description => $::locale->text('Delivery Date')                          },
161                                  { name => 'department_id',           description => $::locale->text('Department (database ID)')               },
162                                  { name => 'department',              description => $::locale->text('Department (description)')               },
163                                  { name => 'direct_debit',            description => $::locale->text('direct debit')                           },
164                                  { name => 'duedate',                 description => $::locale->text('Due Date')                               },
165                                  { name => 'employee_id',             description => $::locale->text('Employee (database ID)')                 },
166                                  { name => 'exchangerate',            description => $::locale->text('Exchangerate')                           },
167                                  { name => 'globalproject_id',        description => $::locale->text('Document Project (database ID)')         },
168                                  { name => 'globalprojectnumber',     description => $::locale->text('Document Project (number)')              },
169                                  { name => 'globalproject',           description => $::locale->text('Document Project (description)')         },
170                                  { name => 'intnotes',                description => $::locale->text('Internal Notes')                         },
171                                  { name => 'invnumber',               description => $::locale->text('Invoice Number')                         },
172                                  { name => 'is_sepa_blocked',         description => $::locale->text('Bank transfer via SEPA is blocked')      },
173                                  { name => 'notes',                   description => $::locale->text('Notes')                                  },
174                                  { name => 'orddate',                 description => $::locale->text('Order Date')                             },
175                                  { name => 'ordnumber',               description => $::locale->text('Order Number')                           },
176                                  { name => 'payment_id',              description => $::locale->text('Payment terms (database ID)')            },
177                                  { name => 'payment',                 description => $::locale->text('Payment terms (name)')                   },
178                                  { name => 'quonumber',               description => $::locale->text('Quotation Number')                       },
179                                  { name => 'quodate',                 description => $::locale->text('Quotation Date')                         },
180                                  { name => 'tax_point',               description => $::locale->text('Tax point')                              },
181                                  { name => 'taxincluded',             description => $::locale->text('Tax Included')                           },
182                                  { name => 'transaction_description', description => $::locale->text('Transaction description')                },
183                                  { name => 'transdate',               description => $::locale->text('Invoice Date')                           },
184                                  { name => 'vendor',                  description => $::locale->text('Vendor (name)')                          },
185                                  { name => 'vendornumber',            description => $::locale->text('Vendor Number')                          },
186                                  { name => 'vendor_gln',              description => $::locale->text('Vendor GLN')                             },
187                                  { name => 'vendor_id',               description => $::locale->text('Vendor (database ID)')                   },
188                                  { name => 'verify_amount',           description => $::locale->text('Amount (for verification)') . ' [2]'     },
189                                  { name => 'verify_netamount',        description => $::locale->text('Net amount (for verification)') . ' [2]' },
190                                  { name => 'apchart',                 description => $::locale->text('Payable account (account number)')       },
191   );
192
193   $self->add_displayable_columns($self->_transaction_column,
194                                  { name => 'datatype',      description => $self->_transaction_column . ' [1]'             },
195                                  { name => 'amount',        description => $::locale->text('Amount')                       },
196                                  { name => 'chart_id',      description => $::locale->text('Account number (database ID)') },
197                                  { name => 'project_id',    description => $::locale->text('Project (database ID)')        },
198                                  { name => 'project',       description => $::locale->text('Project (description)')        },
199                                  { name => 'projectnumber', description => $::locale->text('Project (number)')             },
200                                  { name => 'taxkey',        description => $::locale->text('Taxkey')                       },
201                                  { name => 'accno',         description => $::locale->text('Account number')               },
202                                 );
203 }
204
205 sub init_taxkeys_by {
206   my ($self) = @_;
207
208   my $all_taxes = SL::DB::Manager::Tax->get_all;
209   return { map { $_->taxkey => $_->id } @{ $all_taxes } };
210 }
211
212
213 sub init_charts_by {
214   my ($self) = @_;
215
216   my $all_charts = SL::DB::Manager::Chart->get_all;
217   return { map { my $col = $_; ( $col => { map { ( $_->$col => $_ ) } @{ $all_charts } } ) } qw(id accno) };
218 }
219
220 sub check_objects {
221   my ($self) = @_;
222
223   $self->controller->track_progress(phase => 'building data', progress => 0);
224
225   my $i = 0;
226   my $num_data = scalar @{ $self->controller->data };
227   my $invoice_entry;
228
229   foreach my $entry (@{ $self->controller->data }) {
230     $self->controller->track_progress(progress => $i/$num_data * 100) if $i % 100 == 0;
231
232     if ($entry->{raw_data}->{datatype} eq $self->_ap_column) {
233       $entry->{info_data}->{datatype} = $::locale->text($self->_ap_column);
234       $self->handle_invoice($entry);
235       $invoice_entry = $entry;
236     } elsif ($entry->{raw_data}->{datatype} eq $self->_transaction_column ) {
237       die "Cannot process transaction row without an invoice row" if !$invoice_entry;
238       $entry->{info_data}->{datatype} = $::locale->text($self->_transaction_column);
239       $self->handle_transaction($entry, $invoice_entry);
240     } else {
241       die "unknown datatype";
242     };
243
244   } continue {
245     $i++;
246   } # finished data parsing
247
248   $self->add_info_columns($self->_ap_column,          { header => 'datatype', method => 'datatype' });
249   $self->add_info_columns($self->_transaction_column, { header => 'datatype', method => 'datatype' });
250
251   $self->add_transactions_to_ap(); # go through all data entries again, adding payable entry to ap lines while calculating amount and netamount
252
253   $self->check_verify_amounts();
254
255   foreach my $entry (@{ $self->controller->data }) {
256     next unless ($entry->{raw_data}->{datatype} eq $self->_ap_column);
257     unless ( $entry->{object}->validate_acc_trans ) {
258       push @{ $entry->{errors} }, $::locale->text('Error: ap transaction doesn\'t validate');
259     };
260   };
261
262   # add info columns that aren't directly part of the object to be imported
263   # but are always determined or should always be shown because they are mandatory
264   $self->add_info_columns($self->_ap_column,
265                           { header => $::locale->text('Vendor'),              method => 'vc_name'   },
266                           { header => $::locale->text('Payable account'),     method => 'apchart'   },
267                           { header => $::locale->text('Amount'),              method => 'amount'    },
268                           { header => $::locale->text('Net amount'),          method => 'netamount' },
269                           { header => $::locale->text('Tax zone'),            method => 'taxzone'   });
270
271   # Adding info_header this way only works, if the first invoice $self->controller->data->[0]
272
273   # Todo: access via ->[0] ok? Better: search first order column and use this
274   $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};
275
276   $self->add_info_columns($self->_ap_column, { header => $::locale->text('Project Number'), method => 'globalprojectnumber' }) if $self->controller->data->[0]->{info_data}->{globalprojectnumber};
277
278   $self->add_columns($self->_ap_column,
279                      map { "${_}_id" } grep { exists $self->controller->data->[0]->{raw_data}->{$_} } qw(payment department globalproject taxzone currency));
280   $self->add_columns($self->_ap_column, 'globalproject_id') if exists $self->controller->data->[0]->{raw_data}->{globalprojectnumber};
281   $self->add_columns($self->_ap_column, 'notes')            if exists $self->controller->data->[0]->{raw_data}->{notes};
282
283   # Todo: access via ->[1] ok? Better: search first item column and use this
284   $self->add_info_columns($self->_transaction_column, { header => $::locale->text('Chart'), method => 'accno' });
285   $self->add_columns($self->_transaction_column, 'amount');
286
287   $self->add_info_columns($self->_transaction_column, { header => $::locale->text('Project Number'), method => 'projectnumber' }) if $self->controller->data->[1]->{info_data}->{projectnumber};
288
289   # If requested to not save anything on errors, set all ap rows without error to an error
290   if ($self->controller->profile->get('dont_save_anything_on_errors')) {
291     my $any_errors = any { scalar @{ $_->{errors} } } @{ $self->controller->data };
292     if ($any_errors) {
293       foreach my $entry (grep { ($_->{raw_data}->{datatype} eq $self->_ap_column) && !scalar @{ $_->{errors} } } @{ $self->controller->data }) {
294         push @{ $entry->{errors} }, $::locale->text('There are some errors in the file and it was requested to not save any datasets on errors.');
295       }
296     }
297   }
298
299   # If invoice has errors, add error for acc_trans items
300   # If acc_trans item has an error, add an error to the invoice item
301   my $ap_entry;
302   foreach my $entry (@{ $self->controller->data }) {
303     # Search first order
304     if ($entry->{raw_data}->{datatype} eq $self->_ap_column) {
305       $ap_entry = $entry;
306     } elsif ( defined $ap_entry
307               && $entry->{raw_data}->{datatype} eq $self->_transaction_column
308               && scalar @{ $ap_entry->{errors} } > 0 ) {
309       push @{ $entry->{errors} }, $::locale->text('Error: invalid ap row for this transaction');
310     } elsif ( defined $ap_entry
311               && $entry->{raw_data}->{datatype} eq $self->_transaction_column
312               && scalar @{ $entry->{errors} } > 0 ) {
313       push @{ $ap_entry->{errors} }, $::locale->text('Error: invalid acc transactions for this ap row');
314     }
315   }
316 }
317
318 sub handle_invoice {
319
320   my ($self, $entry) = @_;
321
322   my $object = $entry->{object};
323
324   $object->transactions( [] ); # initialise transactions for ap object so methods work on unsaved transactions
325
326   my $vc_obj;
327   if (any { $entry->{raw_data}->{$_} } qw(vendor vendornumber vendor_gln vendor_id)) {
328     $self->check_vc($entry, 'vendor_id');
329     $vc_obj = SL::DB::Vendor->new(id => $object->vendor_id)->load if $object->vendor_id;
330   } else {
331     push @{ $entry->{errors} }, $::locale->text('Error: Vendor missing');
332   }
333
334   if (!$entry->{raw_data}->{invnumber}) {
335     push @{ $entry->{errors} }, $::locale->text('Error: Invoice Number missing');
336   }
337
338   $self->check_apchart($entry); # checks for payable account
339   $self->check_payment($entry); # currency default from vendor used below
340   $self->check_department($entry);
341   $self->check_taxincluded($entry);
342   $self->check_project($entry, global => 1);
343   $self->check_taxzone($entry); # taxzone default from customer used below
344   $self->check_currency($entry); # currency default from customer used below
345   $self->handle_employee($entry);
346
347   if ($vc_obj ) {
348     # copy defaults from customer if not specified in import file
349     foreach (qw(payment_id language_id taxzone_id currency_id)) {
350       $object->$_($vc_obj->$_) unless $object->$_;
351     }
352   }
353 }
354
355 sub check_taxkey {
356   my ($self, $entry, $invoice_entry, $chart) = @_;
357
358   die "check_taxkey needs chart object as an argument" unless ref($chart) eq 'SL::DB::Chart';
359   # problem: taxkey is not unique in table tax, normally one of those entries is chosen directly from a dropdown
360   # so we check if the chart has an active taxkey, and if it matches the taxkey from the import, use the active taxkey
361   # if the chart doesn't have an active taxkey, use the first entry from Tax that matches the taxkey
362
363   my $object         = $entry->{object};
364   my $invoice_object = $invoice_entry->{object};
365
366   unless ( defined $entry->{raw_data}->{taxkey} ) {
367     push @{ $entry->{errors} }, $::locale->text('Error: taxkey missing'); # don't just assume 0, force taxkey in import
368     return 0;
369   };
370
371   my $tax = $chart->get_active_taxkey($invoice_object->deliverydate // $invoice_object->transdate // DateTime->today_local)->tax;
372   if ( $entry->{raw_data}->{taxkey} != $tax->taxkey ) {
373    # assume there is only one tax entry with that taxkey, can't guess
374     $tax = SL::DB::Manager::Tax->get_first( where => [ taxkey => $entry->{raw_data}->{taxkey} ]);
375   };
376
377   unless ( $tax ) {
378     push @{ $entry->{errors} }, $::locale->text('Error: invalid taxkey');
379     return 0;
380   };
381
382   $object->taxkey($tax->taxkey);
383   $object->tax_id($tax->id);
384   return 1;
385 };
386
387 sub handle_transaction {
388   my ($self, $entry, $invoice_entry) = @_;
389
390   # Prepare acc_trans data. amount is dealt with in add_transactions_to_ap
391
392   my $object = $entry->{object};
393
394   $self->check_project($entry, global => 0);
395   if ( $self->check_chart($entry) ) {
396     my $chart_obj = SL::DB::Manager::Chart->find_by(id => $object->chart_id);
397
398     unless ( $chart_obj->link =~ /AP_amount/ ) {
399       push @{ $entry->{errors} }, $::locale->text('Error: chart isn\'t an ap_amount chart');
400       return 0;
401     };
402
403     if ( $self->check_taxkey($entry, $invoice_entry, $chart_obj) ) {
404       # do nothing, taxkey was assigned, just continue
405     } else {
406       # missing taxkey, don't do anything
407       return 0;
408     };
409   } else {
410     return 0;
411   };
412
413   # check whether taxkey and automatic taxkey match
414   # 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;
415
416   die "no taxkey for transaction object" unless $object->taxkey or $object->taxkey == 0;
417
418 }
419
420 sub check_chart {
421   my ($self, $entry) = @_;
422
423   my $object = $entry->{object};
424
425   if (any { $entry->{raw_data}->{$_} } qw(accno chart_id)) {
426
427     # Check whether or not chart ID is valid.
428     if ($object->chart_id && !$self->charts_by->{id}->{ $object->chart_id }) {
429       push @{ $entry->{errors} }, $::locale->text('Error: invalid chart_id');
430       return 0;
431     }
432
433     # Map number to ID if given.
434     if (!$object->chart_id && $entry->{raw_data}->{accno}) {
435       my $chart = $self->charts_by->{accno}->{ $entry->{raw_data}->{accno} };
436       if (!$chart) {
437         push @{ $entry->{errors} }, $::locale->text('Error: invalid chart (accno)');
438         return 0;
439       }
440
441       $object->chart_id($chart->id);
442     }
443
444     # Map description to ID if given.
445     if (!$object->chart_id && $entry->{raw_data}->{description}) {
446       my $chart = $self->charts_by->{description}->{ $entry->{raw_data}->{description} };
447       if (!$chart) {
448         push @{ $entry->{errors} }, $::locale->text('Error: invalid chart');
449         return 0;
450       }
451
452       $object->chart_id($chart->id);
453     }
454
455     if ($object->chart_id) {
456       # add account number to preview
457       $entry->{info_data}->{accno} = $self->charts_by->{id}->{ $object->chart_id }->accno;
458     } else {
459       push @{ $entry->{errors} }, $::locale->text('Error: chart not found');
460       return 0;
461     }
462   } else {
463     push @{ $entry->{errors} }, $::locale->text('Error: chart missing');
464     return 0;
465   }
466
467   return 1;
468 }
469
470 sub check_apchart {
471   my ($self, $entry) = @_;
472
473   my $chart;
474
475   if ( $entry->{raw_data}->{apchart} ) {
476     my $apchart = $entry->{raw_data}->{apchart};
477     $chart = SL::DB::Manager::Chart->find_by(accno => $apchart);
478     unless ($chart) {
479       push @{ $entry->{errors} }, $::locale->text("Error: can't find ap chart with accno #1", $apchart);
480       return 0;
481     }
482   } elsif ( $::instance_conf->get_ap_chart_id ) {
483     $chart = SL::DB::Manager::Chart->find_by(id => $::instance_conf->get_ap_chart_id);
484   } else {
485     push @{ $entry->{errors} }, $::locale->text("Error: neither apchart passed, no default payable chart configured");
486     return 0;
487   };
488
489   unless ($chart->link eq 'AP') {
490     push @{ $entry->{errors} }, $::locale->text('Error: apchart isn\'t an AP chart');
491     return 0;
492   };
493
494   $entry->{info_data}->{apchart} = $chart->accno;
495   $entry->{object}->{apchart} = $chart;
496   return 1;
497 };
498
499 sub check_taxincluded {
500   my ($self, $entry) = @_;
501
502   my $object = $entry->{object};
503
504   if ( $entry->{raw_data}->{taxincluded} ) {
505     if ( $entry->{raw_data}->{taxincluded} eq 'f' or $entry->{raw_data}->{taxincluded} eq '0' ) {
506       $object->taxincluded('0');
507     } elsif ( $entry->{raw_data}->{taxincluded} eq 't' or $entry->{raw_data}->{taxincluded} eq '1' ) {
508       $object->taxincluded('1');
509     } else {
510       push @{ $entry->{errors} }, $::locale->text('Error: taxincluded has to be t or f');
511       return 0;
512     };
513   } else {
514     push @{ $entry->{errors} }, $::locale->text('Error: taxincluded wasn\'t set');
515     return 0;
516   };
517   return 1;
518 };
519
520 sub check_verify_amounts {
521   my ($self) = @_;
522
523   # If amounts are given, show calculated amounts as info and given amounts (verify_xxx).
524   # And throw an error if the differences are too big.
525   my @to_verify = ( { column      => 'amount',
526                       raw_column  => 'verify_amount',
527                       info_header => 'Calc. Amount',
528                       info_method => 'calc_amount',
529                       err_msg     => $::locale->text('Amounts differ too much'),
530                     },
531                     { column      => 'netamount',
532                       raw_column  => 'verify_netamount',
533                       info_header => 'Calc. Net amount',
534                       info_method => 'calc_netamount',
535                       err_msg     => $::locale->text('Net amounts differ too much'),
536                     } );
537
538   foreach my $tv (@to_verify) {
539     if (exists $self->controller->data->[0]->{raw_data}->{ $tv->{raw_column} }) {
540       $self->add_raw_data_columns($self->_ap_column, $tv->{raw_column});
541       $self->add_info_columns($self->_ap_column,
542                               { header => $::locale->text($tv->{info_header}), method => $tv->{info_method} });
543     }
544
545     # check differences
546     foreach my $entry (@{ $self->controller->data }) {
547       if ($entry->{raw_data}->{datatype} eq $self->_ap_column) {
548         next if !$entry->{raw_data}->{ $tv->{raw_column} };
549         my $parsed_value = $::form->parse_amount(\%::myconfig, $entry->{raw_data}->{ $tv->{raw_column} });
550         # round $abs_diff, otherwise it might trigger for 0.020000000000021
551         my $abs_diff = $::form->round_amount(abs($entry->{object}->${ \$tv->{column} } - $parsed_value),2);
552         if ( $abs_diff > $self->settings->{'max_amount_diff'}) {
553           push @{ $entry->{errors} }, $::locale->text($tv->{err_msg});
554         }
555       }
556     }
557   }
558 };
559
560 sub add_transactions_to_ap {
561   my ($self) = @_;
562
563   # go through all verified ap and acc_trans rows in import, adding acc_trans objects to ap objects
564
565   my $ap_entry;  # the current ap row
566
567   foreach my $entry (@{ $self->controller->data }) {
568     # when we reach an ap_column for the first time, don't do anything, just store in $ap_entry
569     # when we reach an ap_column for the second time, save it
570     if ($entry->{raw_data}->{datatype} eq $self->_ap_column) {
571       if ( $ap_entry && $ap_entry->{object} ) { # won't trigger the first time, finishes the last object
572         if ( $ap_entry->{object}->{apchart} && $ap_entry->{object}->{apchart}->isa('SL::DB::Chart') ) {
573           $ap_entry->{object}->recalculate_amounts; # determine and set amount and netamount for ap
574           $ap_entry->{object}->create_ap_row(chart => $ap_entry->{object}->{apchart});
575           $ap_entry->{info_data}->{calc_amount}    = $ap_entry->{object}->amount_as_number;
576           $ap_entry->{info_data}->{calc_netamount} = $ap_entry->{object}->netamount_as_number;
577         } else {
578           push @{ $ap_entry->{errors} }, $::locale->text("The payable chart isn't a valid chart.");
579         };
580       };
581       $ap_entry = $entry; # remember as last ap_entry
582
583     } elsif ( defined $ap_entry && $entry->{raw_data}->{datatype} eq $self->_transaction_column ) {
584       push @{ $entry->{errors} }, $::locale->text('no tax_id in acc_trans') if !defined $entry->{object}->tax_id;
585       next if @{ $entry->{errors} };
586
587       my $acc_trans_objects = $ap_entry->{object}->add_ap_amount_row(
588         amount      => $entry->{object}->amount,
589         chart       => SL::DB::Manager::Chart->find_by(id => $entry->{object}->chart_id), # add_ap_amount takes chart obj. as argument
590         tax_id      => $entry->{object}->tax_id,
591         project_id  => $entry->{object}->project_id,
592         debug       => 0,
593       );
594
595     } else {
596       die "This should never happen\n";
597     };
598   }
599
600   # finish the final object
601   if ( $ap_entry->{object} ) {
602     if ( $ap_entry->{object}->{apchart} && $ap_entry->{object}->{apchart}->isa('SL::DB::Chart') ) {
603       $ap_entry->{object}->recalculate_amounts;
604       $ap_entry->{info_data}->{calc_amount}    = $ap_entry->{object}->amount_as_number;
605       $ap_entry->{info_data}->{calc_netamount} = $ap_entry->{object}->netamount_as_number;
606
607       $ap_entry->{object}->create_ap_row(chart => $ap_entry->{object}->{apchart});
608     } else {
609       push @{ $ap_entry->{errors} }, $::locale->text("The payable chart isn't a valid chart.");
610       return 0;
611     };
612   } else {
613     die "There was no final ap_entry object";
614   };
615 }
616
617 sub save_objects {
618   my ($self, %params) = @_;
619
620   # save all the Invoice objects
621   my $objects_to_save;
622   foreach my $entry (@{ $self->controller->data }) {
623     # only push the invoice objects that don't have an error
624     next if $entry->{raw_data}->{datatype} ne $self->_ap_column;
625     next if @{ $entry->{errors} };
626
627     die unless $entry->{object}->validate_acc_trans;
628
629     push @{ $objects_to_save }, $entry;
630   }
631
632   $self->SUPER::save_objects(data => $objects_to_save);
633 }
634
635 sub _ap_column {
636   $_[0]->settings->{'ap_column'}
637 }
638
639 sub _transaction_column {
640   $_[0]->settings->{'transaction_column'}
641 }
642
643 1;