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