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