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