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;
 
  47 use SL::DB::RequirementSpecOrder;
 
  51 use SL::HTML::Restrict;
 
  54 use SL::Util qw(trim);
 
  62   $main::lxdebug->enter_sub();
 
  64   my ($self, $myconfig, $form) = @_;
 
  67   my $dbh = $form->get_standard_dbh;
 
  70   my $ordnumber = 'ordnumber';
 
  76   my ($periodic_invoices_columns, $periodic_invoices_joins);
 
  78   my $rate = ($form->{vc} eq 'customer') ? 'buy' : 'sell';
 
  80   if ($form->{type} =~ /_quotation$/) {
 
  82     $ordnumber = 'quonumber';
 
  84   } elsif ($form->{type} eq 'sales_order') {
 
  85     $periodic_invoices_columns = qq| , COALESCE(pcfg.active, 'f') AS periodic_invoices |;
 
  86     $periodic_invoices_joins   = qq| LEFT JOIN periodic_invoices_configs pcfg ON (o.id = pcfg.oe_id) |;
 
  89   my $vc = $form->{vc} eq "customer" ? "customer" : "vendor";
 
  93   if ($form->{l_remaining_amount} || $form->{l_remaining_netamount}) {
 
  95       SELECT from_id, ar.amount, ar.netamount FROM (
 
  98         WHERE from_table = 'oe' AND to_table = 'ar'
 
 100         SELECT rl1.from_id, rl2.to_id
 
 101         FROM record_links rl1
 
 102         LEFT JOIN record_links rl2 ON (rl1.to_table = rl2.from_table AND rl1.to_id = rl2.from_id)
 
 103         WHERE rl1.from_table = 'oe' AND rl2.to_table = 'ar'
 
 105         SELECT rl1.from_id, rl3.to_id
 
 106         FROM record_links rl1
 
 107         JOIN record_links rl2 ON (rl1.to_table = rl2.from_table AND rl1.to_id = rl2.from_id)
 
 108         JOIN record_links rl3 ON (rl2.to_table = rl3.from_table AND rl2.to_id = rl3.from_id)
 
 109         WHERE rl1.from_table = 'oe' AND rl2.to_table = 'ar' AND rl3.to_table = 'ar'
 
 111       LEFT JOIN ar ON ar.id = rl.to_id
 
 113     for my $ref (@{ selectall_hashref_query($form, $dbh, $query) }) {
 
 114       $billed_amount{   $ref->{from_id}} += $ref->{amount};
 
 115       $billed_netamount{$ref->{from_id}} += $ref->{netamount};
 
 119   my ($phone_notes_columns, $phone_notes_join);
 
 120   $form->{phone_notes} = trim($form->{phone_notes});
 
 121   if ($form->{phone_notes}) {
 
 122     $phone_notes_columns = qq| , phone_notes.subject AS phone_notes_subject, phone_notes.body AS phone_notes_body |;
 
 123     $phone_notes_join    = qq| JOIN notes phone_notes ON (o.id = phone_notes.trans_id AND phone_notes.trans_module LIKE 'oe') |;
 
 127     qq|SELECT o.id, o.ordnumber, o.transdate, o.reqdate, | .
 
 128     qq|  o.amount, ct.${vc}number, ct.name, o.netamount, o.${vc}_id, o.globalproject_id, | .
 
 129     qq|  o.closed, o.delivered, o.quonumber, o.cusordnumber, o.shippingpoint, o.shipvia, | .
 
 130     qq|  o.transaction_description, | .
 
 131     qq|  o.marge_total, o.marge_percent, | .
 
 132     qq|  o.exchangerate, | .
 
 133     qq|  o.itime::DATE AS insertdate, | .
 
 135     qq|  department.description as department, | .
 
 136     qq|  ex.$rate AS daily_exchangerate, | .
 
 137     qq|  pt.description AS payment_terms, | .
 
 138     qq|  pr.projectnumber AS globalprojectnumber, | .
 
 139     qq|  e.name AS employee, s.name AS salesman, | .
 
 140     qq|  ct.${vc}number AS vcnumber, ct.country, ct.ustid, ct.business_id,  | .
 
 141     qq|  tz.description AS taxzone | .
 
 142     $periodic_invoices_columns .
 
 143     $phone_notes_columns .
 
 144     qq|  , o.order_probability, o.expected_billing_date, (o.netamount * o.order_probability / 100) AS expected_netamount | .
 
 146     qq|JOIN $vc ct ON (o.${vc}_id = ct.id) | .
 
 147     qq|LEFT JOIN contacts cp ON (o.cp_id = cp.cp_id) | .
 
 148     qq|LEFT JOIN employee e ON (o.employee_id = e.id) | .
 
 149     qq|LEFT JOIN employee s ON (o.salesman_id = s.id) | .
 
 150     qq|LEFT JOIN exchangerate ex ON (ex.currency_id = o.currency_id | .
 
 151     qq|  AND ex.transdate = o.transdate) | .
 
 152     qq|LEFT JOIN project pr ON (o.globalproject_id = pr.id) | .
 
 153     qq|LEFT JOIN payment_terms pt ON (pt.id = o.payment_id)| .
 
 154     qq|LEFT JOIN tax_zones tz ON (o.taxzone_id = tz.id) | .
 
 155     qq|LEFT JOIN department   ON (o.department_id = department.id) | .
 
 156     qq|$periodic_invoices_joins | .
 
 158     qq|WHERE (o.quotation = ?) |;
 
 159   push(@values, $quotation);
 
 161   if ($form->{department_id}) {
 
 162     $query .= qq| AND o.department_id = ?|;
 
 163     push(@values, $form->{department_id});
 
 166   if ($form->{"project_id"}) {
 
 168       qq|AND ((globalproject_id = ?) OR EXISTS | .
 
 169       qq|  (SELECT * FROM orderitems oi | .
 
 170       qq|   WHERE oi.project_id = ? AND oi.trans_id = o.id))|;
 
 171     push(@values, conv_i($form->{"project_id"}), conv_i($form->{"project_id"}));
 
 174   if ($form->{"projectnumber"}) {
 
 176       AND ((pr.projectnumber ILIKE ?) OR EXISTS (
 
 177         SELECT * FROM orderitems oi
 
 178         LEFT JOIN project proi ON proi.id = oi.project_id
 
 179         WHERE proi.projectnumber ILIKE ? AND oi.trans_id = o.id
 
 182     push @values, like($form->{"projectnumber"}), like($form->{"projectnumber"});
 
 185   if ($form->{"business_id"}) {
 
 186     $query .= " AND ct.business_id = ?";
 
 187     push(@values, $form->{"business_id"});
 
 190   if ($form->{"${vc}_id"}) {
 
 191     $query .= " AND o.${vc}_id = ?";
 
 192     push(@values, $form->{"${vc}_id"});
 
 194   } elsif ($form->{$vc}) {
 
 195     $query .= " AND ct.name ILIKE ?";
 
 196     push(@values, like($form->{$vc}));
 
 199   if ($form->{"cp_name"}) {
 
 200     $query .= " AND (cp.cp_name ILIKE ? OR cp.cp_givenname ILIKE ?)";
 
 201     push(@values, (like($form->{"cp_name"}))x2);
 
 204   if ( !(    ($vc eq 'customer' && ($main::auth->assert('sales_all_edit',    1) || $main::auth->assert('sales_order_view',    1)))
 
 205           || ($vc eq 'vendor'   && ($main::auth->assert('purchase_all_edit', 1) || $main::auth->assert('purchase_order_view', 1))) ) ) {
 
 206     $query .= " AND o.employee_id = (select id from employee where login= ?)";
 
 207     push @values, $::myconfig{login};
 
 209   if ($form->{employee_id}) {
 
 210     $query .= " AND o.employee_id = ?";
 
 211     push @values, conv_i($form->{employee_id});
 
 214   if ($form->{salesman_id}) {
 
 215     $query .= " AND o.salesman_id = ?";
 
 216     push @values, conv_i($form->{salesman_id});
 
 219   if (!$form->{open} && !$form->{closed}) {
 
 220     $query .= " AND o.id = 0";
 
 221   } elsif (!($form->{open} && $form->{closed})) {
 
 222     $query .= ($form->{open}) ? " AND o.closed = '0'" : " AND o.closed = '1'";
 
 225   if (($form->{"notdelivered"} || $form->{"delivered"}) &&
 
 226       ($form->{"notdelivered"} ne $form->{"delivered"})) {
 
 227     $query .= $form->{"delivered"} ?
 
 228       " AND o.delivered " : " AND NOT o.delivered";
 
 231   if ($form->{$ordnumber}) {
 
 232     $query .= qq| AND o.$ordnumber ILIKE ?|;
 
 233     push(@values, like($form->{$ordnumber}));
 
 236   if ($form->{cusordnumber}) {
 
 237     $query .= qq| AND o.cusordnumber ILIKE ?|;
 
 238     push(@values, like($form->{cusordnumber}));
 
 241   if($form->{transdatefrom}) {
 
 242     $query .= qq| AND o.transdate >= ?|;
 
 243     push(@values, conv_date($form->{transdatefrom}));
 
 246   if($form->{transdateto}) {
 
 247     $query .= qq| AND o.transdate <= ?|;
 
 248     push(@values, conv_date($form->{transdateto}));
 
 251   if($form->{reqdatefrom}) {
 
 252     $query .= qq| AND o.reqdate >= ?|;
 
 253     push(@values, conv_date($form->{reqdatefrom}));
 
 256   if($form->{reqdateto}) {
 
 257     $query .= qq| AND o.reqdate <= ?|;
 
 258     push(@values, conv_date($form->{reqdateto}));
 
 261   if($form->{insertdatefrom}) {
 
 262     $query .= qq| AND o.itime::DATE >= ?|;
 
 263     push(@values, conv_date($form->{insertdatefrom}));
 
 266   if($form->{insertdateto}) {
 
 267     $query .= qq| AND o.itime::DATE <= ?|;
 
 268     push(@values, conv_date($form->{insertdateto}));
 
 271   if ($form->{shippingpoint}) {
 
 272     $query .= qq| AND o.shippingpoint ILIKE ?|;
 
 273     push(@values, like($form->{shippingpoint}));
 
 276   if ($form->{taxzone_id} ne '') { # taxzone_id could be 0
 
 277     $query .= qq| AND tz.id = ?|;
 
 278     push(@values, $form->{taxzone_id});
 
 281   if ($form->{transaction_description}) {
 
 282     $query .= qq| AND o.transaction_description ILIKE ?|;
 
 283     push(@values, like($form->{transaction_description}));
 
 286   if ($form->{periodic_invoices_active} ne $form->{periodic_invoices_inactive}) {
 
 287     my $not  = $form->{periodic_invoices_inactive} ? 'NOT' : '';
 
 288     $query  .= qq| AND ${not} COALESCE(pcfg.active, 'f')|;
 
 291   if ($form->{reqdate_unset_or_old}) {
 
 292     $query .= qq| AND ((o.reqdate IS NULL) OR (o.reqdate < date_trunc('month', current_date)))|;
 
 295   if (($form->{order_probability_value} || '') ne '') {
 
 296     my $op  = $form->{order_probability_value} eq 'le' ? '<=' : '>=';
 
 297     $query .= qq| AND (o.order_probability ${op} ?)|;
 
 298     push @values, trim($form->{order_probability_value});
 
 301   if ($form->{expected_billing_date_from}) {
 
 302     $query .= qq| AND (o.expected_billing_date >= ?)|;
 
 303     push @values, conv_date($form->{expected_billing_date_from});
 
 306   if ($form->{expected_billing_date_to}) {
 
 307     $query .= qq| AND (o.expected_billing_date <= ?)|;
 
 308     push @values, conv_date($form->{expected_billing_date_to});
 
 311   if ($form->{intnotes}) {
 
 312     $query .= qq| AND o.intnotes ILIKE ?|;
 
 313     push(@values, like($form->{intnotes}));
 
 316   if ($form->{phone_notes}) {
 
 317     $query .= qq| AND (phone_notes.subject ILIKE ? OR phone_notes.body ILIKE ?)|;
 
 318     push(@values, like($form->{phone_notes}), like($form->{phone_notes}));
 
 321   $form->{fulltext} = trim($form->{fulltext});
 
 322   if ($form->{fulltext}) {
 
 323     my @fulltext_fields = qw(o.notes
 
 327                              o.transaction_description
 
 332     $query .= join ' ILIKE ? OR ', @fulltext_fields;
 
 333     $query .= ' ILIKE ?';
 
 337         SELECT files.id FROM files LEFT JOIN file_full_texts ON (file_full_texts.file_id = files.id)
 
 338           WHERE files.object_id = o.id AND files.object_type = 'sales_order'
 
 339             AND file_full_texts.full_text ILIKE ?)
 
 344         SELECT notes.id FROM notes
 
 345           WHERE notes.trans_id = o.id AND notes.trans_module LIKE 'oe'
 
 346             AND (notes.subject ILIKE ? OR notes.body ILIKE ?))
 
 351         SELECT follow_up_links.id FROM follow_up_links
 
 352           WHERE follow_up_links.trans_id = o.id AND trans_type = 'sales_order'
 
 354               SELECT notes.id FROM notes
 
 355                 WHERE trans_module LIKE 'fu' AND trans_id = follow_up_links.follow_up_id
 
 356                   AND (notes.subject ILIKE ? OR notes.body ILIKE ?)))
 
 361     push(@values, like($form->{fulltext})) for 1 .. (scalar @fulltext_fields) + 5;
 
 364   if ($form->{parts_partnumber}) {
 
 367         SELECT orderitems.trans_id
 
 369         LEFT JOIN parts ON (orderitems.parts_id = parts.id)
 
 370         WHERE (orderitems.trans_id = o.id)
 
 371           AND (parts.partnumber ILIKE ?)
 
 375     push @values, like($form->{parts_partnumber});
 
 378   if ($form->{parts_description}) {
 
 381         SELECT orderitems.trans_id
 
 383         WHERE (orderitems.trans_id = o.id)
 
 384           AND (orderitems.description ILIKE ?)
 
 388     push @values, like($form->{parts_description});
 
 392     my @tokens = parse_line('\s+', 0, $form->{all});
 
 393     # ordnumber quonumber customer.name vendor.name transaction_description
 
 395       o.ordnumber ILIKE ? OR
 
 396       o.quonumber ILIKE ? OR
 
 398       o.transaction_description ILIKE ?
 
 400     push @values, (like($_))x4 for @tokens;
 
 403   my ($cvar_where, @cvar_values) = CVar->build_filter_query('module'         => 'CT',
 
 404                                                             'trans_id_field' => 'ct.id',
 
 408     $query .= qq| AND ($cvar_where)|;
 
 409     push @values, @cvar_values;
 
 412   my $sortdir   = !defined $form->{sortdir} ? 'ASC' : $form->{sortdir} ? 'ASC' : 'DESC';
 
 413   my $sortorder = join(', ', map { "${_} ${sortdir} " } ("o.id", $form->sort_columns("transdate", $ordnumber, "name"), "o.itime"));
 
 414   my %allowed_sort_columns = (
 
 415     "transdate"               => "o.transdate",
 
 416     "reqdate"                 => "o.reqdate",
 
 418     "ordnumber"               => "o.ordnumber",
 
 419     "cusordnumber"            => "o.cusordnumber",
 
 420     "quonumber"               => "o.quonumber",
 
 422     "employee"                => "e.name",
 
 423     "salesman"                => "s.name",
 
 424     "shipvia"                 => "o.shipvia",
 
 425     "transaction_description" => "o.transaction_description",
 
 426     "shippingpoint"           => "o.shippingpoint",
 
 427     "insertdate"              => "o.itime",
 
 428     "taxzone"                 => "tz.description",
 
 429     "payment_terms"           => "pt.description",
 
 430     "department"              => "department.description",
 
 431     "intnotes"                => "o.intnotes",
 
 433   if ($form->{sort} && grep($form->{sort}, keys(%allowed_sort_columns))) {
 
 434     $sortorder = $allowed_sort_columns{$form->{sort}} . " ${sortdir}"  . ", o.itime ${sortdir}";
 
 436   $query .= qq| ORDER by | . $sortorder;
 
 438   my $sth = $dbh->prepare($query);
 
 439   $sth->execute(@values) ||
 
 440     $form->dberror($query . " (" . join(", ", @values) . ")");
 
 444   while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
 
 445     $ref->{billed_amount}    = $billed_amount{$ref->{id}};
 
 446     $ref->{billed_netamount} = $billed_netamount{$ref->{id}};
 
 447     if ($ref->{billed_amount} < 0) { # case: credit note(s) higher than invoices
 
 448       $ref->{remaining_amount} = $ref->{amount} + $ref->{billed_amount};
 
 449       $ref->{remaining_netamount} = $ref->{netamount} + $ref->{billed_netamount};
 
 451       $ref->{remaining_amount} = $ref->{amount} - $ref->{billed_amount};
 
 452       $ref->{remaining_netamount} = $ref->{netamount} - $ref->{billed_netamount};
 
 454     $ref->{exchangerate} ||= $ref->{daily_exchangerate};
 
 455     $ref->{exchangerate} ||= 1;
 
 456     push @{ $form->{OE} }, $ref if $ref->{id} != $id{ $ref->{id} };
 
 457     $id{ $ref->{id} } = $ref->{id};
 
 462   $main::lxdebug->leave_sub();
 
 465 sub transactions_for_todo_list {
 
 466   $main::lxdebug->enter_sub();
 
 471   my $myconfig = \%main::myconfig;
 
 472   my $form     = $main::form;
 
 474   my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
 
 476   my $query    = qq|SELECT id FROM employee WHERE login = ?|;
 
 477   my ($e_id)   = selectrow_query($form, $dbh, $query, $::myconfig{login});
 
 480     qq|SELECT oe.id, oe.transdate, oe.reqdate, oe.quonumber, oe.transaction_description, oe.amount,
 
 481          CASE WHEN (COALESCE(oe.customer_id, 0) = 0) THEN 'vendor' ELSE 'customer' END AS vc,
 
 486        LEFT JOIN customer c ON (oe.customer_id = c.id)
 
 487        LEFT JOIN vendor v   ON (oe.vendor_id   = v.id)
 
 488        LEFT JOIN employee e ON (oe.employee_id = e.id)
 
 489        WHERE (COALESCE(quotation, FALSE) = TRUE)
 
 490          AND (COALESCE(closed,    FALSE) = FALSE)
 
 491          AND ((oe.employee_id = ?) OR (oe.salesman_id = ?))
 
 492          AND NOT (oe.reqdate ISNULL)
 
 493          AND (oe.reqdate < current_date)
 
 496   my $quotations = selectall_hashref_query($form, $dbh, $query, $e_id, $e_id);
 
 498   $main::lxdebug->leave_sub();
 
 504   my ($self, $myconfig, $form) = @_;
 
 505   $main::lxdebug->enter_sub();
 
 507   my $rc = SL::DB->client->with_transaction(\&_save, $self, $myconfig, $form);
 
 509   $::lxdebug->leave_sub;
 
 515   $main::lxdebug->enter_sub();
 
 517   my ($self, $myconfig, $form) = @_;
 
 519   my $dbh = SL::DB->client->dbh;
 
 520   my $restricter = SL::HTML::Restrict->create;
 
 522   my ($query, @values, $sth, $null);
 
 523   my $exchangerate = 0;
 
 525   my $all_units = AM->retrieve_units($myconfig, $form);
 
 526   $form->{all_units} = $all_units;
 
 528   my $ic_cvar_configs = CVar->get_configs(module => 'IC',
 
 531   $form->{employee_id} = (split /--/, $form->{employee})[1] if !$form->{employee_id};
 
 532   unless ($form->{employee_id}) {
 
 533     $form->get_employee($dbh);
 
 536   my $ml = ($form->{type} eq 'sales_order') ? 1 : -1;
 
 538   my $number_field         = $form->{type} =~ m{order} ? 'ordnumber' : 'quonumber';
 
 539   my $trans_number         = SL::TransNumber->new(type => $form->{type}, dbh => $dbh, number => $form->{$number_field}, id => $form->{id});
 
 540   $form->{$number_field} ||= $trans_number->create_unique; # set $form->{ordnumber} or $form->{quonumber}
 
 541   my $is_new               = !$form->{id};
 
 544     $query = qq|DELETE FROM custom_variables
 
 545                 WHERE (config_id IN (SELECT id        FROM custom_variable_configs WHERE (module = 'ShipTo')))
 
 546                   AND (trans_id  IN (SELECT shipto_id FROM shipto                  WHERE (module = 'OE') AND (trans_id = ?)))|;
 
 547     do_query($form, $dbh, $query, $form->{id});
 
 549     $query = qq|DELETE FROM shipto | .
 
 550              qq|WHERE trans_id = ? AND module = 'OE'|;
 
 551     do_query($form, $dbh, $query, $form->{id});
 
 555     $query = qq|SELECT nextval('id')|;
 
 556     ($form->{id}) = selectrow_query($form, $dbh, $query);
 
 558     $query = qq|INSERT INTO oe (id, ordnumber, employee_id, currency_id, taxzone_id) VALUES (?, '', ?, (SELECT currency_id FROM defaults), ?)|;
 
 559     do_query($form, $dbh, $query, $form->{id}, $form->{employee_id}, $form->{taxzone_id});
 
 576   my @processed_orderitems;
 
 578   $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS');
 
 579   my %price_factors = map { $_->{id} => $_->{factor} } @{ $form->{ALL_PRICE_FACTORS} };
 
 582   for my $i (1 .. $form->{rowcount}) {
 
 584     map({ $form->{"${_}_$i"} = $form->parse_amount($myconfig, $form->{"${_}_$i"}) } qw(qty ship));
 
 586     if ($form->{"id_$i"}) {
 
 589       $query = qq|SELECT unit FROM parts WHERE id = ?|;
 
 590       my ($item_unit) = selectrow_query($form, $dbh, $query, $form->{"id_$i"});
 
 593       if (defined($all_units->{$item_unit}->{factor}) &&
 
 594           (($all_units->{$item_unit}->{factor} * 1) != 0)) {
 
 595         $basefactor = $all_units->{$form->{"unit_$i"}}->{factor} / $all_units->{$item_unit}->{factor};
 
 597       my $baseqty = $form->{"qty_$i"} * $basefactor;
 
 599       $form->{"marge_percent_$i"} = $form->parse_amount($myconfig, $form->{"marge_percent_$i"}) * 1;
 
 600       $form->{"marge_absolut_$i"} = $form->parse_amount($myconfig, $form->{"marge_absolut_$i"}) * 1;
 
 602       $form->{"lastcost_$i"} = $form->parse_amount($myconfig, $form->{"lastcost_$i"});
 
 604       # keep entered selling price
 
 606         $form->parse_amount($myconfig, $form->{"sellprice_$i"});
 
 608       my ($dec) = ($fxsellprice =~ /\.(\d+)/);
 
 610       my $decimalplaces = ($dec > 2) ? $dec : 2;
 
 612       # undo discount formatting
 
 613       $form->{"discount_$i"} = $form->parse_amount($myconfig, $form->{"discount_$i"}) / 100;
 
 616       $form->{"sellprice_$i"} = $fxsellprice * (1 - $form->{"discount_$i"});
 
 618       # round linetotal at least to 2 decimal places
 
 619       $price_factor = $price_factors{ $form->{"price_factor_id_$i"} } || 1;
 
 620       $linetotal    = $form->round_amount($form->{"sellprice_$i"} * $form->{"qty_$i"} / $price_factor, 2);
 
 622       $form->{"inventory_accno_$i"} *= 1;
 
 623       $form->{"expense_accno_$i"}   *= 1;
 
 625       @taxaccounts = split(/ /, $form->{"taxaccounts_$i"});
 
 629       map { $taxrate += $form->{"${_}_rate"} } @taxaccounts;
 
 631       if ($form->{taxincluded}) {
 
 632         $taxamount = $linetotal * $taxrate / (1 + $taxrate);
 
 633         $taxbase   = $linetotal - $taxamount;
 
 635         # we are not keeping a natural price, do not round
 
 636         $form->{"sellprice_$i"} =
 
 637           $form->{"sellprice_$i"} * (1 / (1 + $taxrate));
 
 639         $taxamount = $linetotal * $taxrate;
 
 640         $taxbase   = $linetotal;
 
 643       if ($form->round_amount($taxrate, 7) == 0) {
 
 644         if ($form->{taxincluded}) {
 
 645           foreach my $item (@taxaccounts) {
 
 646             $taxamount = $form->round_amount($linetotal * $form->{"${item}_rate"} / (1 + abs($form->{"${item}_rate"})), 2);
 
 647             $taxaccounts{$item} += $taxamount;
 
 648             $taxdiff            += $taxamount;
 
 649             $taxbase{$item}     += $taxbase;
 
 651           $taxaccounts{ $taxaccounts[0] } += $taxdiff;
 
 653           foreach my $item (@taxaccounts) {
 
 654             $taxaccounts{$item} += $linetotal * $form->{"${item}_rate"};
 
 655             $taxbase{$item}     += $taxbase;
 
 659         foreach my $item (@taxaccounts) {
 
 660           $taxaccounts{$item} += $taxamount * $form->{"${item}_rate"} / $taxrate;
 
 661           $taxbase{$item} += $taxbase;
 
 665       $netamount += $form->{"sellprice_$i"} * $form->{"qty_$i"} / $price_factor;
 
 667       $reqdate = ($form->{"reqdate_$i"}) ? $form->{"reqdate_$i"} : undef;
 
 669       # Get pricegroup_id and save it. Unfortunately the interface
 
 670       # also uses ID "0" for signalling that none is selected, but "0"
 
 671       # must not be stored in the database. Therefore we cannot simply
 
 673       ($null, my $pricegroup_id) = split(/--/, $form->{"sellprice_pg_$i"});
 
 675       $pricegroup_id  = undef if !$pricegroup_id;
 
 677       # force new project, if not set yet
 
 678       if ($::instance_conf->get_order_always_project && !$form->{"globalproject_id"} && ($form->{type} eq 'sales_order')) {
 
 679         require SL::DB::Customer;
 
 680         my $customer = SL::DB::Manager::Customer->find_by(id => $form->{customer_id});
 
 681         die "Can't find customer" unless $customer;
 
 682         die $main::locale->text("Error while creating project with project number of new order number, project number #1 already exists!", $form->{ordnumber})
 
 683           if SL::DB::Manager::Project->find_by(projectnumber => $form->{ordnumber});
 
 685         my $new_project = SL::DB::Project->new(
 
 686           projectnumber     => $form->{ordnumber},
 
 687           description       => $customer->name,
 
 688           customer_id       => $customer->id,
 
 690           project_type_id   => $::instance_conf->get_project_type_id,
 
 691           project_status_id => $::instance_conf->get_project_status_id,
 
 694         $form->{"globalproject_id"} = $new_project->id;
 
 697       CVar->get_non_editable_ic_cvars(form               => $form,
 
 700                                       sub_module         => 'orderitems',
 
 701                                       may_converted_from => ['orderitems', 'invoice']);
 
 705       # save detail record in orderitems table
 
 706       if (! $form->{"orderitems_id_$i"}) {
 
 707         $query = qq|SELECT nextval('orderitemsid')|;
 
 708         ($form->{"orderitems_id_$i"}) = selectrow_query($form, $dbh, $query);
 
 710         $query = qq|INSERT INTO orderitems (id, position) VALUES (?, ?)|;
 
 711         do_query($form, $dbh, $query, $form->{"orderitems_id_$i"}, conv_i($position));
 
 714       my $orderitems_id = $form->{"orderitems_id_$i"};
 
 715       push @processed_orderitems, $orderitems_id;
 
 718          UPDATE orderitems SET
 
 719           trans_id = ?, position = ?, parts_id = ?, description = ?, longdescription = ?, qty = ?, base_qty = ?,
 
 720           sellprice = ?, discount = ?, unit = ?, reqdate = ?, project_id = ?, serialnumber = ?, ship = ?,
 
 721           pricegroup_id = ?, subtotal = ?,
 
 722           marge_percent = ?, marge_total = ?, lastcost = ?, price_factor_id = ?,
 
 723           active_price_source = ?, active_discount_source = ?,
 
 724           price_factor = (SELECT factor FROM price_factors WHERE id = ?), marge_price_factor = ?
 
 728            conv_i($form->{id}), conv_i($position), conv_i($form->{"id_$i"}),
 
 729            $form->{"description_$i"}, $restricter->process($form->{"longdescription_$i"}),
 
 730            $form->{"qty_$i"}, $baseqty,
 
 731            $fxsellprice, $form->{"discount_$i"},
 
 732            $form->{"unit_$i"}, conv_date($reqdate), conv_i($form->{"project_id_$i"}),
 
 733            $form->{"serialnumber_$i"}, $form->{"ship_$i"},
 
 734            $pricegroup_id, $form->{"subtotal_$i"} ? 't' : 'f',
 
 735            $form->{"marge_percent_$i"}, $form->{"marge_absolut_$i"},
 
 736            $form->{"lastcost_$i"}, conv_i($form->{"price_factor_id_$i"}),
 
 737            $form->{"active_price_source_$i"}, $form->{"active_discount_source_$i"},
 
 738            conv_i($form->{"price_factor_id_$i"}), conv_i($form->{"marge_price_factor_$i"}),
 
 739            conv_i($orderitems_id),
 
 742       do_query($form, $dbh, $query, @values);
 
 744       $form->{"sellprice_$i"} = $fxsellprice;
 
 745       $form->{"discount_$i"} *= 100;
 
 747       CVar->save_custom_variables(module       => 'IC',
 
 748                                   sub_module   => 'orderitems',
 
 749                                   trans_id     => $orderitems_id,
 
 750                                   configs      => $ic_cvar_configs,
 
 752                                   name_prefix  => 'ic_',
 
 753                                   name_postfix => "_$i",
 
 756       # link previous items with orderitems
 
 757       # assume we have a new workflow if we link from invoice or order to quotation
 
 758       # unluckily orderitems are used for quotation and orders - therefore one more
 
 759       # check to be sure NOT to link from order to quotation
 
 760       foreach (qw(orderitems)) {
 
 761         if (!$form->{saveasnew} && !$form->{useasnew} && $form->{"converted_from_${_}_id_$i"}
 
 762               && $form->{type} !~ 'quotation') {
 
 763           RecordLinks->create_links('dbh'        => $dbh,
 
 766                                     'from_ids'   => $form->{"converted_from_${_}_id_$i"},
 
 767                                     'to_table'   => 'orderitems',
 
 768                                     'to_id'      => $orderitems_id,
 
 771         delete $form->{"converted_from_${_}_id_$i"};
 
 776   # search for orphaned ids
 
 777   $query  = sprintf 'SELECT id FROM orderitems WHERE trans_id = ? AND NOT id IN (%s)', join ', ', ("?") x scalar @processed_orderitems;
 
 778   @values = (conv_i($form->{id}), map { conv_i($_) } @processed_orderitems);
 
 779   my @orphaned_ids = map { $_->{id} } selectall_hashref_query($form, $dbh, $query, @values);
 
 781   if (scalar @orphaned_ids) {
 
 782     # clean up orderitems
 
 783     $query  = sprintf 'DELETE FROM orderitems WHERE id IN (%s)', join ', ', ("?") x scalar @orphaned_ids;
 
 784     do_query($form, $dbh, $query, @orphaned_ids);
 
 787   $reqdate = ($form->{reqdate}) ? $form->{reqdate} : undef;
 
 791   map { $tax += $form->round_amount($taxaccounts{$_}, 2) } keys %taxaccounts;
 
 793   $amount = $form->round_amount($netamount + $tax, 2, 1);
 
 794   $netamount = $form->round_amount($netamount, 2);
 
 796   if ($form->{currency} eq $form->{defaultcurrency}) {
 
 797     $form->{exchangerate} = 1;
 
 799     $exchangerate = $form->check_exchangerate($myconfig, $form->{currency}, $form->{transdate}, ($form->{vc} eq 'customer') ? 'buy' : 'sell');
 
 802   # from inputfield (exchangerate) or hidden (forex)
 
 803   my $exchangerate_from_form = $form->{forex} || $form->parse_amount($myconfig, $form->{exchangerate});
 
 805   $form->{exchangerate} = $exchangerate || $exchangerate_from_form;
 
 807   my $quotation = $form->{type} =~ /_order$/ ? 'f' : 't';
 
 812          ordnumber = ?, quonumber = ?, cusordnumber = ?, transdate = ?, vendor_id = ?,
 
 813          customer_id = ?, amount = ?, netamount = ?, reqdate = ?, tax_point = ?, taxincluded = ?,
 
 814          shippingpoint = ?, shipvia = ?, notes = ?, intnotes = ?, currency_id = (SELECT id FROM currencies WHERE name=?), closed = ?,
 
 815          delivered = ?, proforma = ?, quotation = ?, department_id = ?, language_id = ?,
 
 816          taxzone_id = ?, shipto_id = ?, billing_address_id = ?, payment_id = ?, delivery_vendor_id = ?, delivery_customer_id = ?,delivery_term_id = ?,
 
 817          globalproject_id = ?, employee_id = ?, salesman_id = ?, cp_id = ?, transaction_description = ?, marge_total = ?, marge_percent = ?
 
 818          , order_probability = ?, expected_billing_date = ?
 
 821   @values = ($form->{ordnumber} || '', $form->{quonumber},
 
 822              $form->{cusordnumber}, conv_date($form->{transdate}),
 
 823              conv_i($form->{vendor_id}), conv_i($form->{customer_id}),
 
 824              $amount, $netamount, conv_date($reqdate), conv_date($form->{tax_point}),
 
 825              $form->{taxincluded} ? 't' : 'f', $form->{shippingpoint},
 
 826              $form->{shipvia}, $restricter->process($form->{notes}), $form->{intnotes},
 
 827              $form->{currency}, $form->{closed} ? 't' : 'f',
 
 828              $form->{delivered} ? "t" : "f", $form->{proforma} ? 't' : 'f',
 
 829              $quotation, conv_i($form->{department_id}),
 
 830              conv_i($form->{language_id}), conv_i($form->{taxzone_id}),
 
 831              conv_i($form->{shipto_id}), conv_i($form->{billing_address_id}), conv_i($form->{payment_id}),
 
 832              conv_i($form->{delivery_vendor_id}),
 
 833              conv_i($form->{delivery_customer_id}),
 
 834              conv_i($form->{delivery_term_id}),
 
 835              conv_i($form->{globalproject_id}), conv_i($form->{employee_id}),
 
 836              conv_i($form->{salesman_id}), conv_i($form->{cp_id}),
 
 837              $form->{transaction_description},
 
 838              $form->{marge_total} * 1, $form->{marge_percent} * 1,
 
 839              $form->{order_probability} * 1, conv_date($form->{expected_billing_date}),
 
 840              conv_i($form->{id}));
 
 841   do_query($form, $dbh, $query, @values);
 
 843   $form->new_lastmtime('oe');
 
 845   $form->{ordtotal} = $amount;
 
 847   $form->{name} = $form->{ $form->{vc} };
 
 848   $form->{name} =~ s/--\Q$form->{"$form->{vc}_id"}\E//;
 
 851   if (!$form->{shipto_id}) {
 
 852     $form->add_shipto($dbh, $form->{id}, "OE");
 
 855   # save printed, emailed, queued
 
 856   $form->save_status($dbh);
 
 858   # Link this record to the records it was created from.
 
 859   $form->{convert_from_oe_ids} =~ s/^\s+//;
 
 860   $form->{convert_from_oe_ids} =~ s/\s+$//;
 
 861   my @convert_from_oe_ids      =  split m/\s+/, $form->{convert_from_oe_ids};
 
 862   delete $form->{convert_from_oe_ids};
 
 863   if (!$form->{useasnew} && scalar @convert_from_oe_ids) {
 
 864     RecordLinks->create_links('dbh'        => $dbh,
 
 866                               'from_table' => 'oe',
 
 867                               'from_ids'   => \@convert_from_oe_ids,
 
 869                               'to_id'      => $form->{id},
 
 871     $self->_close_quotations_rfqs('dbh'     => $dbh,
 
 872                                   'from_id' => \@convert_from_oe_ids,
 
 873                                   'to_id'   => $form->{id});
 
 876   if (($form->{currency} ne $form->{defaultcurrency}) && !$exchangerate) {
 
 877     if ($form->{vc} eq 'customer') {
 
 878       $form->update_exchangerate($dbh, $form->{currency}, $form->{transdate}, $form->{exchangerate}, 0);
 
 880     if ($form->{vc} eq 'vendor') {
 
 881       $form->update_exchangerate($dbh, $form->{currency}, $form->{transdate}, 0, $form->{exchangerate});
 
 885   $form->{saved_xyznumber} = $form->{$form->{type} =~ /_quotation$/ ?
 
 886                                        "quonumber" : "ordnumber"};
 
 888   Common::webdav_folder($form);
 
 890   $self->save_periodic_invoices_config(dbh         => $dbh,
 
 891                                        oe_id       => $form->{id},
 
 892                                        config_yaml => $form->{periodic_invoices_config})
 
 893     if ($form->{type} eq 'sales_order');
 
 895   $self->_link_created_sales_order_to_requirement_specs_for_sales_quotations(
 
 896     type               => $form->{type},
 
 897     converted_from_ids => \@convert_from_oe_ids,
 
 898     sales_order_id     => $form->{id},
 
 902   $self->_set_project_in_linked_requirement_spec(
 
 903     type           => $form->{type},
 
 904     project_id     => $form->{globalproject_id},
 
 905     sales_order_id => $form->{id},
 
 908   $main::lxdebug->leave_sub();
 
 913 sub _link_created_sales_order_to_requirement_specs_for_sales_quotations {
 
 914   my ($self, %params) = @_;
 
 916   # If this is a sales order created from a sales quotation and if
 
 917   # that sales quotation was created from a requirement spec document
 
 918   # then link the newly created sales order to the requirement spec
 
 921   return if !$params{is_new};
 
 922   return if  $params{type} ne 'sales_order';
 
 923   return if !@{ $params{converted_from_ids} };
 
 925   my $oe_objects       = SL::DB::Manager::Order->get_all(where => [ id => $params{converted_from_ids} ]);
 
 926   my @sales_quotations = grep { $_->is_type('sales_quotation') } @{ $oe_objects };
 
 928   return if !@sales_quotations;
 
 930   my $rs_orders = SL::DB::Manager::RequirementSpecOrder->get_all(where => [ order_id => [ map { $_->id } @sales_quotations ] ]);
 
 932   return if !@{ $rs_orders };
 
 934   $rs_orders->[0]->db->with_transaction(sub {
 
 935     foreach my $rs_order (@{ $rs_orders }) {
 
 936       SL::DB::RequirementSpecOrder->new(
 
 937         order_id            => $params{sales_order_id},
 
 938         requirement_spec_id => $rs_order->requirement_spec_id,
 
 939         version_id          => $rs_order->version_id,
 
 947 sub _set_project_in_linked_requirement_spec {
 
 948   my ($self, %params) = @_;
 
 950   return if  $params{type} ne 'sales_order';
 
 951   return if !$params{project_id} || !$params{sales_order_id};
 
 954     UPDATE requirement_specs
 
 957       SELECT so.requirement_spec_id
 
 958       FROM requirement_spec_orders so
 
 959       WHERE so.order_id = ?
 
 963   do_query($::form, $::form->get_standard_dbh, $query, $params{project_id}, $params{sales_order_id});
 
 966 sub save_periodic_invoices_config {
 
 967   my ($self, %params) = @_;
 
 969   return if !$params{oe_id};
 
 971   my $config = $params{config_yaml} ? SL::YAML::Load($params{config_yaml}) : undef;
 
 972   return if 'HASH' ne ref $config;
 
 974   my $obj  = SL::DB::Manager::PeriodicInvoicesConfig->find_by(oe_id => $params{oe_id})
 
 975           || SL::DB::PeriodicInvoicesConfig->new(oe_id => $params{oe_id});
 
 976   $obj->update_attributes(%{ $config });
 
 979 sub load_periodic_invoice_config {
 
 983   delete $form->{periodic_invoices_config};
 
 986     my $config_obj = SL::DB::Manager::PeriodicInvoicesConfig->find_by(oe_id => $form->{id});
 
 989       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
 
 990                                                       print printer_id copies direct_debit send_email email_recipient_contact_id email_recipient_address email_sender email_subject email_body) };
 
 991       $form->{periodic_invoices_config} = SL::YAML::Dump($config);
 
 996 sub _close_quotations_rfqs {
 
 997   $main::lxdebug->enter_sub();
 
1002   Common::check_params(\%params, qw(from_id to_id));
 
1004   my $myconfig = \%main::myconfig;
 
1005   my $form     = $main::form;
 
1007   my $dbh      = $params{dbh} || SL::DB->client->dbh;
 
1009   SL::DB->client->with_transaction(sub {
 
1011     my $query    = qq|SELECT quotation FROM oe WHERE id = ?|;
 
1012     my $sth      = prepare_query($form, $dbh, $query);
 
1014     do_statement($form, $sth, $query, conv_i($params{to_id}));
 
1016     my ($quotation) = $sth->fetchrow_array();
 
1024     foreach my $from_id (@{ $params{from_id} }) {
 
1025       $from_id = conv_i($from_id);
 
1026       do_statement($form, $sth, $query, $from_id);
 
1027       ($quotation) = $sth->fetchrow_array();
 
1028       push @close_ids, $from_id if ($quotation);
 
1033     if (scalar @close_ids) {
 
1034       $query = qq|UPDATE oe SET closed = TRUE WHERE id IN (| . join(', ', ('?') x scalar @close_ids) . qq|)|;
 
1035       do_query($form, $dbh, $query, @close_ids);
 
1038   }) or do { die SL::DB->client->error };
 
1040   $main::lxdebug->leave_sub();
 
1044   $main::lxdebug->enter_sub();
 
1046   my ($self, $myconfig, $form) = @_;
 
1048   my $rc = SL::DB::Order->new->db->with_transaction(sub {
 
1049     my @spoolfiles = grep { $_ } map { $_->spoolfile } @{ SL::DB::Manager::Status->get_all(where => [ trans_id => $form->{id} ]) };
 
1051     SL::DB::Order->new(id => $form->{id})->delete;
 
1053     my $spool = $::lx_office_conf{paths}->{spool};
 
1054     unlink map { "$spool/$_" } @spoolfiles if $spool;
 
1057   }) or do { die SL::DB->client->error };
 
1059   $main::lxdebug->leave_sub();
 
1065   my ($self, $myconfig, $form) = @_;
 
1066   $main::lxdebug->enter_sub();
 
1068   my $rc = SL::DB->client->with_transaction(\&_retrieve, $self, $myconfig, $form);
 
1070   $::lxdebug->leave_sub;
 
1075   my ($self, $myconfig, $form) = @_;
 
1077   # connect to database
 
1078   my $dbh = SL::DB->client->dbh;
 
1080   my ($query, $query_add, @values, @ids, $sth);
 
1082   # translate the ids (given by id_# and trans_id_#) into one array of ids, so we can join them later
 
1084     push @ids, $form->{"trans_id_$_"}
 
1085       if ($form->{"multi_id_$_"} and $form->{"trans_id_$_"})
 
1086   } (1 .. $form->{"rowcount"});
 
1088   if ($form->{rowcount} && scalar @ids) {
 
1089     $form->{convert_from_oe_ids} = join ' ', @ids;
 
1092   # if called in multi id mode, and still only got one id, switch back to single id
 
1093   if ($form->{"rowcount"} and $#ids == 0) {
 
1094     $form->{"id"} = $ids[0];
 
1096     delete $form->{convert_from_oe_ids};
 
1099   # and remember for the rest of the function
 
1100   my $is_collective_order = scalar @ids;
 
1102   # If collective order was created from exactly 1 order, we assume the same
 
1103   # behaviour as a "save as new" from within an order is actually desired, i.e.
 
1104   # the original order isn't part of a workflow where we want to remember
 
1105   # record_links, but simply a quick way of generating a new order from an old
 
1106   # one without having to enter everything again.
 
1107   # Setting useasnew will prevent the creation of record_links for the items
 
1108   # when saving the new order.
 
1109   # This form variable is probably not necessary, could just set saveasnew instead
 
1110   $form->{useasnew} = 1 if $is_collective_order == 1;
 
1113     my $extra_days = $form->{type} eq 'sales_quotation' ? $::instance_conf->get_reqdate_interval       :
 
1114                      $form->{type} eq 'sales_order'     ? $::instance_conf->get_delivery_date_interval : 1;
 
1115     if (   ($form->{type} eq 'sales_order'     &&  !$::instance_conf->get_deliverydate_on)
 
1116         || ($form->{type} eq 'sales_quotation' &&  !$::instance_conf->get_reqdate_on)) {
 
1117       $form->{reqdate}   = '';
 
1119       $form->{reqdate}   = DateTime->today_local->next_workday(extra_days => $extra_days)->to_kivitendo;
 
1121     $form->{transdate} = DateTime->today_local->to_kivitendo;
 
1124   # get default accounts
 
1125   $query = qq|SELECT (SELECT c.accno FROM chart c WHERE d.inventory_accno_id = c.id) AS inventory_accno,
 
1126                      (SELECT c.accno FROM chart c WHERE d.income_accno_id    = c.id) AS income_accno,
 
1127                      (SELECT c.accno FROM chart c WHERE d.expense_accno_id   = c.id) AS expense_accno,
 
1128                      (SELECT c.accno FROM chart c WHERE d.fxgain_accno_id    = c.id) AS fxgain_accno,
 
1129                      (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id    = c.id) AS fxloss_accno,
 
1130                      (SELECT c.accno FROM chart c WHERE d.rndgain_accno_id   = c.id) AS rndgain_accno,
 
1131                      (SELECT c.accno FROM chart c WHERE d.rndloss_accno_id   = c.id) AS rndloss_accno
 
1134   my $ref = selectfirst_hashref_query($form, $dbh, $query);
 
1135   map { $form->{$_} = $ref->{$_} } keys %$ref;
 
1137   $form->{currency} = $form->get_default_currency($myconfig);
 
1139   # set reqdate if this is an invoice->order conversion. If someone knows a better check to ensure
 
1140   # we come from invoices, feel free.
 
1141   $form->{reqdate} = $form->{deliverydate}
 
1142     if (    $form->{deliverydate}
 
1143         and $form->{callback} =~ /action=ar_transactions/);
 
1145   my $vc = $form->{vc} eq "customer" ? "customer" : "vendor";
 
1147   if ($form->{id} or @ids) {
 
1149     # retrieve order for single id
 
1150     # NOTE: this query is intended to fetch all information only ONCE.
 
1151     # so if any of these infos is important (or even different) for any item,
 
1152     # it will be killed out and then has to be fetched from the item scope query further down
 
1154       qq|SELECT o.cp_id, o.ordnumber, o.transdate, o.reqdate,
 
1155            o.taxincluded, o.shippingpoint, o.shipvia, o.notes, o.intnotes,
 
1156            (SELECT cu.name FROM currencies cu WHERE cu.id=o.currency_id) AS currency, e.name AS employee, o.employee_id, o.salesman_id,
 
1157            o.${vc}_id, cv.name AS ${vc}, o.amount AS invtotal,
 
1158            o.closed, o.reqdate, o.tax_point, o.quonumber, o.department_id, o.cusordnumber,
 
1160            d.description AS department, o.payment_id, o.language_id, o.taxzone_id,
 
1161            o.delivery_customer_id, o.delivery_vendor_id, o.proforma, o.shipto_id, o.billing_address_id,
 
1162            o.globalproject_id, o.delivered, o.transaction_description, o.delivery_term_id,
 
1163            o.itime::DATE AS insertdate, o.order_probability, o.expected_billing_date
 
1165          JOIN ${vc} cv ON (o.${vc}_id = cv.id)
 
1166          LEFT JOIN employee e ON (o.employee_id = e.id)
 
1167          LEFT JOIN department d ON (o.department_id = d.id) | .
 
1170          : "WHERE o.id IN (" . join(', ', map("? ", @ids)) . ")"
 
1172     @values = $form->{id} ? ($form->{id}) : @ids;
 
1173     $sth = prepare_execute_query($form, $dbh, $query, @values);
 
1175     $ref = $sth->fetchrow_hashref("NAME_lc");
 
1178       map { $form->{$_} = $ref->{$_} } keys %$ref;
 
1180       $form->{saved_xyznumber} = $form->{$form->{type} =~ /_quotation$/ ? "quonumber" : "ordnumber"};
 
1182       # set all entries for multiple ids blank that yield different information
 
1183       while ($ref = $sth->fetchrow_hashref("NAME_lc")) {
 
1184         map { $form->{$_} = '' if ($ref->{$_} ne $form->{$_}) } keys %$ref;
 
1187     $form->{mtime}   ||= $form->{itime};
 
1188     $form->{lastmtime} = $form->{mtime};
 
1190     # if not given, fill transdate with current_date
 
1191     $form->{transdate} = $form->current_date($myconfig)
 
1192       unless $form->{transdate};
 
1196     if ($form->{delivery_customer_id}) {
 
1197       $query = qq|SELECT name FROM customer WHERE id = ?|;
 
1198       ($form->{delivery_customer_string}) = selectrow_query($form, $dbh, $query, $form->{delivery_customer_id});
 
1201     if ($form->{delivery_vendor_id}) {
 
1202       $query = qq|SELECT name FROM customer WHERE id = ?|;
 
1203       ($form->{delivery_vendor_string}) = selectrow_query($form, $dbh, $query, $form->{delivery_vendor_id});
 
1206     # shipto and pinted/mailed/queued status makes only sense for single id retrieve
 
1208       $query = qq|SELECT s.* FROM shipto s WHERE s.trans_id = ? AND s.module = 'OE'|;
 
1209       $sth = prepare_execute_query($form, $dbh, $query, $form->{id});
 
1211       $ref = $sth->fetchrow_hashref("NAME_lc");
 
1212       $form->{$_} = $ref->{$_} for grep { m{^shipto(?!_id$)} } keys %$ref;
 
1215       if ($ref->{shipto_id}) {
 
1216         my $cvars = CVar->get_custom_variables(
 
1219           trans_id => $ref->{shipto_id},
 
1221         $form->{"shiptocvar_$_->{name}"} = $_->{value} for @{ $cvars };
 
1224       # get printed, emailed and queued
 
1225       $query = qq|SELECT s.printed, s.emailed, s.spoolfile, s.formname FROM status s WHERE s.trans_id = ?|;
 
1226       $sth = prepare_execute_query($form, $dbh, $query, $form->{id});
 
1228       while ($ref = $sth->fetchrow_hashref("NAME_lc")) {
 
1229         $form->{printed} .= "$ref->{formname} " if $ref->{printed};
 
1230         $form->{emailed} .= "$ref->{formname} " if $ref->{emailed};
 
1231         $form->{queued}  .= "$ref->{formname} $ref->{spoolfile} " if $ref->{spoolfile};
 
1234       map { $form->{$_} =~ s/ +$//g } qw(printed emailed queued);
 
1237     my $transdate = $form->{tax_point} ? $dbh->quote($form->{tax_point}) : $form->{transdate} ? $dbh->quote($form->{transdate}) : "current_date";
 
1239     $form->{taxzone_id} = 0 unless ($form->{taxzone_id});
 
1240     unshift @values, ($form->{taxzone_id}) x 2;
 
1242     # retrieve individual items
 
1243     # this query looks up all information about the items
 
1244     # stuff different from the whole will not be overwritten, but saved with a suffix.
 
1246       qq|SELECT o.id AS orderitems_id,
 
1247            c1.accno AS inventory_accno, c1.new_chart_id AS inventory_new_chart, date($transdate) - c1.valid_from as inventory_valid,
 
1248            c2.accno AS income_accno,    c2.new_chart_id AS income_new_chart,    date($transdate) - c2.valid_from as income_valid,
 
1249            c3.accno AS expense_accno,   c3.new_chart_id AS expense_new_chart,   date($transdate) - c3.valid_from as expense_valid,
 
1250            oe.ordnumber AS ordnumber_oe, oe.transdate AS transdate_oe, oe.cusordnumber AS cusordnumber_oe,
 
1251            p.partnumber, p.part_type, p.listprice, o.description, o.qty,
 
1252            p.classification_id,
 
1253            o.sellprice, o.parts_id AS id, o.unit, o.discount, p.notes AS partnotes, p.part_type,
 
1254            o.reqdate, o.project_id, o.serialnumber, o.ship, o.lastcost,
 
1255            o.ordnumber, o.transdate, o.cusordnumber, o.subtotal, o.longdescription,
 
1256            o.price_factor_id, o.price_factor, o.marge_price_factor, o.active_price_source, o.active_discount_source,
 
1257            pr.projectnumber, p.formel,
 
1258            pg.partsgroup, o.pricegroup_id, (SELECT pricegroup FROM pricegroup WHERE id=o.pricegroup_id) as pricegroup
 
1260          JOIN parts p ON (o.parts_id = p.id)
 
1261          JOIN oe ON (o.trans_id = oe.id)
 
1262          LEFT JOIN chart c1 ON ((SELECT inventory_accno_id                   FROM buchungsgruppen WHERE id=p.buchungsgruppen_id) = c1.id)
 
1263          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)
 
1264          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)
 
1265          LEFT JOIN project pr ON (o.project_id = pr.id)
 
1266          LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id) | .
 
1268        ? qq|WHERE o.trans_id = ?|
 
1269        : qq|WHERE o.trans_id IN (| . join(", ", map("?", @ids)) . qq|)|) .
 
1270       qq|ORDER BY o.trans_id, o.position|;
 
1272     @ids = $form->{id} ? ($form->{id}) : @ids;
 
1273     $sth = prepare_execute_query($form, $dbh, $query, @values);
 
1275     while ($ref = $sth->fetchrow_hashref("NAME_lc")) {
 
1276       # Retrieve custom variables.
 
1277       my $cvars = CVar->get_custom_variables(dbh        => $dbh,
 
1279                                              sub_module => 'orderitems',
 
1280                                              trans_id   => $ref->{orderitems_id},
 
1282       map { $ref->{"ic_cvar_$_->{name}"} = $_->{value} } @{ $cvars };
 
1285       if (!$ref->{"part_type"} eq 'part') {
 
1286         map({ delete($ref->{$_}); } qw(inventory_accno inventory_new_chart inventory_valid));
 
1288       # delete($ref->{"part_inventory_accno_id"});
 
1290       # in collective order, copy global ordnumber, transdate, cusordnumber into item scope
 
1291       #   unless already present there
 
1292       # remove _oe entries afterwards
 
1293       map { $ref->{$_} = $ref->{"${_}_oe"} if ($ref->{$_} eq '') }
 
1294         qw|ordnumber transdate cusordnumber|
 
1296       map { delete $ref->{$_} } qw|ordnumber_oe transdate_oe cusordnumber_oe|;
 
1300       while ($ref->{inventory_new_chart} && ($ref->{inventory_valid} >= 0)) {
 
1302           qq|SELECT accno AS inventory_accno, | .
 
1303           qq|  new_chart_id AS inventory_new_chart, | .
 
1304           qq|  date($transdate) - valid_from AS inventory_valid | .
 
1305           qq|FROM chart WHERE id = $ref->{inventory_new_chart}|;
 
1306         ($ref->{inventory_accno}, $ref->{inventory_new_chart},
 
1307          $ref->{inventory_valid}) = selectrow_query($form, $dbh, $query);
 
1310       while ($ref->{income_new_chart} && ($ref->{income_valid} >= 0)) {
 
1312           qq|SELECT accno AS income_accno, | .
 
1313           qq|  new_chart_id AS income_new_chart, | .
 
1314           qq|  date($transdate) - valid_from AS income_valid | .
 
1315           qq|FROM chart WHERE id = $ref->{income_new_chart}|;
 
1316         ($ref->{income_accno}, $ref->{income_new_chart},
 
1317          $ref->{income_valid}) = selectrow_query($form, $dbh, $query);
 
1320       while ($ref->{expense_new_chart} && ($ref->{expense_valid} >= 0)) {
 
1322           qq|SELECT accno AS expense_accno, | .
 
1323           qq|  new_chart_id AS expense_new_chart, | .
 
1324           qq|  date($transdate) - valid_from AS expense_valid | .
 
1325           qq|FROM chart WHERE id = $ref->{expense_new_chart}|;
 
1326         ($ref->{expense_accno}, $ref->{expense_new_chart},
 
1327          $ref->{expense_valid}) = selectrow_query($form, $dbh, $query);
 
1330       # delete orderitems_id in collective orders, so that they get cloned no matter what
 
1331       # is this correct? or is the following meant?
 
1332       # remember orderitems_ids in converted_from_orderitems_ids, so that they may be linked
 
1333       $ref->{converted_from_orderitems_id} = delete $ref->{orderitems_id} if $is_collective_order;
 
1335       # get tax rates and description
 
1336       my $accno_id = ($form->{vc} eq "customer") ? $ref->{income_accno} : $ref->{expense_accno};
 
1338         qq|SELECT c.accno, t.taxdescription, t.rate, t.id as tax_id, c.accno as taxnumber | .
 
1340         qq|LEFT JOIN chart c on (c.id = t.chart_id) | .
 
1341         qq|WHERE t.id IN (SELECT tk.tax_id FROM taxkeys tk | .
 
1342         qq|               WHERE tk.chart_id = (SELECT id FROM chart WHERE accno = ?) | .
 
1343         qq|                 AND startdate <= $transdate ORDER BY startdate DESC LIMIT 1) | .
 
1344         qq|ORDER BY c.accno|;
 
1345       my $stw = prepare_execute_query($form, $dbh, $query, $accno_id);
 
1346       $ref->{taxaccounts} = "";
 
1348       while (my $ptr = $stw->fetchrow_hashref("NAME_lc")) {
 
1349         if (($ptr->{accno} eq "") && ($ptr->{rate} == 0)) {
 
1353         $ref->{taxaccounts} .= "$ptr->{accno} ";
 
1354         if (!($form->{taxaccounts} =~ /\Q$ptr->{accno}\E/)) {
 
1355           $form->{"$ptr->{accno}_rate"}        = $ptr->{rate};
 
1356           $form->{"$ptr->{accno}_description"} = $ptr->{taxdescription};
 
1357           $form->{"$ptr->{accno}_taxnumber"}   = $ptr->{taxnumber};
 
1358           $form->{"$ptr->{accno}_tax_id"}      = $ptr->{tax_id};
 
1359           $form->{taxaccounts} .= "$ptr->{accno} ";
 
1364       chop $ref->{taxaccounts};
 
1366       push @{ $form->{form_details} }, $ref;
 
1373     # get last name used
 
1374     $form->lastname_used($dbh, $myconfig, $form->{vc})
 
1375       unless $form->{"$form->{vc}_id"};
 
1379   $form->{exchangerate} = $form->get_exchangerate($dbh, $form->{currency}, $form->{transdate}, ($form->{vc} eq 'customer') ? "buy" : "sell");
 
1381   Common::webdav_folder($form);
 
1383   $self->load_periodic_invoice_config($form);
 
1388 sub retrieve_simple {
 
1389   $main::lxdebug->enter_sub();
 
1394   Common::check_params(\%params, qw(id));
 
1396   my $myconfig    = \%main::myconfig;
 
1397   my $form        = $main::form;
 
1399   my $dbh         = $params{dbh} || $form->get_standard_dbh($myconfig);
 
1401   my $oe_query    = qq|SELECT * FROM oe         WHERE id = ?|;
 
1402   my $oi_query    = qq|SELECT * FROM orderitems WHERE trans_id = ? ORDER BY position|;
 
1404   my $order            = selectfirst_hashref_query($form, $dbh, $oe_query, conv_i($params{id}));
 
1405   $order->{orderitems} = selectall_hashref_query(  $form, $dbh, $oi_query, conv_i($params{id}));
 
1407   $main::lxdebug->leave_sub();
 
1413   $main::lxdebug->enter_sub();
 
1415   my ($self, $myconfig, $form) = @_;
 
1417   # connect to database
 
1418   my $dbh = SL::DB->client->dbh;
 
1424   my $nodiscount_subtotal = 0;
 
1425   my $discount_subtotal = 0;
 
1428   my @partsgroup = ();
 
1431   my $subtotal_header = 0;
 
1432   my $subposition = 0;
 
1440   push(@project_ids, $form->{"globalproject_id"}) if ($form->{"globalproject_id"});
 
1442   $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS');
 
1445   foreach my $pfac (@{ $form->{ALL_PRICE_FACTORS} }) {
 
1446     $price_factors{$pfac->{id}}  = $pfac;
 
1447     $pfac->{factor}             *= 1;
 
1448     $pfac->{formatted_factor}    = $form->format_amount($myconfig, $pfac->{factor});
 
1451   # sort items by partsgroup
 
1452   for $i (1 .. $form->{rowcount}) {
 
1454     if ($form->{"partsgroup_$i"} && $form->{groupitems}) {
 
1455       $partsgroup = $form->{"partsgroup_$i"};
 
1457     push @partsgroup, [$i, $partsgroup];
 
1458     push(@project_ids, $form->{"project_id_$i"}) if ($form->{"project_id_$i"});
 
1464     $projects = SL::DB::Manager::Project->get_all(query => [ id => \@project_ids ]);
 
1465     %projects_by_id = map { $_->id => $_ } @$projects;
 
1468   if ($projects_by_id{$form->{"globalproject_id"}}) {
 
1469     $form->{globalprojectnumber} = $projects_by_id{$form->{"globalproject_id"}}->projectnumber;
 
1470     $form->{globalprojectdescription} = $projects_by_id{$form->{"globalproject_id"}}->description;
 
1472     for (@{ $projects_by_id{$form->{"globalproject_id"}}->cvars_by_config }) {
 
1473       $form->{"project_cvar_" . $_->config->name} = $_->value_as_text;
 
1477   $form->{discount} = [];
 
1479   # get some values of parts from db on store them in extra array,
 
1480   # so that they can be sorted in later
 
1481   my %prepared_template_arrays = IC->prepare_parts_for_printing(myconfig => $myconfig, form => $form);
 
1482   my @prepared_arrays          = keys %prepared_template_arrays;
 
1483   my @separate_totals          = qw(non_separate_subtotal);
 
1485   $form->{TEMPLATE_ARRAYS} = { };
 
1487   my $ic_cvar_configs = CVar->get_configs(module => 'IC');
 
1488   my $project_cvar_configs = CVar->get_configs(module => 'Projects');
 
1491     qw(runningnumber number description longdescription qty qty_nofmt ship ship_nofmt unit bin
 
1492        partnotes serialnumber reqdate sellprice sellprice_nofmt listprice listprice_nofmt netprice netprice_nofmt
 
1493        discount discount_nofmt p_discount discount_sub discount_sub_nofmt nodiscount_sub nodiscount_sub_nofmt
 
1494        linetotal linetotal_nofmt nodiscount_linetotal nodiscount_linetotal_nofmt tax_rate projectnumber projectdescription
 
1495        price_factor price_factor_name partsgroup weight weight_nofmt lineweight lineweight_nofmt optional);
 
1497   push @arrays, map { "ic_cvar_$_->{name}" } @{ $ic_cvar_configs };
 
1498   push @arrays, map { "project_cvar_$_->{name}" } @{ $project_cvar_configs };
 
1500   my @tax_arrays = qw(taxbase tax taxdescription taxrate taxnumber);
 
1502   map { $form->{TEMPLATE_ARRAYS}->{$_} = [] } (@arrays, @tax_arrays, @prepared_arrays);
 
1504   my $totalweight = 0;
 
1506   foreach $item (sort { $a->[1] cmp $b->[1] } @partsgroup) {
 
1509     if ($item->[1] ne $sameitem) {
 
1510       push(@{ $form->{TEMPLATE_ARRAYS}->{entry_type}  }, 'partsgroup');
 
1511       push(@{ $form->{TEMPLATE_ARRAYS}->{description} }, qq|$item->[1]|);
 
1512       $sameitem = $item->[1];
 
1514       map({ push(@{ $form->{TEMPLATE_ARRAYS}->{$_} }, "") } grep({ $_ ne "description" } (@arrays, @prepared_arrays)));
 
1517     $form->{"qty_$i"} = $form->parse_amount($myconfig, $form->{"qty_$i"});
 
1519     if ($form->{"id_$i"} != 0) {
 
1521       # add number, description and qty to $form->{number}, ....
 
1523       if ($form->{"subtotal_$i"} && !$subtotal_header) {
 
1524         $subtotal_header = $i;
 
1525         $position = int($position);
 
1528       } elsif ($subtotal_header) {
 
1530         $position = int($position);
 
1531         $position = $position.".".$subposition;
 
1533         $position = int($position);
 
1537       my $price_factor = $price_factors{$form->{"price_factor_id_$i"}} || { 'factor' => 1 };
 
1539       push(@{ $form->{TEMPLATE_ARRAYS}->{$_} },                $prepared_template_arrays{$_}[$i - 1]) for @prepared_arrays;
 
1541       push @{ $form->{TEMPLATE_ARRAYS}->{entry_type} },        'normal';
 
1542       push @{ $form->{TEMPLATE_ARRAYS}->{runningnumber} },     $position;
 
1543       push @{ $form->{TEMPLATE_ARRAYS}->{number} },            $form->{"partnumber_$i"};
 
1544       push @{ $form->{TEMPLATE_ARRAYS}->{description} },       $form->{"description_$i"};
 
1545       push @{ $form->{TEMPLATE_ARRAYS}->{longdescription} },   $form->{"longdescription_$i"};
 
1546       push @{ $form->{TEMPLATE_ARRAYS}->{qty} },               $form->format_amount($myconfig, $form->{"qty_$i"});
 
1547       push @{ $form->{TEMPLATE_ARRAYS}->{qty_nofmt} },         $form->{"qty_$i"};
 
1548       push @{ $form->{TEMPLATE_ARRAYS}->{ship} },              $form->format_amount($myconfig, $form->{"ship_$i"});
 
1549       push @{ $form->{TEMPLATE_ARRAYS}->{ship_nofmt} },        $form->{"ship_$i"};
 
1550       push @{ $form->{TEMPLATE_ARRAYS}->{unit} },              $form->{"unit_$i"};
 
1551       push @{ $form->{TEMPLATE_ARRAYS}->{bin} },               $form->{"bin_$i"};
 
1552       push @{ $form->{TEMPLATE_ARRAYS}->{partnotes} },         $form->{"partnotes_$i"};
 
1553       push @{ $form->{TEMPLATE_ARRAYS}->{serialnumber} },      $form->{"serialnumber_$i"};
 
1554       push @{ $form->{TEMPLATE_ARRAYS}->{reqdate} },           $form->{"reqdate_$i"};
 
1555       push @{ $form->{TEMPLATE_ARRAYS}->{sellprice} },         $form->{"sellprice_$i"};
 
1556       push @{ $form->{TEMPLATE_ARRAYS}->{sellprice_nofmt} },   $form->parse_amount($myconfig, $form->{"sellprice_$i"});
 
1557       push @{ $form->{TEMPLATE_ARRAYS}->{listprice} },         $form->format_amount($myconfig, $form->{"listprice_$i"}, 2);
 
1558       push @{ $form->{TEMPLATE_ARRAYS}->{listprice_nofmt} },   $form->{"listprice_$i"};
 
1559       push @{ $form->{TEMPLATE_ARRAYS}->{price_factor} },      $price_factor->{formatted_factor};
 
1560       push @{ $form->{TEMPLATE_ARRAYS}->{price_factor_name} }, $price_factor->{description};
 
1561       push @{ $form->{TEMPLATE_ARRAYS}->{partsgroup} },        $form->{"partsgroup_$i"};
 
1562       push @{ $form->{TEMPLATE_ARRAYS}->{optional} },          $form->{"optional_$i"};
 
1564       my $sellprice     = $form->parse_amount($myconfig, $form->{"sellprice_$i"});
 
1565       my ($dec)         = ($sellprice =~ /\.(\d+)/);
 
1566       my $decimalplaces = max 2, length($dec);
 
1568       my $parsed_discount            = $form->parse_amount($myconfig, $form->{"discount_$i"});
 
1570       my $linetotal_exact            = $form->{"qty_$i"} * $sellprice * (100 - $parsed_discount) / 100 / $price_factor->{factor};
 
1571       my $linetotal                  = $form->round_amount($linetotal_exact, 2);
 
1573       my $nodiscount_exact_linetotal = $form->{"qty_$i"} * $sellprice                                  / $price_factor->{factor};
 
1574       my $nodiscount_linetotal       = $form->round_amount($nodiscount_exact_linetotal,2);
 
1576       my $discount                   = $nodiscount_linetotal - $linetotal; # is always rounded because $nodiscount_linetotal and $linetotal are rounded
 
1578       my $discount_round_error       = $discount + ($linetotal_exact - $nodiscount_exact_linetotal); # not used
 
1580       $form->{"netprice_$i"}   = $form->round_amount($form->{"qty_$i"} ? ($linetotal / $form->{"qty_$i"}) : 0, $decimalplaces);
 
1582       push @{ $form->{TEMPLATE_ARRAYS}->{netprice} },       ($form->{"netprice_$i"} != 0) ? $form->format_amount($myconfig, $form->{"netprice_$i"}, $decimalplaces) : '';
 
1583       push @{ $form->{TEMPLATE_ARRAYS}->{netprice_nofmt} }, ($form->{"netprice_$i"} != 0) ? $form->{"netprice_$i"} : '';
 
1585       $linetotal = ($linetotal != 0) ? $linetotal : '';
 
1587       push @{ $form->{TEMPLATE_ARRAYS}->{discount} },       ($discount != 0) ? $form->format_amount($myconfig, $discount * -1, 2) : '';
 
1588       push @{ $form->{TEMPLATE_ARRAYS}->{discount_nofmt} }, ($discount != 0) ? $discount * -1 : '';
 
1589       push @{ $form->{TEMPLATE_ARRAYS}->{p_discount} },     $form->{"discount_$i"};
 
1591       if ( $prepared_template_arrays{separate}[$i - 1]  ) {
 
1592         my $pabbr = $prepared_template_arrays{separate}[$i - 1];
 
1593         if ( ! $form->{"separate_${pabbr}_subtotal"} ) {
 
1594             push @separate_totals , "separate_${pabbr}_subtotal";
 
1595             $form->{"separate_${pabbr}_subtotal"} = 0;
 
1597         $form->{"separate_${pabbr}_subtotal"} += $linetotal;
 
1599         $form->{non_separate_subtotal} += $linetotal;
 
1602       $form->{ordtotal}         += $linetotal unless $form->{"optional_$i"};
 
1603       $form->{nodiscount_total} += $nodiscount_linetotal;
 
1604       $form->{discount_total}   += $discount;
 
1606       if ($subtotal_header) {
 
1607         $discount_subtotal   += $linetotal;
 
1608         $nodiscount_subtotal += $nodiscount_linetotal;
 
1611       if ($form->{"subtotal_$i"} && $subtotal_header && ($subtotal_header != $i)) {
 
1612         push @{ $form->{TEMPLATE_ARRAYS}->{discount_sub} },         $form->format_amount($myconfig, $discount_subtotal,   2);
 
1613         push @{ $form->{TEMPLATE_ARRAYS}->{discount_sub_nofmt} },   $discount_subtotal;
 
1614         push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_sub} },       $form->format_amount($myconfig, $nodiscount_subtotal, 2);
 
1615         push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_sub_nofmt} }, $nodiscount_subtotal;
 
1617         $discount_subtotal   = 0;
 
1618         $nodiscount_subtotal = 0;
 
1619         $subtotal_header     = 0;
 
1622         push @{ $form->{TEMPLATE_ARRAYS}->{$_} }, "" for qw(discount_sub nodiscount_sub discount_sub_nofmt nodiscount_sub_nofmt);
 
1625       if (!$form->{"discount_$i"}) {
 
1626         $nodiscount += $linetotal;
 
1629       my $project = $projects_by_id{$form->{"project_id_$i"}} || SL::DB::Project->new;
 
1631       push @{ $form->{TEMPLATE_ARRAYS}->{linetotal} },                  $form->format_amount($myconfig, $linetotal, 2);
 
1632       push @{ $form->{TEMPLATE_ARRAYS}->{linetotal_nofmt} },            $linetotal_exact;
 
1633       push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_linetotal} },       $form->format_amount($myconfig, $nodiscount_linetotal, 2);
 
1634       push @{ $form->{TEMPLATE_ARRAYS}->{nodiscount_linetotal_nofmt} }, $nodiscount_linetotal;
 
1635       push @{ $form->{TEMPLATE_ARRAYS}->{projectnumber} },              $project->projectnumber;
 
1636       push @{ $form->{TEMPLATE_ARRAYS}->{projectdescription} },         $project->description;
 
1638       my $lineweight = $form->{"qty_$i"} * $form->{"weight_$i"};
 
1639       $totalweight += $lineweight;
 
1640       push @{ $form->{TEMPLATE_ARRAYS}->{weight} },            $form->format_amount($myconfig, $form->{"weight_$i"}, 3);
 
1641       push @{ $form->{TEMPLATE_ARRAYS}->{weight_nofmt} },      $form->{"weight_$i"};
 
1642       push @{ $form->{TEMPLATE_ARRAYS}->{lineweight} },        $form->format_amount($myconfig, $lineweight, 3);
 
1643       push @{ $form->{TEMPLATE_ARRAYS}->{lineweight_nofmt} },  $lineweight;
 
1645       my ($taxamount, $taxbase);
 
1648       map { $taxrate += $form->{"${_}_rate"} } split(/ /, $form->{"taxaccounts_$i"});
 
1650       unless ($form->{"optional_$i"}) {
 
1651         if ($form->{taxincluded}) {
 
1654           $taxamount = $linetotal * $taxrate / (1 + $taxrate);
 
1655           $taxbase = $linetotal / (1 + $taxrate);
 
1657           $taxamount = $linetotal * $taxrate;
 
1658           $taxbase   = $linetotal;
 
1662       if ($taxamount != 0) {
 
1663         foreach my $accno (split / /, $form->{"taxaccounts_$i"}) {
 
1664           $taxaccounts{$accno} += $taxamount * $form->{"${accno}_rate"} / $taxrate;
 
1665           $taxbase{$accno}     += $taxbase;
 
1669       $tax_rate = $taxrate * 100;
 
1670       push(@{ $form->{TEMPLATE_ARRAYS}->{tax_rate} }, qq|$tax_rate|);
 
1672       if ($form->{"part_type_$i"} eq 'assembly') {
 
1675         # get parts and push them onto the stack
 
1677         if ($form->{groupitems}) {
 
1678           $sortorder = qq|ORDER BY pg.partsgroup, a.position|;
 
1680           $sortorder = qq|ORDER BY a.position|;
 
1683         $query = qq|SELECT p.partnumber, p.description, p.unit, a.qty, | .
 
1684                  qq|pg.partsgroup | .
 
1685                  qq|FROM assembly a | .
 
1686                  qq|  JOIN parts p ON (a.parts_id = p.id) | .
 
1687                  qq|    LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id) | .
 
1688                  qq|    WHERE a.bom = '1' | .
 
1689                  qq|    AND a.id = ? | . $sortorder;
 
1690         @values = ($form->{"id_$i"});
 
1691         $sth = $dbh->prepare($query);
 
1692         $sth->execute(@values) || $form->dberror($query);
 
1694         while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
 
1695           if ($form->{groupitems} && $ref->{partsgroup} ne $sameitem) {
 
1696             map({ push(@{ $form->{TEMPLATE_ARRAYS}->{$_} }, "") } grep({ $_ ne "description" } (@arrays, @prepared_arrays)));
 
1697             $sameitem = ($ref->{partsgroup}) ? $ref->{partsgroup} : "--";
 
1698             push(@{ $form->{TEMPLATE_ARRAYS}->{entry_type}  }, 'assembly-item-partsgroup');
 
1699             push(@{ $form->{TEMPLATE_ARRAYS}->{description} }, $sameitem);
 
1702           push(@{ $form->{TEMPLATE_ARRAYS}->{entry_type}  }, 'assembly-item');
 
1703           push(@{ $form->{TEMPLATE_ARRAYS}->{description} }, $form->format_amount($myconfig, $ref->{qty} * $form->{"qty_$i"}) . qq|, $ref->{partnumber}, $ref->{description}|);
 
1704           map({ push(@{ $form->{TEMPLATE_ARRAYS}->{$_} }, "") } grep({ $_ ne "description" } (@arrays, @prepared_arrays)));
 
1709       CVar->get_non_editable_ic_cvars(form               => $form,
 
1712                                       sub_module         => 'orderitems',
 
1713                                       may_converted_from => ['orderitems', 'invoice']);
 
1715       push @{ $form->{TEMPLATE_ARRAYS}->{"ic_cvar_$_->{name}"} },
 
1716         CVar->format_to_template(CVar->parse($form->{"ic_cvar_$_->{name}_$i"}, $_), $_)
 
1717           for @{ $ic_cvar_configs };
 
1719       push @{ $form->{TEMPLATE_ARRAYS}->{"project_cvar_" . $_->config->name} }, $_->value_as_text for @{ $project->cvars_by_config };
 
1723   $form->{totalweight}       = $form->format_amount($myconfig, $totalweight, 3);
 
1724   $form->{totalweight_nofmt} = $totalweight;
 
1725   my $defaults = AM->get_defaults();
 
1726   $form->{weightunit}        = $defaults->{weightunit};
 
1729   foreach $item (sort keys %taxaccounts) {
 
1730     $tax += $taxamount = $form->round_amount($taxaccounts{$item}, 2);
 
1732     push(@{ $form->{TEMPLATE_ARRAYS}->{taxbase} },        $form->format_amount($myconfig, $taxbase{$item}, 2));
 
1733     push(@{ $form->{TEMPLATE_ARRAYS}->{taxbase_nofmt} },  $taxbase{$item});
 
1734     push(@{ $form->{TEMPLATE_ARRAYS}->{tax} },            $form->format_amount($myconfig, $taxamount,      2));
 
1735     push(@{ $form->{TEMPLATE_ARRAYS}->{tax_nofmt} },      $taxamount);
 
1736     push(@{ $form->{TEMPLATE_ARRAYS}->{taxrate} },        $form->format_amount($myconfig, $form->{"${item}_rate"} * 100));
 
1737     push(@{ $form->{TEMPLATE_ARRAYS}->{taxrate_nofmt} },  $form->{"${item}_rate"} * 100);
 
1738     push(@{ $form->{TEMPLATE_ARRAYS}->{taxnumber} },      $form->{"${item}_taxnumber"});
 
1739     push(@{ $form->{TEMPLATE_ARRAYS}->{tax_id} },         $form->{"${item}_tax_id"});
 
1741     if ( $form->{"${item}_tax_id"} ) {
 
1742       my $tax_obj = SL::DB::Manager::Tax->find_by(id => $form->{"${item}_tax_id"}) or die "Can't find tax with id " . $form->{"${item}_tax_id"};
 
1743       my $description = $tax_obj ? $tax_obj->translated_attribute('taxdescription',  $form->{language_id}, 0) : '';
 
1744       push(@{ $form->{TEMPLATE_ARRAYS}->{taxdescription} }, $description . q{ } . 100 * $form->{"${item}_rate"} . q{%});
 
1748   $form->{nodiscount_subtotal} = $form->format_amount($myconfig, $form->{nodiscount_total}, 2);
 
1749   $form->{discount_total}      = $form->format_amount($myconfig, $form->{discount_total}, 2);
 
1750   $form->{nodiscount}          = $form->format_amount($myconfig, $nodiscount, 2);
 
1751   $form->{yesdiscount}         = $form->format_amount($myconfig, $form->{nodiscount_total} - $nodiscount, 2);
 
1753   if($form->{taxincluded}) {
 
1754     $form->{subtotal}       = $form->format_amount($myconfig, $form->{ordtotal} - $tax, 2);
 
1755     $form->{subtotal_nofmt} = $form->{ordtotal} - $tax;
 
1757     $form->{subtotal}       = $form->format_amount($myconfig, $form->{ordtotal}, 2);
 
1758     $form->{subtotal_nofmt} = $form->{ordtotal};
 
1761   my $grossamount = ($form->{taxincluded}) ? $form->{ordtotal} : $form->{ordtotal} + $tax;
 
1762   $form->{ordtotal} = $form->round_amount( $grossamount, 2, 1);
 
1763   $form->{rounding} = $form->round_amount(
 
1764     $form->{ordtotal} - $form->round_amount($grossamount, 2),
 
1769   $form->{rounding} = $form->format_amount($myconfig, $form->{rounding}, 2);
 
1770   $form->{quototal} = $form->{ordtotal} = $form->format_amount($myconfig, $form->{ordtotal}, 2);
 
1772   $form->set_payment_options($myconfig, $form->{$form->{type} =~ /_quotation/ ? 'quodate' : 'orddate'}, $form->{type});
 
1774   $form->{username} = $myconfig->{name};
 
1776   $form->{department}    = SL::DB::Manager::Department->find_by(id => $form->{department_id})->description if $form->{department_id};
 
1777   $form->{delivery_term} = SL::DB::Manager::DeliveryTerm->find_by(id => $form->{delivery_term_id} || undef);
 
1778   $form->{delivery_term}->description_long($form->{delivery_term}->translated_attribute('description_long', $form->{language_id})) if $form->{delivery_term} && $form->{language_id};
 
1780   $form->{order} = SL::DB::Manager::Order->find_by(id => $form->{id}) if $form->{id};
 
1781   $form->{$_} = $form->format_amount($myconfig, $form->{$_}, 2) for @separate_totals;
 
1783   $main::lxdebug->leave_sub();
 
1792 OE.pm - Order entry module
 
1796 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>.
 
1802 =item retrieve_simple PARAMS
 
1804 simple OE retrieval by id. does not look up customer, vendor, units or any other stuff. only oe and orderitems.
 
1806   my $order = retrieve_simple(id => 2);