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