Versandort und Steuersatz (Zone) im Auftragsbericht filter- und anzeigbar
[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 = ?,
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->{globalproject_id}), conv_i($form->{employee_id}),
539              conv_i($form->{salesman_id}), conv_i($form->{cp_id}),
540              $form->{transaction_description},
541              $form->{marge_total} * 1, $form->{marge_percent} * 1,
542              conv_i($form->{id}));
543   do_query($form, $dbh, $query, @values);
544
545   $form->{ordtotal} = $amount;
546
547   # add shipto
548   $form->{name} = $form->{ $form->{vc} };
549   $form->{name} =~ s/--\Q$form->{"$form->{vc}_id"}\E//;
550
551   if (!$form->{shipto_id}) {
552     $form->add_shipto($dbh, $form->{id}, "OE");
553   }
554
555   # save printed, emailed, queued
556   $form->save_status($dbh);
557
558   # Link this record to the records it was created from.
559   $form->{convert_from_oe_ids} =~ s/^\s+//;
560   $form->{convert_from_oe_ids} =~ s/\s+$//;
561   my @convert_from_oe_ids      =  split m/\s+/, $form->{convert_from_oe_ids};
562   delete $form->{convert_from_oe_ids};
563
564   if (scalar @convert_from_oe_ids) {
565     RecordLinks->create_links('dbh'        => $dbh,
566                               'mode'       => 'ids',
567                               'from_table' => 'oe',
568                               'from_ids'   => \@convert_from_oe_ids,
569                               'to_table'   => 'oe',
570                               'to_id'      => $form->{id},
571       );
572
573     $self->_close_quotations_rfqs('dbh'     => $dbh,
574                                   'from_id' => \@convert_from_oe_ids,
575                                   'to_id'   => $form->{id});
576   }
577
578   if (($form->{currency} ne $form->{defaultcurrency}) && !$exchangerate) {
579     if ($form->{vc} eq 'customer') {
580       $form->update_exchangerate($dbh, $form->{currency}, $form->{transdate}, $form->{exchangerate}, 0);
581     }
582     if ($form->{vc} eq 'vendor') {
583       $form->update_exchangerate($dbh, $form->{currency}, $form->{transdate}, 0, $form->{exchangerate});
584     }
585   }
586
587   $form->{saved_xyznumber} = $form->{$form->{type} =~ /_quotation$/ ?
588                                        "quonumber" : "ordnumber"};
589
590   Common::webdav_folder($form);
591
592   my $rc = $dbh->commit;
593
594   $self->save_periodic_invoices_config(dbh         => $dbh,
595                                        oe_id       => $form->{id},
596                                        config_yaml => $form->{periodic_invoices_config})
597     if ($form->{type} eq 'sales_order');
598
599   $main::lxdebug->leave_sub();
600
601   return $rc;
602 }
603
604 sub save_periodic_invoices_config {
605   my ($self, %params) = @_;
606
607   return if !$params{oe_id};
608
609   my $config = $params{config_yaml} ? YAML::Load($params{config_yaml}) : undef;
610   return if 'HASH' ne ref $config;
611
612   my $obj  = SL::DB::Manager::PeriodicInvoicesConfig->find_by(oe_id => $params{oe_id})
613           || SL::DB::PeriodicInvoicesConfig->new(oe_id => $params{oe_id});
614   $obj->update_attributes(%{ $config });
615 }
616
617 sub load_periodic_invoice_config {
618   my $self = shift;
619   my $form = shift;
620
621   delete $form->{periodic_invoices_config};
622
623   if ($form->{id}) {
624     my $config_obj = SL::DB::Manager::PeriodicInvoicesConfig->find_by(oe_id => $form->{id});
625
626     if ($config_obj) {
627       my $config = { map { $_ => $config_obj->$_ } qw(active terminated periodicity start_date_as_date end_date_as_date extend_automatically_by ar_chart_id
628                                                       print printer_id copies) };
629       $form->{periodic_invoices_config} = YAML::Dump($config);
630     }
631   }
632 }
633
634 sub _close_quotations_rfqs {
635   $main::lxdebug->enter_sub();
636
637   my $self     = shift;
638   my %params   = @_;
639
640   Common::check_params(\%params, qw(from_id to_id));
641
642   my $myconfig = \%main::myconfig;
643   my $form     = $main::form;
644
645   my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
646
647   my $query    = qq|SELECT quotation FROM oe WHERE id = ?|;
648   my $sth      = prepare_query($form, $dbh, $query);
649
650   do_statement($form, $sth, $query, conv_i($params{to_id}));
651
652   my ($quotation) = $sth->fetchrow_array();
653
654   if ($quotation) {
655     $main::lxdebug->leave_sub();
656     return;
657   }
658
659   my @close_ids;
660
661   foreach my $from_id (@{ $params{from_id} }) {
662     $from_id = conv_i($from_id);
663     do_statement($form, $sth, $query, $from_id);
664     ($quotation) = $sth->fetchrow_array();
665     push @close_ids, $from_id if ($quotation);
666   }
667
668   $sth->finish();
669
670   if (scalar @close_ids) {
671     $query = qq|UPDATE oe SET closed = TRUE WHERE id IN (| . join(', ', ('?') x scalar @close_ids) . qq|)|;
672     do_query($form, $dbh, $query, @close_ids);
673
674     $dbh->commit() unless ($params{dbh});
675   }
676
677   $main::lxdebug->leave_sub();
678 }
679
680 sub delete {
681   $main::lxdebug->enter_sub();
682
683   my ($self, $myconfig, $form) = @_;
684
685   my $rc = SL::DB::Order->new->db->with_transaction(sub {
686     my @spoolfiles = grep { $_ } map { $_->spoolfile } @{ SL::DB::Manager::Status->get_all(where => [ trans_id => $form->{id} ]) };
687
688     SL::DB::Order->new(id => $form->{id})->delete;
689
690     my $spool = $::lx_office_conf{paths}->{spool};
691     unlink map { "$spool/$_" } @spoolfiles if $spool;
692
693     1;
694   });
695
696   $main::lxdebug->leave_sub();
697
698   return $rc;
699 }
700
701 sub retrieve {
702   $main::lxdebug->enter_sub();
703
704   my ($self, $myconfig, $form) = @_;
705
706   # connect to database
707   my $dbh = $form->get_standard_dbh;
708
709   my ($query, $query_add, @values, @ids, $sth);
710
711   # translate the ids (given by id_# and trans_id_#) into one array of ids, so we can join them later
712   map {
713     push @ids, $form->{"trans_id_$_"}
714       if ($form->{"multi_id_$_"} and $form->{"trans_id_$_"})
715   } (1 .. $form->{"rowcount"});
716
717   if ($form->{rowcount} && scalar @ids) {
718     $form->{convert_from_oe_ids} = join ' ', @ids;
719   }
720
721   # if called in multi id mode, and still only got one id, switch back to single id
722   if ($form->{"rowcount"} and $#ids == 0) {
723     $form->{"id"} = $ids[0];
724     undef @ids;
725   }
726
727   # and remember for the rest of the function
728   my $is_collective_order = scalar @ids;
729
730   if (!$form->{id}) {
731     my $wday         = (localtime(time))[6];
732     my $next_workday = $wday == 5 ? 3 : $wday == 6 ? 2 : 1;
733     $query_add       = qq|, current_date AS transdate, date(current_date + interval '${next_workday} days') AS reqdate|;
734   }
735
736   # get default accounts
737   $query = qq|SELECT (SELECT c.accno FROM chart c WHERE d.inventory_accno_id = c.id) AS inventory_accno,
738                      (SELECT c.accno FROM chart c WHERE d.income_accno_id    = c.id) AS income_accno,
739                      (SELECT c.accno FROM chart c WHERE d.expense_accno_id   = c.id) AS expense_accno,
740                      (SELECT c.accno FROM chart c WHERE d.fxgain_accno_id    = c.id) AS fxgain_accno,
741                      (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id    = c.id) AS fxloss_accno
742               $query_add
743               FROM defaults d|;
744   my $ref = selectfirst_hashref_query($form, $dbh, $query);
745   map { $form->{$_} = $ref->{$_} } keys %$ref;
746
747   $form->{currency} = $form->get_default_currency($myconfig);
748
749   # set reqdate if this is an invoice->order conversion. If someone knows a better check to ensure
750   # we come from invoices, feel free.
751   $form->{reqdate} = $form->{deliverydate}
752     if (    $form->{deliverydate}
753         and $form->{callback} =~ /action=ar_transactions/);
754
755   my $vc = $form->{vc} eq "customer" ? "customer" : "vendor";
756
757   if ($form->{id} or @ids) {
758
759     # retrieve order for single id
760     # NOTE: this query is intended to fetch all information only ONCE.
761     # so if any of these infos is important (or even different) for any item,
762     # it will be killed out and then has to be fetched from the item scope query further down
763     $query =
764       qq|SELECT o.cp_id, o.ordnumber, o.transdate, o.reqdate,
765            o.taxincluded, o.shippingpoint, o.shipvia, o.notes, o.intnotes,
766            (SELECT cu.name FROM currencies cu WHERE cu.id=o.currency_id) AS currency, e.name AS employee, o.employee_id, o.salesman_id,
767            o.${vc}_id, cv.name AS ${vc}, o.amount AS invtotal,
768            o.closed, o.reqdate, o.quonumber, o.department_id, o.cusordnumber,
769            d.description AS department, o.payment_id, o.language_id, o.taxzone_id,
770            o.delivery_customer_id, o.delivery_vendor_id, o.proforma, o.shipto_id,
771            o.globalproject_id, o.delivered, o.transaction_description
772          FROM oe o
773          JOIN ${vc} cv ON (o.${vc}_id = cv.id)
774          LEFT JOIN employee e ON (o.employee_id = e.id)
775          LEFT JOIN department d ON (o.department_id = d.id) | .
776         ($form->{id}
777          ? "WHERE o.id = ?"
778          : "WHERE o.id IN (" . join(', ', map("? ", @ids)) . ")"
779         );
780     @values = $form->{id} ? ($form->{id}) : @ids;
781     $sth = prepare_execute_query($form, $dbh, $query, @values);
782
783     $ref = $sth->fetchrow_hashref("NAME_lc");
784
785     if ($ref) {
786       map { $form->{$_} = $ref->{$_} } keys %$ref;
787
788       $form->{saved_xyznumber} = $form->{$form->{type} =~ /_quotation$/ ? "quonumber" : "ordnumber"};
789
790       # set all entries for multiple ids blank that yield different information
791       while ($ref = $sth->fetchrow_hashref("NAME_lc")) {
792         map { $form->{$_} = '' if ($ref->{$_} ne $form->{$_}) } keys %$ref;
793       }
794     }
795
796     # if not given, fill transdate with current_date
797     $form->{transdate} = $form->current_date($myconfig)
798       unless $form->{transdate};
799
800     $sth->finish;
801
802     if ($form->{delivery_customer_id}) {
803       $query = qq|SELECT name FROM customer WHERE id = ?|;
804       ($form->{delivery_customer_string}) = selectrow_query($form, $dbh, $query, $form->{delivery_customer_id});
805     }
806
807     if ($form->{delivery_vendor_id}) {
808       $query = qq|SELECT name FROM customer WHERE id = ?|;
809       ($form->{delivery_vendor_string}) = selectrow_query($form, $dbh, $query, $form->{delivery_vendor_id});
810     }
811
812     # shipto and pinted/mailed/queued status makes only sense for single id retrieve
813     if (!@ids) {
814       $query = qq|SELECT s.* FROM shipto s WHERE s.trans_id = ? AND s.module = 'OE'|;
815       $sth = prepare_execute_query($form, $dbh, $query, $form->{id});
816
817       $ref = $sth->fetchrow_hashref("NAME_lc");
818       delete($ref->{id});
819       map { $form->{$_} = $ref->{$_} } keys %$ref;
820       $sth->finish;
821
822       # get printed, emailed and queued
823       $query = qq|SELECT s.printed, s.emailed, s.spoolfile, s.formname FROM status s WHERE s.trans_id = ?|;
824       $sth = prepare_execute_query($form, $dbh, $query, $form->{id});
825
826       while ($ref = $sth->fetchrow_hashref("NAME_lc")) {
827         $form->{printed} .= "$ref->{formname} " if $ref->{printed};
828         $form->{emailed} .= "$ref->{formname} " if $ref->{emailed};
829         $form->{queued}  .= "$ref->{formname} $ref->{spoolfile} " if $ref->{spoolfile};
830       }
831       $sth->finish;
832       map { $form->{$_} =~ s/ +$//g } qw(printed emailed queued);
833     }    # if !@ids
834
835     my $transdate = $form->{transdate} ? $dbh->quote($form->{transdate}) : "current_date";
836
837     $form->{taxzone_id} = 0 unless ($form->{taxzone_id});
838
839     # retrieve individual items
840     # this query looks up all information about the items
841     # stuff different from the whole will not be overwritten, but saved with a suffix.
842     $query =
843       qq|SELECT o.id AS orderitems_id,
844            c1.accno AS inventory_accno, c1.new_chart_id AS inventory_new_chart, date($transdate) - c1.valid_from as inventory_valid,
845            c2.accno AS income_accno,    c2.new_chart_id AS income_new_chart,    date($transdate) - c2.valid_from as income_valid,
846            c3.accno AS expense_accno,   c3.new_chart_id AS expense_new_chart,   date($transdate) - c3.valid_from as expense_valid,
847            oe.ordnumber AS ordnumber_oe, oe.transdate AS transdate_oe, oe.cusordnumber AS cusordnumber_oe,
848            p.partnumber, p.assembly, p.listprice, o.description, o.qty,
849            o.sellprice, o.parts_id AS id, o.unit, o.discount, p.notes AS partnotes, p.inventory_accno_id AS part_inventory_accno_id,
850            o.reqdate, o.project_id, o.serialnumber, o.ship, o.lastcost,
851            o.ordnumber, o.transdate, o.cusordnumber, o.subtotal, o.longdescription,
852            o.price_factor_id, o.price_factor, o.marge_price_factor,
853            pr.projectnumber, p.formel,
854            pg.partsgroup, o.pricegroup_id, (SELECT pricegroup FROM pricegroup WHERE id=o.pricegroup_id) as pricegroup
855          FROM orderitems o
856          JOIN parts p ON (o.parts_id = p.id)
857          JOIN oe ON (o.trans_id = oe.id)
858          LEFT JOIN chart c1 ON ((SELECT inventory_accno_id                   FROM buchungsgruppen WHERE id=p.buchungsgruppen_id) = c1.id)
859          LEFT JOIN chart c2 ON ((SELECT income_accno_id_$form->{taxzone_id}  FROM buchungsgruppen WHERE id=p.buchungsgruppen_id) = c2.id)
860          LEFT JOIN chart c3 ON ((SELECT expense_accno_id_$form->{taxzone_id} FROM buchungsgruppen WHERE id=p.buchungsgruppen_id) = c3.id)
861          LEFT JOIN project pr ON (o.project_id = pr.id)
862          LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id) | .
863       ($form->{id}
864        ? qq|WHERE o.trans_id = ?|
865        : qq|WHERE o.trans_id IN (| . join(", ", map("?", @ids)) . qq|)|) .
866       qq|ORDER BY o.oid|;
867
868     @ids = $form->{id} ? ($form->{id}) : @ids;
869     $sth = prepare_execute_query($form, $dbh, $query, @values);
870
871     while ($ref = $sth->fetchrow_hashref("NAME_lc")) {
872       # Retrieve custom variables.
873       my $cvars = CVar->get_custom_variables(dbh        => $dbh,
874                                              module     => 'IC',
875                                              sub_module => 'orderitems',
876                                              trans_id   => $ref->{orderitems_id},
877                                             );
878       map { $ref->{"ic_cvar_$_->{name}"} = $_->{value} } @{ $cvars };
879
880       # Handle accounts.
881       if (!$ref->{"part_inventory_accno_id"}) {
882         map({ delete($ref->{$_}); } qw(inventory_accno inventory_new_chart inventory_valid));
883       }
884       delete($ref->{"part_inventory_accno_id"});
885
886       # in collective order, copy global ordnumber, transdate, cusordnumber into item scope
887       #   unless already present there
888       # remove _oe entries afterwards
889       map { $ref->{$_} = $ref->{"${_}_oe"} if ($ref->{$_} eq '') }
890         qw|ordnumber transdate cusordnumber|
891         if (@ids);
892       map { delete $ref->{$_} } qw|ordnumber_oe transdate_oe cusordnumber_oe|;
893
894
895
896       while ($ref->{inventory_new_chart} && ($ref->{inventory_valid} >= 0)) {
897         my $query =
898           qq|SELECT accno AS inventory_accno, | .
899           qq|  new_chart_id AS inventory_new_chart, | .
900           qq|  date($transdate) - valid_from AS inventory_valid | .
901           qq|FROM chart WHERE id = $ref->{inventory_new_chart}|;
902         ($ref->{inventory_accno}, $ref->{inventory_new_chart},
903          $ref->{inventory_valid}) = selectrow_query($form, $dbh, $query);
904       }
905
906       while ($ref->{income_new_chart} && ($ref->{income_valid} >= 0)) {
907         my $query =
908           qq|SELECT accno AS income_accno, | .
909           qq|  new_chart_id AS income_new_chart, | .
910           qq|  date($transdate) - valid_from AS income_valid | .
911           qq|FROM chart WHERE id = $ref->{income_new_chart}|;
912         ($ref->{income_accno}, $ref->{income_new_chart},
913          $ref->{income_valid}) = selectrow_query($form, $dbh, $query);
914       }
915
916       while ($ref->{expense_new_chart} && ($ref->{expense_valid} >= 0)) {
917         my $query =
918           qq|SELECT accno AS expense_accno, | .
919           qq|  new_chart_id AS expense_new_chart, | .
920           qq|  date($transdate) - valid_from AS expense_valid | .
921           qq|FROM chart WHERE id = $ref->{expense_new_chart}|;
922         ($ref->{expense_accno}, $ref->{expense_new_chart},
923          $ref->{expense_valid}) = selectrow_query($form, $dbh, $query);
924       }
925
926       # delete orderitems_id in collective orders, so that they get cloned no matter what
927       delete $ref->{orderitems_id} if (@ids);
928
929       # get tax rates and description
930       my $accno_id = ($form->{vc} eq "customer") ? $ref->{income_accno} : $ref->{expense_accno};
931       $query =
932         qq|SELECT c.accno, t.taxdescription, t.rate, t.taxnumber | .
933         qq|FROM tax t LEFT JOIN chart c on (c.id = t.chart_id) | .
934         qq|WHERE t.id IN (SELECT tk.tax_id FROM taxkeys tk | .
935         qq|               WHERE tk.chart_id = (SELECT id FROM chart WHERE accno = ?) | .
936         qq|                 AND startdate <= $transdate ORDER BY startdate DESC LIMIT 1) | .
937         qq|ORDER BY c.accno|;
938       my $stw = prepare_execute_query($form, $dbh, $query, $accno_id);
939       $ref->{taxaccounts} = "";
940       my $i = 0;
941       while (my $ptr = $stw->fetchrow_hashref("NAME_lc")) {
942         if (($ptr->{accno} eq "") && ($ptr->{rate} == 0)) {
943           $i++;
944           $ptr->{accno} = $i;
945         }
946         $ref->{taxaccounts} .= "$ptr->{accno} ";
947         if (!($form->{taxaccounts} =~ /\Q$ptr->{accno}\E/)) {
948           $form->{"$ptr->{accno}_rate"}        = $ptr->{rate};
949           $form->{"$ptr->{accno}_description"} = $ptr->{taxdescription};
950           $form->{"$ptr->{accno}_taxnumber"}   = $ptr->{taxnumber};
951           $form->{taxaccounts} .= "$ptr->{accno} ";
952         }
953
954       }
955
956       chop $ref->{taxaccounts};
957
958       push @{ $form->{form_details} }, $ref;
959       $stw->finish;
960     }
961     $sth->finish;
962
963   } else {
964
965     # get last name used
966     $form->lastname_used($dbh, $myconfig, $form->{vc})
967       unless $form->{"$form->{vc}_id"};
968
969   }
970
971   $form->{exchangerate} = $form->get_exchangerate($dbh, $form->{currency}, $form->{transdate}, ($form->{vc} eq 'customer') ? "buy" : "sell");
972
973   Common::webdav_folder($form);
974
975   $self->load_periodic_invoice_config($form);
976
977   my $rc = $dbh->commit;
978
979   $main::lxdebug->leave_sub();
980
981   return $rc;
982 }
983
984 sub retrieve_simple {
985   $main::lxdebug->enter_sub();
986
987   my $self     = shift;
988   my %params   = @_;
989
990   Common::check_params(\%params, qw(id));
991
992   my $myconfig    = \%main::myconfig;
993   my $form        = $main::form;
994
995   my $dbh         = $params{dbh} || $form->get_standard_dbh($myconfig);
996
997   my $oe_query    = qq|SELECT * FROM oe         WHERE id = ?|;
998   my $oi_query    = qq|SELECT * FROM orderitems WHERE trans_id = ?|;
999
1000   my $order            = selectfirst_hashref_query($form, $dbh, $oe_query, conv_i($params{id}));
1001   $order->{orderitems} = selectall_hashref_query(  $form, $dbh, $oi_query, conv_i($params{id}));
1002
1003   $main::lxdebug->leave_sub();
1004
1005   return $order;
1006 }
1007
1008 sub order_details {
1009   $main::lxdebug->enter_sub();
1010
1011   my ($self, $myconfig, $form) = @_;
1012
1013   # connect to database
1014   my $dbh = $form->get_standard_dbh;
1015   my $query;
1016   my @values = ();
1017   my $sth;
1018   my $nodiscount;
1019   my $yesdiscount;
1020   my $nodiscount_subtotal = 0;
1021   my $discount_subtotal = 0;
1022   my $item;
1023   my $i;
1024   my @partsgroup = ();
1025   my $partsgroup;
1026   my $position = 0;
1027   my $subtotal_header = 0;
1028   my $subposition = 0;
1029   my %taxaccounts;
1030   my %taxbase;
1031   my $tax_rate;
1032   my $taxamount;
1033
1034   my (@project_ids, %projectnumbers, %projectdescriptions);
1035
1036   push(@project_ids, $form->{"globalproject_id"}) if ($form->{"globalproject_id"});
1037
1038   $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS',
1039                    'departments'   => 'ALL_DEPARTMENTS');
1040   my %price_factors;
1041
1042   foreach my $pfac (@{ $form->{ALL_PRICE_FACTORS} }) {
1043     $price_factors{$pfac->{id}}  = $pfac;
1044     $pfac->{factor}             *= 1;
1045     $pfac->{formatted_factor}    = $form->format_amount($myconfig, $pfac->{factor});
1046   }
1047
1048   # lookup department
1049   foreach my $dept (@{ $form->{ALL_DEPARTMENTS} }) {
1050     next unless $dept->{id} eq $form->{department_id};
1051     $form->{department} = $dept->{description};
1052     last;
1053   }
1054
1055   # sort items by partsgroup
1056   for $i (1 .. $form->{rowcount}) {
1057     $partsgroup = "";
1058     if ($form->{"partsgroup_$i"} && $form->{groupitems}) {
1059       $partsgroup = $form->{"partsgroup_$i"};
1060     }
1061     push @partsgroup, [$i, $partsgroup];
1062     push(@project_ids, $form->{"project_id_$i"}) if ($form->{"project_id_$i"});
1063   }
1064
1065   if (@project_ids) {
1066     $query = "SELECT id, projectnumber, description FROM project WHERE id IN (" .
1067       join(", ", map("?", @project_ids)) . ")";
1068     $sth = prepare_execute_query($form, $dbh, $query, @project_ids);
1069     while (my $ref = $sth->fetchrow_hashref()) {
1070       $projectnumbers{$ref->{id}} = $ref->{projectnumber};
1071       $projectdescriptions{$ref->{id}} = $ref->{description};
1072     }
1073     $sth->finish();
1074   }
1075
1076   $form->{"globalprojectnumber"} = $projectnumbers{$form->{"globalproject_id"}};
1077   $form->{"globalprojectdescription"} = $projectdescriptions{$form->{"globalproject_id"}};
1078
1079   $form->{discount} = [];
1080
1081   $form->{TEMPLATE_ARRAYS} = { };
1082   IC->prepare_parts_for_printing(myconfig => $myconfig, form => $form);
1083
1084   my $ic_cvar_configs = CVar->get_configs(module => 'IC');
1085
1086   my @arrays =
1087     qw(runningnumber number description longdescription qty ship unit bin
1088        partnotes serialnumber reqdate sellprice listprice netprice
1089        discount p_discount discount_sub nodiscount_sub
1090        linetotal  nodiscount_linetotal tax_rate projectnumber projectdescription
1091        price_factor price_factor_name partsgroup weight lineweight);
1092
1093   push @arrays, map { "ic_cvar_$_->{name}" } @{ $ic_cvar_configs };
1094
1095   my @tax_arrays = qw(taxbase tax taxdescription taxrate taxnumber);
1096
1097   map { $form->{TEMPLATE_ARRAYS}->{$_} = [] } (@arrays, @tax_arrays);
1098
1099   my $totalweight = 0;
1100   my $sameitem = "";
1101   foreach $item (sort { $a->[1] cmp $b->[1] } @partsgroup) {
1102     $i = $item->[0];
1103
1104     if ($item->[1] ne $sameitem) {
1105       push(@{ $form->{TEMPLATE_ARRAYS}->{description} }, qq|$item->[1]|);
1106       $sameitem = $item->[1];
1107
1108       map({ push(@{ $form->{TEMPLATE_ARRAYS}->{$_} }, "") } grep({ $_ ne "description" } @arrays));
1109     }
1110
1111     $form->{"qty_$i"} = $form->parse_amount($myconfig, $form->{"qty_$i"});
1112
1113     if ($form->{"id_$i"} != 0) {
1114
1115       # add number, description and qty to $form->{number}, ....
1116
1117       if ($form->{"subtotal_$i"} && !$subtotal_header) {
1118         $subtotal_header = $i;
1119         $position = int($position);
1120         $subposition = 0;
1121         $position++;
1122       } elsif ($subtotal_header) {
1123         $subposition += 1;
1124         $position = int($position);
1125         $position = $position.".".$subposition;
1126       } else {
1127         $position = int($position);
1128         $position++;
1129       }
1130
1131       my $price_factor = $price_factors{$form->{"price_factor_id_$i"}} || { 'factor' => 1 };
1132
1133       push @{ $form->{TEMPLATE_ARRAYS}->{runningnumber} },     $position;
1134       push @{ $form->{TEMPLATE_ARRAYS}->{number} },            $form->{"partnumber_$i"};
1135       push @{ $form->{TEMPLATE_ARRAYS}->{description} },       $form->{"description_$i"};
1136       push @{ $form->{TEMPLATE_ARRAYS}->{longdescription} },   $form->{"longdescription_$i"};
1137       push @{ $form->{TEMPLATE_ARRAYS}->{qty} },               $form->format_amount($myconfig, $form->{"qty_$i"});
1138       push @{ $form->{TEMPLATE_ARRAYS}->{qty_nofmt} },         $form->{"qty_$i"};
1139       push @{ $form->{TEMPLATE_ARRAYS}->{ship} },              $form->format_amount($myconfig, $form->{"ship_$i"});
1140       push @{ $form->{TEMPLATE_ARRAYS}->{ship_nofmt} },        $form->{"ship_$i"};
1141       push @{ $form->{TEMPLATE_ARRAYS}->{unit} },              $form->{"unit_$i"};
1142       push @{ $form->{TEMPLATE_ARRAYS}->{bin} },               $form->{"bin_$i"};
1143       push @{ $form->{TEMPLATE_ARRAYS}->{partnotes} },         $form->{"partnotes_$i"};
1144       push @{ $form->{TEMPLATE_ARRAYS}->{serialnumber} },      $form->{"serialnumber_$i"};
1145       push @{ $form->{TEMPLATE_ARRAYS}->{reqdate} },           $form->{"reqdate_$i"};
1146       push @{ $form->{TEMPLATE_ARRAYS}->{sellprice} },         $form->{"sellprice_$i"};
1147       push @{ $form->{TEMPLATE_ARRAYS}->{sellprice_nofmt} },   $form->parse_amount($myconfig, $form->{"sellprice_$i"});
1148       push @{ $form->{TEMPLATE_ARRAYS}->{listprice} },         $form->{"listprice_$i"};
1149       push @{ $form->{TEMPLATE_ARRAYS}->{price_factor} },      $price_factor->{formatted_factor};
1150       push @{ $form->{TEMPLATE_ARRAYS}->{price_factor_name} }, $price_factor->{description};
1151       push @{ $form->{TEMPLATE_ARRAYS}->{partsgroup} },        $form->{"partsgroup_$i"};
1152
1153       my $sellprice     = $form->parse_amount($myconfig, $form->{"sellprice_$i"});
1154       my ($dec)         = ($sellprice =~ /\.(\d+)/);
1155       my $decimalplaces = max 2, length($dec);
1156
1157       my $parsed_discount            = $form->parse_amount($myconfig, $form->{"discount_$i"});
1158
1159       my $linetotal_exact            = $form->{"qty_$i"} * $sellprice * (100 - $parsed_discount) / 100 / $price_factor->{factor};
1160       my $linetotal                  = $form->round_amount($linetotal_exact, 2);
1161
1162       my $nodiscount_exact_linetotal = $form->{"qty_$i"} * $sellprice                                  / $price_factor->{factor};
1163       my $nodiscount_linetotal       = $form->round_amount($nodiscount_exact_linetotal,2);
1164
1165       my $discount                   = $nodiscount_linetotal - $linetotal; # is always rounded because $nodiscount_linetotal and $linetotal are rounded
1166
1167       my $discount_round_error       = $discount + ($linetotal_exact - $nodiscount_exact_linetotal); # not used
1168
1169       $form->{"netprice_$i"}   = $form->round_amount($form->{"qty_$i"} ? ($linetotal / $form->{"qty_$i"}) : 0, 2);
1170
1171       push @{ $form->{TEMPLATE_ARRAYS}->{netprice} },       ($form->{"netprice_$i"} != 0) ? $form->format_amount($myconfig, $form->{"netprice_$i"}, $decimalplaces) : '';
1172       push @{ $form->{TEMPLATE_ARRAYS}->{netprice_nofmt} }, ($form->{"netprice_$i"} != 0) ? $form->{"netprice_$i"} : '';
1173
1174       $linetotal = ($linetotal != 0) ? $linetotal : '';
1175
1176       push @{ $form->{TEMPLATE_ARRAYS}->{discount} },       ($discount != 0) ? $form->format_amount($myconfig, $discount * -1, 2) : '';
1177       push @{ $form->{TEMPLATE_ARRAYS}->{discount_nofmt} }, ($discount != 0) ? $discount * -1 : '';
1178       push @{ $form->{TEMPLATE_ARRAYS}->{p_discount} },     $form->{"discount_$i"};
1179
1180       $form->{ordtotal}         += $linetotal;
1181       $form->{nodiscount_total} += $nodiscount_linetotal;
1182       $form->{discount_total}   += $discount;
1183
1184       if ($subtotal_header) {
1185         $discount_subtotal   += $linetotal;
1186         $nodiscount_subtotal += $nodiscount_linetotal;
1187       }
1188
1189       if ($form->{"subtotal_$i"} && $subtotal_header && ($subtotal_header != $i)) {
1190         push @{ $form->{TEMPLATE_ARRAYS}->{discount_sub} },         $form->format_amount($myconfig, $discount_subtotal,   2);
1191         push @{ $form->{TEMPLATE_ARRAYS}->{discount_sub_nofmt} },   $discount_subtotal;
1192         push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_sub} },       $form->format_amount($myconfig, $nodiscount_subtotal, 2);
1193         push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_sub_nofmt} }, $nodiscount_subtotal;
1194
1195         $discount_subtotal   = 0;
1196         $nodiscount_subtotal = 0;
1197         $subtotal_header     = 0;
1198
1199       } else {
1200         push @{ $form->{TEMPLATE_ARRAYS}->{$_} }, "" for qw(discount_sub nodiscount_sub discount_sub_nofmt nodiscount_sub_nofmt);
1201       }
1202
1203       if (!$form->{"discount_$i"}) {
1204         $nodiscount += $linetotal;
1205       }
1206
1207       push @{ $form->{TEMPLATE_ARRAYS}->{linetotal} },                  $form->format_amount($myconfig, $linetotal, 2);
1208       push @{ $form->{TEMPLATE_ARRAYS}->{linetotal_nofmt} },            $linetotal_exact;
1209       push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_linetotal} },       $form->format_amount($myconfig, $nodiscount_linetotal, 2);
1210       push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_linetotal_nofmt} }, $nodiscount_linetotal;
1211       push(@{ $form->{TEMPLATE_ARRAYS}->{projectnumber} },              $projectnumbers{$form->{"project_id_$i"}});
1212       push(@{ $form->{TEMPLATE_ARRAYS}->{projectdescription} },         $projectdescriptions{$form->{"project_id_$i"}});
1213
1214       my $lineweight = $form->{"qty_$i"} * $form->{"weight_$i"};
1215       $totalweight += $lineweight;
1216       push @{ $form->{TEMPLATE_ARRAYS}->{weight} },            $form->format_amount($myconfig, $form->{"weight_$i"}, 3);
1217       push @{ $form->{TEMPLATE_ARRAYS}->{weight_nofmt} },      $form->{"weight_$i"};
1218       push @{ $form->{TEMPLATE_ARRAYS}->{lineweight} },        $form->format_amount($myconfig, $lineweight, 3);
1219       push @{ $form->{TEMPLATE_ARRAYS}->{lineweight_nofmt} },  $lineweight;
1220
1221       my ($taxamount, $taxbase);
1222       my $taxrate = 0;
1223
1224       map { $taxrate += $form->{"${_}_rate"} } split(/ /, $form->{"taxaccounts_$i"});
1225
1226       if ($form->{taxincluded}) {
1227
1228         # calculate tax
1229         $taxamount = $linetotal * $taxrate / (1 + $taxrate);
1230         $taxbase = $linetotal / (1 + $taxrate);
1231       } else {
1232         $taxamount = $linetotal * $taxrate;
1233         $taxbase   = $linetotal;
1234       }
1235
1236       if ($taxamount != 0) {
1237         foreach my $accno (split / /, $form->{"taxaccounts_$i"}) {
1238           $taxaccounts{$accno} += $taxamount * $form->{"${accno}_rate"} / $taxrate;
1239           $taxbase{$accno}     += $taxbase;
1240         }
1241       }
1242
1243       $tax_rate = $taxrate * 100;
1244       push(@{ $form->{TEMPLATE_ARRAYS}->{tax_rate} }, qq|$tax_rate|);
1245
1246       if ($form->{"assembly_$i"}) {
1247         $sameitem = "";
1248
1249         # get parts and push them onto the stack
1250         my $sortorder = "";
1251         if ($form->{groupitems}) {
1252           $sortorder = qq|ORDER BY pg.partsgroup, a.oid|;
1253         } else {
1254           $sortorder = qq|ORDER BY a.oid|;
1255         }
1256
1257         $query = qq|SELECT p.partnumber, p.description, p.unit, a.qty, | .
1258                  qq|pg.partsgroup | .
1259                  qq|FROM assembly a | .
1260                  qq|  JOIN parts p ON (a.parts_id = p.id) | .
1261                  qq|    LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id) | .
1262                  qq|    WHERE a.bom = '1' | .
1263                  qq|    AND a.id = ? | . $sortorder;
1264         @values = ($form->{"id_$i"});
1265         $sth = $dbh->prepare($query);
1266         $sth->execute(@values) || $form->dberror($query);
1267
1268         while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
1269           if ($form->{groupitems} && $ref->{partsgroup} ne $sameitem) {
1270             map({ push(@{ $form->{TEMPLATE_ARRAYS}->{$_} }, "") } grep({ $_ ne "description" } @arrays));
1271             $sameitem = ($ref->{partsgroup}) ? $ref->{partsgroup} : "--";
1272             push(@{ $form->{TEMPLATE_ARRAYS}->{description} }, $sameitem);
1273           }
1274
1275           push(@{ $form->{TEMPLATE_ARRAYS}->{description} }, $form->format_amount($myconfig, $ref->{qty} * $form->{"qty_$i"}) . qq|, $ref->{partnumber}, $ref->{description}|);
1276           map({ push(@{ $form->{TEMPLATE_ARRAYS}->{$_} }, "") } grep({ $_ ne "description" } @arrays));
1277         }
1278         $sth->finish;
1279       }
1280
1281       push @{ $form->{TEMPLATE_ARRAYS}->{"ic_cvar_$_->{name}"} },
1282         CVar->format_to_template(CVar->parse($form->{"ic_cvar_$_->{name}_$i"}, $_), $_)
1283           for @{ $ic_cvar_configs };
1284     }
1285   }
1286
1287   $form->{totalweight}       = $form->format_amount($myconfig, $totalweight, 3);
1288   $form->{totalweight_nofmt} = $totalweight;
1289   my $defaults = AM->get_defaults();
1290   $form->{weightunit}        = $defaults->{weightunit};
1291
1292   my $tax = 0;
1293   foreach $item (sort keys %taxaccounts) {
1294     $tax += $taxamount = $form->round_amount($taxaccounts{$item}, 2);
1295
1296     push(@{ $form->{TEMPLATE_ARRAYS}->{taxbase} },        $form->format_amount($myconfig, $taxbase{$item}, 2));
1297     push(@{ $form->{TEMPLATE_ARRAYS}->{taxbase_nofmt} },  $taxbase{$item});
1298     push(@{ $form->{TEMPLATE_ARRAYS}->{tax} },            $form->format_amount($myconfig, $taxamount,      2));
1299     push(@{ $form->{TEMPLATE_ARRAYS}->{tax_nofmt} },      $taxamount);
1300     push(@{ $form->{TEMPLATE_ARRAYS}->{taxrate} },        $form->format_amount($myconfig, $form->{"${item}_rate"} * 100));
1301     push(@{ $form->{TEMPLATE_ARRAYS}->{taxrate_nofmt} },  $form->{"${item}_rate"} * 100);
1302     push(@{ $form->{TEMPLATE_ARRAYS}->{taxnumber} },      $form->{"${item}_taxnumber"});
1303
1304     my $tax_obj     = SL::DB::Manager::Tax->find_by(taxnumber => $form->{"${item}_taxnumber"});
1305     my $description = $tax_obj->translated_attribute('taxdescription',  $form->{language_id}, 0) if $tax_obj;
1306     push(@{ $form->{TEMPLATE_ARRAYS}->{taxdescription} }, $description . q{ } . 100 * $form->{"${item}_rate"} . q{%});
1307   }
1308
1309   $form->{nodiscount_subtotal} = $form->format_amount($myconfig, $form->{nodiscount_total}, 2);
1310   $form->{discount_total}      = $form->format_amount($myconfig, $form->{discount_total}, 2);
1311   $form->{nodiscount}          = $form->format_amount($myconfig, $nodiscount, 2);
1312   $form->{yesdiscount}         = $form->format_amount($myconfig, $form->{nodiscount_total} - $nodiscount, 2);
1313
1314   if($form->{taxincluded}) {
1315     $form->{subtotal}       = $form->format_amount($myconfig, $form->{ordtotal} - $tax, 2);
1316     $form->{subtotal_nofmt} = $form->{ordtotal} - $tax;
1317   } else {
1318     $form->{subtotal}       = $form->format_amount($myconfig, $form->{ordtotal}, 2);
1319     $form->{subtotal_nofmt} = $form->{ordtotal};
1320   }
1321
1322   $form->{ordtotal} = ($form->{taxincluded}) ? $form->{ordtotal} : $form->{ordtotal} + $tax;
1323
1324   # format amounts
1325   $form->{quototal} = $form->{ordtotal} = $form->format_amount($myconfig, $form->{ordtotal}, 2);
1326
1327   if ($form->{type} =~ /_quotation/) {
1328     $form->set_payment_options($myconfig, $form->{quodate});
1329   } else {
1330     $form->set_payment_options($myconfig, $form->{orddate});
1331   }
1332
1333   $form->{username} = $myconfig->{name};
1334
1335   $dbh->disconnect;
1336
1337   $main::lxdebug->leave_sub();
1338 }
1339
1340 sub project_description {
1341   $main::lxdebug->enter_sub();
1342
1343   my ($self, $dbh, $id) = @_;
1344
1345   my $query = qq|SELECT description FROM project WHERE id = ?|;
1346   my ($value) = selectrow_query($main::form, $dbh, $query, $id);
1347
1348   $main::lxdebug->leave_sub();
1349
1350   return $value;
1351 }
1352
1353 1;
1354
1355 __END__
1356
1357 =head1 NAME
1358
1359 OE.pm - Order entry module
1360
1361 =head1 DESCRIPTION
1362
1363 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>.
1364
1365 =head1 FUNCTIONS
1366
1367 =over 4
1368
1369 =item retrieve_simple PARAMS
1370
1371 simple OE retrieval by id. does not look up customer, vendor, units or any other stuff. only oe and orderitems.
1372
1373   my $order = retrieve_simple(id => 2);
1374
1375   $order => {
1376     %_OE_CONTENT,
1377     orderitems => [
1378       %_ORDERITEM_ROW_1,
1379       %_ORDERITEM_ROW_2,
1380       ...
1381     ]
1382   }
1383
1384 =back
1385
1386 =cut