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