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