Bessere Formatierung
[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();
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
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)
836                VALUES (?, (SELECT id FROM chart WHERE accno = ?), ?, ?, (SELECT id FROM tax WHERE taxkey=0), 0, ?)|;
837         @values = (conv_i($trans_id), $accno, $form->{amount_cogs}{$trans_id}{$accno}, conv_date($form->{invdate}), conv_i($project_id));
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)
849                VALUES (?, (SELECT id FROM chart WHERE accno = ?), ?, ?, (SELECT id FROM tax WHERE taxkey=0), 0, ?)|;
850         @values = (conv_i($trans_id), $accno, $form->{amount_cogs}{$trans_id}{$accno}, conv_date($form->{invdate}), conv_i($project_id));
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)
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  FROM chart WHERE accno = ?), ?)|;
874         @values = (conv_i($trans_id), $accno, $form->{amount}{$trans_id}{$accno}, conv_date($form->{invdate}), $accno, conv_date($taxdate), $accno, conv_i($project_id));
875         do_query($form, $dbh, $query, @values);
876         $form->{amount}{$trans_id}{$accno} = 0;
877       }
878     }
879
880     foreach my $accno (keys %{ $form->{amount}{$trans_id} }) {
881       $form->{amount}{$trans_id}{$accno} = $form->round_amount($form->{amount}{$trans_id}{$accno}, 2);
882
883       if (!$payments_only && ($form->{amount}{$trans_id}{$accno} != 0)) {
884         $query =
885           qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, tax_id, taxkey, project_id)
886              VALUES (?, (SELECT id FROM chart WHERE accno = ?), ?, ?,
887                      (SELECT tax_id 
888                       FROM taxkeys 
889                       WHERE chart_id= (SELECT id 
890                                        FROM chart 
891                                        WHERE accno = ?) 
892                       AND startdate <= ? 
893                       ORDER BY startdate DESC LIMIT 1),
894                      (SELECT taxkey_id FROM chart WHERE accno = ?), ?)|;
895         @values = (conv_i($trans_id), $accno, $form->{amount}{$trans_id}{$accno}, conv_date($form->{invdate}), $accno, conv_date($taxdate), $accno, conv_i($project_id));
896         do_query($form, $dbh, $query, @values);
897       }
898     }
899   }
900
901   # deduct payment differences from diff
902   for my $i (1 .. $form->{paidaccounts}) {
903     if ($form->{"paid_$i"} != 0) {
904       $amount =
905         $form->round_amount($form->{"paid_$i"} * $form->{exchangerate}, 2);
906       $diff -= $amount - $form->{"paid_$i"} * $form->{exchangerate};
907     }
908   }
909
910   # record payments and offsetting AR
911   if (!$form->{storno}) {
912     for my $i (1 .. $form->{paidaccounts}) {
913
914       if ($form->{"acc_trans_id_$i"}
915           && $payments_only
916           && (SL::DB::Default->get->payments_changeable == 0)) {
917         next;
918       }
919
920       next if ($form->{"paid_$i"} == 0);
921
922       my ($accno) = split(/--/, $form->{"AR_paid_$i"});
923       $form->{"datepaid_$i"} = $form->{invdate}
924       unless ($form->{"datepaid_$i"});
925       $form->{datepaid} = $form->{"datepaid_$i"};
926
927       $exchangerate = 0;
928
929       if ($form->{currency} eq $defaultcurrency) {
930         $form->{"exchangerate_$i"} = 1;
931       } else {
932         $exchangerate              = $form->check_exchangerate($myconfig, $form->{currency}, $form->{"datepaid_$i"}, 'buy');
933         $form->{"exchangerate_$i"} = $exchangerate || $form->parse_amount($myconfig, $form->{"exchangerate_$i"});
934       }
935
936       # record AR
937       $amount = $form->round_amount($form->{"paid_$i"} * $form->{exchangerate} + $diff, 2);
938
939       if ($form->{amount}{ $form->{id} }{ $form->{AR} } != 0) {
940         $query =
941         qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, tax_id, taxkey, project_id)
942            VALUES (?, (SELECT id FROM chart WHERE accno = ?), ?, ?,
943                    (SELECT tax_id 
944                     FROM taxkeys 
945                     WHERE chart_id= (SELECT id  
946                                      FROM chart 
947                                      WHERE accno = ?) 
948                     AND startdate <= ? 
949                     ORDER BY startdate DESC LIMIT 1),
950                    (SELECT taxkey_id FROM chart WHERE accno = ?), ?)|;
951         @values = (conv_i($form->{"id"}), $form->{AR}, $amount, $form->{"datepaid_$i"}, $form->{AR}, conv_date($taxdate), $form->{AR}, $project_id);
952         do_query($form, $dbh, $query, @values);
953       }
954
955       # record payment
956       $form->{"paid_$i"} *= -1;
957       my $gldate = (conv_date($form->{"gldate_$i"}))? conv_date($form->{"gldate_$i"}) : conv_date($form->current_date($myconfig));
958
959       $query =
960       qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, gldate, source, memo, tax_id, taxkey, project_id)
961          VALUES (?, (SELECT id FROM chart WHERE accno = ?), ?, ?, ?, ?, ?,
962                  (SELECT tax_id 
963                   FROM taxkeys 
964                   WHERE chart_id= (SELECT id  
965                                    FROM chart 
966                                    WHERE accno = ?) 
967                   AND startdate <= ? 
968                   ORDER BY startdate DESC LIMIT 1),
969                  (SELECT taxkey_id FROM chart WHERE accno = ?), ?)|;
970       @values = (conv_i($form->{"id"}), $accno, $form->{"paid_$i"}, $form->{"datepaid_$i"},
971                  $gldate, $form->{"source_$i"}, $form->{"memo_$i"}, $accno, conv_date($taxdate), $accno, $project_id);
972       do_query($form, $dbh, $query, @values);
973
974       # exchangerate difference
975       $form->{fx}{$accno}{ $form->{"datepaid_$i"} } +=
976         $form->{"paid_$i"} * ($form->{"exchangerate_$i"} - 1) + $diff;
977
978       # gain/loss
979       $amount =
980         $form->{"paid_$i"} * $form->{exchangerate} - $form->{"paid_$i"} *
981         $form->{"exchangerate_$i"};
982       if ($amount > 0) {
983         $form->{fx}{ $form->{fxgain_accno} }{ $form->{"datepaid_$i"} } += $amount;
984       } else {
985         $form->{fx}{ $form->{fxloss_accno} }{ $form->{"datepaid_$i"} } += $amount;
986       }
987
988       $diff = 0;
989
990       # update exchange rate
991       if (($form->{currency} ne $defaultcurrency) && !$exchangerate) {
992         $form->update_exchangerate($dbh, $form->{currency},
993                                    $form->{"datepaid_$i"},
994                                    $form->{"exchangerate_$i"}, 0);
995       }
996     }
997
998   } else {                      # if (!$form->{storno})
999     $form->{marge_total} *= -1;
1000   }
1001
1002   IO->set_datepaid(table => 'ar', id => $form->{id}, dbh => $dbh);
1003
1004   # record exchange rate differences and gains/losses
1005   foreach my $accno (keys %{ $form->{fx} }) {
1006     foreach my $transdate (keys %{ $form->{fx}{$accno} }) {
1007       $form->{fx}{$accno}{$transdate} = $form->round_amount($form->{fx}{$accno}{$transdate}, 2);
1008       if ( $form->{fx}{$accno}{$transdate} != 0 ) {
1009
1010         $query =
1011           qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, cleared, fx_transaction, tax_id, taxkey, project_id)
1012              VALUES (?, (SELECT id FROM chart WHERE accno = ?), ?, ?, '0', '1',
1013                  (SELECT tax_id 
1014                   FROM taxkeys 
1015                   WHERE chart_id= (SELECT id  
1016                                    FROM chart 
1017                                    WHERE accno = ?) 
1018                   AND startdate <= ? 
1019                   ORDER BY startdate DESC LIMIT 1),
1020                  (SELECT taxkey_id FROM chart WHERE accno = ?), ?)|;
1021         @values = (conv_i($form->{"id"}), $accno, $form->{fx}{$accno}{$transdate}, conv_date($transdate), $accno, conv_date($taxdate), $accno, conv_i($project_id));
1022         do_query($form, $dbh, $query, @values);
1023       }
1024     }
1025   }
1026
1027   if ($payments_only) {
1028     $query = qq|UPDATE ar SET paid = ? WHERE id = ?|;
1029     do_query($form, $dbh, $query,  $form->{paid}, conv_i($form->{id}));
1030
1031     $dbh->commit if !$provided_dbh;
1032
1033     $main::lxdebug->leave_sub();
1034     return;
1035   }
1036
1037   $amount = $netamount + $tax;
1038
1039   # save AR record
1040   #erweiterung fuer lieferscheinnummer (donumber) 12.02.09 jb
1041
1042   $query = qq|UPDATE ar set
1043                 invnumber   = ?, ordnumber     = ?, quonumber     = ?, cusordnumber  = ?,
1044                 transdate   = ?, orddate       = ?, quodate       = ?, customer_id   = ?,
1045                 amount      = ?, netamount     = ?, paid          = ?,
1046                 duedate     = ?, deliverydate  = ?, invoice       = ?, shippingpoint = ?,
1047                 shipvia     = ?, terms         = ?, notes         = ?, intnotes      = ?,
1048                 curr        = ?, department_id = ?, payment_id    = ?, taxincluded   = ?,
1049                 type        = ?, language_id   = ?, taxzone_id    = ?, shipto_id     = ?,
1050                 employee_id = ?, salesman_id   = ?, storno_id     = ?, storno        = ?,
1051                 cp_id       = ?, marge_total   = ?, marge_percent = ?,
1052                 globalproject_id               = ?, delivery_customer_id             = ?,
1053                 transaction_description        = ?, delivery_vendor_id               = ?,
1054                 donumber    = ?, invnumber_for_credit_note = ?,        direct_debit  = ?
1055               WHERE id = ?|;
1056   @values = (          $form->{"invnumber"},           $form->{"ordnumber"},             $form->{"quonumber"},          $form->{"cusordnumber"},
1057              conv_date($form->{"invdate"}),  conv_date($form->{"orddate"}),    conv_date($form->{"quodate"}),    conv_i($form->{"customer_id"}),
1058                        $amount,                        $netamount,                       $form->{"paid"},
1059              conv_date($form->{"duedate"}),  conv_date($form->{"deliverydate"}),    '1',                                $form->{"shippingpoint"},
1060                        $form->{"shipvia"},      conv_i($form->{"terms"}),                $form->{"notes"},              $form->{"intnotes"},
1061                        $form->{"currency"},     conv_i($form->{"department_id"}), conv_i($form->{"payment_id"}),        $form->{"taxincluded"} ? 't' : 'f',
1062                        $form->{"type"},         conv_i($form->{"language_id"}),   conv_i($form->{"taxzone_id"}), conv_i($form->{"shipto_id"}),
1063                 conv_i($form->{"employee_id"}), conv_i($form->{"salesman_id"}),   conv_i($form->{storno_id}),           $form->{"storno"} ? 't' : 'f',
1064                 conv_i($form->{"cp_id"}),            1 * $form->{marge_total} ,      1 * $form->{marge_percent},
1065                 conv_i($form->{"globalproject_id"}),                              conv_i($form->{"delivery_customer_id"}),
1066                        $form->{transaction_description},                          conv_i($form->{"delivery_vendor_id"}),
1067                        $form->{"donumber"}, $form->{"invnumber_for_credit_note"},        $form->{direct_debit} ? 't' : 'f',
1068                 conv_i($form->{"id"}));
1069   do_query($form, $dbh, $query, @values);
1070
1071
1072   if ($form->{storno}) {
1073     $query =
1074       qq!UPDATE ar SET
1075            paid = paid + amount,
1076            storno = 't',
1077            intnotes = ? || intnotes
1078          WHERE id = ?!;
1079     do_query($form, $dbh, $query, "Rechnung storniert am $form->{invdate} ", conv_i($form->{"storno_id"}));
1080     do_query($form, $dbh, qq|UPDATE ar SET paid = amount WHERE id = ?|, conv_i($form->{"id"}));
1081   }
1082
1083   # add shipto
1084   $form->{name} = $form->{customer};
1085   $form->{name} =~ s/--\Q$form->{customer_id}\E//;
1086
1087   if (!$form->{shipto_id}) {
1088     $form->add_shipto($dbh, $form->{id}, "AR");
1089   }
1090
1091   # save printed, emailed and queued
1092   $form->save_status($dbh);
1093
1094   Common::webdav_folder($form);
1095
1096   # Link this record to the records it was created from.
1097   RecordLinks->create_links('dbh'        => $dbh,
1098                             'mode'       => 'ids',
1099                             'from_table' => 'oe',
1100                             'from_ids'   => $form->{convert_from_oe_ids},
1101                             'to_table'   => 'ar',
1102                             'to_id'      => $form->{id},
1103     );
1104   delete $form->{convert_from_oe_ids};
1105
1106   my @convert_from_do_ids = map { $_ * 1 } grep { $_ } split m/\s+/, $form->{convert_from_do_ids};
1107
1108   if (scalar @convert_from_do_ids) {
1109     DO->close_orders('dbh' => $dbh,
1110                      'ids' => \@convert_from_do_ids);
1111
1112     RecordLinks->create_links('dbh'        => $dbh,
1113                               'mode'       => 'ids',
1114                               'from_table' => 'delivery_orders',
1115                               'from_ids'   => \@convert_from_do_ids,
1116                               'to_table'   => 'ar',
1117                               'to_id'      => $form->{id},
1118       );
1119   }
1120   delete $form->{convert_from_do_ids};
1121
1122   ARAP->close_orders_if_billed('dbh'     => $dbh,
1123                                'arap_id' => $form->{id},
1124                                'table'   => 'ar',);
1125
1126   # safety check datev export
1127   if ($::instance_conf->get_datev_check_on_sales_invoice) {
1128     my $transdate = $::form->{invdate} ? DateTime->from_lxoffice($::form->{invdate}) : undef;
1129     $transdate  ||= DateTime->today;
1130
1131     my $datev = SL::DATEV->new(
1132       exporttype => DATEV_ET_BUCHUNGEN,
1133       format     => DATEV_FORMAT_KNE,
1134       dbh        => $dbh,
1135       from       => $transdate,
1136       to         => $transdate,
1137     );
1138
1139     $datev->export;
1140
1141     if ($datev->errors) {
1142       $dbh->rollback;
1143       die join "\n", $::locale->text('DATEV check returned errors:'), $datev->errors;
1144     }
1145   }
1146
1147   my $rc = 1;
1148   $dbh->commit if !$provided_dbh;
1149
1150   $main::lxdebug->leave_sub();
1151
1152   return $rc;
1153 }
1154
1155 sub _delete_payments {
1156   $main::lxdebug->enter_sub();
1157
1158   my ($self, $form, $dbh) = @_;
1159
1160   my @delete_acc_trans_ids;
1161
1162   # Delete old payment entries from acc_trans.
1163   my $query =
1164     qq|SELECT acc_trans_id
1165        FROM acc_trans
1166        WHERE (trans_id = ?) AND fx_transaction
1167
1168        UNION
1169
1170        SELECT at.acc_trans_id
1171        FROM acc_trans at
1172        LEFT JOIN chart c ON (at.chart_id = c.id)
1173        WHERE (trans_id = ?) AND (c.link LIKE '%AR_paid%')|;
1174   push @delete_acc_trans_ids, selectall_array_query($form, $dbh, $query, conv_i($form->{id}), conv_i($form->{id}));
1175
1176   $query =
1177     qq|SELECT at.acc_trans_id
1178        FROM acc_trans at
1179        LEFT JOIN chart c ON (at.chart_id = c.id)
1180        WHERE (trans_id = ?)
1181          AND ((c.link = 'AR') OR (c.link LIKE '%:AR') OR (c.link LIKE 'AR:%'))
1182        ORDER BY at.acc_trans_id
1183        OFFSET 1|;
1184   push @delete_acc_trans_ids, selectall_array_query($form, $dbh, $query, conv_i($form->{id}));
1185
1186   if (@delete_acc_trans_ids) {
1187     $query = qq|DELETE FROM acc_trans WHERE acc_trans_id IN (| . join(", ", @delete_acc_trans_ids) . qq|)|;
1188     do_query($form, $dbh, $query);
1189   }
1190
1191   $main::lxdebug->leave_sub();
1192 }
1193
1194 sub post_payment {
1195   $main::lxdebug->enter_sub();
1196
1197   my ($self, $myconfig, $form, $locale) = @_;
1198
1199   # connect to database, turn off autocommit
1200   my $dbh = $form->get_standard_dbh;
1201   $dbh->begin_work;
1202
1203   my (%payments, $old_form, $row, $item, $query, %keep_vars);
1204
1205   $old_form = save_form();
1206
1207   # Delete all entries in acc_trans from prior payments.
1208   if (SL::DB::Default->get->payments_changeable != 0) {
1209     $self->_delete_payments($form, $dbh);
1210   }
1211
1212   # Save the new payments the user made before cleaning up $form.
1213   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 };
1214
1215   # Clean up $form so that old content won't tamper the results.
1216   %keep_vars = map { $_, 1 } qw(login password id);
1217   map { delete $form->{$_} unless $keep_vars{$_} } keys %{ $form };
1218
1219   # Retrieve the invoice from the database.
1220   $self->retrieve_invoice($myconfig, $form);
1221
1222   # Set up the content of $form in the way that IS::post_invoice() expects.
1223   $form->{exchangerate} = $form->format_amount($myconfig, $form->{exchangerate});
1224
1225   for $row (1 .. scalar @{ $form->{invoice_details} }) {
1226     $item = $form->{invoice_details}->[$row - 1];
1227
1228     map { $item->{$_} = $form->format_amount($myconfig, $item->{$_}) } qw(qty sellprice discount);
1229
1230     map { $form->{"${_}_${row}"} = $item->{$_} } keys %{ $item };
1231   }
1232
1233   $form->{rowcount} = scalar @{ $form->{invoice_details} };
1234
1235   delete @{$form}{qw(invoice_details paidaccounts storno paid)};
1236
1237   # Restore the payment options from the user input.
1238   map { $form->{$_} = $payments{$_} } keys %payments;
1239
1240   # Get the AR accno (which is normally done by Form::create_links()).
1241   $query =
1242     qq|SELECT c.accno
1243        FROM acc_trans at
1244        LEFT JOIN chart c ON (at.chart_id = c.id)
1245        WHERE (trans_id = ?)
1246          AND ((c.link = 'AR') OR (c.link LIKE '%:AR') OR (c.link LIKE 'AR:%'))
1247        ORDER BY at.acc_trans_id
1248        LIMIT 1|;
1249
1250   ($form->{AR}) = selectfirst_array_query($form, $dbh, $query, conv_i($form->{id}));
1251
1252   # Post the new payments.
1253   $self->post_invoice($myconfig, $form, $dbh, 1);
1254
1255   restore_form($old_form);
1256
1257   my $rc = $dbh->commit();
1258
1259   $main::lxdebug->leave_sub();
1260
1261   return $rc;
1262 }
1263
1264 sub process_assembly {
1265   $main::lxdebug->enter_sub();
1266
1267   my ($dbh, $myconfig, $form, $id, $totalqty) = @_;
1268
1269   my $query =
1270     qq|SELECT a.parts_id, a.qty, p.assembly, p.partnumber, p.description, p.unit,
1271          p.inventory_accno_id, p.income_accno_id, p.expense_accno_id
1272        FROM assembly a
1273        JOIN parts p ON (a.parts_id = p.id)
1274        WHERE (a.id = ?)|;
1275   my $sth = prepare_execute_query($form, $dbh, $query, conv_i($id));
1276
1277   while (my $ref = $sth->fetchrow_hashref('NAME_lc')) {
1278
1279     my $allocated = 0;
1280
1281     $ref->{inventory_accno_id} *= 1;
1282     $ref->{expense_accno_id}   *= 1;
1283
1284     # multiply by number of assemblies
1285     $ref->{qty} *= $totalqty;
1286
1287     if ($ref->{assembly}) {
1288       &process_assembly($dbh, $myconfig, $form, $ref->{parts_id}, $ref->{qty});
1289       next;
1290     } else {
1291       if ($ref->{inventory_accno_id}) {
1292         $allocated = &cogs($dbh, $myconfig, $form, $ref->{parts_id}, $ref->{qty});
1293       }
1294     }
1295
1296     # save detail record for individual assembly item in invoice table
1297     $query =
1298       qq|INSERT INTO invoice (trans_id, description, parts_id, qty, sellprice, fxsellprice, allocated, assemblyitem, unit)
1299          VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)|;
1300     my @values = (conv_i($form->{id}), $ref->{description}, conv_i($ref->{parts_id}), $ref->{qty}, 0, 0, $allocated, 't', $ref->{unit});
1301     do_query($form, $dbh, $query, @values);
1302
1303   }
1304
1305   $sth->finish;
1306
1307   $main::lxdebug->leave_sub();
1308 }
1309
1310 sub cogs {
1311   $main::lxdebug->enter_sub();
1312
1313   # adjust allocated in table invoice according to FIFO princicple
1314   # for a certain part with part_id $id
1315
1316   my ($dbh, $myconfig, $form, $id, $totalqty, $basefactor, $row) = @_;
1317
1318   $basefactor ||= 1;
1319
1320   $form->{taxzone_id} *=1;
1321   my $transdate  = $form->{invdate} ? $dbh->quote($form->{invdate}) : "current_date";
1322   my $taxzone_id = $form->{"taxzone_id"} * 1;
1323   my $query =
1324     qq|SELECT i.id, i.trans_id, i.base_qty, i.allocated, i.sellprice, i.price_factor,
1325          c1.accno AS inventory_accno, c1.new_chart_id AS inventory_new_chart, date($transdate) - c1.valid_from AS inventory_valid,
1326          c2.accno AS    income_accno, c2.new_chart_id AS    income_new_chart, date($transdate) - c2.valid_from AS    income_valid,
1327          c3.accno AS   expense_accno, c3.new_chart_id AS   expense_new_chart, date($transdate) - c3.valid_from AS   expense_valid
1328        FROM invoice i, parts p
1329        LEFT JOIN chart c1 ON ((SELECT inventory_accno_id FROM buchungsgruppen WHERE id = p.buchungsgruppen_id) = c1.id)
1330        LEFT JOIN chart c2 ON ((SELECT income_accno_id_${taxzone_id} FROM buchungsgruppen WHERE id = p.buchungsgruppen_id) = c2.id)
1331        LEFT JOIN chart c3 ON ((select expense_accno_id_${taxzone_id} FROM buchungsgruppen WHERE id = p.buchungsgruppen_id) = c3.id)
1332        WHERE (i.parts_id = p.id)
1333          AND (i.parts_id = ?)
1334          AND ((i.base_qty + i.allocated) < 0)
1335        ORDER BY trans_id|;
1336   my $sth = prepare_execute_query($form, $dbh, $query, conv_i($id));
1337
1338   my $allocated = 0;
1339   my $qty;
1340
1341 # all invoice entries of an example part:
1342
1343 # id | trans_id | base_qty | allocated | sellprice | inventory_accno | income_accno | expense_accno
1344 # ---+----------+----------+-----------+-----------+-----------------+--------------+---------------
1345 #  4 |        4 |       -5 |         5 |  20.00000 | 1140            | 4400         | 5400     bought 5 for 20
1346 #  5 |        5 |        4 |        -4 |  50.00000 | 1140            | 4400         | 5400     sold   4 for 50
1347 #  6 |        6 |        1 |        -1 |  50.00000 | 1140            | 4400         | 5400     sold   1 for 50
1348 #  7 |        7 |       -5 |         1 |  20.00000 | 1140            | 4400         | 5400     bought 5 for 20
1349 #  8 |        8 |        1 |        -1 |  50.00000 | 1140            | 4400         | 5400     sold   1 for 50
1350
1351 # 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
1352 # and all parts have been allocated
1353
1354 # so transaction 8 only sees transaction 7 with unallocated parts and adjusts allocated for that transaction, before allocated was 0
1355 #  7 |        7 |       -5 |         1 |  20.00000 | 1140            | 4400         | 5400     bought 5 for 20
1356
1357 # in this example there are still 4 unsold articles
1358
1359
1360   # search all invoice entries for the part in question, adjusting "allocated"
1361   # until the total number of sold parts has been reached
1362
1363   # ORDER BY trans_id ensures FIFO
1364
1365
1366   while (my $ref = $sth->fetchrow_hashref('NAME_lc')) {
1367     if (($qty = (($ref->{base_qty} * -1) - $ref->{allocated})) > $totalqty) {
1368       $qty = $totalqty;
1369     }
1370
1371     # update allocated in invoice
1372     $form->update_balance($dbh, "invoice", "allocated", qq|id = $ref->{id}|, $qty);
1373
1374     # total expenses and inventory
1375     # sellprice is the cost of the item
1376     my $linetotal = $form->round_amount(($ref->{sellprice} * $qty) / ( ($ref->{price_factor} || 1) * ( $basefactor || 1 )), 2);
1377
1378     if ( $::instance_conf->get_inventory_system eq 'perpetual' ) {
1379       # Bestandsmethode: when selling parts, deduct their purchase value from the inventory account
1380       $ref->{expense_accno} = ($form->{"expense_accno_$row"}) ? $form->{"expense_accno_$row"} : $ref->{expense_accno};
1381       # add to expense
1382       $form->{amount_cogs}{ $form->{id} }{ $ref->{expense_accno} } += -$linetotal;
1383       $form->{expense_inventory} .= " " . $ref->{expense_accno};
1384       $ref->{inventory_accno} = ($form->{"inventory_accno_$row"}) ? $form->{"inventory_accno_$row"} : $ref->{inventory_accno};
1385       # deduct inventory
1386       $form->{amount_cogs}{ $form->{id} }{ $ref->{inventory_accno} } -= -$linetotal;
1387       $form->{expense_inventory} .= " " . $ref->{inventory_accno};
1388     }
1389
1390     # add allocated
1391     $allocated -= $qty;
1392
1393     last if (($totalqty -= $qty) <= 0);
1394   }
1395
1396   $sth->finish;
1397
1398   $main::lxdebug->leave_sub();
1399
1400   return $allocated;
1401 }
1402
1403 sub reverse_invoice {
1404   $main::lxdebug->enter_sub();
1405
1406   my ($dbh, $form) = @_;
1407
1408   # reverse inventory items
1409   my $query =
1410     qq|SELECT i.id, i.parts_id, i.qty, i.assemblyitem, p.assembly, p.inventory_accno_id
1411        FROM invoice i
1412        JOIN parts p ON (i.parts_id = p.id)
1413        WHERE i.trans_id = ?|;
1414   my $sth = prepare_execute_query($form, $dbh, $query, conv_i($form->{"id"}));
1415
1416   while (my $ref = $sth->fetchrow_hashref('NAME_lc')) {
1417
1418     if ($ref->{inventory_accno_id}) {
1419       # de-allocated purchases
1420       $query =
1421         qq|SELECT i.id, i.trans_id, i.allocated
1422            FROM invoice i
1423            WHERE (i.parts_id = ?) AND (i.allocated > 0)
1424            ORDER BY i.trans_id DESC|;
1425       my $sth2 = prepare_execute_query($form, $dbh, $query, conv_i($ref->{"parts_id"}));
1426
1427       while (my $inhref = $sth2->fetchrow_hashref('NAME_lc')) {
1428         my $qty = $ref->{qty};
1429         if (($ref->{qty} - $inhref->{allocated}) > 0) {
1430           $qty = $inhref->{allocated};
1431         }
1432
1433         # update invoice
1434         $form->update_balance($dbh, "invoice", "allocated", qq|id = $inhref->{id}|, $qty * -1);
1435
1436         last if (($ref->{qty} -= $qty) <= 0);
1437       }
1438       $sth2->finish;
1439     }
1440   }
1441
1442   $sth->finish;
1443
1444   # delete acc_trans
1445   my @values = (conv_i($form->{id}));
1446   do_query($form, $dbh, qq|DELETE FROM acc_trans WHERE trans_id = ?|, @values);
1447   do_query($form, $dbh, qq|DELETE FROM invoice WHERE trans_id = ?|, @values);
1448   do_query($form, $dbh, qq|DELETE FROM shipto WHERE (trans_id = ?) AND (module = 'AR')|, @values);
1449
1450   $main::lxdebug->leave_sub();
1451 }
1452
1453 sub delete_invoice {
1454   $main::lxdebug->enter_sub();
1455
1456   my ($self, $myconfig, $form) = @_;
1457
1458   # connect to database
1459   my $dbh = $form->get_standard_dbh;
1460   $dbh->begin_work;
1461
1462   &reverse_invoice($dbh, $form);
1463
1464   my @values = (conv_i($form->{id}));
1465
1466   # Falls wir ein Storno haben, müssen zwei Felder in der stornierten Rechnung wieder
1467   # zurückgesetzt werden. Vgl:
1468   #  id | storno | storno_id |  paid   |  amount
1469   #----+--------+-----------+---------+-----------
1470   # 18 | f      |           | 0.00000 | 119.00000
1471   # ZU:
1472   # 18 | t      |           |  119.00000 |  119.00000
1473   #
1474   if($form->{storno}){
1475     # storno_id auslesen und korrigieren
1476     my ($invoice_id) = selectfirst_array_query($form, $dbh, qq|SELECT storno_id FROM ar WHERE id = ?|,@values);
1477     do_query($form, $dbh, qq|UPDATE ar SET storno = 'f', paid = 0 WHERE id = ?|, $invoice_id);
1478   }
1479
1480   # delete spool files
1481   my @spoolfiles = selectall_array_query($form, $dbh, qq|SELECT spoolfile FROM status WHERE trans_id = ?|, @values);
1482
1483   my @queries = (
1484     qq|DELETE FROM status WHERE trans_id = ?|,
1485     qq|DELETE FROM periodic_invoices WHERE ar_id = ?|,
1486     qq|DELETE FROM ar WHERE id = ?|,
1487   );
1488
1489   map { do_query($form, $dbh, $_, @values) } @queries;
1490
1491   my $rc = $dbh->commit;
1492
1493   if ($rc) {
1494     my $spool = $::lx_office_conf{paths}->{spool};
1495     map { unlink "$spool/$_" if -f "$spool/$_"; } @spoolfiles;
1496   }
1497
1498   $main::lxdebug->leave_sub();
1499
1500   return $rc;
1501 }
1502
1503 sub retrieve_invoice {
1504   $main::lxdebug->enter_sub();
1505
1506   my ($self, $myconfig, $form) = @_;
1507
1508   # connect to database
1509   my $dbh = $form->get_standard_dbh;
1510
1511   my ($sth, $ref, $query);
1512
1513   my $query_transdate = !$form->{id} ? ", current_date AS invdate" : '';
1514
1515   $query =
1516     qq|SELECT
1517          (SELECT c.accno FROM chart c WHERE d.inventory_accno_id = c.id) AS inventory_accno,
1518          (SELECT c.accno FROM chart c WHERE d.income_accno_id = c.id)    AS income_accno,
1519          (SELECT c.accno FROM chart c WHERE d.expense_accno_id = c.id)   AS expense_accno,
1520          (SELECT c.accno FROM chart c WHERE d.fxgain_accno_id = c.id)    AS fxgain_accno,
1521          (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id = c.id)    AS fxloss_accno,
1522          d.curr AS currencies
1523          ${query_transdate}
1524        FROM defaults d|;
1525
1526   $ref = selectfirst_hashref_query($form, $dbh, $query);
1527   map { $form->{$_} = $ref->{$_} } keys %{ $ref };
1528
1529   if ($form->{id}) {
1530     my $id = conv_i($form->{id});
1531
1532     # retrieve invoice
1533     #erweiterung um das entsprechende feld lieferscheinnummer (a.donumber) in der html-maske anzuzeigen 12.02.2009 jb
1534
1535     $query =
1536       qq|SELECT
1537            a.invnumber, a.ordnumber, a.quonumber, a.cusordnumber,
1538            a.orddate, a.quodate, a.globalproject_id,
1539            a.transdate AS invdate, a.deliverydate, a.paid, a.storno, a.gldate,
1540            a.shippingpoint, a.shipvia, a.terms, a.notes, a.intnotes, a.taxzone_id,
1541            a.duedate, a.taxincluded, a.curr AS currency, a.shipto_id, a.cp_id,
1542            a.employee_id, a.salesman_id, a.payment_id,
1543            a.language_id, a.delivery_customer_id, a.delivery_vendor_id, a.type,
1544            a.transaction_description, a.donumber, a.invnumber_for_credit_note,
1545            a.marge_total, a.marge_percent, a.direct_debit,
1546            e.name AS employee
1547          FROM ar a
1548          LEFT JOIN employee e ON (e.id = a.employee_id)
1549          WHERE a.id = ?|;
1550     $ref = selectfirst_hashref_query($form, $dbh, $query, $id);
1551     map { $form->{$_} = $ref->{$_} } keys %{ $ref };
1552
1553     # remove any trailing whitespace
1554     $form->{currency} =~ s/\s*$//;
1555
1556     $form->{exchangerate} = $form->get_exchangerate($dbh, $form->{currency}, $form->{invdate}, "buy");
1557
1558     # get shipto
1559     $query = qq|SELECT * FROM shipto WHERE (trans_id = ?) AND (module = 'AR')|;
1560     $ref = selectfirst_hashref_query($form, $dbh, $query, $id);
1561     delete $ref->{id};
1562     map { $form->{$_} = $ref->{$_} } keys %{ $ref };
1563
1564     foreach my $vc (qw(customer vendor)) {
1565       next if !$form->{"delivery_${vc}_id"};
1566       ($form->{"delivery_${vc}_string"}) = selectrow_query($form, $dbh, qq|SELECT name FROM customer WHERE id = ?|, $id);
1567     }
1568
1569     # get printed, emailed
1570     $query = qq|SELECT printed, emailed, spoolfile, formname FROM status WHERE trans_id = ?|;
1571     $sth = prepare_execute_query($form, $dbh, $query, $id);
1572
1573     while ($ref = $sth->fetchrow_hashref('NAME_lc')) {
1574       $form->{printed} .= "$ref->{formname} " if $ref->{printed};
1575       $form->{emailed} .= "$ref->{formname} " if $ref->{emailed};
1576       $form->{queued} .= "$ref->{formname} $ref->{spoolfile} " if $ref->{spoolfile};
1577     }
1578     $sth->finish;
1579     map { $form->{$_} =~ s/ +$//g } qw(printed emailed queued);
1580
1581     my $transdate = $form->{deliverydate} ? $dbh->quote($form->{deliverydate})
1582                   : $form->{invdate}      ? $dbh->quote($form->{invdate})
1583                   :                         "current_date";
1584
1585
1586     my $taxzone_id = $form->{taxzone_id} *= 1;
1587     $taxzone_id = 0 if (0 > $taxzone_id) || (3 < $taxzone_id);
1588
1589     # retrieve individual items
1590     $query =
1591       qq|SELECT
1592            c1.accno AS inventory_accno, c1.new_chart_id AS inventory_new_chart, date($transdate) - c1.valid_from AS inventory_valid,
1593            c2.accno AS income_accno,    c2.new_chart_id AS income_new_chart,    date($transdate) - c2.valid_from as income_valid,
1594            c3.accno AS expense_accno,   c3.new_chart_id AS expense_new_chart,   date($transdate) - c3.valid_from AS expense_valid,
1595
1596            i.id AS invoice_id,
1597            i.description, i.longdescription, i.qty, i.fxsellprice AS sellprice, i.discount, i.parts_id AS id, i.unit, i.deliverydate AS reqdate,
1598            i.project_id, i.serialnumber, i.id AS invoice_pos, i.pricegroup_id, i.ordnumber, i.transdate, i.cusordnumber, i.subtotal, i.lastcost,
1599            i.price_factor_id, i.price_factor, i.marge_price_factor,
1600            p.partnumber, p.assembly, p.bin, p.notes AS partnotes, p.inventory_accno_id AS part_inventory_accno_id, p.formel, p.listprice,
1601            pr.projectnumber, pg.partsgroup, prg.pricegroup
1602
1603          FROM invoice i
1604          LEFT JOIN parts p ON (i.parts_id = p.id)
1605          LEFT JOIN project pr ON (i.project_id = pr.id)
1606          LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id)
1607          LEFT JOIN pricegroup prg ON (i.pricegroup_id = prg.id)
1608
1609          LEFT JOIN chart c1 ON ((SELECT inventory_accno_id             FROM buchungsgruppen WHERE id = p.buchungsgruppen_id) = c1.id)
1610          LEFT JOIN chart c2 ON ((SELECT income_accno_id_${taxzone_id}  FROM buchungsgruppen WHERE id = p.buchungsgruppen_id) = c2.id)
1611          LEFT JOIN chart c3 ON ((SELECT expense_accno_id_${taxzone_id} FROM buchungsgruppen WHERE id = p.buchungsgruppen_id) = c3.id)
1612
1613          WHERE (i.trans_id = ?) AND NOT (i.assemblyitem = '1') ORDER BY i.id|;
1614
1615     $sth = prepare_execute_query($form, $dbh, $query, $id);
1616
1617     while (my $ref = $sth->fetchrow_hashref('NAME_lc')) {
1618       # Retrieve custom variables.
1619       my $cvars = CVar->get_custom_variables(dbh        => $dbh,
1620                                              module     => 'IC',
1621                                              sub_module => 'invoice',
1622                                              trans_id   => $ref->{invoice_id},
1623                                             );
1624       map { $ref->{"ic_cvar_$_->{name}"} = $_->{value} } @{ $cvars };
1625       delete $ref->{invoice_id};
1626
1627       map({ delete($ref->{$_}); } qw(inventory_accno inventory_new_chart inventory_valid)) if !$ref->{"part_inventory_accno_id"};
1628       delete($ref->{"part_inventory_accno_id"});
1629
1630       foreach my $type (qw(inventory income expense)) {
1631         while ($ref->{"${type}_new_chart"} && ($ref->{"${type}_valid"} >=0)) {
1632           my $query = qq|SELECT accno, new_chart_id, date($transdate) - valid_from FROM chart WHERE id = ?|;
1633           @$ref{ map $type.$_, qw(_accno _new_chart _valid) } = selectrow_query($form, $dbh, $query, $ref->{"${type}_new_chart"});
1634         }
1635       }
1636
1637       # get tax rates and description
1638       my $accno_id = ($form->{vc} eq "customer") ? $ref->{income_accno} : $ref->{expense_accno};
1639       $query =
1640         qq|SELECT c.accno, t.taxdescription, t.rate, t.taxnumber FROM tax t
1641            LEFT JOIN chart c ON (c.id = t.chart_id)
1642            WHERE t.id IN
1643              (SELECT tk.tax_id FROM taxkeys tk
1644               WHERE tk.chart_id = (SELECT id FROM chart WHERE accno = ?)
1645                 AND startdate <= date($transdate)
1646               ORDER BY startdate DESC LIMIT 1)
1647            ORDER BY c.accno|;
1648       my $stw = prepare_execute_query($form, $dbh, $query, $accno_id);
1649       $ref->{taxaccounts} = "";
1650       my $i=0;
1651       while (my $ptr = $stw->fetchrow_hashref('NAME_lc')) {
1652
1653         if (($ptr->{accno} eq "") && ($ptr->{rate} == 0)) {
1654           $i++;
1655           $ptr->{accno} = $i;
1656         }
1657         $ref->{taxaccounts} .= "$ptr->{accno} ";
1658
1659         if (!($form->{taxaccounts} =~ /\Q$ptr->{accno}\E/)) {
1660           $form->{"$ptr->{accno}_rate"}        = $ptr->{rate};
1661           $form->{"$ptr->{accno}_description"} = $ptr->{taxdescription};
1662           $form->{"$ptr->{accno}_taxnumber"}   = $ptr->{taxnumber};
1663           $form->{taxaccounts} .= "$ptr->{accno} ";
1664         }
1665
1666       }
1667
1668       $ref->{qty} *= -1 if $form->{type} eq "credit_note";
1669
1670       chop $ref->{taxaccounts};
1671       push @{ $form->{invoice_details} }, $ref;
1672       $stw->finish;
1673     }
1674     $sth->finish;
1675
1676     Common::webdav_folder($form);
1677   }
1678
1679   my $rc = $dbh->commit;
1680
1681   $main::lxdebug->leave_sub();
1682
1683   return $rc;
1684 }
1685
1686 sub get_customer {
1687   $main::lxdebug->enter_sub();
1688
1689   my ($self, $myconfig, $form) = @_;
1690
1691   # connect to database
1692   my $dbh = $form->get_standard_dbh;
1693
1694   my $dateformat = $myconfig->{dateformat};
1695   $dateformat .= "yy" if $myconfig->{dateformat} !~ /^y/;
1696
1697   my (@values, $duedate, $ref, $query);
1698
1699   if ($form->{invdate}) {
1700     $duedate = "to_date(?, '$dateformat')";
1701     push @values, $form->{invdate};
1702   } else {
1703     $duedate = "current_date";
1704   }
1705
1706   my $cid = conv_i($form->{customer_id});
1707   my $payment_id;
1708
1709   if ($form->{payment_id}) {
1710     $payment_id = "(pt.id = ?) OR";
1711     push @values, conv_i($form->{payment_id});
1712   }
1713
1714   # get customer
1715   $query =
1716     qq|SELECT
1717          c.id AS customer_id, c.name AS customer, c.discount as customer_discount, c.creditlimit, c.terms,
1718          c.email, c.cc, c.bcc, c.language_id, c.payment_id,
1719          c.street, c.zipcode, c.city, c.country,
1720          c.notes AS intnotes, c.klass as customer_klass, c.taxzone_id, c.salesman_id, c.curr,
1721          c.taxincluded_checked, c.direct_debit,
1722          $duedate + COALESCE(pt.terms_netto, 0) AS duedate,
1723          b.discount AS tradediscount, b.description AS business
1724        FROM customer c
1725        LEFT JOIN business b ON (b.id = c.business_id)
1726        LEFT JOIN payment_terms pt ON ($payment_id (c.payment_id = pt.id))
1727        WHERE c.id = ?|;
1728   push @values, $cid;
1729   $ref = selectfirst_hashref_query($form, $dbh, $query, @values);
1730
1731   delete $ref->{salesman_id} if !$ref->{salesman_id};
1732
1733   map { $form->{$_} = $ref->{$_} } keys %$ref;
1734
1735   # remove any trailing whitespace
1736   $form->{curr} =~ s/\s*$//;
1737
1738   # use customer currency if not empty
1739   $form->{currency} = $form->{curr} if $form->{curr};
1740
1741   $query =
1742     qq|SELECT sum(amount - paid) AS dunning_amount
1743        FROM ar
1744        WHERE (paid < amount)
1745          AND (customer_id = ?)
1746          AND (dunning_config_id IS NOT NULL)|;
1747   $ref = selectfirst_hashref_query($form, $dbh, $query, $cid);
1748   map { $form->{$_} = $ref->{$_} } keys %$ref;
1749
1750   $query =
1751     qq|SELECT dnn.dunning_description AS max_dunning_level
1752        FROM dunning_config dnn
1753        WHERE id IN (SELECT dunning_config_id
1754                     FROM ar
1755                     WHERE (paid < amount) AND (customer_id = ?) AND (dunning_config_id IS NOT NULL))
1756        ORDER BY dunning_level DESC LIMIT 1|;
1757   $ref = selectfirst_hashref_query($form, $dbh, $query, $cid);
1758   map { $form->{$_} = $ref->{$_} } keys %$ref;
1759
1760   $form->{creditremaining} = $form->{creditlimit};
1761   $query = qq|SELECT SUM(amount - paid) FROM ar WHERE customer_id = ?|;
1762   my ($value) = selectrow_query($form, $dbh, $query, $cid);
1763   $form->{creditremaining} -= $value;
1764
1765   $query =
1766     qq|SELECT o.amount,
1767          (SELECT e.buy FROM exchangerate e
1768           WHERE e.curr = o.curr
1769             AND e.transdate = o.transdate)
1770        FROM oe o
1771        WHERE o.customer_id = ?
1772          AND o.quotation = '0'
1773          AND o.closed = '0'|;
1774   my $sth = prepare_execute_query($form, $dbh, $query, $cid);
1775
1776   while (my ($amount, $exch) = $sth->fetchrow_array) {
1777     $exch = 1 unless $exch;
1778     $form->{creditremaining} -= $amount * $exch;
1779   }
1780   $sth->finish;
1781
1782   # get shipto if we did not converted an order or invoice
1783   if (!$form->{shipto}) {
1784     map { delete $form->{$_} }
1785       qw(shiptoname shiptodepartment_1 shiptodepartment_2
1786          shiptostreet shiptozipcode shiptocity shiptocountry
1787          shiptocontact shiptophone shiptofax shiptoemail);
1788
1789     $query = qq|SELECT * FROM shipto WHERE trans_id = ? AND module = 'CT'|;
1790     $ref = selectfirst_hashref_query($form, $dbh, $query, $cid);
1791     delete $ref->{id};
1792     map { $form->{$_} = $ref->{$_} } keys %$ref;
1793   }
1794
1795   # setup last accounts used for this customer
1796   if (!$form->{id} && $form->{type} !~ /_(order|quotation)/) {
1797     $query =
1798       qq|SELECT c.id, c.accno, c.description, c.link, c.category
1799          FROM chart c
1800          JOIN acc_trans ac ON (ac.chart_id = c.id)
1801          JOIN ar a ON (a.id = ac.trans_id)
1802          WHERE a.customer_id = ?
1803            AND NOT (c.link LIKE '%_tax%' OR c.link LIKE '%_paid%')
1804            AND a.id IN (SELECT max(a2.id) FROM ar a2 WHERE a2.customer_id = ?)|;
1805     $sth = prepare_execute_query($form, $dbh, $query, $cid, $cid);
1806
1807     my $i = 0;
1808     while ($ref = $sth->fetchrow_hashref('NAME_lc')) {
1809       if ($ref->{category} eq 'I') {
1810         $i++;
1811         $form->{"AR_amount_$i"} = "$ref->{accno}--$ref->{description}";
1812
1813         if ($form->{initial_transdate}) {
1814           my $tax_query =
1815             qq|SELECT tk.tax_id, t.rate
1816                FROM taxkeys tk
1817                LEFT JOIN tax t ON tk.tax_id = t.id
1818                WHERE (tk.chart_id = ?) AND (startdate <= date(?))
1819                ORDER BY tk.startdate DESC
1820                LIMIT 1|;
1821           my ($tax_id, $rate) =
1822             selectrow_query($form, $dbh, $tax_query, $ref->{id},
1823                             $form->{initial_transdate});
1824           $form->{"taxchart_$i"} = "${tax_id}--${rate}";
1825         }
1826       }
1827       if ($ref->{category} eq 'A') {
1828         $form->{ARselected} = $form->{AR_1} = $ref->{accno};
1829       }
1830     }
1831     $sth->finish;
1832     $form->{rowcount} = $i if ($i && !$form->{type});
1833   }
1834
1835   $main::lxdebug->leave_sub();
1836 }
1837
1838 sub retrieve_item {
1839   $main::lxdebug->enter_sub();
1840
1841   my ($self, $myconfig, $form) = @_;
1842
1843   # connect to database
1844   my $dbh = $form->get_standard_dbh;
1845
1846   my $i = $form->{rowcount};
1847
1848   my $where = qq|NOT p.obsolete = '1'|;
1849   my @values;
1850
1851   foreach my $column (qw(p.partnumber p.description pgpartsgroup )) {
1852     my ($table, $field) = split m/\./, $column;
1853     next if !$form->{"${field}_${i}"};
1854     $where .= qq| AND lower(${column}) ILIKE ?|;
1855     push @values, '%' . $form->{"${field}_${i}"} . '%';
1856   }
1857
1858   #Es soll auch nach EAN gesucht werden, ohne Einschränkung durch Beschreibung
1859   if ($form->{"partnumber_$i"} && !$form->{"description_$i"}) {
1860     $where .= qq| OR (NOT p.obsolete = '1' AND p.ean = ? )|;
1861     push @values, $form->{"partnumber_$i"};
1862   }
1863
1864   # Search for part ID overrides all other criteria.
1865   if ($form->{"id_${i}"}) {
1866     $where  = qq|p.id = ?|;
1867     @values = ($form->{"id_${i}"});
1868   }
1869
1870   if ($form->{"description_$i"}) {
1871     $where .= qq| ORDER BY p.description|;
1872   } else {
1873     $where .= qq| ORDER BY p.partnumber|;
1874   }
1875
1876   my $transdate;
1877   if ($form->{type} eq "invoice") {
1878     $transdate =
1879       $form->{deliverydate} ? $dbh->quote($form->{deliverydate}) :
1880       $form->{invdate}      ? $dbh->quote($form->{invdate}) :
1881                               "current_date";
1882   } else {
1883     $transdate =
1884       $form->{transdate}    ? $dbh->quote($form->{transdate}) :
1885                               "current_date";
1886   }
1887
1888   my $taxzone_id = $form->{taxzone_id} * 1;
1889   $taxzone_id = 0 if (0 > $taxzone_id) || (3 < $taxzone_id);
1890
1891   my $query =
1892     qq|SELECT
1893          p.id, p.partnumber, p.description, p.sellprice,
1894          p.listprice, p.inventory_accno_id, p.lastcost,
1895
1896          c1.accno AS inventory_accno,
1897          c1.new_chart_id AS inventory_new_chart,
1898          date($transdate) - c1.valid_from AS inventory_valid,
1899
1900          c2.accno AS income_accno,
1901          c2.new_chart_id AS income_new_chart,
1902          date($transdate)  - c2.valid_from AS income_valid,
1903
1904          c3.accno AS expense_accno,
1905          c3.new_chart_id AS expense_new_chart,
1906          date($transdate) - c3.valid_from AS expense_valid,
1907
1908          p.unit, p.assembly, p.bin, p.onhand,
1909          p.notes AS partnotes, p.notes AS longdescription,
1910          p.not_discountable, p.formel, p.payment_id AS part_payment_id,
1911          p.price_factor_id,
1912
1913          pfac.factor AS price_factor,
1914
1915          pg.partsgroup
1916
1917        FROM parts p
1918        LEFT JOIN chart c1 ON
1919          ((SELECT inventory_accno_id
1920            FROM buchungsgruppen
1921            WHERE id = p.buchungsgruppen_id) = c1.id)
1922        LEFT JOIN chart c2 ON
1923          ((SELECT income_accno_id_${taxzone_id}
1924            FROM buchungsgruppen
1925            WHERE id = p.buchungsgruppen_id) = c2.id)
1926        LEFT JOIN chart c3 ON
1927          ((SELECT expense_accno_id_${taxzone_id}
1928            FROM buchungsgruppen
1929            WHERE id = p.buchungsgruppen_id) = c3.id)
1930        LEFT JOIN partsgroup pg ON (pg.id = p.partsgroup_id)
1931        LEFT JOIN price_factors pfac ON (pfac.id = p.price_factor_id)
1932        WHERE $where|;
1933   my $sth = prepare_execute_query($form, $dbh, $query, @values);
1934
1935   my @translation_queries = ( [ qq|SELECT tr.translation, tr.longdescription
1936                                    FROM translation tr
1937                                    WHERE tr.language_id = ? AND tr.parts_id = ?| ],
1938                               [ qq|SELECT tr.translation, tr.longdescription
1939                                    FROM translation tr
1940                                    WHERE tr.language_id IN
1941                                      (SELECT id
1942                                       FROM language
1943                                       WHERE article_code = (SELECT article_code FROM language WHERE id = ?))
1944                                      AND tr.parts_id = ?
1945                                    LIMIT 1| ] );
1946   map { push @{ $_ }, prepare_query($form, $dbh, $_->[0]) } @translation_queries;
1947
1948   while (my $ref = $sth->fetchrow_hashref('NAME_lc')) {
1949
1950     # In der Buchungsgruppe ist immer ein Bestandskonto verknuepft, auch wenn
1951     # es sich um eine Dienstleistung handelt. Bei Dienstleistungen muss das
1952     # Buchungskonto also aus dem Ergebnis rausgenommen werden.
1953     if (!$ref->{inventory_accno_id}) {
1954       map({ delete($ref->{"inventory_${_}"}); } qw(accno new_chart valid));
1955     }
1956     delete($ref->{inventory_accno_id});
1957
1958     foreach my $type (qw(inventory income expense)) {
1959       while ($ref->{"${type}_new_chart"} && ($ref->{"${type}_valid"} >=0)) {
1960         my $query =
1961           qq|SELECT accno, new_chart_id, date($transdate) - valid_from
1962              FROM chart
1963              WHERE id = ?|;
1964         ($ref->{"${type}_accno"},
1965          $ref->{"${type}_new_chart"},
1966          $ref->{"${type}_valid"})
1967           = selectrow_query($form, $dbh, $query, $ref->{"${type}_new_chart"});
1968       }
1969     }
1970
1971     if ($form->{payment_id} eq "") {
1972       $form->{payment_id} = $form->{part_payment_id};
1973     }
1974
1975     # get tax rates and description
1976     my $accno_id = ($form->{vc} eq "customer") ? $ref->{income_accno} : $ref->{expense_accno};
1977     $query =
1978       qq|SELECT c.accno, t.taxdescription, t.rate, t.taxnumber
1979          FROM tax t
1980          LEFT JOIN chart c ON (c.id = t.chart_id)
1981          WHERE t.id in
1982            (SELECT tk.tax_id
1983             FROM taxkeys tk
1984             WHERE tk.chart_id = (SELECT id from chart WHERE accno = ?)
1985               AND startdate <= ?
1986             ORDER BY startdate DESC
1987             LIMIT 1)
1988          ORDER BY c.accno|;
1989     @values = ($accno_id, $transdate eq "current_date" ? "now" : $transdate);
1990     my $stw = $dbh->prepare($query);
1991     $stw->execute(@values) || $form->dberror($query);
1992
1993     $ref->{taxaccounts} = "";
1994     my $i = 0;
1995     while (my $ptr = $stw->fetchrow_hashref('NAME_lc')) {
1996
1997       #    if ($customertax{$ref->{accno}})
1998       if (($ptr->{accno} eq "") && ($ptr->{rate} == 0)) {
1999         $i++;
2000         $ptr->{accno} = $i;
2001       }
2002       $ref->{taxaccounts} .= "$ptr->{accno} ";
2003
2004       if (!($form->{taxaccounts} =~ /\Q$ptr->{accno}\E/)) {
2005         $form->{"$ptr->{accno}_rate"}        = $ptr->{rate};
2006         $form->{"$ptr->{accno}_description"} = $ptr->{taxdescription};
2007         $form->{"$ptr->{accno}_taxnumber"}   = $ptr->{taxnumber};
2008         $form->{taxaccounts} .= "$ptr->{accno} ";
2009       }
2010
2011     }
2012
2013     $stw->finish;
2014     chop $ref->{taxaccounts};
2015
2016     if ($form->{language_id}) {
2017       for my $spec (@translation_queries) {
2018         do_statement($form, $spec->[1], $spec->[0], conv_i($form->{language_id}), conv_i($ref->{id}));
2019         my ($translation, $longdescription) = $spec->[1]->fetchrow_array;
2020         next unless $translation;
2021         $ref->{description} = $translation;
2022         $ref->{longdescription} = $longdescription;
2023         last;
2024       }
2025     }
2026
2027     $ref->{onhand} *= 1;
2028
2029     push @{ $form->{item_list} }, $ref;
2030   }
2031   $sth->finish;
2032   $_->[1]->finish for @translation_queries;
2033
2034   foreach my $item (@{ $form->{item_list} }) {
2035     my $custom_variables = CVar->get_custom_variables(module   => 'IC',
2036                                                       trans_id => $item->{id},
2037                                                       dbh      => $dbh,
2038                                                      );
2039
2040     map { $item->{"ic_cvar_" . $_->{name} } = $_->{value} } @{ $custom_variables };
2041   }
2042
2043   $main::lxdebug->leave_sub();
2044 }
2045
2046 ##########################
2047 # get pricegroups from database
2048 # build up selected pricegroup
2049 # if an exchange rate - change price
2050 # for each part
2051 #
2052 sub get_pricegroups_for_parts {
2053
2054   $main::lxdebug->enter_sub();
2055
2056   my ($self, $myconfig, $form) = @_;
2057
2058   my $dbh = $form->get_standard_dbh;
2059
2060   $form->{"PRICES"} = {};
2061
2062   my $i  = 1;
2063   my $id = 0;
2064   my $all_units = AM->retrieve_units($myconfig, $form);
2065   while (($form->{"id_$i"}) or ($form->{"new_id_$i"})) {
2066     $form->{"PRICES"}{$i} = [];
2067
2068     $id = $form->{"id_$i"};
2069
2070     if (!($form->{"id_$i"}) and $form->{"new_id_$i"}) {
2071       $id = $form->{"new_id_$i"};
2072     }
2073
2074     my ($price, $selectedpricegroup_id) = split(/--/, $form->{"sellprice_pg_$i"});
2075
2076     my $pricegroup_old = $form->{"pricegroup_old_$i"};
2077
2078     # sellprice has format 13,0000 or 0,00000,  can't check for 0 numerically
2079     my $sellprice = $form->{"sellprice_$i"};
2080     my $pricegroup_id = $form->{"pricegroup_id_$i"};
2081     $form->{"new_pricegroup_$i"} = $selectedpricegroup_id;
2082     $form->{"old_pricegroup_$i"} = $pricegroup_old;
2083
2084     my $price_new = $form->{"price_new_$i"};
2085     my $price_old = $form->{"price_old_$i"};
2086
2087     if (!$form->{"unit_old_$i"}) {
2088       # Neue Ware aus der Datenbank. In diesem Fall ist unit_$i die
2089       # Einheit, wie sie in den Stammdaten hinterlegt wurde.
2090       # Es sollte also angenommen werden, dass diese ausgewaehlt war.
2091       $form->{"unit_old_$i"} = $form->{"unit_$i"};
2092     }
2093
2094     # Die zuletzt ausgewaehlte mit der aktuell ausgewaehlten Einheit
2095     # vergleichen und bei Unterschied den Preis entsprechend umrechnen.
2096     $form->{"selected_unit_$i"} = $form->{"unit_$i"} unless ($form->{"selected_unit_$i"});
2097
2098     if (!$all_units->{$form->{"selected_unit_$i"}} ||
2099         ($all_units->{$form->{"selected_unit_$i"}}->{"base_unit"} ne
2100          $all_units->{$form->{"unit_old_$i"}}->{"base_unit"})) {
2101       # Die ausgewaehlte Einheit ist fuer diesen Artikel nicht gueltig
2102       # (z.B. Dimensionseinheit war ausgewaehlt, es handelt sich aber
2103       # um eine Dienstleistung). Dann keinerlei Umrechnung vornehmen.
2104       $form->{"unit_old_$i"} = $form->{"selected_unit_$i"} = $form->{"unit_$i"};
2105     }
2106
2107     my $basefactor = 1;
2108
2109     if ($form->{"unit_old_$i"} ne $form->{"selected_unit_$i"}) {
2110       if (defined($all_units->{$form->{"unit_old_$i"}}->{"factor"}) &&
2111           $all_units->{$form->{"unit_old_$i"}}->{"factor"}) {
2112         $basefactor = $all_units->{$form->{"selected_unit_$i"}}->{"factor"} /
2113           $all_units->{$form->{"unit_old_$i"}}->{"factor"};
2114       }
2115     }
2116
2117     if (!$form->{"basefactor_$i"}) {
2118       $form->{"basefactor_$i"} = 1;
2119     }
2120
2121     my $query =
2122        qq|SELECT
2123             0 as pricegroup_id,
2124             sellprice AS default_sellprice,
2125             '' AS pricegroup,
2126             sellprice AS price,
2127             'selected' AS selected
2128           FROM parts
2129           WHERE id = ?
2130           UNION ALL
2131           SELECT
2132            pricegroup_id,
2133            parts.sellprice AS default_sellprice,
2134            pricegroup.pricegroup,
2135            price,
2136            '' AS selected
2137           FROM prices
2138           LEFT JOIN parts ON parts.id = parts_id
2139           LEFT JOIN pricegroup ON pricegroup.id = pricegroup_id
2140           WHERE parts_id = ?
2141           ORDER BY pricegroup|;
2142     my @values = (conv_i($id), conv_i($id));
2143     my $pkq = prepare_execute_query($form, $dbh, $query, @values);
2144
2145     while (my $pkr = $pkq->fetchrow_hashref('NAME_lc')) {
2146       $pkr->{id}       = $id;
2147       $pkr->{selected} = '';
2148
2149       # if there is an exchange rate change price
2150       if (($form->{exchangerate} * 1) != 0) {
2151         $pkr->{price} /= $form->{exchangerate};
2152       }
2153
2154       $pkr->{price} *= $form->{"basefactor_$i"};
2155       $pkr->{price} *= $basefactor;
2156       $pkr->{price_ufmt} = $pkr->{price};
2157       $pkr->{price} = $form->format_amount($myconfig, $pkr->{price}, 5);
2158
2159       if (!defined $selectedpricegroup_id) {
2160         # new entries in article list, either old invoice was loaded (edit) or a new article was added
2161         # Case A: open old invoice, no pricegroup selected
2162         # Case B: add new article to invoice, no pricegroup selected
2163
2164         # to distinguish case A and B the variable pricegroup_id_$i is used
2165         # for new articles this variable isn't defined, for loaded articles it is
2166         # sellprice can't be used, as it already has 0,00 set
2167
2168         if ($pkr->{pricegroup_id} eq $form->{"pricegroup_id_$i"} and defined $form->{"pricegroup_id_$i"}) {
2169           # Case A
2170           $pkr->{selected}  = ' selected';
2171         } elsif ($pkr->{pricegroup_id} eq $form->{customer_klass}
2172                  and not defined $form->{"pricegroup_id_$i"}
2173                  and $pkr->{price_ufmt} != 0    # only use customer pricegroup price if it has a value, else use default_sellprice
2174                                                 # for the case where pricegroup prices haven't been set
2175                 ) {
2176           # Case B: use default pricegroup of customer
2177
2178           $pkr->{selected}  = ' selected'; # unless $form->{selected};
2179           # no customer pricesgroup set
2180           if ($pkr->{price_ufmt} == $pkr->{default_sellprice}) {
2181
2182             $pkr->{price} = $form->{"sellprice_$i"};
2183
2184           } else {
2185
2186 # this sub should not set anything and only return. --sschoeling, 20090506
2187 # is this correct? put in again... -- grichardson 20110119
2188             $form->{"sellprice_$i"} = $pkr->{price};
2189           }
2190
2191         } elsif ($pkr->{price_ufmt} == $pkr->{default_sellprice} and $pkr->{default_sellprice} != 0) {
2192           $pkr->{price}    = $form->{"sellprice_$i"};
2193           $pkr->{selected} = ' selected';
2194         }
2195       }
2196
2197       # existing article: pricegroup or price changed
2198       if ($selectedpricegroup_id or $selectedpricegroup_id == 0) {
2199         if ($selectedpricegroup_id ne $pricegroup_old) {
2200           # pricegroup has changed
2201           if ($pkr->{pricegroup_id} eq $selectedpricegroup_id) {
2202             $pkr->{selected}  = ' selected';
2203           }
2204         } elsif ( ($form->parse_amount($myconfig, $price_new)
2205                  != $form->parse_amount($myconfig, $form->{"sellprice_$i"}))
2206                   and ($price_new ne 0) and defined $price_new) {
2207           # sellprice has changed
2208           # when loading existing invoices $price_new is NULL
2209           if ($pkr->{pricegroup_id} == 0) {
2210             $pkr->{price}     = $form->{"sellprice_$i"};
2211             $pkr->{selected}  = ' selected';
2212           }
2213         } elsif ($pkr->{pricegroup_id} eq $selectedpricegroup_id) {
2214           # neither sellprice nor pricegroup changed
2215           $pkr->{selected}  = ' selected';
2216           if (    ($pkr->{pricegroup_id} == 0) and ($pkr->{price} == $form->{"sellprice_$i"})) {
2217             # $pkr->{price}                         = $form->{"sellprice_$i"};
2218           } else {
2219             $pkr->{price} = $form->{"sellprice_$i"};
2220           }
2221         }
2222       }
2223       push @{ $form->{PRICES}{$i} }, $pkr;
2224
2225     }
2226     $form->{"basefactor_$i"} *= $basefactor;
2227
2228     $i++;
2229
2230     $pkq->finish;
2231   }
2232
2233   $main::lxdebug->leave_sub();
2234 }
2235
2236 sub has_storno {
2237   $main::lxdebug->enter_sub();
2238
2239   my ($self, $myconfig, $form, $table) = @_;
2240
2241   $main::lxdebug->leave_sub() and return 0 unless ($form->{id});
2242
2243   # make sure there's no funny stuff in $table
2244   # ToDO: die when this happens and throw an error
2245   $main::lxdebug->leave_sub() and return 0 if ($table =~ /\W/);
2246
2247   my $dbh = $form->get_standard_dbh;
2248
2249   my $query = qq|SELECT storno FROM $table WHERE storno_id = ?|;
2250   my ($result) = selectrow_query($form, $dbh, $query, $form->{id});
2251
2252   $main::lxdebug->leave_sub();
2253
2254   return $result;
2255 }
2256
2257 sub is_storno {
2258   $main::lxdebug->enter_sub();
2259
2260   my ($self, $myconfig, $form, $table, $id) = @_;
2261
2262   $main::lxdebug->leave_sub() and return 0 unless ($id);
2263
2264   # make sure there's no funny stuff in $table
2265   # ToDO: die when this happens and throw an error
2266   $main::lxdebug->leave_sub() and return 0 if ($table =~ /\W/);
2267
2268   my $dbh = $form->get_standard_dbh;
2269
2270   my $query = qq|SELECT storno FROM $table WHERE id = ?|;
2271   my ($result) = selectrow_query($form, $dbh, $query, $id);
2272
2273   $main::lxdebug->leave_sub();
2274
2275   return $result;
2276 }
2277
2278 sub get_standard_accno_current_assets {
2279   $main::lxdebug->enter_sub();
2280
2281   my ($self, $myconfig, $form) = @_;
2282
2283   my $dbh = $form->get_standard_dbh;
2284
2285   my $query = qq| SELECT accno FROM chart WHERE id = (SELECT ar_paid_accno_id FROM defaults)|;
2286   my ($result) = selectrow_query($form, $dbh, $query);
2287
2288   $main::lxdebug->leave_sub();
2289
2290   return $result;
2291 }
2292
2293 1;