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