Merge branch 'b-3.6.1' into mebil
[kivitendo-erp.git] / SL / DB / DeliveryOrder.pm
index f082db6..45d6588 100644 (file)
@@ -17,6 +17,8 @@ use SL::DB::Helper::TransNumberGenerator;
 use SL::DB::Part;
 use SL::DB::Unit;
 
 use SL::DB::Part;
 use SL::DB::Unit;
 
+use SL::DB::DeliveryOrder::TypeData qw(:types);
+
 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);
@@ -77,7 +79,11 @@ sub sales_order {
 }
 
 sub type {
 }
 
 sub type {
-  return shift->customer_id ? 'sales_delivery_order' : 'purchase_delivery_order';
+  goto &order_type;
+}
+
+sub is_type {
+  return shift->type eq shift;
 }
 
 sub displayable_type {
 }
 
 sub displayable_type {
@@ -105,6 +111,10 @@ sub date {
   goto &transdate;
 }
 
   goto &transdate;
 }
 
+sub number {
+  goto &donumber;
+}
+
 sub _clone_orderitem_cvar {
   my ($cvar) = @_;
 
 sub _clone_orderitem_cvar {
   my ($cvar) = @_;
 
@@ -127,11 +137,11 @@ sub new_from {
   }
 
   my %args = ( map({ ( $_ => $source->$_ ) } qw(cp_id currency_id customer_id cusordnumber delivery_term_id department_id employee_id globalproject_id intnotes language_id notes
   }
 
   my %args = ( map({ ( $_ => $source->$_ ) } qw(cp_id currency_id customer_id cusordnumber delivery_term_id department_id employee_id globalproject_id intnotes language_id notes
-                                                ordnumber payment_id reqdate salesman_id shippingpoint shipvia taxincluded taxzone_id transaction_description vendor_id
+                                                ordnumber payment_id reqdate salesman_id shippingpoint shipvia taxincluded taxzone_id transaction_description vendor_id billing_address_id
                                              )),
                closed    => 0,
                                              )),
                closed    => 0,
-               is_sales  => !!$source->customer_id,
                delivered => 0,
                delivered => 0,
+               order_type => $params{type},
                transdate => DateTime->today_local,
             );
 
                transdate => DateTime->today_local,
             );
 
@@ -146,12 +156,19 @@ sub new_from {
     $args{shipto_id} = $source->shipto_id;
   }
 
     $args{shipto_id} = $source->shipto_id;
   }
 
+  # infer type from legacy fields if not given
+  $args{order_type} //= $source->customer_id ? 'sales_delivery_order'
+                      : $source->vendor_id   ? 'purchase_delivery_order'
+                      : $source->is_sales    ? 'sales_delivery_order'
+                      : croak "need some way to set delivery order type from source";
+
   my $delivery_order = $class->new(%args);
   $delivery_order->assign_attributes(%{ $params{attributes} }) if $params{attributes};
   my $items          = delete($params{items}) || $source->items_sorted;
   my %item_parents;
 
   my $delivery_order = $class->new(%args);
   $delivery_order->assign_attributes(%{ $params{attributes} }) if $params{attributes};
   my $items          = delete($params{items}) || $source->items_sorted;
   my %item_parents;
 
-  my @items = map {
+  # do not copy items when converting to supplier delivery order
+  my @items = $delivery_order->is_type(SUPPLIER_DELIVERY_ORDER_TYPE) ? () : map {
     my $source_item      = $_;
     my $source_item_id   = $_->$item_parent_id_column;
     my @custom_variables = map { _clone_orderitem_cvar($_) } @{ $source_item->custom_variables };
     my $source_item      = $_;
     my $source_item_id   = $_->$item_parent_id_column;
     my @custom_variables = map { _clone_orderitem_cvar($_) } @{ $source_item->custom_variables };
@@ -191,12 +208,15 @@ sub new_from_time_recordings {
   #  - ordered and summed by date
   #  - each description goes to an ordered list
   #  - (as time recording descriptions are formatted text by now, use stripped text)
   #  - 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;
 
   # check parts and collect entries
   my %part_by_part_id;
@@ -204,9 +224,9 @@ sub new_from_time_recordings {
   foreach my $source (@$sources) {
     next if !$source->duration;
 
   foreach my $source (@$sources) {
     next if !$source->duration;
 
-    my $part_id  = $source->part_id ? $source->part_id
-                 : $default_part_id ? $default_part_id
-                 : undef;
+    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;
 
@@ -284,6 +304,7 @@ sub new_from_time_recordings {
   } else {
     my %args = (
       is_sales    => 1,
   } else {
     my %args = (
       is_sales    => 1,
+      order_type  => 'sales_delivery_order',
       delivered   => 0,
       customer_id => $sources->[0]->customer_id,
       taxzone_id  => $sources->[0]->customer->taxzone_id,
       delivered   => 0,
       customer_id => $sources->[0]->customer_id,
       taxzone_id  => $sources->[0]->customer->taxzone_id,
@@ -299,8 +320,17 @@ sub new_from_time_recordings {
   return $delivery_order;
 }
 
   return $delivery_order;
 }
 
+# legacy for compatibility
+# use type_data cusomtervendor and transfer direction instead
+sub is_sales {
+  if ($_[0]->order_type) {
+   return SL::DB::DeliveryOrder::TypeData::get3($_[0]->order_type, "properties", "is_customer");
+  }
+  return $_[0]{is_sales};
+}
+
 sub customervendor {
 sub customervendor {
-  $_[0]->is_sales ? $_[0]->customer : $_[0]->vendor;
+  SL::DB::DeliveryOrder::TypeData::get3($_[0]->order_type, "properties", "is_customer") ? $_[0]->customer : $_[0]->vendor;
 }
 
 sub convert_to_invoice {
 }
 
 sub convert_to_invoice {
@@ -434,7 +464,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.
 
@@ -451,6 +482,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>