|
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;
|
CSV-Import Kreditorenbuchungen
Kopie und Anpassung vom Debitorenbuchungsimport