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