Kosmetik
[kivitendo-erp.git] / SL / OE.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) 1999-2003
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 # Order entry module
32 # Quotation
33 #======================================================================
34
35 package OE;
36
37 use List::Util qw(max first);
38 use YAML;
39
40 use SL::AM;
41 use SL::Common;
42 use SL::CVar;
43 use SL::DB::Order;
44 use SL::DB::PeriodicInvoicesConfig;
45 use SL::DB::Project;
46 use SL::DB::ProjectType;
47 use SL::DB::Status;
48 use SL::DB::Tax;
49 use SL::DBUtils;
50 use SL::HTML::Restrict;
51 use SL::IC;
52 use SL::TransNumber;
53
54 use strict;
55
56 sub transactions {
57   $main::lxdebug->enter_sub();
58
59   my ($self, $myconfig, $form) = @_;
60
61   # connect to database
62   my $dbh = $form->get_standard_dbh;
63
64   my $query;
65   my $ordnumber = 'ordnumber';
66   my $quotation = '0';
67
68   my @values;
69   my $where;
70
71   my ($periodic_invoices_columns, $periodic_invoices_joins);
72
73   my $rate = ($form->{vc} eq 'customer') ? 'buy' : 'sell';
74
75   if ($form->{type} =~ /_quotation$/) {
76     $quotation = '1';
77     $ordnumber = 'quonumber';
78
79   } elsif ($form->{type} eq 'sales_order') {
80     $periodic_invoices_columns = qq| , COALESCE(pcfg.active, 'f') AS periodic_invoices |;
81     $periodic_invoices_joins   = qq| LEFT JOIN periodic_invoices_configs pcfg ON (o.id = pcfg.oe_id) |;
82   }
83
84   my $vc = $form->{vc} eq "customer" ? "customer" : "vendor";
85
86   my %billed_amount;
87   my %billed_netamount;
88   if ($form->{l_remaining_amount} || $form->{l_remaining_netamount}) {
89     $query = <<'';
90       SELECT from_id, ar.amount, ar.netamount FROM (
91         SELECT from_id, to_id
92         FROM record_links
93         WHERE from_table = 'oe' AND to_table = 'ar'
94         UNION
95         SELECT rl1.from_id, rl2.to_id
96         FROM record_links rl1
97         LEFT JOIN record_links rl2 ON (rl1.to_table = rl2.from_table AND rl1.to_id = rl2.from_id)
98         WHERE rl1.from_table = 'oe' AND rl2.to_table = 'ar'
99       ) rl
100       LEFT JOIN ar ON ar.id = rl.to_id
101
102     for my $ref (@{ selectall_hashref_query($form, $dbh, $query) }) {
103       $billed_amount{   $ref->{from_id}} += $ref->{amount};
104       $billed_netamount{$ref->{from_id}} += $ref->{netamount};
105     }
106   }
107
108   $query =
109     qq|SELECT o.id, o.ordnumber, o.transdate, o.reqdate, | .
110     qq|  o.amount, ct.${vc}number, ct.name, o.netamount, o.${vc}_id, o.globalproject_id, | .
111     qq|  o.closed, o.delivered, o.quonumber, o.cusordnumber, o.shippingpoint, o.shipvia, | .
112     qq|  o.transaction_description, | .
113     qq|  o.marge_total, o.marge_percent, | .
114     qq|  o.itime::DATE AS insertdate, | .
115     qq|  ex.$rate AS exchangerate, | .
116     qq|  pt.description AS payment_terms, | .
117     qq|  pr.projectnumber AS globalprojectnumber, | .
118     qq|  e.name AS employee, s.name AS salesman, | .
119     qq|  ct.${vc}number AS vcnumber, ct.country, ct.ustid, ct.business_id,  | .
120     qq|  tz.description AS taxzone | .
121     $periodic_invoices_columns .
122     qq|  , o.order_probability, o.expected_billing_date, (o.netamount * o.order_probability / 100) AS expected_netamount | .
123     qq|FROM oe o | .
124     qq|JOIN $vc ct ON (o.${vc}_id = ct.id) | .
125     qq|LEFT JOIN contacts cp ON (o.cp_id = cp.cp_id) | .
126     qq|LEFT JOIN employee e ON (o.employee_id = e.id) | .
127     qq|LEFT JOIN employee s ON (o.salesman_id = s.id) | .
128     qq|LEFT JOIN exchangerate ex ON (ex.currency_id = o.currency_id | .
129     qq|  AND ex.transdate = o.transdate) | .
130     qq|LEFT JOIN project pr ON (o.globalproject_id = pr.id) | .
131     qq|LEFT JOIN payment_terms pt ON (pt.id = o.payment_id)| .
132     qq|LEFT JOIN tax_zones tz ON (o.taxzone_id = tz.id) | .
133     qq|$periodic_invoices_joins | .
134     qq|WHERE (o.quotation = ?) |;
135   push(@values, $quotation);
136
137   my ($null, $split_department_id) = split /--/, $form->{department};
138   my $department_id = $form->{department_id} || $split_department_id;
139   if ($department_id) {
140     $query .= qq| AND o.department_id = ?|;
141     push(@values, $department_id);
142   }
143
144   if ($form->{"project_id"}) {
145     $query .=
146       qq|AND ((globalproject_id = ?) OR EXISTS | .
147       qq|  (SELECT * FROM orderitems oi | .
148       qq|   WHERE oi.project_id = ? AND oi.trans_id = o.id))|;
149     push(@values, conv_i($form->{"project_id"}), conv_i($form->{"project_id"}));
150   }
151
152   if ($form->{"projectnumber"}) {
153     $query .= <<SQL;
154       AND ((pr.projectnumber ILIKE ?) OR EXISTS (
155         SELECT * FROM orderitems oi
156         LEFT JOIN project proi ON proi.id = oi.project_id
157         WHERE proi.projectnumber ILIKE ? AND oi.trans_id = o.id
158       ))
159 SQL
160     push @values, "%" . $form->{"projectnumber"} . "%", "%" . $form->{"projectnumber"} . "%" ;
161   }
162
163   if ($form->{"business_id"}) {
164     $query .= " AND ct.business_id = ?";
165     push(@values, $form->{"business_id"});
166   }
167
168   if ($form->{"${vc}_id"}) {
169     $query .= " AND o.${vc}_id = ?";
170     push(@values, $form->{"${vc}_id"});
171
172   } elsif ($form->{$vc}) {
173     $query .= " AND ct.name ILIKE ?";
174     push(@values, '%' . $form->{$vc} . '%');
175   }
176
177   if ($form->{"cp_name"}) {
178     $query .= " AND (cp.cp_name ILIKE ? OR cp.cp_givenname ILIKE ?)";
179     push(@values, ('%' . $form->{"cp_name"} . '%')x2);
180   }
181
182   if (!$main::auth->assert('sales_all_edit', 1)) {
183     $query .= " AND o.employee_id = (select id from employee where login= ?)";
184     push @values, $::myconfig{login};
185   }
186   if ($form->{employee_id}) {
187     $query .= " AND o.employee_id = ?";
188     push @values, conv_i($form->{employee_id});
189   }
190
191   if ($form->{salesman_id}) {
192     $query .= " AND o.salesman_id = ?";
193     push @values, conv_i($form->{salesman_id});
194   }
195
196   if (!$form->{open} && !$form->{closed}) {
197     $query .= " AND o.id = 0";
198   } elsif (!($form->{open} && $form->{closed})) {
199     $query .= ($form->{open}) ? " AND o.closed = '0'" : " AND o.closed = '1'";
200   }
201
202   if (($form->{"notdelivered"} || $form->{"delivered"}) &&
203       ($form->{"notdelivered"} ne $form->{"delivered"})) {
204     $query .= $form->{"delivered"} ?
205       " AND o.delivered " : " AND NOT o.delivered";
206   }
207
208   if ($form->{$ordnumber}) {
209     $query .= qq| AND o.$ordnumber ILIKE ?|;
210     push(@values, '%' . $form->{$ordnumber} . '%');
211   }
212
213   if ($form->{cusordnumber}) {
214     $query .= qq| AND o.cusordnumber ILIKE ?|;
215     push(@values, '%' . $form->{cusordnumber} . '%');
216   }
217
218   if($form->{transdatefrom}) {
219     $query .= qq| AND o.transdate >= ?|;
220     push(@values, conv_date($form->{transdatefrom}));
221   }
222
223   if($form->{transdateto}) {
224     $query .= qq| AND o.transdate <= ?|;
225     push(@values, conv_date($form->{transdateto}));
226   }
227
228   if($form->{reqdatefrom}) {
229     $query .= qq| AND o.reqdate >= ?|;
230     push(@values, conv_date($form->{reqdatefrom}));
231   }
232
233   if($form->{reqdateto}) {
234     $query .= qq| AND o.reqdate <= ?|;
235     push(@values, conv_date($form->{reqdateto}));
236   }
237
238   if($form->{insertdatefrom}) {
239     $query .= qq| AND o.itime::DATE >= ?|;
240     push(@values, conv_date($form->{insertdatefrom}));
241   }
242
243   if($form->{insertdateto}) {
244     $query .= qq| AND o.itime::DATE <= ?|;
245     push(@values, conv_date($form->{insertdateto}));
246   }
247
248   if ($form->{shippingpoint}) {
249     $query .= qq| AND o.shippingpoint ILIKE ?|;
250     push(@values, '%' . $form->{shippingpoint} . '%');
251   }
252
253   if ($form->{taxzone_id} ne '') { # taxzone_id could be 0
254     $query .= qq| AND tz.id = ?|;
255     push(@values, $form->{taxzone_id});
256   }
257
258   if ($form->{transaction_description}) {
259     $query .= qq| AND o.transaction_description ILIKE ?|;
260     push(@values, '%' . $form->{transaction_description} . '%');
261   }
262
263   if ($form->{periodic_invoices_active} ne $form->{periodic_invoices_inactive}) {
264     my $not  = $form->{periodic_invoices_inactive} ? 'NOT' : '';
265     $query  .= qq| AND ${not} COALESCE(pcfg.active, 'f')|;
266   }
267
268   if ($form->{reqdate_unset_or_old}) {
269     $query .= qq| AND ((o.reqdate IS NULL) OR (o.reqdate < date_trunc('month', current_date)))|;
270   }
271
272   if (($form->{order_probability_value} || '') ne '') {
273     my $op  = $form->{order_probability_value} eq 'le' ? '<=' : '>=';
274     $query .= qq| AND (o.order_probability ${op} ?)|;
275     push @values, $form->{order_probability_value};
276   }
277
278   if ($form->{expected_billing_date_from}) {
279     $query .= qq| AND (o.expected_billing_date >= ?)|;
280     push @values, conv_date($form->{expected_billing_date_from});
281   }
282
283   if ($form->{expected_billing_date_to}) {
284     $query .= qq| AND (o.expected_billing_date <= ?)|;
285     push @values, conv_date($form->{expected_billing_date_to});
286   }
287
288   my ($cvar_where, @cvar_values) = CVar->build_filter_query('module'         => 'CT',
289                                                             'trans_id_field' => 'ct.id',
290                                                             'filter'         => $form,
291                                                            );
292   if ($cvar_where) {
293     $query .= qq| AND ($cvar_where)|;
294     push @values, @cvar_values;
295   }
296
297   my $sortdir   = !defined $form->{sortdir} ? 'ASC' : $form->{sortdir} ? 'ASC' : 'DESC';
298   my $sortorder = join(', ', map { "${_} ${sortdir} " } ("o.id", $form->sort_columns("transdate", $ordnumber, "name"), "o.itime"));
299   my %allowed_sort_columns = (
300     "transdate"               => "o.transdate",
301     "reqdate"                 => "o.reqdate",
302     "id"                      => "o.id",
303     "ordnumber"               => "o.ordnumber",
304     "cusordnumber"            => "o.cusordnumber",
305     "quonumber"               => "o.quonumber",
306     "name"                    => "ct.name",
307     "employee"                => "e.name",
308     "salesman"                => "s.name",
309     "shipvia"                 => "o.shipvia",
310     "transaction_description" => "o.transaction_description",
311     "shippingpoint"           => "o.shippingpoint",
312     "insertdate"              => "o.itime",
313     "taxzone"                 => "tz.description",
314     "payment_terms"           => "pt.description",
315   );
316   if ($form->{sort} && grep($form->{sort}, keys(%allowed_sort_columns))) {
317     $sortorder = $allowed_sort_columns{$form->{sort}} . " ${sortdir}"  . ", o.itime ${sortdir}";
318   }
319   $query .= qq| ORDER by | . $sortorder;
320
321   my $sth = $dbh->prepare($query);
322   $sth->execute(@values) ||
323     $form->dberror($query . " (" . join(", ", @values) . ")");
324
325   my %id = ();
326   $form->{OE} = [];
327   while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
328     $ref->{billed_amount}    = $billed_amount{$ref->{id}};
329     $ref->{billed_netamount} = $billed_netamount{$ref->{id}};
330     $ref->{remaining_amount} = $ref->{amount} - $ref->{billed_amount};
331     $ref->{remaining_netamount} = $ref->{netamount} - $ref->{billed_netamount};
332     $ref->{exchangerate} = 1 unless $ref->{exchangerate};
333     push @{ $form->{OE} }, $ref if $ref->{id} != $id{ $ref->{id} };
334     $id{ $ref->{id} } = $ref->{id};
335   }
336
337   $sth->finish;
338
339   $main::lxdebug->leave_sub();
340 }
341
342 sub transactions_for_todo_list {
343   $main::lxdebug->enter_sub();
344
345   my $self     = shift;
346   my %params   = @_;
347
348   my $myconfig = \%main::myconfig;
349   my $form     = $main::form;
350
351   my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
352
353   my $query    = qq|SELECT id FROM employee WHERE login = ?|;
354   my ($e_id)   = selectrow_query($form, $dbh, $query, $::myconfig{login});
355
356   $query       =
357     qq|SELECT oe.id, oe.transdate, oe.reqdate, oe.quonumber, oe.transaction_description, oe.amount,
358          CASE WHEN (COALESCE(oe.customer_id, 0) = 0) THEN 'vendor' ELSE 'customer' END AS vc,
359          c.name AS customer,
360          v.name AS vendor,
361          e.name AS employee
362        FROM oe
363        LEFT JOIN customer c ON (oe.customer_id = c.id)
364        LEFT JOIN vendor v   ON (oe.vendor_id   = v.id)
365        LEFT JOIN employee e ON (oe.employee_id = e.id)
366        WHERE (COALESCE(quotation, FALSE) = TRUE)
367          AND (COALESCE(closed,    FALSE) = FALSE)
368          AND ((oe.employee_id = ?) OR (oe.salesman_id = ?))
369          AND NOT (oe.reqdate ISNULL)
370          AND (oe.reqdate < current_date)
371        ORDER BY transdate|;
372
373   my $quotations = selectall_hashref_query($form, $dbh, $query, $e_id, $e_id);
374
375   $main::lxdebug->leave_sub();
376
377   return $quotations;
378 }
379
380 sub save {
381   $main::lxdebug->enter_sub();
382
383   my ($self, $myconfig, $form) = @_;
384
385   # connect to database, turn off autocommit
386   my $dbh = $form->get_standard_dbh;
387   my $restricter = SL::HTML::Restrict->create;
388
389   my ($query, @values, $sth, $null);
390   my $exchangerate = 0;
391
392   my $all_units = AM->retrieve_units($myconfig, $form);
393   $form->{all_units} = $all_units;
394
395   my $ic_cvar_configs = CVar->get_configs(module => 'IC',
396                                           dbh    => $dbh);
397
398   $form->{employee_id} = (split /--/, $form->{employee})[1] if !$form->{employee_id};
399   unless ($form->{employee_id}) {
400     $form->get_employee($dbh);
401   }
402
403   my $ml = ($form->{type} eq 'sales_order') ? 1 : -1;
404
405   my $number_field         = $form->{type} =~ m{order} ? 'ordnumber' : 'quonumber';
406   my $trans_number         = SL::TransNumber->new(type => $form->{type}, dbh => $dbh, number => $form->{$number_field}, id => $form->{id});
407   $form->{$number_field} ||= $trans_number->create_unique; # set $form->{ordnumber} or $form->{quonumber}
408
409   if ($form->{id}) {
410     $query = qq|DELETE FROM shipto | .
411              qq|WHERE trans_id = ? AND module = 'OE'|;
412     do_query($form, $dbh, $query, $form->{id});
413
414   } else {
415
416     $query = qq|SELECT nextval('id')|;
417     ($form->{id}) = selectrow_query($form, $dbh, $query);
418
419     $query = qq|INSERT INTO oe (id, ordnumber, employee_id, currency_id, taxzone_id) VALUES (?, '', ?, (SELECT currency_id FROM defaults), ?)|;
420     do_query($form, $dbh, $query, $form->{id}, $form->{employee_id}, $form->{taxzone_id});
421   }
422
423   my $amount    = 0;
424   my $linetotal = 0;
425   my $discount  = 0;
426   my $project_id;
427   my $reqdate;
428   my $taxrate;
429   my $taxbase;
430   my $taxdiff;
431   my $taxamount = 0;
432   my $fxsellprice;
433   my %taxbase;
434   my @taxaccounts;
435   my %taxaccounts;
436   my $netamount = 0;
437   my @processed_orderitems;
438
439   $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS');
440   my %price_factors = map { $_->{id} => $_->{factor} } @{ $form->{ALL_PRICE_FACTORS} };
441   my $price_factor;
442
443   for my $i (1 .. $form->{rowcount}) {
444
445     map({ $form->{"${_}_$i"} = $form->parse_amount($myconfig, $form->{"${_}_$i"}) } qw(qty ship));
446
447     if ($form->{"id_$i"}) {
448
449       # get item baseunit
450       $query = qq|SELECT unit FROM parts WHERE id = ?|;
451       my ($item_unit) = selectrow_query($form, $dbh, $query, $form->{"id_$i"});
452
453       my $basefactor = 1;
454       if (defined($all_units->{$item_unit}->{factor}) &&
455           (($all_units->{$item_unit}->{factor} * 1) != 0)) {
456         $basefactor = $all_units->{$form->{"unit_$i"}}->{factor} / $all_units->{$item_unit}->{factor};
457       }
458       my $baseqty = $form->{"qty_$i"} * $basefactor;
459
460       $form->{"marge_percent_$i"} = $form->parse_amount($myconfig, $form->{"marge_percent_$i"}) * 1;
461       $form->{"marge_absolut_$i"} = $form->parse_amount($myconfig, $form->{"marge_absolut_$i"}) * 1;
462
463       $form->{"lastcost_$i"} = $form->parse_amount($myconfig, $form->{"lastcost_$i"});
464
465       # keep entered selling price
466       my $fxsellprice =
467         $form->parse_amount($myconfig, $form->{"sellprice_$i"});
468
469       my ($dec) = ($fxsellprice =~ /\.(\d+)/);
470       $dec = length $dec;
471       my $decimalplaces = ($dec > 2) ? $dec : 2;
472
473       # undo discount formatting
474       $form->{"discount_$i"} = $form->parse_amount($myconfig, $form->{"discount_$i"}) / 100;
475
476       # deduct discount
477       $form->{"sellprice_$i"} = $fxsellprice * (1 - $form->{"discount_$i"});
478
479       # round linetotal at least to 2 decimal places
480       $price_factor = $price_factors{ $form->{"price_factor_id_$i"} } || 1;
481       $linetotal    = $form->round_amount($form->{"sellprice_$i"} * $form->{"qty_$i"} / $price_factor, 2);
482
483       $form->{"inventory_accno_$i"} *= 1;
484       $form->{"expense_accno_$i"}   *= 1;
485
486       @taxaccounts = split(/ /, $form->{"taxaccounts_$i"});
487       $taxrate     = 0;
488       $taxdiff     = 0;
489
490       map { $taxrate += $form->{"${_}_rate"} } @taxaccounts;
491
492       if ($form->{taxincluded}) {
493         $taxamount = $linetotal * $taxrate / (1 + $taxrate);
494         $taxbase   = $linetotal - $taxamount;
495
496         # we are not keeping a natural price, do not round
497         $form->{"sellprice_$i"} =
498           $form->{"sellprice_$i"} * (1 / (1 + $taxrate));
499       } else {
500         $taxamount = $linetotal * $taxrate;
501         $taxbase   = $linetotal;
502       }
503
504       if ($form->round_amount($taxrate, 7) == 0) {
505         if ($form->{taxincluded}) {
506           foreach my $item (@taxaccounts) {
507             $taxamount = $form->round_amount($linetotal * $form->{"${item}_rate"} / (1 + abs($form->{"${item}_rate"})), 2);
508             $taxaccounts{$item} += $taxamount;
509             $taxdiff            += $taxamount;
510             $taxbase{$item}     += $taxbase;
511           }
512           $taxaccounts{ $taxaccounts[0] } += $taxdiff;
513         } else {
514           foreach my $item (@taxaccounts) {
515             $taxaccounts{$item} += $linetotal * $form->{"${item}_rate"};
516             $taxbase{$item}     += $taxbase;
517           }
518         }
519       } else {
520         foreach my $item (@taxaccounts) {
521           $taxaccounts{$item} += $taxamount * $form->{"${item}_rate"} / $taxrate;
522           $taxbase{$item} += $taxbase;
523         }
524       }
525
526       $netamount += $form->{"sellprice_$i"} * $form->{"qty_$i"} / $price_factor;
527
528       $reqdate = ($form->{"reqdate_$i"}) ? $form->{"reqdate_$i"} : undef;
529
530       # Get pricegroup_id and save it. Unfortunately the interface
531       # also uses ID "0" for signalling that none is selected, but "0"
532       # must not be stored in the database. Therefore we cannot simply
533       # use conv_i().
534       ($null, my $pricegroup_id) = split(/--/, $form->{"sellprice_pg_$i"});
535       $pricegroup_id *= 1;
536       $pricegroup_id  = undef if !$pricegroup_id;
537
538       # force new project, if not set yet
539       if ($::instance_conf->get_order_always_project && !$form->{"globalproject_id"} && ($form->{type} eq 'sales_order')) {
540         require SL::DB::Customer;
541         my $customer = SL::DB::Manager::Customer->find_by(id => $form->{customer_id});
542         die "Can't find customer" unless $customer;
543         my $new_project = SL::DB::Project->new(
544           projectnumber     => $form->{ordnumber},
545           description       => $customer->name,
546           customer_id       => $customer->id,
547           active            => 1,
548           project_type_id   => $::instance_conf->get_project_type_id,
549           project_status_id => $::instance_conf->get_project_status_id,
550         );
551         $new_project->save;
552         $form->{"globalproject_id"} = $new_project->id;
553       };
554
555       CVar->get_non_editable_ic_cvars(form               => $form,
556                                       dbh                => $dbh,
557                                       row                => $i,
558                                       sub_module         => 'orderitems',
559                                       may_converted_from => ['orderitems', 'invoice']);
560
561       my $position = $i;
562
563       # save detail record in orderitems table
564       if (! $form->{"orderitems_id_$i"}) {
565         $query = qq|SELECT nextval('orderitemsid')|;
566         ($form->{"orderitems_id_$i"}) = selectrow_query($form, $dbh, $query);
567
568         $query = qq|INSERT INTO orderitems (id, position) VALUES (?, ?)|;
569         do_query($form, $dbh, $query, $form->{"orderitems_id_$i"}, conv_i($position));
570       }
571
572       my $orderitems_id = $form->{"orderitems_id_$i"};
573       push @processed_orderitems, $orderitems_id;
574
575        $query = <<SQL;
576          UPDATE orderitems SET
577           trans_id = ?, position = ?, parts_id = ?, description = ?, longdescription = ?, qty = ?, base_qty = ?,
578           sellprice = ?, discount = ?, unit = ?, reqdate = ?, project_id = ?, serialnumber = ?, ship = ?,
579           pricegroup_id = ?, subtotal = ?,
580           marge_percent = ?, marge_total = ?, lastcost = ?, price_factor_id = ?,
581           active_price_source = ?, active_discount_source = ?,
582           price_factor = (SELECT factor FROM price_factors WHERE id = ?), marge_price_factor = ?
583         WHERE id = ?
584 SQL
585       @values = (
586            conv_i($form->{id}), conv_i($position), conv_i($form->{"id_$i"}),
587            $form->{"description_$i"}, $restricter->process($form->{"longdescription_$i"}),
588            $form->{"qty_$i"}, $baseqty,
589            $fxsellprice, $form->{"discount_$i"},
590            $form->{"unit_$i"}, conv_date($reqdate), conv_i($form->{"project_id_$i"}),
591            $form->{"serialnumber_$i"}, $form->{"ship_$i"},
592            $pricegroup_id, $form->{"subtotal_$i"} ? 't' : 'f',
593            $form->{"marge_percent_$i"}, $form->{"marge_absolut_$i"},
594            $form->{"lastcost_$i"}, conv_i($form->{"price_factor_id_$i"}),
595            $form->{"active_price_source_$i"}, $form->{"active_discount_source_$i"},
596            conv_i($form->{"price_factor_id_$i"}), conv_i($form->{"marge_price_factor_$i"}),
597            conv_i($orderitems_id),
598       );
599
600       do_query($form, $dbh, $query, @values);
601
602       $form->{"sellprice_$i"} = $fxsellprice;
603       $form->{"discount_$i"} *= 100;
604
605       CVar->save_custom_variables(module       => 'IC',
606                                   sub_module   => 'orderitems',
607                                   trans_id     => $orderitems_id,
608                                   configs      => $ic_cvar_configs,
609                                   variables    => $form,
610                                   name_prefix  => 'ic_',
611                                   name_postfix => "_$i",
612                                   dbh          => $dbh);
613
614       # link previous items with orderitems
615       foreach (qw(orderitems invoice)) {
616         if (!$form->{saveasnew} && !$form->{useasnew} && $form->{"converted_from_${_}_id_$i"}) {
617           RecordLinks->create_links('dbh'        => $dbh,
618                                     'mode'       => 'ids',
619                                     'from_table' => $_,
620                                     'from_ids'   => $form->{"converted_from_${_}_id_$i"},
621                                     'to_table'   => 'orderitems',
622                                     'to_id'      => $orderitems_id,
623           );
624         }
625         delete $form->{"converted_from_${_}_id_$i"};
626       }
627     }
628   }
629
630   # search for orphaned ids
631   $query  = sprintf 'SELECT id FROM orderitems WHERE trans_id = ? AND NOT id IN (%s)', join ', ', ("?") x scalar @processed_orderitems;
632   @values = (conv_i($form->{id}), map { conv_i($_) } @processed_orderitems);
633   my @orphaned_ids = map { $_->{id} } selectall_hashref_query($form, $dbh, $query, @values);
634
635   if (scalar @orphaned_ids) {
636     # clean up orderitems
637     $query  = sprintf 'DELETE FROM orderitems WHERE id IN (%s)', join ', ', ("?") x scalar @orphaned_ids;
638     do_query($form, $dbh, $query, @orphaned_ids);
639   }
640
641   $reqdate = ($form->{reqdate}) ? $form->{reqdate} : undef;
642
643   # add up the tax
644   my $tax = 0;
645   map { $tax += $form->round_amount($taxaccounts{$_}, 2) } keys %taxaccounts;
646
647   $amount = $form->round_amount($netamount + $tax, 2);
648   $netamount = $form->round_amount($netamount, 2);
649
650   if ($form->{currency} eq $form->{defaultcurrency}) {
651     $form->{exchangerate} = 1;
652   } else {
653     $exchangerate = $form->check_exchangerate($myconfig, $form->{currency}, $form->{transdate}, ($form->{vc} eq 'customer') ? 'buy' : 'sell');
654   }
655
656   $form->{exchangerate} = $exchangerate || $form->parse_amount($myconfig, $form->{exchangerate});
657
658   my $quotation = $form->{type} =~ /_order$/ ? 'f' : 't';
659
660   ($null, $form->{department_id}) = split(/--/, $form->{department}) if $form->{department};
661
662   # save OE record
663   $query =
664     qq|UPDATE oe SET
665          ordnumber = ?, quonumber = ?, cusordnumber = ?, transdate = ?, vendor_id = ?,
666          customer_id = ?, amount = ?, netamount = ?, reqdate = ?, taxincluded = ?,
667          shippingpoint = ?, shipvia = ?, notes = ?, intnotes = ?, currency_id = (SELECT id FROM currencies WHERE name=?), closed = ?,
668          delivered = ?, proforma = ?, quotation = ?, department_id = ?, language_id = ?,
669          taxzone_id = ?, shipto_id = ?, payment_id = ?, delivery_vendor_id = ?, delivery_customer_id = ?,delivery_term_id = ?,
670          globalproject_id = ?, employee_id = ?, salesman_id = ?, cp_id = ?, transaction_description = ?, marge_total = ?, marge_percent = ?
671          , order_probability = ?, expected_billing_date = ?
672        WHERE id = ?|;
673
674   @values = ($form->{ordnumber} || '', $form->{quonumber},
675              $form->{cusordnumber}, conv_date($form->{transdate}),
676              conv_i($form->{vendor_id}), conv_i($form->{customer_id}),
677              $amount, $netamount, conv_date($reqdate),
678              $form->{taxincluded} ? 't' : 'f', $form->{shippingpoint},
679              $form->{shipvia}, $restricter->process($form->{notes}), $form->{intnotes},
680              $form->{currency}, $form->{closed} ? 't' : 'f',
681              $form->{delivered} ? "t" : "f", $form->{proforma} ? 't' : 'f',
682              $quotation, conv_i($form->{department_id}),
683              conv_i($form->{language_id}), conv_i($form->{taxzone_id}),
684              conv_i($form->{shipto_id}), conv_i($form->{payment_id}),
685              conv_i($form->{delivery_vendor_id}),
686              conv_i($form->{delivery_customer_id}),
687              conv_i($form->{delivery_term_id}),
688              conv_i($form->{globalproject_id}), conv_i($form->{employee_id}),
689              conv_i($form->{salesman_id}), conv_i($form->{cp_id}),
690              $form->{transaction_description},
691              $form->{marge_total} * 1, $form->{marge_percent} * 1,
692              $form->{order_probability} * 1, conv_date($form->{expected_billing_date}),
693              conv_i($form->{id}));
694   do_query($form, $dbh, $query, @values);
695
696   $form->{ordtotal} = $amount;
697
698   $form->{name} = $form->{ $form->{vc} };
699   $form->{name} =~ s/--\Q$form->{"$form->{vc}_id"}\E//;
700
701   # add shipto
702   if (!$form->{shipto_id}) {
703     $form->add_shipto($dbh, $form->{id}, "OE");
704   }
705
706   # save printed, emailed, queued
707   $form->save_status($dbh);
708
709   # Link this record to the records it was created from.
710   # check every record type we may link. i am not happy with converting the string to array back
711   # should be a array from the start (OE.pm -> retrieve).
712   #  and that i need the local array ref for close_quotation_rfqs. better ideas welcome
713   $form->{convert_from_oe_ids} =~ s/^\s+//;
714   $form->{convert_from_oe_ids} =~ s/\s+$//;
715   my @convert_from_oe_ids      =  split m/\s+/, $form->{convert_from_oe_ids};
716   delete $form->{convert_from_oe_ids};
717   @{ $form->{convert_from_oe_ids} }      =  @convert_from_oe_ids;
718   foreach (qw(ar oe)) {
719     if (!$form->{useasnew} && $form->{"convert_from_${_}_ids"}) {
720       RecordLinks->create_links('dbh'        => $dbh,
721                                 'mode'       => 'ids',
722                                 'from_table' => $_,
723                                 'from_ids'   => $form->{"convert_from_${_}_ids"},
724                                 'to_table'   => 'oe',
725                                 'to_id'      => $form->{id},
726         );
727       delete $form->{"convert_from_${_}_ids"};
728     }
729     $self->_close_quotations_rfqs('dbh'     => $dbh,
730                                   'from_id' => \@convert_from_oe_ids,
731                                   'to_id'   => $form->{id}) if $_ eq 'oe';
732   }
733
734   if (($form->{currency} ne $form->{defaultcurrency}) && !$exchangerate) {
735     if ($form->{vc} eq 'customer') {
736       $form->update_exchangerate($dbh, $form->{currency}, $form->{transdate}, $form->{exchangerate}, 0);
737     }
738     if ($form->{vc} eq 'vendor') {
739       $form->update_exchangerate($dbh, $form->{currency}, $form->{transdate}, 0, $form->{exchangerate});
740     }
741   }
742
743   $form->{saved_xyznumber} = $form->{$form->{type} =~ /_quotation$/ ?
744                                        "quonumber" : "ordnumber"};
745
746   Common::webdav_folder($form);
747
748   my $rc = $dbh->commit;
749
750   $self->save_periodic_invoices_config(dbh         => $dbh,
751                                        oe_id       => $form->{id},
752                                        config_yaml => $form->{periodic_invoices_config})
753     if ($form->{type} eq 'sales_order');
754
755   $main::lxdebug->leave_sub();
756
757   return $rc;
758 }
759
760 sub save_periodic_invoices_config {
761   my ($self, %params) = @_;
762
763   return if !$params{oe_id};
764
765   my $config = $params{config_yaml} ? YAML::Load($params{config_yaml}) : undef;
766   return if 'HASH' ne ref $config;
767
768   my $obj  = SL::DB::Manager::PeriodicInvoicesConfig->find_by(oe_id => $params{oe_id})
769           || SL::DB::PeriodicInvoicesConfig->new(oe_id => $params{oe_id});
770   $obj->update_attributes(%{ $config });
771 }
772
773 sub load_periodic_invoice_config {
774   my $self = shift;
775   my $form = shift;
776
777   delete $form->{periodic_invoices_config};
778
779   if ($form->{id}) {
780     my $config_obj = SL::DB::Manager::PeriodicInvoicesConfig->find_by(oe_id => $form->{id});
781
782     if ($config_obj) {
783       my $config = { map { $_ => $config_obj->$_ } qw(active terminated periodicity order_value_periodicity start_date_as_date end_date_as_date first_billing_date_as_date extend_automatically_by ar_chart_id
784                                                       print printer_id copies direct_debit) };
785       $form->{periodic_invoices_config} = YAML::Dump($config);
786     }
787   }
788 }
789
790 sub _close_quotations_rfqs {
791   $main::lxdebug->enter_sub();
792
793   my $self     = shift;
794   my %params   = @_;
795
796   Common::check_params(\%params, qw(from_id to_id));
797
798   my $myconfig = \%main::myconfig;
799   my $form     = $main::form;
800
801   my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
802
803   my $query    = qq|SELECT quotation FROM oe WHERE id = ?|;
804   my $sth      = prepare_query($form, $dbh, $query);
805
806   do_statement($form, $sth, $query, conv_i($params{to_id}));
807
808   my ($quotation) = $sth->fetchrow_array();
809
810   if ($quotation) {
811     $main::lxdebug->leave_sub();
812     return;
813   }
814
815   my @close_ids;
816
817   foreach my $from_id (@{ $params{from_id} }) {
818     $from_id = conv_i($from_id);
819     do_statement($form, $sth, $query, $from_id);
820     ($quotation) = $sth->fetchrow_array();
821     push @close_ids, $from_id if ($quotation);
822   }
823
824   $sth->finish();
825
826   if (scalar @close_ids) {
827     $query = qq|UPDATE oe SET closed = TRUE WHERE id IN (| . join(', ', ('?') x scalar @close_ids) . qq|)|;
828     do_query($form, $dbh, $query, @close_ids);
829
830     $dbh->commit() unless ($params{dbh});
831   }
832
833   $main::lxdebug->leave_sub();
834 }
835
836 sub delete {
837   $main::lxdebug->enter_sub();
838
839   my ($self, $myconfig, $form) = @_;
840
841   my $rc = SL::DB::Order->new->db->with_transaction(sub {
842     my @spoolfiles = grep { $_ } map { $_->spoolfile } @{ SL::DB::Manager::Status->get_all(where => [ trans_id => $form->{id} ]) };
843
844     SL::DB::Order->new(id => $form->{id})->delete;
845
846     my $spool = $::lx_office_conf{paths}->{spool};
847     unlink map { "$spool/$_" } @spoolfiles if $spool;
848
849     1;
850   });
851
852   $main::lxdebug->leave_sub();
853
854   return $rc;
855 }
856
857 sub retrieve {
858   $main::lxdebug->enter_sub();
859
860   my ($self, $myconfig, $form) = @_;
861
862   # connect to database
863   my $dbh = $form->get_standard_dbh;
864
865   my ($query, $query_add, @values, @ids, $sth);
866
867   # translate the ids (given by id_# and trans_id_#) into one array of ids, so we can join them later
868   map {
869     push @ids, $form->{"trans_id_$_"}
870       if ($form->{"multi_id_$_"} and $form->{"trans_id_$_"})
871   } (1 .. $form->{"rowcount"});
872
873   if ($form->{rowcount} && scalar @ids) {
874     $form->{convert_from_oe_ids} = join ' ', @ids;
875   }
876
877   # if called in multi id mode, and still only got one id, switch back to single id
878   if ($form->{"rowcount"} and $#ids == 0) {
879     $form->{"id"} = $ids[0];
880     undef @ids;
881     delete $form->{convert_from_oe_ids};
882   }
883
884   # and remember for the rest of the function
885   my $is_collective_order = scalar @ids;
886
887   # If collective order was created from exactly 1 order, we assume the same
888   # behaviour as a "save as new" from within an order is actually desired, i.e.
889   # the original order isn't part of a workflow where we want to remember
890   # record_links, but simply a quick way of generating a new order from an old
891   # one without having to enter everything again.
892   # Setting useasnew will prevent the creation of record_links for the items
893   # when saving the new order.
894   # This form variable is probably not necessary, could just set saveasnew instead
895   $form->{useasnew} = 1 if $is_collective_order == 1;
896
897   if (!$form->{id}) {
898     my $extra_days     = $form->{type} eq 'sales_quotation' ? $::instance_conf->get_reqdate_interval : 1;
899     $form->{reqdate}   = DateTime->today_local->next_workday(extra_days => $extra_days)->to_kivitendo;
900     $form->{transdate} = DateTime->today_local->to_kivitendo;
901   }
902
903   # get default accounts
904   $query = qq|SELECT (SELECT c.accno FROM chart c WHERE d.inventory_accno_id = c.id) AS inventory_accno,
905                      (SELECT c.accno FROM chart c WHERE d.income_accno_id    = c.id) AS income_accno,
906                      (SELECT c.accno FROM chart c WHERE d.expense_accno_id   = c.id) AS expense_accno,
907                      (SELECT c.accno FROM chart c WHERE d.fxgain_accno_id    = c.id) AS fxgain_accno,
908                      (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id    = c.id) AS fxloss_accno
909               $query_add
910               FROM defaults d|;
911   my $ref = selectfirst_hashref_query($form, $dbh, $query);
912   map { $form->{$_} = $ref->{$_} } keys %$ref;
913
914   $form->{currency} = $form->get_default_currency($myconfig);
915
916   # set reqdate if this is an invoice->order conversion. If someone knows a better check to ensure
917   # we come from invoices, feel free.
918   $form->{reqdate} = $form->{deliverydate}
919     if (    $form->{deliverydate}
920         and $form->{callback} =~ /action=ar_transactions/);
921
922   my $vc = $form->{vc} eq "customer" ? "customer" : "vendor";
923
924   if ($form->{id} or @ids) {
925
926     # retrieve order for single id
927     # NOTE: this query is intended to fetch all information only ONCE.
928     # so if any of these infos is important (or even different) for any item,
929     # it will be killed out and then has to be fetched from the item scope query further down
930     $query =
931       qq|SELECT o.cp_id, o.ordnumber, o.transdate, o.reqdate,
932            o.taxincluded, o.shippingpoint, o.shipvia, o.notes, o.intnotes,
933            (SELECT cu.name FROM currencies cu WHERE cu.id=o.currency_id) AS currency, e.name AS employee, o.employee_id, o.salesman_id,
934            o.${vc}_id, cv.name AS ${vc}, o.amount AS invtotal,
935            o.closed, o.reqdate, o.quonumber, o.department_id, o.cusordnumber,
936            o.mtime, o.itime,
937            d.description AS department, o.payment_id, o.language_id, o.taxzone_id,
938            o.delivery_customer_id, o.delivery_vendor_id, o.proforma, o.shipto_id,
939            o.globalproject_id, o.delivered, o.transaction_description, o.delivery_term_id,
940            o.itime::DATE AS insertdate, o.order_probability, o.expected_billing_date
941          FROM oe o
942          JOIN ${vc} cv ON (o.${vc}_id = cv.id)
943          LEFT JOIN employee e ON (o.employee_id = e.id)
944          LEFT JOIN department d ON (o.department_id = d.id) | .
945         ($form->{id}
946          ? "WHERE o.id = ?"
947          : "WHERE o.id IN (" . join(', ', map("? ", @ids)) . ")"
948         );
949     @values = $form->{id} ? ($form->{id}) : @ids;
950     $sth = prepare_execute_query($form, $dbh, $query, @values);
951
952     $ref = $sth->fetchrow_hashref("NAME_lc");
953
954     if ($ref) {
955       map { $form->{$_} = $ref->{$_} } keys %$ref;
956
957       $form->{saved_xyznumber} = $form->{$form->{type} =~ /_quotation$/ ? "quonumber" : "ordnumber"};
958
959       # set all entries for multiple ids blank that yield different information
960       while ($ref = $sth->fetchrow_hashref("NAME_lc")) {
961         map { $form->{$_} = '' if ($ref->{$_} ne $form->{$_}) } keys %$ref;
962       }
963     }
964     $form->{mtime}   ||= $form->{itime};
965     $form->{lastmtime} = $form->{mtime};
966
967     # if not given, fill transdate with current_date
968     $form->{transdate} = $form->current_date($myconfig)
969       unless $form->{transdate};
970
971     $sth->finish;
972
973     if ($form->{delivery_customer_id}) {
974       $query = qq|SELECT name FROM customer WHERE id = ?|;
975       ($form->{delivery_customer_string}) = selectrow_query($form, $dbh, $query, $form->{delivery_customer_id});
976     }
977
978     if ($form->{delivery_vendor_id}) {
979       $query = qq|SELECT name FROM customer WHERE id = ?|;
980       ($form->{delivery_vendor_string}) = selectrow_query($form, $dbh, $query, $form->{delivery_vendor_id});
981     }
982
983     # shipto and pinted/mailed/queued status makes only sense for single id retrieve
984     if (!@ids) {
985       $query = qq|SELECT s.* FROM shipto s WHERE s.trans_id = ? AND s.module = 'OE'|;
986       $sth = prepare_execute_query($form, $dbh, $query, $form->{id});
987
988       $ref = $sth->fetchrow_hashref("NAME_lc");
989       delete($ref->{id});
990       map { $form->{$_} = $ref->{$_} } keys %$ref;
991       $sth->finish;
992
993       # get printed, emailed and queued
994       $query = qq|SELECT s.printed, s.emailed, s.spoolfile, s.formname FROM status s WHERE s.trans_id = ?|;
995       $sth = prepare_execute_query($form, $dbh, $query, $form->{id});
996
997       while ($ref = $sth->fetchrow_hashref("NAME_lc")) {
998         $form->{printed} .= "$ref->{formname} " if $ref->{printed};
999         $form->{emailed} .= "$ref->{formname} " if $ref->{emailed};
1000         $form->{queued}  .= "$ref->{formname} $ref->{spoolfile} " if $ref->{spoolfile};
1001       }
1002       $sth->finish;
1003       map { $form->{$_} =~ s/ +$//g } qw(printed emailed queued);
1004     }    # if !@ids
1005
1006     my $transdate = $form->{transdate} ? $dbh->quote($form->{transdate}) : "current_date";
1007
1008     $form->{taxzone_id} = 0 unless ($form->{taxzone_id});
1009
1010     # retrieve individual items
1011     # this query looks up all information about the items
1012     # stuff different from the whole will not be overwritten, but saved with a suffix.
1013     $query =
1014       qq|SELECT o.id AS orderitems_id,
1015            c1.accno AS inventory_accno, c1.new_chart_id AS inventory_new_chart, date($transdate) - c1.valid_from as inventory_valid,
1016            c2.accno AS income_accno,    c2.new_chart_id AS income_new_chart,    date($transdate) - c2.valid_from as income_valid,
1017            c3.accno AS expense_accno,   c3.new_chart_id AS expense_new_chart,   date($transdate) - c3.valid_from as expense_valid,
1018            oe.ordnumber AS ordnumber_oe, oe.transdate AS transdate_oe, oe.cusordnumber AS cusordnumber_oe,
1019            p.partnumber, p.assembly, p.listprice, o.description, o.qty,
1020            o.sellprice, o.parts_id AS id, o.unit, o.discount, p.notes AS partnotes, p.inventory_accno_id AS part_inventory_accno_id,
1021            o.reqdate, o.project_id, o.serialnumber, o.ship, o.lastcost,
1022            o.ordnumber, o.transdate, o.cusordnumber, o.subtotal, o.longdescription,
1023            o.price_factor_id, o.price_factor, o.marge_price_factor, o.active_price_source, o.active_discount_source,
1024            pr.projectnumber, p.formel,
1025            pg.partsgroup, o.pricegroup_id, (SELECT pricegroup FROM pricegroup WHERE id=o.pricegroup_id) as pricegroup
1026          FROM orderitems o
1027          JOIN parts p ON (o.parts_id = p.id)
1028          JOIN oe ON (o.trans_id = oe.id)
1029          LEFT JOIN chart c1 ON ((SELECT inventory_accno_id                   FROM buchungsgruppen WHERE id=p.buchungsgruppen_id) = c1.id)
1030          LEFT JOIN chart c2 ON ((SELECT tc.income_accno_id FROM taxzone_charts tc WHERE tc.taxzone_id = '$form->{taxzone_id}' and tc.buchungsgruppen_id = p.buchungsgruppen_id) = c2.id)
1031          LEFT JOIN chart c3 ON ((SELECT tc.expense_accno_id FROM taxzone_charts tc WHERE tc.taxzone_id = '$form->{taxzone_id}' and tc.buchungsgruppen_id = p.buchungsgruppen_id) = c3.id)
1032          LEFT JOIN project pr ON (o.project_id = pr.id)
1033          LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id) | .
1034       ($form->{id}
1035        ? qq|WHERE o.trans_id = ?|
1036        : qq|WHERE o.trans_id IN (| . join(", ", map("?", @ids)) . qq|)|) .
1037       qq|ORDER BY o.trans_id, o.position|;
1038
1039     @ids = $form->{id} ? ($form->{id}) : @ids;
1040     $sth = prepare_execute_query($form, $dbh, $query, @values);
1041
1042     while ($ref = $sth->fetchrow_hashref("NAME_lc")) {
1043       # Retrieve custom variables.
1044       my $cvars = CVar->get_custom_variables(dbh        => $dbh,
1045                                              module     => 'IC',
1046                                              sub_module => 'orderitems',
1047                                              trans_id   => $ref->{orderitems_id},
1048                                             );
1049       map { $ref->{"ic_cvar_$_->{name}"} = $_->{value} } @{ $cvars };
1050
1051       # Handle accounts.
1052       if (!$ref->{"part_inventory_accno_id"}) {
1053         map({ delete($ref->{$_}); } qw(inventory_accno inventory_new_chart inventory_valid));
1054       }
1055       delete($ref->{"part_inventory_accno_id"});
1056
1057       # in collective order, copy global ordnumber, transdate, cusordnumber into item scope
1058       #   unless already present there
1059       # remove _oe entries afterwards
1060       map { $ref->{$_} = $ref->{"${_}_oe"} if ($ref->{$_} eq '') }
1061         qw|ordnumber transdate cusordnumber|
1062         if (@ids);
1063       map { delete $ref->{$_} } qw|ordnumber_oe transdate_oe cusordnumber_oe|;
1064
1065
1066
1067       while ($ref->{inventory_new_chart} && ($ref->{inventory_valid} >= 0)) {
1068         my $query =
1069           qq|SELECT accno AS inventory_accno, | .
1070           qq|  new_chart_id AS inventory_new_chart, | .
1071           qq|  date($transdate) - valid_from AS inventory_valid | .
1072           qq|FROM chart WHERE id = $ref->{inventory_new_chart}|;
1073         ($ref->{inventory_accno}, $ref->{inventory_new_chart},
1074          $ref->{inventory_valid}) = selectrow_query($form, $dbh, $query);
1075       }
1076
1077       while ($ref->{income_new_chart} && ($ref->{income_valid} >= 0)) {
1078         my $query =
1079           qq|SELECT accno AS income_accno, | .
1080           qq|  new_chart_id AS income_new_chart, | .
1081           qq|  date($transdate) - valid_from AS income_valid | .
1082           qq|FROM chart WHERE id = $ref->{income_new_chart}|;
1083         ($ref->{income_accno}, $ref->{income_new_chart},
1084          $ref->{income_valid}) = selectrow_query($form, $dbh, $query);
1085       }
1086
1087       while ($ref->{expense_new_chart} && ($ref->{expense_valid} >= 0)) {
1088         my $query =
1089           qq|SELECT accno AS expense_accno, | .
1090           qq|  new_chart_id AS expense_new_chart, | .
1091           qq|  date($transdate) - valid_from AS expense_valid | .
1092           qq|FROM chart WHERE id = $ref->{expense_new_chart}|;
1093         ($ref->{expense_accno}, $ref->{expense_new_chart},
1094          $ref->{expense_valid}) = selectrow_query($form, $dbh, $query);
1095       }
1096
1097       # delete orderitems_id in collective orders, so that they get cloned no matter what
1098       # is this correct? or is the following meant?
1099       # remember orderitems_ids in converted_from_orderitems_ids, so that they may be linked
1100       $ref->{converted_from_orderitems_id} = delete $ref->{orderitems_id} if $is_collective_order;
1101
1102       # get tax rates and description
1103       my $accno_id = ($form->{vc} eq "customer") ? $ref->{income_accno} : $ref->{expense_accno};
1104       $query =
1105         qq|SELECT c.accno, t.taxdescription, t.rate, t.taxnumber | .
1106         qq|FROM tax t LEFT JOIN chart c on (c.id = t.chart_id) | .
1107         qq|WHERE t.id IN (SELECT tk.tax_id FROM taxkeys tk | .
1108         qq|               WHERE tk.chart_id = (SELECT id FROM chart WHERE accno = ?) | .
1109         qq|                 AND startdate <= $transdate ORDER BY startdate DESC LIMIT 1) | .
1110         qq|ORDER BY c.accno|;
1111       my $stw = prepare_execute_query($form, $dbh, $query, $accno_id);
1112       $ref->{taxaccounts} = "";
1113       my $i = 0;
1114       while (my $ptr = $stw->fetchrow_hashref("NAME_lc")) {
1115         if (($ptr->{accno} eq "") && ($ptr->{rate} == 0)) {
1116           $i++;
1117           $ptr->{accno} = $i;
1118         }
1119         $ref->{taxaccounts} .= "$ptr->{accno} ";
1120         if (!($form->{taxaccounts} =~ /\Q$ptr->{accno}\E/)) {
1121           $form->{"$ptr->{accno}_rate"}        = $ptr->{rate};
1122           $form->{"$ptr->{accno}_description"} = $ptr->{taxdescription};
1123           $form->{"$ptr->{accno}_taxnumber"}   = $ptr->{taxnumber};
1124           $form->{taxaccounts} .= "$ptr->{accno} ";
1125         }
1126
1127       }
1128
1129       chop $ref->{taxaccounts};
1130
1131       push @{ $form->{form_details} }, $ref;
1132       $stw->finish;
1133     }
1134     $sth->finish;
1135
1136   } else {
1137
1138     # get last name used
1139     $form->lastname_used($dbh, $myconfig, $form->{vc})
1140       unless $form->{"$form->{vc}_id"};
1141
1142   }
1143
1144   $form->{exchangerate} = $form->get_exchangerate($dbh, $form->{currency}, $form->{transdate}, ($form->{vc} eq 'customer') ? "buy" : "sell");
1145
1146   Common::webdav_folder($form);
1147
1148   $self->load_periodic_invoice_config($form);
1149
1150   my $rc = $dbh->commit;
1151
1152   $main::lxdebug->leave_sub();
1153
1154   return $rc;
1155 }
1156
1157 sub retrieve_simple {
1158   $main::lxdebug->enter_sub();
1159
1160   my $self     = shift;
1161   my %params   = @_;
1162
1163   Common::check_params(\%params, qw(id));
1164
1165   my $myconfig    = \%main::myconfig;
1166   my $form        = $main::form;
1167
1168   my $dbh         = $params{dbh} || $form->get_standard_dbh($myconfig);
1169
1170   my $oe_query    = qq|SELECT * FROM oe         WHERE id = ?|;
1171   my $oi_query    = qq|SELECT * FROM orderitems WHERE trans_id = ? ORDER BY position|;
1172
1173   my $order            = selectfirst_hashref_query($form, $dbh, $oe_query, conv_i($params{id}));
1174   $order->{orderitems} = selectall_hashref_query(  $form, $dbh, $oi_query, conv_i($params{id}));
1175
1176   $main::lxdebug->leave_sub();
1177
1178   return $order;
1179 }
1180
1181 sub order_details {
1182   $main::lxdebug->enter_sub();
1183
1184   my ($self, $myconfig, $form) = @_;
1185
1186   # connect to database
1187   my $dbh = $form->get_standard_dbh;
1188   my $query;
1189   my @values = ();
1190   my $sth;
1191   my $nodiscount;
1192   my $yesdiscount;
1193   my $nodiscount_subtotal = 0;
1194   my $discount_subtotal = 0;
1195   my $item;
1196   my $i;
1197   my @partsgroup = ();
1198   my $partsgroup;
1199   my $position = 0;
1200   my $subtotal_header = 0;
1201   my $subposition = 0;
1202   my %taxaccounts;
1203   my %taxbase;
1204   my $tax_rate;
1205   my $taxamount;
1206
1207   my (@project_ids);
1208
1209   push(@project_ids, $form->{"globalproject_id"}) if ($form->{"globalproject_id"});
1210
1211   $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS',
1212                    'departments'   => 'ALL_DEPARTMENTS');
1213   my %price_factors;
1214
1215   foreach my $pfac (@{ $form->{ALL_PRICE_FACTORS} }) {
1216     $price_factors{$pfac->{id}}  = $pfac;
1217     $pfac->{factor}             *= 1;
1218     $pfac->{formatted_factor}    = $form->format_amount($myconfig, $pfac->{factor});
1219   }
1220
1221   # lookup department
1222   foreach my $dept (@{ $form->{ALL_DEPARTMENTS} }) {
1223     next unless $dept->{id} eq $form->{department_id};
1224     $form->{department} = $dept->{description};
1225     last;
1226   }
1227
1228   # sort items by partsgroup
1229   for $i (1 .. $form->{rowcount}) {
1230     $partsgroup = "";
1231     if ($form->{"partsgroup_$i"} && $form->{groupitems}) {
1232       $partsgroup = $form->{"partsgroup_$i"};
1233     }
1234     push @partsgroup, [$i, $partsgroup];
1235     push(@project_ids, $form->{"project_id_$i"}) if ($form->{"project_id_$i"});
1236   }
1237
1238   my $projects = [];
1239   my %projects_by_id;
1240   if (@project_ids) {
1241     $projects = SL::DB::Manager::Project->get_all(query => [ id => \@project_ids ]);
1242     %projects_by_id = map { $_->id => $_ } @$projects;
1243   }
1244
1245   if ($projects_by_id{$form->{"globalproject_id"}}) {
1246     $form->{globalprojectnumber} = $projects_by_id{$form->{"globalproject_id"}}->projectnumber;
1247     $form->{globalprojectdescription} = $projects_by_id{$form->{"globalproject_id"}}->description;
1248
1249     for (@{ $projects_by_id{$form->{"globalproject_id"}}->cvars_by_config }) {
1250       $form->{"project_cvar_" . $_->config->name} = $_->value_as_text;
1251     }
1252   }
1253
1254   $form->{discount} = [];
1255
1256   # get some values of parts from db on store them in extra array,
1257   # so that they can be sorted in later
1258   my %prepared_template_arrays = IC->prepare_parts_for_printing(myconfig => $myconfig, form => $form);
1259   my @prepared_arrays          = keys %prepared_template_arrays;
1260
1261   $form->{TEMPLATE_ARRAYS} = { };
1262
1263   my $ic_cvar_configs = CVar->get_configs(module => 'IC');
1264   my $project_cvar_configs = CVar->get_configs(module => 'Projects');
1265
1266   my @arrays =
1267     qw(runningnumber number description longdescription qty qty_nofmt ship ship_nofmt unit bin
1268        partnotes serialnumber reqdate sellprice sellprice_nofmt listprice listprice_nofmt netprice netprice_nofmt
1269        discount discount_nofmt p_discount discount_sub discount_sub_nofmt nodiscount_sub nodiscount_sub_nofmt
1270        linetotal linetotal_nofmt nodiscount_linetotal nodiscount_linetotal_nofmt tax_rate projectnumber projectdescription
1271        price_factor price_factor_name partsgroup weight weight_nofmt lineweight lineweight_nofmt);
1272
1273   push @arrays, map { "ic_cvar_$_->{name}" } @{ $ic_cvar_configs };
1274   push @arrays, map { "project_cvar_$_->{name}" } @{ $project_cvar_configs };
1275
1276   my @tax_arrays = qw(taxbase tax taxdescription taxrate taxnumber);
1277
1278   map { $form->{TEMPLATE_ARRAYS}->{$_} = [] } (@arrays, @tax_arrays, @prepared_arrays);
1279
1280   my $totalweight = 0;
1281   my $sameitem = "";
1282   foreach $item (sort { $a->[1] cmp $b->[1] } @partsgroup) {
1283     $i = $item->[0];
1284
1285     if ($item->[1] ne $sameitem) {
1286       push(@{ $form->{TEMPLATE_ARRAYS}->{entry_type}  }, 'partsgroup');
1287       push(@{ $form->{TEMPLATE_ARRAYS}->{description} }, qq|$item->[1]|);
1288       $sameitem = $item->[1];
1289
1290       map({ push(@{ $form->{TEMPLATE_ARRAYS}->{$_} }, "") } grep({ $_ ne "description" } (@arrays, @prepared_arrays)));
1291     }
1292
1293     $form->{"qty_$i"} = $form->parse_amount($myconfig, $form->{"qty_$i"});
1294
1295     if ($form->{"id_$i"} != 0) {
1296
1297       # add number, description and qty to $form->{number}, ....
1298
1299       if ($form->{"subtotal_$i"} && !$subtotal_header) {
1300         $subtotal_header = $i;
1301         $position = int($position);
1302         $subposition = 0;
1303         $position++;
1304       } elsif ($subtotal_header) {
1305         $subposition += 1;
1306         $position = int($position);
1307         $position = $position.".".$subposition;
1308       } else {
1309         $position = int($position);
1310         $position++;
1311       }
1312
1313       my $price_factor = $price_factors{$form->{"price_factor_id_$i"}} || { 'factor' => 1 };
1314
1315       push(@{ $form->{TEMPLATE_ARRAYS}->{$_} },                $prepared_template_arrays{$_}[$i - 1]) for @prepared_arrays;
1316
1317       push @{ $form->{TEMPLATE_ARRAYS}->{entry_type} },        'normal';
1318       push @{ $form->{TEMPLATE_ARRAYS}->{runningnumber} },     $position;
1319       push @{ $form->{TEMPLATE_ARRAYS}->{number} },            $form->{"partnumber_$i"};
1320       push @{ $form->{TEMPLATE_ARRAYS}->{description} },       $form->{"description_$i"};
1321       push @{ $form->{TEMPLATE_ARRAYS}->{longdescription} },   $form->{"longdescription_$i"};
1322       push @{ $form->{TEMPLATE_ARRAYS}->{qty} },               $form->format_amount($myconfig, $form->{"qty_$i"});
1323       push @{ $form->{TEMPLATE_ARRAYS}->{qty_nofmt} },         $form->{"qty_$i"};
1324       push @{ $form->{TEMPLATE_ARRAYS}->{ship} },              $form->format_amount($myconfig, $form->{"ship_$i"});
1325       push @{ $form->{TEMPLATE_ARRAYS}->{ship_nofmt} },        $form->{"ship_$i"};
1326       push @{ $form->{TEMPLATE_ARRAYS}->{unit} },              $form->{"unit_$i"};
1327       push @{ $form->{TEMPLATE_ARRAYS}->{bin} },               $form->{"bin_$i"};
1328       push @{ $form->{TEMPLATE_ARRAYS}->{partnotes} },         $form->{"partnotes_$i"};
1329       push @{ $form->{TEMPLATE_ARRAYS}->{serialnumber} },      $form->{"serialnumber_$i"};
1330       push @{ $form->{TEMPLATE_ARRAYS}->{reqdate} },           $form->{"reqdate_$i"};
1331       push @{ $form->{TEMPLATE_ARRAYS}->{sellprice} },         $form->{"sellprice_$i"};
1332       push @{ $form->{TEMPLATE_ARRAYS}->{sellprice_nofmt} },   $form->parse_amount($myconfig, $form->{"sellprice_$i"});
1333       push @{ $form->{TEMPLATE_ARRAYS}->{listprice} },         $form->format_amount($myconfig, $form->{"listprice_$i"}, 2);
1334       push @{ $form->{TEMPLATE_ARRAYS}->{listprice_nofmt} },   $form->{"listprice_$i"};
1335       push @{ $form->{TEMPLATE_ARRAYS}->{price_factor} },      $price_factor->{formatted_factor};
1336       push @{ $form->{TEMPLATE_ARRAYS}->{price_factor_name} }, $price_factor->{description};
1337       push @{ $form->{TEMPLATE_ARRAYS}->{partsgroup} },        $form->{"partsgroup_$i"};
1338
1339       my $sellprice     = $form->parse_amount($myconfig, $form->{"sellprice_$i"});
1340       my ($dec)         = ($sellprice =~ /\.(\d+)/);
1341       my $decimalplaces = max 2, length($dec);
1342
1343       my $parsed_discount            = $form->parse_amount($myconfig, $form->{"discount_$i"});
1344
1345       my $linetotal_exact            = $form->{"qty_$i"} * $sellprice * (100 - $parsed_discount) / 100 / $price_factor->{factor};
1346       my $linetotal                  = $form->round_amount($linetotal_exact, 2);
1347
1348       my $nodiscount_exact_linetotal = $form->{"qty_$i"} * $sellprice                                  / $price_factor->{factor};
1349       my $nodiscount_linetotal       = $form->round_amount($nodiscount_exact_linetotal,2);
1350
1351       my $discount                   = $nodiscount_linetotal - $linetotal; # is always rounded because $nodiscount_linetotal and $linetotal are rounded
1352
1353       my $discount_round_error       = $discount + ($linetotal_exact - $nodiscount_exact_linetotal); # not used
1354
1355       $form->{"netprice_$i"}   = $form->round_amount($form->{"qty_$i"} ? ($linetotal / $form->{"qty_$i"}) : 0, $decimalplaces);
1356
1357       push @{ $form->{TEMPLATE_ARRAYS}->{netprice} },       ($form->{"netprice_$i"} != 0) ? $form->format_amount($myconfig, $form->{"netprice_$i"}, $decimalplaces) : '';
1358       push @{ $form->{TEMPLATE_ARRAYS}->{netprice_nofmt} }, ($form->{"netprice_$i"} != 0) ? $form->{"netprice_$i"} : '';
1359
1360       $linetotal = ($linetotal != 0) ? $linetotal : '';
1361
1362       push @{ $form->{TEMPLATE_ARRAYS}->{discount} },       ($discount != 0) ? $form->format_amount($myconfig, $discount * -1, 2) : '';
1363       push @{ $form->{TEMPLATE_ARRAYS}->{discount_nofmt} }, ($discount != 0) ? $discount * -1 : '';
1364       push @{ $form->{TEMPLATE_ARRAYS}->{p_discount} },     $form->{"discount_$i"};
1365
1366       $form->{ordtotal}         += $linetotal;
1367       $form->{nodiscount_total} += $nodiscount_linetotal;
1368       $form->{discount_total}   += $discount;
1369
1370       if ($subtotal_header) {
1371         $discount_subtotal   += $linetotal;
1372         $nodiscount_subtotal += $nodiscount_linetotal;
1373       }
1374
1375       if ($form->{"subtotal_$i"} && $subtotal_header && ($subtotal_header != $i)) {
1376         push @{ $form->{TEMPLATE_ARRAYS}->{discount_sub} },         $form->format_amount($myconfig, $discount_subtotal,   2);
1377         push @{ $form->{TEMPLATE_ARRAYS}->{discount_sub_nofmt} },   $discount_subtotal;
1378         push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_sub} },       $form->format_amount($myconfig, $nodiscount_subtotal, 2);
1379         push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_sub_nofmt} }, $nodiscount_subtotal;
1380
1381         $discount_subtotal   = 0;
1382         $nodiscount_subtotal = 0;
1383         $subtotal_header     = 0;
1384
1385       } else {
1386         push @{ $form->{TEMPLATE_ARRAYS}->{$_} }, "" for qw(discount_sub nodiscount_sub discount_sub_nofmt nodiscount_sub_nofmt);
1387       }
1388
1389       if (!$form->{"discount_$i"}) {
1390         $nodiscount += $linetotal;
1391       }
1392
1393       my $project = $projects_by_id{$form->{"project_id_$i"}} || SL::DB::Project->new;
1394
1395       push @{ $form->{TEMPLATE_ARRAYS}->{linetotal} },                  $form->format_amount($myconfig, $linetotal, 2);
1396       push @{ $form->{TEMPLATE_ARRAYS}->{linetotal_nofmt} },            $linetotal_exact;
1397       push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_linetotal} },       $form->format_amount($myconfig, $nodiscount_linetotal, 2);
1398       push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_linetotal_nofmt} }, $nodiscount_linetotal;
1399       push @{ $form->{TEMPLATE_ARRAYS}->{projectnumber} },              $project->projectnumber;
1400       push @{ $form->{TEMPLATE_ARRAYS}->{projectdescription} },         $project->description;
1401
1402       my $lineweight = $form->{"qty_$i"} * $form->{"weight_$i"};
1403       $totalweight += $lineweight;
1404       push @{ $form->{TEMPLATE_ARRAYS}->{weight} },            $form->format_amount($myconfig, $form->{"weight_$i"}, 3);
1405       push @{ $form->{TEMPLATE_ARRAYS}->{weight_nofmt} },      $form->{"weight_$i"};
1406       push @{ $form->{TEMPLATE_ARRAYS}->{lineweight} },        $form->format_amount($myconfig, $lineweight, 3);
1407       push @{ $form->{TEMPLATE_ARRAYS}->{lineweight_nofmt} },  $lineweight;
1408
1409       my ($taxamount, $taxbase);
1410       my $taxrate = 0;
1411
1412       map { $taxrate += $form->{"${_}_rate"} } split(/ /, $form->{"taxaccounts_$i"});
1413
1414       if ($form->{taxincluded}) {
1415
1416         # calculate tax
1417         $taxamount = $linetotal * $taxrate / (1 + $taxrate);
1418         $taxbase = $linetotal / (1 + $taxrate);
1419       } else {
1420         $taxamount = $linetotal * $taxrate;
1421         $taxbase   = $linetotal;
1422       }
1423
1424       if ($taxamount != 0) {
1425         foreach my $accno (split / /, $form->{"taxaccounts_$i"}) {
1426           $taxaccounts{$accno} += $taxamount * $form->{"${accno}_rate"} / $taxrate;
1427           $taxbase{$accno}     += $taxbase;
1428         }
1429       }
1430
1431       $tax_rate = $taxrate * 100;
1432       push(@{ $form->{TEMPLATE_ARRAYS}->{tax_rate} }, qq|$tax_rate|);
1433
1434       if ($form->{"assembly_$i"}) {
1435         $sameitem = "";
1436
1437         # get parts and push them onto the stack
1438         my $sortorder = "";
1439         if ($form->{groupitems}) {
1440           $sortorder = qq|ORDER BY pg.partsgroup, a.oid|;
1441         } else {
1442           $sortorder = qq|ORDER BY a.oid|;
1443         }
1444
1445         $query = qq|SELECT p.partnumber, p.description, p.unit, a.qty, | .
1446                  qq|pg.partsgroup | .
1447                  qq|FROM assembly a | .
1448                  qq|  JOIN parts p ON (a.parts_id = p.id) | .
1449                  qq|    LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id) | .
1450                  qq|    WHERE a.bom = '1' | .
1451                  qq|    AND a.id = ? | . $sortorder;
1452         @values = ($form->{"id_$i"});
1453         $sth = $dbh->prepare($query);
1454         $sth->execute(@values) || $form->dberror($query);
1455
1456         while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
1457           if ($form->{groupitems} && $ref->{partsgroup} ne $sameitem) {
1458             map({ push(@{ $form->{TEMPLATE_ARRAYS}->{$_} }, "") } grep({ $_ ne "description" } (@arrays, @prepared_arrays)));
1459             $sameitem = ($ref->{partsgroup}) ? $ref->{partsgroup} : "--";
1460             push(@{ $form->{TEMPLATE_ARRAYS}->{entry_type}  }, 'assembly-item-partsgroup');
1461             push(@{ $form->{TEMPLATE_ARRAYS}->{description} }, $sameitem);
1462           }
1463
1464           push(@{ $form->{TEMPLATE_ARRAYS}->{entry_type}  }, 'assembly-item');
1465           push(@{ $form->{TEMPLATE_ARRAYS}->{description} }, $form->format_amount($myconfig, $ref->{qty} * $form->{"qty_$i"}) . qq|, $ref->{partnumber}, $ref->{description}|);
1466           map({ push(@{ $form->{TEMPLATE_ARRAYS}->{$_} }, "") } grep({ $_ ne "description" } (@arrays, @prepared_arrays)));
1467         }
1468         $sth->finish;
1469       }
1470
1471       CVar->get_non_editable_ic_cvars(form               => $form,
1472                                       dbh                => $dbh,
1473                                       row                => $i,
1474                                       sub_module         => 'orderitems',
1475                                       may_converted_from => ['orderitems', 'invoice']);
1476
1477       push @{ $form->{TEMPLATE_ARRAYS}->{"ic_cvar_$_->{name}"} },
1478         CVar->format_to_template(CVar->parse($form->{"ic_cvar_$_->{name}_$i"}, $_), $_)
1479           for @{ $ic_cvar_configs };
1480
1481       push @{ $form->{TEMPLATE_ARRAYS}->{"project_cvar_" . $_->config->name} }, $_->value_as_text for @{ $project->cvars_by_config };
1482     }
1483   }
1484
1485   $form->{totalweight}       = $form->format_amount($myconfig, $totalweight, 3);
1486   $form->{totalweight_nofmt} = $totalweight;
1487   my $defaults = AM->get_defaults();
1488   $form->{weightunit}        = $defaults->{weightunit};
1489
1490   my $tax = 0;
1491   foreach $item (sort keys %taxaccounts) {
1492     $tax += $taxamount = $form->round_amount($taxaccounts{$item}, 2);
1493
1494     push(@{ $form->{TEMPLATE_ARRAYS}->{taxbase} },        $form->format_amount($myconfig, $taxbase{$item}, 2));
1495     push(@{ $form->{TEMPLATE_ARRAYS}->{taxbase_nofmt} },  $taxbase{$item});
1496     push(@{ $form->{TEMPLATE_ARRAYS}->{tax} },            $form->format_amount($myconfig, $taxamount,      2));
1497     push(@{ $form->{TEMPLATE_ARRAYS}->{tax_nofmt} },      $taxamount);
1498     push(@{ $form->{TEMPLATE_ARRAYS}->{taxrate} },        $form->format_amount($myconfig, $form->{"${item}_rate"} * 100));
1499     push(@{ $form->{TEMPLATE_ARRAYS}->{taxrate_nofmt} },  $form->{"${item}_rate"} * 100);
1500     push(@{ $form->{TEMPLATE_ARRAYS}->{taxnumber} },      $form->{"${item}_taxnumber"});
1501
1502     my $tax_obj     = SL::DB::Manager::Tax->find_by(taxnumber => $form->{"${item}_taxnumber"});
1503     my $description = $tax_obj ? $tax_obj->translated_attribute('taxdescription',  $form->{language_id}, 0) : '';
1504     push(@{ $form->{TEMPLATE_ARRAYS}->{taxdescription} }, $description . q{ } . 100 * $form->{"${item}_rate"} . q{%});
1505   }
1506
1507   $form->{nodiscount_subtotal} = $form->format_amount($myconfig, $form->{nodiscount_total}, 2);
1508   $form->{discount_total}      = $form->format_amount($myconfig, $form->{discount_total}, 2);
1509   $form->{nodiscount}          = $form->format_amount($myconfig, $nodiscount, 2);
1510   $form->{yesdiscount}         = $form->format_amount($myconfig, $form->{nodiscount_total} - $nodiscount, 2);
1511
1512   if($form->{taxincluded}) {
1513     $form->{subtotal}       = $form->format_amount($myconfig, $form->{ordtotal} - $tax, 2);
1514     $form->{subtotal_nofmt} = $form->{ordtotal} - $tax;
1515   } else {
1516     $form->{subtotal}       = $form->format_amount($myconfig, $form->{ordtotal}, 2);
1517     $form->{subtotal_nofmt} = $form->{ordtotal};
1518   }
1519
1520   $form->{ordtotal} = ($form->{taxincluded}) ? $form->{ordtotal} : $form->{ordtotal} + $tax;
1521
1522   # format amounts
1523   $form->{quototal} = $form->{ordtotal} = $form->format_amount($myconfig, $form->{ordtotal}, 2);
1524
1525   if ($form->{type} =~ /_quotation/) {
1526     $form->set_payment_options($myconfig, $form->{quodate});
1527   } else {
1528     $form->set_payment_options($myconfig, $form->{orddate});
1529   }
1530
1531   $form->{username} = $myconfig->{name};
1532
1533   $dbh->disconnect;
1534
1535   $form->{delivery_term} = SL::DB::Manager::DeliveryTerm->find_by(id => $form->{delivery_term_id} || undef);
1536   $form->{delivery_term}->description_long($form->{delivery_term}->translated_attribute('description_long', $form->{language_id})) if $form->{delivery_term} && $form->{language_id};
1537
1538   $form->{order} = SL::DB::Manager::Order->find_by(id => $form->{id}) if $form->{id};
1539
1540   $main::lxdebug->leave_sub();
1541 }
1542
1543 sub project_description {
1544   $main::lxdebug->enter_sub();
1545
1546   my ($self, $dbh, $id) = @_;
1547
1548   my $query = qq|SELECT description FROM project WHERE id = ?|;
1549   my ($value) = selectrow_query($main::form, $dbh, $query, $id);
1550
1551   $main::lxdebug->leave_sub();
1552
1553   return $value;
1554 }
1555
1556 1;
1557
1558 __END__
1559
1560 =head1 NAME
1561
1562 OE.pm - Order entry module
1563
1564 =head1 DESCRIPTION
1565
1566 OE.pm is part of the OE module. OE is responsible for sales and purchase orders, as well as sales quotations and purchase requests. This file abstracts the database tables C<oe> and C<orderitems>.
1567
1568 =head1 FUNCTIONS
1569
1570 =over 4
1571
1572 =item retrieve_simple PARAMS
1573
1574 simple OE retrieval by id. does not look up customer, vendor, units or any other stuff. only oe and orderitems.
1575
1576   my $order = retrieve_simple(id => 2);
1577
1578   $order => {
1579     %_OE_CONTENT,
1580     orderitems => [
1581       %_ORDERITEM_ROW_1,
1582       %_ORDERITEM_ROW_2,
1583       ...
1584     ]
1585   }
1586
1587 =back
1588
1589 =cut