Angebote/Aufträge: Beim "als neu Speichern" Wechselkurs richtig übernehmen.
[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, 1);
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   # from inputfield (exchangerate) or hidden (forex)
657   my $exchangerate_from_form = $form->{forex} || $form->parse_amount($myconfig, $form->{exchangerate});
658
659   $form->{exchangerate} = $exchangerate || $exchangerate_from_form;
660
661   my $quotation = $form->{type} =~ /_order$/ ? 'f' : 't';
662
663   ($null, $form->{department_id}) = split(/--/, $form->{department}) if $form->{department};
664
665   # save OE record
666   $query =
667     qq|UPDATE oe SET
668          ordnumber = ?, quonumber = ?, cusordnumber = ?, transdate = ?, vendor_id = ?,
669          customer_id = ?, amount = ?, netamount = ?, reqdate = ?, taxincluded = ?,
670          shippingpoint = ?, shipvia = ?, notes = ?, intnotes = ?, currency_id = (SELECT id FROM currencies WHERE name=?), closed = ?,
671          delivered = ?, proforma = ?, quotation = ?, department_id = ?, language_id = ?,
672          taxzone_id = ?, shipto_id = ?, payment_id = ?, delivery_vendor_id = ?, delivery_customer_id = ?,delivery_term_id = ?,
673          globalproject_id = ?, employee_id = ?, salesman_id = ?, cp_id = ?, transaction_description = ?, marge_total = ?, marge_percent = ?
674          , order_probability = ?, expected_billing_date = ?
675        WHERE id = ?|;
676
677   @values = ($form->{ordnumber} || '', $form->{quonumber},
678              $form->{cusordnumber}, conv_date($form->{transdate}),
679              conv_i($form->{vendor_id}), conv_i($form->{customer_id}),
680              $amount, $netamount, conv_date($reqdate),
681              $form->{taxincluded} ? 't' : 'f', $form->{shippingpoint},
682              $form->{shipvia}, $restricter->process($form->{notes}), $form->{intnotes},
683              $form->{currency}, $form->{closed} ? 't' : 'f',
684              $form->{delivered} ? "t" : "f", $form->{proforma} ? 't' : 'f',
685              $quotation, conv_i($form->{department_id}),
686              conv_i($form->{language_id}), conv_i($form->{taxzone_id}),
687              conv_i($form->{shipto_id}), conv_i($form->{payment_id}),
688              conv_i($form->{delivery_vendor_id}),
689              conv_i($form->{delivery_customer_id}),
690              conv_i($form->{delivery_term_id}),
691              conv_i($form->{globalproject_id}), conv_i($form->{employee_id}),
692              conv_i($form->{salesman_id}), conv_i($form->{cp_id}),
693              $form->{transaction_description},
694              $form->{marge_total} * 1, $form->{marge_percent} * 1,
695              $form->{order_probability} * 1, conv_date($form->{expected_billing_date}),
696              conv_i($form->{id}));
697   do_query($form, $dbh, $query, @values);
698
699   $form->{ordtotal} = $amount;
700
701   $form->{name} = $form->{ $form->{vc} };
702   $form->{name} =~ s/--\Q$form->{"$form->{vc}_id"}\E//;
703
704   # add shipto
705   if (!$form->{shipto_id}) {
706     $form->add_shipto($dbh, $form->{id}, "OE");
707   }
708
709   # save printed, emailed, queued
710   $form->save_status($dbh);
711
712   # Link this record to the records it was created from.
713   # check every record type we may link. i am not happy with converting the string to array back
714   # should be a array from the start (OE.pm -> retrieve).
715   #  and that i need the local array ref for close_quotation_rfqs. better ideas welcome
716   $form->{convert_from_oe_ids} =~ s/^\s+//;
717   $form->{convert_from_oe_ids} =~ s/\s+$//;
718   my @convert_from_oe_ids      =  split m/\s+/, $form->{convert_from_oe_ids};
719   delete $form->{convert_from_oe_ids};
720   @{ $form->{convert_from_oe_ids} }      =  @convert_from_oe_ids;
721   foreach (qw(ar oe)) {
722     if (!$form->{useasnew} && $form->{"convert_from_${_}_ids"}) {
723       RecordLinks->create_links('dbh'        => $dbh,
724                                 'mode'       => 'ids',
725                                 'from_table' => $_,
726                                 'from_ids'   => $form->{"convert_from_${_}_ids"},
727                                 'to_table'   => 'oe',
728                                 'to_id'      => $form->{id},
729         );
730       delete $form->{"convert_from_${_}_ids"};
731     }
732     $self->_close_quotations_rfqs('dbh'     => $dbh,
733                                   'from_id' => \@convert_from_oe_ids,
734                                   'to_id'   => $form->{id}) if $_ eq 'oe';
735   }
736
737   if (($form->{currency} ne $form->{defaultcurrency}) && !$exchangerate) {
738     if ($form->{vc} eq 'customer') {
739       $form->update_exchangerate($dbh, $form->{currency}, $form->{transdate}, $form->{exchangerate}, 0);
740     }
741     if ($form->{vc} eq 'vendor') {
742       $form->update_exchangerate($dbh, $form->{currency}, $form->{transdate}, 0, $form->{exchangerate});
743     }
744   }
745
746   $form->{saved_xyznumber} = $form->{$form->{type} =~ /_quotation$/ ?
747                                        "quonumber" : "ordnumber"};
748
749   Common::webdav_folder($form);
750
751   my $rc = $dbh->commit;
752
753   $self->save_periodic_invoices_config(dbh         => $dbh,
754                                        oe_id       => $form->{id},
755                                        config_yaml => $form->{periodic_invoices_config})
756     if ($form->{type} eq 'sales_order');
757
758   $main::lxdebug->leave_sub();
759
760   return $rc;
761 }
762
763 sub save_periodic_invoices_config {
764   my ($self, %params) = @_;
765
766   return if !$params{oe_id};
767
768   my $config = $params{config_yaml} ? YAML::Load($params{config_yaml}) : undef;
769   return if 'HASH' ne ref $config;
770
771   my $obj  = SL::DB::Manager::PeriodicInvoicesConfig->find_by(oe_id => $params{oe_id})
772           || SL::DB::PeriodicInvoicesConfig->new(oe_id => $params{oe_id});
773   $obj->update_attributes(%{ $config });
774 }
775
776 sub load_periodic_invoice_config {
777   my $self = shift;
778   my $form = shift;
779
780   delete $form->{periodic_invoices_config};
781
782   if ($form->{id}) {
783     my $config_obj = SL::DB::Manager::PeriodicInvoicesConfig->find_by(oe_id => $form->{id});
784
785     if ($config_obj) {
786       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
787                                                       print printer_id copies direct_debit) };
788       $form->{periodic_invoices_config} = YAML::Dump($config);
789     }
790   }
791 }
792
793 sub _close_quotations_rfqs {
794   $main::lxdebug->enter_sub();
795
796   my $self     = shift;
797   my %params   = @_;
798
799   Common::check_params(\%params, qw(from_id to_id));
800
801   my $myconfig = \%main::myconfig;
802   my $form     = $main::form;
803
804   my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
805
806   my $query    = qq|SELECT quotation FROM oe WHERE id = ?|;
807   my $sth      = prepare_query($form, $dbh, $query);
808
809   do_statement($form, $sth, $query, conv_i($params{to_id}));
810
811   my ($quotation) = $sth->fetchrow_array();
812
813   if ($quotation) {
814     $main::lxdebug->leave_sub();
815     return;
816   }
817
818   my @close_ids;
819
820   foreach my $from_id (@{ $params{from_id} }) {
821     $from_id = conv_i($from_id);
822     do_statement($form, $sth, $query, $from_id);
823     ($quotation) = $sth->fetchrow_array();
824     push @close_ids, $from_id if ($quotation);
825   }
826
827   $sth->finish();
828
829   if (scalar @close_ids) {
830     $query = qq|UPDATE oe SET closed = TRUE WHERE id IN (| . join(', ', ('?') x scalar @close_ids) . qq|)|;
831     do_query($form, $dbh, $query, @close_ids);
832
833     $dbh->commit() unless ($params{dbh});
834   }
835
836   $main::lxdebug->leave_sub();
837 }
838
839 sub delete {
840   $main::lxdebug->enter_sub();
841
842   my ($self, $myconfig, $form) = @_;
843
844   my $rc = SL::DB::Order->new->db->with_transaction(sub {
845     my @spoolfiles = grep { $_ } map { $_->spoolfile } @{ SL::DB::Manager::Status->get_all(where => [ trans_id => $form->{id} ]) };
846
847     SL::DB::Order->new(id => $form->{id})->delete;
848
849     my $spool = $::lx_office_conf{paths}->{spool};
850     unlink map { "$spool/$_" } @spoolfiles if $spool;
851
852     1;
853   });
854
855   $main::lxdebug->leave_sub();
856
857   return $rc;
858 }
859
860 sub retrieve {
861   $main::lxdebug->enter_sub();
862
863   my ($self, $myconfig, $form) = @_;
864
865   # connect to database
866   my $dbh = $form->get_standard_dbh;
867
868   my ($query, $query_add, @values, @ids, $sth);
869
870   # translate the ids (given by id_# and trans_id_#) into one array of ids, so we can join them later
871   map {
872     push @ids, $form->{"trans_id_$_"}
873       if ($form->{"multi_id_$_"} and $form->{"trans_id_$_"})
874   } (1 .. $form->{"rowcount"});
875
876   if ($form->{rowcount} && scalar @ids) {
877     $form->{convert_from_oe_ids} = join ' ', @ids;
878   }
879
880   # if called in multi id mode, and still only got one id, switch back to single id
881   if ($form->{"rowcount"} and $#ids == 0) {
882     $form->{"id"} = $ids[0];
883     undef @ids;
884     delete $form->{convert_from_oe_ids};
885   }
886
887   # and remember for the rest of the function
888   my $is_collective_order = scalar @ids;
889
890   # If collective order was created from exactly 1 order, we assume the same
891   # behaviour as a "save as new" from within an order is actually desired, i.e.
892   # the original order isn't part of a workflow where we want to remember
893   # record_links, but simply a quick way of generating a new order from an old
894   # one without having to enter everything again.
895   # Setting useasnew will prevent the creation of record_links for the items
896   # when saving the new order.
897   # This form variable is probably not necessary, could just set saveasnew instead
898   $form->{useasnew} = 1 if $is_collective_order == 1;
899
900   if (!$form->{id}) {
901     my $extra_days     = $form->{type} eq 'sales_quotation' ? $::instance_conf->get_reqdate_interval : 1;
902     $form->{reqdate}   = DateTime->today_local->next_workday(extra_days => $extra_days)->to_kivitendo;
903     $form->{transdate} = DateTime->today_local->to_kivitendo;
904   }
905
906   # get default accounts
907   $query = qq|SELECT (SELECT c.accno FROM chart c WHERE d.inventory_accno_id = c.id) AS inventory_accno,
908                      (SELECT c.accno FROM chart c WHERE d.income_accno_id    = c.id) AS income_accno,
909                      (SELECT c.accno FROM chart c WHERE d.expense_accno_id   = c.id) AS expense_accno,
910                      (SELECT c.accno FROM chart c WHERE d.fxgain_accno_id    = c.id) AS fxgain_accno,
911                      (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id    = c.id) AS fxloss_accno,
912                      (SELECT c.accno FROM chart c WHERE d.rndgain_accno_id   = c.id) AS rndgain_accno,
913                      (SELECT c.accno FROM chart c WHERE d.rndloss_accno_id   = c.id) AS rndloss_accno
914               $query_add
915               FROM defaults d|;
916   my $ref = selectfirst_hashref_query($form, $dbh, $query);
917   map { $form->{$_} = $ref->{$_} } keys %$ref;
918
919   $form->{currency} = $form->get_default_currency($myconfig);
920
921   # set reqdate if this is an invoice->order conversion. If someone knows a better check to ensure
922   # we come from invoices, feel free.
923   $form->{reqdate} = $form->{deliverydate}
924     if (    $form->{deliverydate}
925         and $form->{callback} =~ /action=ar_transactions/);
926
927   my $vc = $form->{vc} eq "customer" ? "customer" : "vendor";
928
929   if ($form->{id} or @ids) {
930
931     # retrieve order for single id
932     # NOTE: this query is intended to fetch all information only ONCE.
933     # so if any of these infos is important (or even different) for any item,
934     # it will be killed out and then has to be fetched from the item scope query further down
935     $query =
936       qq|SELECT o.cp_id, o.ordnumber, o.transdate, o.reqdate,
937            o.taxincluded, o.shippingpoint, o.shipvia, o.notes, o.intnotes,
938            (SELECT cu.name FROM currencies cu WHERE cu.id=o.currency_id) AS currency, e.name AS employee, o.employee_id, o.salesman_id,
939            o.${vc}_id, cv.name AS ${vc}, o.amount AS invtotal,
940            o.closed, o.reqdate, o.quonumber, o.department_id, o.cusordnumber,
941            o.mtime, o.itime,
942            d.description AS department, o.payment_id, o.language_id, o.taxzone_id,
943            o.delivery_customer_id, o.delivery_vendor_id, o.proforma, o.shipto_id,
944            o.globalproject_id, o.delivered, o.transaction_description, o.delivery_term_id,
945            o.itime::DATE AS insertdate, o.order_probability, o.expected_billing_date
946          FROM oe o
947          JOIN ${vc} cv ON (o.${vc}_id = cv.id)
948          LEFT JOIN employee e ON (o.employee_id = e.id)
949          LEFT JOIN department d ON (o.department_id = d.id) | .
950         ($form->{id}
951          ? "WHERE o.id = ?"
952          : "WHERE o.id IN (" . join(', ', map("? ", @ids)) . ")"
953         );
954     @values = $form->{id} ? ($form->{id}) : @ids;
955     $sth = prepare_execute_query($form, $dbh, $query, @values);
956
957     $ref = $sth->fetchrow_hashref("NAME_lc");
958
959     if ($ref) {
960       map { $form->{$_} = $ref->{$_} } keys %$ref;
961
962       $form->{saved_xyznumber} = $form->{$form->{type} =~ /_quotation$/ ? "quonumber" : "ordnumber"};
963
964       # set all entries for multiple ids blank that yield different information
965       while ($ref = $sth->fetchrow_hashref("NAME_lc")) {
966         map { $form->{$_} = '' if ($ref->{$_} ne $form->{$_}) } keys %$ref;
967       }
968     }
969     $form->{mtime}   ||= $form->{itime};
970     $form->{lastmtime} = $form->{mtime};
971
972     # if not given, fill transdate with current_date
973     $form->{transdate} = $form->current_date($myconfig)
974       unless $form->{transdate};
975
976     $sth->finish;
977
978     if ($form->{delivery_customer_id}) {
979       $query = qq|SELECT name FROM customer WHERE id = ?|;
980       ($form->{delivery_customer_string}) = selectrow_query($form, $dbh, $query, $form->{delivery_customer_id});
981     }
982
983     if ($form->{delivery_vendor_id}) {
984       $query = qq|SELECT name FROM customer WHERE id = ?|;
985       ($form->{delivery_vendor_string}) = selectrow_query($form, $dbh, $query, $form->{delivery_vendor_id});
986     }
987
988     # shipto and pinted/mailed/queued status makes only sense for single id retrieve
989     if (!@ids) {
990       $query = qq|SELECT s.* FROM shipto s WHERE s.trans_id = ? AND s.module = 'OE'|;
991       $sth = prepare_execute_query($form, $dbh, $query, $form->{id});
992
993       $ref = $sth->fetchrow_hashref("NAME_lc");
994       delete($ref->{id});
995       map { $form->{$_} = $ref->{$_} } keys %$ref;
996       $sth->finish;
997
998       # get printed, emailed and queued
999       $query = qq|SELECT s.printed, s.emailed, s.spoolfile, s.formname FROM status s WHERE s.trans_id = ?|;
1000       $sth = prepare_execute_query($form, $dbh, $query, $form->{id});
1001
1002       while ($ref = $sth->fetchrow_hashref("NAME_lc")) {
1003         $form->{printed} .= "$ref->{formname} " if $ref->{printed};
1004         $form->{emailed} .= "$ref->{formname} " if $ref->{emailed};
1005         $form->{queued}  .= "$ref->{formname} $ref->{spoolfile} " if $ref->{spoolfile};
1006       }
1007       $sth->finish;
1008       map { $form->{$_} =~ s/ +$//g } qw(printed emailed queued);
1009     }    # if !@ids
1010
1011     my $transdate = $form->{transdate} ? $dbh->quote($form->{transdate}) : "current_date";
1012
1013     $form->{taxzone_id} = 0 unless ($form->{taxzone_id});
1014
1015     # retrieve individual items
1016     # this query looks up all information about the items
1017     # stuff different from the whole will not be overwritten, but saved with a suffix.
1018     $query =
1019       qq|SELECT o.id AS orderitems_id,
1020            c1.accno AS inventory_accno, c1.new_chart_id AS inventory_new_chart, date($transdate) - c1.valid_from as inventory_valid,
1021            c2.accno AS income_accno,    c2.new_chart_id AS income_new_chart,    date($transdate) - c2.valid_from as income_valid,
1022            c3.accno AS expense_accno,   c3.new_chart_id AS expense_new_chart,   date($transdate) - c3.valid_from as expense_valid,
1023            oe.ordnumber AS ordnumber_oe, oe.transdate AS transdate_oe, oe.cusordnumber AS cusordnumber_oe,
1024            p.partnumber, p.assembly, p.listprice, o.description, o.qty,
1025            o.sellprice, o.parts_id AS id, o.unit, o.discount, p.notes AS partnotes, p.inventory_accno_id AS part_inventory_accno_id,
1026            o.reqdate, o.project_id, o.serialnumber, o.ship, o.lastcost,
1027            o.ordnumber, o.transdate, o.cusordnumber, o.subtotal, o.longdescription,
1028            o.price_factor_id, o.price_factor, o.marge_price_factor, o.active_price_source, o.active_discount_source,
1029            pr.projectnumber, p.formel,
1030            pg.partsgroup, o.pricegroup_id, (SELECT pricegroup FROM pricegroup WHERE id=o.pricegroup_id) as pricegroup
1031          FROM orderitems o
1032          JOIN parts p ON (o.parts_id = p.id)
1033          JOIN oe ON (o.trans_id = oe.id)
1034          LEFT JOIN chart c1 ON ((SELECT inventory_accno_id                   FROM buchungsgruppen WHERE id=p.buchungsgruppen_id) = c1.id)
1035          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)
1036          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)
1037          LEFT JOIN project pr ON (o.project_id = pr.id)
1038          LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id) | .
1039       ($form->{id}
1040        ? qq|WHERE o.trans_id = ?|
1041        : qq|WHERE o.trans_id IN (| . join(", ", map("?", @ids)) . qq|)|) .
1042       qq|ORDER BY o.trans_id, o.position|;
1043
1044     @ids = $form->{id} ? ($form->{id}) : @ids;
1045     $sth = prepare_execute_query($form, $dbh, $query, @values);
1046
1047     while ($ref = $sth->fetchrow_hashref("NAME_lc")) {
1048       # Retrieve custom variables.
1049       my $cvars = CVar->get_custom_variables(dbh        => $dbh,
1050                                              module     => 'IC',
1051                                              sub_module => 'orderitems',
1052                                              trans_id   => $ref->{orderitems_id},
1053                                             );
1054       map { $ref->{"ic_cvar_$_->{name}"} = $_->{value} } @{ $cvars };
1055
1056       # Handle accounts.
1057       if (!$ref->{"part_inventory_accno_id"}) {
1058         map({ delete($ref->{$_}); } qw(inventory_accno inventory_new_chart inventory_valid));
1059       }
1060       delete($ref->{"part_inventory_accno_id"});
1061
1062       # in collective order, copy global ordnumber, transdate, cusordnumber into item scope
1063       #   unless already present there
1064       # remove _oe entries afterwards
1065       map { $ref->{$_} = $ref->{"${_}_oe"} if ($ref->{$_} eq '') }
1066         qw|ordnumber transdate cusordnumber|
1067         if (@ids);
1068       map { delete $ref->{$_} } qw|ordnumber_oe transdate_oe cusordnumber_oe|;
1069
1070
1071
1072       while ($ref->{inventory_new_chart} && ($ref->{inventory_valid} >= 0)) {
1073         my $query =
1074           qq|SELECT accno AS inventory_accno, | .
1075           qq|  new_chart_id AS inventory_new_chart, | .
1076           qq|  date($transdate) - valid_from AS inventory_valid | .
1077           qq|FROM chart WHERE id = $ref->{inventory_new_chart}|;
1078         ($ref->{inventory_accno}, $ref->{inventory_new_chart},
1079          $ref->{inventory_valid}) = selectrow_query($form, $dbh, $query);
1080       }
1081
1082       while ($ref->{income_new_chart} && ($ref->{income_valid} >= 0)) {
1083         my $query =
1084           qq|SELECT accno AS income_accno, | .
1085           qq|  new_chart_id AS income_new_chart, | .
1086           qq|  date($transdate) - valid_from AS income_valid | .
1087           qq|FROM chart WHERE id = $ref->{income_new_chart}|;
1088         ($ref->{income_accno}, $ref->{income_new_chart},
1089          $ref->{income_valid}) = selectrow_query($form, $dbh, $query);
1090       }
1091
1092       while ($ref->{expense_new_chart} && ($ref->{expense_valid} >= 0)) {
1093         my $query =
1094           qq|SELECT accno AS expense_accno, | .
1095           qq|  new_chart_id AS expense_new_chart, | .
1096           qq|  date($transdate) - valid_from AS expense_valid | .
1097           qq|FROM chart WHERE id = $ref->{expense_new_chart}|;
1098         ($ref->{expense_accno}, $ref->{expense_new_chart},
1099          $ref->{expense_valid}) = selectrow_query($form, $dbh, $query);
1100       }
1101
1102       # delete orderitems_id in collective orders, so that they get cloned no matter what
1103       # is this correct? or is the following meant?
1104       # remember orderitems_ids in converted_from_orderitems_ids, so that they may be linked
1105       $ref->{converted_from_orderitems_id} = delete $ref->{orderitems_id} if $is_collective_order;
1106
1107       # get tax rates and description
1108       my $accno_id = ($form->{vc} eq "customer") ? $ref->{income_accno} : $ref->{expense_accno};
1109       $query =
1110         qq|SELECT c.accno, t.taxdescription, t.rate, t.taxnumber | .
1111         qq|FROM tax t LEFT JOIN chart c on (c.id = t.chart_id) | .
1112         qq|WHERE t.id IN (SELECT tk.tax_id FROM taxkeys tk | .
1113         qq|               WHERE tk.chart_id = (SELECT id FROM chart WHERE accno = ?) | .
1114         qq|                 AND startdate <= $transdate ORDER BY startdate DESC LIMIT 1) | .
1115         qq|ORDER BY c.accno|;
1116       my $stw = prepare_execute_query($form, $dbh, $query, $accno_id);
1117       $ref->{taxaccounts} = "";
1118       my $i = 0;
1119       while (my $ptr = $stw->fetchrow_hashref("NAME_lc")) {
1120         if (($ptr->{accno} eq "") && ($ptr->{rate} == 0)) {
1121           $i++;
1122           $ptr->{accno} = $i;
1123         }
1124         $ref->{taxaccounts} .= "$ptr->{accno} ";
1125         if (!($form->{taxaccounts} =~ /\Q$ptr->{accno}\E/)) {
1126           $form->{"$ptr->{accno}_rate"}        = $ptr->{rate};
1127           $form->{"$ptr->{accno}_description"} = $ptr->{taxdescription};
1128           $form->{"$ptr->{accno}_taxnumber"}   = $ptr->{taxnumber};
1129           $form->{taxaccounts} .= "$ptr->{accno} ";
1130         }
1131
1132       }
1133
1134       chop $ref->{taxaccounts};
1135
1136       push @{ $form->{form_details} }, $ref;
1137       $stw->finish;
1138     }
1139     $sth->finish;
1140
1141   } else {
1142
1143     # get last name used
1144     $form->lastname_used($dbh, $myconfig, $form->{vc})
1145       unless $form->{"$form->{vc}_id"};
1146
1147   }
1148
1149   $form->{exchangerate} = $form->get_exchangerate($dbh, $form->{currency}, $form->{transdate}, ($form->{vc} eq 'customer') ? "buy" : "sell");
1150
1151   Common::webdav_folder($form);
1152
1153   $self->load_periodic_invoice_config($form);
1154
1155   my $rc = $dbh->commit;
1156
1157   $main::lxdebug->leave_sub();
1158
1159   return $rc;
1160 }
1161
1162 sub retrieve_simple {
1163   $main::lxdebug->enter_sub();
1164
1165   my $self     = shift;
1166   my %params   = @_;
1167
1168   Common::check_params(\%params, qw(id));
1169
1170   my $myconfig    = \%main::myconfig;
1171   my $form        = $main::form;
1172
1173   my $dbh         = $params{dbh} || $form->get_standard_dbh($myconfig);
1174
1175   my $oe_query    = qq|SELECT * FROM oe         WHERE id = ?|;
1176   my $oi_query    = qq|SELECT * FROM orderitems WHERE trans_id = ? ORDER BY position|;
1177
1178   my $order            = selectfirst_hashref_query($form, $dbh, $oe_query, conv_i($params{id}));
1179   $order->{orderitems} = selectall_hashref_query(  $form, $dbh, $oi_query, conv_i($params{id}));
1180
1181   $main::lxdebug->leave_sub();
1182
1183   return $order;
1184 }
1185
1186 sub order_details {
1187   $main::lxdebug->enter_sub();
1188
1189   my ($self, $myconfig, $form) = @_;
1190
1191   # connect to database
1192   my $dbh = $form->get_standard_dbh;
1193   my $query;
1194   my @values = ();
1195   my $sth;
1196   my $nodiscount;
1197   my $yesdiscount;
1198   my $nodiscount_subtotal = 0;
1199   my $discount_subtotal = 0;
1200   my $item;
1201   my $i;
1202   my @partsgroup = ();
1203   my $partsgroup;
1204   my $position = 0;
1205   my $subtotal_header = 0;
1206   my $subposition = 0;
1207   my %taxaccounts;
1208   my %taxbase;
1209   my $tax_rate;
1210   my $taxamount;
1211
1212   my (@project_ids);
1213
1214   push(@project_ids, $form->{"globalproject_id"}) if ($form->{"globalproject_id"});
1215
1216   $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS',
1217                    'departments'   => 'ALL_DEPARTMENTS');
1218   my %price_factors;
1219
1220   foreach my $pfac (@{ $form->{ALL_PRICE_FACTORS} }) {
1221     $price_factors{$pfac->{id}}  = $pfac;
1222     $pfac->{factor}             *= 1;
1223     $pfac->{formatted_factor}    = $form->format_amount($myconfig, $pfac->{factor});
1224   }
1225
1226   # lookup department
1227   foreach my $dept (@{ $form->{ALL_DEPARTMENTS} }) {
1228     next unless $dept->{id} eq $form->{department_id};
1229     $form->{department} = $dept->{description};
1230     last;
1231   }
1232
1233   # sort items by partsgroup
1234   for $i (1 .. $form->{rowcount}) {
1235     $partsgroup = "";
1236     if ($form->{"partsgroup_$i"} && $form->{groupitems}) {
1237       $partsgroup = $form->{"partsgroup_$i"};
1238     }
1239     push @partsgroup, [$i, $partsgroup];
1240     push(@project_ids, $form->{"project_id_$i"}) if ($form->{"project_id_$i"});
1241   }
1242
1243   my $projects = [];
1244   my %projects_by_id;
1245   if (@project_ids) {
1246     $projects = SL::DB::Manager::Project->get_all(query => [ id => \@project_ids ]);
1247     %projects_by_id = map { $_->id => $_ } @$projects;
1248   }
1249
1250   if ($projects_by_id{$form->{"globalproject_id"}}) {
1251     $form->{globalprojectnumber} = $projects_by_id{$form->{"globalproject_id"}}->projectnumber;
1252     $form->{globalprojectdescription} = $projects_by_id{$form->{"globalproject_id"}}->description;
1253
1254     for (@{ $projects_by_id{$form->{"globalproject_id"}}->cvars_by_config }) {
1255       $form->{"project_cvar_" . $_->config->name} = $_->value_as_text;
1256     }
1257   }
1258
1259   $form->{discount} = [];
1260
1261   # get some values of parts from db on store them in extra array,
1262   # so that they can be sorted in later
1263   my %prepared_template_arrays = IC->prepare_parts_for_printing(myconfig => $myconfig, form => $form);
1264   my @prepared_arrays          = keys %prepared_template_arrays;
1265
1266   $form->{TEMPLATE_ARRAYS} = { };
1267
1268   my $ic_cvar_configs = CVar->get_configs(module => 'IC');
1269   my $project_cvar_configs = CVar->get_configs(module => 'Projects');
1270
1271   my @arrays =
1272     qw(runningnumber number description longdescription qty qty_nofmt ship ship_nofmt unit bin
1273        partnotes serialnumber reqdate sellprice sellprice_nofmt listprice listprice_nofmt netprice netprice_nofmt
1274        discount discount_nofmt p_discount discount_sub discount_sub_nofmt nodiscount_sub nodiscount_sub_nofmt
1275        linetotal linetotal_nofmt nodiscount_linetotal nodiscount_linetotal_nofmt tax_rate projectnumber projectdescription
1276        price_factor price_factor_name partsgroup weight weight_nofmt lineweight lineweight_nofmt);
1277
1278   push @arrays, map { "ic_cvar_$_->{name}" } @{ $ic_cvar_configs };
1279   push @arrays, map { "project_cvar_$_->{name}" } @{ $project_cvar_configs };
1280
1281   my @tax_arrays = qw(taxbase tax taxdescription taxrate taxnumber);
1282
1283   map { $form->{TEMPLATE_ARRAYS}->{$_} = [] } (@arrays, @tax_arrays, @prepared_arrays);
1284
1285   my $totalweight = 0;
1286   my $sameitem = "";
1287   foreach $item (sort { $a->[1] cmp $b->[1] } @partsgroup) {
1288     $i = $item->[0];
1289
1290     if ($item->[1] ne $sameitem) {
1291       push(@{ $form->{TEMPLATE_ARRAYS}->{entry_type}  }, 'partsgroup');
1292       push(@{ $form->{TEMPLATE_ARRAYS}->{description} }, qq|$item->[1]|);
1293       $sameitem = $item->[1];
1294
1295       map({ push(@{ $form->{TEMPLATE_ARRAYS}->{$_} }, "") } grep({ $_ ne "description" } (@arrays, @prepared_arrays)));
1296     }
1297
1298     $form->{"qty_$i"} = $form->parse_amount($myconfig, $form->{"qty_$i"});
1299
1300     if ($form->{"id_$i"} != 0) {
1301
1302       # add number, description and qty to $form->{number}, ....
1303
1304       if ($form->{"subtotal_$i"} && !$subtotal_header) {
1305         $subtotal_header = $i;
1306         $position = int($position);
1307         $subposition = 0;
1308         $position++;
1309       } elsif ($subtotal_header) {
1310         $subposition += 1;
1311         $position = int($position);
1312         $position = $position.".".$subposition;
1313       } else {
1314         $position = int($position);
1315         $position++;
1316       }
1317
1318       my $price_factor = $price_factors{$form->{"price_factor_id_$i"}} || { 'factor' => 1 };
1319
1320       push(@{ $form->{TEMPLATE_ARRAYS}->{$_} },                $prepared_template_arrays{$_}[$i - 1]) for @prepared_arrays;
1321
1322       push @{ $form->{TEMPLATE_ARRAYS}->{entry_type} },        'normal';
1323       push @{ $form->{TEMPLATE_ARRAYS}->{runningnumber} },     $position;
1324       push @{ $form->{TEMPLATE_ARRAYS}->{number} },            $form->{"partnumber_$i"};
1325       push @{ $form->{TEMPLATE_ARRAYS}->{description} },       $form->{"description_$i"};
1326       push @{ $form->{TEMPLATE_ARRAYS}->{longdescription} },   $form->{"longdescription_$i"};
1327       push @{ $form->{TEMPLATE_ARRAYS}->{qty} },               $form->format_amount($myconfig, $form->{"qty_$i"});
1328       push @{ $form->{TEMPLATE_ARRAYS}->{qty_nofmt} },         $form->{"qty_$i"};
1329       push @{ $form->{TEMPLATE_ARRAYS}->{ship} },              $form->format_amount($myconfig, $form->{"ship_$i"});
1330       push @{ $form->{TEMPLATE_ARRAYS}->{ship_nofmt} },        $form->{"ship_$i"};
1331       push @{ $form->{TEMPLATE_ARRAYS}->{unit} },              $form->{"unit_$i"};
1332       push @{ $form->{TEMPLATE_ARRAYS}->{bin} },               $form->{"bin_$i"};
1333       push @{ $form->{TEMPLATE_ARRAYS}->{partnotes} },         $form->{"partnotes_$i"};
1334       push @{ $form->{TEMPLATE_ARRAYS}->{serialnumber} },      $form->{"serialnumber_$i"};
1335       push @{ $form->{TEMPLATE_ARRAYS}->{reqdate} },           $form->{"reqdate_$i"};
1336       push @{ $form->{TEMPLATE_ARRAYS}->{sellprice} },         $form->{"sellprice_$i"};
1337       push @{ $form->{TEMPLATE_ARRAYS}->{sellprice_nofmt} },   $form->parse_amount($myconfig, $form->{"sellprice_$i"});
1338       push @{ $form->{TEMPLATE_ARRAYS}->{listprice} },         $form->format_amount($myconfig, $form->{"listprice_$i"}, 2);
1339       push @{ $form->{TEMPLATE_ARRAYS}->{listprice_nofmt} },   $form->{"listprice_$i"};
1340       push @{ $form->{TEMPLATE_ARRAYS}->{price_factor} },      $price_factor->{formatted_factor};
1341       push @{ $form->{TEMPLATE_ARRAYS}->{price_factor_name} }, $price_factor->{description};
1342       push @{ $form->{TEMPLATE_ARRAYS}->{partsgroup} },        $form->{"partsgroup_$i"};
1343
1344       my $sellprice     = $form->parse_amount($myconfig, $form->{"sellprice_$i"});
1345       my ($dec)         = ($sellprice =~ /\.(\d+)/);
1346       my $decimalplaces = max 2, length($dec);
1347
1348       my $parsed_discount            = $form->parse_amount($myconfig, $form->{"discount_$i"});
1349
1350       my $linetotal_exact            = $form->{"qty_$i"} * $sellprice * (100 - $parsed_discount) / 100 / $price_factor->{factor};
1351       my $linetotal                  = $form->round_amount($linetotal_exact, 2);
1352
1353       my $nodiscount_exact_linetotal = $form->{"qty_$i"} * $sellprice                                  / $price_factor->{factor};
1354       my $nodiscount_linetotal       = $form->round_amount($nodiscount_exact_linetotal,2);
1355
1356       my $discount                   = $nodiscount_linetotal - $linetotal; # is always rounded because $nodiscount_linetotal and $linetotal are rounded
1357
1358       my $discount_round_error       = $discount + ($linetotal_exact - $nodiscount_exact_linetotal); # not used
1359
1360       $form->{"netprice_$i"}   = $form->round_amount($form->{"qty_$i"} ? ($linetotal / $form->{"qty_$i"}) : 0, $decimalplaces);
1361
1362       push @{ $form->{TEMPLATE_ARRAYS}->{netprice} },       ($form->{"netprice_$i"} != 0) ? $form->format_amount($myconfig, $form->{"netprice_$i"}, $decimalplaces) : '';
1363       push @{ $form->{TEMPLATE_ARRAYS}->{netprice_nofmt} }, ($form->{"netprice_$i"} != 0) ? $form->{"netprice_$i"} : '';
1364
1365       $linetotal = ($linetotal != 0) ? $linetotal : '';
1366
1367       push @{ $form->{TEMPLATE_ARRAYS}->{discount} },       ($discount != 0) ? $form->format_amount($myconfig, $discount * -1, 2) : '';
1368       push @{ $form->{TEMPLATE_ARRAYS}->{discount_nofmt} }, ($discount != 0) ? $discount * -1 : '';
1369       push @{ $form->{TEMPLATE_ARRAYS}->{p_discount} },     $form->{"discount_$i"};
1370
1371       $form->{ordtotal}         += $linetotal;
1372       $form->{nodiscount_total} += $nodiscount_linetotal;
1373       $form->{discount_total}   += $discount;
1374
1375       if ($subtotal_header) {
1376         $discount_subtotal   += $linetotal;
1377         $nodiscount_subtotal += $nodiscount_linetotal;
1378       }
1379
1380       if ($form->{"subtotal_$i"} && $subtotal_header && ($subtotal_header != $i)) {
1381         push @{ $form->{TEMPLATE_ARRAYS}->{discount_sub} },         $form->format_amount($myconfig, $discount_subtotal,   2);
1382         push @{ $form->{TEMPLATE_ARRAYS}->{discount_sub_nofmt} },   $discount_subtotal;
1383         push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_sub} },       $form->format_amount($myconfig, $nodiscount_subtotal, 2);
1384         push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_sub_nofmt} }, $nodiscount_subtotal;
1385
1386         $discount_subtotal   = 0;
1387         $nodiscount_subtotal = 0;
1388         $subtotal_header     = 0;
1389
1390       } else {
1391         push @{ $form->{TEMPLATE_ARRAYS}->{$_} }, "" for qw(discount_sub nodiscount_sub discount_sub_nofmt nodiscount_sub_nofmt);
1392       }
1393
1394       if (!$form->{"discount_$i"}) {
1395         $nodiscount += $linetotal;
1396       }
1397
1398       my $project = $projects_by_id{$form->{"project_id_$i"}} || SL::DB::Project->new;
1399
1400       push @{ $form->{TEMPLATE_ARRAYS}->{linetotal} },                  $form->format_amount($myconfig, $linetotal, 2);
1401       push @{ $form->{TEMPLATE_ARRAYS}->{linetotal_nofmt} },            $linetotal_exact;
1402       push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_linetotal} },       $form->format_amount($myconfig, $nodiscount_linetotal, 2);
1403       push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_linetotal_nofmt} }, $nodiscount_linetotal;
1404       push @{ $form->{TEMPLATE_ARRAYS}->{projectnumber} },              $project->projectnumber;
1405       push @{ $form->{TEMPLATE_ARRAYS}->{projectdescription} },         $project->description;
1406
1407       my $lineweight = $form->{"qty_$i"} * $form->{"weight_$i"};
1408       $totalweight += $lineweight;
1409       push @{ $form->{TEMPLATE_ARRAYS}->{weight} },            $form->format_amount($myconfig, $form->{"weight_$i"}, 3);
1410       push @{ $form->{TEMPLATE_ARRAYS}->{weight_nofmt} },      $form->{"weight_$i"};
1411       push @{ $form->{TEMPLATE_ARRAYS}->{lineweight} },        $form->format_amount($myconfig, $lineweight, 3);
1412       push @{ $form->{TEMPLATE_ARRAYS}->{lineweight_nofmt} },  $lineweight;
1413
1414       my ($taxamount, $taxbase);
1415       my $taxrate = 0;
1416
1417       map { $taxrate += $form->{"${_}_rate"} } split(/ /, $form->{"taxaccounts_$i"});
1418
1419       if ($form->{taxincluded}) {
1420
1421         # calculate tax
1422         $taxamount = $linetotal * $taxrate / (1 + $taxrate);
1423         $taxbase = $linetotal / (1 + $taxrate);
1424       } else {
1425         $taxamount = $linetotal * $taxrate;
1426         $taxbase   = $linetotal;
1427       }
1428
1429       if ($taxamount != 0) {
1430         foreach my $accno (split / /, $form->{"taxaccounts_$i"}) {
1431           $taxaccounts{$accno} += $taxamount * $form->{"${accno}_rate"} / $taxrate;
1432           $taxbase{$accno}     += $taxbase;
1433         }
1434       }
1435
1436       $tax_rate = $taxrate * 100;
1437       push(@{ $form->{TEMPLATE_ARRAYS}->{tax_rate} }, qq|$tax_rate|);
1438
1439       if ($form->{"assembly_$i"}) {
1440         $sameitem = "";
1441
1442         # get parts and push them onto the stack
1443         my $sortorder = "";
1444         if ($form->{groupitems}) {
1445           $sortorder = qq|ORDER BY pg.partsgroup, a.oid|;
1446         } else {
1447           $sortorder = qq|ORDER BY a.oid|;
1448         }
1449
1450         $query = qq|SELECT p.partnumber, p.description, p.unit, a.qty, | .
1451                  qq|pg.partsgroup | .
1452                  qq|FROM assembly a | .
1453                  qq|  JOIN parts p ON (a.parts_id = p.id) | .
1454                  qq|    LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id) | .
1455                  qq|    WHERE a.bom = '1' | .
1456                  qq|    AND a.id = ? | . $sortorder;
1457         @values = ($form->{"id_$i"});
1458         $sth = $dbh->prepare($query);
1459         $sth->execute(@values) || $form->dberror($query);
1460
1461         while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
1462           if ($form->{groupitems} && $ref->{partsgroup} ne $sameitem) {
1463             map({ push(@{ $form->{TEMPLATE_ARRAYS}->{$_} }, "") } grep({ $_ ne "description" } (@arrays, @prepared_arrays)));
1464             $sameitem = ($ref->{partsgroup}) ? $ref->{partsgroup} : "--";
1465             push(@{ $form->{TEMPLATE_ARRAYS}->{entry_type}  }, 'assembly-item-partsgroup');
1466             push(@{ $form->{TEMPLATE_ARRAYS}->{description} }, $sameitem);
1467           }
1468
1469           push(@{ $form->{TEMPLATE_ARRAYS}->{entry_type}  }, 'assembly-item');
1470           push(@{ $form->{TEMPLATE_ARRAYS}->{description} }, $form->format_amount($myconfig, $ref->{qty} * $form->{"qty_$i"}) . qq|, $ref->{partnumber}, $ref->{description}|);
1471           map({ push(@{ $form->{TEMPLATE_ARRAYS}->{$_} }, "") } grep({ $_ ne "description" } (@arrays, @prepared_arrays)));
1472         }
1473         $sth->finish;
1474       }
1475
1476       CVar->get_non_editable_ic_cvars(form               => $form,
1477                                       dbh                => $dbh,
1478                                       row                => $i,
1479                                       sub_module         => 'orderitems',
1480                                       may_converted_from => ['orderitems', 'invoice']);
1481
1482       push @{ $form->{TEMPLATE_ARRAYS}->{"ic_cvar_$_->{name}"} },
1483         CVar->format_to_template(CVar->parse($form->{"ic_cvar_$_->{name}_$i"}, $_), $_)
1484           for @{ $ic_cvar_configs };
1485
1486       push @{ $form->{TEMPLATE_ARRAYS}->{"project_cvar_" . $_->config->name} }, $_->value_as_text for @{ $project->cvars_by_config };
1487     }
1488   }
1489
1490   $form->{totalweight}       = $form->format_amount($myconfig, $totalweight, 3);
1491   $form->{totalweight_nofmt} = $totalweight;
1492   my $defaults = AM->get_defaults();
1493   $form->{weightunit}        = $defaults->{weightunit};
1494
1495   my $tax = 0;
1496   foreach $item (sort keys %taxaccounts) {
1497     $tax += $taxamount = $form->round_amount($taxaccounts{$item}, 2);
1498
1499     push(@{ $form->{TEMPLATE_ARRAYS}->{taxbase} },        $form->format_amount($myconfig, $taxbase{$item}, 2));
1500     push(@{ $form->{TEMPLATE_ARRAYS}->{taxbase_nofmt} },  $taxbase{$item});
1501     push(@{ $form->{TEMPLATE_ARRAYS}->{tax} },            $form->format_amount($myconfig, $taxamount,      2));
1502     push(@{ $form->{TEMPLATE_ARRAYS}->{tax_nofmt} },      $taxamount);
1503     push(@{ $form->{TEMPLATE_ARRAYS}->{taxrate} },        $form->format_amount($myconfig, $form->{"${item}_rate"} * 100));
1504     push(@{ $form->{TEMPLATE_ARRAYS}->{taxrate_nofmt} },  $form->{"${item}_rate"} * 100);
1505     push(@{ $form->{TEMPLATE_ARRAYS}->{taxnumber} },      $form->{"${item}_taxnumber"});
1506
1507     my $tax_obj     = SL::DB::Manager::Tax->find_by(taxnumber => $form->{"${item}_taxnumber"});
1508     my $description = $tax_obj ? $tax_obj->translated_attribute('taxdescription',  $form->{language_id}, 0) : '';
1509     push(@{ $form->{TEMPLATE_ARRAYS}->{taxdescription} }, $description . q{ } . 100 * $form->{"${item}_rate"} . q{%});
1510   }
1511
1512   $form->{nodiscount_subtotal} = $form->format_amount($myconfig, $form->{nodiscount_total}, 2);
1513   $form->{discount_total}      = $form->format_amount($myconfig, $form->{discount_total}, 2);
1514   $form->{nodiscount}          = $form->format_amount($myconfig, $nodiscount, 2);
1515   $form->{yesdiscount}         = $form->format_amount($myconfig, $form->{nodiscount_total} - $nodiscount, 2);
1516
1517   if($form->{taxincluded}) {
1518     $form->{subtotal}       = $form->format_amount($myconfig, $form->{ordtotal} - $tax, 2);
1519     $form->{subtotal_nofmt} = $form->{ordtotal} - $tax;
1520   } else {
1521     $form->{subtotal}       = $form->format_amount($myconfig, $form->{ordtotal}, 2);
1522     $form->{subtotal_nofmt} = $form->{ordtotal};
1523   }
1524
1525   $form->{ordtotal} = ($form->{taxincluded}) ? $form->{ordtotal} : $form->{ordtotal} + $tax;
1526
1527   # format amounts
1528   $form->{quototal} = $form->{ordtotal} = $form->format_amount($myconfig, $form->{ordtotal}, 2);
1529
1530   if ($form->{type} =~ /_quotation/) {
1531     $form->set_payment_options($myconfig, $form->{quodate});
1532   } else {
1533     $form->set_payment_options($myconfig, $form->{orddate});
1534   }
1535
1536   $form->{username} = $myconfig->{name};
1537
1538   $dbh->disconnect;
1539
1540   $form->{delivery_term} = SL::DB::Manager::DeliveryTerm->find_by(id => $form->{delivery_term_id} || undef);
1541   $form->{delivery_term}->description_long($form->{delivery_term}->translated_attribute('description_long', $form->{language_id})) if $form->{delivery_term} && $form->{language_id};
1542
1543   $form->{order} = SL::DB::Manager::Order->find_by(id => $form->{id}) if $form->{id};
1544
1545   $main::lxdebug->leave_sub();
1546 }
1547
1548 sub project_description {
1549   $main::lxdebug->enter_sub();
1550
1551   my ($self, $dbh, $id) = @_;
1552
1553   my $query = qq|SELECT description FROM project WHERE id = ?|;
1554   my ($value) = selectrow_query($main::form, $dbh, $query, $id);
1555
1556   $main::lxdebug->leave_sub();
1557
1558   return $value;
1559 }
1560
1561 1;
1562
1563 __END__
1564
1565 =head1 NAME
1566
1567 OE.pm - Order entry module
1568
1569 =head1 DESCRIPTION
1570
1571 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>.
1572
1573 =head1 FUNCTIONS
1574
1575 =over 4
1576
1577 =item retrieve_simple PARAMS
1578
1579 simple OE retrieval by id. does not look up customer, vendor, units or any other stuff. only oe and orderitems.
1580
1581   my $order = retrieve_simple(id => 2);
1582
1583   $order => {
1584     %_OE_CONTENT,
1585     orderitems => [
1586       %_ORDERITEM_ROW_1,
1587       %_ORDERITEM_ROW_2,
1588       ...
1589     ]
1590   }
1591
1592 =back
1593
1594 =cut