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