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