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