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