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