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., 675 Mass Ave, Cambridge, MA 02139, USA.
 
  29 #======================================================================
 
  33 #======================================================================
 
  37 use List::Util qw(max first);
 
  44 use SL::DB::PeriodicInvoicesConfig;
 
  48 use SL::HTML::Restrict;
 
  55   $main::lxdebug->enter_sub();
 
  57   my ($self, $myconfig, $form) = @_;
 
  60   my $dbh = $form->get_standard_dbh;
 
  63   my $ordnumber = 'ordnumber';
 
  69   my ($periodic_invoices_columns, $periodic_invoices_joins);
 
  71   my $rate = ($form->{vc} eq 'customer') ? 'buy' : 'sell';
 
  73   if ($form->{type} =~ /_quotation$/) {
 
  75     $ordnumber = 'quonumber';
 
  77   } elsif ($form->{type} eq 'sales_order') {
 
  78     $periodic_invoices_columns = qq| , COALESCE(pcfg.active, 'f') AS periodic_invoices |;
 
  79     $periodic_invoices_joins   = qq| LEFT JOIN periodic_invoices_configs pcfg ON (o.id = pcfg.oe_id) |;
 
  82   my $vc = $form->{vc} eq "customer" ? "customer" : "vendor";
 
  86   if ($form->{l_remaining_amount} || $form->{l_remaining_netamount}) {
 
  88       SELECT from_id, ar.amount, ar.netamount FROM (
 
  91         WHERE from_table = 'oe' AND to_table = 'ar'
 
  93         SELECT rl1.from_id, rl2.to_id
 
  95         LEFT JOIN record_links rl2 ON (rl1.to_table = rl2.from_table AND rl1.to_id = rl2.from_id)
 
  96         WHERE rl1.from_table = 'oe' AND rl2.to_table = 'ar'
 
  98       LEFT JOIN ar ON ar.id = rl.to_id
 
 100     for my $ref (@{ selectall_hashref_query($form, $dbh, $query) }) {
 
 101       $billed_amount{   $ref->{from_id}} += $ref->{amount};
 
 102       $billed_netamount{$ref->{from_id}} += $ref->{netamount};
 
 107     qq|SELECT o.id, o.ordnumber, o.transdate, o.reqdate, | .
 
 108     qq|  o.amount, ct.${vc}number, ct.name, o.netamount, o.${vc}_id, o.globalproject_id, | .
 
 109     qq|  o.closed, o.delivered, o.quonumber, o.cusordnumber, o.shippingpoint, o.shipvia, | .
 
 110     qq|  o.transaction_description, | .
 
 111     qq|  o.marge_total, o.marge_percent, | .
 
 112     qq|  ex.$rate AS exchangerate, | .
 
 113     qq|  pr.projectnumber AS globalprojectnumber, | .
 
 114     qq|  e.name AS employee, s.name AS salesman, | .
 
 115     qq|  ct.${vc}number AS vcnumber, ct.country, ct.ustid, ct.business_id,  | .
 
 116     qq|  tz.description AS taxzone | .
 
 117     $periodic_invoices_columns .
 
 118     qq|  , o.order_probability, o.expected_billing_date, (o.netamount * o.order_probability / 100) AS expected_netamount | .
 
 120     qq|JOIN $vc ct ON (o.${vc}_id = ct.id) | .
 
 121     qq|LEFT JOIN contacts cp ON (o.cp_id = cp.cp_id) | .
 
 122     qq|LEFT JOIN employee e ON (o.employee_id = e.id) | .
 
 123     qq|LEFT JOIN employee s ON (o.salesman_id = s.id) | .
 
 124     qq|LEFT JOIN exchangerate ex ON (ex.currency_id = o.currency_id | .
 
 125     qq|  AND ex.transdate = o.transdate) | .
 
 126     qq|LEFT JOIN project pr ON (o.globalproject_id = pr.id) | .
 
 127     qq|LEFT JOIN tax_zones tz ON (o.taxzone_id = tz.id) | .
 
 128     qq|$periodic_invoices_joins | .
 
 129     qq|WHERE (o.quotation = ?) |;
 
 130   push(@values, $quotation);
 
 132   my ($null, $split_department_id) = split /--/, $form->{department};
 
 133   my $department_id = $form->{department_id} || $split_department_id;
 
 134   if ($department_id) {
 
 135     $query .= qq| AND o.department_id = ?|;
 
 136     push(@values, $department_id);
 
 139   if ($form->{"project_id"}) {
 
 141       qq|AND ((globalproject_id = ?) OR EXISTS | .
 
 142       qq|  (SELECT * FROM orderitems oi | .
 
 143       qq|   WHERE oi.project_id = ? AND oi.trans_id = o.id))|;
 
 144     push(@values, conv_i($form->{"project_id"}), conv_i($form->{"project_id"}));
 
 147   if ($form->{"projectnumber"}) {
 
 149       AND ((pr.projectnumber ILIKE ?) OR EXISTS (
 
 150         SELECT * FROM orderitems oi
 
 151         LEFT JOIN project proi ON proi.id = oi.project_id
 
 152         WHERE proi.projectnumber ILIKE ? AND oi.trans_id = o.id
 
 155     push @values, "%" . $form->{"projectnumber"} . "%", "%" . $form->{"projectnumber"} . "%" ;
 
 158   if ($form->{"business_id"}) {
 
 159     $query .= " AND ct.business_id = ?";
 
 160     push(@values, $form->{"business_id"});
 
 163   if ($form->{"${vc}_id"}) {
 
 164     $query .= " AND o.${vc}_id = ?";
 
 165     push(@values, $form->{"${vc}_id"});
 
 167   } elsif ($form->{$vc}) {
 
 168     $query .= " AND ct.name ILIKE ?";
 
 169     push(@values, '%' . $form->{$vc} . '%');
 
 172   if ($form->{"cp_name"}) {
 
 173     $query .= " AND (cp.cp_name ILIKE ? OR cp.cp_givenname ILIKE ?)";
 
 174     push(@values, ('%' . $form->{"cp_name"} . '%')x2);
 
 177   if (!$main::auth->assert('sales_all_edit', 1)) {
 
 178     $query .= " AND o.employee_id = (select id from employee where login= ?)";
 
 179     push @values, $form->{login};
 
 181   if ($form->{employee_id}) {
 
 182     $query .= " AND o.employee_id = ?";
 
 183     push @values, conv_i($form->{employee_id});
 
 186   if ($form->{salesman_id}) {
 
 187     $query .= " AND o.salesman_id = ?";
 
 188     push @values, conv_i($form->{salesman_id});
 
 191   if (!$form->{open} && !$form->{closed}) {
 
 192     $query .= " AND o.id = 0";
 
 193   } elsif (!($form->{open} && $form->{closed})) {
 
 194     $query .= ($form->{open}) ? " AND o.closed = '0'" : " AND o.closed = '1'";
 
 197   if (($form->{"notdelivered"} || $form->{"delivered"}) &&
 
 198       ($form->{"notdelivered"} ne $form->{"delivered"})) {
 
 199     $query .= $form->{"delivered"} ?
 
 200       " AND o.delivered " : " AND NOT o.delivered";
 
 203   if ($form->{$ordnumber}) {
 
 204     $query .= qq| AND o.$ordnumber ILIKE ?|;
 
 205     push(@values, '%' . $form->{$ordnumber} . '%');
 
 208   if ($form->{cusordnumber}) {
 
 209     $query .= qq| AND o.cusordnumber ILIKE ?|;
 
 210     push(@values, '%' . $form->{cusordnumber} . '%');
 
 213   if($form->{transdatefrom}) {
 
 214     $query .= qq| AND o.transdate >= ?|;
 
 215     push(@values, conv_date($form->{transdatefrom}));
 
 218   if($form->{transdateto}) {
 
 219     $query .= qq| AND o.transdate <= ?|;
 
 220     push(@values, conv_date($form->{transdateto}));
 
 223   if($form->{reqdatefrom}) {
 
 224     $query .= qq| AND o.reqdate >= ?|;
 
 225     push(@values, conv_date($form->{reqdatefrom}));
 
 228   if($form->{reqdateto}) {
 
 229     $query .= qq| AND o.reqdate <= ?|;
 
 230     push(@values, conv_date($form->{reqdateto}));
 
 233   if ($form->{shippingpoint}) {
 
 234     $query .= qq| AND o.shippingpoint ILIKE ?|;
 
 235     push(@values, '%' . $form->{shippingpoint} . '%');
 
 238   if ($form->{taxzone_id} ne '') { # taxzone_id could be 0
 
 239     $query .= qq| AND tz.id = ?|;
 
 240     push(@values, $form->{taxzone_id});
 
 243   if ($form->{transaction_description}) {
 
 244     $query .= qq| AND o.transaction_description ILIKE ?|;
 
 245     push(@values, '%' . $form->{transaction_description} . '%');
 
 248   if ($form->{periodic_invoices_active} ne $form->{periodic_invoices_inactive}) {
 
 249     my $not  = $form->{periodic_invoices_inactive} ? 'NOT' : '';
 
 250     $query  .= qq| AND ${not} COALESCE(pcfg.active, 'f')|;
 
 253   if ($form->{reqdate_unset_or_old}) {
 
 254     $query .= qq| AND ((o.reqdate IS NULL) OR (o.reqdate < date_trunc('month', current_date)))|;
 
 257   if (($form->{order_probability_value} || '') ne '') {
 
 258     my $op  = $form->{order_probability_value} eq 'le' ? '<=' : '>=';
 
 259     $query .= qq| AND (o.order_probability ${op} ?)|;
 
 260     push @values, $form->{order_probability_value};
 
 263   if ($form->{expected_billing_date_from}) {
 
 264     $query .= qq| AND (o.expected_billing_date >= ?)|;
 
 265     push @values, conv_date($form->{expected_billing_date_from});
 
 268   if ($form->{expected_billing_date_to}) {
 
 269     $query .= qq| AND (o.expected_billing_date <= ?)|;
 
 270     push @values, conv_date($form->{expected_billing_date_to});
 
 273   my $sortdir   = !defined $form->{sortdir} ? 'ASC' : $form->{sortdir} ? 'ASC' : 'DESC';
 
 274   my $sortorder = join(', ', map { "${_} ${sortdir} " } ("o.id", $form->sort_columns("transdate", $ordnumber, "name")));
 
 275   my %allowed_sort_columns = (
 
 276     "transdate"               => "o.transdate",
 
 277     "reqdate"                 => "o.reqdate",
 
 279     "ordnumber"               => "o.ordnumber",
 
 280     "cusordnumber"            => "o.cusordnumber",
 
 281     "quonumber"               => "o.quonumber",
 
 283     "employee"                => "e.name",
 
 284     "salesman"                => "s.name",
 
 285     "shipvia"                 => "o.shipvia",
 
 286     "transaction_description" => "o.transaction_description",
 
 287     "shippingpoint"           => "o.shippingpoint",
 
 288     "taxzone"                 => "tz.description",
 
 290   if ($form->{sort} && grep($form->{sort}, keys(%allowed_sort_columns))) {
 
 291     $sortorder = $allowed_sort_columns{$form->{sort}} . " ${sortdir}";
 
 293   $query .= qq| ORDER by | . $sortorder;
 
 295   my $sth = $dbh->prepare($query);
 
 296   $sth->execute(@values) ||
 
 297     $form->dberror($query . " (" . join(", ", @values) . ")");
 
 301   while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
 
 302     $ref->{billed_amount}    = $billed_amount{$ref->{id}};
 
 303     $ref->{billed_netamount} = $billed_netamount{$ref->{id}};
 
 304     $ref->{remaining_amount} = $ref->{amount} - $ref->{billed_amount};
 
 305     $ref->{remaining_netamount} = $ref->{netamount} - $ref->{billed_netamount};
 
 306     $ref->{exchangerate} = 1 unless $ref->{exchangerate};
 
 307     push @{ $form->{OE} }, $ref if $ref->{id} != $id{ $ref->{id} };
 
 308     $id{ $ref->{id} } = $ref->{id};
 
 313   $main::lxdebug->leave_sub();
 
 316 sub transactions_for_todo_list {
 
 317   $main::lxdebug->enter_sub();
 
 322   my $myconfig = \%main::myconfig;
 
 323   my $form     = $main::form;
 
 325   my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
 
 327   my $query    = qq|SELECT id FROM employee WHERE login = ?|;
 
 328   my ($e_id)   = selectrow_query($form, $dbh, $query, $form->{login});
 
 331     qq|SELECT oe.id, oe.transdate, oe.reqdate, oe.quonumber, oe.transaction_description, oe.amount,
 
 332          CASE WHEN (COALESCE(oe.customer_id, 0) = 0) THEN 'vendor' ELSE 'customer' END AS vc,
 
 337        LEFT JOIN customer c ON (oe.customer_id = c.id)
 
 338        LEFT JOIN vendor v   ON (oe.vendor_id   = v.id)
 
 339        LEFT JOIN employee e ON (oe.employee_id = e.id)
 
 340        WHERE (COALESCE(quotation, FALSE) = TRUE)
 
 341          AND (COALESCE(closed,    FALSE) = FALSE)
 
 342          AND ((oe.employee_id = ?) OR (oe.salesman_id = ?))
 
 343          AND NOT (oe.reqdate ISNULL)
 
 344          AND (oe.reqdate < current_date)
 
 347   my $quotations = selectall_hashref_query($form, $dbh, $query, $e_id, $e_id);
 
 349   $main::lxdebug->leave_sub();
 
 355   $main::lxdebug->enter_sub();
 
 357   my ($self, $myconfig, $form) = @_;
 
 359   # connect to database, turn off autocommit
 
 360   my $dbh = $form->get_standard_dbh;
 
 361   my $restricter = SL::HTML::Restrict->create;
 
 363   my ($query, @values, $sth, $null);
 
 364   my $exchangerate = 0;
 
 366   my $all_units = AM->retrieve_units($myconfig, $form);
 
 367   $form->{all_units} = $all_units;
 
 369   my $ic_cvar_configs = CVar->get_configs(module => 'IC',
 
 372   $form->{employee_id} = (split /--/, $form->{employee})[1] if !$form->{employee_id};
 
 373   unless ($form->{employee_id}) {
 
 374     $form->get_employee($dbh);
 
 377   my $ml = ($form->{type} eq 'sales_order') ? 1 : -1;
 
 379   my $number_field         = $form->{type} =~ m{order} ? 'ordnumber' : 'quonumber';
 
 380   my $trans_number         = SL::TransNumber->new(type => $form->{type}, dbh => $dbh, number => $form->{$number_field}, id => $form->{id});
 
 381   $form->{$number_field} ||= $trans_number->create_unique;
 
 384     $query = qq|DELETE FROM custom_variables
 
 385                 WHERE (config_id IN (SELECT id FROM custom_variable_configs WHERE module = 'IC'))
 
 386                   AND (sub_module = 'orderitems')
 
 387                   AND (trans_id IN (SELECT id FROM orderitems WHERE trans_id = ?))|;
 
 388     do_query($form, $dbh, $query, $form->{id});
 
 390     $query = qq|DELETE FROM shipto | .
 
 391              qq|WHERE trans_id = ? AND module = 'OE'|;
 
 392     do_query($form, $dbh, $query, $form->{id});
 
 396     $query = qq|SELECT nextval('id')|;
 
 397     ($form->{id}) = selectrow_query($form, $dbh, $query);
 
 399     $query = qq|INSERT INTO oe (id, ordnumber, employee_id, currency_id, taxzone_id) VALUES (?, '', ?, (SELECT currency_id FROM defaults), ?)|;
 
 400     do_query($form, $dbh, $query, $form->{id}, $form->{employee_id}, $form->{taxzone_id});
 
 417   my @processed_orderitems;
 
 419   $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS');
 
 420   my %price_factors = map { $_->{id} => $_->{factor} } @{ $form->{ALL_PRICE_FACTORS} };
 
 423   for my $i (1 .. $form->{rowcount}) {
 
 425     map({ $form->{"${_}_$i"} = $form->parse_amount($myconfig, $form->{"${_}_$i"}) } qw(qty ship));
 
 427     if ($form->{"id_$i"}) {
 
 430       $query = qq|SELECT unit FROM parts WHERE id = ?|;
 
 431       my ($item_unit) = selectrow_query($form, $dbh, $query, $form->{"id_$i"});
 
 434       if (defined($all_units->{$item_unit}->{factor}) &&
 
 435           (($all_units->{$item_unit}->{factor} * 1) != 0)) {
 
 436         $basefactor = $all_units->{$form->{"unit_$i"}}->{factor} / $all_units->{$item_unit}->{factor};
 
 438       my $baseqty = $form->{"qty_$i"} * $basefactor;
 
 440       $form->{"marge_percent_$i"} = $form->parse_amount($myconfig, $form->{"marge_percent_$i"}) * 1;
 
 441       $form->{"marge_absolut_$i"} = $form->parse_amount($myconfig, $form->{"marge_absolut_$i"}) * 1;
 
 443       $form->{"lastcost_$i"} = $form->parse_amount($myconfig, $form->{"lastcost_$i"});
 
 445       # keep entered selling price
 
 447         $form->parse_amount($myconfig, $form->{"sellprice_$i"});
 
 449       my ($dec) = ($fxsellprice =~ /\.(\d+)/);
 
 451       my $decimalplaces = ($dec > 2) ? $dec : 2;
 
 453       # undo discount formatting
 
 454       $form->{"discount_$i"} = $form->parse_amount($myconfig, $form->{"discount_$i"}) / 100;
 
 457       $form->{"sellprice_$i"} = $fxsellprice * (1 - $form->{"discount_$i"});
 
 459       # round linetotal at least to 2 decimal places
 
 460       $price_factor = $price_factors{ $form->{"price_factor_id_$i"} } || 1;
 
 461       $linetotal    = $form->round_amount($form->{"sellprice_$i"} * $form->{"qty_$i"} / $price_factor, 2);
 
 463       $form->{"inventory_accno_$i"} *= 1;
 
 464       $form->{"expense_accno_$i"}   *= 1;
 
 466       @taxaccounts = split(/ /, $form->{"taxaccounts_$i"});
 
 470       map { $taxrate += $form->{"${_}_rate"} } @taxaccounts;
 
 472       if ($form->{taxincluded}) {
 
 473         $taxamount = $linetotal * $taxrate / (1 + $taxrate);
 
 474         $taxbase   = $linetotal - $taxamount;
 
 476         # we are not keeping a natural price, do not round
 
 477         $form->{"sellprice_$i"} =
 
 478           $form->{"sellprice_$i"} * (1 / (1 + $taxrate));
 
 480         $taxamount = $linetotal * $taxrate;
 
 481         $taxbase   = $linetotal;
 
 484       if ($form->round_amount($taxrate, 7) == 0) {
 
 485         if ($form->{taxincluded}) {
 
 486           foreach my $item (@taxaccounts) {
 
 487             $taxamount = $form->round_amount($linetotal * $form->{"${item}_rate"} / (1 + abs($form->{"${item}_rate"})), 2);
 
 488             $taxaccounts{$item} += $taxamount;
 
 489             $taxdiff            += $taxamount;
 
 490             $taxbase{$item}     += $taxbase;
 
 492           $taxaccounts{ $taxaccounts[0] } += $taxdiff;
 
 494           foreach my $item (@taxaccounts) {
 
 495             $taxaccounts{$item} += $linetotal * $form->{"${item}_rate"};
 
 496             $taxbase{$item}     += $taxbase;
 
 500         foreach my $item (@taxaccounts) {
 
 501           $taxaccounts{$item} += $taxamount * $form->{"${item}_rate"} / $taxrate;
 
 502           $taxbase{$item} += $taxbase;
 
 506       $netamount += $form->{"sellprice_$i"} * $form->{"qty_$i"} / $price_factor;
 
 508       $reqdate = ($form->{"reqdate_$i"}) ? $form->{"reqdate_$i"} : undef;
 
 510       # Get pricegroup_id and save it. Unfortunately the interface
 
 511       # also uses ID "0" for signalling that none is selected, but "0"
 
 512       # must not be stored in the database. Therefore we cannot simply
 
 514       ($null, my $pricegroup_id) = split(/--/, $form->{"sellprice_pg_$i"});
 
 516       $pricegroup_id  = undef if !$pricegroup_id;
 
 518       # save detail record in orderitems table
 
 519       if (! $form->{"orderitems_id_$i"}) {
 
 520         $query = qq|SELECT nextval('orderitemsid')|;
 
 521         ($form->{"orderitems_id_$i"}) = selectrow_query($form, $dbh, $query);
 
 523         $query = qq|INSERT INTO orderitems (id) VALUES (?)|;
 
 524         do_query($form, $dbh, $query, $form->{"orderitems_id_$i"});
 
 526       my $orderitems_id = $form->{"orderitems_id_$i"};
 
 527       push @processed_orderitems, $orderitems_id;
 
 530          UPDATE orderitems SET
 
 531           trans_id = ?, parts_id = ?, description = ?, longdescription = ?, qty = ?, base_qty = ?,
 
 532           sellprice = ?, discount = ?, unit = ?, reqdate = ?, project_id = ?, serialnumber = ?, ship = ?,
 
 533           pricegroup_id = ?, ordnumber = ?, transdate = ?, cusordnumber = ?, subtotal = ?,
 
 534           marge_percent = ?, marge_total = ?, lastcost = ?, price_factor_id = ?,
 
 535           active_price_source = ?, active_discount_source = ?,
 
 536           price_factor = (SELECT factor FROM price_factors WHERE id = ?), marge_price_factor = ?
 
 540            conv_i($form->{id}), conv_i($form->{"id_$i"}),
 
 541            $form->{"description_$i"}, $restricter->process($form->{"longdescription_$i"}),
 
 542            $form->{"qty_$i"}, $baseqty,
 
 543            $fxsellprice, $form->{"discount_$i"},
 
 544            $form->{"unit_$i"}, conv_date($reqdate), conv_i($form->{"project_id_$i"}),
 
 545            $form->{"serialnumber_$i"}, $form->{"ship_$i"}, $pricegroup_id,
 
 546            $form->{"ordnumber_$i"}, conv_date($form->{"transdate_$i"}),
 
 547            $form->{"cusordnumber_$i"}, $form->{"subtotal_$i"} ? 't' : 'f',
 
 548            $form->{"marge_percent_$i"}, $form->{"marge_absolut_$i"},
 
 549            $form->{"lastcost_$i"}, conv_i($form->{"price_factor_id_$i"}),
 
 550            $form->{"active_price_source_$i"}, $form->{"active_discount_source_$i"},
 
 551            conv_i($form->{"price_factor_id_$i"}), conv_i($form->{"marge_price_factor_$i"}),
 
 552            conv_i($orderitems_id),
 
 555       do_query($form, $dbh, $query, @values);
 
 557       $form->{"sellprice_$i"} = $fxsellprice;
 
 558       $form->{"discount_$i"} *= 100;
 
 560       CVar->save_custom_variables(module       => 'IC',
 
 561                                   sub_module   => 'orderitems',
 
 562                                   trans_id     => $orderitems_id,
 
 563                                   configs      => $ic_cvar_configs,
 
 565                                   name_prefix  => 'ic_',
 
 566                                   name_postfix => "_$i",
 
 570   # search for orphaned ids
 
 571   $query  = sprintf 'SELECT id FROM orderitems WHERE trans_id = ? AND NOT id IN (%s)', join ', ', ("?") x scalar @processed_orderitems;
 
 572   @values = (conv_i($form->{id}), map { conv_i($_) } @processed_orderitems);
 
 573   my @orphaned_ids = map { $_->{id} } selectall_hashref_query($form, $dbh, $query, @values);
 
 575   if (scalar @orphaned_ids) {
 
 576     # clean up orderitems
 
 577     $query  = sprintf 'DELETE FROM orderitems WHERE id IN (%s)', join ', ', ("?") x scalar @orphaned_ids;
 
 578     do_query($form, $dbh, $query, @orphaned_ids);
 
 581   $reqdate = ($form->{reqdate}) ? $form->{reqdate} : undef;
 
 585   map { $tax += $form->round_amount($taxaccounts{$_}, 2) } keys %taxaccounts;
 
 587   $amount = $form->round_amount($netamount + $tax, 2);
 
 588   $netamount = $form->round_amount($netamount, 2);
 
 590   if ($form->{currency} eq $form->{defaultcurrency}) {
 
 591     $form->{exchangerate} = 1;
 
 593     $exchangerate = $form->check_exchangerate($myconfig, $form->{currency}, $form->{transdate}, ($form->{vc} eq 'customer') ? 'buy' : 'sell');
 
 596   $form->{exchangerate} = $exchangerate || $form->parse_amount($myconfig, $form->{exchangerate});
 
 598   my $quotation = $form->{type} =~ /_order$/ ? 'f' : 't';
 
 600   ($null, $form->{department_id}) = split(/--/, $form->{department}) if $form->{department};
 
 605          ordnumber = ?, quonumber = ?, cusordnumber = ?, transdate = ?, vendor_id = ?,
 
 606          customer_id = ?, amount = ?, netamount = ?, reqdate = ?, taxincluded = ?,
 
 607          shippingpoint = ?, shipvia = ?, notes = ?, intnotes = ?, currency_id = (SELECT id FROM currencies WHERE name=?), closed = ?,
 
 608          delivered = ?, proforma = ?, quotation = ?, department_id = ?, language_id = ?,
 
 609          taxzone_id = ?, shipto_id = ?, payment_id = ?, delivery_vendor_id = ?, delivery_customer_id = ?,delivery_term_id = ?,
 
 610          globalproject_id = ?, employee_id = ?, salesman_id = ?, cp_id = ?, transaction_description = ?, marge_total = ?, marge_percent = ?
 
 611          , order_probability = ?, expected_billing_date = ?
 
 614   @values = ($form->{ordnumber} || '', $form->{quonumber},
 
 615              $form->{cusordnumber}, conv_date($form->{transdate}),
 
 616              conv_i($form->{vendor_id}), conv_i($form->{customer_id}),
 
 617              $amount, $netamount, conv_date($reqdate),
 
 618              $form->{taxincluded} ? 't' : 'f', $form->{shippingpoint},
 
 619              $form->{shipvia}, $form->{notes}, $form->{intnotes},
 
 620              $form->{currency}, $form->{closed} ? 't' : 'f',
 
 621              $form->{delivered} ? "t" : "f", $form->{proforma} ? 't' : 'f',
 
 622              $quotation, conv_i($form->{department_id}),
 
 623              conv_i($form->{language_id}), conv_i($form->{taxzone_id}),
 
 624              conv_i($form->{shipto_id}), conv_i($form->{payment_id}),
 
 625              conv_i($form->{delivery_vendor_id}),
 
 626              conv_i($form->{delivery_customer_id}),
 
 627              conv_i($form->{delivery_term_id}),
 
 628              conv_i($form->{globalproject_id}), conv_i($form->{employee_id}),
 
 629              conv_i($form->{salesman_id}), conv_i($form->{cp_id}),
 
 630              $form->{transaction_description},
 
 631              $form->{marge_total} * 1, $form->{marge_percent} * 1,
 
 632              $form->{order_probability} * 1, conv_date($form->{expected_billing_date}),
 
 633              conv_i($form->{id}));
 
 634   do_query($form, $dbh, $query, @values);
 
 636   $form->{ordtotal} = $amount;
 
 638   $form->{name} = $form->{ $form->{vc} };
 
 639   $form->{name} =~ s/--\Q$form->{"$form->{vc}_id"}\E//;
 
 642   if (!$form->{shipto_id}) {
 
 643     $form->add_shipto($dbh, $form->{id}, "OE");
 
 646   # save printed, emailed, queued
 
 647   $form->save_status($dbh);
 
 649   # Link this record to the records it was created from.
 
 650   $form->{convert_from_oe_ids} =~ s/^\s+//;
 
 651   $form->{convert_from_oe_ids} =~ s/\s+$//;
 
 652   my @convert_from_oe_ids      =  split m/\s+/, $form->{convert_from_oe_ids};
 
 653   delete $form->{convert_from_oe_ids};
 
 655   if (scalar @convert_from_oe_ids) {
 
 656     RecordLinks->create_links('dbh'        => $dbh,
 
 658                               'from_table' => 'oe',
 
 659                               'from_ids'   => \@convert_from_oe_ids,
 
 661                               'to_id'      => $form->{id},
 
 664     $self->_close_quotations_rfqs('dbh'     => $dbh,
 
 665                                   'from_id' => \@convert_from_oe_ids,
 
 666                                   'to_id'   => $form->{id});
 
 669   if (($form->{currency} ne $form->{defaultcurrency}) && !$exchangerate) {
 
 670     if ($form->{vc} eq 'customer') {
 
 671       $form->update_exchangerate($dbh, $form->{currency}, $form->{transdate}, $form->{exchangerate}, 0);
 
 673     if ($form->{vc} eq 'vendor') {
 
 674       $form->update_exchangerate($dbh, $form->{currency}, $form->{transdate}, 0, $form->{exchangerate});
 
 678   $form->{saved_xyznumber} = $form->{$form->{type} =~ /_quotation$/ ?
 
 679                                        "quonumber" : "ordnumber"};
 
 681   Common::webdav_folder($form);
 
 683   my $rc = $dbh->commit;
 
 685   $self->save_periodic_invoices_config(dbh         => $dbh,
 
 686                                        oe_id       => $form->{id},
 
 687                                        config_yaml => $form->{periodic_invoices_config})
 
 688     if ($form->{type} eq 'sales_order');
 
 690   $main::lxdebug->leave_sub();
 
 695 sub save_periodic_invoices_config {
 
 696   my ($self, %params) = @_;
 
 698   return if !$params{oe_id};
 
 700   my $config = $params{config_yaml} ? YAML::Load($params{config_yaml}) : undef;
 
 701   return if 'HASH' ne ref $config;
 
 703   my $obj  = SL::DB::Manager::PeriodicInvoicesConfig->find_by(oe_id => $params{oe_id})
 
 704           || SL::DB::PeriodicInvoicesConfig->new(oe_id => $params{oe_id});
 
 705   $obj->update_attributes(%{ $config });
 
 708 sub load_periodic_invoice_config {
 
 712   delete $form->{periodic_invoices_config};
 
 715     my $config_obj = SL::DB::Manager::PeriodicInvoicesConfig->find_by(oe_id => $form->{id});
 
 718       my $config = { map { $_ => $config_obj->$_ } qw(active terminated periodicity start_date_as_date end_date_as_date first_billing_date_as_date extend_automatically_by ar_chart_id
 
 719                                                       print printer_id copies) };
 
 720       $form->{periodic_invoices_config} = YAML::Dump($config);
 
 725 sub _close_quotations_rfqs {
 
 726   $main::lxdebug->enter_sub();
 
 731   Common::check_params(\%params, qw(from_id to_id));
 
 733   my $myconfig = \%main::myconfig;
 
 734   my $form     = $main::form;
 
 736   my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
 
 738   my $query    = qq|SELECT quotation FROM oe WHERE id = ?|;
 
 739   my $sth      = prepare_query($form, $dbh, $query);
 
 741   do_statement($form, $sth, $query, conv_i($params{to_id}));
 
 743   my ($quotation) = $sth->fetchrow_array();
 
 746     $main::lxdebug->leave_sub();
 
 752   foreach my $from_id (@{ $params{from_id} }) {
 
 753     $from_id = conv_i($from_id);
 
 754     do_statement($form, $sth, $query, $from_id);
 
 755     ($quotation) = $sth->fetchrow_array();
 
 756     push @close_ids, $from_id if ($quotation);
 
 761   if (scalar @close_ids) {
 
 762     $query = qq|UPDATE oe SET closed = TRUE WHERE id IN (| . join(', ', ('?') x scalar @close_ids) . qq|)|;
 
 763     do_query($form, $dbh, $query, @close_ids);
 
 765     $dbh->commit() unless ($params{dbh});
 
 768   $main::lxdebug->leave_sub();
 
 772   $main::lxdebug->enter_sub();
 
 774   my ($self, $myconfig, $form) = @_;
 
 776   my $rc = SL::DB::Order->new->db->with_transaction(sub {
 
 777     my @spoolfiles = grep { $_ } map { $_->spoolfile } @{ SL::DB::Manager::Status->get_all(where => [ trans_id => $form->{id} ]) };
 
 779     SL::DB::Order->new(id => $form->{id})->delete;
 
 781     my $spool = $::lx_office_conf{paths}->{spool};
 
 782     unlink map { "$spool/$_" } @spoolfiles if $spool;
 
 787   $main::lxdebug->leave_sub();
 
 793   $main::lxdebug->enter_sub();
 
 795   my ($self, $myconfig, $form) = @_;
 
 797   # connect to database
 
 798   my $dbh = $form->get_standard_dbh;
 
 800   my ($query, $query_add, @values, @ids, $sth);
 
 802   # translate the ids (given by id_# and trans_id_#) into one array of ids, so we can join them later
 
 804     push @ids, $form->{"trans_id_$_"}
 
 805       if ($form->{"multi_id_$_"} and $form->{"trans_id_$_"})
 
 806   } (1 .. $form->{"rowcount"});
 
 808   if ($form->{rowcount} && scalar @ids) {
 
 809     $form->{convert_from_oe_ids} = join ' ', @ids;
 
 812   # if called in multi id mode, and still only got one id, switch back to single id
 
 813   if ($form->{"rowcount"} and $#ids == 0) {
 
 814     $form->{"id"} = $ids[0];
 
 818   # and remember for the rest of the function
 
 819   my $is_collective_order = scalar @ids;
 
 822     my $wday         = (localtime(time))[6];
 
 823     my $next_workday = $wday == 5 ? 3 : $wday == 6 ? 2 : 1;
 
 825     # if we have a client configured interval for sales quotation, we add this
 
 826     $next_workday   += $::instance_conf->get_reqdate_interval if ($::instance_conf->get_reqdate_interval &&
 
 827                                                                     $form->{type} eq 'sales_quotation' );
 
 829     $query_add       = qq|, current_date AS transdate, date(current_date + interval '${next_workday} days') AS reqdate|;
 
 832   # get default accounts
 
 833   $query = qq|SELECT (SELECT c.accno FROM chart c WHERE d.inventory_accno_id = c.id) AS inventory_accno,
 
 834                      (SELECT c.accno FROM chart c WHERE d.income_accno_id    = c.id) AS income_accno,
 
 835                      (SELECT c.accno FROM chart c WHERE d.expense_accno_id   = c.id) AS expense_accno,
 
 836                      (SELECT c.accno FROM chart c WHERE d.fxgain_accno_id    = c.id) AS fxgain_accno,
 
 837                      (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id    = c.id) AS fxloss_accno
 
 840   my $ref = selectfirst_hashref_query($form, $dbh, $query);
 
 841   map { $form->{$_} = $ref->{$_} } keys %$ref;
 
 843   $form->{currency} = $form->get_default_currency($myconfig);
 
 845   # set reqdate if this is an invoice->order conversion. If someone knows a better check to ensure
 
 846   # we come from invoices, feel free.
 
 847   $form->{reqdate} = $form->{deliverydate}
 
 848     if (    $form->{deliverydate}
 
 849         and $form->{callback} =~ /action=ar_transactions/);
 
 851   my $vc = $form->{vc} eq "customer" ? "customer" : "vendor";
 
 853   if ($form->{id} or @ids) {
 
 855     # retrieve order for single id
 
 856     # NOTE: this query is intended to fetch all information only ONCE.
 
 857     # so if any of these infos is important (or even different) for any item,
 
 858     # it will be killed out and then has to be fetched from the item scope query further down
 
 860       qq|SELECT o.cp_id, o.ordnumber, o.transdate, o.reqdate,
 
 861            o.taxincluded, o.shippingpoint, o.shipvia, o.notes, o.intnotes,
 
 862            (SELECT cu.name FROM currencies cu WHERE cu.id=o.currency_id) AS currency, e.name AS employee, o.employee_id, o.salesman_id,
 
 863            o.${vc}_id, cv.name AS ${vc}, o.amount AS invtotal,
 
 864            o.closed, o.reqdate, o.quonumber, o.department_id, o.cusordnumber,
 
 865            d.description AS department, o.payment_id, o.language_id, o.taxzone_id,
 
 866            o.delivery_customer_id, o.delivery_vendor_id, o.proforma, o.shipto_id,
 
 867            o.globalproject_id, o.delivered, o.transaction_description, o.delivery_term_id
 
 868            , o.order_probability, o.expected_billing_date
 
 870          JOIN ${vc} cv ON (o.${vc}_id = cv.id)
 
 871          LEFT JOIN employee e ON (o.employee_id = e.id)
 
 872          LEFT JOIN department d ON (o.department_id = d.id) | .
 
 875          : "WHERE o.id IN (" . join(', ', map("? ", @ids)) . ")"
 
 877     @values = $form->{id} ? ($form->{id}) : @ids;
 
 878     $sth = prepare_execute_query($form, $dbh, $query, @values);
 
 880     $ref = $sth->fetchrow_hashref("NAME_lc");
 
 883       map { $form->{$_} = $ref->{$_} } keys %$ref;
 
 885       $form->{saved_xyznumber} = $form->{$form->{type} =~ /_quotation$/ ? "quonumber" : "ordnumber"};
 
 887       # set all entries for multiple ids blank that yield different information
 
 888       while ($ref = $sth->fetchrow_hashref("NAME_lc")) {
 
 889         map { $form->{$_} = '' if ($ref->{$_} ne $form->{$_}) } keys %$ref;
 
 893     # if not given, fill transdate with current_date
 
 894     $form->{transdate} = $form->current_date($myconfig)
 
 895       unless $form->{transdate};
 
 899     if ($form->{delivery_customer_id}) {
 
 900       $query = qq|SELECT name FROM customer WHERE id = ?|;
 
 901       ($form->{delivery_customer_string}) = selectrow_query($form, $dbh, $query, $form->{delivery_customer_id});
 
 904     if ($form->{delivery_vendor_id}) {
 
 905       $query = qq|SELECT name FROM customer WHERE id = ?|;
 
 906       ($form->{delivery_vendor_string}) = selectrow_query($form, $dbh, $query, $form->{delivery_vendor_id});
 
 909     # shipto and pinted/mailed/queued status makes only sense for single id retrieve
 
 911       $query = qq|SELECT s.* FROM shipto s WHERE s.trans_id = ? AND s.module = 'OE'|;
 
 912       $sth = prepare_execute_query($form, $dbh, $query, $form->{id});
 
 914       $ref = $sth->fetchrow_hashref("NAME_lc");
 
 916       map { $form->{$_} = $ref->{$_} } keys %$ref;
 
 919       # get printed, emailed and queued
 
 920       $query = qq|SELECT s.printed, s.emailed, s.spoolfile, s.formname FROM status s WHERE s.trans_id = ?|;
 
 921       $sth = prepare_execute_query($form, $dbh, $query, $form->{id});
 
 923       while ($ref = $sth->fetchrow_hashref("NAME_lc")) {
 
 924         $form->{printed} .= "$ref->{formname} " if $ref->{printed};
 
 925         $form->{emailed} .= "$ref->{formname} " if $ref->{emailed};
 
 926         $form->{queued}  .= "$ref->{formname} $ref->{spoolfile} " if $ref->{spoolfile};
 
 929       map { $form->{$_} =~ s/ +$//g } qw(printed emailed queued);
 
 932     my $transdate = $form->{transdate} ? $dbh->quote($form->{transdate}) : "current_date";
 
 934     $form->{taxzone_id} = 0 unless ($form->{taxzone_id});
 
 936     # retrieve individual items
 
 937     # this query looks up all information about the items
 
 938     # stuff different from the whole will not be overwritten, but saved with a suffix.
 
 940       qq|SELECT o.id AS orderitems_id,
 
 941            c1.accno AS inventory_accno, c1.new_chart_id AS inventory_new_chart, date($transdate) - c1.valid_from as inventory_valid,
 
 942            c2.accno AS income_accno,    c2.new_chart_id AS income_new_chart,    date($transdate) - c2.valid_from as income_valid,
 
 943            c3.accno AS expense_accno,   c3.new_chart_id AS expense_new_chart,   date($transdate) - c3.valid_from as expense_valid,
 
 944            oe.ordnumber AS ordnumber_oe, oe.transdate AS transdate_oe, oe.cusordnumber AS cusordnumber_oe,
 
 945            p.partnumber, p.assembly, p.listprice, o.description, o.qty,
 
 946            o.sellprice, o.parts_id AS id, o.unit, o.discount, p.notes AS partnotes, p.inventory_accno_id AS part_inventory_accno_id,
 
 947            o.reqdate, o.project_id, o.serialnumber, o.ship, o.lastcost,
 
 948            o.ordnumber, o.transdate, o.cusordnumber, o.subtotal, o.longdescription,
 
 949            o.price_factor_id, o.price_factor, o.marge_price_factor, o.active_price_source, o.active_discount_source,
 
 950            pr.projectnumber, p.formel,
 
 951            pg.partsgroup, o.pricegroup_id, (SELECT pricegroup FROM pricegroup WHERE id=o.pricegroup_id) as pricegroup
 
 953          JOIN parts p ON (o.parts_id = p.id)
 
 954          JOIN oe ON (o.trans_id = oe.id)
 
 955          LEFT JOIN chart c1 ON ((SELECT inventory_accno_id                   FROM buchungsgruppen WHERE id=p.buchungsgruppen_id) = c1.id)
 
 956          LEFT JOIN chart c2 ON ((SELECT tc.income_accno_id FROM taxzone_charts tc WHERE tc.taxzone_id = '$form->{taxzone_id}' and tc.buchungsgruppen_id = p.buchungsgruppen_id) = c2.id)
 
 957          LEFT JOIN chart c3 ON ((SELECT tc.expense_accno_id FROM taxzone_charts tc WHERE tc.taxzone_id = '$form->{taxzone_id}' and tc.buchungsgruppen_id = p.buchungsgruppen_id) = c3.id)
 
 958          LEFT JOIN project pr ON (o.project_id = pr.id)
 
 959          LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id) | .
 
 961        ? qq|WHERE o.trans_id = ?|
 
 962        : qq|WHERE o.trans_id IN (| . join(", ", map("?", @ids)) . qq|)|) .
 
 965     @ids = $form->{id} ? ($form->{id}) : @ids;
 
 966     $sth = prepare_execute_query($form, $dbh, $query, @values);
 
 968     while ($ref = $sth->fetchrow_hashref("NAME_lc")) {
 
 969       # Retrieve custom variables.
 
 970       my $cvars = CVar->get_custom_variables(dbh        => $dbh,
 
 972                                              sub_module => 'orderitems',
 
 973                                              trans_id   => $ref->{orderitems_id},
 
 975       map { $ref->{"ic_cvar_$_->{name}"} = $_->{value} } @{ $cvars };
 
 978       if (!$ref->{"part_inventory_accno_id"}) {
 
 979         map({ delete($ref->{$_}); } qw(inventory_accno inventory_new_chart inventory_valid));
 
 981       delete($ref->{"part_inventory_accno_id"});
 
 983       # in collective order, copy global ordnumber, transdate, cusordnumber into item scope
 
 984       #   unless already present there
 
 985       # remove _oe entries afterwards
 
 986       map { $ref->{$_} = $ref->{"${_}_oe"} if ($ref->{$_} eq '') }
 
 987         qw|ordnumber transdate cusordnumber|
 
 989       map { delete $ref->{$_} } qw|ordnumber_oe transdate_oe cusordnumber_oe|;
 
 993       while ($ref->{inventory_new_chart} && ($ref->{inventory_valid} >= 0)) {
 
 995           qq|SELECT accno AS inventory_accno, | .
 
 996           qq|  new_chart_id AS inventory_new_chart, | .
 
 997           qq|  date($transdate) - valid_from AS inventory_valid | .
 
 998           qq|FROM chart WHERE id = $ref->{inventory_new_chart}|;
 
 999         ($ref->{inventory_accno}, $ref->{inventory_new_chart},
 
1000          $ref->{inventory_valid}) = selectrow_query($form, $dbh, $query);
 
1003       while ($ref->{income_new_chart} && ($ref->{income_valid} >= 0)) {
 
1005           qq|SELECT accno AS income_accno, | .
 
1006           qq|  new_chart_id AS income_new_chart, | .
 
1007           qq|  date($transdate) - valid_from AS income_valid | .
 
1008           qq|FROM chart WHERE id = $ref->{income_new_chart}|;
 
1009         ($ref->{income_accno}, $ref->{income_new_chart},
 
1010          $ref->{income_valid}) = selectrow_query($form, $dbh, $query);
 
1013       while ($ref->{expense_new_chart} && ($ref->{expense_valid} >= 0)) {
 
1015           qq|SELECT accno AS expense_accno, | .
 
1016           qq|  new_chart_id AS expense_new_chart, | .
 
1017           qq|  date($transdate) - valid_from AS expense_valid | .
 
1018           qq|FROM chart WHERE id = $ref->{expense_new_chart}|;
 
1019         ($ref->{expense_accno}, $ref->{expense_new_chart},
 
1020          $ref->{expense_valid}) = selectrow_query($form, $dbh, $query);
 
1023       # delete orderitems_id in collective orders, so that they get cloned no matter what
 
1024       delete $ref->{orderitems_id} if $is_collective_order;
 
1026       # get tax rates and description
 
1027       my $accno_id = ($form->{vc} eq "customer") ? $ref->{income_accno} : $ref->{expense_accno};
 
1029         qq|SELECT c.accno, t.taxdescription, t.rate, t.taxnumber | .
 
1030         qq|FROM tax t LEFT JOIN chart c on (c.id = t.chart_id) | .
 
1031         qq|WHERE t.id IN (SELECT tk.tax_id FROM taxkeys tk | .
 
1032         qq|               WHERE tk.chart_id = (SELECT id FROM chart WHERE accno = ?) | .
 
1033         qq|                 AND startdate <= $transdate ORDER BY startdate DESC LIMIT 1) | .
 
1034         qq|ORDER BY c.accno|;
 
1035       my $stw = prepare_execute_query($form, $dbh, $query, $accno_id);
 
1036       $ref->{taxaccounts} = "";
 
1038       while (my $ptr = $stw->fetchrow_hashref("NAME_lc")) {
 
1039         if (($ptr->{accno} eq "") && ($ptr->{rate} == 0)) {
 
1043         $ref->{taxaccounts} .= "$ptr->{accno} ";
 
1044         if (!($form->{taxaccounts} =~ /\Q$ptr->{accno}\E/)) {
 
1045           $form->{"$ptr->{accno}_rate"}        = $ptr->{rate};
 
1046           $form->{"$ptr->{accno}_description"} = $ptr->{taxdescription};
 
1047           $form->{"$ptr->{accno}_taxnumber"}   = $ptr->{taxnumber};
 
1048           $form->{taxaccounts} .= "$ptr->{accno} ";
 
1053       chop $ref->{taxaccounts};
 
1055       push @{ $form->{form_details} }, $ref;
 
1062     # get last name used
 
1063     $form->lastname_used($dbh, $myconfig, $form->{vc})
 
1064       unless $form->{"$form->{vc}_id"};
 
1068   $form->{exchangerate} = $form->get_exchangerate($dbh, $form->{currency}, $form->{transdate}, ($form->{vc} eq 'customer') ? "buy" : "sell");
 
1070   Common::webdav_folder($form);
 
1072   $self->load_periodic_invoice_config($form);
 
1074   my $rc = $dbh->commit;
 
1076   $main::lxdebug->leave_sub();
 
1081 sub retrieve_simple {
 
1082   $main::lxdebug->enter_sub();
 
1087   Common::check_params(\%params, qw(id));
 
1089   my $myconfig    = \%main::myconfig;
 
1090   my $form        = $main::form;
 
1092   my $dbh         = $params{dbh} || $form->get_standard_dbh($myconfig);
 
1094   my $oe_query    = qq|SELECT * FROM oe         WHERE id = ?|;
 
1095   my $oi_query    = qq|SELECT * FROM orderitems WHERE trans_id = ?|;
 
1097   my $order            = selectfirst_hashref_query($form, $dbh, $oe_query, conv_i($params{id}));
 
1098   $order->{orderitems} = selectall_hashref_query(  $form, $dbh, $oi_query, conv_i($params{id}));
 
1100   $main::lxdebug->leave_sub();
 
1106   $main::lxdebug->enter_sub();
 
1108   my ($self, $myconfig, $form) = @_;
 
1110   # connect to database
 
1111   my $dbh = $form->get_standard_dbh;
 
1117   my $nodiscount_subtotal = 0;
 
1118   my $discount_subtotal = 0;
 
1121   my @partsgroup = ();
 
1124   my $subtotal_header = 0;
 
1125   my $subposition = 0;
 
1133   push(@project_ids, $form->{"globalproject_id"}) if ($form->{"globalproject_id"});
 
1135   $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS',
 
1136                    'departments'   => 'ALL_DEPARTMENTS');
 
1139   foreach my $pfac (@{ $form->{ALL_PRICE_FACTORS} }) {
 
1140     $price_factors{$pfac->{id}}  = $pfac;
 
1141     $pfac->{factor}             *= 1;
 
1142     $pfac->{formatted_factor}    = $form->format_amount($myconfig, $pfac->{factor});
 
1146   foreach my $dept (@{ $form->{ALL_DEPARTMENTS} }) {
 
1147     next unless $dept->{id} eq $form->{department_id};
 
1148     $form->{department} = $dept->{description};
 
1152   # sort items by partsgroup
 
1153   for $i (1 .. $form->{rowcount}) {
 
1155     if ($form->{"partsgroup_$i"} && $form->{groupitems}) {
 
1156       $partsgroup = $form->{"partsgroup_$i"};
 
1158     push @partsgroup, [$i, $partsgroup];
 
1159     push(@project_ids, $form->{"project_id_$i"}) if ($form->{"project_id_$i"});
 
1165     $projects = SL::DB::Manager::Project->get_all(query => [ id => \@project_ids ]);
 
1166     %projects_by_id = map { $_->id => $_ } @$projects;
 
1169   if ($projects_by_id{$form->{"globalproject_id"}}) {
 
1170     $form->{globalprojectnumber} = $projects_by_id{$form->{"globalproject_id"}}->projectnumber;
 
1171     $form->{globalprojectdescription} = $projects_by_id{$form->{"globalproject_id"}}->description;
 
1173     for (@{ $projects_by_id{$form->{"globalproject_id"}}->cvars_by_config }) {
 
1174       $form->{"project_cvar_" . $_->config->name} = $_->value_as_text;
 
1178   $form->{discount} = [];
 
1180   $form->{TEMPLATE_ARRAYS} = { };
 
1181   IC->prepare_parts_for_printing(myconfig => $myconfig, form => $form);
 
1183   my $ic_cvar_configs = CVar->get_configs(module => 'IC');
 
1184   my $project_cvar_configs = CVar->get_configs(module => 'Projects');
 
1187     qw(runningnumber number description longdescription qty ship unit bin
 
1188        partnotes serialnumber reqdate sellprice listprice netprice
 
1189        discount p_discount discount_sub nodiscount_sub
 
1190        linetotal  nodiscount_linetotal tax_rate projectnumber projectdescription
 
1191        price_factor price_factor_name partsgroup weight lineweight);
 
1193   push @arrays, map { "ic_cvar_$_->{name}" } @{ $ic_cvar_configs };
 
1194   push @arrays, map { "project_cvar_$_->{name}" } @{ $project_cvar_configs };
 
1196   my @tax_arrays = qw(taxbase tax taxdescription taxrate taxnumber);
 
1198   map { $form->{TEMPLATE_ARRAYS}->{$_} = [] } (@arrays, @tax_arrays);
 
1200   my $totalweight = 0;
 
1202   foreach $item (sort { $a->[1] cmp $b->[1] } @partsgroup) {
 
1205     if ($item->[1] ne $sameitem) {
 
1206       push(@{ $form->{TEMPLATE_ARRAYS}->{description} }, qq|$item->[1]|);
 
1207       $sameitem = $item->[1];
 
1209       map({ push(@{ $form->{TEMPLATE_ARRAYS}->{$_} }, "") } grep({ $_ ne "description" } @arrays));
 
1212     $form->{"qty_$i"} = $form->parse_amount($myconfig, $form->{"qty_$i"});
 
1214     if ($form->{"id_$i"} != 0) {
 
1216       # add number, description and qty to $form->{number}, ....
 
1218       if ($form->{"subtotal_$i"} && !$subtotal_header) {
 
1219         $subtotal_header = $i;
 
1220         $position = int($position);
 
1223       } elsif ($subtotal_header) {
 
1225         $position = int($position);
 
1226         $position = $position.".".$subposition;
 
1228         $position = int($position);
 
1232       my $price_factor = $price_factors{$form->{"price_factor_id_$i"}} || { 'factor' => 1 };
 
1234       push @{ $form->{TEMPLATE_ARRAYS}->{runningnumber} },     $position;
 
1235       push @{ $form->{TEMPLATE_ARRAYS}->{number} },            $form->{"partnumber_$i"};
 
1236       push @{ $form->{TEMPLATE_ARRAYS}->{description} },       $form->{"description_$i"};
 
1237       push @{ $form->{TEMPLATE_ARRAYS}->{longdescription} },   $form->{"longdescription_$i"};
 
1238       push @{ $form->{TEMPLATE_ARRAYS}->{qty} },               $form->format_amount($myconfig, $form->{"qty_$i"});
 
1239       push @{ $form->{TEMPLATE_ARRAYS}->{qty_nofmt} },         $form->{"qty_$i"};
 
1240       push @{ $form->{TEMPLATE_ARRAYS}->{ship} },              $form->format_amount($myconfig, $form->{"ship_$i"});
 
1241       push @{ $form->{TEMPLATE_ARRAYS}->{ship_nofmt} },        $form->{"ship_$i"};
 
1242       push @{ $form->{TEMPLATE_ARRAYS}->{unit} },              $form->{"unit_$i"};
 
1243       push @{ $form->{TEMPLATE_ARRAYS}->{bin} },               $form->{"bin_$i"};
 
1244       push @{ $form->{TEMPLATE_ARRAYS}->{partnotes} },         $form->{"partnotes_$i"};
 
1245       push @{ $form->{TEMPLATE_ARRAYS}->{serialnumber} },      $form->{"serialnumber_$i"};
 
1246       push @{ $form->{TEMPLATE_ARRAYS}->{reqdate} },           $form->{"reqdate_$i"};
 
1247       push @{ $form->{TEMPLATE_ARRAYS}->{sellprice} },         $form->{"sellprice_$i"};
 
1248       push @{ $form->{TEMPLATE_ARRAYS}->{sellprice_nofmt} },   $form->parse_amount($myconfig, $form->{"sellprice_$i"});
 
1249       push @{ $form->{TEMPLATE_ARRAYS}->{listprice} },         $form->{"listprice_$i"};
 
1250       push @{ $form->{TEMPLATE_ARRAYS}->{price_factor} },      $price_factor->{formatted_factor};
 
1251       push @{ $form->{TEMPLATE_ARRAYS}->{price_factor_name} }, $price_factor->{description};
 
1252       push @{ $form->{TEMPLATE_ARRAYS}->{partsgroup} },        $form->{"partsgroup_$i"};
 
1254       my $sellprice     = $form->parse_amount($myconfig, $form->{"sellprice_$i"});
 
1255       my ($dec)         = ($sellprice =~ /\.(\d+)/);
 
1256       my $decimalplaces = max 2, length($dec);
 
1258       my $parsed_discount            = $form->parse_amount($myconfig, $form->{"discount_$i"});
 
1260       my $linetotal_exact            = $form->{"qty_$i"} * $sellprice * (100 - $parsed_discount) / 100 / $price_factor->{factor};
 
1261       my $linetotal                  = $form->round_amount($linetotal_exact, 2);
 
1263       my $nodiscount_exact_linetotal = $form->{"qty_$i"} * $sellprice                                  / $price_factor->{factor};
 
1264       my $nodiscount_linetotal       = $form->round_amount($nodiscount_exact_linetotal,2);
 
1266       my $discount                   = $nodiscount_linetotal - $linetotal; # is always rounded because $nodiscount_linetotal and $linetotal are rounded
 
1268       my $discount_round_error       = $discount + ($linetotal_exact - $nodiscount_exact_linetotal); # not used
 
1270       $form->{"netprice_$i"}   = $form->round_amount($form->{"qty_$i"} ? ($linetotal / $form->{"qty_$i"}) : 0, $decimalplaces);
 
1272       push @{ $form->{TEMPLATE_ARRAYS}->{netprice} },       ($form->{"netprice_$i"} != 0) ? $form->format_amount($myconfig, $form->{"netprice_$i"}, $decimalplaces) : '';
 
1273       push @{ $form->{TEMPLATE_ARRAYS}->{netprice_nofmt} }, ($form->{"netprice_$i"} != 0) ? $form->{"netprice_$i"} : '';
 
1275       $linetotal = ($linetotal != 0) ? $linetotal : '';
 
1277       push @{ $form->{TEMPLATE_ARRAYS}->{discount} },       ($discount != 0) ? $form->format_amount($myconfig, $discount * -1, 2) : '';
 
1278       push @{ $form->{TEMPLATE_ARRAYS}->{discount_nofmt} }, ($discount != 0) ? $discount * -1 : '';
 
1279       push @{ $form->{TEMPLATE_ARRAYS}->{p_discount} },     $form->{"discount_$i"};
 
1281       $form->{ordtotal}         += $linetotal;
 
1282       $form->{nodiscount_total} += $nodiscount_linetotal;
 
1283       $form->{discount_total}   += $discount;
 
1285       if ($subtotal_header) {
 
1286         $discount_subtotal   += $linetotal;
 
1287         $nodiscount_subtotal += $nodiscount_linetotal;
 
1290       if ($form->{"subtotal_$i"} && $subtotal_header && ($subtotal_header != $i)) {
 
1291         push @{ $form->{TEMPLATE_ARRAYS}->{discount_sub} },         $form->format_amount($myconfig, $discount_subtotal,   2);
 
1292         push @{ $form->{TEMPLATE_ARRAYS}->{discount_sub_nofmt} },   $discount_subtotal;
 
1293         push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_sub} },       $form->format_amount($myconfig, $nodiscount_subtotal, 2);
 
1294         push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_sub_nofmt} }, $nodiscount_subtotal;
 
1296         $discount_subtotal   = 0;
 
1297         $nodiscount_subtotal = 0;
 
1298         $subtotal_header     = 0;
 
1301         push @{ $form->{TEMPLATE_ARRAYS}->{$_} }, "" for qw(discount_sub nodiscount_sub discount_sub_nofmt nodiscount_sub_nofmt);
 
1304       if (!$form->{"discount_$i"}) {
 
1305         $nodiscount += $linetotal;
 
1308       my $project = $projects_by_id{$form->{"project_id_$i"}} || SL::DB::Project->new;
 
1310       push @{ $form->{TEMPLATE_ARRAYS}->{linetotal} },                  $form->format_amount($myconfig, $linetotal, 2);
 
1311       push @{ $form->{TEMPLATE_ARRAYS}->{linetotal_nofmt} },            $linetotal_exact;
 
1312       push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_linetotal} },       $form->format_amount($myconfig, $nodiscount_linetotal, 2);
 
1313       push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_linetotal_nofmt} }, $nodiscount_linetotal;
 
1314       push @{ $form->{TEMPLATE_ARRAYS}->{projectnumber} },              $project->projectnumber;
 
1315       push @{ $form->{TEMPLATE_ARRAYS}->{projectdescription} },         $project->description;
 
1317       my $lineweight = $form->{"qty_$i"} * $form->{"weight_$i"};
 
1318       $totalweight += $lineweight;
 
1319       push @{ $form->{TEMPLATE_ARRAYS}->{weight} },            $form->format_amount($myconfig, $form->{"weight_$i"}, 3);
 
1320       push @{ $form->{TEMPLATE_ARRAYS}->{weight_nofmt} },      $form->{"weight_$i"};
 
1321       push @{ $form->{TEMPLATE_ARRAYS}->{lineweight} },        $form->format_amount($myconfig, $lineweight, 3);
 
1322       push @{ $form->{TEMPLATE_ARRAYS}->{lineweight_nofmt} },  $lineweight;
 
1324       my ($taxamount, $taxbase);
 
1327       map { $taxrate += $form->{"${_}_rate"} } split(/ /, $form->{"taxaccounts_$i"});
 
1329       if ($form->{taxincluded}) {
 
1332         $taxamount = $linetotal * $taxrate / (1 + $taxrate);
 
1333         $taxbase = $linetotal / (1 + $taxrate);
 
1335         $taxamount = $linetotal * $taxrate;
 
1336         $taxbase   = $linetotal;
 
1339       if ($taxamount != 0) {
 
1340         foreach my $accno (split / /, $form->{"taxaccounts_$i"}) {
 
1341           $taxaccounts{$accno} += $taxamount * $form->{"${accno}_rate"} / $taxrate;
 
1342           $taxbase{$accno}     += $taxbase;
 
1346       $tax_rate = $taxrate * 100;
 
1347       push(@{ $form->{TEMPLATE_ARRAYS}->{tax_rate} }, qq|$tax_rate|);
 
1349       if ($form->{"assembly_$i"}) {
 
1352         # get parts and push them onto the stack
 
1354         if ($form->{groupitems}) {
 
1355           $sortorder = qq|ORDER BY pg.partsgroup, a.oid|;
 
1357           $sortorder = qq|ORDER BY a.oid|;
 
1360         $query = qq|SELECT p.partnumber, p.description, p.unit, a.qty, | .
 
1361                  qq|pg.partsgroup | .
 
1362                  qq|FROM assembly a | .
 
1363                  qq|  JOIN parts p ON (a.parts_id = p.id) | .
 
1364                  qq|    LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id) | .
 
1365                  qq|    WHERE a.bom = '1' | .
 
1366                  qq|    AND a.id = ? | . $sortorder;
 
1367         @values = ($form->{"id_$i"});
 
1368         $sth = $dbh->prepare($query);
 
1369         $sth->execute(@values) || $form->dberror($query);
 
1371         while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
 
1372           if ($form->{groupitems} && $ref->{partsgroup} ne $sameitem) {
 
1373             map({ push(@{ $form->{TEMPLATE_ARRAYS}->{$_} }, "") } grep({ $_ ne "description" } @arrays));
 
1374             $sameitem = ($ref->{partsgroup}) ? $ref->{partsgroup} : "--";
 
1375             push(@{ $form->{TEMPLATE_ARRAYS}->{description} }, $sameitem);
 
1378           push(@{ $form->{TEMPLATE_ARRAYS}->{description} }, $form->format_amount($myconfig, $ref->{qty} * $form->{"qty_$i"}) . qq|, $ref->{partnumber}, $ref->{description}|);
 
1379           map({ push(@{ $form->{TEMPLATE_ARRAYS}->{$_} }, "") } grep({ $_ ne "description" } @arrays));
 
1384       push @{ $form->{TEMPLATE_ARRAYS}->{"ic_cvar_$_->{name}"} },
 
1385         CVar->format_to_template(CVar->parse($form->{"ic_cvar_$_->{name}_$i"}, $_), $_)
 
1386           for @{ $ic_cvar_configs };
 
1388       push @{ $form->{TEMPLATE_ARRAYS}->{"project_cvar_" . $_->config->name} }, $_->value_as_text for @{ $project->cvars_by_config };
 
1392   $form->{totalweight}       = $form->format_amount($myconfig, $totalweight, 3);
 
1393   $form->{totalweight_nofmt} = $totalweight;
 
1394   my $defaults = AM->get_defaults();
 
1395   $form->{weightunit}        = $defaults->{weightunit};
 
1398   foreach $item (sort keys %taxaccounts) {
 
1399     $tax += $taxamount = $form->round_amount($taxaccounts{$item}, 2);
 
1401     push(@{ $form->{TEMPLATE_ARRAYS}->{taxbase} },        $form->format_amount($myconfig, $taxbase{$item}, 2));
 
1402     push(@{ $form->{TEMPLATE_ARRAYS}->{taxbase_nofmt} },  $taxbase{$item});
 
1403     push(@{ $form->{TEMPLATE_ARRAYS}->{tax} },            $form->format_amount($myconfig, $taxamount,      2));
 
1404     push(@{ $form->{TEMPLATE_ARRAYS}->{tax_nofmt} },      $taxamount);
 
1405     push(@{ $form->{TEMPLATE_ARRAYS}->{taxrate} },        $form->format_amount($myconfig, $form->{"${item}_rate"} * 100));
 
1406     push(@{ $form->{TEMPLATE_ARRAYS}->{taxrate_nofmt} },  $form->{"${item}_rate"} * 100);
 
1407     push(@{ $form->{TEMPLATE_ARRAYS}->{taxnumber} },      $form->{"${item}_taxnumber"});
 
1409     my $tax_obj     = SL::DB::Manager::Tax->find_by(taxnumber => $form->{"${item}_taxnumber"});
 
1410     my $description = $tax_obj ? $tax_obj->translated_attribute('taxdescription',  $form->{language_id}, 0) : '';
 
1411     push(@{ $form->{TEMPLATE_ARRAYS}->{taxdescription} }, $description . q{ } . 100 * $form->{"${item}_rate"} . q{%});
 
1414   $form->{nodiscount_subtotal} = $form->format_amount($myconfig, $form->{nodiscount_total}, 2);
 
1415   $form->{discount_total}      = $form->format_amount($myconfig, $form->{discount_total}, 2);
 
1416   $form->{nodiscount}          = $form->format_amount($myconfig, $nodiscount, 2);
 
1417   $form->{yesdiscount}         = $form->format_amount($myconfig, $form->{nodiscount_total} - $nodiscount, 2);
 
1419   if($form->{taxincluded}) {
 
1420     $form->{subtotal}       = $form->format_amount($myconfig, $form->{ordtotal} - $tax, 2);
 
1421     $form->{subtotal_nofmt} = $form->{ordtotal} - $tax;
 
1423     $form->{subtotal}       = $form->format_amount($myconfig, $form->{ordtotal}, 2);
 
1424     $form->{subtotal_nofmt} = $form->{ordtotal};
 
1427   $form->{ordtotal} = ($form->{taxincluded}) ? $form->{ordtotal} : $form->{ordtotal} + $tax;
 
1430   $form->{quototal} = $form->{ordtotal} = $form->format_amount($myconfig, $form->{ordtotal}, 2);
 
1432   if ($form->{type} =~ /_quotation/) {
 
1433     $form->set_payment_options($myconfig, $form->{quodate});
 
1435     $form->set_payment_options($myconfig, $form->{orddate});
 
1438   $form->{username} = $myconfig->{name};
 
1442   $form->{delivery_term} = SL::DB::Manager::DeliveryTerm->find_by(id => $form->{delivery_term_id} || undef);
 
1443   $form->{delivery_term}->description_long($form->{delivery_term}->translated_attribute('description_long', $form->{language_id})) if $form->{delivery_term} && $form->{language_id};
 
1445   $::form->{order} = SL::DB::Manager::Order->find_by(id => $::form->{id});
 
1447   $main::lxdebug->leave_sub();
 
1450 sub project_description {
 
1451   $main::lxdebug->enter_sub();
 
1453   my ($self, $dbh, $id) = @_;
 
1455   my $query = qq|SELECT description FROM project WHERE id = ?|;
 
1456   my ($value) = selectrow_query($main::form, $dbh, $query, $id);
 
1458   $main::lxdebug->leave_sub();
 
1469 OE.pm - Order entry module
 
1473 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>.
 
1479 =item retrieve_simple PARAMS
 
1481 simple OE retrieval by id. does not look up customer, vendor, units or any other stuff. only oe and orderitems.
 
1483   my $order = retrieve_simple(id => 2);