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