Preisfatkoren implementiert.
[kivitendo-erp.git] / SL / IR.pm
index 748a9c1..6561404 100644 (file)
--- a/SL/IR.pm
+++ b/SL/IR.pm
@@ -37,14 +37,17 @@ package IR;
 use SL::AM;
 use SL::Common;
 use SL::DBUtils;
+use SL::MoreCommon;
+use List::Util qw(min);
 
 sub post_invoice {
   $main::lxdebug->enter_sub();
 
-  my ($self, $myconfig, $form) = @_;
+  my ($self, $myconfig, $form, $provided_dbh, $payments_only) = @_;
 
   # connect to database, turn off autocommit
-  my $dbh = $form->dbconnect_noauto($myconfig);
+  my $dbh = $provided_dbh ? $provided_dbh : $form->dbconnect_noauto($myconfig);
+  $form->{defaultcurrency} = $form->get_default_currency($myconfig);
 
   my ($query, $sth, @values, $project_id);
   my ($allocated, $taxrate, $taxamount, $taxdiff, $item);
@@ -54,47 +57,42 @@ sub post_invoice {
 
   my $all_units = AM->retrieve_units($myconfig, $form);
 
-  if ($form->{id}) {
-
-    &reverse_invoice($dbh, $form);
-
-  } else {
-    ($form->{id}) = selectrow_query($form, $dbh, qq|SELECT nextval('glid')|);
-
-    do_query($form, $dbh, qq|INSERT INTO ap (id, invnumber) VALUES (?, '')|, $form->{id});
+  if (!$payments_only) {
+    if ($form->{id}) {
+      &reverse_invoice($dbh, $form);
+    } else {
+      ($form->{id}) = selectrow_query($form, $dbh, qq|SELECT nextval('glid')|);
+      do_query($form, $dbh, qq|INSERT INTO ap (id, invnumber) VALUES (?, '')|, $form->{id});
+    }
   }
 
-  if ($form->{currency} eq $form->{defaultcurrency}) {
+  my ($currencies)    = selectfirst_array_query($form, $dbh, qq|SELECT curr FROM defaults|);
+  my $defaultcurrency = (split m/:/, $currencies)[0];
+
+  if ($form->{currency} eq $defaultcurrency) {
     $form->{exchangerate} = 1;
   } else {
-    $exchangerate =
-      $form->check_exchangerate($myconfig, $form->{currency},
-                                $form->{transdate}, 'sell');
+    $exchangerate = $form->check_exchangerate($myconfig, $form->{currency}, $form->{transdate}, 'sell');
   }
 
-  $form->{exchangerate} =
-    ($exchangerate)
-    ? $exchangerate
-    : $form->parse_amount($myconfig, $form->{exchangerate});
-
+  $form->{exchangerate} = $exchangerate || $form->parse_amount($myconfig, $form->{exchangerate});
   $form->{exchangerate} = 1 unless ($form->{exchangerate} * 1);
 
   my %item_units;
   my $q_item_unit = qq|SELECT unit FROM parts WHERE id = ?|;
   my $h_item_unit = prepare_query($form, $dbh, $q_item_unit);
 
+  $form->get_lists('price_factors' => 'ALL_PRICE_FACTORS');
+  my %price_factors = map { $_->{id} => $_->{factor} } @{ $form->{ALL_PRICE_FACTORS} };
+  my $price_factor;
+
   for my $i (1 .. $form->{rowcount}) {
     next unless $form->{"id_$i"};
 
-    $form->{"qty_$i"} = $form->parse_amount($myconfig, $form->{"qty_$i"});
+    $form->{"qty_$i"}  = $form->parse_amount($myconfig, $form->{"qty_$i"});
+    $form->{"qty_$i"} *= -1 if $form->{storno};
 
-    if ($form->{storno}) {
-      $form->{"qty_$i"} *= -1;
-    }
-
-    if ($main::eur) {
-      $form->{"inventory_accno_$i"} = $form->{"expense_accno_$i"};
-    }
+    $form->{"inventory_accno_$i"} = $form->{"expense_accno_$i"} if $main::eur;
 
     # get item baseunit
     if (!$item_units{$form->{"id_$i"}}) {
@@ -105,8 +103,8 @@ sub post_invoice {
     my $item_unit = $item_units{$form->{"id_$i"}};
 
     if (defined($all_units->{$item_unit}->{factor})
-        && ($all_units->{$item_unit}->{factor} ne '')
-        && ($all_units->{$item_unit}->{factor} * 1 != 0)) {
+            && ($all_units->{$item_unit}->{factor} ne '')
+            && ($all_units->{$item_unit}->{factor} * 1 != 0)) {
       $basefactor = $all_units->{$form->{"unit_$i"}}->{factor} / $all_units->{$item_unit}->{factor};
     } else {
       $basefactor = 1;
@@ -119,17 +117,17 @@ sub post_invoice {
     $taxrate     = 0;
 
     $form->{"sellprice_$i"} = $form->parse_amount($myconfig, $form->{"sellprice_$i"});
-    my $fxsellprice = $form->{"sellprice_$i"};
-
-    my ($dec) = ($fxsellprice =~ /\.(\d+)/);
-    $dec = length $dec;
+    (my $fxsellprice = $form->{"sellprice_$i"}) =~ /\.(\d+)/;
+    my $dec = length $1;
     my $decimalplaces = ($dec > 2) ? $dec : 2;
 
     map { $taxrate += $form->{"${_}_rate"} } @taxaccounts;
 
+    $price_factor = $price_factors{ $form->{"price_factor_id_$i"} } || 1;
+
     if ($form->{"inventory_accno_$i"}) {
 
-      $linetotal = $form->round_amount($form->{"sellprice_$i"} * $form->{"qty_$i"}, 2);
+      $linetotal = $form->round_amount($form->{"sellprice_$i"} * $form->{"qty_$i"} / $price_factor, 2);
 
       if ($form->{taxincluded}) {
         $taxamount              = $linetotal * ($taxrate / (1 + $taxrate));
@@ -159,8 +157,8 @@ sub post_invoice {
       }
 
       # add purchase to inventory, this one is without the tax!
-      $amount    = $form->{"sellprice_$i"} * $form->{"qty_$i"} * $form->{exchangerate};
-      $linetotal = $form->round_amount($form->{"sellprice_$i"} * $form->{"qty_$i"}, 2) * $form->{exchangerate};
+      $amount    = $form->{"sellprice_$i"} * $form->{"qty_$i"} * $form->{exchangerate} / $price_factor;
+      $linetotal = $form->round_amount($form->{"sellprice_$i"} * $form->{"qty_$i"} / $price_factor, 2) * $form->{exchangerate};
       $linetotal = $form->round_amount($linetotal, 2);
 
       # this is the difference for the inventory
@@ -169,17 +167,18 @@ sub post_invoice {
       $form->{amount}{ $form->{id} }{ $form->{"inventory_accno_$i"} } -= $linetotal;
 
       # adjust and round sellprice
-      $form->{"sellprice_$i"} =
-        $form->round_amount($form->{"sellprice_$i"} * $form->{exchangerate}, $decimalplaces);
+      $form->{"sellprice_$i"} = $form->round_amount($form->{"sellprice_$i"} * $form->{exchangerate}, $decimalplaces);
+
+      $lastinventoryaccno = $form->{"inventory_accno_$i"};
+
+      next if $payments_only;
 
       # update parts table
       $query = qq|UPDATE parts SET lastcost = ? WHERE id = ?|;
       @values = ($form->{"sellprice_$i"}, conv_i($form->{"id_$i"}));
       do_query($form, $dbh, $query, @values);
 
-      if (!$form->{shipped}) {
-        $form->update_balance($dbh, "parts", "onhand", qq|id = ?|, $baseqty, $form->{"id_$i"})
-      }
+      $form->update_balance($dbh, "parts", "onhand", qq|id = ?|, $baseqty, $form->{"id_$i"}) if !$form->{shipped};
 
       # check if we sold the item already and
       # make an entry for the expense and inventory
@@ -197,13 +196,7 @@ sub post_invoice {
       my $totalqty = $base_qty;
 
       while (my $ref = $sth->fetchrow_hashref(NAME_lc)) {
-
-        my $qty = $ref->{base_qty} + $ref->{allocated};
-
-        if (($qty - $totalqty) > 0) {
-          $qty = $totalqty;
-        }
-
+        my $qty    = min $totalqty, ($ref->{base_qty} + $ref->{allocated});
         $linetotal = $form->round_amount(($form->{"sellprice_$i"} * $qty) / $basefactor, 2);
 
         if ($ref->{allocated} < 0) {
@@ -223,17 +216,13 @@ sub post_invoice {
 
         } elsif ($linetotal != 0) {
           # add entry for inventory, this one is for the sold item
-          $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, taxkey)
-                      VALUES (?, ?, ?, ?, (SELECT taxkey_id FROM chart WHERE id = ?))|;
-          @values = ($ref->{trans_id},  $ref->{inventory_accno_id}, $linetotal,
-                     $ref->{transdate}, $ref->{inventory_accno_id});
+          $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, taxkey) VALUES (?, ?, ?, ?, (SELECT taxkey_id FROM chart WHERE id = ?))|;
+          @values = ($ref->{trans_id},  $ref->{inventory_accno_id}, $linetotal, $ref->{transdate}, $ref->{inventory_accno_id});
           do_query($form, $dbh, $query, @values);
 
           # add expense
-          $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, taxkey)
-                      VALUES (?, ?, ?, ?, (SELECT taxkey from tax WHERE chart_id = ?))|;
-          @values = ($ref->{trans_id},  $ref->{expense_accno_id}, ($linetotal * -1),
-                     $ref->{transdate}, $ref->{expense_accno_id});
+          $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, taxkey) VALUES (?, ?, ?, ?, (SELECT taxkey from tax WHERE chart_id = ?))|;
+          @values = ($ref->{trans_id},  $ref->{expense_accno_id}, ($linetotal * -1), $ref->{transdate}, $ref->{expense_accno_id});
           do_query($form, $dbh, $query, @values);
         }
 
@@ -242,16 +231,14 @@ sub post_invoice {
 
         $allocated += $qty;
 
-        last if (($totalqty -= $qty) <= 0);
+        last if ($totalqty -= $qty) <= 0;
       }
 
       $sth->finish();
 
-      $lastinventoryaccno = $form->{"inventory_accno_$i"};
+    } else {                    # if ($form->{"inventory_accno_id_$i"})
 
-    } else {
-
-      $linetotal = $form->round_amount($form->{"sellprice_$i"} * $form->{"qty_$i"}, 2);
+      $linetotal = $form->round_amount($form->{"sellprice_$i"} * $form->{"qty_$i"} / $price_factor, 2);
 
       if ($form->{taxincluded}) {
         $taxamount              = $linetotal * ($taxrate / (1 + $taxrate));
@@ -266,23 +253,19 @@ sub post_invoice {
       if ($form->round_amount($taxrate, 7) == 0) {
         if ($form->{taxincluded}) {
           foreach $item (@taxaccounts) {
-            $taxamount =
-              $linetotal * $form->{"${item}_rate"}
-              / (1 + abs($form->{"${item}_rate"}));
+            $taxamount = $linetotal * $form->{"${item}_rate"} / (1 + abs($form->{"${item}_rate"}));
             $totaltax += $taxamount;
             $form->{amount}{ $form->{id} }{$item} -= $taxamount;
           }
-
         } else {
           map { $form->{amount}{ $form->{id} }{$_} -= $linetotal * $form->{"${_}_rate"} } @taxaccounts;
         }
-
       } else {
         map { $form->{amount}{ $form->{id} }{$_} -= $taxamount * $form->{"${_}_rate"} / $taxrate } @taxaccounts;
       }
 
-      $amount    = $form->{"sellprice_$i"} * $form->{"qty_$i"} * $form->{exchangerate};
-      $linetotal = $form->round_amount($form->{"sellprice_$i"} * $form->{"qty_$i"}, 2) * $form->{exchangerate};
+      $amount    = $form->{"sellprice_$i"} * $form->{"qty_$i"} * $form->{exchangerate} / $price_factor;
+      $linetotal = $form->round_amount($form->{"sellprice_$i"} * $form->{"qty_$i"} / $price_factor, 2) * $form->{exchangerate};
       $linetotal = $form->round_amount($linetotal, 2);
 
       # this is the difference for expense
@@ -296,22 +279,27 @@ sub post_invoice {
       # adjust and round sellprice
       $form->{"sellprice_$i"} = $form->round_amount($form->{"sellprice_$i"} * $form->{exchangerate}, $decimalplaces);
 
+      next if $payments_only;
+
       # update lastcost
       $query = qq|UPDATE parts SET lastcost = ? WHERE id = ?|;
       do_query($form, $dbh, $query, $form->{"sellprice_$i"}, conv_i($form->{"id_$i"}));
     }
 
+    next if $payments_only;
+
     # save detail record in invoice table
     $query =
       qq|INSERT INTO invoice (trans_id, parts_id, description, qty, base_qty,
                               sellprice, fxsellprice, allocated, unit, deliverydate,
-                              project_id, serialnumber)
-         VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)|;
+                              project_id, serialnumber, price_factor_id, price_factor, marge_price_factor)
+         VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, (SELECT factor FROM price_factors WHERE id = ?), ?)|;
     @values = (conv_i($form->{id}), conv_i($form->{"id_$i"}),
                $form->{"description_$i"}, $form->{"qty_$i"} * -1,
                $baseqty * -1, $form->{"sellprice_$i"}, $fxsellprice, $allocated,
                $form->{"unit_$i"}, conv_date($form->{deliverydate}),
-               conv_i($form->{"project_id_$i"}), $form->{"serialnumber_$i"});
+               conv_i($form->{"project_id_$i"}), $form->{"serialnumber_$i"},
+               conv_i($form->{"price_factor_id_$i"}), conv_i($form->{"price_factor_id_$i"}), conv_i($form->{"marge_price_factor_$i"}));
     do_query($form, $dbh, $query, @values);
   }
 
@@ -329,7 +317,7 @@ sub post_invoice {
   for my $i (1 .. $form->{paidaccounts}) {
     $form->{"paid_$i"}  = $form->parse_amount($myconfig, $form->{"paid_$i"});
     $form->{paid}      += $form->{"paid_$i"};
-    $form->{datepaid}   = $form->{"datepaid_$i"} if ($form->{"datepaid_$i"});
+    $form->{datepaid}   = $form->{"datepaid_$i"} if $form->{"datepaid_$i"};
   }
 
   my ($tax, $paiddiff) = (0, 0);
@@ -356,12 +344,9 @@ sub post_invoice {
     $expensediff += $paiddiff;
 
     ######## this only applies to tax included
-    if ($lastinventoryaccno) {
-      $form->{amount}{ $form->{id} }{$lastinventoryaccno} -= $invoicediff;
-    }
-    if ($lastexpenseaccno) {
-      $form->{amount}{ $form->{id} }{$lastexpenseaccno} -= $expensediff;
-    }
+    
+    $form->{amount}{ $form->{id} }{$lastinventoryaccno} -= $invoicediff if $lastinventoryaccno;
+    $form->{amount}{ $form->{id} }{$lastexpenseaccno}   -= $expensediff if $lastexpenseaccno;
 
   } else {
     $amount    = $form->round_amount($netamount * $form->{exchangerate}, 2);
@@ -380,20 +365,20 @@ sub post_invoice {
 
   $form->{amount}{ $form->{id} }{ $form->{AP} } = $netamount + $tax;
 
-  if ($form->{paid} != 0) {
-    $form->{paid} = $form->round_amount($form->{paid} * $form->{exchangerate} + $paiddiff, 2);
-  }
+  
+  $form->{paid} = $form->round_amount($form->{paid} * $form->{exchangerate} + $paiddiff, 2) if $form->{paid} != 0;
 
   # update exchangerate
-  if (($form->{currency} ne $form->{defaultcurrency}) && !$exchangerate) {
-    $form->update_exchangerate($dbh, $form->{currency}, $form->{invdate}, 0, $form->{exchangerate});
-  }
+  
+  $form->update_exchangerate($dbh, $form->{currency}, $form->{invdate}, 0, $form->{exchangerate})
+    if ($form->{currency} ne $defaultcurrency) && !$exchangerate;
 
   # record acc_trans transactions
   foreach my $trans_id (keys %{ $form->{amount} }) {
     foreach my $accno (keys %{ $form->{amount}{$trans_id} }) {
       $form->{amount}{$trans_id}{$accno} = $form->round_amount($form->{amount}{$trans_id}{$accno}, 2);
-      next unless $form->{amount}{$trans_id}{$accno};
+
+      next if $payments_only || !$form->{amount}{$trans_id}{$accno};
 
       $query = qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, taxkey, project_id)
                   VALUES (?, (SELECT id FROM chart WHERE accno = ?), ?, ?,
@@ -413,13 +398,12 @@ sub post_invoice {
   }
 
   # force AP entry if 0
-  if ($form->{amount}{ $form->{id} }{ $form->{AP} } == 0) {
-    $form->{amount}{ $form->{id} }{ $form->{AP} } = $form->{paid};
-  }
+  
+  $form->{amount}{ $form->{id} }{ $form->{AP} } = $form->{paid} if $form->{amount}{$form->{id}}{$form->{AP}} == 0;
 
   # record payments and offsetting AP
   for my $i (1 .. $form->{paidaccounts}) {
-    next if ($form->{"paid_$i"} == 0);
+    next if $form->{"paid_$i"} == 0;
 
     my ($accno)            = split /--/, $form->{"AP_paid_$i"};
     $form->{"datepaid_$i"} = $form->{invdate} unless ($form->{"datepaid_$i"});
@@ -447,20 +431,15 @@ sub post_invoice {
 
     $exchangerate = 0;
 
-    if ($form->{currency} eq $form->{defaultcurrency}) {
+    if ($form->{currency} eq $defaultcurrency) {
       $form->{"exchangerate_$i"} = 1;
     } else {
-      $exchangerate = $form->check_exchangerate($myconfig, $form->{currency}, $form->{"datepaid_$i"}, 'sell');
-
-      $form->{"exchangerate_$i"} =
-        ($exchangerate)
-        ? $exchangerate
-        : $form->parse_amount($myconfig, $form->{"exchangerate_$i"});
+      $exchangerate              = $form->check_exchangerate($myconfig, $form->{currency}, $form->{"datepaid_$i"}, 'sell');
+      $form->{"exchangerate_$i"} = $exchangerate || $form->parse_amount($myconfig, $form->{"exchangerate_$i"});
     }
 
     # exchangerate difference
-    $form->{fx}{$accno}{ $form->{"datepaid_$i"} } +=
-      $form->{"paid_$i"} * ($form->{"exchangerate_$i"} - 1) + $paiddiff;
+    $form->{fx}{$accno}{ $form->{"datepaid_$i"} } += $form->{"paid_$i"} * ($form->{"exchangerate_$i"} - 1) + $paiddiff;
 
     # gain/loss
     $amount =
@@ -468,7 +447,6 @@ sub post_invoice {
       ($form->{"paid_$i"} * $form->{"exchangerate_$i"});
     if ($amount > 0) {
       $form->{fx}{ $form->{fxgain_accno} }{ $form->{"datepaid_$i"} } += $amount;
-
     } else {
       $form->{fx}{ $form->{fxloss_accno} }{ $form->{"datepaid_$i"} } += $amount;
     }
@@ -476,11 +454,8 @@ sub post_invoice {
     $paiddiff = 0;
 
     # update exchange rate
-    if (($form->{currency} ne $form->{defaultcurrency}) && !$exchangerate) {
-      $form->update_exchangerate($dbh, $form->{currency},
-                                 $form->{"datepaid_$i"},
-                                 0, $form->{"exchangerate_$i"});
-    }
+    $form->update_exchangerate($dbh, $form->{currency}, $form->{"datepaid_$i"}, 0, $form->{"exchangerate_$i"})
+      if ($form->{currency} ne $defaultcurrency) && !$exchangerate;
   }
 
   # record exchange rate differences and gains/losses
@@ -496,6 +471,19 @@ sub post_invoice {
     }
   }
 
+  if ($payments_only) {
+    $query = qq|UPDATE ap SET paid = ?, datepaid = ? WHERE id = ?|;
+    do_query($form, $dbh, $query,  $form->{paid}, $form->{paid} ? conv_date($form->{datepaid}) : undef, conv_i($form->{id}));
+
+    if (!$provided_dbh) {
+      $dbh->commit();
+      $dbh->disconnect();
+    }
+
+    $main::lxdebug->leave_sub();
+    return;
+  }
+
   $amount = $netamount + $tax;
 
   # set values which could be empty
@@ -503,45 +491,28 @@ sub post_invoice {
   $form->{department_id} = (split /--/, $form->{department})[1];
   $form->{invnumber}     = $form->{id} unless $form->{invnumber};
 
-  $taxzone_id = 0 if ((3 < $taxzone_id) || (0 > $taxzone_id));
+  $taxzone_id = 0 if (3 < $taxzone_id) || (0 > $taxzone_id);
 
   # save AP record
   $query = qq|UPDATE ap SET
-                invnumber = ?,
-                ordnumber = ?,
-                quonumber = ?,
-                transdate = ?,
-                orddate = ?,
-                quodate = ?,
-                vendor_id = ?,
-                amount = ?,
-                netamount = ?,
-                paid = ?,
-                datepaid = ?,
-                duedate = ?,
-                invoice = '1',
-                taxzone_id = ?,
-                taxincluded = ?,
-                notes = ?,
-                intnotes = ?,
-                curr = ?,
-                department_id = ?,
-                storno = ?,
-                globalproject_id = ?,
-                cp_id = ?,
-                employee_id = ?
+                invnumber    = ?, ordnumber   = ?, quonumber     = ?, transdate   = ?,
+                orddate      = ?, quodate     = ?, vendor_id     = ?, amount      = ?,
+                netamount    = ?, paid        = ?, duedate       = ?, datepaid    = ?,
+                invoice      = ?, taxzone_id  = ?, notes         = ?, taxincluded = ?,
+                intnotes     = ?, curr        = ?, storno_id     = ?, storno      = ?,
+                cp_id        = ?, employee_id = ?, department_id = ?, 
+                globalproject_id = ?
               WHERE id = ?|;
-  @values = ($form->{invnumber}, $form->{ordnumber}, $form->{quonumber},
-             conv_date($form->{invdate}), conv_date($form->{orddate}), conv_date($form->{quodate}),
-             conv_i($form->{vendor_id}), $amount, $netamount, $form->{paid},
-             $form->{paid} ? conv_date($form->{datepaid}) : undef,
-             conv_date($form->{duedate}), $taxzone_id,
-             $form->{taxincluded} ? 't' : 'f',
-             $form->{notes}, $form->{intnotes}, $form->{currency}, conv_i($form->{department_id}),
-             $form->{storno} ? 't' : 'f',
-             conv_i($form->{globalproject_id}), conv_i($form->{cp_id}),
-             conv_i($form->{employee_id}),
-             conv_i($form->{id}));
+  @values = (
+                $form->{invnumber},          $form->{ordnumber},           $form->{quonumber},      conv_date($form->{invdate}),
+      conv_date($form->{orddate}), conv_date($form->{quodate}),     conv_i($form->{vendor_id}),               $amount,
+                $netamount,                  $form->{paid},      conv_date($form->{duedate}),       $form->{paid} ? conv_date($form->{datepaid}) : undef,
+            '1',                             $taxzone_id,                  $form->{notes},          $form->{taxincluded} ? 't' : 'f',
+                $form->{intnotes},           $form->{currency},     conv_i($form->{storno_id}),     $form->{storno}      ? 't' : 'f',
+         conv_i($form->{cp_id}),      conv_i($form->{employee_id}), conv_i($form->{department_id}), 
+         conv_i($form->{globalproject_id}),
+         conv_i($form->{id})
+  );
   do_query($form, $dbh, $query, @values);
 
   if ($form->{storno}) {
@@ -569,8 +540,12 @@ sub post_invoice {
 
   Common::webdav_folder($form) if ($main::webdav);
 
-  my $rc = $dbh->commit;
-  $dbh->disconnect;
+  my $rc = 1;
+
+  if (!$provided_dbh) {
+    $rc = $dbh->commit();
+    $dbh->disconnect();
+  }
 
   $main::lxdebug->leave_sub();
 
@@ -694,20 +669,31 @@ sub retrieve_invoice {
 
   my ($query, $sth, $ref, $q_invdate);
 
-  $q_invdate = ", current_date AS invdate" unless $form->{id};
+  if (!$form->{id}) {
+    $q_invdate = qq|, COALESCE((SELECT transdate FROM ar WHERE id = (SELECT MAX(id) FROM ar)), current_date) AS invdate|;
+    if ($form->{vendor_id}) {
+      my $vendor_id = $dbh->quote($form->{vendor_id} * 1);
+      $q_invdate .=
+        qq|, COALESCE((SELECT transdate FROM ar WHERE id = (SELECT MAX(id) FROM ar)), current_date) +
+             COALESCE((SELECT pt.terms_netto
+                       FROM vendor v
+                       LEFT JOIN payment_terms pt ON (v.payment_id = pt.id)
+                       WHERE v.id = $vendor_id),
+                      0) AS duedate|;
+    }
+  }
 
   # get default accounts and last invoice number
 
-  $query=
-    qq|SELECT
-         (SELECT c.accno FROM chart c WHERE d.inventory_accno_id = c.id) AS inventory_accno,
-         (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,
-         d.curr AS currencies
-         $q_invdate
-         FROM defaults d|;
+  $query = qq|SELECT
+               (SELECT c.accno FROM chart c WHERE d.inventory_accno_id = c.id) AS inventory_accno,
+               (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,
+               d.curr AS currencies
+               $q_invdate
+               FROM defaults d|;
   $ref = selectfirst_hashref_query($form, $dbh, $query);
   map { $form->{$_} = $ref->{$_} } keys %$ref;
 
@@ -719,17 +705,16 @@ sub retrieve_invoice {
   }
 
   # retrieve invoice
-  $query =
-    qq|SELECT cp_id, invnumber, transdate AS invdate, duedate,
-         orddate, quodate, globalproject_id,
-         ordnumber, quonumber, paid, taxincluded, notes, taxzone_id, storno, gldate,
-         intnotes, curr AS currency
-       FROM ap
-       WHERE id = ?|;
+  $query = qq|SELECT cp_id, invnumber, transdate AS invdate, duedate,
+                orddate, quodate, globalproject_id,
+                ordnumber, quonumber, paid, taxincluded, notes, taxzone_id, storno, gldate,
+                intnotes, curr AS currency
+              FROM ap
+              WHERE id = ?|;
   $ref = selectfirst_hashref_query($form, $dbh, $query, conv_i($form->{id}));
   map { $form->{$_} = $ref->{$_} } keys %$ref;
 
-  $form->{exchangerate} = $form->get_exchangerate($dbh, $form->{currency}, $form->{invdate}, "sell");
+  $form->{exchangerate}  = $form->get_exchangerate($dbh, $form->{currency}, $form->{invdate}, "sell");
 
   # get shipto
   $query = qq|SELECT * FROM shipto WHERE (trans_id = ?) AND (module = 'AP')|;
@@ -745,39 +730,19 @@ sub retrieve_invoice {
   # retrieve individual items
   $query =
     qq|SELECT
-        c1.accno                         AS inventory_accno,
-        c1.new_chart_id                  AS inventory_new_chart,
-        date($transdate) - c1.valid_from AS inventory_valid,
-
-        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,
-
-        i.description, i.qty, i.fxsellprice AS sellprice,
-        i.parts_id AS id, i.unit, i.deliverydate, i.project_id, i.serialnumber,
+        c1.accno AS inventory_accno, c1.new_chart_id AS inventory_new_chart, date($transdate) - c1.valid_from AS inventory_valid,
+        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,
 
-        p.partnumber, p.inventory_accno_id AS part_inventory_accno_id, p.bin,
-        pr.projectnumber,
-        pg.partsgroup
+        i.description, i.qty, i.fxsellprice AS sellprice, i.parts_id AS id, i.unit, i.deliverydate, i.project_id, i.serialnumber,
+        i.price_factor_id, i.price_factor, i.marge_price_factor,
+        p.partnumber, p.inventory_accno_id AS part_inventory_accno_id, p.bin, pr.projectnumber, pg.partsgroup
 
         FROM invoice i
         JOIN parts p ON (i.parts_id = p.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 income_accno_id_${taxzone_id}
-            FROM buchungsgruppen
-            WHERE id = p.buchungsgruppen_id) = c2.id)
-        LEFT JOIN chart c3 ON
-          ((SELECT expense_accno_id_${taxzone_id}
-            FROM buchungsgruppen
-            WHERE id = p.buchungsgruppen_id) = c3.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 income_accno_id_${taxzone_id}  FROM buchungsgruppen WHERE id = p.buchungsgruppen_id) = c2.id)
+        LEFT JOIN chart c3 ON ((SELECT expense_accno_id_${taxzone_id} FROM buchungsgruppen WHERE id = p.buchungsgruppen_id) = c3.id)
         LEFT JOIN project pr    ON (i.project_id = pr.id)
         LEFT JOIN partsgroup pg ON (pg.id = p.partsgroup_id)
 
@@ -787,37 +752,24 @@ sub retrieve_invoice {
   $sth = prepare_execute_query($form, $dbh, $query, conv_i($form->{id}));
 
   while (my $ref = $sth->fetchrow_hashref(NAME_lc)) {
-    if (!$ref->{"part_inventory_accno_id"}) {
-      map({ delete($ref->{$_}); } qw(inventory_accno inventory_new_chart inventory_valid));
-    }
+    map({ delete($ref->{$_}); } qw(inventory_accno inventory_new_chart inventory_valid)) if !$ref->{"part_inventory_accno_id"};
     delete($ref->{"part_inventory_accno_id"});
 
     foreach my $type (qw(inventory income expense)) {
       while ($ref->{"${type}_new_chart"} && ($ref->{"${type}_valid"} >=0)) {
-        my $query =
-          qq|SELECT accno, new_chart_id, date($transdate) - valid_from
-             FROM chart
-             WHERE id = ?|;
-        ($ref->{"${type}_accno"},
-         $ref->{"${type}_new_chart"},
-         $ref->{"${type}_valid"})
-          = selectrow_query($form, $dbh, $query, $ref->{"${type}_new_chart"});
+        my $query = qq|SELECT accno, new_chart_id, date($transdate) - valid_from FROM chart WHERE id = ?|;
+        @$ref{ map $type.$_, qw(_accno _new_chart _valid) } = selectrow_query($form, $dbh, $query, $ref->{"${type}_new_chart"});
       }
     }
 
     # 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
-         FROM tax t
+      qq|SELECT c.accno, t.taxdescription, t.rate, t.taxnumber FROM tax t
          LEFT JOIN chart c ON (c.id = t.chart_id)
          WHERE t.id in
-           (SELECT tk.tax_id
-            FROM taxkeys tk
-            WHERE tk.chart_id =
-              (SELECT id
-               FROM chart
-               WHERE accno = ?)
+           (SELECT tk.tax_id FROM taxkeys tk
+            WHERE tk.chart_id = (SELECT id FROM chart WHERE accno = ?)
               AND (startdate <= $transdate)
             ORDER BY startdate DESC
             LIMIT 1)
@@ -978,7 +930,7 @@ sub retrieve_item {
   foreach my $table_column (qw(p.partnumber p.description pg.partsgroup)) {
     my $field = (split m{\.}, $table_column)[1];
     next unless $form->{"${field}_${i}"};
-    $where .= " AND lower(${table_column}) LIKE ?";
+    $where .= " AND lower(${table_column}) LIKE lower(?)";
     push @values, '%' . $form->{"${field}_${i}"} . '%';
   }
 
@@ -1003,7 +955,9 @@ sub retrieve_item {
          p.id, p.partnumber, p.description, p.lastcost AS sellprice, p.listprice,
          p.unit, p.assembly, p.bin, p.onhand, p.formel,
          p.notes AS partnotes, p.notes AS longdescription, p.not_discountable,
-         p.inventory_accno_id,
+         p.inventory_accno_id, p.price_factor_id,
+
+         pfac.factor AS price_factor,
 
          c1.accno                         AS inventory_accno,
          c1.new_chart_id                  AS inventory_new_chart,
@@ -1033,6 +987,7 @@ sub retrieve_item {
            FROM buchungsgruppen
            WHERE id = p.buchungsgruppen_id) = c3.id)
        LEFT JOIN partsgroup pg ON (pg.id = p.partsgroup_id)
+       LEFT JOIN price_factors pfac ON (pfac.id = p.price_factor_id)
        WHERE $where|;
   my $sth = prepare_execute_query($form, $dbh, $query, @values);
 
@@ -1177,145 +1132,104 @@ sub item_links {
   $main::lxdebug->leave_sub();
 }
 
-sub post_payment {
+sub _delete_payments {
   $main::lxdebug->enter_sub();
 
-  my ($self, $myconfig, $form, $locale) = @_;
+  my ($self, $form, $dbh) = @_;
 
-  # connect to database, turn off autocommit
-  my $dbh = $form->dbconnect_noauto($myconfig);
+  my @delete_oids;
 
-  $form->{datepaid} = $form->{invdate};
+  # Delete old payment entries from acc_trans.
+  my $query =
+    qq|SELECT oid
+       FROM acc_trans
+       WHERE (trans_id = ?) AND fx_transaction
 
-  # total payments, don't move we need it here
-  for my $i (1 .. $form->{paidaccounts}) {
-    $form->{"paid_$i"}  = $form->parse_amount($myconfig, $form->{"paid_$i"});
-    $form->{paid}      += $form->{"paid_$i"};
-    $form->{datepaid}   = $form->{"datepaid_$i"} if ($form->{"datepaid_$i"});
-  }
+       UNION
 
-  $form->{exchangerate} =
-      $form->get_exchangerate($dbh, $form->{currency}, $form->{invdate}, "buy");
+       SELECT at.oid
+       FROM acc_trans at
+       LEFT JOIN chart c ON (at.chart_id = c.id)
+       WHERE (trans_id = ?) AND (c.link LIKE '%AP_paid%')|;
+  push @delete_oids, selectall_array_query($form, $dbh, $query, conv_i($form->{id}), conv_i($form->{id}));
 
-  my $project_id = conv_i($form->{"globalproject_id"});
+  $query =
+    qq|SELECT at.oid
+       FROM acc_trans at
+       LEFT JOIN chart c ON (at.chart_id = c.id)
+       WHERE (trans_id = ?)
+         AND ((c.link = 'AP') OR (c.link LIKE '%:AP') OR (c.link LIKE 'AP:%'))
+       ORDER BY at.oid
+       OFFSET 1|;
+  push @delete_oids, selectall_array_query($form, $dbh, $query, conv_i($form->{id}));
+
+  if (@delete_oids) {
+    $query = qq|DELETE FROM acc_trans WHERE oid IN (| . join(", ", @delete_oids) . qq|)|;
+    do_query($form, $dbh, $query);
+  }
 
-  # record payments and offsetting AP
-  for my $i (1 .. $form->{paidaccounts}) {
-    next if $form->{"paid_$i"} == 0;
+  $main::lxdebug->leave_sub();
+}
 
-    my ($accno)            = split /--/, $form->{"AP_paid_$i"};
-    $form->{"datepaid_$i"} = $form->{invdate} unless $form->{"datepaid_$i"};
-    $form->{datepaid}      = $form->{"datepaid_$i"};
+sub post_payment {
+  $main::lxdebug->enter_sub();
 
-    $exchangerate = 0;
-    if (($form->{currency} eq $form->{defaultcurrency}) || ($form->{defaultcurrency} eq "")) {
-      $form->{"exchangerate_$i"} = 1;
+  my ($self, $myconfig, $form, $locale) = @_;
 
-    } else {
-      $exchangerate = $form->check_exchangerate($myconfig, $form->{currency}, $form->{"datepaid_$i"}, 'buy');
+  # connect to database, turn off autocommit
+  my $dbh = $form->dbconnect_noauto($myconfig);
 
-      $form->{"exchangerate_$i"} =
-        ($exchangerate)
-        ? $exchangerate
-        : $form->parse_amount($myconfig, $form->{"exchangerate_$i"});
-    }
+  my (%payments, $old_form, $row, $item, $query, %keep_vars);
 
-    # record AP
-    $amount = $form->round_amount($form->{"paid_$i"} * $form->{"exchangerate"}, 2) * -1;
+  $old_form = save_form();
 
-    $query =
-      qq|DELETE FROM acc_trans
-         WHERE (trans_id = ?)
-           AND (chart_id = (SELECT c.id FROM chart c WHERE c.accno = ?))
-           AND (amount = ?)
-           AND (transdate = ?)|;
-    @values = (conv_i($form->{id}), $form->{AP}, $amount, conv_date($form->{"datepaid_$i"}));
-    do_query($form, $dbh, $query, @values);
+  # Delete all entries in acc_trans from prior payments.
+  $self->_delete_payments($form, $dbh);
 
-    $query =
-      qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, taxkey, project_id)
-         VALUES (?, (SELECT c.id FROM chart c WHERE c.accno = ?), ?, ?,
-                 (SELECT taxkey_id FROM chart WHERE accno = ?), ?)|;
-    @values = (conv_i($form->{id}), $form->{AP}, $amount,
-               conv_date($form->{"datepaid_$i"}), $form->{AP}, $project_id);
-    do_query($form, $dbh, $query, @values);
+  # Save the new payments the user made before cleaning up $form.
+  map { $payments{$_} = $form->{$_} } grep m/^datepaid_\d+$|^memo_\d+$|^source_\d+$|^exchangerate_\d+$|^paid_\d+$|^AP_paid_\d+$|^paidaccounts$/, keys %{ $form };
 
-    $query =
-      qq|DELETE FROM acc_trans
-         WHERE (trans_id = ?)
-          AND (chart_id=(SELECT c.id FROM chart c WHERE c.accno = ?))
-          AND (amount = ?)
-          AND (transdate = ?)
-          AND (source = ?)
-          AND (memo = ?)|;
-    @values = (conv_i($form->{id}), $accno, $form->{"paid_$i"},
-               conv_date($form->{"datepaid_$i"}), $form->{"source_$i"},
-               $form->{"memo_$i"});
-    do_query($form, $dbh, $query, @values);
+  # Clean up $form so that old content won't tamper the results.
+  %keep_vars = map { $_, 1 } qw(login password id);
+  map { delete $form->{$_} unless $keep_vars{$_} } keys %{ $form };
 
-    $query =
-      qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, source, memo, taxkey, project_id)
-         VALUES (?, (SELECT c.id FROM chart c WHERE c.accno = ?), ?, ?, ?, ?,
-                 (SELECT taxkey_id FROM chart WHERE accno = ?), ?)|;
-    @values = (conv_i($form->{id}), $accno, $form->{"paid_$i"},
-               conv_date($form->{"datepaid_$i"}), $form->{"source_$i"},
-               $form->{"memo_$i"}, $accno, $project_id);
-    do_query($form, $dbh, $query, @values);
+  # Retrieve the invoice from the database.
+  $self->retrieve_invoice($myconfig, $form);
 
-    # gain/loss
-    $amount = $form->{"paid_$i"} * $form->{exchangerate} - $form->{"paid_$i"} * $form->{"exchangerate_$i"};
+  # Set up the content of $form in the way that IR::post_invoice() expects.
+  $form->{exchangerate} = $form->format_amount($myconfig, $form->{exchangerate});
 
-    if ($amount > 0) {
-      $form->{fx}{ $form->{fxgain_accno} }{ $form->{"datepaid_$i"} } += $amount;
-    } else {
-      $form->{fx}{ $form->{fxloss_accno} }{ $form->{"datepaid_$i"} } += $amount;
-    }
+  for $row (1 .. scalar @{ $form->{invoice_details} }) {
+    $item = $form->{invoice_details}->[$row - 1];
 
-    $diff = 0;
+    map { $item->{$_} = $form->format_amount($myconfig, $item->{$_}) } qw(qty sellprice);
 
-    # update exchange rate
-    if (($form->{currency} ne $form->{defaultcurrency}) && !$exchangerate) {
-      $form->update_exchangerate($dbh, $form->{currency},
-                                 $form->{"datepaid_$i"},
-                                 $form->{"exchangerate_$i"}, 0);
-    }
+    map { $form->{"${_}_${row}"} = $item->{$_} } keys %{ $item };
   }
 
-  # record exchange rate differences and gains/losses
-  foreach my $accno (keys %{ $form->{fx} }) {
-    foreach my $transdate (keys %{ $form->{fx}{$accno} }) {
-      $form->{fx}{$accno}{$transdate} = $form->round_amount($form->{fx}{$accno}{$transdate}, 2);
-      next if $form->{fx}{$accno}{$transdate} == 0;
+  $form->{rowcount} = scalar @{ $form->{invoice_details} };
 
-      $query =
-        qq|DELETE FROM acc_trans
-           WHERE (trans_id = ?)
-             AND (chart_id = (SELECT id FROM chart WHERE accno = ?))
-             AND (amount = ?)
-             AND (transdate = ?)
-             AND (cleared = '0')
-             AND (fx_transaction = '1')|;
-      @values = (conv_i($form->{id}), $accno, $form->{fx}{$accno}{$transdate}, $transdate);
-      do_query($form, $dbh, $query, @values);
+  delete @{$form}{qw(invoice_details paidaccounts storno paid)};
 
-      $query =
-        qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, cleared, fx_transaction, taxkey, project_id)
-           VALUES (?, (SELECT id FROM chart WHERE accno = ?), ?, ?, '0', '1',
-                   (SELECT taxkey_id FROM chart WHERE accno = ?), ?)|;
-      @values = (conv_i($form->{id}), $accno, $form->{fx}{$accno}{$transdate},
-                 $transdate, $accno, $project_id);
-      do_query($form, $dbh, $query, @values);
-    }
-  }
+  # Restore the payment options from the user input.
+  map { $form->{$_} = $payments{$_} } keys %payments;
+
+  # Get the AP accno (which is normally done by Form::create_links()).
+  $query =
+    qq|SELECT c.accno
+       FROM acc_trans at
+       LEFT JOIN chart c ON (at.chart_id = c.id)
+       WHERE (trans_id = ?)
+         AND ((c.link = 'AP') OR (c.link LIKE '%:AP') OR (c.link LIKE 'AP:%'))
+       ORDER BY at.oid
+       LIMIT 1|;
 
-  my $datepaid = $form->{paid}    ? qq|'$form->{datepaid}'| : "NULL";
+  ($form->{AP}) = selectfirst_array_query($form, $dbh, $query, conv_i($form->{id}));
 
-  # save AP record
-  my $query = qq|UPDATE ap
-                 SET paid = ?, datepaid = ?
-                 WHERE id = ?|;
-  @values = ($form->{paid}, $form->{paid} ? conv_date($form->{datepaid}) : undef, conv_i($form->{id}));
-  do_query($form, $dbh, $query, @values);
+  # Post the new payments.
+  $self->post_invoice($myconfig, $form, $dbh, 1);
+
+  restore_form($old_form);
 
   my $rc = $dbh->commit();
   $dbh->disconnect();