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