Merge branch 'b-3.6.1' of ../kivitendo-erp_20220811
[kivitendo-erp.git] / SL / OE.pm
index 3bb6458..4ce1b10 100644 (file)
--- a/SL/OE.pm
+++ b/SL/OE.pm
@@ -25,7 +25,8 @@
 # GNU General Public License for more details.
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, write to the Free Software
-# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1335, USA.
 #======================================================================
 #
 # Order entry module
 package OE;
 
 use List::Util qw(max first);
-use YAML;
 
 use SL::AM;
 use SL::Common;
 use SL::CVar;
 use SL::DB::Order;
 use SL::DB::PeriodicInvoicesConfig;
+use SL::DB::Project;
+use SL::DB::ProjectType;
+use SL::DB::RequirementSpecOrder;
 use SL::DB::Status;
 use SL::DB::Tax;
 use SL::DBUtils;
 use SL::HTML::Restrict;
 use SL::IC;
 use SL::TransNumber;
+use SL::Util qw(trim);
+use SL::DB;
+use SL::YAML;
+use Text::ParseWords;
 
 use strict;
 
@@ -94,6 +101,12 @@ sub transactions {
         FROM record_links rl1
         LEFT JOIN record_links rl2 ON (rl1.to_table = rl2.from_table AND rl1.to_id = rl2.from_id)
         WHERE rl1.from_table = 'oe' AND rl2.to_table = 'ar'
+        UNION
+        SELECT rl1.from_id, rl3.to_id
+        FROM record_links rl1
+        JOIN record_links rl2 ON (rl1.to_table = rl2.from_table AND rl1.to_id = rl2.from_id)
+        JOIN record_links rl3 ON (rl2.to_table = rl3.from_table AND rl2.to_id = rl3.from_id)
+        WHERE rl1.from_table = 'oe' AND rl2.to_table = 'ar' AND rl3.to_table = 'ar'
       ) rl
       LEFT JOIN ar ON ar.id = rl.to_id
 
@@ -103,20 +116,31 @@ sub transactions {
     }
   }
 
+  my ($phone_notes_columns, $phone_notes_join);
+  $form->{phone_notes} = trim($form->{phone_notes});
+  if ($form->{phone_notes}) {
+    $phone_notes_columns = qq| , phone_notes.subject AS phone_notes_subject, phone_notes.body AS phone_notes_body |;
+    $phone_notes_join    = qq| JOIN notes phone_notes ON (o.id = phone_notes.trans_id AND phone_notes.trans_module LIKE 'oe') |;
+  }
+
   $query =
     qq|SELECT o.id, o.ordnumber, o.transdate, o.reqdate, | .
     qq|  o.amount, ct.${vc}number, ct.name, o.netamount, o.${vc}_id, o.globalproject_id, | .
     qq|  o.closed, o.delivered, o.quonumber, o.cusordnumber, o.shippingpoint, o.shipvia, | .
     qq|  o.transaction_description, | .
     qq|  o.marge_total, o.marge_percent, | .
+    qq|  o.exchangerate, | .
     qq|  o.itime::DATE AS insertdate, | .
-    qq|  ex.$rate AS exchangerate, | .
+    qq|  o.intnotes, | .
+    qq|  department.description as department, | .
+    qq|  ex.$rate AS daily_exchangerate, | .
     qq|  pt.description AS payment_terms, | .
     qq|  pr.projectnumber AS globalprojectnumber, | .
     qq|  e.name AS employee, s.name AS salesman, | .
     qq|  ct.${vc}number AS vcnumber, ct.country, ct.ustid, ct.business_id,  | .
     qq|  tz.description AS taxzone | .
     $periodic_invoices_columns .
+    $phone_notes_columns .
     qq|  , o.order_probability, o.expected_billing_date, (o.netamount * o.order_probability / 100) AS expected_netamount | .
     qq|FROM oe o | .
     qq|JOIN $vc ct ON (o.${vc}_id = ct.id) | .
