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