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