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