@@ -128,15 +152,15 @@ sub transactions {
     qq|LEFT JOIN project pr ON (o.globalproject_id = pr.id) | .
     qq|LEFT JOIN payment_terms pt ON (pt.id = o.payment_id)| .
     qq|LEFT JOIN tax_zones tz ON (o.taxzone_id = tz.id) | .
+    qq|LEFT JOIN department   ON (o.department_id = department.id) | .
     qq|$periodic_invoices_joins | .
+    $phone_notes_join .
     qq|WHERE (o.quotation = ?) |;
   push(@values, $quotation);
 
-  my ($null, $split_department_id) = split /--/, $form->{department};
-  my $department_id = $form->{department_id} || $split_department_id;
-  if ($department_id) {
+  if ($form->{department_id}) {
     $query .= qq| AND o.department_id = ?|;
-    push(@values, $department_id);
+    push(@values, $form->{department_id});
   }
 
   if ($form->{"project_id"}) {
@@ -155,7 +179,7 @@ sub transactions {
         WHERE proi.projectnumber ILIKE ? AND oi.trans_id = o.id
       ))
 SQL
-    push @values, "%" . $form->{"projectnumber"} . "%", "%" . $form->{"projectnumber"} . "%" ;
+    push @values, like($form->{"projectnumber"}), like($form->{"projectnumber"});
   }
 
   if ($form->{"business_id"}) {
@@ -169,15 +193,16 @@ SQL
 
   } elsif ($form->{$vc}) {
     $query .= " AND ct.name ILIKE ?";
-    push(@values, '%' . $form->{$vc} . '%');
+    push(@values, like($form->{$vc}));
   }
 
   if ($form->{"cp_name"}) {
     $query .= " AND (cp.cp_name ILIKE ? OR cp.cp_givenname ILIKE ?)";
-    push(@values, ('%' . $form->{"cp_name"} . '%')x2);
+    push(@values, (like($form->{"cp_name"}))x2);
   }
 
-  if (!$main::auth->assert('sales_all_edit', 1)) {
+  if ( !(    ($vc eq 'customer' && ($main::auth->assert('sales_all_edit',    1) || $main::auth->assert('sales_order_view',    1)))
+          || ($vc eq 'vendor'   && ($main::auth->assert('purchase_all_edit', 1) || $main::auth->assert('purchase_order_view', 1))) ) ) {
     $query .= " AND o.employee_id = (select id from employee where login= ?)";
     push @values, $::myconfig{login};
   }
@@ -205,12 +230,12 @@ SQL
 
   if ($form->{$ordnumber}) {
     $query .= qq| AND o.$ordnumber ILIKE ?|;
-    push(@values, '%' . $form->{$ordnumber} . '%');
+    push(@values, like($form->{$ordnumber}));
   }
 
   if ($form->{cusordnumber}) {
     $query .= qq| AND o.cusordnumber ILIKE ?|;
-    push(@values, '%' . $form->{cusordnumber} . '%');
+    push(@values, like($form->{cusordnumber}));
   }
 
   if($form->{transdatefrom}) {
@@ -245,7 +270,7 @@ SQL
 
   if ($form->{shippingpoint}) {
     $query .= qq| AND o.shippingpoint ILIKE ?|;
-    push(@values, '%' . $form->{shippingpoint} . '%');
+    push(@values, like($form->{shippingpoint}));
   }
 
   if ($form->{taxzone_id} ne '') { # taxzone_id could be 0
@@ -255,7 +280,7 @@ SQL
 
   if ($form->{transaction_description}) {
     $query .= qq| AND o.transaction_description ILIKE ?|;
-    push(@values, '%' . $form->{transaction_description} . '%');
+    push(@values, like($form->{transaction_description}));
   }
 
   if ($form->{periodic_invoices_active} ne $form->{periodic_invoices_inactive}) {
@@ -270,7 +295,7 @@ SQL
   if (($form->{order_probability_value} || '') ne '') {
     my $op  = $form->{order_probability_value} eq 'le' ? '<=' : '>=';
     $query .= qq| AND (o.order_probability ${op} ?)|;
-    push @values, $form->{order_probability_value};
+    push @values, trim($form->{order_probability_value});
   }
 
   if ($form->{expected_billing_date_from}) {
@@ -283,6 +308,98 @@ SQL
     push @values, conv_date($form->{expected_billing_date_to});
   }
 
+  if ($form->{intnotes}) {
+    $query .= qq| AND o.intnotes ILIKE ?|;
+    push(@values, like($form->{intnotes}));
+  }
+
+  if ($form->{phone_notes}) {
+    $query .= qq| AND (phone_notes.subject ILIKE ? OR phone_notes.body ILIKE ?)|;
+    push(@values, like($form->{phone_notes}), like($form->{phone_notes}));
+  }
+
+  $form->{fulltext} = trim($form->{fulltext});
+  if ($form->{fulltext}) {
+    my @fulltext_fields = qw(o.notes
+                             o.intnotes
+                             o.shippingpoint
+                             o.shipvia
+                             o.transaction_description
+                             o.quonumber
+                             o.ordnumber
+                             o.cusordnumber);
+    $query .= ' AND (';
+    $query .= join ' ILIKE ? OR ', @fulltext_fields;
+    $query .= ' ILIKE ?';
+
+    $query .= <<SQL;
+      OR EXISTS (
+        SELECT files.id FROM files LEFT JOIN file_full_texts ON (file_full_texts.file_id = files.id)
+          WHERE files.object_id = o.id AND files.object_type = 'sales_order'
+            AND file_full_texts.full_text ILIKE ?)
+SQL
+
+    $query .= <<SQL;
+      OR EXISTS (
+        SELECT notes.id FROM notes
+          WHERE notes.trans_id = o.id AND notes.trans_module LIKE 'oe'
+            AND (notes.subject ILIKE ? OR notes.body ILIKE ?))
+SQL
+
+    $query .= <<SQL;
+      OR EXISTS (
+        SELECT follow_up_links.id FROM follow_up_links
+          WHERE follow_up_links.trans_id = o.id AND trans_type = 'sales_order'
+            AND EXISTS (
+              SELECT notes.id FROM notes
+                WHERE trans_module LIKE 'fu' AND trans_id = follow_up_links.follow_up_id
+                  AND (notes.subject ILIKE ? OR notes.body ILIKE ?)))
+SQL
+
+    $query .= ')';
+
+    push(@values, like($form->{fulltext})) for 1 .. (scalar @fulltext_fields) + 5;
+  }
+
+  if ($form->{parts_partnumber}) {
+    $query .= <<SQL;
+      AND EXISTS (
+        SELECT orderitems.trans_id
+        FROM orderitems
+        LEFT JOIN parts ON (orderitems.parts_id = parts.id)
+        WHERE (orderitems.trans_id = o.id)
+          AND (parts.partnumber ILIKE ?)
+        LIMIT 1
+      )
+SQL
+    push @values, like($form->{parts_partnumber});
+  }
+
+  if ($form->{parts_description}) {
+    $query .= <<SQL;
+      AND EXISTS (
+        SELECT orderitems.trans_id
+        FROM orderitems
+        WHERE (orderitems.trans_id = o.id)
+          AND (orderitems.description ILIKE ?)
+        LIMIT 1
+      )
+SQL
+    push @values, like($form->{parts_description});
+  }
+
+  if ($form->{all}) {
+    my @tokens = parse_line('\s+', 0, $form->{all});
+    # ordnumber quonumber customer.name vendor.name transaction_description
+    $query .= qq| AND (
+      o.ordnumber ILIKE ? OR
+      o.quonumber ILIKE ? OR
+      ct.name     ILIKE ? OR
+      o.transaction_description ILIKE ?
+    )| for @tokens;
+    push @values, (like($_))x4 for @tokens;
+  }
+
   my ($cvar_where, @cvar_values) = CVar->build_filter_query('module'         => 'CT',
                                                             'trans_id_field' => 'ct.id',
                                                             'filter'         => $form,
@@ -310,6 +427,8 @@ SQL
     "insertdate"              => "o.itime",
     "taxzone"                 => "tz.description",
     "payment_terms"           => "pt.description",
+    "department"              => "department.description",
+    "intnotes"                => "o.intnotes",
   );
   if ($form->{sort} && grep($form->{sort}, keys(%allowed_sort_columns))) {
     $sortorder = $allowed_sort_columns{$form->{sort}} . " ${sortdir}"  . ", o.itime ${sortdir}";
@@ -325,9 +444,15 @@ SQL
   while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
     $ref->{billed_amount}    = $billed_amount{$ref->{id}};
     $ref->{billed_netamount} = $billed_netamount{$ref->{id}};
-    $ref->{remaining_amount} = $ref->{amount} - $ref->{billed_amount};
-    $ref->{remaining_netamount} = $ref->{netamount} - $ref->{billed_netamount};
-    $ref->{exchangerate} = 1 unless $ref->{exchangerate};
+    if ($ref->{billed_amount} < 0) { # case: credit note(s) higher than invoices
+      $ref->{remaining_amount} = $ref->{amount} + $ref->{billed_amount};
+      $ref->{remaining_netamount} = $ref->{netamount} + $ref->{billed_netamount};
+    } else {
+      $ref->{remaining_amount} = $ref->{amount} - $ref->{billed_amount};
+      $ref->{remaining_netamount} = $ref->{netamount} - $ref->{billed_netamount};
+    }
+    $ref->{exchangerate} ||= $ref->{daily_exchangerate};
+    $ref->{exchangerate} ||= 1;
     push @{ $form->{OE} }, $ref if $ref->{id} != $id{ $ref->{id} };
     $id{ $ref->{id} } = $ref->{id};
   }
@@ -376,12 +501,22 @@ sub transactions_for_todo_list {
 }
 
 sub save {
+  my ($self, $myconfig, $form) = @_;
+  $main::lxdebug->enter_sub();
+
+  my $rc = SL::DB->client->with_transaction(\&_save, $self, $myconfig, $form);
+
+  $::lxdebug->leave_sub;
+
+  return $rc;
+}
+
+sub _save {
   $main::lxdebug->enter_sub();
 
   my ($self, $myconfig, $form) = @_;
 
-  # connect to database, turn off autocommit
-  my $dbh = $form->get_standard_dbh;
+  my $dbh = SL::DB->client->dbh;
   my $restricter = SL::HTML::Restrict->create;
 
   my ($query, @values, $sth, $null);
@@ -402,9 +537,15 @@ sub save {
 
   my $number_field         = $form->{type} =~ m{order} ? 'ordnumber' : 'quonumber';
   my $trans_number         = SL::TransNumber->new(type => $form->{type}, dbh => $dbh, number => $form->{$number_field}, id => $form->{id});
-  $form->{$number_field} ||= $trans_number->create_unique;
+  $form->{$number_field} ||= $trans_number->create_unique; # set $form->{ordnumber} or $form->{quonumber}
+  my $is_new               = !$form->{id};
 
   if ($form->{id}) {
+    $query = qq|DELETE FROM custom_variables
+                WHERE (config_id IN (SELECT id        FROM custom_variable_configs WHERE (module = 'ShipTo')))
+                  AND (trans_id  IN (SELECT shipto_id FROM shipto                  WHERE (module = 'OE') AND (trans_id = ?)))|;
+    do_query($form, $dbh, $query, $form->{id});
+
     $query = qq|DELETE FROM shipto | .
              qq|WHERE trans_id = ? AND module = 'OE'|;
     do_query($form, $dbh, $query, $form->{id});
@@ -533,6 +674,26 @@ sub save {
       $pricegroup_id *= 1;
       $pricegroup_id  = undef if !$pricegroup_id;
 
+      # force new project, if not set yet
+      if ($::instance_conf->get_order_always_project && !$form->{"globalproject_id"} && ($form->{type} eq 'sales_order')) {
+        require SL::DB::Customer;
+        my $customer = SL::DB::Manager::Customer->find_by(id => $form->{customer_id});
+        die "Can't find customer" unless $customer;
+        die $main::locale->text("Error while creating project with project number of new order number, project number #1 already exists!", $form->{ordnumber})
+          if SL::DB::Manager::Project->find_by(projectnumber => $form->{ordnumber});
+
+        my $new_project = SL::DB::Project->new(
+          projectnumber     => $form->{ordnumber},
+          description       => $customer->name,
+          customer_id       => $customer->id,
+          active            => 1,
+          project_type_id   => $::instance_conf->get_project_type_id,
+          project_status_id => $::instance_conf->get_project_status_id,
+        );
+        $new_project->save;
+        $form->{"globalproject_id"} = $new_project->id;
+      }
+
       CVar->get_non_editable_ic_cvars(form               => $form,
                                       dbh                => $dbh,
                                       row                => $i,
@@ -593,8 +754,12 @@ SQL
                                   dbh          => $dbh);
 
       # link previous items with orderitems
-      foreach (qw(orderitems invoice)) {
-        if (!$form->{saveasnew} && !$form->{useasnew} && $form->{"converted_from_${_}_id_$i"}) {
+      # assume we have a new workflow if we link from invoice or order to quotation
+      # unluckily orderitems are used for quotation and orders - therefore one more
+      # check to be sure NOT to link from order to quotation
+      foreach (qw(orderitems)) {
+        if (!$form->{saveasnew} && !$form->{useasnew} && $form->{"converted_from_${_}_id_$i"}
+              && $form->{type} !~ 'quotation') {
           RecordLinks->create_links('dbh'        => $dbh,
                                     'mode'       => 'ids',
                                     'from_table' => $_,
@@ -625,7 +790,7 @@ SQL
   my $tax = 0;
   map { $tax += $form->round_amount($taxaccounts{$_}, 2) } keys %taxaccounts;
 
-  $amount = $form->round_amount($netamount + $tax, 2);
+  $amount = $form->round_amount($netamount + $tax, 2, 1);
   $netamount = $form->round_amount($netamount, 2);
 
   if ($form->{currency} eq $form->{defaultcurrency}) {
@@ -634,20 +799,21 @@ SQL
     $exchangerate = $form->check_exchangerate($myconfig, $form->{currency}, $form->{transdate}, ($form->{vc} eq 'customer') ? 'buy' : 'sell');
   }
 
-  $form->{exchangerate} = $exchangerate || $form->parse_amount($myconfig, $form->{exchangerate});
+  # from inputfield (exchangerate) or hidden (forex)
+  my $exchangerate_from_form = $form->{forex} || $form->parse_amount($myconfig, $form->{exchangerate});
 
-  my $quotation = $form->{type} =~ /_order$/ ? 'f' : 't';
+  $form->{exchangerate} = $exchangerate || $exchangerate_from_form;
 
-  ($null, $form->{department_id}) = split(/--/, $form->{department}) if $form->{department};
+  my $quotation = $form->{type} =~ /_order$/ ? 'f' : 't';
 
   # save OE record
   $query =
     qq|UPDATE oe SET
          ordnumber = ?, quonumber = ?, cusordnumber = ?, transdate = ?, vendor_id = ?,
-         customer_id = ?, amount = ?, netamount = ?, reqdate = ?, taxincluded = ?,
+         customer_id = ?, amount = ?, netamount = ?, reqdate = ?, tax_point = ?, taxincluded = ?,
          shippingpoint = ?, shipvia = ?, notes = ?, intnotes = ?, currency_id = (SELECT id FROM currencies WHERE name=?), closed = ?,
          delivered = ?, proforma = ?, quotation = ?, department_id = ?, language_id = ?,
-         taxzone_id = ?, shipto_id = ?, payment_id = ?, delivery_vendor_id = ?, delivery_customer_id = ?,delivery_term_id = ?,
+         taxzone_id = ?, shipto_id = ?, billing_address_id = ?, payment_id = ?, delivery_vendor_id = ?, delivery_customer_id = ?,delivery_term_id = ?,
          globalproject_id = ?, employee_id = ?, salesman_id = ?, cp_id = ?, transaction_description = ?, marge_total = ?, marge_percent = ?
          , order_probability = ?, expected_billing_date = ?
        WHERE id = ?|;
@@ -655,14 +821,14 @@ SQL
   @values = ($form->{ordnumber} || '', $form->{quonumber},
              $form->{cusordnumber}, conv_date($form->{transdate}),
              conv_i($form->{vendor_id}), conv_i($form->{customer_id}),
-             $amount, $netamount, conv_date($reqdate),
+             $amount, $netamount, conv_date($reqdate), conv_date($form->{tax_point}),
              $form->{taxincluded} ? 't' : 'f', $form->{shippingpoint},
              $form->{shipvia}, $restricter->process($form->{notes}), $form->{intnotes},
              $form->{currency}, $form->{closed} ? 't' : 'f',
              $form->{delivered} ? "t" : "f", $form->{proforma} ? 't' : 'f',
              $quotation, conv_i($form->{department_id}),
              conv_i($form->{language_id}), conv_i($form->{taxzone_id}),
-             conv_i($form->{shipto_id}), conv_i($form->{payment_id}),
+             conv_i($form->{shipto_id}), conv_i($form->{billing_address_id}), conv_i($form->{payment_id}),
              conv_i($form->{delivery_vendor_id}),
              conv_i($form->{delivery_customer_id}),
              conv_i($form->{delivery_term_id}),
@@ -674,6 +840,8 @@ SQL
              conv_i($form->{id}));
   do_query($form, $dbh, $query, @values);
 
+  $form->new_lastmtime('oe');
+
   $form->{ordtotal} = $amount;
 
   $form->{name} = $form->{ $form->{vc} };
@@ -688,28 +856,21 @@ SQL
   $form->save_status($dbh);
 
   # Link this record to the records it was created from.
-  # check every record type we may link. i am not happy with converting the string to array back
-  # should be a array from the start (OE.pm -> retrieve).
-  #  and that i need the local array ref for close_quotation_rfqs. better ideas welcome
   $form->{convert_from_oe_ids} =~ s/^\s+//;
   $form->{convert_from_oe_ids} =~ s/\s+$//;
   my @convert_from_oe_ids      =  split m/\s+/, $form->{convert_from_oe_ids};
   delete $form->{convert_from_oe_ids};
-  @{ $form->{convert_from_oe_ids} }      =  @convert_from_oe_ids;
-  foreach (qw(ar oe)) {
-    if (!$form->{useasnew} && $form->{"convert_from_${_}_ids"}) {
-      RecordLinks->create_links('dbh'        => $dbh,
-                                'mode'       => 'ids',
-                                'from_table' => $_,
-                                'from_ids'   => $form->{"convert_from_${_}_ids"},
-                                'to_table'   => 'oe',
-                                'to_id'      => $form->{id},
-        );
-      delete $form->{"convert_from_${_}_ids"};
-    }
+  if (!$form->{useasnew} && scalar @convert_from_oe_ids) {
+    RecordLinks->create_links('dbh'        => $dbh,
+                              'mode'       => 'ids',
+                              'from_table' => 'oe',
+                              'from_ids'   => \@convert_from_oe_ids,
+                              'to_table'   => 'oe',
+                              'to_id'      => $form->{id},
+      );
     $self->_close_quotations_rfqs('dbh'     => $dbh,
                                   'from_id' => \@convert_from_oe_ids,
-                                  'to_id'   => $form->{id}) if $_ eq 'oe';
+                                  'to_id'   => $form->{id});
   }
 
   if (($form->{currency} ne $form->{defaultcurrency}) && !$exchangerate) {
@@ -726,16 +887,80 @@ SQL
 
   Common::webdav_folder($form);
 
-  my $rc = $dbh->commit;
-
   $self->save_periodic_invoices_config(dbh         => $dbh,
                                        oe_id       => $form->{id},
                                        config_yaml => $form->{periodic_invoices_config})
     if ($form->{type} eq 'sales_order');
 
+  $self->_link_created_sales_order_to_requirement_specs_for_sales_quotations(
+    type               => $form->{type},
+    converted_from_ids => \@convert_from_oe_ids,
+    sales_order_id     => $form->{id},
+    is_new             => $is_new,
+  );
+
+  $self->_set_project_in_linked_requirement_spec(
+    type           => $form->{type},
+    project_id     => $form->{globalproject_id},
+    sales_order_id => $form->{id},
+  );
+
   $main::lxdebug->leave_sub();
 
-  return $rc;
+  return 1;
+}
+
+sub _link_created_sales_order_to_requirement_specs_for_sales_quotations {
+  my ($self, %params) = @_;
+
+  # If this is a sales order created from a sales quotation and if
+  # that sales quotation was created from a requirement spec document
+  # then link the newly created sales order to the requirement spec
+  # document, too.
+
+  return if !$params{is_new};
+  return if  $params{type} ne 'sales_order';
+  return if !@{ $params{converted_from_ids} };
+
+  my $oe_objects       = SL::DB::Manager::Order->get_all(where => [ id => $params{converted_from_ids} ]);
+  my @sales_quotations = grep { $_->is_type('sales_quotation') } @{ $oe_objects };
+
+  return if !@sales_quotations;
+
+  my $rs_orders = SL::DB::Manager::RequirementSpecOrder->get_all(where => [ order_id => [ map { $_->id } @sales_quotations ] ]);
+
+  return if !@{ $rs_orders };
+
+  $rs_orders->[0]->db->with_transaction(sub {
+    foreach my $rs_order (@{ $rs_orders }) {
+      SL::DB::RequirementSpecOrder->new(
+        order_id            => $params{sales_order_id},
+        requirement_spec_id => $rs_order->requirement_spec_id,
+        version_id          => $rs_order->version_id,
+      )->save;
+    }
+
+    1;
+  });
+}
+
+sub _set_project_in_linked_requirement_spec {
+  my ($self, %params) = @_;
+
+  return if  $params{type} ne 'sales_order';
+  return if !$params{project_id} || !$params{sales_order_id};
+
+  my $query = <<SQL;
+    UPDATE requirement_specs
+    SET project_id = ?
+    WHERE id IN (
+      SELECT so.requirement_spec_id
+      FROM requirement_spec_orders so
+      WHERE so.order_id = ?
+    )
+SQL
+
+  do_query($::form, $::form->get_standard_dbh, $query, $params{project_id}, $params{sales_order_id});
 }
 
 sub save_periodic_invoices_config {
@@ -743,7 +968,7 @@ sub save_periodic_invoices_config {
 
   return if !$params{oe_id};
 
-  my $config = $params{config_yaml} ? YAML::Load($params{config_yaml}) : undef;
+  my $config = $params{config_yaml} ? SL::YAML::Load($params{config_yaml}) : undef;
   return if 'HASH' ne ref $config;
 
   my $obj  = SL::DB::Manager::PeriodicInvoicesConfig->find_by(oe_id => $params{oe_id})
@@ -762,8 +987,8 @@ sub load_periodic_invoice_config {
 
     if ($config_obj) {
       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
-                                                      print printer_id copies direct_debit) };
-      $form->{periodic_invoices_config} = YAML::Dump($config);
+                                                      print printer_id copies direct_debit send_email email_recipient_contact_id email_recipient_address email_sender email_subject email_body) };
+      $form->{periodic_invoices_config} = SL::YAML::Dump($config);
     }
   }
 }
@@ -779,37 +1004,38 @@ sub _close_quotations_rfqs {
   my $myconfig = \%main::myconfig;
   my $form     = $main::form;
 
-  my $dbh      = $params{dbh} || $form->get_standard_dbh($myconfig);
+  my $dbh      = $params{dbh} || SL::DB->client->dbh;
 
-  my $query    = qq|SELECT quotation FROM oe WHERE id = ?|;
-  my $sth      = prepare_query($form, $dbh, $query);
+  SL::DB->client->with_transaction(sub {
 
-  do_statement($form, $sth, $query, conv_i($params{to_id}));
+    my $query    = qq|SELECT quotation FROM oe WHERE id = ?|;
+    my $sth      = prepare_query($form, $dbh, $query);
 
-  my ($quotation) = $sth->fetchrow_array();
+    do_statement($form, $sth, $query, conv_i($params{to_id}));
 
-  if ($quotation) {
-    $main::lxdebug->leave_sub();
-    return;
-  }
+    my ($quotation) = $sth->fetchrow_array();
 
-  my @close_ids;
+    if ($quotation) {
+      return 1;
+    }
 
-  foreach my $from_id (@{ $params{from_id} }) {
-    $from_id = conv_i($from_id);
-    do_statement($form, $sth, $query, $from_id);
-    ($quotation) = $sth->fetchrow_array();
-    push @close_ids, $from_id if ($quotation);
-  }
+    my @close_ids;
 
-  $sth->finish();
+    foreach my $from_id (@{ $params{from_id} }) {
+      $from_id = conv_i($from_id);
+      do_statement($form, $sth, $query, $from_id);
+      ($quotation) = $sth->fetchrow_array();
+      push @close_ids, $from_id if ($quotation);
+    }
 
-  if (scalar @close_ids) {
-    $query = qq|UPDATE oe SET closed = TRUE WHERE id IN (| . join(', ', ('?') x scalar @close_ids) . qq|)|;
-    do_query($form, $dbh, $query, @close_ids);
+    $sth->finish();
 
-    $dbh->commit() unless ($params{dbh});
-  }
+    if (scalar @close_ids) {
+      $query = qq|UPDATE oe SET closed = TRUE WHERE id IN (| . join(', ', ('?') x scalar @close_ids) . qq|)|;
+      do_query($form, $dbh, $query, @close_ids);
+    }
+    1;
+  }) or do { die SL::DB->client->error };
 
   $main::lxdebug->leave_sub();
 }
@@ -828,7 +1054,7 @@ sub delete {
     unlink map { "$spool/$_" } @spoolfiles if $spool;
 
     1;
-  });
+  }) or do { die SL::DB->client->error };
 
   $main::lxdebug->leave_sub();
 
@@ -836,12 +1062,20 @@ sub delete {
 }
 
 sub retrieve {
+  my ($self, $myconfig, $form) = @_;
   $main::lxdebug->enter_sub();
 
+  my $rc = SL::DB->client->with_transaction(\&_retrieve, $self, $myconfig, $form);
+
+  $::lxdebug->leave_sub;
+  return $rc;
+}
+
+sub _retrieve {
   my ($self, $myconfig, $form) = @_;
 
   # connect to database
-  my $dbh = $form->get_standard_dbh;
+  my $dbh = SL::DB->client->dbh;
 
   my ($query, $query_add, @values, @ids, $sth);
 
@@ -876,8 +1110,14 @@ sub retrieve {
   $form->{useasnew} = 1 if $is_collective_order == 1;
 
   if (!$form->{id}) {
-    my $extra_days     = $form->{type} eq 'sales_quotation' ? $::instance_conf->get_reqdate_interval : 1;
-    $form->{reqdate}   = DateTime->today_local->next_workday(extra_days => $extra_days)->to_kivitendo;
+    my $extra_days = $form->{type} eq 'sales_quotation' ? $::instance_conf->get_reqdate_interval       :
+                     $form->{type} eq 'sales_order'     ? $::instance_conf->get_delivery_date_interval : 1;
+    if (   ($form->{type} eq 'sales_order'     &&  !$::instance_conf->get_deliverydate_on)
+        || ($form->{type} eq 'sales_quotation' &&  !$::instance_conf->get_reqdate_on)) {
+      $form->{reqdate}   = '';
+    } else {
+      $form->{reqdate}   = DateTime->today_local->next_workday(extra_days => $extra_days)->to_kivitendo;
+    }
     $form->{transdate} = DateTime->today_local->to_kivitendo;
   }
 
@@ -886,7 +1126,9 @@ sub retrieve {
                      (SELECT c.accno FROM chart c WHERE d.income_accno_id    = c.id) AS income_accno,
                      (SELECT c.accno FROM chart c WHERE d.expense_accno_id   = c.id) AS expense_accno,
                      (SELECT c.accno FROM chart c WHERE d.fxgain_accno_id    = c.id) AS fxgain_accno,
-                     (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id    = c.id) AS fxloss_accno
+                     (SELECT c.accno FROM chart c WHERE d.fxloss_accno_id    = c.id) AS fxloss_accno,
+                     (SELECT c.accno FROM chart c WHERE d.rndgain_accno_id   = c.id) AS rndgain_accno,
+                     (SELECT c.accno FROM chart c WHERE d.rndloss_accno_id   = c.id) AS rndloss_accno
               $query_add
               FROM defaults d|;
   my $ref = selectfirst_hashref_query($form, $dbh, $query);
@@ -913,10 +1155,10 @@ sub retrieve {
            o.taxincluded, o.shippingpoint, o.shipvia, o.notes, o.intnotes,
            (SELECT cu.name FROM currencies cu WHERE cu.id=o.currency_id) AS currency, e.name AS employee, o.employee_id, o.salesman_id,
            o.${vc}_id, cv.name AS ${vc}, o.amount AS invtotal,
-           o.closed, o.reqdate, o.quonumber, o.department_id, o.cusordnumber,
+           o.closed, o.reqdate, o.tax_point, o.quonumber, o.department_id, o.cusordnumber,
            o.mtime, o.itime,
            d.description AS department, o.payment_id, o.language_id, o.taxzone_id,
-           o.delivery_customer_id, o.delivery_vendor_id, o.proforma, o.shipto_id,
+           o.delivery_customer_id, o.delivery_vendor_id, o.proforma, o.shipto_id, o.billing_address_id,
            o.globalproject_id, o.delivered, o.transaction_description, o.delivery_term_id,
            o.itime::DATE AS insertdate, o.order_probability, o.expected_billing_date
          FROM oe o
@@ -967,10 +1209,18 @@ sub retrieve {
       $sth = prepare_execute_query($form, $dbh, $query, $form->{id});
 
       $ref = $sth->fetchrow_hashref("NAME_lc");
-      delete($ref->{id});
-      map { $form->{$_} = $ref->{$_} } keys %$ref;
+      $form->{$_} = $ref->{$_} for grep { m{^shipto(?!_id$)} } keys %$ref;
       $sth->finish;
 
+      if ($ref->{shipto_id}) {
+        my $cvars = CVar->get_custom_variables(
+          dbh      => $dbh,
+          module   => 'ShipTo',
+          trans_id => $ref->{shipto_id},
+        );
+        $form->{"shiptocvar_$_->{name}"} = $_->{value} for @{ $cvars };
+      }
+
       # get printed, emailed and queued
       $query = qq|SELECT s.printed, s.emailed, s.spoolfile, s.formname FROM status s WHERE s.trans_id = ?|;
       $sth = prepare_execute_query($form, $dbh, $query, $form->{id});
@@ -984,9 +1234,10 @@ sub retrieve {
       map { $form->{$_} =~ s/ +$//g } qw(printed emailed queued);
     }    # if !@ids
 
-    my $transdate = $form->{transdate} ? $dbh->quote($form->{transdate}) : "current_date";
+    my $transdate = $form->{tax_point} ? $dbh->quote($form->{tax_point}) : $form->{transdate} ? $dbh->quote($form->{transdate}) : "current_date";
 
     $form->{taxzone_id} = 0 unless ($form->{taxzone_id});
+    unshift @values, ($form->{taxzone_id}) x 2;
 
     # retrieve individual items
     # this query looks up all information about the items
@@ -997,8 +1248,9 @@ sub retrieve {
            c2.accno AS income_accno,    c2.new_chart_id AS income_new_chart,    date($transdate) - c2.valid_from as income_valid,
            c3.accno AS expense_accno,   c3.new_chart_id AS expense_new_chart,   date($transdate) - c3.valid_from as expense_valid,
            oe.ordnumber AS ordnumber_oe, oe.transdate AS transdate_oe, oe.cusordnumber AS cusordnumber_oe,
-           p.partnumber, p.assembly, p.listprice, o.description, o.qty,
-           o.sellprice, o.parts_id AS id, o.unit, o.discount, p.notes AS partnotes, p.inventory_accno_id AS part_inventory_accno_id,
+           p.partnumber, p.part_type, p.listprice, o.description, o.qty,
+           p.classification_id,
+           o.sellprice, o.parts_id AS id, o.unit, o.discount, p.notes AS partnotes, p.part_type,
            o.reqdate, o.project_id, o.serialnumber, o.ship, o.lastcost,
            o.ordnumber, o.transdate, o.cusordnumber, o.subtotal, o.longdescription,
            o.price_factor_id, o.price_factor, o.marge_price_factor, o.active_price_source, o.active_discount_source,
@@ -1008,8 +1260,8 @@ sub retrieve {
          JOIN parts p ON (o.parts_id = p.id)
          JOIN oe ON (o.trans_id = oe.id)
          LEFT JOIN chart c1 ON ((SELECT inventory_accno_id                   FROM buchungsgruppen WHERE id=p.buchungsgruppen_id) = c1.id)
-         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)
-         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)
+         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)
+         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)
          LEFT JOIN project pr ON (o.project_id = pr.id)
          LEFT JOIN partsgroup pg ON (p.partsgroup_id = pg.id) | .
       ($form->{id}
@@ -1030,10 +1282,10 @@ sub retrieve {
       map { $ref->{"ic_cvar_$_->{name}"} = $_->{value} } @{ $cvars };
 
       # Handle accounts.
-      if (!$ref->{"part_inventory_accno_id"}) {
+      if (!$ref->{"part_type"} eq 'part') {
         map({ delete($ref->{$_}); } qw(inventory_accno inventory_new_chart inventory_valid));
       }
-      delete($ref->{"part_inventory_accno_id"});
+      delete($ref->{"part_inventory_accno_id"});
 
       # in collective order, copy global ordnumber, transdate, cusordnumber into item scope
       #   unless already present there
@@ -1083,8 +1335,9 @@ sub retrieve {
       # get tax rates and description
       my $accno_id = ($form->{vc} eq "customer") ? $ref->{income_accno} : $ref->{expense_accno};
       $query =
-        qq|SELECT c.accno, t.taxdescription, t.rate, t.taxnumber | .
-        qq|FROM tax t LEFT JOIN chart c on (c.id = t.chart_id) | .
+        qq|SELECT c.accno, t.taxdescription, t.rate, t.id as tax_id, c.accno as taxnumber | .
+        qq|FROM tax t | .
+        qq|LEFT JOIN chart c on (c.id = t.chart_id) | .
         qq|WHERE t.id IN (SELECT tk.tax_id FROM taxkeys tk | .
         qq|               WHERE tk.chart_id = (SELECT id FROM chart WHERE accno = ?) | .
         qq|                 AND startdate <= $transdate ORDER BY startdate DESC LIMIT 1) | .
@@ -1102,6 +1355,7 @@ sub retrieve {
           $form->{"$ptr->{accno}_rate"}        = $ptr->{rate};
           $form->{"$ptr->{accno}_description"} = $ptr->{taxdescription};
           $form->{"$ptr->{accno}_taxnumber"}   = $ptr->{taxnumber};
+          $form->{"$ptr->{accno}_tax_id"}      = $ptr->{tax_id};
           $form->{taxaccounts} .= "$ptr->{accno} ";
         }
 
@@ -1128,11 +1382,7 @@ sub retrieve {
 
   $self->load_periodic_invoice_config($form);
 
-  my $rc = $dbh->commit;
-
-  $main::lxdebug->leave_sub();
-
-  return $rc;
+  return 1;
 }
 
 sub retrieve_simple {
@@ -1165,7 +1415,7 @@ sub order_details {
   my ($self, $myconfig, $form) = @_;
 
   # connect to database
-  my $dbh = $form->get_standard_dbh;
+  my $dbh = SL::DB->client->dbh;
   my $query;
   my @values = ();
   my $sth;
@@ -1189,8 +1439,7 @@ sub order_details {
 
   push(@project_ids, $form->{"globalproject_id"}) if ($form->{"globalproject_id"});
 
-  $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS',
-                   'departments'   => 'ALL_DEPARTMENTS');
+  $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS');
   my %price_factors;
 
   foreach my $pfac (@{ $form->{ALL_PRICE_FACTORS} }) {
@@ -1199,13 +1448,6 @@ sub order_details {
     $pfac->{formatted_factor}    = $form->format_amount($myconfig, $pfac->{factor});
   }
 
-  # lookup department
-  foreach my $dept (@{ $form->{ALL_DEPARTMENTS} }) {
-    next unless $dept->{id} eq $form->{department_id};
-    $form->{department} = $dept->{description};
-    last;
-  }
-
   # sort items by partsgroup
   for $i (1 .. $form->{rowcount}) {
     $partsgroup = "";
@@ -1238,6 +1480,7 @@ sub order_details {
   # so that they can be sorted in later
   my %prepared_template_arrays = IC->prepare_parts_for_printing(myconfig => $myconfig, form => $form);
   my @prepared_arrays          = keys %prepared_template_arrays;
+  my @separate_totals          = qw(non_separate_subtotal);
 
   $form->{TEMPLATE_ARRAYS} = { };
 
@@ -1249,7 +1492,7 @@ sub order_details {
        partnotes serialnumber reqdate sellprice sellprice_nofmt listprice listprice_nofmt netprice netprice_nofmt
        discount discount_nofmt p_discount discount_sub discount_sub_nofmt nodiscount_sub nodiscount_sub_nofmt
        linetotal linetotal_nofmt nodiscount_linetotal nodiscount_linetotal_nofmt tax_rate projectnumber projectdescription
-       price_factor price_factor_name partsgroup weight weight_nofmt lineweight lineweight_nofmt);
+       price_factor price_factor_name partsgroup weight weight_nofmt lineweight lineweight_nofmt optional);
 
   push @arrays, map { "ic_cvar_$_->{name}" } @{ $ic_cvar_configs };
   push @arrays, map { "project_cvar_$_->{name}" } @{ $project_cvar_configs };
@@ -1316,6 +1559,7 @@ sub order_details {
       push @{ $form->{TEMPLATE_ARRAYS}->{price_factor} },      $price_factor->{formatted_factor};
       push @{ $form->{TEMPLATE_ARRAYS}->{price_factor_name} }, $price_factor->{description};
       push @{ $form->{TEMPLATE_ARRAYS}->{partsgroup} },        $form->{"partsgroup_$i"};
+      push @{ $form->{TEMPLATE_ARRAYS}->{optional} },          $form->{"optional_$i"};
 
       my $sellprice     = $form->parse_amount($myconfig, $form->{"sellprice_$i"});
       my ($dec)         = ($sellprice =~ /\.(\d+)/);
@@ -1344,7 +1588,18 @@ sub order_details {
       push @{ $form->{TEMPLATE_ARRAYS}->{discount_nofmt} }, ($discount != 0) ? $discount * -1 : '';
       push @{ $form->{TEMPLATE_ARRAYS}->{p_discount} },     $form->{"discount_$i"};
 
-      $form->{ordtotal}         += $linetotal;
+      if ( $prepared_template_arrays{separate}[$i - 1]  ) {
+        my $pabbr = $prepared_template_arrays{separate}[$i - 1];
+        if ( ! $form->{"separate_${pabbr}_subtotal"} ) {
+            push @separate_totals , "separate_${pabbr}_subtotal";
+            $form->{"separate_${pabbr}_subtotal"} = 0;
+        }
+        $form->{"separate_${pabbr}_subtotal"} += $linetotal;
+      } else {
+        $form->{non_separate_subtotal} += $linetotal;
+      }
+
+      $form->{ordtotal}         += $linetotal unless $form->{"optional_$i"};
       $form->{nodiscount_total} += $nodiscount_linetotal;
       $form->{discount_total}   += $discount;
 
@@ -1392,14 +1647,16 @@ sub order_details {
 
       map { $taxrate += $form->{"${_}_rate"} } split(/ /, $form->{"taxaccounts_$i"});
 
-      if ($form->{taxincluded}) {
+      unless ($form->{"optional_$i"}) {
+        if ($form->{taxincluded}) {
 
-        # calculate tax
-        $taxamount = $linetotal * $taxrate / (1 + $taxrate);
-        $taxbase = $linetotal / (1 + $taxrate);
-      } else {
-        $taxamount = $linetotal * $taxrate;
-        $taxbase   = $linetotal;
+          # calculate tax
+          $taxamount = $linetotal * $taxrate / (1 + $taxrate);
+          $taxbase = $linetotal / (1 + $taxrate);
+        } else {
+          $taxamount = $linetotal * $taxrate;
+          $taxbase   = $linetotal;
+        }
       }
 
       if ($taxamount != 0) {
@@ -1412,15 +1669,15 @@ sub order_details {
       $tax_rate = $taxrate * 100;
       push(@{ $form->{TEMPLATE_ARRAYS}->{tax_rate} }, qq|$tax_rate|);
 
-      if ($form->{"assembly_$i"}) {
+      if ($form->{"part_type_$i"} eq 'assembly') {
         $sameitem = "";
 
         # get parts and push them onto the stack
         my $sortorder = "";
         if ($form->{groupitems}) {
-          $sortorder = qq|ORDER BY pg.partsgroup, a.oid|;
+          $sortorder = qq|ORDER BY pg.partsgroup, a.position|;
         } else {
-          $sortorder = qq|ORDER BY a.oid|;
+          $sortorder = qq|ORDER BY a.position|;
         }
 
         $query = qq|SELECT p.partnumber, p.description, p.unit, a.qty, | .
@@ -1479,10 +1736,13 @@ sub order_details {
     push(@{ $form->{TEMPLATE_ARRAYS}->{taxrate} },        $form->format_amount($myconfig, $form->{"${item}_rate"} * 100));
     push(@{ $form->{TEMPLATE_ARRAYS}->{taxrate_nofmt} },  $form->{"${item}_rate"} * 100);
     push(@{ $form->{TEMPLATE_ARRAYS}->{taxnumber} },      $form->{"${item}_taxnumber"});
+    push(@{ $form->{TEMPLATE_ARRAYS}->{tax_id} },         $form->{"${item}_tax_id"});
 
-    my $tax_obj     = SL::DB::Manager::Tax->find_by(taxnumber => $form->{"${item}_taxnumber"});
-    my $description = $tax_obj ? $tax_obj->translated_attribute('taxdescription',  $form->{language_id}, 0) : '';
-    push(@{ $form->{TEMPLATE_ARRAYS}->{taxdescription} }, $description . q{ } . 100 * $form->{"${item}_rate"} . q{%});
+    if ( $form->{"${item}_tax_id"} ) {
+      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"};
+      my $description = $tax_obj ? $tax_obj->translated_attribute('taxdescription',  $form->{language_id}, 0) : '';
+      push(@{ $form->{TEMPLATE_ARRAYS}->{taxdescription} }, $description . q{ } . 100 * $form->{"${item}_rate"} . q{%});
+    }
   }
 
   $form->{nodiscount_subtotal} = $form->format_amount($myconfig, $form->{nodiscount_total}, 2);
@@ -1498,42 +1758,31 @@ sub order_details {
     $form->{subtotal_nofmt} = $form->{ordtotal};
   }
 
-  $form->{ordtotal} = ($form->{taxincluded}) ? $form->{ordtotal} : $form->{ordtotal} + $tax;
+  my $grossamount = ($form->{taxincluded}) ? $form->{ordtotal} : $form->{ordtotal} + $tax;
+  $form->{ordtotal} = $form->round_amount( $grossamount, 2, 1);
+  $form->{rounding} = $form->round_amount(
+    $form->{ordtotal} - $form->round_amount($grossamount, 2),
+    2
+  );
 
   # format amounts
+  $form->{rounding} = $form->format_amount($myconfig, $form->{rounding}, 2);
   $form->{quototal} = $form->{ordtotal} = $form->format_amount($myconfig, $form->{ordtotal}, 2);
 
-  if ($form->{type} =~ /_quotation/) {
-    $form->set_payment_options($myconfig, $form->{quodate});
-  } else {
-    $form->set_payment_options($myconfig, $form->{orddate});
-  }
+  $form->set_payment_options($myconfig, $form->{$form->{type} =~ /_quotation/ ? 'quodate' : 'orddate'}, $form->{type});
 
   $form->{username} = $myconfig->{name};
 
-  $dbh->disconnect;
-
+  $form->{department}    = SL::DB::Manager::Department->find_by(id => $form->{department_id})->description if $form->{department_id};
   $form->{delivery_term} = SL::DB::Manager::DeliveryTerm->find_by(id => $form->{delivery_term_id} || undef);
   $form->{delivery_term}->description_long($form->{delivery_term}->translated_attribute('description_long', $form->{language_id})) if $form->{delivery_term} && $form->{language_id};
 
   $form->{order} = SL::DB::Manager::Order->find_by(id => $form->{id}) if $form->{id};
+  $form->{$_} = $form->format_amount($myconfig, $form->{$_}, 2) for @separate_totals;
 
   $main::lxdebug->leave_sub();
 }
 
-sub project_description {
-  $main::lxdebug->enter_sub();
-
-  my ($self, $dbh, $id) = @_;
-
-  my $query = qq|SELECT description FROM project WHERE id = ?|;
-  my ($value) = selectrow_query($main::form, $dbh, $query, $id);
-
-  $main::lxdebug->leave_sub();
-
-  return $value;
-}
-
 1;
 
 __END__