Developer Recht und neue Menüeinträge für Testcontroller
[kivitendo-erp.git] / SL / Helper / ShippedQty.pm
index 5135823..2ac4e39 100644 (file)
@@ -3,17 +3,19 @@ package SL::Helper::ShippedQty;
 use strict;
 use parent qw(Rose::Object);
 
-use SL::AM;
+use Carp;
 use Scalar::Util qw(blessed);
-use SL::DBUtils qw(selectall_hashref_query selectall_as_map);
 use List::Util qw(min);
 use List::MoreUtils qw(any all uniq);
 use List::UtilsBy qw(partition_by);
+use SL::AM;
+use SL::DBUtils qw(selectall_hashref_query selectall_as_map);
 use SL::Locale::String qw(t8);
 
 use Rose::Object::MakeMethods::Generic (
   'scalar'                => [ qw(objects objects_or_ids shipped_qty keep_matches) ],
-  'scalar --get_set_init' => [ qw(oe_ids dbh require_stock_out fill_up item_identity_fields oi2oe oi_qty delivered matches) ],
+  'scalar --get_set_init' => [ qw(oe_ids dbh require_stock_out fill_up item_identity_fields oi2oe oi_qty delivered matches
+                                  services_deliverable) ],
 );
 
 my $no_stock_item_links_query = <<'';
@@ -25,6 +27,7 @@ my $no_stock_item_links_query = <<'';
   ORDER BY oi.trans_id, oi.position
 
 # oi not item linked. takes about 250ms for 100k hits
+# obsolete since 3.5.6
 my $fill_up_oi_query = <<'';
   SELECT oi.id, oi.trans_id, oi.position, oi.parts_id, oi.description, oi.reqdate, oi.serialnumber, oi.qty, oi.unit
   FROM orderitems oi
@@ -32,6 +35,7 @@ my $fill_up_oi_query = <<'';
   ORDER BY oi.trans_id, oi.position
 
 # doi linked by record, but not by items; 250ms for 100k hits
+# obsolete since 3.5.6
 my $no_stock_fill_up_doi_query = <<'';
   SELECT doi.id, doi.delivery_order_id, doi.position, doi.parts_id, doi.description, doi.reqdate, doi.serialnumber, doi.qty, doi.unit
   FROM delivery_order_items doi
