1 #====================================================================
4 # Based on SQL-Ledger Version 2.1.9
5 # Web http://www.lx-office.org
7 #=====================================================================
8 # SQL-Ledger Accounting
9 # Copyright (C) 1999-2003
11 # Author: Dieter Simader
12 # Email: dsimader@sql-ledger.org
13 # Web: http://www.sql-ledger.org
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.
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., 51 Franklin Street, Fifth Floor, Boston,
30 #======================================================================
34 #======================================================================
38 use List::Util qw(max first);
44 use SL::DB::PeriodicInvoicesConfig;
46 use SL::DB::ProjectType;
50 use SL::HTML::Restrict;
53 use SL::Util qw(trim);
61 $main::lxdebug->enter_sub();
63 my ($self, $myconfig, $form) = @_;
66 my $dbh = $form->get_standard_dbh;
69 my $ordnumber = 'ordnumber';
75 my ($periodic_invoices_columns, $periodic_invoices_joins);
77 my $rate = ($form->{vc} eq 'customer') ? 'buy' : 'sell';
79 if ($form->{type} =~ /_quotation$/) {
81 $ordnumber = 'quonumber';
83 } elsif ($form->{type} eq 'sales_order') {
84 $periodic_invoices_columns = qq| , COALESCE(pcfg.active, 'f') AS periodic_invoices |;
85 $periodic_invoices_joins = qq| LEFT JOIN periodic_invoices_configs pcfg ON (o.id = pcfg.oe_id) |;
88 my $vc = $form->{vc} eq "customer" ? "customer" : "vendor";
92 if ($form->{l_remaining_amount} || $form->{l_remaining_netamount}) {
94 SELECT from_id, ar.amount, ar.netamount FROM (
97 WHERE from_table = 'oe' AND to_table = 'ar'
99 SELECT rl1.from_id, rl2.to_id
100 FROM record_links rl1
101 LEFT JOIN record_links rl2 ON (rl1.to_table = rl2.from_table AND rl1.to_id = rl2.from_id)
102 WHERE rl1.from_table = 'oe' AND rl2.to_table = 'ar'
104 SELECT rl1.from_id, rl3.to_id
105 FROM record_links rl1
106 JOIN record_links rl2 ON (rl1.to_table = rl2.from_table AND rl1.to_id = rl2.from_id)
107 JOIN record_links rl3 ON (rl2.to_table = rl3.from_table AND rl2.to_id = rl3.from_id)
108 WHERE rl1.from_table = 'oe' AND rl2.to_table = 'ar' AND rl3.to_table = 'ar'
110 LEFT JOIN ar ON ar.id = rl.to_id
112 for my $ref (@{ selectall_hashref_query($form, $dbh, $query) }) {
113 $billed_amount{ $ref->{from_id}} += $ref->{amount};
114 $billed_netamount{$ref->{from_id}} += $ref->{netamount};
119 qq|SELECT o.id, o.ordnumber, o.transdate, o.reqdate, | .
120 qq| o.amount, ct.${vc}number, ct.name, o.netamount, o.${vc}_id, o.globalproject_id, | .
121 qq| o.closed, o.delivered, o.quonumber, o.cusordnumber, o.shippingpoint, o.shipvia, | .
122 qq| o.transaction_description, | .
123 qq| o.marge_total, o.marge_percent, | .
124 qq| o.itime::DATE AS insertdate, | .
125 qq| department.description as department, | .
126 qq| ex.$rate AS exchangerate, | .
127 qq| pt.description AS payment_terms, | .
128 qq| pr.projectnumber AS globalprojectnumber, | .
129 qq| e.name AS employee, s.name AS salesman, | .
130 qq| ct.${vc}number AS vcnumber, ct.country, ct.ustid, ct.business_id, | .
131 qq| tz.description AS taxzone | .
132 $periodic_invoices_columns .
133 qq| , o.order_probability, o.expected_billing_date, (o.netamount * o.order_probability / 100) AS expected_netamount | .
135 qq|JOIN $vc ct ON (o.${vc}_id = ct.id) | .
136 qq|LEFT JOIN contacts cp ON (o.cp_id = cp.cp_id) | .
137 qq|LEFT JOIN employee e ON (o.employee_id = e.id) | .
138 qq|LEFT JOIN employee s ON (o.salesman_id = s.id) | .
139 qq|LEFT JOIN exchangerate ex ON (ex.currency_id = o.currency_id | .
140 qq| AND ex.transdate = o.transdate) | .
141 qq|LEFT JOIN project pr ON (o.globalproject_id = pr.id) | .
142 qq|LEFT JOIN payment_terms pt ON (pt.id = o.payment_id)| .
143 qq|LEFT JOIN tax_zones tz ON (o.taxzone_id = tz.id) | .
144 qq|LEFT JOIN department ON (o.department_id = department.id) | .
145 qq|$periodic_invoices_joins | .
146 qq|WHERE (o.quotation = ?) |;
147 push(@values, $quotation);
149 if ($form->{department_id}) {
150 $query .= qq| AND o.department_id = ?|;
151 push(@values, $form->{department_id});
154 if ($form->{"project_id"}) {
156 qq|AND ((globalproject_id = ?) OR EXISTS | .
157 qq| (SELECT * FROM orderitems oi | .
158 qq| WHERE oi.project_id = ? AND oi.trans_id = o.id))|;
159 push(@values, conv_i($form->{"project_id"}), conv_i($form->{"project_id"}));
162 if ($form->{"projectnumber"}) {
164 AND ((pr.projectnumber ILIKE ?) OR EXISTS (
165 SELECT * FROM orderitems oi
166 LEFT JOIN project proi ON proi.id = oi.project_id
167 WHERE proi.projectnumber ILIKE ? AND oi.trans_id = o.id
170 push @values, like($form->{"projectnumber"}), like($form->{"projectnumber"});
173 if ($form->{"business_id"}) {
174 $query .= " AND ct.business_id = ?";
175 push(@values, $form->{"business_id"});
178 if ($form->{"${vc}_id"}) {
179 $query .= " AND o.${vc}_id = ?";
180 push(@values, $form->{"${vc}_id"});
182 } elsif ($form->{$vc}) {
183 $query .= " AND ct.name ILIKE ?";
184 push(@values, like($form->{$vc}));
187 if ($form->{"cp_name"}) {
188 $query .= " AND (cp.cp_name ILIKE ? OR cp.cp_givenname ILIKE ?)";
189 push(@values, (like($form->{"cp_name"}))x2);
192 if ( !(($vc eq 'customer' && $main::auth->assert('sales_all_edit', 1)) || ($vc eq 'vendor' && $main::auth->assert('purchase_all_edit', 1))) ) {
193 $query .= " AND o.employee_id = (select id from employee where login= ?)";
194 push @values, $::myconfig{login};
196 if ($form->{employee_id}) {
197 $query .= " AND o.employee_id = ?";
198 push @values, conv_i($form->{employee_id});
201 if ($form->{salesman_id}) {
202 $query .= " AND o.salesman_id = ?";
203 push @values, conv_i($form->{salesman_id});
206 if (!$form->{open} && !$form->{closed}) {
207 $query .= " AND o.id = 0";
208 } elsif (!($form->{open} && $form->{closed})) {
209 $query .= ($form->{open}) ? " AND o.closed = '0'" : " AND o.closed = '1'";
212 if (($form->{"notdelivered"} || $form->{"delivered"}) &&
213 ($form->{"notdelivered"} ne $form->{"delivered"})) {
214 $query .= $form->{"delivered"} ?
215 " AND o.delivered " : " AND NOT o.delivered";
218 if ($form->{$ordnumber}) {
219 $query .= qq| AND o.$ordnumber ILIKE ?|;
220 push(@values, like($form->{$ordnumber}));
223 if ($form->{cusordnumber}) {
224 $query .= qq| AND o.cusordnumber ILIKE ?|;
225 push(@values, like($form->{cusordnumber}));
228 if($form->{transdatefrom}) {
229 $query .= qq| AND o.transdate >= ?|;
230 push(@values, conv_date($form->{transdatefrom}));
233 if($form->{transdateto}) {
234 $query .= qq| AND o.transdate <= ?|;
235 push(@values, conv_date($form->{transdateto}));
238 if($form->{reqdatefrom}) {
239 $query .= qq| AND o.reqdate >= ?|;
240 push(@values, conv_date($form->{reqdatefrom}));
243 if($form->{reqdateto}) {
244 $query .= qq| AND o.reqdate <= ?|;
245 push(@values, conv_date($form->{reqdateto}));
248 if($form->{insertdatefrom}) {
249 $query .= qq| AND o.itime::DATE >= ?|;
250 push(@values, conv_date($form->{insertdatefrom}));
253 if($form->{insertdateto}) {
254 $query .= qq| AND o.itime::DATE <= ?|;
255 push(@values, conv_date($form->{insertdateto}));
258 if ($form->{shippingpoint}) {
259 $query .= qq| AND o.shippingpoint ILIKE ?|;
260 push(@values, like($form->{shippingpoint}));
263 if ($form->{taxzone_id} ne '') { # taxzone_id could be 0
264 $query .= qq| AND tz.id = ?|;
265 push(@values, $form->{taxzone_id});
268 if ($form->{transaction_description}) {
269 $query .= qq| AND o.transaction_description ILIKE ?|;
270 push(@values, like($form->{transaction_description}));
273 if ($form->{periodic_invoices_active} ne $form->{periodic_invoices_inactive}) {
274 my $not = $form->{periodic_invoices_inactive} ? 'NOT' : '';
275 $query .= qq| AND ${not} COALESCE(pcfg.active, 'f')|;
278 if ($form->{reqdate_unset_or_old}) {
279 $query .= qq| AND ((o.reqdate IS NULL) OR (o.reqdate < date_trunc('month', current_date)))|;
282 if (($form->{order_probability_value} || '') ne '') {
283 my $op = $form->{order_probability_value} eq 'le' ? '<=' : '>=';
284 $query .= qq| AND (o.order_probability ${op} ?)|;
285 push @values, trim($form->{order_probability_value});
288 if ($form->{expected_billing_date_from}) {
289 $query .= qq| AND (o.expected_billing_date >= ?)|;
290 push @values, conv_date($form->{expected_billing_date_from});
293 if ($form->{expected_billing_date_to}) {
294 $query .= qq| AND (o.expected_billing_date <= ?)|;
295 push @values, conv_date($form->{expected_billing_date_to});
298 if ($form->{parts_partnumber}) {
301 SELECT orderitems.trans_id
303 LEFT JOIN parts ON (orderitems.parts_id = parts.id)
304 WHERE (orderitems.trans_id = o.id)
305 AND (parts.partnumber ILIKE ?)
309 push @values, like($form->{parts_partnumber});
312 if ($form->{parts_description}) {
315 SELECT orderitems.trans_id
317 WHERE (orderitems.trans_id = o.id)
318 AND (orderitems.description ILIKE ?)
322 push @values, like($form->{parts_description});
326 my @tokens = parse_line('\s+', 0, $form->{all});
327 # ordnumber quonumber customer.name vendor.name transaction_description
329 o.ordnumber ILIKE ? OR
330 o.quonumber ILIKE ? OR
332 o.transaction_description ILIKE ?
334 push @values, (like($_))x4 for @tokens;
337 my ($cvar_where, @cvar_values) = CVar->build_filter_query('module' => 'CT',
338 'trans_id_field' => 'ct.id',
342 $query .= qq| AND ($cvar_where)|;
343 push @values, @cvar_values;
346 my $sortdir = !defined $form->{sortdir} ? 'ASC' : $form->{sortdir} ? 'ASC' : 'DESC';
347 my $sortorder = join(', ', map { "${_} ${sortdir} " } ("o.id", $form->sort_columns("transdate", $ordnumber, "name"), "o.itime"));
348 my %allowed_sort_columns = (
349 "transdate" => "o.transdate",
350 "reqdate" => "o.reqdate",
352 "ordnumber" => "o.ordnumber",
353 "cusordnumber" => "o.cusordnumber",
354 "quonumber" => "o.quonumber",
356 "employee" => "e.name",
357 "salesman" => "s.name",
358 "shipvia" => "o.shipvia",
359 "transaction_description" => "o.transaction_description",
360 "shippingpoint" => "o.shippingpoint",
361 "insertdate" => "o.itime",
362 "taxzone" => "tz.description",
363 "payment_terms" => "pt.description",
364 "department" => "department.description",
366 if ($form->{sort} && grep($form->{sort}, keys(%allowed_sort_columns))) {
367 $sortorder = $allowed_sort_columns{$form->{sort}} . " ${sortdir}" . ", o.itime ${sortdir}";
369 $query .= qq| ORDER by | . $sortorder;
371 my $sth = $dbh->prepare($query);
372 $sth->execute(@values) ||
373 $form->dberror($query . " (" . join(", ", @values) . ")");
377 while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
378 $ref->{billed_amount} = $billed_amount{$ref->{id}};
379 $ref->{billed_netamount} = $billed_netamount{$ref->{id}};
380 if ($ref->{billed_amount} < 0) { # case: credit note(s) higher than invoices
381 $ref->{remaining_amount} = $ref->{amount} + $ref->{billed_amount};
382 $ref->{remaining_netamount} = $ref->{netamount} + $ref->{billed_netamount};
384 $ref->{remaining_amount} = $ref->{amount} - $ref->{billed_amount};
385 $ref->{remaining_netamount} = $ref->{netamount} - $ref->{billed_netamount};
387 $ref->{exchangerate} = 1 unless $ref->{exchangerate};
388 push @{ $form->{OE} }, $ref if $ref->{id} != $id{ $ref->{id} };
389 $id{ $ref->{id} } = $ref->{id};
394 $main::lxdebug->leave_sub();
397 sub transactions_for_todo_list {
398 $main::lxdebug->enter_sub();
403 my $myconfig = \%main::myconfig;
404 my $form = $main::form;
406 my $dbh = $params{dbh} || $form->get_standard_dbh($myconfig);
408 my $query = qq|SELECT id FROM employee WHERE login = ?|;
409 my ($e_id) = selectrow_query($form, $dbh, $query, $::myconfig{login});
412 qq|SELECT oe.id, oe.transdate, oe.reqdate, oe.quonumber, oe.transaction_description, oe.amount,
413 CASE WHEN (COALESCE(oe.customer_id, 0) = 0) THEN 'vendor' ELSE 'customer' END AS vc,
418 LEFT JOIN customer c ON (oe.customer_id = c.id)
419 LEFT JOIN vendor v ON (oe.vendor_id = v.id)
420 LEFT JOIN employee e ON (oe.employee_id = e.id)
421 WHERE (COALESCE(quotation, FALSE) = TRUE)
422 AND (COALESCE(closed, FALSE) = FALSE)
423 AND ((oe.employee_id = ?) OR (oe.salesman_id = ?))
424 AND NOT (oe.reqdate ISNULL)
425 AND (oe.reqdate < current_date)
428 my $quotations = selectall_hashref_query($form, $dbh, $query, $e_id, $e_id);
430 $main::lxdebug->leave_sub();
436 my ($self, $myconfig, $form) = @_;
437 $main::lxdebug->enter_sub();
439 my $rc = SL::DB->client->with_transaction(\&_save, $self, $myconfig, $form);
441 $::lxdebug->leave_sub;
447 $main::lxdebug->enter_sub();
449 my ($self, $myconfig, $form) = @_;
451 my $dbh = SL::DB->client->dbh;
452 my $restricter = SL::HTML::Restrict->create;
454 my ($query, @values, $sth, $null);
455 my $exchangerate = 0;
457 my $all_units = AM->retrieve_units($myconfig, $form);
458 $form->{all_units} = $all_units;
460 my $ic_cvar_configs = CVar->get_configs(module => 'IC',
463 $form->{employee_id} = (split /--/, $form->{employee})[1] if !$form->{employee_id};
464 unless ($form->{employee_id}) {
465 $form->get_employee($dbh);
468 my $ml = ($form->{type} eq 'sales_order') ? 1 : -1;
470 my $number_field = $form->{type} =~ m{order} ? 'ordnumber' : 'quonumber';
471 my $trans_number = SL::TransNumber->new(type => $form->{type}, dbh => $dbh, number => $form->{$number_field}, id => $form->{id});
472 $form->{$number_field} ||= $trans_number->create_unique; # set $form->{ordnumber} or $form->{quonumber}
475 $query = qq|DELETE FROM custom_variables
476 WHERE (config_id IN (SELECT id FROM custom_variable_configs WHERE (module = 'ShipTo')))
477 AND (trans_id IN (SELECT shipto_id FROM shipto WHERE (module = 'OE') AND (trans_id = ?)))|;
478 do_query($form, $dbh, $query, $form->{id});
480 $query = qq|DELETE FROM shipto | .
481 qq|WHERE trans_id = ? AND module = 'OE'|;
482 do_query($form, $dbh, $query, $form->{id});
486 $query = qq|SELECT nextval('id')|;
487 ($form->{id}) = selectrow_query($form, $dbh, $query);
489 $query = qq|INSERT INTO oe (id, ordnumber, employee_id, currency_id, taxzone_id) VALUES (?, '', ?, (SELECT currency_id FROM defaults), ?)|;
490 do_query($form, $dbh, $query, $form->{id}, $form->{employee_id}, $form->{taxzone_id});
507 my @processed_orderitems;
509 $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS');
510 my %price_factors = map { $_->{id} => $_->{factor} } @{ $form->{ALL_PRICE_FACTORS} };
513 for my $i (1 .. $form->{rowcount}) {
515 map({ $form->{"${_}_$i"} = $form->parse_amount($myconfig, $form->{"${_}_$i"}) } qw(qty ship));
517 if ($form->{"id_$i"}) {
520 $query = qq|SELECT unit FROM parts WHERE id = ?|;
521 my ($item_unit) = selectrow_query($form, $dbh, $query, $form->{"id_$i"});
524 if (defined($all_units->{$item_unit}->{factor}) &&
525 (($all_units->{$item_unit}->{factor} * 1) != 0)) {
526 $basefactor = $all_units->{$form->{"unit_$i"}}->{factor} / $all_units->{$item_unit}->{factor};
528 my $baseqty = $form->{"qty_$i"} * $basefactor;
530 $form->{"marge_percent_$i"} = $form->parse_amount($myconfig, $form->{"marge_percent_$i"}) * 1;
531 $form->{"marge_absolut_$i"} = $form->parse_amount($myconfig, $form->{"marge_absolut_$i"}) * 1;
533 $form->{"lastcost_$i"} = $form->parse_amount($myconfig, $form->{"lastcost_$i"});
535 # keep entered selling price
537 $form->parse_amount($myconfig, $form->{"sellprice_$i"});
539 my ($dec) = ($fxsellprice =~ /\.(\d+)/);
541 my $decimalplaces = ($dec > 2) ? $dec : 2;
543 # undo discount formatting
544 $form->{"discount_$i"} = $form->parse_amount($myconfig, $form->{"discount_$i"}) / 100;
547 $form->{"sellprice_$i"} = $fxsellprice * (1 - $form->{"discount_$i"});
549 # round linetotal at least to 2 decimal places
550 $price_factor = $price_factors{ $form->{"price_factor_id_$i"} } || 1;
551 $linetotal = $form->round_amount($form->{"sellprice_$i"} * $form->{"qty_$i"} / $price_factor, 2);
553 $form->{"inventory_accno_$i"} *= 1;
554 $form->{"expense_accno_$i"} *= 1;
556 @taxaccounts = split(/ /, $form->{"taxaccounts_$i"});
560 map { $taxrate += $form->{"${_}_rate"} } @taxaccounts;
562 if ($form->{taxincluded}) {
563 $taxamount = $linetotal * $taxrate / (1 + $taxrate);
564 $taxbase = $linetotal - $taxamount;
566 # we are not keeping a natural price, do not round
567 $form->{"sellprice_$i"} =
568 $form->{"sellprice_$i"} * (1 / (1 + $taxrate));
570 $taxamount = $linetotal * $taxrate;
571 $taxbase = $linetotal;
574 if ($form->round_amount($taxrate, 7) == 0) {
575 if ($form->{taxincluded}) {
576 foreach my $item (@taxaccounts) {
577 $taxamount = $form->round_amount($linetotal * $form->{"${item}_rate"} / (1 + abs($form->{"${item}_rate"})), 2);
578 $taxaccounts{$item} += $taxamount;
579 $taxdiff += $taxamount;
580 $taxbase{$item} += $taxbase;
582 $taxaccounts{ $taxaccounts[0] } += $taxdiff;
584 foreach my $item (@taxaccounts) {
585 $taxaccounts{$item} += $linetotal * $form->{"${item}_rate"};
586 $taxbase{$item} += $taxbase;
590 foreach my $item (@taxaccounts) {
591 $taxaccounts{$item} += $taxamount * $form->{"${item}_rate"} / $taxrate;
592 $taxbase{$item} += $taxbase;
596 $netamount += $form->{"sellprice_$i"} * $form->{"qty_$i"} / $price_factor;
598 $reqdate = ($form->{"reqdate_$i"}) ? $form->{"reqdate_$i"} : undef;
600 # Get pricegroup_id and save it. Unfortunately the interface
601 # also uses ID "0" for signalling that none is selected, but "0"
602 # must not be stored in the database. Therefore we cannot simply
604 ($null, my $pricegroup_id) = split(/--/, $form->{"sellprice_pg_$i"});
606 $pricegroup_id = undef if !$pricegroup_id;
608 # force new project, if not set yet
609 if ($::instance_conf->get_order_always_project && !$form->{"globalproject_id"} && ($form->{type} eq 'sales_order')) {
610 require SL::DB::Customer;
611 my $customer = SL::DB::Manager::Customer->find_by(id => $form->{customer_id});
612 die "Can't find customer" unless $customer;
613 my $new_project = SL::DB::Project->new(
614 projectnumber => $form->{ordnumber},
615 description => $customer->name,
616 customer_id => $customer->id,
618 project_type_id => $::instance_conf->get_project_type_id,
619 project_status_id => $::instance_conf->get_project_status_id,
622 $form->{"globalproject_id"} = $new_project->id;
625 CVar->get_non_editable_ic_cvars(form => $form,
628 sub_module => 'orderitems',
629 may_converted_from => ['orderitems', 'invoice']);
633 # save detail record in orderitems table
634 if (! $form->{"orderitems_id_$i"}) {
635 $query = qq|SELECT nextval('orderitemsid')|;
636 ($form->{"orderitems_id_$i"}) = selectrow_query($form, $dbh, $query);
638 $query = qq|INSERT INTO orderitems (id, position) VALUES (?, ?)|;
639 do_query($form, $dbh, $query, $form->{"orderitems_id_$i"}, conv_i($position));
642 my $orderitems_id = $form->{"orderitems_id_$i"};
643 push @processed_orderitems, $orderitems_id;
646 UPDATE orderitems SET
647 trans_id = ?, position = ?, parts_id = ?, description = ?, longdescription = ?, qty = ?, base_qty = ?,
648 sellprice = ?, discount = ?, unit = ?, reqdate = ?, project_id = ?, serialnumber = ?, ship = ?,
649 pricegroup_id = ?, subtotal = ?,
650 marge_percent = ?, marge_total = ?, lastcost = ?, price_factor_id = ?,
651 active_price_source = ?, active_discount_source = ?,
652 price_factor = (SELECT factor FROM price_factors WHERE id = ?), marge_price_factor = ?
656 conv_i($form->{id}), conv_i($position), conv_i($form->{"id_$i"}),
657 $form->{"description_$i"}, $restricter->process($form->{"longdescription_$i"}),
658 $form->{"qty_$i"}, $baseqty,
659 $fxsellprice, $form->{"discount_$i"},
660 $form->{"unit_$i"}, conv_date($reqdate), conv_i($form->{"project_id_$i"}),
661 $form->{"serialnumber_$i"}, $form->{"ship_$i"},
662 $pricegroup_id, $form->{"subtotal_$i"} ? 't' : 'f',
663 $form->{"marge_percent_$i"}, $form->{"marge_absolut_$i"},
664 $form->{"lastcost_$i"}, conv_i($form->{"price_factor_id_$i"}),
665 $form->{"active_price_source_$i"}, $form->{"active_discount_source_$i"},
666 conv_i($form->{"price_factor_id_$i"}), conv_i($form->{"marge_price_factor_$i"}),
667 conv_i($orderitems_id),
670 do_query($form, $dbh, $query, @values);
672 $form->{"sellprice_$i"} = $fxsellprice;
673 $form->{"discount_$i"} *= 100;
675 CVar->save_custom_variables(module => 'IC',
676 sub_module => 'orderitems',
677 trans_id => $orderitems_id,
678 configs => $ic_cvar_configs,
680 name_prefix => 'ic_',
681 name_postfix => "_$i",
684 # link previous items with orderitems
685 # assume we have a new workflow if we link from invoice or order to quotation
686 # unluckily orderitems are used for quotation and orders - therefore one more
687 # check to be sure NOT to link from order to quotation
688 foreach (qw(orderitems)) {
689 if (!$form->{saveasnew} && !$form->{useasnew} && $form->{"converted_from_${_}_id_$i"}
690 && $form->{type} !~ 'quotation') {
691 RecordLinks->create_links('dbh' => $dbh,
694 'from_ids' => $form->{"converted_from_${_}_id_$i"},
695 'to_table' => 'orderitems',
696 'to_id' => $orderitems_id,
699 delete $form->{"converted_from_${_}_id_$i"};
704 # search for orphaned ids
705 $query = sprintf 'SELECT id FROM orderitems WHERE trans_id = ? AND NOT id IN (%s)', join ', ', ("?") x scalar @processed_orderitems;
706 @values = (conv_i($form->{id}), map { conv_i($_) } @processed_orderitems);
707 my @orphaned_ids = map { $_->{id} } selectall_hashref_query($form, $dbh, $query, @values);
709 if (scalar @orphaned_ids) {
710 # clean up orderitems
711 $query = sprintf 'DELETE FROM orderitems WHERE id IN (%s)', join ', ', ("?") x scalar @orphaned_ids;
712 do_query($form, $dbh, $query, @orphaned_ids);
715 $reqdate = ($form->{reqdate}) ? $form->{reqdate} : undef;
719 map { $tax += $form->round_amount($taxaccounts{$_}, 2) } keys %taxaccounts;
721 $amount = $form->round_amount($netamount + $tax, 2, 1);
722 $netamount = $form->round_amount($netamount, 2);
724 if ($form->{currency} eq $form->{defaultcurrency}) {
725 $form->{exchangerate} = 1;
727 $exchangerate = $form->check_exchangerate($myconfig, $form->{currency}, $form->{transdate}, ($form->{vc} eq 'customer') ? 'buy' : 'sell');
730 # from inputfield (exchangerate) or hidden (forex)
731 my $exchangerate_from_form = $form->{forex} || $form->parse_amount($myconfig, $form->{exchangerate});
733 $form->{exchangerate} = $exchangerate || $exchangerate_from_form;
735 my $quotation = $form->{type} =~ /_order$/ ? 'f' : 't';
740 ordnumber = ?, quonumber = ?, cusordnumber = ?, transdate = ?, vendor_id = ?,
741 customer_id = ?, amount = ?, netamount = ?, reqdate = ?, taxincluded = ?,
742 shippingpoint = ?, shipvia = ?, notes = ?, intnotes = ?, currency_id = (SELECT id FROM currencies WHERE name=?), closed = ?,
743 delivered = ?, proforma = ?, quotation = ?, department_id = ?, language_id = ?,
744 taxzone_id = ?, shipto_id = ?, payment_id = ?, delivery_vendor_id = ?, delivery_customer_id = ?,delivery_term_id = ?,
745 globalproject_id = ?, employee_id = ?, salesman_id = ?, cp_id = ?, transaction_description = ?, marge_total = ?, marge_percent = ?
746 , order_probability = ?, expected_billing_date = ?
749 @values = ($form->{ordnumber} || '', $form->{quonumber},
750 $form->{cusordnumber}, conv_date($form->{transdate}),
751 conv_i($form->{vendor_id}), conv_i($form->{customer_id}),
752 $amount, $netamount, conv_date($reqdate),
753 $form->{taxincluded} ? 't' : 'f', $form->{shippingpoint},
754 $form->{shipvia}, $restricter->process($form->{notes}), $form->{intnotes},
755 $form->{currency}, $form->{closed} ? 't' : 'f',
756 $form->{delivered} ? "t" : "f", $form->{proforma} ? 't' : 'f',
757 $quotation, conv_i($form->{department_id}),
758 conv_i($form->{language_id}), conv_i($form->{taxzone_id}),
759 conv_i($form->{shipto_id}), conv_i($form->{payment_id}),
760 conv_i($form->{delivery_vendor_id}),
761 conv_i($form->{delivery_customer_id}),
762 conv_i($form->{delivery_term_id}),
763 conv_i($form->{globalproject_id}), conv_i($form->{employee_id}),
764 conv_i($form->{salesman_id}), conv_i($form->{cp_id}),
765 $form->{transaction_description},
766 $form->{marge_total} * 1, $form->{marge_percent} * 1,
767 $form->{order_probability} * 1, conv_date($form->{expected_billing_date}),
768 conv_i($form->{id}));
769 do_query($form, $dbh, $query, @values);
771 $form->new_lastmtime('oe');
773 $form->{ordtotal} = $amount;
775 $form->{name} = $form->{ $form->{vc} };
776 $form->{name} =~ s/--\Q$form->{"$form->{vc}_id"}\E//;
779 if (!$form->{shipto_id}) {
780 $form->add_shipto($dbh, $form->{id}, "OE");
783 # save printed, emailed, queued
784 $form->save_status($dbh);
786 # Link this record to the records it was created from.
787 $form->{convert_from_oe_ids} =~ s/^\s+//;
788 $form->{convert_from_oe_ids} =~ s/\s+$//;
789 my @convert_from_oe_ids = split m/\s+/, $form->{convert_from_oe_ids};
790 delete $form->{convert_from_oe_ids};
791 if (!$form->{useasnew} && scalar @convert_from_oe_ids) {
792 RecordLinks->create_links('dbh' => $dbh,
794 'from_table' => 'oe',
795 'from_ids' => \@convert_from_oe_ids,
797 'to_id' => $form->{id},
799 $self->_close_quotations_rfqs('dbh' => $dbh,
800 'from_id' => \@convert_from_oe_ids,
801 'to_id' => $form->{id});
804 if (($form->{currency} ne $form->{defaultcurrency}) && !$exchangerate) {
805 if ($form->{vc} eq 'customer') {
806 $form->update_exchangerate($dbh, $form->{currency}, $form->{transdate}, $form->{exchangerate}, 0);
808 if ($form->{vc} eq 'vendor') {
809 $form->update_exchangerate($dbh, $form->{currency}, $form->{transdate}, 0, $form->{exchangerate});
813 $form->{saved_xyznumber} = $form->{$form->{type} =~ /_quotation$/ ?
814 "quonumber" : "ordnumber"};
816 Common::webdav_folder($form);
818 $self->save_periodic_invoices_config(dbh => $dbh,
819 oe_id => $form->{id},
820 config_yaml => $form->{periodic_invoices_config})
821 if ($form->{type} eq 'sales_order');
823 $main::lxdebug->leave_sub();
828 sub save_periodic_invoices_config {
829 my ($self, %params) = @_;
831 return if !$params{oe_id};
833 my $config = $params{config_yaml} ? SL::YAML::Load($params{config_yaml}) : undef;
834 return if 'HASH' ne ref $config;
836 my $obj = SL::DB::Manager::PeriodicInvoicesConfig->find_by(oe_id => $params{oe_id})
837 || SL::DB::PeriodicInvoicesConfig->new(oe_id => $params{oe_id});
838 $obj->update_attributes(%{ $config });
841 sub load_periodic_invoice_config {
845 delete $form->{periodic_invoices_config};
848 my $config_obj = SL::DB::Manager::PeriodicInvoicesConfig->find_by(oe_id => $form->{id});
851 my $config = { map { $_ => $config_obj->$_ } qw(active terminated periodicity order_value_periodicity start_date_as_date end_date_as_date first_billing_date_as_date extend_automatically_by ar_chart_id
852 print printer_id copies direct_debit send_email email_recipient_contact_id email_recipient_address email_sender email_subject email_body) };
853 $form->{periodic_invoices_config} = SL::YAML::Dump($config);
858 sub _close_quotations_rfqs {
859 $main::lxdebug->enter_sub();
864 Common::check_params(\%params, qw(from_id to_id));
866 my $myconfig = \%main::myconfig;
867 my $form = $main::form;
869 my $dbh = $params{dbh} || SL::DB->client->dbh;
871 SL::DB->client->with_transaction(sub {
873 my $query = qq|SELECT quotation FROM oe WHERE id = ?|;
874 my $sth = prepare_query($form, $dbh, $query);
876 do_statement($form, $sth, $query, conv_i($params{to_id}));
878 my ($quotation) = $sth->fetchrow_array();
886 foreach my $from_id (@{ $params{from_id} }) {
887 $from_id = conv_i($from_id);
888 do_statement($form, $sth, $query, $from_id);
889 ($quotation) = $sth->fetchrow_array();
890 push @close_ids, $from_id if ($quotation);
895 if (scalar @close_ids) {
896 $query = qq|UPDATE oe SET closed = TRUE WHERE id IN (| . join(', ', ('?') x scalar @close_ids) . qq|)|;
897 do_query($form, $dbh, $query, @close_ids);
900 }) or do { die SL::DB->client->error };
902 $main::lxdebug->leave_sub();
906 $main::lxdebug->enter_sub();
908 my ($self, $myconfig, $form) = @_;
910 my $rc = SL::DB::Order->new->db->with_transaction(sub {
911 my @spoolfiles = grep { $_ } map { $_->spoolfile } @{ SL::DB::Manager::Status->get_all(where => [ trans_id => $form->{id} ]) };
913 SL::DB::Order->new(id => $form->{id})->delete;
915 my $spool = $::lx_office_conf{paths}->{spool};
916 unlink map { "$spool/$_" } @spoolfiles if $spool;
919 }) or do { die SL::DB->client->error };
921 $main::lxdebug->leave_sub();
927 my ($self, $myconfig, $form) = @_;
928 $main::lxdebug->enter_sub();
930 my $rc = SL::DB->client->with_transaction(\&_retrieve, $self, $myconfig, $form);
932 $::lxdebug->leave_sub;
937 my ($self, $myconfig, $form) = @_;
939 # connect to database
940 my $dbh = SL::DB->client->dbh;
942 my ($query, $query_add, @values, @ids, $sth);
944 # translate the ids (given by id_# and trans_id_#) into one array of ids, so we can join them later
946 push @ids, $form->{"trans_id_$_"}
947 if ($form->{"multi_id_$_"} and $form->{"trans_id_$_"})
948 } (1 .. $form->{"rowcount"});
950 if ($form->{rowcount} && scalar @ids) {
951 $form->{convert_from_oe_ids} = join ' ', @ids;
954 # if called in multi id mode, and still only got one id, switch back to single id
955 if ($form->{"rowcount"} and $#ids == 0) {
956 $form->{"id"} = $ids[0];
958 delete $form->{convert_from_oe_ids};
961 # and remember for the rest of the function
962 my $is_collective_order = scalar @ids;
964 # If collective order was created from exactly 1 order, we assume the same
965 # behaviour as a "save as new" from within an order is actually desired, i.e.
966 # the original order isn't part of a workflow where we want to remember
967 # record_links, but simply a quick way of generating a new order from an old
968 # one without having to enter everything again.
969 # Setting useasnew will prevent the creation of record_links for the items
970 # when saving the new order.
971 # This form variable is probably not necessary, could just set saveasnew instead
972 $form->{useasnew} = 1 if $is_collective_order == 1;
975 my $extra_days = $form->{type} eq 'sales_quotation' ? $::instance_conf->get_reqdate_interval :
976 $form->{type} eq 'sales_order' ? $::instance_conf->get_delivery_date_interval : 1;
977 $form->{reqdate} = DateTime->today_local->next_workday(extra_days => $extra_days)->to_kivitendo;
978 $form->{transdate} = DateTime->today_local->to_kivitendo;
981 # get default accounts
982 $query = qq|SELECT (SELECT c.accno FROM chart c WHERE d.inventory_accno_id = c.id) AS inventory_accno,
983 (SELECT c.accno FROM chart c WHERE d.income_accno_id = c.id) AS income_accno,
984 (SELECT c.accno FROM chart c WHERE d.expense_accno_id = c.id) AS expense_accno,
985 (SELECT c.accno FROM chart c WHERE d.fxgain_accno_id = c.id) AS fxgain_accno,
986 (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id = c.id) AS fxloss_accno,
987 (SELECT c.accno FROM chart c WHERE d.rndgain_accno_id = c.id) AS rndgain_accno,
988 (SELECT c.accno FROM chart c WHERE d.rndloss_accno_id = c.id) AS rndloss_accno
991 my $ref = selectfirst_hashref_query($form, $dbh, $query);
992 map { $form->{$_} = $ref->{$_} } keys %$ref;
994 $form->{currency} = $form->get_default_currency($myconfig);
996 # set reqdate if this is an invoice->order conversion. If someone knows a better check to ensure
997 # we come from invoices, feel free.
998 $form->{reqdate} = $form->{deliverydate}
999 if ( $form->{deliverydate}
1000 and $form->{callback} =~ /action=ar_transactions/);
1002 my $vc = $form->{vc} eq "customer" ? "customer" : "vendor";
1004 if ($form->{id} or @ids) {
1006 # retrieve order for single id
1007 # NOTE: this query is intended to fetch all information only ONCE.
1008 # so if any of these infos is important (or even different) for any item,
1009 # it will be killed out and then has to be fetched from the item scope query further down
1011 qq|SELECT o.cp_id, o.ordnumber, o.transdate, o.reqdate,
1012 o.taxincluded, o.shippingpoint, o.shipvia, o.notes, o.intnotes,
1013 (SELECT cu.name FROM currencies cu WHERE cu.id=o.currency_id) AS currency, e.name AS employee, o.employee_id, o.salesman_id,
1014 o.${vc}_id, cv.name AS ${vc}, o.amount AS invtotal,
1015 o.closed, o.reqdate, o.quonumber, o.department_id, o.cusordnumber,
1017 d.description AS department, o.payment_id, o.language_id, o.taxzone_id,
1018 o.delivery_customer_id, o.delivery_vendor_id, o.proforma, o.shipto_id,
1019 o.globalproject_id, o.delivered, o.transaction_description, o.delivery_term_id,
1020 o.itime::DATE AS insertdate, o.order_probability, o.expected_billing_date
1022 JOIN ${vc} cv ON (o.${vc}_id = cv.id)
1023 LEFT JOIN employee e ON (o.employee_id = e.id)
1024 LEFT JOIN department d ON (o.department_id = d.id) | .
1027 : "WHERE o.id IN (" . join(', ', map("? ", @ids)) . ")"
1029 @values = $form->{id} ? ($form->{id}) : @ids;
1030 $sth = prepare_execute_query($form, $dbh, $query, @values);
1032 $ref = $sth->fetchrow_hashref("NAME_lc");
1035 map { $form->{$_} = $ref->{$_} } keys %$ref;
1037 $form->{saved_xyznumber} = $form->{$form->{type} =~ /_quotation$/ ? "quonumber" : "ordnumber"};
1039 # set all entries for multiple ids blank that yield different information
1040 while ($ref = $sth->fetchrow_hashref("NAME_lc")) {
1041 map { $form->{$_} = '' if ($ref->{$_} ne $form->{$_}) } keys %$ref;
1044 $form->{mtime} ||= $form->{itime};
1045 $form->{lastmtime} = $form->{mtime};
1047 # if not given, fill transdate with current_date
1048 $form->{transdate} = $form->current_date($myconfig)
1049 unless $form->{transdate};
1053 if ($form->{delivery_customer_id}) {
1054 $query = qq|SELECT name FROM customer WHERE id = ?|;
1055 ($form->{delivery_customer_string}) = selectrow_query($form, $dbh, $query, $form->{delivery_customer_id});
1058 if ($form->{delivery_vendor_id}) {
1059 $query = qq|SELECT name FROM customer WHERE id = ?|;
1060 ($form->{delivery_vendor_string}) = selectrow_query($form, $dbh, $query, $form->{delivery_vendor_id});
1063 # shipto and pinted/mailed/queued status makes only sense for single id retrieve
1065 $query = qq|SELECT s.* FROM shipto s WHERE s.trans_id = ? AND s.module = 'OE'|;
1066 $sth = prepare_execute_query($form, $dbh, $query, $form->{id});
1068 $ref = $sth->fetchrow_hashref("NAME_lc");
1069 $form->{$_} = $ref->{$_} for grep { m{^shipto(?!_id$)} } keys %$ref;
1072 if ($ref->{shipto_id}) {
1073 my $cvars = CVar->get_custom_variables(
1076 trans_id => $ref->{shipto_id},
1078 $form->{"shiptocvar_$_->{name}"} = $_->{value} for @{ $cvars };
1081 # get printed, emailed and queued
1082 $query = qq|SELECT s.printed, s.emailed, s.spoolfile, s.formname FROM status s WHERE s.trans_id = ?|;
1083 $sth = prepare_execute_query($form, $dbh, $query, $form->{id});
1085 while ($ref = $sth->fetchrow_hashref("NAME_lc")) {
1086 $form->{printed} .= "$ref->{formname} " if $ref->{printed};
1087 $form->{emailed} .= "$ref->{formname} " if $ref->{emailed};
1088 $form->{queued} .= "$ref->{formname} $ref->{spoolfile} " if $ref->{spoolfile};
1091 map { $form->{$_} =~ s/ +$//g } qw(printed emailed queued);
1094 my $transdate = $form->{transdate} ? $dbh->quote($form->{transdate}) : "current_date";
1096 $form->{taxzone_id} = 0 unless ($form->{taxzone_id});
1097 unshift @values, ($form->{taxzone_id}) x 2;
1099 # retrieve individual items
1100 # this query looks up all information about the items
1101 # stuff different from the whole will not be overwritten, but saved with a suffix.
1103 qq|SELECT o.id AS orderitems_id,
1104 c1.accno AS inventory_accno, c1.new_chart_id AS inventory_new_chart, date($transdate) - c1.valid_from as inventory_valid,
1105 c2.accno AS income_accno, c2.new_chart_id AS income_new_chart, date($transdate) - c2.valid_from as income_valid,
1106 c3.accno AS expense_accno, c3.new_chart_id AS expense_new_chart, date($transdate) - c3.valid_from as expense_valid,
1107 oe.ordnumber AS ordnumber_oe, oe.transdate AS transdate_oe, oe.cusordnumber AS cusordnumber_oe,
1108 p.partnumber, p.part_type, p.listprice, o.description, o.qty,
1109 p.classification_id,
1110 o.sellprice, o.parts_id AS id, o.unit, o.discount, p.notes AS partnotes, p.part_type,
1111 o.reqdate, o.project_id, o.serialnumber, o.ship, o.lastcost,
1112 o.ordnumber, o.transdate, o.cusordnumber, o.subtotal, o.longdescription,
1113 o.price_factor_id, o.price_factor, o.marge_price_factor, o.active_price_source, o.active_discount_source,
1114 pr.projectnumber, p.formel,
1115 pg.partsgroup, o.pricegroup_id, (SELECT pricegroup FROM pricegroup WHERE id=o.pricegroup_id) as pricegroup
1117 JOIN parts p ON (o.parts_id = p.id)
1118 JOIN oe ON (o.trans_id = oe.id)
1119 LEFT JOIN chart c1 ON ((SELECT inventory_accno_id FROM buchungsgruppen WHERE id=p.buchungsgruppen_id) = c1.id)
1120 LEFT JOIN chart c2 ON ((SELECT tc.income_accno_id FROM taxzone_charts tc WHERE tc.taxzone_id = ? and tc.buchungsgruppen_id = p.buchungsgruppen_id) = c2.id)
1121 LEFT JOIN chart c3 ON ((SELECT tc.expense_accno_id FROM taxzone_charts tc WHERE tc.taxzone_id = ? and tc.buchungsgruppen_id = p.buchungsgruppen_id) = c3.id)
1122 LEFT JOIN project pr ON (o.project_id = pr.id)
1123 LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id) | .
1125 ? qq|WHERE o.trans_id = ?|
1126 : qq|WHERE o.trans_id IN (| . join(", ", map("?", @ids)) . qq|)|) .
1127 qq|ORDER BY o.trans_id, o.position|;
1129 @ids = $form->{id} ? ($form->{id}) : @ids;
1130 $sth = prepare_execute_query($form, $dbh, $query, @values);
1132 while ($ref = $sth->fetchrow_hashref("NAME_lc")) {
1133 # Retrieve custom variables.
1134 my $cvars = CVar->get_custom_variables(dbh => $dbh,
1136 sub_module => 'orderitems',
1137 trans_id => $ref->{orderitems_id},
1139 map { $ref->{"ic_cvar_$_->{name}"} = $_->{value} } @{ $cvars };
1142 if (!$ref->{"part_type"} eq 'part') {
1143 map({ delete($ref->{$_}); } qw(inventory_accno inventory_new_chart inventory_valid));
1145 # delete($ref->{"part_inventory_accno_id"});
1147 # in collective order, copy global ordnumber, transdate, cusordnumber into item scope
1148 # unless already present there
1149 # remove _oe entries afterwards
1150 map { $ref->{$_} = $ref->{"${_}_oe"} if ($ref->{$_} eq '') }
1151 qw|ordnumber transdate cusordnumber|
1153 map { delete $ref->{$_} } qw|ordnumber_oe transdate_oe cusordnumber_oe|;
1157 while ($ref->{inventory_new_chart} && ($ref->{inventory_valid} >= 0)) {
1159 qq|SELECT accno AS inventory_accno, | .
1160 qq| new_chart_id AS inventory_new_chart, | .
1161 qq| date($transdate) - valid_from AS inventory_valid | .
1162 qq|FROM chart WHERE id = $ref->{inventory_new_chart}|;
1163 ($ref->{inventory_accno}, $ref->{inventory_new_chart},
1164 $ref->{inventory_valid}) = selectrow_query($form, $dbh, $query);
1167 while ($ref->{income_new_chart} && ($ref->{income_valid} >= 0)) {
1169 qq|SELECT accno AS income_accno, | .
1170 qq| new_chart_id AS income_new_chart, | .
1171 qq| date($transdate) - valid_from AS income_valid | .
1172 qq|FROM chart WHERE id = $ref->{income_new_chart}|;
1173 ($ref->{income_accno}, $ref->{income_new_chart},
1174 $ref->{income_valid}) = selectrow_query($form, $dbh, $query);
1177 while ($ref->{expense_new_chart} && ($ref->{expense_valid} >= 0)) {
1179 qq|SELECT accno AS expense_accno, | .
1180 qq| new_chart_id AS expense_new_chart, | .
1181 qq| date($transdate) - valid_from AS expense_valid | .
1182 qq|FROM chart WHERE id = $ref->{expense_new_chart}|;
1183 ($ref->{expense_accno}, $ref->{expense_new_chart},
1184 $ref->{expense_valid}) = selectrow_query($form, $dbh, $query);
1187 # delete orderitems_id in collective orders, so that they get cloned no matter what
1188 # is this correct? or is the following meant?
1189 # remember orderitems_ids in converted_from_orderitems_ids, so that they may be linked
1190 $ref->{converted_from_orderitems_id} = delete $ref->{orderitems_id} if $is_collective_order;
1192 # get tax rates and description
1193 my $accno_id = ($form->{vc} eq "customer") ? $ref->{income_accno} : $ref->{expense_accno};
1195 qq|SELECT c.accno, t.taxdescription, t.rate, c.accno as taxnumber | .
1197 qq|LEFT JOIN chart c on (c.id = t.chart_id) | .
1198 qq|WHERE t.id IN (SELECT tk.tax_id FROM taxkeys tk | .
1199 qq| WHERE tk.chart_id = (SELECT id FROM chart WHERE accno = ?) | .
1200 qq| AND startdate <= $transdate ORDER BY startdate DESC LIMIT 1) | .
1201 qq|ORDER BY c.accno|;
1202 my $stw = prepare_execute_query($form, $dbh, $query, $accno_id);
1203 $ref->{taxaccounts} = "";
1205 while (my $ptr = $stw->fetchrow_hashref("NAME_lc")) {
1206 if (($ptr->{accno} eq "") && ($ptr->{rate} == 0)) {
1210 $ref->{taxaccounts} .= "$ptr->{accno} ";
1211 if (!($form->{taxaccounts} =~ /\Q$ptr->{accno}\E/)) {
1212 $form->{"$ptr->{accno}_rate"} = $ptr->{rate};
1213 $form->{"$ptr->{accno}_description"} = $ptr->{taxdescription};
1214 $form->{"$ptr->{accno}_taxnumber"} = $ptr->{taxnumber};
1215 $form->{taxaccounts} .= "$ptr->{accno} ";
1220 chop $ref->{taxaccounts};
1222 push @{ $form->{form_details} }, $ref;
1229 # get last name used
1230 $form->lastname_used($dbh, $myconfig, $form->{vc})
1231 unless $form->{"$form->{vc}_id"};
1235 $form->{exchangerate} = $form->get_exchangerate($dbh, $form->{currency}, $form->{transdate}, ($form->{vc} eq 'customer') ? "buy" : "sell");
1237 Common::webdav_folder($form);
1239 $self->load_periodic_invoice_config($form);
1244 sub retrieve_simple {
1245 $main::lxdebug->enter_sub();
1250 Common::check_params(\%params, qw(id));
1252 my $myconfig = \%main::myconfig;
1253 my $form = $main::form;
1255 my $dbh = $params{dbh} || $form->get_standard_dbh($myconfig);
1257 my $oe_query = qq|SELECT * FROM oe WHERE id = ?|;
1258 my $oi_query = qq|SELECT * FROM orderitems WHERE trans_id = ? ORDER BY position|;
1260 my $order = selectfirst_hashref_query($form, $dbh, $oe_query, conv_i($params{id}));
1261 $order->{orderitems} = selectall_hashref_query( $form, $dbh, $oi_query, conv_i($params{id}));
1263 $main::lxdebug->leave_sub();
1269 $main::lxdebug->enter_sub();
1271 my ($self, $myconfig, $form) = @_;
1273 # connect to database
1274 my $dbh = SL::DB->client->dbh;
1280 my $nodiscount_subtotal = 0;
1281 my $discount_subtotal = 0;
1284 my @partsgroup = ();
1287 my $subtotal_header = 0;
1288 my $subposition = 0;
1296 push(@project_ids, $form->{"globalproject_id"}) if ($form->{"globalproject_id"});
1298 $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS');
1301 foreach my $pfac (@{ $form->{ALL_PRICE_FACTORS} }) {
1302 $price_factors{$pfac->{id}} = $pfac;
1303 $pfac->{factor} *= 1;
1304 $pfac->{formatted_factor} = $form->format_amount($myconfig, $pfac->{factor});
1307 # sort items by partsgroup
1308 for $i (1 .. $form->{rowcount}) {
1310 if ($form->{"partsgroup_$i"} && $form->{groupitems}) {
1311 $partsgroup = $form->{"partsgroup_$i"};
1313 push @partsgroup, [$i, $partsgroup];
1314 push(@project_ids, $form->{"project_id_$i"}) if ($form->{"project_id_$i"});
1320 $projects = SL::DB::Manager::Project->get_all(query => [ id => \@project_ids ]);
1321 %projects_by_id = map { $_->id => $_ } @$projects;
1324 if ($projects_by_id{$form->{"globalproject_id"}}) {
1325 $form->{globalprojectnumber} = $projects_by_id{$form->{"globalproject_id"}}->projectnumber;
1326 $form->{globalprojectdescription} = $projects_by_id{$form->{"globalproject_id"}}->description;
1328 for (@{ $projects_by_id{$form->{"globalproject_id"}}->cvars_by_config }) {
1329 $form->{"project_cvar_" . $_->config->name} = $_->value_as_text;
1333 $form->{discount} = [];
1335 # get some values of parts from db on store them in extra array,
1336 # so that they can be sorted in later
1337 my %prepared_template_arrays = IC->prepare_parts_for_printing(myconfig => $myconfig, form => $form);
1338 my @prepared_arrays = keys %prepared_template_arrays;
1339 my @separate_totals = qw(non_separate_subtotal);
1341 $form->{TEMPLATE_ARRAYS} = { };
1343 my $ic_cvar_configs = CVar->get_configs(module => 'IC');
1344 my $project_cvar_configs = CVar->get_configs(module => 'Projects');
1347 qw(runningnumber number description longdescription qty qty_nofmt ship ship_nofmt unit bin
1348 partnotes serialnumber reqdate sellprice sellprice_nofmt listprice listprice_nofmt netprice netprice_nofmt
1349 discount discount_nofmt p_discount discount_sub discount_sub_nofmt nodiscount_sub nodiscount_sub_nofmt
1350 linetotal linetotal_nofmt nodiscount_linetotal nodiscount_linetotal_nofmt tax_rate projectnumber projectdescription
1351 price_factor price_factor_name partsgroup weight weight_nofmt lineweight lineweight_nofmt);
1353 push @arrays, map { "ic_cvar_$_->{name}" } @{ $ic_cvar_configs };
1354 push @arrays, map { "project_cvar_$_->{name}" } @{ $project_cvar_configs };
1356 my @tax_arrays = qw(taxbase tax taxdescription taxrate taxnumber);
1358 map { $form->{TEMPLATE_ARRAYS}->{$_} = [] } (@arrays, @tax_arrays, @prepared_arrays);
1360 my $totalweight = 0;
1362 foreach $item (sort { $a->[1] cmp $b->[1] } @partsgroup) {
1365 if ($item->[1] ne $sameitem) {
1366 push(@{ $form->{TEMPLATE_ARRAYS}->{entry_type} }, 'partsgroup');
1367 push(@{ $form->{TEMPLATE_ARRAYS}->{description} }, qq|$item->[1]|);
1368 $sameitem = $item->[1];
1370 map({ push(@{ $form->{TEMPLATE_ARRAYS}->{$_} }, "") } grep({ $_ ne "description" } (@arrays, @prepared_arrays)));
1373 $form->{"qty_$i"} = $form->parse_amount($myconfig, $form->{"qty_$i"});
1375 if ($form->{"id_$i"} != 0) {
1377 # add number, description and qty to $form->{number}, ....
1379 if ($form->{"subtotal_$i"} && !$subtotal_header) {
1380 $subtotal_header = $i;
1381 $position = int($position);
1384 } elsif ($subtotal_header) {
1386 $position = int($position);
1387 $position = $position.".".$subposition;
1389 $position = int($position);
1393 my $price_factor = $price_factors{$form->{"price_factor_id_$i"}} || { 'factor' => 1 };
1395 push(@{ $form->{TEMPLATE_ARRAYS}->{$_} }, $prepared_template_arrays{$_}[$i - 1]) for @prepared_arrays;
1397 push @{ $form->{TEMPLATE_ARRAYS}->{entry_type} }, 'normal';
1398 push @{ $form->{TEMPLATE_ARRAYS}->{runningnumber} }, $position;
1399 push @{ $form->{TEMPLATE_ARRAYS}->{number} }, $form->{"partnumber_$i"};
1400 push @{ $form->{TEMPLATE_ARRAYS}->{description} }, $form->{"description_$i"};
1401 push @{ $form->{TEMPLATE_ARRAYS}->{longdescription} }, $form->{"longdescription_$i"};
1402 push @{ $form->{TEMPLATE_ARRAYS}->{qty} }, $form->format_amount($myconfig, $form->{"qty_$i"});
1403 push @{ $form->{TEMPLATE_ARRAYS}->{qty_nofmt} }, $form->{"qty_$i"};
1404 push @{ $form->{TEMPLATE_ARRAYS}->{ship} }, $form->format_amount($myconfig, $form->{"ship_$i"});
1405 push @{ $form->{TEMPLATE_ARRAYS}->{ship_nofmt} }, $form->{"ship_$i"};
1406 push @{ $form->{TEMPLATE_ARRAYS}->{unit} }, $form->{"unit_$i"};
1407 push @{ $form->{TEMPLATE_ARRAYS}->{bin} }, $form->{"bin_$i"};
1408 push @{ $form->{TEMPLATE_ARRAYS}->{partnotes} }, $form->{"partnotes_$i"};
1409 push @{ $form->{TEMPLATE_ARRAYS}->{serialnumber} }, $form->{"serialnumber_$i"};
1410 push @{ $form->{TEMPLATE_ARRAYS}->{reqdate} }, $form->{"reqdate_$i"};
1411 push @{ $form->{TEMPLATE_ARRAYS}->{sellprice} }, $form->{"sellprice_$i"};
1412 push @{ $form->{TEMPLATE_ARRAYS}->{sellprice_nofmt} }, $form->parse_amount($myconfig, $form->{"sellprice_$i"});
1413 push @{ $form->{TEMPLATE_ARRAYS}->{listprice} }, $form->format_amount($myconfig, $form->{"listprice_$i"}, 2);
1414 push @{ $form->{TEMPLATE_ARRAYS}->{listprice_nofmt} }, $form->{"listprice_$i"};
1415 push @{ $form->{TEMPLATE_ARRAYS}->{price_factor} }, $price_factor->{formatted_factor};
1416 push @{ $form->{TEMPLATE_ARRAYS}->{price_factor_name} }, $price_factor->{description};
1417 push @{ $form->{TEMPLATE_ARRAYS}->{partsgroup} }, $form->{"partsgroup_$i"};
1419 my $sellprice = $form->parse_amount($myconfig, $form->{"sellprice_$i"});
1420 my ($dec) = ($sellprice =~ /\.(\d+)/);
1421 my $decimalplaces = max 2, length($dec);
1423 my $parsed_discount = $form->parse_amount($myconfig, $form->{"discount_$i"});
1425 my $linetotal_exact = $form->{"qty_$i"} * $sellprice * (100 - $parsed_discount) / 100 / $price_factor->{factor};
1426 my $linetotal = $form->round_amount($linetotal_exact, 2);
1428 my $nodiscount_exact_linetotal = $form->{"qty_$i"} * $sellprice / $price_factor->{factor};
1429 my $nodiscount_linetotal = $form->round_amount($nodiscount_exact_linetotal,2);
1431 my $discount = $nodiscount_linetotal - $linetotal; # is always rounded because $nodiscount_linetotal and $linetotal are rounded
1433 my $discount_round_error = $discount + ($linetotal_exact - $nodiscount_exact_linetotal); # not used
1435 $form->{"netprice_$i"} = $form->round_amount($form->{"qty_$i"} ? ($linetotal / $form->{"qty_$i"}) : 0, $decimalplaces);
1437 push @{ $form->{TEMPLATE_ARRAYS}->{netprice} }, ($form->{"netprice_$i"} != 0) ? $form->format_amount($myconfig, $form->{"netprice_$i"}, $decimalplaces) : '';
1438 push @{ $form->{TEMPLATE_ARRAYS}->{netprice_nofmt} }, ($form->{"netprice_$i"} != 0) ? $form->{"netprice_$i"} : '';
1440 $linetotal = ($linetotal != 0) ? $linetotal : '';
1442 push @{ $form->{TEMPLATE_ARRAYS}->{discount} }, ($discount != 0) ? $form->format_amount($myconfig, $discount * -1, 2) : '';
1443 push @{ $form->{TEMPLATE_ARRAYS}->{discount_nofmt} }, ($discount != 0) ? $discount * -1 : '';
1444 push @{ $form->{TEMPLATE_ARRAYS}->{p_discount} }, $form->{"discount_$i"};
1446 if ( $prepared_template_arrays{separate}[$i - 1] ) {
1447 my $pabbr = $prepared_template_arrays{separate}[$i - 1];
1448 if ( ! $form->{"separate_${pabbr}_subtotal"} ) {
1449 push @separate_totals , "separate_${pabbr}_subtotal";
1450 $form->{"separate_${pabbr}_subtotal"} = 0;
1452 $form->{"separate_${pabbr}_subtotal"} += $linetotal;
1454 $form->{non_separate_subtotal} += $linetotal;
1457 $form->{ordtotal} += $linetotal;
1458 $form->{nodiscount_total} += $nodiscount_linetotal;
1459 $form->{discount_total} += $discount;
1461 if ($subtotal_header) {
1462 $discount_subtotal += $linetotal;
1463 $nodiscount_subtotal += $nodiscount_linetotal;
1466 if ($form->{"subtotal_$i"} && $subtotal_header && ($subtotal_header != $i)) {
1467 push @{ $form->{TEMPLATE_ARRAYS}->{discount_sub} }, $form->format_amount($myconfig, $discount_subtotal, 2);
1468 push @{ $form->{TEMPLATE_ARRAYS}->{discount_sub_nofmt} }, $discount_subtotal;
1469 push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_sub} }, $form->format_amount($myconfig, $nodiscount_subtotal, 2);
1470 push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_sub_nofmt} }, $nodiscount_subtotal;
1472 $discount_subtotal = 0;
1473 $nodiscount_subtotal = 0;
1474 $subtotal_header = 0;
1477 push @{ $form->{TEMPLATE_ARRAYS}->{$_} }, "" for qw(discount_sub nodiscount_sub discount_sub_nofmt nodiscount_sub_nofmt);
1480 if (!$form->{"discount_$i"}) {
1481 $nodiscount += $linetotal;
1484 my $project = $projects_by_id{$form->{"project_id_$i"}} || SL::DB::Project->new;
1486 push @{ $form->{TEMPLATE_ARRAYS}->{linetotal} }, $form->format_amount($myconfig, $linetotal, 2);
1487 push @{ $form->{TEMPLATE_ARRAYS}->{linetotal_nofmt} }, $linetotal_exact;
1488 push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_linetotal} }, $form->format_amount($myconfig, $nodiscount_linetotal, 2);
1489 push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_linetotal_nofmt} }, $nodiscount_linetotal;
1490 push @{ $form->{TEMPLATE_ARRAYS}->{projectnumber} }, $project->projectnumber;
1491 push @{ $form->{TEMPLATE_ARRAYS}->{projectdescription} }, $project->description;
1493 my $lineweight = $form->{"qty_$i"} * $form->{"weight_$i"};
1494 $totalweight += $lineweight;
1495 push @{ $form->{TEMPLATE_ARRAYS}->{weight} }, $form->format_amount($myconfig, $form->{"weight_$i"}, 3);
1496 push @{ $form->{TEMPLATE_ARRAYS}->{weight_nofmt} }, $form->{"weight_$i"};
1497 push @{ $form->{TEMPLATE_ARRAYS}->{lineweight} }, $form->format_amount($myconfig, $lineweight, 3);
1498 push @{ $form->{TEMPLATE_ARRAYS}->{lineweight_nofmt} }, $lineweight;
1500 my ($taxamount, $taxbase);
1503 map { $taxrate += $form->{"${_}_rate"} } split(/ /, $form->{"taxaccounts_$i"});
1505 if ($form->{taxincluded}) {
1508 $taxamount = $linetotal * $taxrate / (1 + $taxrate);
1509 $taxbase = $linetotal / (1 + $taxrate);
1511 $taxamount = $linetotal * $taxrate;
1512 $taxbase = $linetotal;
1515 if ($taxamount != 0) {
1516 foreach my $accno (split / /, $form->{"taxaccounts_$i"}) {
1517 $taxaccounts{$accno} += $taxamount * $form->{"${accno}_rate"} / $taxrate;
1518 $taxbase{$accno} += $taxbase;
1522 $tax_rate = $taxrate * 100;
1523 push(@{ $form->{TEMPLATE_ARRAYS}->{tax_rate} }, qq|$tax_rate|);
1525 if ($form->{"part_type_$i"} eq 'assembly') {
1528 # get parts and push them onto the stack
1530 if ($form->{groupitems}) {
1531 $sortorder = qq|ORDER BY pg.partsgroup, a.oid|;
1533 $sortorder = qq|ORDER BY a.oid|;
1536 $query = qq|SELECT p.partnumber, p.description, p.unit, a.qty, | .
1537 qq|pg.partsgroup | .
1538 qq|FROM assembly a | .
1539 qq| JOIN parts p ON (a.parts_id = p.id) | .
1540 qq| LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id) | .
1541 qq| WHERE a.bom = '1' | .
1542 qq| AND a.id = ? | . $sortorder;
1543 @values = ($form->{"id_$i"});
1544 $sth = $dbh->prepare($query);
1545 $sth->execute(@values) || $form->dberror($query);
1547 while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
1548 if ($form->{groupitems} && $ref->{partsgroup} ne $sameitem) {
1549 map({ push(@{ $form->{TEMPLATE_ARRAYS}->{$_} }, "") } grep({ $_ ne "description" } (@arrays, @prepared_arrays)));
1550 $sameitem = ($ref->{partsgroup}) ? $ref->{partsgroup} : "--";
1551 push(@{ $form->{TEMPLATE_ARRAYS}->{entry_type} }, 'assembly-item-partsgroup');
1552 push(@{ $form->{TEMPLATE_ARRAYS}->{description} }, $sameitem);
1555 push(@{ $form->{TEMPLATE_ARRAYS}->{entry_type} }, 'assembly-item');
1556 push(@{ $form->{TEMPLATE_ARRAYS}->{description} }, $form->format_amount($myconfig, $ref->{qty} * $form->{"qty_$i"}) . qq|, $ref->{partnumber}, $ref->{description}|);
1557 map({ push(@{ $form->{TEMPLATE_ARRAYS}->{$_} }, "") } grep({ $_ ne "description" } (@arrays, @prepared_arrays)));
1562 CVar->get_non_editable_ic_cvars(form => $form,
1565 sub_module => 'orderitems',
1566 may_converted_from => ['orderitems', 'invoice']);
1568 push @{ $form->{TEMPLATE_ARRAYS}->{"ic_cvar_$_->{name}"} },
1569 CVar->format_to_template(CVar->parse($form->{"ic_cvar_$_->{name}_$i"}, $_), $_)
1570 for @{ $ic_cvar_configs };
1572 push @{ $form->{TEMPLATE_ARRAYS}->{"project_cvar_" . $_->config->name} }, $_->value_as_text for @{ $project->cvars_by_config };
1576 $form->{totalweight} = $form->format_amount($myconfig, $totalweight, 3);
1577 $form->{totalweight_nofmt} = $totalweight;
1578 my $defaults = AM->get_defaults();
1579 $form->{weightunit} = $defaults->{weightunit};
1582 foreach $item (sort keys %taxaccounts) {
1583 $tax += $taxamount = $form->round_amount($taxaccounts{$item}, 2);
1585 push(@{ $form->{TEMPLATE_ARRAYS}->{taxbase} }, $form->format_amount($myconfig, $taxbase{$item}, 2));
1586 push(@{ $form->{TEMPLATE_ARRAYS}->{taxbase_nofmt} }, $taxbase{$item});
1587 push(@{ $form->{TEMPLATE_ARRAYS}->{tax} }, $form->format_amount($myconfig, $taxamount, 2));
1588 push(@{ $form->{TEMPLATE_ARRAYS}->{tax_nofmt} }, $taxamount);
1589 push(@{ $form->{TEMPLATE_ARRAYS}->{taxrate} }, $form->format_amount($myconfig, $form->{"${item}_rate"} * 100));
1590 push(@{ $form->{TEMPLATE_ARRAYS}->{taxrate_nofmt} }, $form->{"${item}_rate"} * 100);
1591 push(@{ $form->{TEMPLATE_ARRAYS}->{taxnumber} }, $form->{"${item}_taxnumber"});
1593 my $tax_objs = SL::DB::Manager::Tax->get_objects_from_sql(
1594 sql => 'SELECT * from tax where chart_id = (SELECT id FROM chart WHERE accno = ?)',
1595 args => [ $form->{"${item}_taxnumber"} ]
1599 $tax_obj = $tax_objs->[0];
1601 my $description = $tax_obj ? $tax_obj->translated_attribute('taxdescription', $form->{language_id}, 0) : '';
1602 push(@{ $form->{TEMPLATE_ARRAYS}->{taxdescription} }, $description . q{ } . 100 * $form->{"${item}_rate"} . q{%});
1605 $form->{nodiscount_subtotal} = $form->format_amount($myconfig, $form->{nodiscount_total}, 2);
1606 $form->{discount_total} = $form->format_amount($myconfig, $form->{discount_total}, 2);
1607 $form->{nodiscount} = $form->format_amount($myconfig, $nodiscount, 2);
1608 $form->{yesdiscount} = $form->format_amount($myconfig, $form->{nodiscount_total} - $nodiscount, 2);
1610 if($form->{taxincluded}) {
1611 $form->{subtotal} = $form->format_amount($myconfig, $form->{ordtotal} - $tax, 2);
1612 $form->{subtotal_nofmt} = $form->{ordtotal} - $tax;
1614 $form->{subtotal} = $form->format_amount($myconfig, $form->{ordtotal}, 2);
1615 $form->{subtotal_nofmt} = $form->{ordtotal};
1618 my $grossamount = ($form->{taxincluded}) ? $form->{ordtotal} : $form->{ordtotal} + $tax;
1619 $form->{ordtotal} = $form->round_amount( $grossamount, 2, 1);
1620 $form->{rounding} = $form->round_amount(
1621 $form->{ordtotal} - $form->round_amount($grossamount, 2),
1626 $form->{rounding} = $form->format_amount($myconfig, $form->{rounding}, 2);
1627 $form->{quototal} = $form->{ordtotal} = $form->format_amount($myconfig, $form->{ordtotal}, 2);
1629 $form->set_payment_options($myconfig, $form->{$form->{type} =~ /_quotation/ ? 'quodate' : 'orddate'}, $form->{type});
1631 $form->{username} = $myconfig->{name};
1633 $form->{department} = SL::DB::Manager::Department->find_by(id => $form->{department_id})->description if $form->{department_id};
1634 $form->{delivery_term} = SL::DB::Manager::DeliveryTerm->find_by(id => $form->{delivery_term_id} || undef);
1635 $form->{delivery_term}->description_long($form->{delivery_term}->translated_attribute('description_long', $form->{language_id})) if $form->{delivery_term} && $form->{language_id};
1637 $form->{order} = SL::DB::Manager::Order->find_by(id => $form->{id}) if $form->{id};
1638 $form->{$_} = $form->format_amount($myconfig, $form->{$_}, 2) for @separate_totals;
1640 $main::lxdebug->leave_sub();
1649 OE.pm - Order entry module
1653 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>.
1659 =item retrieve_simple PARAMS
1661 simple OE retrieval by id. does not look up customer, vendor, units or any other stuff. only oe and orderitems.
1663 my $order = retrieve_simple(id => 2);