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