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