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