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