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