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