Merge branch 'sepa-fixes-2320'
[kivitendo-erp.git] / SL / IS.pm
1 #=====================================================================
2 # LX-Office ERP
3 # Copyright (C) 2004
4 # Based on SQL-Ledger Version 2.1.9
5 # Web http://www.lx-office.org
6 #
7 #=====================================================================
8 # SQL-Ledger Accounting
9 # Copyright (C) 1998-2002
10 #
11 #  Author: Dieter Simader
12 #   Email: dsimader@sql-ledger.org
13 #     Web: http://www.sql-ledger.org
14 #
15 #  Contributors:
16 #
17 # This program is free software; you can redistribute it and/or modify
18 # it under the terms of the GNU General Public License as published by
19 # the Free Software Foundation; either version 2 of the License, or
20 # (at your option) any later version.
21 #
22 # This program is distributed in the hope that it will be useful,
23 # but WITHOUT ANY WARRANTY; without even the implied warranty of
24 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25 # GNU General Public License for more details.
26 # You should have received a copy of the GNU General Public License
27 # along with this program; if not, write to the Free Software
28 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
29 #======================================================================
30 #
31 # Inventory invoicing module
32 #
33 #======================================================================
34
35 package IS;
36
37 use List::Util qw(max);
38
39 use SL::AM;
40 use SL::ARAP;
41 use SL::CVar;
42 use SL::Common;
43 use SL::DATEV qw(:CONSTANTS);
44 use SL::DBUtils;
45 use SL::DO;
46 use SL::GenericTranslations;
47 use SL::MoreCommon;
48 use SL::IC;
49 use SL::IO;
50 use SL::TransNumber;
51 use SL::DB::Default;
52 use SL::DB::Tax;
53 use Data::Dumper;
54
55 use strict;
56
57 sub invoice_details {
58   $main::lxdebug->enter_sub();
59
60   my ($self, $myconfig, $form, $locale) = @_;
61
62   $form->{duedate} ||= $form->{invdate};
63
64   # connect to database
65   my $dbh = $form->get_standard_dbh;
66   my $sth;
67
68   my $query = qq|SELECT date | . conv_dateq($form->{duedate}) . qq| - date | . conv_dateq($form->{invdate}) . qq| AS terms|;
69   ($form->{terms}) = selectrow_query($form, $dbh, $query);
70
71   my (@project_ids);
72   $form->{TEMPLATE_ARRAYS} = {};
73
74   push(@project_ids, $form->{"globalproject_id"}) if ($form->{"globalproject_id"});
75
76   $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS');
77   my %price_factors;
78
79   foreach my $pfac (@{ $form->{ALL_PRICE_FACTORS} }) {
80     $price_factors{$pfac->{id}}  = $pfac;
81     $pfac->{factor}             *= 1;
82     $pfac->{formatted_factor}    = $form->format_amount($myconfig, $pfac->{factor});
83   }
84
85   # sort items by partsgroup
86   for my $i (1 .. $form->{rowcount}) {
87 #    $partsgroup = "";
88 #    if ($form->{"partsgroup_$i"} && $form->{groupitems}) {
89 #      $partsgroup = $form->{"partsgroup_$i"};
90 #    }
91 #    push @partsgroup, [$i, $partsgroup];
92     push(@project_ids, $form->{"project_id_$i"}) if ($form->{"project_id_$i"});
93   }
94
95   my $projects = [];
96   my %projects_by_id;
97   if (@project_ids) {
98     $projects = SL::DB::Manager::Project->get_all(query => [ id => \@project_ids ]);
99     %projects_by_id = map { $_->id => $_ } @$projects;
100   }
101
102   if ($projects_by_id{$form->{"globalproject_id"}}) {
103     $form->{globalprojectnumber} = $projects_by_id{$form->{"globalproject_id"}}->projectnumber;
104     $form->{globalprojectdescription} = $projects_by_id{$form->{"globalproject_id"}}->description;
105
106     for (@{ $projects_by_id{$form->{"globalproject_id"}}->cvars_by_config }) {
107       $form->{"project_cvar_" . $_->config->name} = $_->value_as_text;
108     }
109   }
110
111   my $tax = 0;
112   my $item;
113   my $i;
114   my @partsgroup = ();
115   my $partsgroup;
116
117   # sort items by partsgroup
118   for $i (1 .. $form->{rowcount}) {
119     $partsgroup = "";
120     if ($form->{"partsgroup_$i"} && $form->{groupitems}) {
121       $partsgroup = $form->{"partsgroup_$i"};
122     }
123     push @partsgroup, [$i, $partsgroup];
124   }
125
126   my $sameitem = "";
127   my @taxaccounts;
128   my %taxaccounts;
129   my %taxbase;
130   my $taxrate;
131   my $taxamount;
132   my $taxbase;
133   my $taxdiff;
134   my $nodiscount;
135   my $yesdiscount;
136   my $nodiscount_subtotal = 0;
137   my $discount_subtotal = 0;
138   my $position = 0;
139   my $subtotal_header = 0;
140   my $subposition = 0;
141
142   $form->{discount} = [];
143
144   IC->prepare_parts_for_printing(myconfig => $myconfig, form => $form);
145
146   my $ic_cvar_configs = CVar->get_configs(module => 'IC');
147   my $project_cvar_configs = CVar->get_configs(module => 'Projects');
148
149   my @arrays =
150     qw(runningnumber number description longdescription qty ship unit bin
151        deliverydate_oe ordnumber_oe transdate_oe validuntil
152        partnotes serialnumber reqdate sellprice listprice netprice
153        discount p_discount discount_sub nodiscount_sub
154        linetotal  nodiscount_linetotal tax_rate projectnumber projectdescription
155        price_factor price_factor_name partsgroup weight lineweight);
156
157   push @arrays, map { "ic_cvar_$_->{name}" } @{ $ic_cvar_configs };
158   push @arrays, map { "project_cvar_$_->{name}" } @{ $project_cvar_configs };
159
160   my @tax_arrays = qw(taxbase tax taxdescription taxrate taxnumber);
161
162   my @payment_arrays = qw(payment paymentaccount paymentdate paymentsource paymentmemo);
163
164   map { $form->{TEMPLATE_ARRAYS}->{$_} = [] } (@arrays, @tax_arrays, @payment_arrays);
165
166   my $totalweight = 0;
167   foreach $item (sort { $a->[1] cmp $b->[1] } @partsgroup) {
168     $i = $item->[0];
169
170     if ($item->[1] ne $sameitem) {
171       push(@{ $form->{TEMPLATE_ARRAYS}->{description} }, qq|$item->[1]|);
172       $sameitem = $item->[1];
173
174       map({ push(@{ $form->{TEMPLATE_ARRAYS}->{$_} }, "") } grep({ $_ ne "description" } @arrays));
175     }
176
177     $form->{"qty_$i"} = $form->parse_amount($myconfig, $form->{"qty_$i"});
178
179     if ($form->{"id_$i"} != 0) {
180
181       # add number, description and qty to $form->{number},
182       if ($form->{"subtotal_$i"} && !$subtotal_header) {
183         $subtotal_header = $i;
184         $position = int($position);
185         $subposition = 0;
186         $position++;
187       } elsif ($subtotal_header) {
188         $subposition += 1;
189         $position = int($position);
190         $position = $position.".".$subposition;
191       } else {
192         $position = int($position);
193         $position++;
194       }
195
196       my $price_factor = $price_factors{$form->{"price_factor_id_$i"}} || { 'factor' => 1 };
197
198       push @{ $form->{TEMPLATE_ARRAYS}->{runningnumber} },     $position;
199       push @{ $form->{TEMPLATE_ARRAYS}->{number} },            $form->{"partnumber_$i"};
200       push @{ $form->{TEMPLATE_ARRAYS}->{serialnumber} },      $form->{"serialnumber_$i"};
201       push @{ $form->{TEMPLATE_ARRAYS}->{bin} },               $form->{"bin_$i"};
202       push @{ $form->{TEMPLATE_ARRAYS}->{partnotes} },         $form->{"partnotes_$i"};
203       push @{ $form->{TEMPLATE_ARRAYS}->{description} },       $form->{"description_$i"};
204       push @{ $form->{TEMPLATE_ARRAYS}->{longdescription} },   $form->{"longdescription_$i"};
205       push @{ $form->{TEMPLATE_ARRAYS}->{qty} },               $form->format_amount($myconfig, $form->{"qty_$i"});
206       push @{ $form->{TEMPLATE_ARRAYS}->{qty_nofmt} },         $form->{"qty_$i"};
207       push @{ $form->{TEMPLATE_ARRAYS}->{unit} },              $form->{"unit_$i"};
208       push @{ $form->{TEMPLATE_ARRAYS}->{deliverydate_oe} },   $form->{"reqdate_$i"};
209       push @{ $form->{TEMPLATE_ARRAYS}->{sellprice} },         $form->{"sellprice_$i"};
210       push @{ $form->{TEMPLATE_ARRAYS}->{sellprice_nofmt} },   $form->parse_amount($myconfig, $form->{"sellprice_$i"});
211       push @{ $form->{TEMPLATE_ARRAYS}->{ordnumber_oe} },      $form->{"ordnumber_$i"};
212       push @{ $form->{TEMPLATE_ARRAYS}->{transdate_oe} },      $form->{"transdate_$i"};
213       push @{ $form->{TEMPLATE_ARRAYS}->{invnumber} },         $form->{"invnumber"};
214       push @{ $form->{TEMPLATE_ARRAYS}->{invdate} },           $form->{"invdate"};
215       push @{ $form->{TEMPLATE_ARRAYS}->{price_factor} },      $price_factor->{formatted_factor};
216       push @{ $form->{TEMPLATE_ARRAYS}->{price_factor_name} }, $price_factor->{description};
217       push @{ $form->{TEMPLATE_ARRAYS}->{partsgroup} },        $form->{"partsgroup_$i"};
218       push @{ $form->{TEMPLATE_ARRAYS}->{reqdate} },           $form->{"reqdate_$i"};
219       push(@{ $form->{TEMPLATE_ARRAYS}->{listprice} },         $form->{"listprice_$i"});
220
221       my $sellprice     = $form->parse_amount($myconfig, $form->{"sellprice_$i"});
222       my ($dec)         = ($sellprice =~ /\.(\d+)/);
223       my $decimalplaces = max 2, length($dec);
224
225       my $parsed_discount            = $form->parse_amount($myconfig, $form->{"discount_$i"});
226
227       my $linetotal_exact            = $form->{"qty_$i"} * $sellprice * (100 - $parsed_discount) / 100 / $price_factor->{factor};
228       my $linetotal                  = $form->round_amount($linetotal_exact, 2);
229
230       my $nodiscount_exact_linetotal = $form->{"qty_$i"} * $sellprice                                  / $price_factor->{factor};
231       my $nodiscount_linetotal       = $form->round_amount($nodiscount_exact_linetotal,2);
232
233       my $discount                   = $nodiscount_linetotal - $linetotal; # is always rounded because $nodiscount_linetotal and $linetotal are rounded
234
235       my $discount_round_error       = $discount + ($linetotal_exact - $nodiscount_exact_linetotal); # not used
236
237       $form->{"netprice_$i"}   = $form->round_amount($form->{"qty_$i"} ? ($linetotal / $form->{"qty_$i"}) : 0, 2);
238
239       push @{ $form->{TEMPLATE_ARRAYS}->{netprice} },       ($form->{"netprice_$i"} != 0) ? $form->format_amount($myconfig, $form->{"netprice_$i"}, $decimalplaces) : '';
240       push @{ $form->{TEMPLATE_ARRAYS}->{netprice_nofmt} }, ($form->{"netprice_$i"} != 0) ? $form->{"netprice_$i"} : '';
241
242       $linetotal = ($linetotal != 0) ? $linetotal : '';
243
244       push @{ $form->{TEMPLATE_ARRAYS}->{discount} },       ($discount != 0) ? $form->format_amount($myconfig, $discount * -1, 2) : '';
245       push @{ $form->{TEMPLATE_ARRAYS}->{discount_nofmt} }, ($discount != 0) ? $discount * -1 : '';
246       push @{ $form->{TEMPLATE_ARRAYS}->{p_discount} },     $form->{"discount_$i"};
247
248       $form->{total}            += $linetotal;
249       $form->{nodiscount_total} += $nodiscount_linetotal;
250       $form->{discount_total}   += $discount;
251
252       if ($subtotal_header) {
253         $discount_subtotal   += $linetotal;
254         $nodiscount_subtotal += $nodiscount_linetotal;
255       }
256
257       if ($form->{"subtotal_$i"} && $subtotal_header && ($subtotal_header != $i)) {
258         push @{ $form->{TEMPLATE_ARRAYS}->{discount_sub} },         $form->format_amount($myconfig, $discount_subtotal,   2);
259         push @{ $form->{TEMPLATE_ARRAYS}->{discount_sub_nofmt} },   $discount_subtotal;
260         push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_sub} },       $form->format_amount($myconfig, $nodiscount_subtotal, 2);
261         push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_sub_nofmt} }, $nodiscount_subtotal;
262
263         $discount_subtotal   = 0;
264         $nodiscount_subtotal = 0;
265         $subtotal_header     = 0;
266
267       } else {
268         push @{ $form->{TEMPLATE_ARRAYS}->{$_} }, "" for qw(discount_sub nodiscount_sub discount_sub_nofmt nodiscount_sub_nofmt);
269       }
270
271       if (!$form->{"discount_$i"}) {
272         $nodiscount += $linetotal;
273       }
274
275       push @{ $form->{TEMPLATE_ARRAYS}->{linetotal} },                  $form->format_amount($myconfig, $linetotal, 2);
276       push @{ $form->{TEMPLATE_ARRAYS}->{linetotal_nofmt} },            $linetotal_exact;
277       push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_linetotal} },       $form->format_amount($myconfig, $nodiscount_linetotal, 2);
278       push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_linetotal_nofmt} }, $nodiscount_linetotal;
279
280       my $project = $projects_by_id{$form->{"project_id_$i"}} || SL::DB::Project->new;
281
282       push @{ $form->{TEMPLATE_ARRAYS}->{projectnumber} },              $project->projectnumber;
283       push @{ $form->{TEMPLATE_ARRAYS}->{projectdescription} },         $project->description;
284
285       my $lineweight = $form->{"qty_$i"} * $form->{"weight_$i"};
286       $totalweight += $lineweight;
287       push @{ $form->{TEMPLATE_ARRAYS}->{weight} },            $form->format_amount($myconfig, $form->{"weight_$i"}, 3);
288       push @{ $form->{TEMPLATE_ARRAYS}->{weight_nofmt} },      $form->{"weight_$i"};
289       push @{ $form->{TEMPLATE_ARRAYS}->{lineweight} },        $form->format_amount($myconfig, $lineweight, 3);
290       push @{ $form->{TEMPLATE_ARRAYS}->{lineweight_nofmt} },  $lineweight;
291
292       @taxaccounts = split(/ /, $form->{"taxaccounts_$i"});
293       $taxrate     = 0;
294       $taxdiff     = 0;
295
296       map { $taxrate += $form->{"${_}_rate"} } @taxaccounts;
297
298       if ($form->{taxincluded}) {
299
300         # calculate tax
301         $taxamount = $linetotal * $taxrate / (1 + $taxrate);
302         $taxbase = $linetotal - $taxamount;
303       } else {
304         $taxamount = $linetotal * $taxrate;
305         $taxbase   = $linetotal;
306       }
307
308       if ($form->round_amount($taxrate, 7) == 0) {
309         if ($form->{taxincluded}) {
310           foreach my $accno (@taxaccounts) {
311             $taxamount            = $form->round_amount($linetotal * $form->{"${accno}_rate"} / (1 + abs($form->{"${accno}_rate"})), 2);
312
313             $taxaccounts{$accno} += $taxamount;
314             $taxdiff             += $taxamount;
315
316             $taxbase{$accno}     += $taxbase;
317           }
318           $taxaccounts{ $taxaccounts[0] } += $taxdiff;
319         } else {
320           foreach my $accno (@taxaccounts) {
321             $taxaccounts{$accno} += $linetotal * $form->{"${accno}_rate"};
322             $taxbase{$accno}     += $taxbase;
323           }
324         }
325       } else {
326         foreach my $accno (@taxaccounts) {
327           $taxaccounts{$accno} += $taxamount * $form->{"${accno}_rate"} / $taxrate;
328           $taxbase{$accno}     += $taxbase;
329         }
330       }
331       my $tax_rate = $taxrate * 100;
332       push(@{ $form->{TEMPLATE_ARRAYS}->{tax_rate} }, qq|$tax_rate|);
333       if ($form->{"assembly_$i"}) {
334         $sameitem = "";
335
336         # get parts and push them onto the stack
337         my $sortorder = "";
338         if ($form->{groupitems}) {
339           $sortorder =
340             qq|ORDER BY pg.partsgroup, a.oid|;
341         } else {
342           $sortorder = qq|ORDER BY a.oid|;
343         }
344
345         $query =
346           qq|SELECT p.partnumber, p.description, p.unit, a.qty, pg.partsgroup
347              FROM assembly a
348              JOIN parts p ON (a.parts_id = p.id)
349              LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id)
350              WHERE (a.bom = '1') AND (a.id = ?) $sortorder|;
351         $sth = prepare_execute_query($form, $dbh, $query, conv_i($form->{"id_$i"}));
352
353         while (my $ref = $sth->fetchrow_hashref('NAME_lc')) {
354           if ($form->{groupitems} && $ref->{partsgroup} ne $sameitem) {
355             map({ push(@{ $form->{TEMPLATE_ARRAYS}->{$_} }, "") } grep({ $_ ne "description" } @arrays));
356             $sameitem = ($ref->{partsgroup}) ? $ref->{partsgroup} : "--";
357             push(@{ $form->{TEMPLATE_ARRAYS}->{description} }, $sameitem);
358           }
359
360           map { $form->{"a_$_"} = $ref->{$_} } qw(partnumber description);
361
362           push(@{ $form->{TEMPLATE_ARRAYS}->{description} },
363                $form->format_amount($myconfig, $ref->{qty} * $form->{"qty_$i"}
364                  )
365                  . qq| -- $form->{"a_partnumber"}, $form->{"a_description"}|);
366           map({ push(@{ $form->{TEMPLATE_ARRAYS}->{$_} }, "") } grep({ $_ ne "description" } @arrays));
367
368         }
369         $sth->finish;
370       }
371
372       push @{ $form->{TEMPLATE_ARRAYS}->{"ic_cvar_$_->{name}"} },
373         CVar->format_to_template(CVar->parse($form->{"ic_cvar_$_->{name}_$i"}, $_), $_)
374           for @{ $ic_cvar_configs };
375
376       push @{ $form->{TEMPLATE_ARRAYS}->{"project_cvar_" . $_->config->name} }, $_->value_as_text for @{ $project->cvars_by_config };
377     }
378   }
379
380   $form->{totalweight}       = $form->format_amount($myconfig, $totalweight, 3);
381   $form->{totalweight_nofmt} = $totalweight;
382   my $defaults = AM->get_defaults();
383   $form->{weightunit}        = $defaults->{weightunit};
384
385   foreach my $item (sort keys %taxaccounts) {
386     $tax += $taxamount = $form->round_amount($taxaccounts{$item}, 2);
387
388     push(@{ $form->{TEMPLATE_ARRAYS}->{taxbase} },        $form->format_amount($myconfig, $taxbase{$item}, 2));
389     push(@{ $form->{TEMPLATE_ARRAYS}->{taxbase_nofmt} },  $taxbase{$item});
390     push(@{ $form->{TEMPLATE_ARRAYS}->{tax} },            $form->format_amount($myconfig, $taxamount,      2));
391     push(@{ $form->{TEMPLATE_ARRAYS}->{tax_nofmt} },      $taxamount );
392     push(@{ $form->{TEMPLATE_ARRAYS}->{taxrate} },        $form->format_amount($myconfig, $form->{"${item}_rate"} * 100));
393     push(@{ $form->{TEMPLATE_ARRAYS}->{taxrate_nofmt} },  $form->{"${item}_rate"} * 100);
394     push(@{ $form->{TEMPLATE_ARRAYS}->{taxnumber} },      $form->{"${item}_taxnumber"});
395
396     my $tax_obj     = SL::DB::Manager::Tax->find_by(taxnumber => $form->{"${item}_taxnumber"});
397     my $description = $tax_obj->translated_attribute('taxdescription',  $form->{language_id}, 0) if $tax_obj;
398     push(@{ $form->{TEMPLATE_ARRAYS}->{taxdescription} }, $description . q{ } . 100 * $form->{"${item}_rate"} . q{%});
399   }
400
401   for my $i (1 .. $form->{paidaccounts}) {
402     if ($form->{"paid_$i"}) {
403       my ($accno, $description) = split(/--/, $form->{"AR_paid_$i"});
404
405       push(@{ $form->{TEMPLATE_ARRAYS}->{payment} },        $form->{"paid_$i"});
406       push(@{ $form->{TEMPLATE_ARRAYS}->{paymentaccount} }, $description);
407       push(@{ $form->{TEMPLATE_ARRAYS}->{paymentdate} },    $form->{"datepaid_$i"});
408       push(@{ $form->{TEMPLATE_ARRAYS}->{paymentsource} },  $form->{"source_$i"});
409       push(@{ $form->{TEMPLATE_ARRAYS}->{paymentmemo} },    $form->{"memo_$i"});
410
411       $form->{paid} += $form->parse_amount($myconfig, $form->{"paid_$i"});
412     }
413   }
414   if($form->{taxincluded}) {
415     $form->{subtotal}       = $form->format_amount($myconfig, $form->{total} - $tax, 2);
416     $form->{subtotal_nofmt} = $form->{total} - $tax;
417   }
418   else {
419     $form->{subtotal}       = $form->format_amount($myconfig, $form->{total}, 2);
420     $form->{subtotal_nofmt} = $form->{total};
421   }
422
423   $form->{nodiscount_subtotal} = $form->format_amount($myconfig, $form->{nodiscount_total}, 2);
424   $form->{discount_total}      = $form->format_amount($myconfig, $form->{discount_total}, 2);
425   $form->{nodiscount}          = $form->format_amount($myconfig, $nodiscount, 2);
426   $form->{yesdiscount}         = $form->format_amount($myconfig, $form->{nodiscount_total} - $nodiscount, 2);
427
428   $form->{invtotal} = ($form->{taxincluded}) ? $form->{total} : $form->{total} + $tax;
429   $form->{total}    = $form->format_amount($myconfig, $form->{invtotal} - $form->{paid}, 2);
430
431   $form->{invtotal} = $form->format_amount($myconfig, $form->{invtotal}, 2);
432   $form->{paid}     = $form->format_amount($myconfig, $form->{paid}, 2);
433
434   $form->set_payment_options($myconfig, $form->{invdate});
435
436   $form->{delivery_term} = SL::DB::Manager::DeliveryTerm->find_by(id => $form->{delivery_term_id} || undef);
437   $form->{delivery_term}->description_long($form->{delivery_term}->translated_attribute('description_long', $form->{language_id})) if $form->{delivery_term} && $form->{language_id};
438
439   $form->{username} = $myconfig->{name};
440
441   $main::lxdebug->leave_sub();
442 }
443
444 sub project_description {
445   $main::lxdebug->enter_sub();
446
447   my ($self, $dbh, $id) = @_;
448   my $form = \%main::form;
449
450   my $query = qq|SELECT description FROM project WHERE id = ?|;
451   my ($description) = selectrow_query($form, $dbh, $query, conv_i($id));
452
453   $main::lxdebug->leave_sub();
454
455   return $_;
456 }
457
458 sub customer_details {
459   $main::lxdebug->enter_sub();
460
461   my ($self, $myconfig, $form, @wanted_vars) = @_;
462
463   # connect to database
464   my $dbh = $form->get_standard_dbh;
465
466   my $language_id = $form->{language_id};
467
468   # get contact id, set it if nessessary
469   $form->{cp_id} *= 1;
470
471   my @values =  (conv_i($form->{customer_id}));
472
473   my $where = "";
474   if ($form->{cp_id}) {
475     $where = qq| AND (cp.cp_id = ?) |;
476     push(@values, conv_i($form->{cp_id}));
477   }
478
479   # get rest for the customer
480   my $query =
481     qq|SELECT ct.*, cp.*, ct.notes as customernotes,
482          ct.phone AS customerphone, ct.fax AS customerfax, ct.email AS customeremail,
483          cu.name AS currency
484        FROM customer ct
485        LEFT JOIN contacts cp on ct.id = cp.cp_cv_id
486        LEFT JOIN currencies cu ON (ct.currency_id = cu.id)
487        WHERE (ct.id = ?) $where
488        ORDER BY cp.cp_id
489        LIMIT 1|;
490   my $ref = selectfirst_hashref_query($form, $dbh, $query, @values);
491
492   # remove id and taxincluded before copy back
493   delete @$ref{qw(id taxincluded)};
494
495   @wanted_vars = grep({ $_ } @wanted_vars);
496   if (scalar(@wanted_vars) > 0) {
497     my %h_wanted_vars;
498     map({ $h_wanted_vars{$_} = 1; } @wanted_vars);
499     map({ delete($ref->{$_}) unless ($h_wanted_vars{$_}); } keys(%{$ref}));
500   }
501
502   map { $form->{$_} = $ref->{$_} } keys %$ref;
503
504   if ($form->{delivery_customer_id}) {
505     $query =
506       qq|SELECT *, notes as customernotes
507          FROM customer
508          WHERE id = ?
509          LIMIT 1|;
510     $ref = selectfirst_hashref_query($form, $dbh, $query, conv_i($form->{delivery_customer_id}));
511
512     map { $form->{"dc_$_"} = $ref->{$_} } keys %$ref;
513   }
514
515   if ($form->{delivery_vendor_id}) {
516     $query =
517       qq|SELECT *, notes as customernotes
518          FROM customer
519          WHERE id = ?
520          LIMIT 1|;
521     $ref = selectfirst_hashref_query($form, $dbh, $query, conv_i($form->{delivery_vendor_id}));
522
523     map { $form->{"dv_$_"} = $ref->{$_} } keys %$ref;
524   }
525
526   my $custom_variables = CVar->get_custom_variables('dbh'      => $dbh,
527                                                     'module'   => 'CT',
528                                                     'trans_id' => $form->{customer_id});
529   map { $form->{"vc_cvar_$_->{name}"} = $_->{value} } @{ $custom_variables };
530
531   $form->{cp_greeting} = GenericTranslations->get('dbh'              => $dbh,
532                                                   'translation_type' => 'greetings::' . ($form->{cp_gender} eq 'f' ? 'female' : 'male'),
533                                                   'language_id'      => $language_id,
534                                                   'allow_fallback'   => 1);
535
536
537   $main::lxdebug->leave_sub();
538 }
539
540 sub post_invoice {
541   $main::lxdebug->enter_sub();
542
543   my ($self, $myconfig, $form, $provided_dbh, $payments_only) = @_;
544
545   # connect to database, turn off autocommit
546   my $dbh = $provided_dbh ? $provided_dbh : $form->get_standard_dbh;
547
548   my ($query, $sth, $null, $project_id, @values);
549   my $exchangerate = 0;
550
551   my $ic_cvar_configs = CVar->get_configs(module => 'IC',
552                                           dbh    => $dbh);
553
554   if (!$form->{employee_id}) {
555     $form->get_employee($dbh);
556   }
557
558   $form->{defaultcurrency} = $form->get_default_currency($myconfig);
559   my $defaultcurrency = $form->{defaultcurrency};
560
561   # Seit neuestem wird die department_id schon Ã¼bergeben UND $form->department nicht mehr
562   # korrekt zusammengebaut. Sehr wahrscheinlich beim Umstieg auf T8 kaputt gegangen
563   # Ich lass den Code von 2005 erstmal noch stehen ;-) jb 03-2011
564   if (!$form->{department_id}){
565     ($null, $form->{department_id}) = split(/--/, $form->{department});
566   }
567
568   my $all_units = AM->retrieve_units($myconfig, $form);
569
570   if (!$payments_only) {
571     if ($form->{id}) {
572       &reverse_invoice($dbh, $form);
573
574     } else {
575       my $trans_number   = SL::TransNumber->new(type => $form->{type}, dbh => $dbh, number => $form->{invnumber}, save => 1);
576       $form->{invnumber} = $trans_number->create_unique unless $trans_number->is_unique;
577
578       $query = qq|SELECT nextval('glid')|;
579       ($form->{"id"}) = selectrow_query($form, $dbh, $query);
580
581       $query = qq|INSERT INTO ar (id, invnumber, currency_id) VALUES (?, ?, (SELECT id FROM currencies WHERE name=?))|;
582       do_query($form, $dbh, $query, $form->{"id"}, $form->{"id"}, $form->{currency});
583
584       if (!$form->{invnumber}) {
585         $form->{invnumber} =
586           $form->update_defaults($myconfig, $form->{type} eq "credit_note" ?
587                                  "cnnumber" : "invnumber", $dbh);
588       }
589     }
590   }
591
592   my ($netamount, $invoicediff) = (0, 0);
593   my ($amount, $linetotal, $lastincomeaccno);
594
595   if ($form->{currency} eq $defaultcurrency) {
596     $form->{exchangerate} = 1;
597   } else {
598     $exchangerate = $form->check_exchangerate($myconfig, $form->{currency}, $form->{invdate}, 'buy');
599   }
600
601   $form->{exchangerate} =
602     ($exchangerate)
603     ? $exchangerate
604     : $form->parse_amount($myconfig, $form->{exchangerate});
605
606   $form->{expense_inventory} = "";
607
608   my %baseunits;
609
610   $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS');
611   my %price_factors = map { $_->{id} => $_->{factor} } @{ $form->{ALL_PRICE_FACTORS} };
612   my $price_factor;
613
614   $form->{amount}      = {};
615   $form->{amount_cogs} = {};
616
617   foreach my $i (1 .. $form->{rowcount}) {
618     if ($form->{type} eq "credit_note") {
619       $form->{"qty_$i"} = $form->parse_amount($myconfig, $form->{"qty_$i"}) * -1;
620       $form->{shipped} = 1;
621     } else {
622       $form->{"qty_$i"} = $form->parse_amount($myconfig, $form->{"qty_$i"});
623     }
624     my $basefactor;
625     my $baseqty;
626
627     $form->{"marge_percent_$i"} = $form->parse_amount($myconfig, $form->{"marge_percent_$i"}) * 1;
628     $form->{"marge_absolut_$i"} = $form->parse_amount($myconfig, $form->{"marge_absolut_$i"}) * 1;
629     $form->{"lastcost_$i"} = $form->parse_amount($myconfig, $form->{"lastcost_$i"}) * 1;
630
631     if ($form->{storno}) {
632       $form->{"qty_$i"} *= -1;
633     }
634
635     if ($form->{"id_$i"}) {
636       my $item_unit;
637
638       if (defined($baseunits{$form->{"id_$i"}})) {
639         $item_unit = $baseunits{$form->{"id_$i"}};
640       } else {
641         # get item baseunit
642         $query = qq|SELECT unit FROM parts WHERE id = ?|;
643         ($item_unit) = selectrow_query($form, $dbh, $query, conv_i($form->{"id_$i"}));
644         $baseunits{$form->{"id_$i"}} = $item_unit;
645       }
646
647       if (defined($all_units->{$item_unit}->{factor})
648           && ($all_units->{$item_unit}->{factor} ne '')
649           && ($all_units->{$item_unit}->{factor} != 0)) {
650         $basefactor = $all_units->{$form->{"unit_$i"}}->{factor} / $all_units->{$item_unit}->{factor};
651       } else {
652         $basefactor = 1;
653       }
654       $baseqty = $form->{"qty_$i"} * $basefactor;
655
656       my ($allocated, $taxrate) = (0, 0);
657       my $taxamount;
658
659       # add tax rates
660       map { $taxrate += $form->{"${_}_rate"} } split(/ /, $form->{"taxaccounts_$i"});
661
662       # keep entered selling price
663       my $fxsellprice =
664         $form->parse_amount($myconfig, $form->{"sellprice_$i"});
665
666       my ($dec) = ($fxsellprice =~ /\.(\d+)/);
667       $dec = length $dec;
668       my $decimalplaces = ($dec > 2) ? $dec : 2;
669
670       # undo discount formatting
671       $form->{"discount_$i"} = $form->parse_amount($myconfig, $form->{"discount_$i"}) / 100;
672
673       # deduct discount
674       $form->{"sellprice_$i"} = $fxsellprice * (1 - $form->{"discount_$i"});
675
676       # round linetotal to 2 decimal places
677       $price_factor = $price_factors{ $form->{"price_factor_id_$i"} } || 1;
678       $linetotal    = $form->round_amount($form->{"sellprice_$i"} * $form->{"qty_$i"} / $price_factor, 2);
679
680       if ($form->{taxincluded}) {
681         $taxamount = $linetotal * ($taxrate / (1 + $taxrate));
682         $form->{"sellprice_$i"} =
683           $form->{"sellprice_$i"} * (1 / (1 + $taxrate));
684       } else {
685         $taxamount = $linetotal * $taxrate;
686       }
687
688       $netamount += $linetotal;
689
690       if ($taxamount != 0) {
691         map {
692           $form->{amount}{ $form->{id} }{$_} +=
693             $taxamount * $form->{"${_}_rate"} / $taxrate
694         } split(/ /, $form->{"taxaccounts_$i"});
695       }
696
697       # add amount to income, $form->{amount}{trans_id}{accno}
698       $amount = $form->{"sellprice_$i"} * $form->{"qty_$i"} * $form->{exchangerate} / $price_factor;
699
700       $linetotal = $form->round_amount($form->{"sellprice_$i"} * $form->{"qty_$i"} / $price_factor, 2) * $form->{exchangerate};
701       $linetotal = $form->round_amount($linetotal, 2);
702
703       # this is the difference from the inventory
704       $invoicediff += ($amount - $linetotal);
705
706       $form->{amount}{ $form->{id} }{ $form->{"income_accno_$i"} } +=
707         $linetotal;
708
709       $lastincomeaccno = $form->{"income_accno_$i"};
710
711       # adjust and round sellprice
712       $form->{"sellprice_$i"} =
713         $form->round_amount($form->{"sellprice_$i"} * $form->{exchangerate},
714                             $decimalplaces);
715
716       next if $payments_only;
717
718       if ($form->{"inventory_accno_$i"} || $form->{"assembly_$i"}) {
719
720         if ($form->{"assembly_$i"}) {
721           # record assembly item as allocated
722           &process_assembly($dbh, $myconfig, $form, $form->{"id_$i"}, $baseqty);
723
724         } else {
725           $allocated = &cogs($dbh, $myconfig, $form, $form->{"id_$i"}, $baseqty, $basefactor, $i);
726         }
727       }
728
729       # Get pricegroup_id and save it. Unfortunately the interface
730       # also uses ID "0" for signalling that none is selected, but "0"
731       # must not be stored in the database. Therefore we cannot simply
732       # use conv_i().
733       ($null, my $pricegroup_id) = split(/--/, $form->{"sellprice_pg_$i"});
734       $pricegroup_id *= 1;
735       $pricegroup_id  = undef if !$pricegroup_id;
736
737       my ($invoice_id) = selectfirst_array_query($form, $dbh, qq|SELECT nextval('invoiceid')|);
738
739       # save detail record in invoice table
740       $query =
741         qq|INSERT INTO invoice (id, trans_id, parts_id, description, longdescription, qty,
742                                 sellprice, fxsellprice, discount, allocated, assemblyitem,
743                                 unit, deliverydate, project_id, serialnumber, pricegroup_id,
744                                 ordnumber, transdate, cusordnumber, base_qty, subtotal,
745                                 marge_percent, marge_total, lastcost,
746                                 price_factor_id, price_factor, marge_price_factor)
747            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
748                    (SELECT factor FROM price_factors WHERE id = ?), ?)|;
749
750       @values = ($invoice_id, conv_i($form->{id}), conv_i($form->{"id_$i"}),
751                  $form->{"description_$i"}, $form->{"longdescription_$i"}, $form->{"qty_$i"},
752                  $form->{"sellprice_$i"}, $fxsellprice,
753                  $form->{"discount_$i"}, $allocated, 'f',
754                  $form->{"unit_$i"}, conv_date($form->{"reqdate_$i"}), conv_i($form->{"project_id_$i"}),
755                  $form->{"serialnumber_$i"}, $pricegroup_id,
756                  $form->{"ordnumber_$i"}, conv_date($form->{"transdate_$i"}),
757                  $form->{"cusordnumber_$i"}, $baseqty, $form->{"subtotal_$i"} ? 't' : 'f',
758                  $form->{"marge_percent_$i"}, $form->{"marge_absolut_$i"},
759                  $form->{"lastcost_$i"},
760                  conv_i($form->{"price_factor_id_$i"}), conv_i($form->{"price_factor_id_$i"}),
761                  conv_i($form->{"marge_price_factor_$i"}));
762       do_query($form, $dbh, $query, @values);
763
764       CVar->save_custom_variables(module       => 'IC',
765                                   sub_module   => 'invoice',
766                                   trans_id     => $invoice_id,
767                                   configs      => $ic_cvar_configs,
768                                   variables    => $form,
769                                   name_prefix  => 'ic_',
770                                   name_postfix => "_$i",
771                                   dbh          => $dbh);
772     }
773   }
774
775   # total payments, don't move we need it here
776   for my $i (1 .. $form->{paidaccounts}) {
777     if ($form->{type} eq "credit_note") {
778       $form->{"paid_$i"} = $form->parse_amount($myconfig, $form->{"paid_$i"}) * -1;
779     } else {
780       $form->{"paid_$i"} = $form->parse_amount($myconfig, $form->{"paid_$i"});
781     }
782     $form->{paid} += $form->{"paid_$i"};
783     $form->{datepaid} = $form->{"datepaid_$i"} if ($form->{"datepaid_$i"});
784   }
785
786   my ($tax, $diff) = (0, 0);
787
788   $netamount = $form->round_amount($netamount, 2);
789
790   # figure out rounding errors for total amount vs netamount + taxes
791   if ($form->{taxincluded}) {
792
793     $amount = $form->round_amount($netamount * $form->{exchangerate}, 2);
794     $diff += $amount - $netamount * $form->{exchangerate};
795     $netamount = $amount;
796
797     foreach my $item (split(/ /, $form->{taxaccounts})) {
798       $amount = $form->{amount}{ $form->{id} }{$item} * $form->{exchangerate};
799       $form->{amount}{ $form->{id} }{$item} = $form->round_amount($amount, 2);
800       $tax += $form->{amount}{ $form->{id} }{$item};
801       $netamount -= $form->{amount}{ $form->{id} }{$item};
802     }
803
804     $invoicediff += $diff;
805     ######## this only applies to tax included
806     if ($lastincomeaccno) {
807       $form->{amount}{ $form->{id} }{$lastincomeaccno} += $invoicediff;
808     }
809
810   } else {
811     $amount    = $form->round_amount($netamount * $form->{exchangerate}, 2);
812     $diff      = $amount - $netamount * $form->{exchangerate};
813     $netamount = $amount;
814     foreach my $item (split(/ /, $form->{taxaccounts})) {
815       $form->{amount}{ $form->{id} }{$item} =
816         $form->round_amount($form->{amount}{ $form->{id} }{$item}, 2);
817       $amount =
818         $form->round_amount(
819                  $form->{amount}{ $form->{id} }{$item} * $form->{exchangerate},
820                  2);
821       $diff +=
822         $amount - $form->{amount}{ $form->{id} }{$item} *
823         $form->{exchangerate};
824       $form->{amount}{ $form->{id} }{$item} = $form->round_amount($amount, 2);
825       $tax += $form->{amount}{ $form->{id} }{$item};
826     }
827   }
828
829   $form->{amount}{ $form->{id} }{ $form->{AR} } = $netamount + $tax;
830   $form->{paid} =
831     $form->round_amount($form->{paid} * $form->{exchangerate} + $diff, 2);
832
833   # reverse AR
834   $form->{amount}{ $form->{id} }{ $form->{AR} } *= -1;
835
836   # update exchangerate
837   if (($form->{currency} ne $defaultcurrency) && !$exchangerate) {
838     $form->update_exchangerate($dbh, $form->{currency}, $form->{invdate},
839                                $form->{exchangerate}, 0);
840   }
841
842   $project_id = conv_i($form->{"globalproject_id"});
843   # entsprechend auch beim Bestimmen des Steuerschlüssels in Taxkey.pm berücksichtigen
844   my $taxdate = $form->{deliverydate} ? $form->{deliverydate} : $form->{invdate};
845
846   foreach my $trans_id (keys %{ $form->{amount_cogs} }) {
847     foreach my $accno (keys %{ $form->{amount_cogs}{$trans_id} }) {
848       next unless ($form->{expense_inventory} =~ /\Q$accno\E/);
849
850       $form->{amount_cogs}{$trans_id}{$accno} = $form->round_amount($form->{amount_cogs}{$trans_id}{$accno}, 2);
851
852       if (!$payments_only && ($form->{amount_cogs}{$trans_id}{$accno} != 0)) {
853         $query =
854           qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, tax_id, taxkey, project_id, chart_link)
855                VALUES (?, (SELECT id FROM chart WHERE accno = ?), ?, ?, (SELECT id FROM tax WHERE taxkey=0), 0, ?, (SELECT link FROM chart WHERE accno = ?))|;
856         @values = (conv_i($trans_id), $accno, $form->{amount_cogs}{$trans_id}{$accno}, conv_date($form->{invdate}), conv_i($project_id), $accno);
857         do_query($form, $dbh, $query, @values);
858         $form->{amount_cogs}{$trans_id}{$accno} = 0;
859       }
860     }
861
862     foreach my $accno (keys %{ $form->{amount_cogs}{$trans_id} }) {
863       $form->{amount_cogs}{$trans_id}{$accno} = $form->round_amount($form->{amount_cogs}{$trans_id}{$accno}, 2);
864
865       if (!$payments_only && ($form->{amount_cogs}{$trans_id}{$accno} != 0)) {
866         $query =
867           qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, tax_id, taxkey, project_id, chart_link)
868                VALUES (?, (SELECT id FROM chart WHERE accno = ?), ?, ?, (SELECT id FROM tax WHERE taxkey=0), 0, ?, (SELECT link FROM chart WHERE accno = ?))|;
869         @values = (conv_i($trans_id), $accno, $form->{amount_cogs}{$trans_id}{$accno}, conv_date($form->{invdate}), conv_i($project_id), $accno);
870         do_query($form, $dbh, $query, @values);
871       }
872     }
873   }
874
875   foreach my $trans_id (keys %{ $form->{amount} }) {
876     foreach my $accno (keys %{ $form->{amount}{$trans_id} }) {
877       next unless ($form->{expense_inventory} =~ /\Q$accno\E/);
878
879       $form->{amount}{$trans_id}{$accno} = $form->round_amount($form->{amount}{$trans_id}{$accno}, 2);
880
881       if (!$payments_only && ($form->{amount}{$trans_id}{$accno} != 0)) {
882         $query =
883           qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, tax_id, taxkey, project_id, chart_link)
884              VALUES (?, (SELECT id FROM chart WHERE accno = ?), ?, ?,
885                      (SELECT tax_id
886                       FROM taxkeys
887                       WHERE chart_id= (SELECT id
888                                        FROM chart
889                                        WHERE accno = ?)
890                       AND startdate <= ?
891                       ORDER BY startdate DESC LIMIT 1),
892                      (SELECT taxkey_id
893                       FROM taxkeys
894                       WHERE chart_id= (SELECT id
895                                        FROM chart
896                                        WHERE accno = ?)
897                       AND startdate <= ?
898                       ORDER BY startdate DESC LIMIT 1),
899                      ?,
900                      (SELECT link FROM chart WHERE accno = ?))|;
901         @values = (conv_i($trans_id), $accno, $form->{amount}{$trans_id}{$accno}, conv_date($form->{invdate}), $accno, conv_date($taxdate), $accno, conv_date($taxdate), conv_i($project_id), $accno);
902         do_query($form, $dbh, $query, @values);
903         $form->{amount}{$trans_id}{$accno} = 0;
904       }
905     }
906
907     foreach my $accno (keys %{ $form->{amount}{$trans_id} }) {
908       $form->{amount}{$trans_id}{$accno} = $form->round_amount($form->{amount}{$trans_id}{$accno}, 2);
909
910       if (!$payments_only && ($form->{amount}{$trans_id}{$accno} != 0)) {
911         $query =
912           qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, tax_id, taxkey, project_id, chart_link)
913              VALUES (?, (SELECT id FROM chart WHERE accno = ?), ?, ?,
914                      (SELECT tax_id
915                       FROM taxkeys
916                       WHERE chart_id= (SELECT id
917                                        FROM chart
918                                        WHERE accno = ?)
919                       AND startdate <= ?
920                       ORDER BY startdate DESC LIMIT 1),
921                      (SELECT taxkey_id
922                       FROM taxkeys
923                       WHERE chart_id= (SELECT id
924                                        FROM chart
925                                        WHERE accno = ?)
926                       AND startdate <= ?
927                       ORDER BY startdate DESC LIMIT 1),
928                      ?,
929                      (SELECT link FROM chart WHERE accno = ?))|;
930         @values = (conv_i($trans_id), $accno, $form->{amount}{$trans_id}{$accno}, conv_date($form->{invdate}), $accno, conv_date($taxdate), $accno, conv_date($taxdate), conv_i($project_id), $accno);
931         do_query($form, $dbh, $query, @values);
932       }
933     }
934   }
935
936   # deduct payment differences from diff
937   for my $i (1 .. $form->{paidaccounts}) {
938     if ($form->{"paid_$i"} != 0) {
939       $amount =
940         $form->round_amount($form->{"paid_$i"} * $form->{exchangerate}, 2);
941       $diff -= $amount - $form->{"paid_$i"} * $form->{exchangerate};
942     }
943   }
944
945   # record payments and offsetting AR
946   if (!$form->{storno}) {
947     for my $i (1 .. $form->{paidaccounts}) {
948
949       if ($form->{"acc_trans_id_$i"}
950           && $payments_only
951           && (SL::DB::Default->get->payments_changeable == 0)) {
952         next;
953       }
954
955       next if ($form->{"paid_$i"} == 0);
956
957       my ($accno) = split(/--/, $form->{"AR_paid_$i"});
958       $form->{"datepaid_$i"} = $form->{invdate}
959       unless ($form->{"datepaid_$i"});
960       $form->{datepaid} = $form->{"datepaid_$i"};
961
962       $exchangerate = 0;
963
964       if ($form->{currency} eq $defaultcurrency) {
965         $form->{"exchangerate_$i"} = 1;
966       } else {
967         $exchangerate              = $form->check_exchangerate($myconfig, $form->{currency}, $form->{"datepaid_$i"}, 'buy');
968         $form->{"exchangerate_$i"} = $exchangerate || $form->parse_amount($myconfig, $form->{"exchangerate_$i"});
969       }
970
971       # record AR
972       $amount = $form->round_amount($form->{"paid_$i"} * $form->{exchangerate} + $diff, 2);
973
974       if ($form->{amount}{ $form->{id} }{ $form->{AR} } != 0) {
975         $query =
976         qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, tax_id, taxkey, project_id, chart_link)
977            VALUES (?, (SELECT id FROM chart WHERE accno = ?), ?, ?,
978                    (SELECT tax_id
979                     FROM taxkeys
980                     WHERE chart_id= (SELECT id
981                                      FROM chart
982                                      WHERE accno = ?)
983                     AND startdate <= ?
984                     ORDER BY startdate DESC LIMIT 1),
985                    (SELECT taxkey_id
986                     FROM taxkeys
987                     WHERE chart_id= (SELECT id
988                                      FROM chart
989                                      WHERE accno = ?)
990                     AND startdate <= ?
991                     ORDER BY startdate DESC LIMIT 1),
992                    ?,
993                    (SELECT link FROM chart WHERE accno = ?))|;
994         @values = (conv_i($form->{"id"}), $form->{AR}, $amount, $form->{"datepaid_$i"}, $form->{AR}, conv_date($taxdate), $form->{AR}, conv_date($taxdate), $project_id, $form->{AR});
995         do_query($form, $dbh, $query, @values);
996       }
997
998       # record payment
999       $form->{"paid_$i"} *= -1;
1000       my $gldate = (conv_date($form->{"gldate_$i"}))? conv_date($form->{"gldate_$i"}) : conv_date($form->current_date($myconfig));
1001
1002       $query =
1003       qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, gldate, source, memo, tax_id, taxkey, project_id, chart_link)
1004          VALUES (?, (SELECT id FROM chart WHERE accno = ?), ?, ?, ?, ?, ?,
1005                  (SELECT tax_id
1006                   FROM taxkeys
1007                   WHERE chart_id= (SELECT id
1008                                    FROM chart
1009                                    WHERE accno = ?)
1010                   AND startdate <= ?
1011                   ORDER BY startdate DESC LIMIT 1),
1012                  (SELECT taxkey_id
1013                   FROM taxkeys
1014                   WHERE chart_id= (SELECT id
1015                                    FROM chart
1016                                    WHERE accno = ?)
1017                   AND startdate <= ?
1018                   ORDER BY startdate DESC LIMIT 1),
1019                  ?,
1020                  (SELECT link FROM chart WHERE accno = ?))|;
1021       @values = (conv_i($form->{"id"}), $accno, $form->{"paid_$i"}, $form->{"datepaid_$i"},
1022                  $gldate, $form->{"source_$i"}, $form->{"memo_$i"}, $accno, conv_date($taxdate), $accno, conv_date($taxdate), $project_id, $accno);
1023       do_query($form, $dbh, $query, @values);
1024
1025       # exchangerate difference
1026       $form->{fx}{$accno}{ $form->{"datepaid_$i"} } +=
1027         $form->{"paid_$i"} * ($form->{"exchangerate_$i"} - 1) + $diff;
1028
1029       # gain/loss
1030       $amount =
1031         $form->{"paid_$i"} * $form->{exchangerate} - $form->{"paid_$i"} *
1032         $form->{"exchangerate_$i"};
1033       if ($amount > 0) {
1034         $form->{fx}{ $form->{fxgain_accno} }{ $form->{"datepaid_$i"} } += $amount;
1035       } else {
1036         $form->{fx}{ $form->{fxloss_accno} }{ $form->{"datepaid_$i"} } += $amount;
1037       }
1038
1039       $diff = 0;
1040
1041       # update exchange rate
1042       if (($form->{currency} ne $defaultcurrency) && !$exchangerate) {
1043         $form->update_exchangerate($dbh, $form->{currency},
1044                                    $form->{"datepaid_$i"},
1045                                    $form->{"exchangerate_$i"}, 0);
1046       }
1047     }
1048
1049   } else {                      # if (!$form->{storno})
1050     $form->{marge_total} *= -1;
1051   }
1052
1053   IO->set_datepaid(table => 'ar', id => $form->{id}, dbh => $dbh);
1054
1055   # record exchange rate differences and gains/losses
1056   foreach my $accno (keys %{ $form->{fx} }) {
1057     foreach my $transdate (keys %{ $form->{fx}{$accno} }) {
1058       $form->{fx}{$accno}{$transdate} = $form->round_amount($form->{fx}{$accno}{$transdate}, 2);
1059       if ( $form->{fx}{$accno}{$transdate} != 0 ) {
1060
1061         $query =
1062           qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, cleared, fx_transaction, tax_id, taxkey, project_id, chart_link)
1063              VALUES (?, (SELECT id FROM chart WHERE accno = ?), ?, ?, '0', '1',
1064                  (SELECT tax_id
1065                   FROM taxkeys
1066                   WHERE chart_id= (SELECT id
1067                                    FROM chart
1068                                    WHERE accno = ?)
1069                   AND startdate <= ?
1070                   ORDER BY startdate DESC LIMIT 1),
1071                  (SELECT taxkey_id
1072                   FROM taxkeys
1073                   WHERE chart_id= (SELECT id
1074                                    FROM chart
1075                                    WHERE accno = ?)
1076                   AND startdate <= ?
1077                   ORDER BY startdate DESC LIMIT 1),
1078                  ?,
1079                  (SELECT link FROM chart WHERE accno = ?))|;
1080         @values = (conv_i($form->{"id"}), $accno, $form->{fx}{$accno}{$transdate}, conv_date($transdate), $accno, conv_date($taxdate), $accno, conv_date($taxdate), conv_i($project_id), $accno);
1081         do_query($form, $dbh, $query, @values);
1082       }
1083     }
1084   }
1085
1086   if ($payments_only) {
1087     $query = qq|UPDATE ar SET paid = ? WHERE id = ?|;
1088     do_query($form, $dbh, $query,  $form->{paid}, conv_i($form->{id}));
1089
1090     $dbh->commit if !$provided_dbh;
1091
1092     $main::lxdebug->leave_sub();
1093     return;
1094   }
1095
1096   $amount = $netamount + $tax;
1097
1098   # save AR record
1099   #erweiterung fuer lieferscheinnummer (donumber) 12.02.09 jb
1100
1101   $query = qq|UPDATE ar set
1102                 invnumber   = ?, ordnumber     = ?, quonumber     = ?, cusordnumber  = ?,
1103                 transdate   = ?, orddate       = ?, quodate       = ?, customer_id   = ?,
1104                 amount      = ?, netamount     = ?, paid          = ?,
1105                 duedate     = ?, deliverydate  = ?, invoice       = ?, shippingpoint = ?,
1106                 shipvia     = ?, terms         = ?, notes         = ?, intnotes      = ?,
1107                 currency_id = (SELECT id FROM currencies WHERE name = ?),
1108                 department_id = ?, payment_id    = ?, taxincluded   = ?,
1109                 type        = ?, language_id   = ?, taxzone_id    = ?, shipto_id     = ?,
1110                 employee_id = ?, salesman_id   = ?, storno_id     = ?, storno        = ?,
1111                 cp_id       = ?, marge_total   = ?, marge_percent = ?,
1112                 globalproject_id               = ?, delivery_customer_id             = ?,
1113                 transaction_description        = ?, delivery_vendor_id               = ?,
1114                 donumber    = ?, invnumber_for_credit_note = ?,        direct_debit  = ?,
1115                 delivery_term_id = ?
1116               WHERE id = ?|;
1117   @values = (          $form->{"invnumber"},           $form->{"ordnumber"},             $form->{"quonumber"},          $form->{"cusordnumber"},
1118              conv_date($form->{"invdate"}),  conv_date($form->{"orddate"}),    conv_date($form->{"quodate"}),    conv_i($form->{"customer_id"}),
1119                        $amount,                        $netamount,                       $form->{"paid"},
1120              conv_date($form->{"duedate"}),  conv_date($form->{"deliverydate"}),    '1',                                $form->{"shippingpoint"},
1121                        $form->{"shipvia"},      conv_i($form->{"terms"}),                $form->{"notes"},              $form->{"intnotes"},
1122                        $form->{"currency"},     conv_i($form->{"department_id"}), conv_i($form->{"payment_id"}),        $form->{"taxincluded"} ? 't' : 'f',
1123                        $form->{"type"},         conv_i($form->{"language_id"}),   conv_i($form->{"taxzone_id"}), conv_i($form->{"shipto_id"}),
1124                 conv_i($form->{"employee_id"}), conv_i($form->{"salesman_id"}),   conv_i($form->{storno_id}),           $form->{"storno"} ? 't' : 'f',
1125                 conv_i($form->{"cp_id"}),            1 * $form->{marge_total} ,      1 * $form->{marge_percent},
1126                 conv_i($form->{"globalproject_id"}),                              conv_i($form->{"delivery_customer_id"}),
1127                        $form->{transaction_description},                          conv_i($form->{"delivery_vendor_id"}),
1128                        $form->{"donumber"}, $form->{"invnumber_for_credit_note"},        $form->{direct_debit} ? 't' : 'f',
1129                 conv_i($form->{delivery_term_id}),
1130                 conv_i($form->{"id"}));
1131   do_query($form, $dbh, $query, @values);
1132
1133
1134   if ($form->{storno}) {
1135     $query =
1136       qq!UPDATE ar SET
1137            paid = paid + amount,
1138            storno = 't',
1139            intnotes = ? || intnotes
1140          WHERE id = ?!;
1141     do_query($form, $dbh, $query, "Rechnung storniert am $form->{invdate} ", conv_i($form->{"storno_id"}));
1142     do_query($form, $dbh, qq|UPDATE ar SET paid = amount WHERE id = ?|, conv_i($form->{"id"}));
1143   }
1144
1145   # add shipto
1146   $form->{name} = $form->{customer};
1147   $form->{name} =~ s/--\Q$form->{customer_id}\E//;
1148
1149   if (!$form->{shipto_id}) {
1150     $form->add_shipto($dbh, $form->{id}, "AR");
1151   }
1152
1153   # save printed, emailed and queued
1154   $form->save_status($dbh);
1155
1156   Common::webdav_folder($form);
1157
1158   # Link this record to the records it was created from.
1159   RecordLinks->create_links('dbh'        => $dbh,
1160                             'mode'       => 'ids',
1161                             'from_table' => 'oe',
1162                             'from_ids'   => $form->{convert_from_oe_ids},
1163                             'to_table'   => 'ar',
1164                             'to_id'      => $form->{id},
1165     );
1166   delete $form->{convert_from_oe_ids};
1167
1168   my @convert_from_do_ids = map { $_ * 1 } grep { $_ } split m/\s+/, $form->{convert_from_do_ids};
1169
1170   if (scalar @convert_from_do_ids) {
1171     DO->close_orders('dbh' => $dbh,
1172                      'ids' => \@convert_from_do_ids);
1173
1174     RecordLinks->create_links('dbh'        => $dbh,
1175                               'mode'       => 'ids',
1176                               'from_table' => 'delivery_orders',
1177                               'from_ids'   => \@convert_from_do_ids,
1178                               'to_table'   => 'ar',
1179                               'to_id'      => $form->{id},
1180       );
1181   }
1182   delete $form->{convert_from_do_ids};
1183
1184   ARAP->close_orders_if_billed('dbh'     => $dbh,
1185                                'arap_id' => $form->{id},
1186                                'table'   => 'ar',);
1187
1188   # safety check datev export
1189   if ($::instance_conf->get_datev_check_on_sales_invoice) {
1190     my $transdate = $::form->{invdate} ? DateTime->from_lxoffice($::form->{invdate}) : undef;
1191     $transdate  ||= DateTime->today;
1192
1193     my $datev = SL::DATEV->new(
1194       exporttype => DATEV_ET_BUCHUNGEN,
1195       format     => DATEV_FORMAT_KNE,
1196       dbh        => $dbh,
1197       from       => $transdate,
1198       to         => $transdate,
1199       trans_id   => $form->{id},
1200     );
1201
1202     $datev->export;
1203
1204     if ($datev->errors) {
1205       $dbh->rollback;
1206       die join "\n", $::locale->text('DATEV check returned errors:'), $datev->errors;
1207     }
1208   }
1209
1210   my $rc = 1;
1211   $dbh->commit if !$provided_dbh;
1212
1213   $main::lxdebug->leave_sub();
1214
1215   return $rc;
1216 }
1217
1218 sub _delete_payments {
1219   $main::lxdebug->enter_sub();
1220
1221   my ($self, $form, $dbh) = @_;
1222
1223   my @delete_acc_trans_ids;
1224
1225   # Delete old payment entries from acc_trans.
1226   my $query =
1227     qq|SELECT acc_trans_id
1228        FROM acc_trans
1229        WHERE (trans_id = ?) AND fx_transaction
1230
1231        UNION
1232
1233        SELECT at.acc_trans_id
1234        FROM acc_trans at
1235        LEFT JOIN chart c ON (at.chart_id = c.id)
1236        WHERE (trans_id = ?) AND (c.link LIKE '%AR_paid%')|;
1237   push @delete_acc_trans_ids, selectall_array_query($form, $dbh, $query, conv_i($form->{id}), conv_i($form->{id}));
1238
1239   $query =
1240     qq|SELECT at.acc_trans_id
1241        FROM acc_trans at
1242        LEFT JOIN chart c ON (at.chart_id = c.id)
1243        WHERE (trans_id = ?)
1244          AND ((c.link = 'AR') OR (c.link LIKE '%:AR') OR (c.link LIKE 'AR:%'))
1245        ORDER BY at.acc_trans_id
1246        OFFSET 1|;
1247   push @delete_acc_trans_ids, selectall_array_query($form, $dbh, $query, conv_i($form->{id}));
1248
1249   if (@delete_acc_trans_ids) {
1250     $query = qq|DELETE FROM acc_trans WHERE acc_trans_id IN (| . join(", ", @delete_acc_trans_ids) . qq|)|;
1251     do_query($form, $dbh, $query);
1252   }
1253
1254   $main::lxdebug->leave_sub();
1255 }
1256
1257 sub post_payment {
1258   $main::lxdebug->enter_sub();
1259
1260   my ($self, $myconfig, $form, $locale) = @_;
1261
1262   # connect to database, turn off autocommit
1263   my $dbh = $form->get_standard_dbh;
1264
1265   my (%payments, $old_form, $row, $item, $query, %keep_vars);
1266
1267   $old_form = save_form();
1268
1269   # Delete all entries in acc_trans from prior payments.
1270   if (SL::DB::Default->get->payments_changeable != 0) {
1271     $self->_delete_payments($form, $dbh);
1272   }
1273
1274   # Save the new payments the user made before cleaning up $form.
1275   map { $payments{$_} = $form->{$_} } grep m/^datepaid_\d+$|^gldate_\d+$|^acc_trans_id_\d+$|^memo_\d+$|^source_\d+$|^exchangerate_\d+$|^paid_\d+$|^AR_paid_\d+$|^paidaccounts$/, keys %{ $form };
1276
1277   # Clean up $form so that old content won't tamper the results.
1278   %keep_vars = map { $_, 1 } qw(login password id);
1279   map { delete $form->{$_} unless $keep_vars{$_} } keys %{ $form };
1280
1281   # Retrieve the invoice from the database.
1282   $self->retrieve_invoice($myconfig, $form);
1283
1284   # Set up the content of $form in the way that IS::post_invoice() expects.
1285   $form->{exchangerate} = $form->format_amount($myconfig, $form->{exchangerate});
1286
1287   for $row (1 .. scalar @{ $form->{invoice_details} }) {
1288     $item = $form->{invoice_details}->[$row - 1];
1289
1290     map { $item->{$_} = $form->format_amount($myconfig, $item->{$_}) } qw(qty sellprice discount);
1291
1292     map { $form->{"${_}_${row}"} = $item->{$_} } keys %{ $item };
1293   }
1294
1295   $form->{rowcount} = scalar @{ $form->{invoice_details} };
1296
1297   delete @{$form}{qw(invoice_details paidaccounts storno paid)};
1298
1299   # Restore the payment options from the user input.
1300   map { $form->{$_} = $payments{$_} } keys %payments;
1301
1302   # Get the AR accno (which is normally done by Form::create_links()).
1303   $query =
1304     qq|SELECT c.accno
1305        FROM acc_trans at
1306        LEFT JOIN chart c ON (at.chart_id = c.id)
1307        WHERE (trans_id = ?)
1308          AND ((c.link = 'AR') OR (c.link LIKE '%:AR') OR (c.link LIKE 'AR:%'))
1309        ORDER BY at.acc_trans_id
1310        LIMIT 1|;
1311
1312   ($form->{AR}) = selectfirst_array_query($form, $dbh, $query, conv_i($form->{id}));
1313
1314   # Post the new payments.
1315   $self->post_invoice($myconfig, $form, $dbh, 1);
1316
1317   restore_form($old_form);
1318
1319   my $rc = $dbh->commit();
1320
1321   $main::lxdebug->leave_sub();
1322
1323   return $rc;
1324 }
1325
1326 sub process_assembly {
1327   $main::lxdebug->enter_sub();
1328
1329   my ($dbh, $myconfig, $form, $id, $totalqty) = @_;
1330
1331   my $query =
1332     qq|SELECT a.parts_id, a.qty, p.assembly, p.partnumber, p.description, p.unit,
1333          p.inventory_accno_id, p.income_accno_id, p.expense_accno_id
1334        FROM assembly a
1335        JOIN parts p ON (a.parts_id = p.id)
1336        WHERE (a.id = ?)|;
1337   my $sth = prepare_execute_query($form, $dbh, $query, conv_i($id));
1338
1339   while (my $ref = $sth->fetchrow_hashref('NAME_lc')) {
1340
1341     my $allocated = 0;
1342
1343     $ref->{inventory_accno_id} *= 1;
1344     $ref->{expense_accno_id}   *= 1;
1345
1346     # multiply by number of assemblies
1347     $ref->{qty} *= $totalqty;
1348
1349     if ($ref->{assembly}) {
1350       &process_assembly($dbh, $myconfig, $form, $ref->{parts_id}, $ref->{qty});
1351       next;
1352     } else {
1353       if ($ref->{inventory_accno_id}) {
1354         $allocated = &cogs($dbh, $myconfig, $form, $ref->{parts_id}, $ref->{qty});
1355       }
1356     }
1357
1358     # save detail record for individual assembly item in invoice table
1359     $query =
1360       qq|INSERT INTO invoice (trans_id, description, parts_id, qty, sellprice, fxsellprice, allocated, assemblyitem, unit)
1361          VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)|;
1362     my @values = (conv_i($form->{id}), $ref->{description}, conv_i($ref->{parts_id}), $ref->{qty}, 0, 0, $allocated, 't', $ref->{unit});
1363     do_query($form, $dbh, $query, @values);
1364
1365   }
1366
1367   $sth->finish;
1368
1369   $main::lxdebug->leave_sub();
1370 }
1371
1372 sub cogs {
1373   $main::lxdebug->enter_sub();
1374
1375   # adjust allocated in table invoice according to FIFO princicple
1376   # for a certain part with part_id $id
1377
1378   my ($dbh, $myconfig, $form, $id, $totalqty, $basefactor, $row) = @_;
1379
1380   $basefactor ||= 1;
1381
1382   $form->{taxzone_id} *=1;
1383   my $transdate  = $form->{invdate} ? $dbh->quote($form->{invdate}) : "current_date";
1384   my $taxzone_id = $form->{"taxzone_id"} * 1;
1385   my $query =
1386     qq|SELECT i.id, i.trans_id, i.base_qty, i.allocated, i.sellprice, i.price_factor,
1387          c1.accno AS inventory_accno, c1.new_chart_id AS inventory_new_chart, date($transdate) - c1.valid_from AS inventory_valid,
1388          c2.accno AS    income_accno, c2.new_chart_id AS    income_new_chart, date($transdate) - c2.valid_from AS    income_valid,
1389          c3.accno AS   expense_accno, c3.new_chart_id AS   expense_new_chart, date($transdate) - c3.valid_from AS   expense_valid
1390        FROM invoice i, parts p
1391        LEFT JOIN chart c1 ON ((SELECT inventory_accno_id FROM buchungsgruppen WHERE id = p.buchungsgruppen_id) = c1.id)
1392        LEFT JOIN chart c2 ON ((SELECT income_accno_id_${taxzone_id} FROM buchungsgruppen WHERE id = p.buchungsgruppen_id) = c2.id)
1393        LEFT JOIN chart c3 ON ((select expense_accno_id_${taxzone_id} FROM buchungsgruppen WHERE id = p.buchungsgruppen_id) = c3.id)
1394        WHERE (i.parts_id = p.id)
1395          AND (i.parts_id = ?)
1396          AND ((i.base_qty + i.allocated) < 0)
1397        ORDER BY trans_id|;
1398   my $sth = prepare_execute_query($form, $dbh, $query, conv_i($id));
1399
1400   my $allocated = 0;
1401   my $qty;
1402
1403 # all invoice entries of an example part:
1404
1405 # id | trans_id | base_qty | allocated | sellprice | inventory_accno | income_accno | expense_accno
1406 # ---+----------+----------+-----------+-----------+-----------------+--------------+---------------
1407 #  4 |        4 |       -5 |         5 |  20.00000 | 1140            | 4400         | 5400     bought 5 for 20
1408 #  5 |        5 |        4 |        -4 |  50.00000 | 1140            | 4400         | 5400     sold   4 for 50
1409 #  6 |        6 |        1 |        -1 |  50.00000 | 1140            | 4400         | 5400     sold   1 for 50
1410 #  7 |        7 |       -5 |         1 |  20.00000 | 1140            | 4400         | 5400     bought 5 for 20
1411 #  8 |        8 |        1 |        -1 |  50.00000 | 1140            | 4400         | 5400     sold   1 for 50
1412
1413 # AND ((i.base_qty + i.allocated) < 0) filters out all but line with id=7, elsewhere i.base_qty + i.allocated has already reached 0
1414 # and all parts have been allocated
1415
1416 # so transaction 8 only sees transaction 7 with unallocated parts and adjusts allocated for that transaction, before allocated was 0
1417 #  7 |        7 |       -5 |         1 |  20.00000 | 1140            | 4400         | 5400     bought 5 for 20
1418
1419 # in this example there are still 4 unsold articles
1420
1421
1422   # search all invoice entries for the part in question, adjusting "allocated"
1423   # until the total number of sold parts has been reached
1424
1425   # ORDER BY trans_id ensures FIFO
1426
1427
1428   while (my $ref = $sth->fetchrow_hashref('NAME_lc')) {
1429     if (($qty = (($ref->{base_qty} * -1) - $ref->{allocated})) > $totalqty) {
1430       $qty = $totalqty;
1431     }
1432
1433     # update allocated in invoice
1434     $form->update_balance($dbh, "invoice", "allocated", qq|id = $ref->{id}|, $qty);
1435
1436     # total expenses and inventory
1437     # sellprice is the cost of the item
1438     my $linetotal = $form->round_amount(($ref->{sellprice} * $qty) / ( ($ref->{price_factor} || 1) * ( $basefactor || 1 )), 2);
1439
1440     if ( $::instance_conf->get_inventory_system eq 'perpetual' ) {
1441       # Bestandsmethode: when selling parts, deduct their purchase value from the inventory account
1442       $ref->{expense_accno} = ($form->{"expense_accno_$row"}) ? $form->{"expense_accno_$row"} : $ref->{expense_accno};
1443       # add to expense
1444       $form->{amount_cogs}{ $form->{id} }{ $ref->{expense_accno} } += -$linetotal;
1445       $form->{expense_inventory} .= " " . $ref->{expense_accno};
1446       $ref->{inventory_accno} = ($form->{"inventory_accno_$row"}) ? $form->{"inventory_accno_$row"} : $ref->{inventory_accno};
1447       # deduct inventory
1448       $form->{amount_cogs}{ $form->{id} }{ $ref->{inventory_accno} } -= -$linetotal;
1449       $form->{expense_inventory} .= " " . $ref->{inventory_accno};
1450     }
1451
1452     # add allocated
1453     $allocated -= $qty;
1454
1455     last if (($totalqty -= $qty) <= 0);
1456   }
1457
1458   $sth->finish;
1459
1460   $main::lxdebug->leave_sub();
1461
1462   return $allocated;
1463 }
1464
1465 sub reverse_invoice {
1466   $main::lxdebug->enter_sub();
1467
1468   my ($dbh, $form) = @_;
1469
1470   # reverse inventory items
1471   my $query =
1472     qq|SELECT i.id, i.parts_id, i.qty, i.assemblyitem, p.assembly, p.inventory_accno_id
1473        FROM invoice i
1474        JOIN parts p ON (i.parts_id = p.id)
1475        WHERE i.trans_id = ?|;
1476   my $sth = prepare_execute_query($form, $dbh, $query, conv_i($form->{"id"}));
1477
1478   while (my $ref = $sth->fetchrow_hashref('NAME_lc')) {
1479
1480     if ($ref->{inventory_accno_id}) {
1481       # de-allocated purchases
1482       $query =
1483         qq|SELECT i.id, i.trans_id, i.allocated
1484            FROM invoice i
1485            WHERE (i.parts_id = ?) AND (i.allocated > 0)
1486            ORDER BY i.trans_id DESC|;
1487       my $sth2 = prepare_execute_query($form, $dbh, $query, conv_i($ref->{"parts_id"}));
1488
1489       while (my $inhref = $sth2->fetchrow_hashref('NAME_lc')) {
1490         my $qty = $ref->{qty};
1491         if (($ref->{qty} - $inhref->{allocated}) > 0) {
1492           $qty = $inhref->{allocated};
1493         }
1494
1495         # update invoice
1496         $form->update_balance($dbh, "invoice", "allocated", qq|id = $inhref->{id}|, $qty * -1);
1497
1498         last if (($ref->{qty} -= $qty) <= 0);
1499       }
1500       $sth2->finish;
1501     }
1502   }
1503
1504   $sth->finish;
1505
1506   # delete acc_trans
1507   my @values = (conv_i($form->{id}));
1508   do_query($form, $dbh, qq|DELETE FROM acc_trans WHERE trans_id = ?|, @values);
1509   do_query($form, $dbh, qq|DELETE FROM invoice WHERE trans_id = ?|, @values);
1510   do_query($form, $dbh, qq|DELETE FROM shipto WHERE (trans_id = ?) AND (module = 'AR')|, @values);
1511
1512   $main::lxdebug->leave_sub();
1513 }
1514
1515 sub delete_invoice {
1516   $main::lxdebug->enter_sub();
1517
1518   my ($self, $myconfig, $form) = @_;
1519
1520   # connect to database
1521   my $dbh = $form->get_standard_dbh;
1522
1523   &reverse_invoice($dbh, $form);
1524
1525   my @values = (conv_i($form->{id}));
1526
1527   # Falls wir ein Storno haben, müssen zwei Felder in der stornierten Rechnung wieder
1528   # zurückgesetzt werden. Vgl:
1529   #  id | storno | storno_id |  paid   |  amount
1530   #----+--------+-----------+---------+-----------
1531   # 18 | f      |           | 0.00000 | 119.00000
1532   # ZU:
1533   # 18 | t      |           |  119.00000 |  119.00000
1534   #
1535   if($form->{storno}){
1536     # storno_id auslesen und korrigieren
1537     my ($invoice_id) = selectfirst_array_query($form, $dbh, qq|SELECT storno_id FROM ar WHERE id = ?|,@values);
1538     do_query($form, $dbh, qq|UPDATE ar SET storno = 'f', paid = 0 WHERE id = ?|, $invoice_id);
1539   }
1540
1541   # delete spool files
1542   my @spoolfiles = selectall_array_query($form, $dbh, qq|SELECT spoolfile FROM status WHERE trans_id = ?|, @values);
1543
1544   my @queries = (
1545     qq|DELETE FROM status WHERE trans_id = ?|,
1546     qq|DELETE FROM periodic_invoices WHERE ar_id = ?|,
1547     qq|DELETE FROM ar WHERE id = ?|,
1548   );
1549
1550   map { do_query($form, $dbh, $_, @values) } @queries;
1551
1552   my $rc = $dbh->commit;
1553
1554   if ($rc) {
1555     my $spool = $::lx_office_conf{paths}->{spool};
1556     map { unlink "$spool/$_" if -f "$spool/$_"; } @spoolfiles;
1557   }
1558
1559   $main::lxdebug->leave_sub();
1560
1561   return $rc;
1562 }
1563
1564 sub retrieve_invoice {
1565   $main::lxdebug->enter_sub();
1566
1567   my ($self, $myconfig, $form) = @_;
1568
1569   # connect to database
1570   my $dbh = $form->get_standard_dbh;
1571
1572   my ($sth, $ref, $query);
1573
1574   my $query_transdate = !$form->{id} ? ", current_date AS invdate" : '';
1575
1576   $query =
1577     qq|SELECT
1578          (SELECT c.accno FROM chart c WHERE d.inventory_accno_id = c.id) AS inventory_accno,
1579          (SELECT c.accno FROM chart c WHERE d.income_accno_id = c.id)    AS income_accno,
1580          (SELECT c.accno FROM chart c WHERE d.expense_accno_id = c.id)   AS expense_accno,
1581          (SELECT c.accno FROM chart c WHERE d.fxgain_accno_id = c.id)    AS fxgain_accno,
1582          (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id = c.id)    AS fxloss_accno
1583          ${query_transdate}
1584        FROM defaults d|;
1585
1586   $ref = selectfirst_hashref_query($form, $dbh, $query);
1587   map { $form->{$_} = $ref->{$_} } keys %{ $ref };
1588
1589   if ($form->{id}) {
1590     my $id = conv_i($form->{id});
1591
1592     # retrieve invoice
1593     #erweiterung um das entsprechende feld lieferscheinnummer (a.donumber) in der html-maske anzuzeigen 12.02.2009 jb
1594
1595     $query =
1596       qq|SELECT
1597            a.invnumber, a.ordnumber, a.quonumber, a.cusordnumber,
1598            a.orddate, a.quodate, a.globalproject_id,
1599            a.transdate AS invdate, a.deliverydate, a.paid, a.storno, a.gldate,
1600            a.shippingpoint, a.shipvia, a.terms, a.notes, a.intnotes, a.taxzone_id,
1601            a.duedate, a.taxincluded, (SELECT cu.name FROM currencies cu WHERE cu.id=a.currency_id) AS currency, a.shipto_id, a.cp_id,
1602            a.employee_id, a.salesman_id, a.payment_id,
1603            a.language_id, a.delivery_customer_id, a.delivery_vendor_id, a.type,
1604            a.transaction_description, a.donumber, a.invnumber_for_credit_note,
1605            a.marge_total, a.marge_percent, a.direct_debit, a.delivery_term_id,
1606            e.name AS employee
1607          FROM ar a
1608          LEFT JOIN employee e ON (e.id = a.employee_id)
1609          WHERE a.id = ?|;
1610     $ref = selectfirst_hashref_query($form, $dbh, $query, $id);
1611     map { $form->{$_} = $ref->{$_} } keys %{ $ref };
1612
1613     $form->{exchangerate} = $form->get_exchangerate($dbh, $form->{currency}, $form->{invdate}, "buy");
1614
1615     # get shipto
1616     $query = qq|SELECT * FROM shipto WHERE (trans_id = ?) AND (module = 'AR')|;
1617     $ref = selectfirst_hashref_query($form, $dbh, $query, $id);
1618     delete $ref->{id};
1619     map { $form->{$_} = $ref->{$_} } keys %{ $ref };
1620
1621     foreach my $vc (qw(customer vendor)) {
1622       next if !$form->{"delivery_${vc}_id"};
1623       ($form->{"delivery_${vc}_string"}) = selectrow_query($form, $dbh, qq|SELECT name FROM customer WHERE id = ?|, $id);
1624     }
1625
1626     # get printed, emailed
1627     $query = qq|SELECT printed, emailed, spoolfile, formname FROM status WHERE trans_id = ?|;
1628     $sth = prepare_execute_query($form, $dbh, $query, $id);
1629
1630     while ($ref = $sth->fetchrow_hashref('NAME_lc')) {
1631       $form->{printed} .= "$ref->{formname} " if $ref->{printed};
1632       $form->{emailed} .= "$ref->{formname} " if $ref->{emailed};
1633       $form->{queued} .= "$ref->{formname} $ref->{spoolfile} " if $ref->{spoolfile};
1634     }
1635     $sth->finish;
1636     map { $form->{$_} =~ s/ +$//g } qw(printed emailed queued);
1637
1638     my $transdate = $form->{deliverydate} ? $dbh->quote($form->{deliverydate})
1639                   : $form->{invdate}      ? $dbh->quote($form->{invdate})
1640                   :                         "current_date";
1641
1642
1643     my $taxzone_id = $form->{taxzone_id} *= 1;
1644     $taxzone_id = 0 if (0 > $taxzone_id) || (3 < $taxzone_id);
1645
1646     # retrieve individual items
1647     $query =
1648       qq|SELECT
1649            c1.accno AS inventory_accno, c1.new_chart_id AS inventory_new_chart, date($transdate) - c1.valid_from AS inventory_valid,
1650            c2.accno AS income_accno,    c2.new_chart_id AS income_new_chart,    date($transdate) - c2.valid_from as income_valid,
1651            c3.accno AS expense_accno,   c3.new_chart_id AS expense_new_chart,   date($transdate) - c3.valid_from AS expense_valid,
1652
1653            i.id AS invoice_id,
1654            i.description, i.longdescription, i.qty, i.fxsellprice AS sellprice, i.discount, i.parts_id AS id, i.unit, i.deliverydate AS reqdate,
1655            i.project_id, i.serialnumber, i.id AS invoice_pos, i.pricegroup_id, i.ordnumber, i.transdate, i.cusordnumber, i.subtotal, i.lastcost,
1656            i.price_factor_id, i.price_factor, i.marge_price_factor,
1657            p.partnumber, p.assembly, p.notes AS partnotes, p.inventory_accno_id AS part_inventory_accno_id, p.formel, p.listprice,
1658            pr.projectnumber, pg.partsgroup, prg.pricegroup
1659
1660          FROM invoice i
1661          LEFT JOIN parts p ON (i.parts_id = p.id)
1662          LEFT JOIN project pr ON (i.project_id = pr.id)
1663          LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id)
1664          LEFT JOIN pricegroup prg ON (i.pricegroup_id = prg.id)
1665
1666          LEFT JOIN chart c1 ON ((SELECT inventory_accno_id             FROM buchungsgruppen WHERE id = p.buchungsgruppen_id) = c1.id)
1667          LEFT JOIN chart c2 ON ((SELECT income_accno_id_${taxzone_id}  FROM buchungsgruppen WHERE id = p.buchungsgruppen_id) = c2.id)
1668          LEFT JOIN chart c3 ON ((SELECT expense_accno_id_${taxzone_id} FROM buchungsgruppen WHERE id = p.buchungsgruppen_id) = c3.id)
1669
1670          WHERE (i.trans_id = ?) AND NOT (i.assemblyitem = '1') ORDER BY i.id|;
1671
1672     $sth = prepare_execute_query($form, $dbh, $query, $id);
1673
1674     while (my $ref = $sth->fetchrow_hashref('NAME_lc')) {
1675       # Retrieve custom variables.
1676       my $cvars = CVar->get_custom_variables(dbh        => $dbh,
1677                                              module     => 'IC',
1678                                              sub_module => 'invoice',
1679                                              trans_id   => $ref->{invoice_id},
1680                                             );
1681       map { $ref->{"ic_cvar_$_->{name}"} = $_->{value} } @{ $cvars };
1682       delete $ref->{invoice_id};
1683
1684       map({ delete($ref->{$_}); } qw(inventory_accno inventory_new_chart inventory_valid)) if !$ref->{"part_inventory_accno_id"};
1685       delete($ref->{"part_inventory_accno_id"});
1686
1687       foreach my $type (qw(inventory income expense)) {
1688         while ($ref->{"${type}_new_chart"} && ($ref->{"${type}_valid"} >=0)) {
1689           my $query = qq|SELECT accno, new_chart_id, date($transdate) - valid_from FROM chart WHERE id = ?|;
1690           @$ref{ map $type.$_, qw(_accno _new_chart _valid) } = selectrow_query($form, $dbh, $query, $ref->{"${type}_new_chart"});
1691         }
1692       }
1693
1694       # get tax rates and description
1695       my $accno_id = ($form->{vc} eq "customer") ? $ref->{income_accno} : $ref->{expense_accno};
1696       $query =
1697         qq|SELECT c.accno, t.taxdescription, t.rate, t.taxnumber FROM tax t
1698            LEFT JOIN chart c ON (c.id = t.chart_id)
1699            WHERE t.id IN
1700              (SELECT tk.tax_id FROM taxkeys tk
1701               WHERE tk.chart_id = (SELECT id FROM chart WHERE accno = ?)
1702                 AND startdate <= date($transdate)
1703               ORDER BY startdate DESC LIMIT 1)
1704            ORDER BY c.accno|;
1705       my $stw = prepare_execute_query($form, $dbh, $query, $accno_id);
1706       $ref->{taxaccounts} = "";
1707       my $i=0;
1708       while (my $ptr = $stw->fetchrow_hashref('NAME_lc')) {
1709
1710         if (($ptr->{accno} eq "") && ($ptr->{rate} == 0)) {
1711           $i++;
1712           $ptr->{accno} = $i;
1713         }
1714         $ref->{taxaccounts} .= "$ptr->{accno} ";
1715
1716         if (!($form->{taxaccounts} =~ /\Q$ptr->{accno}\E/)) {
1717           $form->{"$ptr->{accno}_rate"}        = $ptr->{rate};
1718           $form->{"$ptr->{accno}_description"} = $ptr->{taxdescription};
1719           $form->{"$ptr->{accno}_taxnumber"}   = $ptr->{taxnumber};
1720           $form->{taxaccounts} .= "$ptr->{accno} ";
1721         }
1722
1723       }
1724
1725       $ref->{qty} *= -1 if $form->{type} eq "credit_note";
1726
1727       chop $ref->{taxaccounts};
1728       push @{ $form->{invoice_details} }, $ref;
1729       $stw->finish;
1730     }
1731     $sth->finish;
1732
1733     Common::webdav_folder($form);
1734   }
1735
1736   my $rc = $dbh->commit;
1737
1738   $main::lxdebug->leave_sub();
1739
1740   return $rc;
1741 }
1742
1743 sub get_customer {
1744   $main::lxdebug->enter_sub();
1745
1746   my ($self, $myconfig, $form) = @_;
1747
1748   # connect to database
1749   my $dbh = $form->get_standard_dbh;
1750
1751   my $dateformat = $myconfig->{dateformat};
1752   $dateformat .= "yy" if $myconfig->{dateformat} !~ /^y/;
1753
1754   my (@values, $duedate, $ref, $query);
1755
1756   if ($form->{invdate}) {
1757     $duedate = "to_date(?, '$dateformat')";
1758     push @values, $form->{invdate};
1759   } else {
1760     $duedate = "current_date";
1761   }
1762
1763   my $cid = conv_i($form->{customer_id});
1764   my $payment_id;
1765
1766   if ($form->{payment_id}) {
1767     $payment_id = "(pt.id = ?) OR";
1768     push @values, conv_i($form->{payment_id});
1769   }
1770
1771   # get customer
1772   $query =
1773     qq|SELECT
1774          c.id AS customer_id, c.name AS customer, c.discount as customer_discount, c.creditlimit, c.terms,
1775          c.email, c.cc, c.bcc, c.language_id, c.payment_id, c.delivery_term_id,
1776          c.street, c.zipcode, c.city, c.country,
1777          c.notes AS intnotes, c.klass as customer_klass, c.taxzone_id, c.salesman_id, cu.name AS curr,
1778          c.taxincluded_checked, c.direct_debit,
1779          $duedate + COALESCE(pt.terms_netto, 0) AS duedate,
1780          b.discount AS tradediscount, b.description AS business
1781        FROM customer c
1782        LEFT JOIN business b ON (b.id = c.business_id)
1783        LEFT JOIN payment_terms pt ON ($payment_id (c.payment_id = pt.id))
1784        LEFT JOIN currencies cu ON (c.currency_id=cu.id)
1785        WHERE c.id = ?|;
1786   push @values, $cid;
1787   $ref = selectfirst_hashref_query($form, $dbh, $query, @values);
1788
1789   delete $ref->{salesman_id} if !$ref->{salesman_id};
1790
1791   map { $form->{$_} = $ref->{$_} } keys %$ref;
1792
1793   # use customer currency
1794   $form->{currency} = $form->{curr};
1795
1796   $query =
1797     qq|SELECT sum(amount - paid) AS dunning_amount
1798        FROM ar
1799        WHERE (paid < amount)
1800          AND (customer_id = ?)
1801          AND (dunning_config_id IS NOT NULL)|;
1802   $ref = selectfirst_hashref_query($form, $dbh, $query, $cid);
1803   map { $form->{$_} = $ref->{$_} } keys %$ref;
1804
1805   $query =
1806     qq|SELECT dnn.dunning_description AS max_dunning_level
1807        FROM dunning_config dnn
1808        WHERE id IN (SELECT dunning_config_id
1809                     FROM ar
1810                     WHERE (paid < amount) AND (customer_id = ?) AND (dunning_config_id IS NOT NULL))
1811        ORDER BY dunning_level DESC LIMIT 1|;
1812   $ref = selectfirst_hashref_query($form, $dbh, $query, $cid);
1813   map { $form->{$_} = $ref->{$_} } keys %$ref;
1814
1815   $form->{creditremaining} = $form->{creditlimit};
1816   $query = qq|SELECT SUM(amount - paid) FROM ar WHERE customer_id = ?|;
1817   my ($value) = selectrow_query($form, $dbh, $query, $cid);
1818   $form->{creditremaining} -= $value;
1819
1820   $query =
1821     qq|SELECT o.amount,
1822          (SELECT e.buy FROM exchangerate e
1823           WHERE e.currency_id = o.currency_id
1824             AND e.transdate = o.transdate)
1825        FROM oe o
1826        WHERE o.customer_id = ?
1827          AND o.quotation = '0'
1828          AND o.closed = '0'|;
1829   my $sth = prepare_execute_query($form, $dbh, $query, $cid);
1830
1831   while (my ($amount, $exch) = $sth->fetchrow_array) {
1832     $exch = 1 unless $exch;
1833     $form->{creditremaining} -= $amount * $exch;
1834   }
1835   $sth->finish;
1836
1837   # get shipto if we did not converted an order or invoice
1838   if (!$form->{shipto}) {
1839     map { delete $form->{$_} }
1840       qw(shiptoname shiptodepartment_1 shiptodepartment_2
1841          shiptostreet shiptozipcode shiptocity shiptocountry
1842          shiptocontact shiptophone shiptofax shiptoemail);
1843
1844     $query = qq|SELECT * FROM shipto WHERE trans_id = ? AND module = 'CT'|;
1845     $ref = selectfirst_hashref_query($form, $dbh, $query, $cid);
1846     delete $ref->{id};
1847     map { $form->{$_} = $ref->{$_} } keys %$ref;
1848   }
1849
1850   # setup last accounts used for this customer
1851   if (!$form->{id} && $form->{type} !~ /_(order|quotation)/) {
1852     $query =
1853       qq|SELECT c.id, c.accno, c.description, c.link, c.category
1854          FROM chart c
1855          JOIN acc_trans ac ON (ac.chart_id = c.id)
1856          JOIN ar a ON (a.id = ac.trans_id)
1857          WHERE a.customer_id = ?
1858            AND NOT (c.link LIKE '%_tax%' OR c.link LIKE '%_paid%')
1859            AND a.id IN (SELECT max(a2.id) FROM ar a2 WHERE a2.customer_id = ?)|;
1860     $sth = prepare_execute_query($form, $dbh, $query, $cid, $cid);
1861
1862     my $i = 0;
1863     while ($ref = $sth->fetchrow_hashref('NAME_lc')) {
1864       if ($ref->{category} eq 'I') {
1865         $i++;
1866         $form->{"AR_amount_$i"} = "$ref->{accno}--$ref->{description}";
1867
1868         if ($form->{initial_transdate}) {
1869           my $tax_query =
1870             qq|SELECT tk.tax_id, t.rate
1871                FROM taxkeys tk
1872                LEFT JOIN tax t ON tk.tax_id = t.id
1873                WHERE (tk.chart_id = ?) AND (startdate <= date(?))
1874                ORDER BY tk.startdate DESC
1875                LIMIT 1|;
1876           my ($tax_id, $rate) =
1877             selectrow_query($form, $dbh, $tax_query, $ref->{id},
1878                             $form->{initial_transdate});
1879           $form->{"taxchart_$i"} = "${tax_id}--${rate}";
1880         }
1881       }
1882       if ($ref->{category} eq 'A') {
1883         $form->{ARselected} = $form->{AR_1} = $ref->{accno};
1884       }
1885     }
1886     $sth->finish;
1887     $form->{rowcount} = $i if ($i && !$form->{type});
1888   }
1889
1890   $main::lxdebug->leave_sub();
1891 }
1892
1893 sub retrieve_item {
1894   $main::lxdebug->enter_sub();
1895
1896   my ($self, $myconfig, $form) = @_;
1897
1898   # connect to database
1899   my $dbh = $form->get_standard_dbh;
1900
1901   my $i = $form->{rowcount};
1902
1903   my $where = qq|NOT p.obsolete = '1'|;
1904   my @values;
1905
1906   foreach my $column (qw(p.partnumber p.description pgpartsgroup )) {
1907     my ($table, $field) = split m/\./, $column;
1908     next if !$form->{"${field}_${i}"};
1909     $where .= qq| AND lower(${column}) ILIKE ?|;
1910     push @values, '%' . $form->{"${field}_${i}"} . '%';
1911   }
1912
1913   #Es soll auch nach EAN gesucht werden, ohne Einschränkung durch Beschreibung
1914   if ($form->{"partnumber_$i"} && !$form->{"description_$i"}) {
1915     $where .= qq| OR (NOT p.obsolete = '1' AND p.ean = ? )|;
1916     push @values, $form->{"partnumber_$i"};
1917   }
1918
1919   # Search for part ID overrides all other criteria.
1920   if ($form->{"id_${i}"}) {
1921     $where  = qq|p.id = ?|;
1922     @values = ($form->{"id_${i}"});
1923   }
1924
1925   if ($form->{"description_$i"}) {
1926     $where .= qq| ORDER BY p.description|;
1927   } else {
1928     $where .= qq| ORDER BY p.partnumber|;
1929   }
1930
1931   my $transdate;
1932   if ($form->{type} eq "invoice") {
1933     $transdate =
1934       $form->{deliverydate} ? $dbh->quote($form->{deliverydate}) :
1935       $form->{invdate}      ? $dbh->quote($form->{invdate}) :
1936                               "current_date";
1937   } else {
1938     $transdate =
1939       $form->{transdate}    ? $dbh->quote($form->{transdate}) :
1940                               "current_date";
1941   }
1942
1943   my $taxzone_id = $form->{taxzone_id} * 1;
1944   $taxzone_id = 0 if (0 > $taxzone_id) || (3 < $taxzone_id);
1945
1946   my $query =
1947     qq|SELECT
1948          p.id, p.partnumber, p.description, p.sellprice,
1949          p.listprice, p.inventory_accno_id, p.lastcost,
1950
1951          c1.accno AS inventory_accno,
1952          c1.new_chart_id AS inventory_new_chart,
1953          date($transdate) - c1.valid_from AS inventory_valid,
1954
1955          c2.accno AS income_accno,
1956          c2.new_chart_id AS income_new_chart,
1957          date($transdate)  - c2.valid_from AS income_valid,
1958
1959          c3.accno AS expense_accno,
1960          c3.new_chart_id AS expense_new_chart,
1961          date($transdate) - c3.valid_from AS expense_valid,
1962
1963          p.unit, p.assembly, p.onhand,
1964          p.notes AS partnotes, p.notes AS longdescription,
1965          p.not_discountable, p.formel, p.payment_id AS part_payment_id,
1966          p.price_factor_id, p.weight,
1967
1968          pfac.factor AS price_factor,
1969
1970          pg.partsgroup
1971
1972        FROM parts p
1973        LEFT JOIN chart c1 ON
1974          ((SELECT inventory_accno_id
1975            FROM buchungsgruppen
1976            WHERE id = p.buchungsgruppen_id) = c1.id)
1977        LEFT JOIN chart c2 ON
1978          ((SELECT income_accno_id_${taxzone_id}
1979            FROM buchungsgruppen
1980            WHERE id = p.buchungsgruppen_id) = c2.id)
1981        LEFT JOIN chart c3 ON
1982          ((SELECT expense_accno_id_${taxzone_id}
1983            FROM buchungsgruppen
1984            WHERE id = p.buchungsgruppen_id) = c3.id)
1985        LEFT JOIN partsgroup pg ON (pg.id = p.partsgroup_id)
1986        LEFT JOIN price_factors pfac ON (pfac.id = p.price_factor_id)
1987        WHERE $where|;
1988   my $sth = prepare_execute_query($form, $dbh, $query, @values);
1989
1990   my @translation_queries = ( [ qq|SELECT tr.translation, tr.longdescription
1991                                    FROM translation tr
1992                                    WHERE tr.language_id = ? AND tr.parts_id = ?| ],
1993                               [ qq|SELECT tr.translation, tr.longdescription
1994                                    FROM translation tr
1995                                    WHERE tr.language_id IN
1996                                      (SELECT id
1997                                       FROM language
1998                                       WHERE article_code = (SELECT article_code FROM language WHERE id = ?))
1999                                      AND tr.parts_id = ?
2000                                    LIMIT 1| ] );
2001   map { push @{ $_ }, prepare_query($form, $dbh, $_->[0]) } @translation_queries;
2002
2003   while (my $ref = $sth->fetchrow_hashref('NAME_lc')) {
2004
2005     # In der Buchungsgruppe ist immer ein Bestandskonto verknuepft, auch wenn
2006     # es sich um eine Dienstleistung handelt. Bei Dienstleistungen muss das
2007     # Buchungskonto also aus dem Ergebnis rausgenommen werden.
2008     if (!$ref->{inventory_accno_id}) {
2009       map({ delete($ref->{"inventory_${_}"}); } qw(accno new_chart valid));
2010     }
2011     delete($ref->{inventory_accno_id});
2012
2013     foreach my $type (qw(inventory income expense)) {
2014       while ($ref->{"${type}_new_chart"} && ($ref->{"${type}_valid"} >=0)) {
2015         my $query =
2016           qq|SELECT accno, new_chart_id, date($transdate) - valid_from
2017              FROM chart
2018              WHERE id = ?|;
2019         ($ref->{"${type}_accno"},
2020          $ref->{"${type}_new_chart"},
2021          $ref->{"${type}_valid"})
2022           = selectrow_query($form, $dbh, $query, $ref->{"${type}_new_chart"});
2023       }
2024     }
2025
2026     if ($form->{payment_id} eq "") {
2027       $form->{payment_id} = $form->{part_payment_id};
2028     }
2029
2030     # get tax rates and description
2031     my $accno_id = ($form->{vc} eq "customer") ? $ref->{income_accno} : $ref->{expense_accno};
2032     $query =
2033       qq|SELECT c.accno, t.taxdescription, t.rate, t.taxnumber
2034          FROM tax t
2035          LEFT JOIN chart c ON (c.id = t.chart_id)
2036          WHERE t.id in
2037            (SELECT tk.tax_id
2038             FROM taxkeys tk
2039             WHERE tk.chart_id = (SELECT id from chart WHERE accno = ?)
2040               AND startdate <= ?
2041             ORDER BY startdate DESC
2042             LIMIT 1)
2043          ORDER BY c.accno|;
2044     @values = ($accno_id, $transdate eq "current_date" ? "now" : $transdate);
2045     my $stw = $dbh->prepare($query);
2046     $stw->execute(@values) || $form->dberror($query);
2047
2048     $ref->{taxaccounts} = "";
2049     my $i = 0;
2050     while (my $ptr = $stw->fetchrow_hashref('NAME_lc')) {
2051
2052       if (($ptr->{accno} eq "") && ($ptr->{rate} == 0)) {
2053         $i++;
2054         $ptr->{accno} = $i;
2055       }
2056       $ref->{taxaccounts} .= "$ptr->{accno} ";
2057
2058       if (!($form->{taxaccounts} =~ /\Q$ptr->{accno}\E/)) {
2059         $form->{"$ptr->{accno}_rate"}        = $ptr->{rate};
2060         $form->{"$ptr->{accno}_description"} = $ptr->{taxdescription};
2061         $form->{"$ptr->{accno}_taxnumber"}   = $ptr->{taxnumber};
2062         $form->{taxaccounts} .= "$ptr->{accno} ";
2063       }
2064
2065     }
2066
2067     $stw->finish;
2068     chop $ref->{taxaccounts};
2069
2070     if ($form->{language_id}) {
2071       for my $spec (@translation_queries) {
2072         do_statement($form, $spec->[1], $spec->[0], conv_i($form->{language_id}), conv_i($ref->{id}));
2073         my ($translation, $longdescription) = $spec->[1]->fetchrow_array;
2074         next unless $translation;
2075         $ref->{description} = $translation;
2076         $ref->{longdescription} = $longdescription;
2077         last;
2078       }
2079     }
2080
2081     $ref->{onhand} *= 1;
2082
2083     push @{ $form->{item_list} }, $ref;
2084   }
2085   $sth->finish;
2086   $_->[1]->finish for @translation_queries;
2087
2088   foreach my $item (@{ $form->{item_list} }) {
2089     my $custom_variables = CVar->get_custom_variables(module   => 'IC',
2090                                                       trans_id => $item->{id},
2091                                                       dbh      => $dbh,
2092                                                      );
2093
2094     map { $item->{"ic_cvar_" . $_->{name} } = $_->{value} } @{ $custom_variables };
2095   }
2096
2097   $main::lxdebug->leave_sub();
2098 }
2099
2100 ##########################
2101 # get pricegroups from database
2102 # build up selected pricegroup
2103 # if an exchange rate - change price
2104 # for each part
2105 #
2106 sub get_pricegroups_for_parts {
2107
2108   $main::lxdebug->enter_sub();
2109
2110   my ($self, $myconfig, $form) = @_;
2111
2112   my $dbh = $form->get_standard_dbh;
2113
2114   $form->{"PRICES"} = {};
2115
2116   my $i  = 1;
2117   my $id = 0;
2118   my $all_units = AM->retrieve_units($myconfig, $form);
2119   while (($form->{"id_$i"}) or ($form->{"new_id_$i"})) {
2120     $form->{"PRICES"}{$i} = [];
2121
2122     $id = $form->{"id_$i"};
2123
2124     if (!($form->{"id_$i"}) and $form->{"new_id_$i"}) {
2125       $id = $form->{"new_id_$i"};
2126     }
2127
2128     my ($price, $selectedpricegroup_id) = split(/--/, $form->{"sellprice_pg_$i"});
2129
2130     my $pricegroup_old = $form->{"pricegroup_old_$i"};
2131
2132     # sellprice has format 13,0000 or 0,00000,  can't check for 0 numerically
2133     my $sellprice = $form->{"sellprice_$i"};
2134     my $pricegroup_id = $form->{"pricegroup_id_$i"};
2135     $form->{"new_pricegroup_$i"} = $selectedpricegroup_id;
2136     $form->{"old_pricegroup_$i"} = $pricegroup_old;
2137
2138     my $price_new = $form->{"price_new_$i"};
2139     my $price_old = $form->{"price_old_$i"};
2140
2141     if (!$form->{"unit_old_$i"}) {
2142       # Neue Ware aus der Datenbank. In diesem Fall ist unit_$i die
2143       # Einheit, wie sie in den Stammdaten hinterlegt wurde.
2144       # Es sollte also angenommen werden, dass diese ausgewaehlt war.
2145       $form->{"unit_old_$i"} = $form->{"unit_$i"};
2146     }
2147
2148     # Die zuletzt ausgewaehlte mit der aktuell ausgewaehlten Einheit
2149     # vergleichen und bei Unterschied den Preis entsprechend umrechnen.
2150     $form->{"selected_unit_$i"} = $form->{"unit_$i"} unless ($form->{"selected_unit_$i"});
2151
2152     if (!$all_units->{$form->{"selected_unit_$i"}} ||
2153         ($all_units->{$form->{"selected_unit_$i"}}->{"base_unit"} ne
2154          $all_units->{$form->{"unit_old_$i"}}->{"base_unit"})) {
2155       # Die ausgewaehlte Einheit ist fuer diesen Artikel nicht gueltig
2156       # (z.B. Dimensionseinheit war ausgewaehlt, es handelt sich aber
2157       # um eine Dienstleistung). Dann keinerlei Umrechnung vornehmen.
2158       $form->{"unit_old_$i"} = $form->{"selected_unit_$i"} = $form->{"unit_$i"};
2159     }
2160
2161     my $basefactor = 1;
2162
2163     if ($form->{"unit_old_$i"} ne $form->{"selected_unit_$i"}) {
2164       if (defined($all_units->{$form->{"unit_old_$i"}}->{"factor"}) &&
2165           $all_units->{$form->{"unit_old_$i"}}->{"factor"}) {
2166         $basefactor = $all_units->{$form->{"selected_unit_$i"}}->{"factor"} /
2167           $all_units->{$form->{"unit_old_$i"}}->{"factor"};
2168       }
2169     }
2170
2171     if (!$form->{"basefactor_$i"}) {
2172       $form->{"basefactor_$i"} = 1;
2173     }
2174
2175     my $query =
2176        qq|SELECT
2177             0 as pricegroup_id,
2178             sellprice AS default_sellprice,
2179             '' AS pricegroup,
2180             sellprice AS price,
2181             'selected' AS selected
2182           FROM parts
2183           WHERE id = ?
2184           UNION ALL
2185           SELECT
2186            pricegroup_id,
2187            parts.sellprice AS default_sellprice,
2188            pricegroup.pricegroup,
2189            price,
2190            '' AS selected
2191           FROM prices
2192           LEFT JOIN parts ON parts.id = parts_id
2193           LEFT JOIN pricegroup ON pricegroup.id = pricegroup_id
2194           WHERE parts_id = ?
2195           ORDER BY pricegroup|;
2196     my @values = (conv_i($id), conv_i($id));
2197     my $pkq = prepare_execute_query($form, $dbh, $query, @values);
2198
2199     while (my $pkr = $pkq->fetchrow_hashref('NAME_lc')) {
2200       $pkr->{id}       = $id;
2201       $pkr->{selected} = '';
2202
2203       # if there is an exchange rate change price
2204       if (($form->{exchangerate} * 1) != 0) {
2205         $pkr->{price} /= $form->{exchangerate};
2206       }
2207
2208       $pkr->{price} *= $form->{"basefactor_$i"};
2209       $pkr->{price} *= $basefactor;
2210       $pkr->{price_ufmt} = $pkr->{price};
2211       $pkr->{price} = $form->format_amount($myconfig, $pkr->{price}, 5);
2212
2213       if (!defined $selectedpricegroup_id) {
2214         # new entries in article list, either old invoice was loaded (edit) or a new article was added
2215         # Case A: open old invoice, no pricegroup selected
2216         # Case B: add new article to invoice, no pricegroup selected
2217
2218         # to distinguish case A and B the variable pricegroup_id_$i is used
2219         # for new articles this variable isn't defined, for loaded articles it is
2220         # sellprice can't be used, as it already has 0,00 set
2221
2222         if ($pkr->{pricegroup_id} eq $form->{"pricegroup_id_$i"} and defined $form->{"pricegroup_id_$i"}) {
2223           # Case A
2224           $pkr->{selected}  = ' selected';
2225         } elsif ($pkr->{pricegroup_id} eq $form->{customer_klass}
2226                  and not defined $form->{"pricegroup_id_$i"}
2227                  and $pkr->{price_ufmt} != 0    # only use customer pricegroup price if it has a value, else use default_sellprice
2228                                                 # for the case where pricegroup prices haven't been set
2229                 ) {
2230           # Case B: use default pricegroup of customer
2231
2232           $pkr->{selected}  = ' selected'; # unless $form->{selected};
2233           # no customer pricesgroup set
2234           if ($pkr->{price_ufmt} == $pkr->{default_sellprice}) {
2235
2236             $pkr->{price} = $form->{"sellprice_$i"};
2237
2238           } else {
2239
2240 # this sub should not set anything and only return. --sschoeling, 20090506
2241 # is this correct? put in again... -- grichardson 20110119
2242             $form->{"sellprice_$i"} = $pkr->{price};
2243           }
2244
2245         } elsif ($pkr->{price_ufmt} == $pkr->{default_sellprice} and $pkr->{default_sellprice} != 0) {
2246           $pkr->{price}    = $form->{"sellprice_$i"};
2247           $pkr->{selected} = ' selected';
2248         }
2249       }
2250
2251       # existing article: pricegroup or price changed
2252       if ($selectedpricegroup_id or $selectedpricegroup_id == 0) {
2253         if ($selectedpricegroup_id ne $pricegroup_old) {
2254           # pricegroup has changed
2255           if ($pkr->{pricegroup_id} eq $selectedpricegroup_id) {
2256             $pkr->{selected}  = ' selected';
2257           }
2258         } elsif ( ($form->parse_amount($myconfig, $price_new)
2259                  != $form->parse_amount($myconfig, $form->{"sellprice_$i"}))
2260                   and ($price_new ne 0) and defined $price_new) {
2261           # sellprice has changed
2262           # when loading existing invoices $price_new is NULL
2263           if ($pkr->{pricegroup_id} == 0) {
2264             $pkr->{price}     = $form->{"sellprice_$i"};
2265             $pkr->{selected}  = ' selected';
2266           }
2267         } elsif ($pkr->{pricegroup_id} eq $selectedpricegroup_id) {
2268           # neither sellprice nor pricegroup changed
2269           $pkr->{selected}  = ' selected';
2270           if (    ($pkr->{pricegroup_id} == 0) and ($pkr->{price} == $form->{"sellprice_$i"})) {
2271             # $pkr->{price}                         = $form->{"sellprice_$i"};
2272           } else {
2273             $pkr->{price} = $form->{"sellprice_$i"};
2274           }
2275         }
2276       }
2277       push @{ $form->{PRICES}{$i} }, $pkr;
2278
2279     }
2280     $form->{"basefactor_$i"} *= $basefactor;
2281
2282     $i++;
2283
2284     $pkq->finish;
2285   }
2286
2287   $main::lxdebug->leave_sub();
2288 }
2289
2290 sub has_storno {
2291   $main::lxdebug->enter_sub();
2292
2293   my ($self, $myconfig, $form, $table) = @_;
2294
2295   $main::lxdebug->leave_sub() and return 0 unless ($form->{id});
2296
2297   # make sure there's no funny stuff in $table
2298   # ToDO: die when this happens and throw an error
2299   $main::lxdebug->leave_sub() and return 0 if ($table =~ /\W/);
2300
2301   my $dbh = $form->get_standard_dbh;
2302
2303   my $query = qq|SELECT storno FROM $table WHERE storno_id = ?|;
2304   my ($result) = selectrow_query($form, $dbh, $query, $form->{id});
2305
2306   $main::lxdebug->leave_sub();
2307
2308   return $result;
2309 }
2310
2311 sub is_storno {
2312   $main::lxdebug->enter_sub();
2313
2314   my ($self, $myconfig, $form, $table, $id) = @_;
2315
2316   $main::lxdebug->leave_sub() and return 0 unless ($id);
2317
2318   # make sure there's no funny stuff in $table
2319   # ToDO: die when this happens and throw an error
2320   $main::lxdebug->leave_sub() and return 0 if ($table =~ /\W/);
2321
2322   my $dbh = $form->get_standard_dbh;
2323
2324   my $query = qq|SELECT storno FROM $table WHERE id = ?|;
2325   my ($result) = selectrow_query($form, $dbh, $query, $id);
2326
2327   $main::lxdebug->leave_sub();
2328
2329   return $result;
2330 }
2331
2332 sub get_standard_accno_current_assets {
2333   $main::lxdebug->enter_sub();
2334
2335   my ($self, $myconfig, $form) = @_;
2336
2337   my $dbh = $form->get_standard_dbh;
2338
2339   my $query = qq| SELECT accno FROM chart WHERE id = (SELECT ar_paid_accno_id FROM defaults)|;
2340   my ($result) = selectrow_query($form, $dbh, $query);
2341
2342   $main::lxdebug->leave_sub();
2343
2344   return $result;
2345 }
2346
2347 1;