@@ -102,7 +106,7 @@ my %item_identity_fields = (
 sub calculate {
   my ($self, $data) = @_;
 
-  die 'Need exactly one argument, either id, object or arrayref of ids or objects.' unless 2 == @_;
+  croak 'Need exactly one argument, either id, object or arrayref of ids or objects.' unless 2 == @_;
 
   $self->normalize_input($data);
 
@@ -207,7 +211,7 @@ sub calculate_fill_up {
 sub write_to {
   my ($self, $objects) = @_;
 
-  die 'expecting array of objects' unless 'ARRAY' eq ref $objects;
+  croak 'expecting array of objects' unless 'ARRAY' eq ref $objects;
 
   my $shipped_qty = $self->shipped_qty;
 
@@ -216,12 +220,13 @@ sub write_to {
       $obj->{shipped_qty} = $shipped_qty->{$obj->id} //= 0;
       $obj->{delivered}   = $shipped_qty->{$obj->id} == $obj->qty;
     } elsif ('SL::DB::Order' eq ref $obj) {
-      if (defined $obj->{orderitems}) {
-        $self->write_to($obj->{orderitems});
-        $obj->{delivered} = all { $_->{delivered} } @{ $obj->{orderitems} };
+      # load all orderitems unless not already loaded
+      $obj->orderitems unless (defined $obj->{orderitems});
+      $self->write_to($obj->{orderitems});
+      if ($self->services_deliverable) {
+        $obj->{delivered} = all { $_->{delivered} } grep { !$_->{optional} } @{ $obj->{orderitems} };
       } else {
-        # don't force a load on items. just compute by oe_id directly
-        $obj->{delivered} = $self->delivered->{$obj->id};
+        $obj->{delivered} = all { $_->{delivered} } grep { !$_->{optional} && !$_->part->is_service } @{ $obj->{orderitems} };
       }
     } else {
       die "unknown reference '@{[ ref $obj ]}' for @{[ __PACKAGE__ ]}::write_to";
@@ -235,7 +240,7 @@ sub write_to_objects {
 
   return unless @{ $self->oe_ids };
 
-  die 'Can only use write_to_objects, when calculate was called with objects. Use write_to instead.' unless $self->objects_or_ids;
+  croak 'Can only use write_to_objects, when calculate was called with objects. Use write_to instead.' unless $self->objects_or_ids;
 
   $self->write_to($self->objects);
 }
@@ -254,16 +259,33 @@ sub normalize_input {
   $self->objects_or_ids(!!blessed($data->[0]));
 
   if ($self->objects_or_ids) {
-    die 'unblessed object in data while expecting object' if any { !blessed($_) } @$data;
+    croak 'unblessed object in data while expecting object' if any { !blessed($_) } @$data;
     $self->objects($data);
   } else {
-    die 'object or reference in data while expecting ids' if any { ref($_) } @$data;
+    croak 'object or reference in data while expecting ids' if any { ref($_) } @$data;
+    croak 'ids need to be numbers'                          if any { ! ($_ * 1) } @$data;
     $self->oe_ids($data);
   }
 
   $self->shipped_qty({});
 }
 
+# some of the invocations never need to load all orderitems to copute their answers
+# delivered however needs oi_qty to be set for each orderitem to decide whether
+# delivered should be set or not.
+sub ensure_all_orderitems_for_orders {
+  my ($self) = @_;
+
+  return if $self->fill_up;
+
+  my $oi_query  = sprintf $fill_up_oi_query,   join (', ', ('?')x@{ $self->oe_ids });
+  my $oi  = selectall_hashref_query($::form, $self->dbh, $oi_query, @{ $self->oe_ids });
+  for (@$oi) {
+    $self->{oi_qty}{ $_->{id} } //= $_->{qty};
+    $self->{oi2oe}{ $_->{id} }  //= $_->{trans_id};
+  }
+}
+
 sub available_item_identity_fields {
   map { [ $_ => $item_identity_fields{$_} ] } @known_item_identity_fields;
 }
@@ -271,9 +293,9 @@ sub available_item_identity_fields {
 sub init_oe_ids {
   my ($self) = @_;
 
-  die 'oe_ids not initialized in id mode'            if !$self->objects_or_ids;
-  die 'objects not initialized before accessing ids' if $self->objects_or_ids && !defined $self->objects;
-  die 'objects need to be Order or OrderItem'        if any  {  ref($_) !~ /^SL::DB::Order(?:Item)?$/ } @{ $self->objects };
+  croak 'oe_ids not initialized in id mode'            if !$self->objects_or_ids;
+  croak 'objects not initialized before accessing ids' if $self->objects_or_ids && !defined $self->objects;
+  croak 'objects need to be Order or OrderItem'        if any  {  ref($_) !~ /^SL::DB::Order(?:Item)?$/ } @{ $self->objects };
 
   [ uniq map { ref($_) =~ /Item/ ? $_->trans_id : $_->id } @{ $self->objects } ]
 }
@@ -285,6 +307,10 @@ sub init_oi_qty { {} }
 sub init_matches { [] }
 sub init_delivered {
   my ($self) = @_;
+
+  # is needed in odyn
+  # $self->ensure_all_orderitems_for_orders;
+
   my $d = { };
   for (keys %{ $self->oi_qty }) {
     my $oe_id = $self->oi2oe->{$_};
@@ -298,6 +324,17 @@ sub init_require_stock_out    { $::instance_conf->get_shipped_qty_require_stock_
 sub init_item_identity_fields { [ grep $item_identity_fields{$_}, @{ $::instance_conf->get_shipped_qty_item_identity_fields } ] }
 sub init_fill_up              { $::instance_conf->get_shipped_qty_fill_up  }
 
+sub init_services_deliverable  {
+  my ($self) = @_;
+  if ($::form->{type} =~ m/^sales_/) {
+    $::instance_conf->get_sales_delivery_order_check_service;
+  } elsif ($::form->{type} =~ m/^purchase_/) {
+    $::instance_conf->get_purchase_delivery_order_check_service;
+  } else {
+    croak "wrong call, no customer or vendor object referenced";
+  }
+}
+
 1;
 
 __END__
@@ -325,7 +362,7 @@ SL::Helper::ShippedQty - Algorithmic module for calculating shipped qty
   $helper->calculate($oe_id);
   $helper->calculate(\@oe_ids);
 
-  # if these are items set elivered and shipped_qty
+  # if these are items set delivered and shipped_qty
   # if these are orders, iterate through their items and set delivered on order
   $helper->write_to($objects);
 
@@ -415,7 +452,9 @@ include a bulk mode to speed up multiple objects.
 
 =item C<new PARAMS>
 
-Creates a new helper object. PARAMS may include:
+Creates a new helper object, $::form->{type} is mandatory.
+
+PARAMS may include:
 
 =over 4
 
@@ -455,6 +494,7 @@ than this modules provides.
 
 See C<matches> for the returned format.
 
+
 =back
 
 =item C<calculate OBJECTS>