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