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