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