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