SL::ShopConnector::WooCommerce Kategorien per page
[kivitendo-erp.git] / SL / DB / DeliveryOrder.pm
index 8c5efab..395a449 100644 (file)
@@ -20,7 +20,7 @@ use SL::DB::Unit;
 use SL::Helper::Number qw(_format_total _round_total);
 
 use List::Util qw(first);
 use SL::Helper::Number qw(_format_total _round_total);
 
 use List::Util qw(first);
-use List::MoreUtils qw(any);
+use List::MoreUtils qw(any pairwise);
 use Math::Round qw(nhimult);
 
 __PACKAGE__->meta->add_relationship(orderitems => { type         => 'one to many',
 use Math::Round qw(nhimult);
 
 __PACKAGE__->meta->add_relationship(orderitems => { type         => 'one to many',
@@ -185,39 +185,31 @@ sub new_from_time_recordings {
   croak("Unsupported object type in sources")                                      if any { ref($_) ne 'SL::DB::TimeRecording' }            @$sources;
   croak("Cannot create delivery order from source records of different customers") if any { $_->customer_id != $sources->[0]->customer_id } @$sources;
 
   croak("Unsupported object type in sources")                                      if any { ref($_) ne 'SL::DB::TimeRecording' }            @$sources;
   croak("Cannot create delivery order from source records of different customers") if any { $_->customer_id != $sources->[0]->customer_id } @$sources;
 
-  my %args = (
-    is_sales    => 1,
-    delivered   => 0,
-    customer_id => $sources->[0]->customer_id,
-    taxzone_id  => $sources->[0]->customer->taxzone_id,
-    currency_id => $sources->[0]->customer->currency_id,
-    employee_id => SL::DB::Manager::Employee->current->id,
-    salesman_id => SL::DB::Manager::Employee->current->id,
-    items       => [],
-  );
-  my $delivery_order = $class->new(%args);
-  $delivery_order->assign_attributes(%{ $params{attributes} }) if $params{attributes};
-
   # - one item per part (article)
   # - qty is sum of duration
   # - description goes to item longdescription
   #  - ordered and summed by date
   #  - each description goes to an ordered list
   #  - (as time recording descriptions are formatted text by now, use stripped text)
   # - one item per part (article)
   # - qty is sum of duration
   # - description goes to item longdescription
   #  - ordered and summed by date
   #  - each description goes to an ordered list
   #  - (as time recording descriptions are formatted text by now, use stripped text)
-  #  - merge same descriptions (todo)
+  #  - merge same descriptions
   #
 
   #
 
-  my $default_part_id = $params{default_part_id}    ? $params{default_part_id}
-                      : $params{default_partnumber} ? SL::DB::Manager::Part->find_by(partnumber => $params{default_partnumber})->id
-                      : undef;
+  my $default_part_id  = $params{default_part_id}     ? $params{default_part_id}
+                       : $params{default_partnumber}  ? SL::DB::Manager::Part->find_by(partnumber => $params{default_partnumber})->id
+                       : undef;
+  my $override_part_id = $params{override_part_id}    ? $params{override_part_id}
+                       : $params{override_partnumber} ? SL::DB::Manager::Part->find_by(partnumber => $params{override_partnumber})->id
+                       : undef;
 
   # check parts and collect entries
   my %part_by_part_id;
   my $entries;
   foreach my $source (@$sources) {
 
   # check parts and collect entries
   my %part_by_part_id;
   my $entries;
   foreach my $source (@$sources) {
-    my $part_id  = $source->part_id ? $source->part_id
-                 : $default_part_id ? $default_part_id
-                 : undef;
+    next if !$source->duration;
+
+    my $part_id   = $override_part_id;
+    $part_id    ||= $source->part_id;
+    $part_id    ||= $default_part_id;
 
     die 'article not found for entry "' . $source->displayable_times . '"' if !$part_id;
 
 
     die 'article not found for entry "' . $source->displayable_times . '"' if !$part_id;
 
@@ -226,7 +218,7 @@ sub new_from_time_recordings {
       die 'article unit must be time based for entry "' . $source->displayable_times . '"' if !$part_by_part_id{$part_id}->unit_obj->is_time_based;
     }
 
       die 'article unit must be time based for entry "' . $source->displayable_times . '"' if !$part_by_part_id{$part_id}->unit_obj->is_time_based;
     }
 
-    my $date = $source->start_time->to_kivitendo;
+    my $date = $source->date->to_kivitendo;
     $entries->{$part_id}->{$date}->{duration} += $params{rounding}
                                                ? nhimult(0.25, ($source->duration_in_hours))
                                                : _round_total($source->duration_in_hours);
     $entries->{$part_id}->{$date}->{duration} += $params{rounding}
                                                ? nhimult(0.25, ($source->duration_in_hours))
                                                : _round_total($source->duration_in_hours);
@@ -236,9 +228,11 @@ sub new_from_time_recordings {
     $entries->{$part_id}->{$date}->{content}  .= '<li>' . $new_description . '</li>'
       unless $entries->{$part_id}->{$date}->{content} =~ m/\Q$new_description/;
 
     $entries->{$part_id}->{$date}->{content}  .= '<li>' . $new_description . '</li>'
       unless $entries->{$part_id}->{$date}->{content} =~ m/\Q$new_description/;
 
-    $entries->{$part_id}->{$date}->{date_obj}  = $source->start_time; # for sorting
+    $entries->{$part_id}->{$date}->{date_obj}  = $source->start_time || $source->date; # for sorting
   }
 
   }
 
+  my @items;
+
   my $h_unit = SL::DB::Manager::Unit->find_h_unit;
 
   my @keys = sort { $part_by_part_id{$a}->partnumber cmp $part_by_part_id{$b}->partnumber } keys %$entries;
   my $h_unit = SL::DB::Manager::Unit->find_h_unit;
 
   my @keys = sort { $part_by_part_id{$a}->partnumber cmp $part_by_part_id{$b}->partnumber } keys %$entries;
@@ -261,13 +255,48 @@ sub new_from_time_recordings {
       parts_id        => $part_by_part_id{$key}->id,
       description     => $part_by_part_id{$key}->description,
       qty             => $qty,
       parts_id        => $part_by_part_id{$key}->id,
       description     => $part_by_part_id{$key}->description,
       qty             => $qty,
-      base_qty        => $qty,
+      base_qty        => $h_unit->convert_to($qty, $part_by_part_id{$key}->unit_obj),
       unit_obj        => $h_unit,
       unit_obj        => $h_unit,
-      sellprice       => $part_by_part_id{$key}->sellprice,
+      sellprice       => $part_by_part_id{$key}->sellprice, # Todo: use price rules to get sellprice
       longdescription => $longdescription,
     );
 
       longdescription => $longdescription,
     );
 
-    $delivery_order->add_items($item);
+    push @items, $item;
+  }
+
+  my $delivery_order;
+
+  if ($params{related_order}) {
+    # collect suitable items in related order
+    my @items_to_use;
+    my @new_attributes;
+    foreach my $item (@items) {
+      my $item_to_use = first {$item->parts_id == $_->parts_id} @{ $params{related_order}->items_sorted };
+
+      die "no suitable item found in related order" if !$item_to_use;
+
+      my %new_attributes;
+      $new_attributes{$_} = $item->$_ for qw(qty base_qty unit_obj longdescription);
+      push @items_to_use,   $item_to_use;
+      push @new_attributes, \%new_attributes;
+    }
+
+    $delivery_order = $class->new_from($params{related_order}, items => \@items_to_use, %params);
+    pairwise { $a->assign_attributes( %$b) } @{$delivery_order->items}, @new_attributes;
+
+  } else {
+    my %args = (
+      is_sales    => 1,
+      delivered   => 0,
+      customer_id => $sources->[0]->customer_id,
+      taxzone_id  => $sources->[0]->customer->taxzone_id,
+      currency_id => $sources->[0]->customer->currency_id,
+      employee_id => SL::DB::Manager::Employee->current->id,
+      salesman_id => SL::DB::Manager::Employee->current->id,
+      items       => \@items,
+    );
+    $delivery_order = $class->new(%args);
+    $delivery_order->assign_attributes(%{ $params{attributes} }) if $params{attributes};
   }
 
   return $delivery_order;
   }
 
   return $delivery_order;
@@ -408,7 +437,8 @@ Creates a new C<SL::DB::DeliveryOrder> instance from the time recordings
 given as C<$sources>. All time recording entries must belong to the same
 customer. Time recordings are sorted by article and date. For each article
 a new delivery order item is created. If no article is associated with an
 given as C<$sources>. All time recording entries must belong to the same
 customer. Time recordings are sorted by article and date. For each article
 a new delivery order item is created. If no article is associated with an
-entry, a default article will be used (hard coded).
+entry, a default article will be used. The article given in the time
+recording entry can be overriden.
 Entries of the same date (for each article) are summed together and form a
 list entry in the long description of the item.
 
 Entries of the same date (for each article) are summed together and form a
 list entry in the long description of the item.
 
@@ -425,6 +455,36 @@ C<%params> can include the following options:
 An optional hash reference. If it exists then it is used to set
 attributes of the newly created delivery order object.
 
 An optional hash reference. If it exists then it is used to set
 attributes of the newly created delivery order object.
 
+=item C<default_part_id>
+
+An optional part id which is used as default value if no part is set
+in the time recording entry.
+
+=item C<default_partnumber>
+
+Like C<default_part_id> but given as partnumber, not as id.
+
+=item C<override_part_id>
+
+An optional part id which is used instead of a value set in the time
+recording entry.
+
+=item C<override_partnumber>
+
+Like C<overrride_part_id> but given as partnumber, not as id.
+
+=item C<related_order>
+
+An optional C<SL::DB::Order> object. If it exists then it is used to
+generate the delivery order from that via C<new_from>.
+The generated items are created from a suitable item of the related
+order. If no suitable item is found, an exception is thrown.
+
+=item C<rounding>
+
+An optional boolean value. If truish, then the durations of the time entries
+are rounded up to the full quarters of an hour.
+
 =back
 
 =item C<sales_order>
 =back
 
 =item C<sales_order>