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