Letter: model sortierung benutzen
[kivitendo-erp.git] / SL / IS.pm
index a6445a3..7705a11 100644 (file)
--- a/SL/IS.pm
+++ b/SL/IS.pm
@@ -36,6 +36,7 @@ package IS;
 
 use List::Util qw(max);
 
+use Carp;
 use SL::AM;
 use SL::ARAP;
 use SL::CVar;
@@ -60,6 +61,8 @@ use strict;
 sub invoice_details {
   $main::lxdebug->enter_sub();
 
+  # prepare invoice for printing
+
   my ($self, $myconfig, $form, $locale) = @_;
 
   $form->{duedate} ||= $form->{invdate};
@@ -68,9 +71,6 @@ sub invoice_details {
   my $dbh = $form->get_standard_dbh;
   my $sth;
 
-  my $query = qq|SELECT date | . conv_dateq($form->{duedate}) . qq| - date | . conv_dateq($form->{invdate}) . qq| AS terms|;
-  ($form->{terms}) = selectrow_query($form, $dbh, $query);
-
   my (@project_ids);
   $form->{TEMPLATE_ARRAYS} = {};
 
@@ -185,6 +185,74 @@ sub invoice_details {
 
     if ($form->{"id_$i"} != 0) {
 
+      # Prepare linked items for printing
+      if ( $form->{"invoice_id_$i"} ) {
+
+        require SL::DB::InvoiceItem;
+        my $invoice_item = SL::DB::Manager::InvoiceItem->find_by( id => $form->{"invoice_id_$i"} );
+        my $linkeditems  = $invoice_item->linked_records( direction => 'from', recursive => 1 );
+
+        # check for (recursively) linked sales quotation items, sales order
+        # items and sales delivery order items.
+
+        # The checks for $form->{"ordnumber_$i"} and quo and do are for the old
+        # behaviour, where this data was stored in its own database fields in
+        # the invoice items, and there were no record links for the items.
+
+        # If this information were to be fetched in retrieve_invoice, e.g. for showing
+        # this information in the second row, then these fields will already have
+        # been set and won't be calculated again. This shouldn't be done there
+        # though, as each invocation creates several database calls per item, and would
+        # make the interface very slow for many items. So currently these
+        # requests are only made when printing the record.
+
+        # When using the workflow an invoice item can only be (recursively) linked to at
+        # most one sales quotation item and at most one delivery order item.  But it may
+        # be linked back to several order items, if collective orders were involved. If
+        # that is the case we will always choose the very first order item from the
+        # original order, i.e. where it first appeared in an order.
+
+        # TODO: credit note items aren't checked for a record link to their
+        # invoice item
+
+        unless ( $form->{"ordnumber_$i"} ) {
+
+          # $form->{"ordnumber_$i"} comes from ordnumber in invoice, if an
+          # entry exists this must be from before the change from ordnumber to linked items.
+          # So we just use that value and don't check for linked items.
+          # In that case there won't be any links for quo or do items either
+
+          # sales order items are fetched and sorted by id, the lowest id is first
+          # It is assumed that the id always grows, so the item we want (the original) will have the lowest id
+          # better solution: filter the order_item that doesn't have any links from other order_items
+          #                  or maybe fetch linked_records with param save_path and order by _record_length_depth
+          my @linked_orderitems = grep { $_->isa("SL::DB::OrderItem") && $_->record->type eq 'sales_order' } @{$linkeditems};
+          if ( scalar @linked_orderitems ) {
+            @linked_orderitems = sort { $a->id <=> $b->id } @linked_orderitems;
+            my $orderitem = $linked_orderitems[0]; # 0: the original order item, -1: the last collective order item
+
+            $form->{"ordnumber_$i"}       = $orderitem->record->record_number;
+            $form->{"transdate_oe_$i"}    = $orderitem->record->transdate->to_kivitendo;
+            $form->{"cusordnumber_oe_$i"} = $orderitem->record->cusordnumber;
+          };
+
+          my @linked_quoitems = grep { $_->isa("SL::DB::OrderItem") && $_->record->type eq 'sales_quotation' } @{$linkeditems};
+          if ( scalar @linked_quoitems ) {
+            croak "an invoice item may only be linked back to 1 sales quotation item, something is wrong\n" unless scalar @linked_quoitems == 1;
+            $form->{"quonumber_$i"}     = $linked_quoitems[0]->record->record_number;
+            $form->{"transdate_quo_$i"} = $linked_quoitems[0]->record->transdate->to_kivitendo;
+          };
+
+          my @linked_deliveryorderitems = grep { $_->isa("SL::DB::DeliveryOrderItem") && $_->record->type eq 'sales_delivery_order' } @{$linkeditems};
+          if ( scalar @linked_deliveryorderitems ) {
+            croak "an invoice item may only be linked back to 1 sales delivery item, something is wrong\n" unless scalar @linked_deliveryorderitems == 1;
+            $form->{"donumber_$i"}     = $linked_deliveryorderitems[0]->record->record_number;
+            $form->{"transdate_do_$i"} = $linked_deliveryorderitems[0]->record->transdate->to_kivitendo;
+          };
+        };
+      };
+
+
       # add number, description and qty to $form->{number},
       if ($form->{"subtotal_$i"} && !$subtotal_header) {
         $subtotal_header = $i;
@@ -218,9 +286,15 @@ sub invoice_details {
       push @{ $form->{TEMPLATE_ARRAYS}->{deliverydate_oe} },   $form->{"reqdate_$i"};
       push @{ $form->{TEMPLATE_ARRAYS}->{sellprice} },         $form->{"sellprice_$i"};
       push @{ $form->{TEMPLATE_ARRAYS}->{sellprice_nofmt} },   $form->parse_amount($myconfig, $form->{"sellprice_$i"});
+      # linked item print variables
+      push @{ $form->{TEMPLATE_ARRAYS}->{quonumber_quo} },     $form->{"quonumber_$i"};
+      push @{ $form->{TEMPLATE_ARRAYS}->{transdate_quo} },     $form->{"transdate_quo_$i"};
       push @{ $form->{TEMPLATE_ARRAYS}->{ordnumber_oe} },      $form->{"ordnumber_$i"};
+      push @{ $form->{TEMPLATE_ARRAYS}->{transdate_oe} },      $form->{"transdate_oe_$i"};
+      push @{ $form->{TEMPLATE_ARRAYS}->{cusordnumber_oe} },   $form->{"cusordnumber_oe_$i"};
       push @{ $form->{TEMPLATE_ARRAYS}->{donumber_do} },       $form->{"donumber_$i"};
-      push @{ $form->{TEMPLATE_ARRAYS}->{transdate_oe} },      $form->{"transdate_$i"};
+      push @{ $form->{TEMPLATE_ARRAYS}->{transdate_do} },      $form->{"transdate_do_$i"};
+
       push @{ $form->{TEMPLATE_ARRAYS}->{invnumber} },         $form->{"invnumber"};
       push @{ $form->{TEMPLATE_ARRAYS}->{invdate} },           $form->{"invdate"};
       push @{ $form->{TEMPLATE_ARRAYS}->{price_factor} },      $price_factor->{formatted_factor};
@@ -354,7 +428,7 @@ sub invoice_details {
           $sortorder = qq|ORDER BY a.oid|;
         }
 
-        $query =
+        my $query =
           qq|SELECT p.partnumber, p.description, p.unit, a.qty, pg.partsgroup
              FROM assembly a
              JOIN parts p ON (a.parts_id = p.id)
@@ -455,6 +529,7 @@ sub invoice_details {
 
   $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->{department}    = SL::DB::Manager::Department->find_by(id => $form->{department_id})->description if $form->{department_id};
 
   $form->{username} = $myconfig->{name};
 
@@ -792,7 +867,7 @@ sub post_invoice {
         UPDATE invoice SET trans_id = ?, position = ?, parts_id = ?, description = ?, longdescription = ?, qty = ?,
                            sellprice = ?, fxsellprice = ?, discount = ?, allocated = ?, assemblyitem = ?,
                            unit = ?, deliverydate = ?, project_id = ?, serialnumber = ?, pricegroup_id = ?,
-                           ordnumber = ?, donumber = ?, transdate = ?, cusordnumber = ?, base_qty = ?, subtotal = ?,
+                           base_qty = ?, subtotal = ?,
                            marge_percent = ?, marge_total = ?, lastcost = ?, active_price_source = ?, active_discount_source = ?,
                            price_factor_id = ?, price_factor = (SELECT factor FROM price_factors WHERE id = ?), marge_price_factor = ?
         WHERE id = ?
@@ -804,8 +879,7 @@ SQL
                  $form->{"discount_$i"}, $allocated, 'f',
                  $form->{"unit_$i"}, conv_date($form->{"reqdate_$i"}), conv_i($form->{"project_id_$i"}),
                  $form->{"serialnumber_$i"}, $pricegroup_id,
-                 $form->{"ordnumber_$i"}, $form->{"donumber_$i"}, conv_date($form->{"transdate_$i"}),
-                 $form->{"cusordnumber_$i"}, $baseqty, $form->{"subtotal_$i"} ? 't' : 'f',
+                 $baseqty, $form->{"subtotal_$i"} ? 't' : 'f',
                  $form->{"marge_percent_$i"}, $form->{"marge_absolut_$i"},
                  $form->{"lastcost_$i"},
                  $form->{"active_price_source_$i"}, $form->{"active_discount_source_$i"},
@@ -893,7 +967,14 @@ SQL
     }
   }
 
-  $form->{amount}{ $form->{id} }{ $form->{AR} } = $netamount + $tax;
+  # Invoice Summary includes Rounding
+  my $rounding = $form->round_amount(
+    $form->round_amount($netamount + $tax, 2, 1) - $form->round_amount($netamount + $tax, 2), 2
+  );
+  my $rnd_accno = $rounding == 0 ? 0
+                : $rounding > 0  ? $form->{rndgain_accno}
+                :                  $form->{rndloss_accno};
+  $form->{amount}{ $form->{id} }{ $form->{AR} } = $form->round_amount($netamount + $tax, 2, 1);
   $form->{paid} =
     $form->round_amount($form->{paid} * $form->{exchangerate} + $diff, 2);
 
@@ -998,6 +1079,14 @@ SQL
         do_query($form, $dbh, $query, @values);
       }
     }
+    if (!$payments_only && ($rnd_accno != 0)) {
+      $query =
+        qq|INSERT INTO acc_trans (trans_id, chart_id, amount, transdate, tax_id, taxkey, project_id, chart_link)
+             VALUES (?, (SELECT id FROM chart WHERE accno = ?), ?, ?, (SELECT id FROM tax WHERE taxkey=0), 0, ?, (SELECT link FROM chart WHERE accno = ?))|;
+      @values = (conv_i($trans_id), $rnd_accno, $rounding, conv_date($form->{invdate}), conv_i($project_id), $rnd_accno);
+      do_query($form, $dbh, $query, @values);
+      $rnd_accno = 0;
+    }
   }
 
   # deduct payment differences from diff
@@ -1160,7 +1249,7 @@ SQL
     return;
   }
 
-  $amount = $netamount + $tax;
+  $amount = $form->round_amount( $netamount + $tax, 2, 1);
 
   # save AR record
   #erweiterung fuer lieferscheinnummer (donumber) 12.02.09 jb
@@ -1170,7 +1259,7 @@ SQL
                 transdate   = ?, orddate       = ?, quodate       = ?, customer_id   = ?,
                 amount      = ?, netamount     = ?, paid          = ?,
                 duedate     = ?, deliverydate  = ?, invoice       = ?, shippingpoint = ?,
-                shipvia     = ?, terms         = ?, notes         = ?, intnotes      = ?,
+                shipvia     = ?,                    notes         = ?, intnotes      = ?,
                 currency_id = (SELECT id FROM currencies WHERE name = ?),
                 department_id = ?, payment_id    = ?, taxincluded   = ?,
                 type        = ?, language_id   = ?, taxzone_id    = ?, shipto_id     = ?,
@@ -1185,7 +1274,7 @@ SQL
              conv_date($form->{"invdate"}),  conv_date($form->{"orddate"}),    conv_date($form->{"quodate"}),    conv_i($form->{"customer_id"}),
                        $amount,                        $netamount,                       $form->{"paid"},
              conv_date($form->{"duedate"}),  conv_date($form->{"deliverydate"}),    '1',                                $form->{"shippingpoint"},
-                       $form->{"shipvia"},      conv_i($form->{"terms"}), $restricter->process($form->{"notes"}),       $form->{"intnotes"},
+                       $form->{"shipvia"},                                $restricter->process($form->{"notes"}),       $form->{"intnotes"},
                        $form->{"currency"},     conv_i($form->{"department_id"}), conv_i($form->{"payment_id"}),        $form->{"taxincluded"} ? 't' : 'f',
                        $form->{"type"},         conv_i($form->{"language_id"}),   conv_i($form->{"taxzone_id"}), conv_i($form->{"shipto_id"}),
                 conv_i($form->{"employee_id"}), conv_i($form->{"salesman_id"}),   conv_i($form->{storno_id}),           $form->{"storno"} ? 't' : 'f',
@@ -1798,7 +1887,9 @@ sub retrieve_invoice {
          (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_transdate}
        FROM defaults d|;
 
@@ -1816,18 +1907,23 @@ sub retrieve_invoice {
            a.invnumber, a.ordnumber, a.quonumber, a.cusordnumber,
            a.orddate, a.quodate, a.globalproject_id,
            a.transdate AS invdate, a.deliverydate, a.paid, a.storno, a.gldate,
-           a.shippingpoint, a.shipvia, a.terms, a.notes, a.intnotes, a.taxzone_id,
+           a.shippingpoint, a.shipvia, a.notes, a.intnotes, a.taxzone_id,
            a.duedate, a.taxincluded, (SELECT cu.name FROM currencies cu WHERE cu.id=a.currency_id) AS currency, a.shipto_id, a.cp_id,
            a.employee_id, a.salesman_id, a.payment_id,
+           a.mtime, a.itime,
            a.language_id, a.delivery_customer_id, a.delivery_vendor_id, a.type,
            a.transaction_description, a.donumber, a.invnumber_for_credit_note,
            a.marge_total, a.marge_percent, a.direct_debit, a.delivery_term_id,
+           dc.dunning_description,
            e.name AS employee
          FROM ar a
          LEFT JOIN employee e ON (e.id = a.employee_id)
+         LEFT JOIN dunning_config dc ON (a.dunning_config_id = dc.id)
          WHERE a.id = ?|;
     $ref = selectfirst_hashref_query($form, $dbh, $query, $id);
     map { $form->{$_} = $ref->{$_} } keys %{ $ref };
+    $form->{mtime} = $form->{itime} if !$form->{mtime};
+    $form->{lastmtime} = $form->{mtime};
 
     $form->{exchangerate} = $form->get_exchangerate($dbh, $form->{currency}, $form->{invdate}, "buy");
 
@@ -1963,45 +2059,39 @@ sub get_customer {
   my $dateformat = $myconfig->{dateformat};
   $dateformat .= "yy" if $myconfig->{dateformat} !~ /^y/;
 
-  my (@values, $duedate, $ref, $query);
-
-  if ($form->{invdate}) {
-    $duedate = "to_date(?, '$dateformat')";
-    push @values, $form->{invdate};
-  } else {
-    $duedate = "current_date";
-  }
+  my (@values, $ref, $query);
 
   my $cid = conv_i($form->{customer_id});
   my $payment_id;
 
-  if ($form->{payment_id}) {
-    $payment_id = "(pt.id = ?) OR";
-    push @values, conv_i($form->{payment_id});
-  }
-
   # get customer
   $query =
     qq|SELECT
-         c.id AS customer_id, c.name AS customer, c.discount as customer_discount, c.creditlimit, c.terms,
+         c.id AS customer_id, c.name AS customer, c.discount as customer_discount, c.creditlimit,
          c.email, c.cc, c.bcc, c.language_id, c.payment_id, c.delivery_term_id,
          c.street, c.zipcode, c.city, c.country,
          c.notes AS intnotes, c.klass as customer_klass, c.taxzone_id, c.salesman_id, cu.name AS curr,
          c.taxincluded_checked, c.direct_debit,
-         $duedate + COALESCE(pt.terms_netto, 0) AS duedate,
          b.discount AS tradediscount, b.description AS business
        FROM customer c
        LEFT JOIN business b ON (b.id = c.business_id)
-       LEFT JOIN payment_terms pt ON ($payment_id (c.payment_id = pt.id))
        LEFT JOIN currencies cu ON (c.currency_id=cu.id)
        WHERE c.id = ?|;
   push @values, $cid;
   $ref = selectfirst_hashref_query($form, $dbh, $query, @values);
 
   delete $ref->{salesman_id} if !$ref->{salesman_id};
+  delete $ref->{payment_id}  if $form->{payment_id};
 
   map { $form->{$_} = $ref->{$_} } keys %$ref;
 
+  if ($form->{payment_id}) {
+    my $reference_date = $form->{invdate} ? DateTime->from_kivitendo($form->{invdate}) : undef;
+    $form->{duedate}   = SL::DB::PaymentTerm->new(id => $form->{payment_id})->load->calc_date(reference_date => $reference_date)->to_kivitendo;
+  } else {
+    $form->{duedate}   = DateTime->today_local->to_kivitendo;
+  }
+
   # use customer currency
   $form->{currency} = $form->{curr};
 
@@ -2159,7 +2249,7 @@ sub retrieve_item {
     qq|SELECT
          p.id, p.partnumber, p.description, p.sellprice,
          p.listprice, p.inventory_accno_id, p.lastcost,
-         p.ean,
+         p.ean, p.notes,
 
          c1.accno AS inventory_accno,
          c1.new_chart_id AS inventory_new_chart,
@@ -2220,7 +2310,7 @@ sub retrieve_item {
       push @{ $ref->{matches} ||= [] }, $::locale->text('Model') . ': ' . join ', ', map { $_->{model} } @{ $mm_by_id{$ref->{id}} };
     }
 
-    if ($ref->{ean} eq $::form->{"partnumber_$i"}) {
+    if (($::form->{"partnumber_$i"} ne '') && ($ref->{ean} eq $::form->{"partnumber_$i"})) {
       push @{ $ref->{matches} ||= [] }, $::locale->text('EAN') . ': ' . $ref->{ean};
     }