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