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