Die Warengruppe beim Ausdruck der Vorlage zur Verfügung stellen.
[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   $query = qq|UPDATE ar set
955                 invnumber   = ?, ordnumber     = ?, quonumber     = ?, cusordnumber  = ?,
956                 transdate   = ?, orddate       = ?, quodate       = ?, customer_id   = ?,
957                 amount      = ?, netamount     = ?, paid          = ?, datepaid      = ?,
958                 duedate     = ?, deliverydate  = ?, invoice       = ?, shippingpoint = ?,
959                 shipvia     = ?, terms         = ?, notes         = ?, intnotes      = ?,
960                 curr        = ?, department_id = ?, payment_id    = ?, taxincluded   = ?,
961                 type        = ?, language_id   = ?, taxzone_id    = ?, shipto_id     = ?,
962                 employee_id = ?, salesman_id   = ?, storno_id     = ?, storno        = ?,
963                 cp_id       = ?, marge_total   = ?, marge_percent = ?, 
964                 globalproject_id               = ?, delivery_customer_id             = ?,
965                 transaction_description        = ?, delivery_vendor_id               = ?
966               WHERE id = ?|;
967   @values = (          $form->{"invnumber"},           $form->{"ordnumber"},             $form->{"quonumber"},          $form->{"cusordnumber"},
968              conv_date($form->{"invdate"}),  conv_date($form->{"orddate"}),    conv_date($form->{"quodate"}),    conv_i($form->{"customer_id"}), 
969                        $amount,                        $netamount,                       $form->{"paid"},     conv_date($form->{"datepaid"}), 
970              conv_date($form->{"duedate"}),  conv_date($form->{"deliverydate"}),    '1',                                $form->{"shippingpoint"},
971                        $form->{"shipvia"},      conv_i($form->{"terms"}),                $form->{"notes"},              $form->{"intnotes"},
972                        $form->{"currency"},     conv_i($form->{"department_id"}), conv_i($form->{"payment_id"}),        $form->{"taxincluded"} ? 't' : 'f',
973                        $form->{"type"},         conv_i($form->{"language_id"}),   conv_i($form->{"taxzone_id"}), conv_i($form->{"shipto_id"}),
974                 conv_i($form->{"employee_id"}), conv_i($form->{"salesman_id"}),   conv_i($form->{storno_id}),           $form->{"storno"} ? 't' : 'f', 
975                 conv_i($form->{"cp_id"}),            1 * $form->{marge_total} ,      1 * $form->{marge_percent},
976                 conv_i($form->{"globalproject_id"}),                              conv_i($form->{"delivery_customer_id"}), 
977                        $form->{transaction_description},                          conv_i($form->{"delivery_vendor_id"}),
978                 conv_i($form->{"id"}));
979   do_query($form, $dbh, $query, @values);
980   
981   if($form->{"formname"} eq "credit_note") {
982     for my $i (1 .. $form->{rowcount}) {
983       $query = qq|UPDATE parts SET onhand = onhand - ? WHERE id = ?|;
984       @values = (conv_i($form->{"qty_$i"}), conv_i($form->{"id_$i"}));
985       do_query($form, $dbh, $query, @values);
986     }
987   }
988   
989   if ($form->{storno}) {
990     $query =
991       qq!UPDATE ar SET
992            paid = paid + amount,
993            storno = 't',
994            intnotes = ? || intnotes
995          WHERE id = ?!;
996     do_query($form, $dbh, $query, "Rechnung storniert am $form->{invdate} ", conv_i($form->{"storno_id"}));
997     do_query($form, $dbh, qq|UPDATE ar SET paid = amount WHERE id = ?|, conv_i($form->{"id"}));
998   }
999
1000   # add shipto
1001   $form->{name} = $form->{customer};
1002   $form->{name} =~ s/--\Q$form->{customer_id}\E//;
1003
1004   if (!$form->{shipto_id}) {
1005     $form->add_shipto($dbh, $form->{id}, "AR");
1006   }
1007
1008   # save printed, emailed and queued
1009   $form->save_status($dbh);
1010
1011   Common::webdav_folder($form) if ($main::webdav);
1012
1013   # Link this record to the records it was created from.
1014   RecordLinks->create_links('dbh'        => $dbh,
1015                             'mode'       => 'ids',
1016                             'from_table' => 'oe',
1017                             'from_ids'   => $form->{convert_from_oe_ids},
1018                             'to_table'   => 'ar',
1019                             'to_id'      => $form->{id},
1020     );
1021   delete $form->{convert_from_oe_ids};
1022
1023   my @convert_from_do_ids = map { $_ * 1 } grep { $_ } split m/\s+/, $form->{convert_from_do_ids};
1024
1025   if (scalar @convert_from_do_ids) {
1026     DO->close_orders('dbh' => $dbh,
1027                      'ids' => \@convert_from_do_ids);
1028
1029     RecordLinks->create_links('dbh'        => $dbh,
1030                               'mode'       => 'ids',
1031                               'from_table' => 'delivery_orders',
1032                               'from_ids'   => \@convert_from_do_ids,
1033                               'to_table'   => 'ar',
1034                               'to_id'      => $form->{id},
1035       );
1036   }
1037   delete $form->{convert_from_do_ids};
1038
1039   ARAP->close_orders_if_billed('dbh'     => $dbh,
1040                                'arap_id' => $form->{id},
1041                                'table'   => 'ar',);
1042
1043   my $rc = 1;
1044   if (!$provided_dbh) {
1045     $dbh->commit();
1046     $dbh->disconnect();
1047   }
1048
1049   $main::lxdebug->leave_sub();
1050
1051   return $rc;
1052 }
1053
1054 sub _delete_payments {
1055   $main::lxdebug->enter_sub();
1056
1057   my ($self, $form, $dbh) = @_;
1058
1059   my @delete_oids;
1060
1061   # Delete old payment entries from acc_trans.
1062   my $query =
1063     qq|SELECT oid
1064        FROM acc_trans
1065        WHERE (trans_id = ?) AND fx_transaction
1066
1067        UNION
1068
1069        SELECT at.oid
1070        FROM acc_trans at
1071        LEFT JOIN chart c ON (at.chart_id = c.id)
1072        WHERE (trans_id = ?) AND (c.link LIKE '%AR_paid%')|;
1073   push @delete_oids, selectall_array_query($form, $dbh, $query, conv_i($form->{id}), conv_i($form->{id}));
1074
1075   $query =
1076     qq|SELECT at.oid
1077        FROM acc_trans at
1078        LEFT JOIN chart c ON (at.chart_id = c.id)
1079        WHERE (trans_id = ?)
1080          AND ((c.link = 'AR') OR (c.link LIKE '%:AR') OR (c.link LIKE 'AR:%'))
1081        ORDER BY at.oid
1082        OFFSET 1|;
1083   push @delete_oids, selectall_array_query($form, $dbh, $query, conv_i($form->{id}));
1084
1085   if (@delete_oids) {
1086     $query = qq|DELETE FROM acc_trans WHERE oid IN (| . join(", ", @delete_oids) . qq|)|;
1087     do_query($form, $dbh, $query);
1088   }
1089
1090   $main::lxdebug->leave_sub();
1091 }
1092
1093 sub post_payment {
1094   $main::lxdebug->enter_sub();
1095
1096   my ($self, $myconfig, $form, $locale) = @_;
1097
1098   # connect to database, turn off autocommit
1099   my $dbh = $form->dbconnect_noauto($myconfig);
1100
1101   my (%payments, $old_form, $row, $item, $query, %keep_vars);
1102
1103   $old_form = save_form();
1104
1105   # Delete all entries in acc_trans from prior payments.
1106   $self->_delete_payments($form, $dbh);
1107
1108   # Save the new payments the user made before cleaning up $form.
1109   map { $payments{$_} = $form->{$_} } grep m/^datepaid_\d+$|^memo_\d+$|^source_\d+$|^exchangerate_\d+$|^paid_\d+$|^AR_paid_\d+$|^paidaccounts$/, keys %{ $form };
1110
1111   # Clean up $form so that old content won't tamper the results.
1112   %keep_vars = map { $_, 1 } qw(login password id);
1113   map { delete $form->{$_} unless $keep_vars{$_} } keys %{ $form };
1114
1115   # Retrieve the invoice from the database.
1116   $self->retrieve_invoice($myconfig, $form);
1117
1118   # Set up the content of $form in the way that IS::post_invoice() expects.
1119   $form->{exchangerate} = $form->format_amount($myconfig, $form->{exchangerate});
1120
1121   for $row (1 .. scalar @{ $form->{invoice_details} }) {
1122     $item = $form->{invoice_details}->[$row - 1];
1123
1124     map { $item->{$_} = $form->format_amount($myconfig, $item->{$_}) } qw(qty sellprice discount);
1125
1126     map { $form->{"${_}_${row}"} = $item->{$_} } keys %{ $item };
1127   }
1128
1129   $form->{rowcount} = scalar @{ $form->{invoice_details} };
1130
1131   delete @{$form}{qw(invoice_details paidaccounts storno paid)};
1132
1133   # Restore the payment options from the user input.
1134   map { $form->{$_} = $payments{$_} } keys %payments;
1135
1136   # Get the AR accno (which is normally done by Form::create_links()).
1137   $query =
1138     qq|SELECT c.accno
1139        FROM acc_trans at
1140        LEFT JOIN chart c ON (at.chart_id = c.id)
1141        WHERE (trans_id = ?)
1142          AND ((c.link = 'AR') OR (c.link LIKE '%:AR') OR (c.link LIKE 'AR:%'))
1143        ORDER BY at.oid
1144        LIMIT 1|;
1145
1146   ($form->{AR}) = selectfirst_array_query($form, $dbh, $query, conv_i($form->{id}));
1147
1148   # Post the new payments.
1149   $self->post_invoice($myconfig, $form, $dbh, 1);
1150
1151   restore_form($old_form);
1152
1153   my $rc = $dbh->commit();
1154   $dbh->disconnect();
1155
1156   $main::lxdebug->leave_sub();
1157
1158   return $rc;
1159 }
1160
1161 sub process_assembly {
1162   $main::lxdebug->enter_sub();
1163
1164   my ($dbh, $form, $id, $totalqty) = @_;
1165
1166   my $query =
1167     qq|SELECT a.parts_id, a.qty, p.assembly, p.partnumber, p.description, p.unit,
1168          p.inventory_accno_id, p.income_accno_id, p.expense_accno_id
1169        FROM assembly a
1170        JOIN parts p ON (a.parts_id = p.id)
1171        WHERE (a.id = ?)|;
1172   my $sth = prepare_execute_query($form, $dbh, $query, conv_i($id));
1173
1174   while (my $ref = $sth->fetchrow_hashref('NAME_lc')) {
1175
1176     my $allocated = 0;
1177
1178     $ref->{inventory_accno_id} *= 1;
1179     $ref->{expense_accno_id}   *= 1;
1180
1181     # multiply by number of assemblies
1182     $ref->{qty} *= $totalqty;
1183
1184     if ($ref->{assembly}) {
1185       &process_assembly($dbh, $form, $ref->{parts_id}, $ref->{qty});
1186       next;
1187     } else {
1188       if ($ref->{inventory_accno_id}) {
1189         $allocated = &cogs($dbh, $form, $ref->{parts_id}, $ref->{qty});
1190       }
1191     }
1192
1193     # save detail record for individual assembly item in invoice table
1194     $query =
1195       qq|INSERT INTO invoice (trans_id, description, parts_id, qty, sellprice, fxsellprice, allocated, assemblyitem, unit)
1196          VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)|;
1197     my @values = (conv_i($form->{id}), $ref->{description}, conv_i($ref->{parts_id}), $ref->{qty}, 0, 0, $allocated, 't', $ref->{unit});
1198     do_query($form, $dbh, $query, @values);
1199
1200   }
1201
1202   $sth->finish;
1203
1204   $main::lxdebug->leave_sub();
1205 }
1206
1207 sub cogs {
1208   $main::lxdebug->enter_sub();
1209
1210   my ($dbh, $form, $id, $totalqty, $basefactor, $row) = @_;
1211   $form->{taxzone_id} *=1;
1212   my $transdate  = $form->{invdate} ? $dbh->quote($form->{invdate}) : "current_date";
1213   my $taxzone_id = $form->{"taxzone_id"} * 1;
1214   my $query =
1215     qq|SELECT i.id, i.trans_id, i.base_qty, i.allocated, i.sellprice,
1216          c1.accno AS inventory_accno, c1.new_chart_id AS inventory_new_chart, date($transdate) - c1.valid_from AS inventory_valid,
1217          c2.accno AS    income_accno, c2.new_chart_id AS    income_new_chart, date($transdate) - c2.valid_from AS    income_valid,
1218          c3.accno AS   expense_accno, c3.new_chart_id AS   expense_new_chart, date($transdate) - c3.valid_from AS   expense_valid
1219        FROM invoice i, parts p
1220        LEFT JOIN chart c1 ON ((SELECT inventory_accno_id FROM buchungsgruppen WHERE id = p.buchungsgruppen_id) = c1.id)
1221        LEFT JOIN chart c2 ON ((SELECT income_accno_id_${taxzone_id} FROM buchungsgruppen WHERE id = p.buchungsgruppen_id) = c2.id)
1222        LEFT JOIN chart c3 ON ((select expense_accno_id_${taxzone_id} FROM buchungsgruppen WHERE id = p.buchungsgruppen_id) = c3.id)
1223        WHERE (i.parts_id = p.id)
1224          AND (i.parts_id = ?)
1225          AND ((i.base_qty + i.allocated) < 0)
1226        ORDER BY trans_id|;
1227   my $sth = prepare_execute_query($form, $dbh, $query, conv_i($id));
1228
1229   my $allocated = 0;
1230   my $qty;
1231
1232   while (my $ref = $sth->fetchrow_hashref('NAME_lc')) {
1233     if (($qty = (($ref->{base_qty} * -1) - $ref->{allocated})) > $totalqty) {
1234       $qty = $totalqty;
1235     }
1236
1237     $form->update_balance($dbh, "invoice", "allocated", qq|id = $ref->{id}|, $qty);
1238
1239     # total expenses and inventory
1240     # sellprice is the cost of the item
1241     my $linetotal = $form->round_amount(($ref->{sellprice} * $qty) / $basefactor, 2);
1242
1243     if (!$main::eur) {
1244       $ref->{expense_accno} = ($form->{"expense_accno_$row"}) ? $form->{"expense_accno_$row"} : $ref->{expense_accno};
1245       # add to expense
1246       $form->{amount_cogs}{ $form->{id} }{ $ref->{expense_accno} } += -$linetotal;
1247       $form->{expense_inventory} .= " " . $ref->{expense_accno};
1248       $ref->{inventory_accno} = ($form->{"inventory_accno_$row"}) ? $form->{"inventory_accno_$row"} : $ref->{inventory_accno};
1249       # deduct inventory
1250       $form->{amount_cogs}{ $form->{id} }{ $ref->{inventory_accno} } -= -$linetotal;
1251       $form->{expense_inventory} .= " " . $ref->{inventory_accno};
1252     }
1253
1254     # add allocated
1255     $allocated -= $qty;
1256
1257     last if (($totalqty -= $qty) <= 0);
1258   }
1259
1260   $sth->finish;
1261
1262   $main::lxdebug->leave_sub();
1263
1264   return $allocated;
1265 }
1266
1267 sub reverse_invoice {
1268   $main::lxdebug->enter_sub();
1269
1270   my ($dbh, $form) = @_;
1271
1272   # reverse inventory items
1273   my $query =
1274     qq|SELECT i.id, i.parts_id, i.qty, i.assemblyitem, p.assembly, p.inventory_accno_id
1275        FROM invoice i
1276        JOIN parts p ON (i.parts_id = p.id)
1277        WHERE i.trans_id = ?|;
1278   my $sth = prepare_execute_query($form, $dbh, $query, conv_i($form->{"id"}));
1279
1280   while (my $ref = $sth->fetchrow_hashref('NAME_lc')) {
1281
1282     if ($ref->{inventory_accno_id}) {
1283       # de-allocated purchases
1284       $query =
1285         qq|SELECT i.id, i.trans_id, i.allocated
1286            FROM invoice i
1287            WHERE (i.parts_id = ?) AND (i.allocated > 0)
1288            ORDER BY i.trans_id DESC|;
1289       my $sth2 = prepare_execute_query($form, $dbh, $query, conv_i($ref->{"parts_id"}));
1290
1291       while (my $inhref = $sth2->fetchrow_hashref('NAME_lc')) {
1292         my $qty = $ref->{qty};
1293         if (($ref->{qty} - $inhref->{allocated}) > 0) {
1294           $qty = $inhref->{allocated};
1295         }
1296
1297         # update invoice
1298         $form->update_balance($dbh, "invoice", "allocated", qq|id = $inhref->{id}|, $qty * -1);
1299
1300         last if (($ref->{qty} -= $qty) <= 0);
1301       }
1302       $sth2->finish;
1303     }
1304   }
1305
1306   $sth->finish;
1307
1308   # delete acc_trans
1309   my @values = (conv_i($form->{id}));
1310   do_query($form, $dbh, qq|DELETE FROM acc_trans WHERE trans_id = ?|, @values);
1311   do_query($form, $dbh, qq|DELETE FROM invoice WHERE trans_id = ?|, @values);
1312
1313   if ($form->{lizenzen}) {
1314     $query =
1315       qq|DELETE FROM licenseinvoice
1316          WHERE trans_id in (SELECT id FROM invoice WHERE trans_id = ?)|;
1317     do_query($form, $dbh, $query, @values);
1318   }
1319
1320   do_query($form, $dbh, qq|DELETE FROM shipto WHERE (trans_id = ?) AND (module = 'AR')|, @values);
1321
1322   $main::lxdebug->leave_sub();
1323 }
1324
1325 sub delete_invoice {
1326   $main::lxdebug->enter_sub();
1327
1328   my ($self, $myconfig, $form, $spool) = @_;
1329
1330   # connect to database
1331   my $dbh = $form->dbconnect_noauto($myconfig);
1332
1333   &reverse_invoice($dbh, $form);
1334
1335   my @values = (conv_i($form->{id}));
1336
1337   # delete AR record
1338   do_query($form, $dbh, qq|DELETE FROM ar WHERE id = ?|, @values);
1339
1340   # delete spool files
1341   my @spoolfiles = selectall_array_query($form, $dbh, qq|SELECT spoolfile FROM status WHERE trans_id = ?|, @values);
1342
1343   # delete status entries
1344   do_query($form, $dbh, qq|DELETE FROM status WHERE trans_id = ?|, @values);
1345
1346   my $rc = $dbh->commit;
1347   $dbh->disconnect;
1348
1349   if ($rc) {
1350     map { unlink "$spool/$_" if -f "$spool/$_"; } @spoolfiles;
1351   }
1352
1353   $main::lxdebug->leave_sub();
1354
1355   return $rc;
1356 }
1357
1358 sub retrieve_invoice {
1359   $main::lxdebug->enter_sub();
1360
1361   my ($self, $myconfig, $form) = @_;
1362
1363   # connect to database
1364   my $dbh = $form->dbconnect_noauto($myconfig);
1365
1366   my ($sth, $ref, $query);
1367
1368   my $query_transdate = ", current_date AS invdate" if !$form->{id};
1369
1370   $query =
1371     qq|SELECT
1372          (SELECT c.accno FROM chart c WHERE d.inventory_accno_id = c.id) AS inventory_accno,
1373          (SELECT c.accno FROM chart c WHERE d.income_accno_id = c.id)    AS income_accno,
1374          (SELECT c.accno FROM chart c WHERE d.expense_accno_id = c.id)   AS expense_accno,
1375          (SELECT c.accno FROM chart c WHERE d.fxgain_accno_id = c.id)    AS fxgain_accno,
1376          (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id = c.id)    AS fxloss_accno,
1377          d.curr AS currencies
1378          ${query_transdate}
1379        FROM defaults d|;
1380
1381   $ref = selectfirst_hashref_query($form, $dbh, $query);
1382   map { $form->{$_} = $ref->{$_} } keys %{ $ref };
1383
1384   if ($form->{id}) {
1385     my $id = conv_i($form->{id});
1386
1387     # retrieve invoice
1388     $query =
1389       qq|SELECT
1390            a.invnumber, a.ordnumber, a.quonumber, a.cusordnumber,
1391            a.orddate, a.quodate, a.globalproject_id,
1392            a.transdate AS invdate, a.deliverydate, a.paid, a.storno, a.gldate,
1393            a.shippingpoint, a.shipvia, a.terms, a.notes, a.intnotes, a.taxzone_id,
1394            a.duedate, a.taxincluded, a.curr AS currency, a.shipto_id, a.cp_id,
1395            a.employee_id, a.salesman_id, a.payment_id,
1396            a.language_id, a.delivery_customer_id, a.delivery_vendor_id, a.type,
1397            a.transaction_description,
1398            a.marge_total, a.marge_percent,
1399            e.name AS employee
1400          FROM ar a
1401          LEFT JOIN employee e ON (e.id = a.employee_id)
1402          WHERE a.id = ?|;
1403     $ref = selectfirst_hashref_query($form, $dbh, $query, $id);
1404     map { $form->{$_} = $ref->{$_} } keys %{ $ref };
1405
1406
1407     $form->{exchangerate} = $form->get_exchangerate($dbh, $form->{currency}, $form->{invdate}, "buy");
1408
1409     # get shipto
1410     $query = qq|SELECT * FROM shipto WHERE (trans_id = ?) AND (module = 'AR')|;
1411     $ref = selectfirst_hashref_query($form, $dbh, $query, $id);
1412     delete $ref->{id};
1413     map { $form->{$_} = $ref->{$_} } keys %{ $ref };
1414
1415     foreach my $vc (qw(customer vendor)) {
1416       next if !$form->{"delivery_${vc}_id"};
1417       ($form->{"delivery_${vc}_string"}) = selectrow_query($form, $dbh, qq|SELECT name FROM customer WHERE id = ?|, $id);
1418     }
1419
1420     # get printed, emailed
1421     $query = qq|SELECT printed, emailed, spoolfile, formname FROM status WHERE trans_id = ?|;
1422     $sth = prepare_execute_query($form, $dbh, $query, $id);
1423
1424     while ($ref = $sth->fetchrow_hashref('NAME_lc')) {
1425       $form->{printed} .= "$ref->{formname} " if $ref->{printed};
1426       $form->{emailed} .= "$ref->{formname} " if $ref->{emailed};
1427       $form->{queued} .= "$ref->{formname} $ref->{spoolfile} " if $ref->{spoolfile};
1428     }
1429     $sth->finish;
1430     map { $form->{$_} =~ s/ +$//g } qw(printed emailed queued);
1431
1432     my $transdate = $form->{deliverydate} ? $dbh->quote($form->{deliverydate})
1433                   : $form->{invdate}      ? $dbh->quote($form->{invdate})
1434                   :                         "current_date";
1435      
1436
1437     my $taxzone_id = $form->{taxzone_id} *= 1;
1438     $taxzone_id = 0 if (0 > $taxzone_id) || (3 < $taxzone_id);
1439
1440     # retrieve individual items
1441     $query =
1442       qq|SELECT
1443            c1.accno AS inventory_accno, c1.new_chart_id AS inventory_new_chart, date($transdate) - c1.valid_from AS inventory_valid,
1444            c2.accno AS income_accno,    c2.new_chart_id AS income_new_chart,    date($transdate) - c2.valid_from as income_valid,
1445            c3.accno AS expense_accno,   c3.new_chart_id AS expense_new_chart,   date($transdate) - c3.valid_from AS expense_valid,
1446
1447            i.description, i.longdescription, i.qty, i.fxsellprice AS sellprice, i.discount, i.parts_id AS id, i.unit, i.deliverydate AS reqdate,
1448            i.project_id, i.serialnumber, i.id AS invoice_pos, i.pricegroup_id, i.ordnumber, i.transdate, i.cusordnumber, i.subtotal, i.lastcost,
1449            i.price_factor_id, i.price_factor, i.marge_price_factor,
1450            p.partnumber, p.assembly, p.bin, p.notes AS partnotes, p.inventory_accno_id AS part_inventory_accno_id, p.formel,
1451            pr.projectnumber, pg.partsgroup, prg.pricegroup
1452
1453          FROM invoice i
1454          LEFT JOIN parts p ON (i.parts_id = p.id)
1455          LEFT JOIN project pr ON (i.project_id = pr.id)
1456          LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id)
1457          LEFT JOIN pricegroup prg ON (i.pricegroup_id = prg.id)
1458
1459          LEFT JOIN chart c1 ON ((SELECT inventory_accno_id             FROM buchungsgruppen WHERE id = p.buchungsgruppen_id) = c1.id)
1460          LEFT JOIN chart c2 ON ((SELECT income_accno_id_${taxzone_id}  FROM buchungsgruppen WHERE id = p.buchungsgruppen_id) = c2.id)
1461          LEFT JOIN chart c3 ON ((SELECT expense_accno_id_${taxzone_id} FROM buchungsgruppen WHERE id = p.buchungsgruppen_id) = c3.id)
1462
1463          WHERE (i.trans_id = ?) AND NOT (i.assemblyitem = '1') ORDER BY i.id|;
1464
1465     $sth = prepare_execute_query($form, $dbh, $query, $id);
1466
1467     while (my $ref = $sth->fetchrow_hashref('NAME_lc')) {
1468       map({ delete($ref->{$_}); } qw(inventory_accno inventory_new_chart inventory_valid)) if !$ref->{"part_inventory_accno_id"};
1469       delete($ref->{"part_inventory_accno_id"});
1470
1471       foreach my $type (qw(inventory income expense)) {
1472         while ($ref->{"${type}_new_chart"} && ($ref->{"${type}_valid"} >=0)) {
1473           my $query = qq|SELECT accno, new_chart_id, date($transdate) - valid_from FROM chart WHERE id = ?|;
1474           @$ref{ map $type.$_, qw(_accno _new_chart _valid) } = selectrow_query($form, $dbh, $query, $ref->{"${type}_new_chart"});
1475         }
1476       }
1477
1478       # get tax rates and description
1479       my $accno_id = ($form->{vc} eq "customer") ? $ref->{income_accno} : $ref->{expense_accno};
1480       $query =
1481         qq|SELECT c.accno, t.taxdescription, t.rate, t.taxnumber FROM tax t
1482            LEFT JOIN chart c ON (c.id = t.chart_id)
1483            WHERE t.id IN
1484              (SELECT tk.tax_id FROM taxkeys tk
1485               WHERE tk.chart_id = (SELECT id FROM chart WHERE accno = ?) 
1486                 AND startdate <= date($transdate)
1487               ORDER BY startdate DESC LIMIT 1)
1488            ORDER BY c.accno|;
1489       my $stw = prepare_execute_query($form, $dbh, $query, $accno_id);
1490       $ref->{taxaccounts} = "";
1491       my $i=0;
1492       while (my $ptr = $stw->fetchrow_hashref('NAME_lc')) {
1493
1494         if (($ptr->{accno} eq "") && ($ptr->{rate} == 0)) {
1495           $i++;
1496           $ptr->{accno} = $i;
1497         }
1498         $ref->{taxaccounts} .= "$ptr->{accno} ";
1499
1500         if (!($form->{taxaccounts} =~ /\Q$ptr->{accno}\E/)) {
1501           $form->{"$ptr->{accno}_rate"}        = $ptr->{rate};
1502           $form->{"$ptr->{accno}_description"} = $ptr->{taxdescription};
1503           $form->{"$ptr->{accno}_taxnumber"}   = $ptr->{taxnumber};
1504           $form->{taxaccounts} .= "$ptr->{accno} ";
1505         }
1506
1507       }
1508
1509       if ($form->{lizenzen}) {
1510         $query = qq|SELECT l.licensenumber, l.id AS licenseid FROM license l, licenseinvoice li WHERE l.id = li.license_id AND li.trans_id = ?|;
1511         my ($licensenumber, $licenseid) = selectrow_query($form, $dbh, $query, conv_i($ref->{invoice_pos}));
1512         $ref->{lizenzen} = "<option value=\"$licenseid\">$licensenumber</option>";
1513       }
1514
1515       $ref->{qty} *= -1 if $form->{type} eq "credit_note";
1516
1517       chop $ref->{taxaccounts};
1518       push @{ $form->{invoice_details} }, $ref;
1519       $stw->finish;
1520     }
1521     $sth->finish;
1522
1523     Common::webdav_folder($form) if ($main::webdav);
1524   }
1525
1526   my $rc = $dbh->commit;
1527   $dbh->disconnect;
1528
1529   $main::lxdebug->leave_sub();
1530
1531   return $rc;
1532 }
1533
1534 sub get_customer {
1535   $main::lxdebug->enter_sub();
1536
1537   my ($self, $myconfig, $form) = @_;
1538
1539   # connect to database
1540   my $dbh = $form->dbconnect($myconfig);
1541
1542   my $dateformat = $myconfig->{dateformat};
1543   $dateformat .= "yy" if $myconfig->{dateformat} !~ /^y/;
1544
1545   my (@values, $duedate, $ref, $query);
1546
1547   if ($form->{invdate}) {
1548     $duedate = "to_date(?, '$dateformat')";
1549     push @values, $form->{invdate};
1550   } else {
1551     $duedate = "current_date";
1552   }
1553
1554   my $cid = conv_i($form->{customer_id});
1555   my $payment_id;
1556
1557   if ($form->{payment_id}) {
1558     $payment_id = "(pt.id = ?) OR";
1559     push @values, conv_i($form->{payment_id});
1560   }
1561
1562   # get customer
1563   $query =
1564     qq|SELECT
1565          c.id AS customer_id, c.name AS customer, c.discount, c.creditlimit, c.terms,
1566          c.email, c.cc, c.bcc, c.language_id, c.payment_id,
1567          c.street, c.zipcode, c.city, c.country,
1568          c.notes AS intnotes, c.klass as customer_klass, c.taxzone_id, c.salesman_id,
1569          $duedate + COALESCE(pt.terms_netto, 0) AS duedate,
1570          b.discount AS tradediscount, b.description AS business
1571        FROM customer c
1572        LEFT JOIN business b ON (b.id = c.business_id)
1573        LEFT JOIN payment_terms pt ON ($payment_id (c.payment_id = pt.id))
1574        WHERE c.id = ?|;
1575   push @values, $cid;
1576   $ref = selectfirst_hashref_query($form, $dbh, $query, @values);
1577   map { $form->{$_} = $ref->{$_} } keys %$ref;
1578
1579   $query =
1580     qq|SELECT sum(amount - paid) AS dunning_amount
1581        FROM ar
1582        WHERE (paid < amount)
1583          AND (customer_id = ?)
1584          AND (dunning_config_id IS NOT NULL)|;
1585   $ref = selectfirst_hashref_query($form, $dbh, $query, $cid);
1586   map { $form->{$_} = $ref->{$_} } keys %$ref;
1587
1588   $query =
1589     qq|SELECT dnn.dunning_description AS max_dunning_level
1590        FROM dunning_config dnn
1591        WHERE id IN (SELECT dunning_config_id
1592                     FROM ar
1593                     WHERE (paid < amount) AND (customer_id = ?) AND (dunning_config_id IS NOT NULL))
1594        ORDER BY dunning_level DESC LIMIT 1|;
1595   $ref = selectfirst_hashref_query($form, $dbh, $query, $cid);
1596   map { $form->{$_} = $ref->{$_} } keys %$ref;
1597
1598   $form->{creditremaining} = $form->{creditlimit};
1599   $query = qq|SELECT SUM(amount - paid) FROM ar WHERE customer_id = ?|;
1600   my ($value) = selectrow_query($form, $dbh, $query, $cid);
1601   $form->{creditremaining} -= $value;
1602
1603   $query =
1604     qq|SELECT o.amount,
1605          (SELECT e.buy FROM exchangerate e
1606           WHERE e.curr = o.curr
1607             AND e.transdate = o.transdate)
1608        FROM oe o
1609        WHERE o.customer_id = ?
1610          AND o.quotation = '0'
1611          AND o.closed = '0'|;
1612   my $sth = prepare_execute_query($form, $dbh, $query, $cid);
1613
1614   while (my ($amount, $exch) = $sth->fetchrow_array) {
1615     $exch = 1 unless $exch;
1616     $form->{creditremaining} -= $amount * $exch;
1617   }
1618   $sth->finish;
1619
1620   # get shipto if we did not converted an order or invoice
1621   if (!$form->{shipto}) {
1622     map { delete $form->{$_} }
1623       qw(shiptoname shiptodepartment_1 shiptodepartment_2
1624          shiptostreet shiptozipcode shiptocity shiptocountry
1625          shiptocontact shiptophone shiptofax shiptoemail);
1626
1627     $query = qq|SELECT * FROM shipto WHERE trans_id = ? AND module = 'CT'|;
1628     $ref = selectfirst_hashref_query($form, $dbh, $query, $cid);
1629     delete $ref->{id};
1630     map { $form->{$_} = $ref->{$_} } keys %$ref;
1631   }
1632
1633   # setup last accounts used for this customer
1634   if (!$form->{id} && $form->{type} !~ /_(order|quotation)/) {
1635     $query =
1636       qq|SELECT c.id, c.accno, c.description, c.link, c.category
1637          FROM chart c
1638          JOIN acc_trans ac ON (ac.chart_id = c.id)
1639          JOIN ar a ON (a.id = ac.trans_id)
1640          WHERE a.customer_id = ?
1641            AND NOT (c.link LIKE '%_tax%' OR c.link LIKE '%_paid%')
1642            AND a.id IN (SELECT max(a2.id) FROM ar a2 WHERE a2.customer_id = ?)|;
1643     $sth = prepare_execute_query($form, $dbh, $query, $cid, $cid);
1644
1645     my $i = 0;
1646     while ($ref = $sth->fetchrow_hashref('NAME_lc')) {
1647       if ($ref->{category} eq 'I') {
1648         $i++;
1649         $form->{"AR_amount_$i"} = "$ref->{accno}--$ref->{description}";
1650
1651         if ($form->{initial_transdate}) {
1652           my $tax_query =
1653             qq|SELECT tk.tax_id, t.rate
1654                FROM taxkeys tk
1655                LEFT JOIN tax t ON tk.tax_id = t.id
1656                WHERE (tk.chart_id = ?) AND (startdate <= date(?))
1657                ORDER BY tk.startdate DESC
1658                LIMIT 1|;
1659           my ($tax_id, $rate) =
1660             selectrow_query($form, $dbh, $tax_query, $ref->{id},
1661                             $form->{initial_transdate});
1662           $form->{"taxchart_$i"} = "${tax_id}--${rate}";
1663         }
1664       }
1665       if ($ref->{category} eq 'A') {
1666         $form->{ARselected} = $form->{AR_1} = $ref->{accno};
1667       }
1668     }
1669     $sth->finish;
1670     $form->{rowcount} = $i if ($i && !$form->{type});
1671   }
1672
1673   $dbh->disconnect;
1674
1675   $main::lxdebug->leave_sub();
1676 }
1677
1678 sub retrieve_item {
1679   $main::lxdebug->enter_sub();
1680
1681   my ($self, $myconfig, $form) = @_;
1682
1683   # connect to database
1684   my $dbh = $form->dbconnect($myconfig);
1685
1686   my $i = $form->{rowcount};
1687
1688   my $where = qq|NOT p.obsolete = '1'|;
1689   my @values;
1690
1691   foreach my $column (qw(p.partnumber p.description pgpartsgroup)) {
1692     my ($table, $field) = split m/\./, $column;
1693     next if !$form->{"${field}_${i}"};
1694     $where .= qq| AND lower(${column}) ILIKE ?|;
1695     push @values, '%' . $form->{"${field}_${i}"} . '%';
1696   }
1697
1698   if ($form->{"description_$i"}) {
1699     $where .= qq| ORDER BY p.description|;
1700   } else {
1701     $where .= qq| ORDER BY p.partnumber|;
1702   }
1703
1704   my $transdate;
1705   if ($form->{type} eq "invoice") {
1706     $transdate =
1707       $form->{deliverydate} ? $dbh->quote($form->{deliverydate}) :
1708       $form->{invdate}      ? $dbh->quote($form->{invdate}) :
1709                               "current_date";
1710   } else {
1711     $transdate =
1712       $form->{transdate}    ? $dbh->quote($form->{transdate}) :
1713                               "current_date";
1714   }
1715
1716   my $taxzone_id = $form->{taxzone_id} * 1;
1717   $taxzone_id = 0 if (0 > $taxzone_id) || (3 < $taxzone_id);
1718
1719   my $query =
1720     qq|SELECT
1721          p.id, p.partnumber, p.description, p.sellprice,
1722          p.listprice, p.inventory_accno_id, p.lastcost,
1723
1724          c1.accno AS inventory_accno,
1725          c1.new_chart_id AS inventory_new_chart,
1726          date($transdate) - c1.valid_from AS inventory_valid,
1727
1728          c2.accno AS income_accno,
1729          c2.new_chart_id AS income_new_chart,
1730          date($transdate)  - c2.valid_from AS income_valid,
1731
1732          c3.accno AS expense_accno,
1733          c3.new_chart_id AS expense_new_chart,
1734          date($transdate) - c3.valid_from AS expense_valid,
1735
1736          p.unit, p.assembly, p.bin, p.onhand,
1737          p.notes AS partnotes, p.notes AS longdescription,
1738          p.not_discountable, p.formel, p.payment_id AS part_payment_id,
1739          p.price_factor_id,
1740
1741          pfac.factor AS price_factor,
1742
1743          pg.partsgroup
1744
1745        FROM parts p
1746        LEFT JOIN chart c1 ON
1747          ((SELECT inventory_accno_id
1748            FROM buchungsgruppen
1749            WHERE id = p.buchungsgruppen_id) = c1.id)
1750        LEFT JOIN chart c2 ON
1751          ((SELECT income_accno_id_${taxzone_id}
1752            FROM buchungsgruppen
1753            WHERE id = p.buchungsgruppen_id) = c2.id)
1754        LEFT JOIN chart c3 ON
1755          ((SELECT expense_accno_id_${taxzone_id}
1756            FROM buchungsgruppen
1757            WHERE id = p.buchungsgruppen_id) = c3.id)
1758        LEFT JOIN partsgroup pg ON (pg.id = p.partsgroup_id)
1759        LEFT JOIN price_factors pfac ON (pfac.id = p.price_factor_id)
1760        WHERE $where|;
1761   my $sth = prepare_execute_query($form, $dbh, $query, @values);
1762
1763   while (my $ref = $sth->fetchrow_hashref('NAME_lc')) {
1764
1765     # In der Buchungsgruppe ist immer ein Bestandskonto verknuepft, auch wenn
1766     # es sich um eine Dienstleistung handelt. Bei Dienstleistungen muss das
1767     # Buchungskonto also aus dem Ergebnis rausgenommen werden.
1768     if (!$ref->{inventory_accno_id}) {
1769       map({ delete($ref->{"inventory_${_}"}); } qw(accno new_chart valid));
1770     }
1771     delete($ref->{inventory_accno_id});
1772
1773     foreach my $type (qw(inventory income expense)) {
1774       while ($ref->{"${type}_new_chart"} && ($ref->{"${type}_valid"} >=0)) {
1775         my $query =
1776           qq|SELECT accno, new_chart_id, date($transdate) - valid_from
1777              FROM chart
1778              WHERE id = ?|;
1779         ($ref->{"${type}_accno"},
1780          $ref->{"${type}_new_chart"},
1781          $ref->{"${type}_valid"})
1782           = selectrow_query($form, $dbh, $query, $ref->{"${type}_new_chart"});
1783       }
1784     }
1785
1786     if ($form->{payment_id} eq "") {
1787       $form->{payment_id} = $form->{part_payment_id};
1788     }
1789
1790     # get tax rates and description
1791     my $accno_id = ($form->{vc} eq "customer") ? $ref->{income_accno} : $ref->{expense_accno};
1792     $query =
1793       qq|SELECT c.accno, t.taxdescription, t.rate, t.taxnumber
1794          FROM tax t
1795          LEFT JOIN chart c ON (c.id = t.chart_id)
1796          WHERE t.id in
1797            (SELECT tk.tax_id
1798             FROM taxkeys tk
1799             WHERE tk.chart_id = (SELECT id from chart WHERE accno = ?)
1800               AND startdate <= ?
1801             ORDER BY startdate DESC
1802             LIMIT 1)
1803          ORDER BY c.accno|;
1804     @values = ($accno_id, $transdate eq "current_date" ? "now" : $transdate);
1805     my $stw = $dbh->prepare($query);
1806     $stw->execute(@values) || $form->dberror($query);
1807
1808     $ref->{taxaccounts} = "";
1809     my $i = 0;
1810     while (my $ptr = $stw->fetchrow_hashref('NAME_lc')) {
1811
1812       #    if ($customertax{$ref->{accno}}) 
1813       if (($ptr->{accno} eq "") && ($ptr->{rate} == 0)) {
1814         $i++;
1815         $ptr->{accno} = $i;
1816       }
1817       $ref->{taxaccounts} .= "$ptr->{accno} ";
1818
1819       if (!($form->{taxaccounts} =~ /\Q$ptr->{accno}\E/)) {
1820         $form->{"$ptr->{accno}_rate"}        = $ptr->{rate};
1821         $form->{"$ptr->{accno}_description"} = $ptr->{taxdescription};
1822         $form->{"$ptr->{accno}_taxnumber"}   = $ptr->{taxnumber};
1823         $form->{taxaccounts} .= "$ptr->{accno} ";
1824       }
1825
1826     }
1827
1828     $stw->finish;
1829     chop $ref->{taxaccounts};
1830     if ($form->{language_id}) {
1831       $query =
1832         qq|SELECT tr.translation, tr.longdescription
1833            FROM translation tr
1834            WHERE tr.language_id = ? AND tr.parts_id = ?|;
1835       @values = (conv_i($form->{language_id}), conv_i($ref->{id}));
1836       my ($translation, $longdescription) = selectrow_query($form, $dbh, $query, @values);
1837       if ($translation ne "") {
1838         $ref->{description} = $translation;
1839         $ref->{longdescription} = $longdescription;
1840
1841       } else {
1842         $query =
1843           qq|SELECT tr.translation, tr.longdescription
1844              FROM translation tr
1845              WHERE tr.language_id IN
1846                (SELECT id
1847                 FROM language
1848                 WHERE article_code = (SELECT article_code FROM language WHERE id = ?))
1849                AND tr.parts_id = ?
1850              LIMIT 1|;
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       }
1858     }
1859
1860     $ref->{onhand} *= 1;
1861
1862     push @{ $form->{item_list} }, $ref;
1863
1864     if ($form->{lizenzen}) {
1865       if ($ref->{inventory_accno} > 0) {
1866         $query =
1867           qq|SELECT l.*
1868              FROM license l
1869              WHERE l.parts_id = ? AND NOT l.id IN (SELECT li.license_id FROM licenseinvoice li)|;
1870         my $stw = prepare_execute_query($form, $dbh, $query, conv_i($ref->{id}));
1871         while (my $ptr = $stw->fetchrow_hashref('NAME_lc')) {
1872           push @{ $form->{LIZENZEN}{ $ref->{id} } }, $ptr;
1873         }
1874         $stw->finish;
1875       }
1876     }
1877   }
1878   $sth->finish;
1879   $dbh->disconnect;
1880
1881   $main::lxdebug->leave_sub();
1882 }
1883
1884 ##########################
1885 # get pricegroups from database
1886 # build up selected pricegroup
1887 # if an exchange rate - change price
1888 # for each part
1889 #
1890 sub get_pricegroups_for_parts {
1891
1892   $main::lxdebug->enter_sub();
1893
1894   my ($self, $myconfig, $form) = @_;
1895
1896   my $dbh = $form->dbconnect($myconfig);
1897
1898   $form->{"PRICES"} = {};
1899
1900   my $i  = 1;
1901   my $id = 0;
1902   my $all_units = AM->retrieve_units($myconfig, $form);
1903   while (($form->{"id_$i"}) or ($form->{"new_id_$i"})) {
1904     $form->{"PRICES"}{$i} = [];
1905
1906     $id = $form->{"id_$i"};
1907
1908     if (!($form->{"id_$i"}) and $form->{"new_id_$i"}) {
1909
1910       $id = $form->{"new_id_$i"};
1911     }
1912
1913     my ($price, $selectedpricegroup_id) = split(/--/,
1914       $form->{"sellprice_pg_$i"});
1915
1916     my $pricegroup_old = $form->{"pricegroup_old_$i"};
1917     $form->{"new_pricegroup_$i"} = $selectedpricegroup_id;
1918     $form->{"old_pricegroup_$i"} = $pricegroup_old;
1919
1920     my $price_new = $form->{"price_new_$i"};
1921     my $price_old = $form->{"price_old_$i"};
1922
1923     if (!$form->{"unit_old_$i"}) {
1924       # Neue Ware aus der Datenbank. In diesem Fall ist unit_$i die
1925       # Einheit, wie sie in den Stammdaten hinterlegt wurde.
1926       # Es sollte also angenommen werden, dass diese ausgewaehlt war.
1927       $form->{"unit_old_$i"} = $form->{"unit_$i"};
1928     }
1929
1930     # Die zuletzt ausgewaehlte mit der aktuell ausgewaehlten Einheit
1931     # vergleichen und bei Unterschied den Preis entsprechend umrechnen.
1932     $form->{"selected_unit_$i"} = $form->{"unit_$i"} unless ($form->{"selected_unit_$i"});
1933
1934     if (!$all_units->{$form->{"selected_unit_$i"}} ||
1935         ($all_units->{$form->{"selected_unit_$i"}}->{"base_unit"} ne
1936          $all_units->{$form->{"unit_old_$i"}}->{"base_unit"})) {
1937       # Die ausgewaehlte Einheit ist fuer diesen Artikel nicht gueltig
1938       # (z.B. Dimensionseinheit war ausgewaehlt, es handelt sich aber
1939       # um eine Dienstleistung). Dann keinerlei Umrechnung vornehmen.
1940       $form->{"unit_old_$i"} = $form->{"selected_unit_$i"} = $form->{"unit_$i"};
1941     }
1942
1943     my $basefactor = 1;
1944
1945     if ($form->{"unit_old_$i"} ne $form->{"selected_unit_$i"}) {
1946       if (defined($all_units->{$form->{"unit_old_$i"}}->{"factor"}) &&
1947           $all_units->{$form->{"unit_old_$i"}}->{"factor"}) {
1948         $basefactor = $all_units->{$form->{"selected_unit_$i"}}->{"factor"} /
1949           $all_units->{$form->{"unit_old_$i"}}->{"factor"};
1950       }
1951     }
1952
1953     if (!$form->{"basefactor_$i"}) {
1954       $form->{"basefactor_$i"} = 1;
1955     }
1956
1957     my $query =
1958       qq|SELECT
1959            pricegroup_id,
1960            (SELECT p.sellprice FROM parts p WHERE p.id = ?) AS default_sellprice,
1961            (SELECT pg.pricegroup FROM pricegroup pg WHERE id = pricegroup_id) AS pricegroup,
1962            price,
1963            '' AS selected
1964           FROM prices
1965           WHERE parts_id = ?
1966
1967           UNION
1968
1969           SELECT
1970             0 as pricegroup_id,
1971             (SELECT sellprice FROM parts WHERE id = ?) AS default_sellprice,
1972             '' AS pricegroup,
1973             (SELECT DISTINCT sellprice FROM parts where id = ?) AS price,
1974             'selected' AS selected
1975           FROM prices
1976
1977           ORDER BY pricegroup|;
1978     my @values = (conv_i($id), conv_i($id), conv_i($id), conv_i($id));
1979     my $pkq = prepare_execute_query($form, $dbh, $query, @values);
1980
1981     while (my $pkr = $pkq->fetchrow_hashref('NAME_lc')) {
1982       $pkr->{id}       = $id;
1983       $pkr->{selected} = '';
1984
1985       # if there is an exchange rate change price
1986       if (($form->{exchangerate} * 1) != 0) {
1987
1988         $pkr->{price} /= $form->{exchangerate};
1989       }
1990
1991       $pkr->{price} *= $form->{"basefactor_$i"};
1992
1993       $pkr->{price} *= $basefactor;
1994
1995       $pkr->{price} = $form->format_amount($myconfig, $pkr->{price}, 5);
1996
1997       if ($selectedpricegroup_id eq undef) {
1998         if ($pkr->{pricegroup_id} eq $form->{customer_klass}) {
1999
2000           $pkr->{selected}  = ' selected';
2001
2002           # no customer pricesgroup set
2003           if ($pkr->{price} == $pkr->{default_sellprice}) {
2004
2005             $pkr->{price} = $form->{"sellprice_$i"};
2006
2007           } else {
2008
2009             $form->{"sellprice_$i"} = $pkr->{price};
2010           }
2011
2012         } elsif ($pkr->{price} == $pkr->{default_sellprice}) {
2013           $pkr->{price}    = $form->{"sellprice_$i"};
2014           $pkr->{selected} = ' selected';
2015         }
2016       }
2017
2018       if ($selectedpricegroup_id or $selectedpricegroup_id == 0) {
2019         if ($selectedpricegroup_id ne $pricegroup_old) {
2020           if ($pkr->{pricegroup_id} eq $selectedpricegroup_id) {
2021             $pkr->{selected}  = ' selected';
2022           }
2023         } elsif (($price_new != $form->{"sellprice_$i"}) and ($price_new ne 0)) {
2024           if ($pkr->{pricegroup_id} == 0) {
2025             $pkr->{price}     = $form->{"sellprice_$i"};
2026             $pkr->{selected}  = ' selected';
2027           }
2028         } elsif ($pkr->{pricegroup_id} eq $selectedpricegroup_id) {
2029           $pkr->{selected}  = ' selected';
2030           if (    ($pkr->{pricegroup_id} == 0)
2031               and ($pkr->{price} == $form->{"sellprice_$i"})) {
2032             # $pkr->{price}                         = $form->{"sellprice_$i"};
2033           } else {
2034             $pkr->{price} = $form->{"sellprice_$i"};
2035           }
2036         }
2037       }
2038       push @{ $form->{PRICES}{$i} }, $pkr;
2039
2040     }
2041     $form->{"basefactor_$i"} *= $basefactor;
2042
2043     $i++;
2044
2045     $pkq->finish;
2046   }
2047
2048   $dbh->disconnect;
2049
2050   $main::lxdebug->leave_sub();
2051 }
2052
2053 sub has_storno {
2054   $main::lxdebug->enter_sub();
2055
2056   my ($self, $myconfig, $form, $table) = @_;
2057
2058   $main::lxdebug->leave_sub() and return 0 unless ($form->{id});
2059
2060   # make sure there's no funny stuff in $table
2061   # ToDO: die when this happens and throw an error
2062   $main::lxdebug->leave_sub() and return 0 if ($table =~ /\W/);
2063
2064   my $dbh = $form->dbconnect($myconfig);
2065
2066   my $query = qq|SELECT storno FROM $table WHERE storno_id = ?|;
2067   my ($result) = selectrow_query($form, $dbh, $query, $form->{id});
2068
2069   $dbh->disconnect();
2070
2071   $main::lxdebug->leave_sub();
2072
2073   return $result;
2074 }
2075
2076 sub is_storno {
2077   $main::lxdebug->enter_sub();
2078
2079   my ($self, $myconfig, $form, $table, $id) = @_;
2080
2081   $main::lxdebug->leave_sub() and return 0 unless ($id);
2082
2083   # make sure there's no funny stuff in $table
2084   # ToDO: die when this happens and throw an error
2085   $main::lxdebug->leave_sub() and return 0 if ($table =~ /\W/);
2086
2087   my $dbh = $form->dbconnect($myconfig);
2088
2089   my $query = qq|SELECT storno FROM $table WHERE id = ?|;
2090   my ($result) = selectrow_query($form, $dbh, $query, $id);
2091
2092   $dbh->disconnect();
2093
2094   $main::lxdebug->leave_sub();
2095
2096   return $result;
2097 }
2098
2099 1;