]> wagnertech.de Git - mfinanz.git/blobdiff - SL/DB/DeliveryOrder.pm
kivitendo 3.9.2-0.2
[mfinanz.git] / SL / DB / DeliveryOrder.pm
index 45d658876023f2efbe8890d5008e3cf09682b6e2..f95f9a10eff79a22c72bf57a3b434e566d62f228 100644 (file)
@@ -4,7 +4,7 @@ use strict;
 
 use Carp;
 
-use Rose::DB::Object::Helpers ();
+use Rose::DB::Object::Helpers qw(as_tree strip);
 
 use SL::DB::MetaSetup::DeliveryOrder;
 use SL::DB::Manager::DeliveryOrder;
@@ -12,14 +12,16 @@ use SL::DB::Helper::AttrHTML;
 use SL::DB::Helper::AttrSorted;
 use SL::DB::Helper::FlattenToForm;
 use SL::DB::Helper::LinkedRecords;
+use SL::DB::Helper::TypeDataProxy;
 use SL::DB::Helper::TransNumberGenerator;
-
-use SL::DB::Part;
-use SL::DB::Unit;
+use SL::DB::Helper::RecordLink qw(RECORD_ID RECORD_TYPE_REF);
 
 use SL::DB::DeliveryOrder::TypeData qw(:types);
+use SL::DB::Order::TypeData qw(:types);
+use SL::DB::Reclamation::TypeData qw(:types);
 
 use SL::Helper::Number qw(_format_total _round_total);
+use SL::Helper::ShippedQty;
 
 use List::Util qw(first);
 use List::MoreUtils qw(any pairwise);
@@ -44,6 +46,8 @@ __PACKAGE__->attr_html('notes');
 __PACKAGE__->attr_sorted('items');
 
 __PACKAGE__->before_save('_before_save_set_donumber');
+__PACKAGE__->after_save('_after_save_link_records');
+__PACKAGE__->after_save('_mark_orders_if_delivered');
 
 # hooks
 
@@ -55,6 +59,30 @@ sub _before_save_set_donumber {
   return 1;
 }
 
+sub _after_save_link_records {
+  my ($self) = @_;
+
+  my @allowed_record_sources = qw(SL::DB::Reclamation SL::DB::Order);
+  my @allowed_item_sources = qw(SL::DB::ReclamationItem SL::DB::OrderItem);
+
+  SL::DB::Helper::RecordLink::link_records(
+    $self,
+    \@allowed_record_sources,
+    \@allowed_item_sources,
+  );
+}
+
+sub _mark_orders_if_delivered {
+  my ($self) = @_;
+  my $orders = $self->linked_records(from => 'Order');
+  SL::Helper::ShippedQty->new->calculate($orders)->write_to_objects;
+  foreach my $order (@$orders) {
+    next if $order->is_sales != $self->is_sales;
+    $order->update_attributes(delivered => $order->{delivered});
+  }
+  return 1;
+}
+
 # methods
 
 sub items { goto &orderitems; }
@@ -75,11 +103,11 @@ sub sales_order {
     ],
   );
 
-  return first { $_->is_type('sales_order') } @{ $orders };
+  return first { $_->is_type(SALES_ORDER_TYPE()) } @{ $orders };
 }
 
 sub type {
-  goto &order_type;
+  goto &record_type;
 }
 
 sub is_type {
@@ -87,12 +115,8 @@ sub is_type {
 }
 
 sub displayable_type {
-  my $type = shift->type;
-
-  return $::locale->text('Sales Delivery Order')    if $type eq 'sales_delivery_order';
-  return $::locale->text('Purchase Delivery Order') if $type eq 'purchase_delivery_order';
-
-  die 'invalid type';
+  my ($self) = @_;
+  return $self->type_data->text('type');
 }
 
 sub displayable_name {
@@ -115,6 +139,22 @@ sub number {
   goto &donumber;
 }
 
+sub preceding_purchase_order_confirmations {
+  my ($self) = @_;
+
+  my @lrs = ();
+  if ($self->id) {
+    @lrs = grep { $_->record_type eq PURCHASE_ORDER_CONFIRMATION_TYPE() } @{$self->linked_records(from => 'SL::DB::Order')};
+  } else {
+    if ('SL::DB::Order' eq $self->{RECORD_TYPE_REF()}) {
+      my $order = SL::DB::Order->load_cached($self->{RECORD_ID()});
+      push @lrs, $order if $order->record_type eq PURCHASE_ORDER_CONFIRMATION_TYPE();
+    }
+  }
+
+  return \@lrs;
+}
+
 sub _clone_orderitem_cvar {
   my ($cvar) = @_;
 
@@ -124,68 +164,151 @@ sub _clone_orderitem_cvar {
   return $cloned;
 }
 
+sub convert_to_reclamation {
+  my ($self, %params) = @_;
+
+  $params{destination_type} = $self->is_sales ? SALES_RECLAMATION_TYPE()
+                                              : PURCHASE_RECLAMATION_TYPE();
+
+  my $reclamation = SL::DB::Reclamation->new_from($self, %params);
+
+  return $reclamation;
+}
+
 sub new_from {
   my ($class, $source, %params) = @_;
 
-  croak("Unsupported source object type '" . ref($source) . "'") unless ref($source) eq 'SL::DB::Order';
+  my %allowed_sources = map { $_ => 1 } qw(
+    SL::DB::Reclamation
+    SL::DB::Order
+    SL::DB::DeliveryOrder
+  );
+  unless( $allowed_sources{ref $source} ) {
+    croak("Unsupported source object type '" . ref($source) . "'");
+  }
 
-  my ($item_parent_id_column, $item_parent_column);
+  my %record_args = (
+    donumber => undef,
+    employee => SL::DB::Manager::Employee->current,
+    closed    => 0,
+    delivered => 0,
+    record_type => $params{destination_type},
+    transdate => DateTime->today_local,
+  );
 
-  if (ref($source) eq 'SL::DB::Order') {
-    $item_parent_id_column = 'trans_id';
-    $item_parent_column    = 'order';
+  if ( ref($source) eq 'SL::DB::Order' ) {
+    map{ ( $record_args{$_} = $source->$_ ) } # {{{ for vim folds
+    qw(
+      billing_address_id
+      cp_id
+      currency_id
+      cusordnumber
+      customer_id
+      delivery_term_id
+      department_id
+      globalproject_id
+      intnotes
+      language_id
+      notes
+      payment_id
+      reqdate
+      salesman_id
+      shippingpoint
+      shipvia
+      taxincluded
+      taxzone_id
+      transaction_description
+      vendor_confirmation_number
+      vendor_id
+    );
+    if ($source->record_type eq PURCHASE_ORDER_CONFIRMATION_TYPE()) {
+      $record_args{ordnumber} = join ' ', map { $_->ordnumber } @{$source->preceding_purchase_orders()};
+    } else {
+      $record_args{ordnumber} = $source->ordnumber;
+    }
+    # }}} for vim folds
+  } elsif ( ref($source) eq 'SL::DB::Reclamation' ) {
+    map{ ( $record_args{$_} = $source->$_ ) } # {{{ for vim folds
+    qw(
+      billing_address_id
+      currency_id
+      customer_id
+      delivery_term_id
+      department_id
+      globalproject_id
+      intnotes
+      language_id
+      notes
+      payment_id
+      reqdate
+      salesman_id
+      shippingpoint
+      shipvia
+      taxincluded
+      taxzone_id
+      transaction_description
+      vendor_id
+    );
+    $record_args{cp_id} = $source->contact_id;
+    $record_args{cusordnumber} = $source->cv_record_number;
+    $record_args{is_sales} = $source->is_sales;
+    # }}} for vim folds
+  } elsif ( ref($source) eq 'SL::DB::DeliveryOrder' ) {
+    map{ ( $record_args{$_} = $source->$_ ) } # {{{ for vim folds
+    qw(
+      billing_address_id
+      cp_id
+      currency_id
+      cusordnumber
+      customer_id
+      delivery_term_id
+      department_id
+      donumber
+      globalproject_id
+      intnotes
+      language_id
+      notes
+      ordnumber
+      oreqnumber
+      payment_id
+      reqdate
+      salesman_id
+      shippingpoint
+      shipto_id
+      shipvia
+      taxincluded
+      taxzone_id
+      transdate
+      transaction_description
+      vendor_confirmation_number
+      vendor_id
+    );
+    # }}} for vim folds
   }
 
-  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 billing_address_id
-                                             )),
-               closed    => 0,
-               delivered => 0,
-               order_type => $params{type},
-               transdate => DateTime->today_local,
-            );
-
   # Custom shipto addresses (the ones specific to the sales/purchase
   # record and not to the customer/vendor) are only linked from
   # shipto → delivery_orders. Meaning delivery_orders.shipto_id
   # will not be filled in that case.
   if (!$source->shipto_id && $source->id) {
-    $args{custom_shipto} = $source->custom_shipto->clone($class) if $source->can('custom_shipto') && $source->custom_shipto;
-
+    $record_args{custom_shipto} = $source->custom_shipto->clone($class) if $source->can('custom_shipto') && $source->custom_shipto;
   } else {
-    $args{shipto_id} = $source->shipto_id;
+    $record_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";
+  $record_args{record_type} //= $source->customer_id ? SALES_DELIVERY_ORDER_TYPE()
+                              : $source->vendor_id   ? PURCHASE_DELIVERY_ORDER_TYPE()
+                              : $source->is_sales    ? SALES_DELIVERY_ORDER_TYPE()
+                              : croak "need some way to set delivery order type from source";
 
-  my $delivery_order = $class->new(%args);
+  my $delivery_order = $class->new(%record_args);
   $delivery_order->assign_attributes(%{ $params{attributes} }) if $params{attributes};
-  my $items          = delete($params{items}) || $source->items_sorted;
-  my %item_parents;
-
-  # 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 };
-
-    $item_parents{$source_item_id} ||= $source_item->$item_parent_column;
-    my $item_parent                  = $item_parents{$source_item_id};
-
-    my $current_do_item = SL::DB::DeliveryOrderItem->new(map({ ( $_ => $source_item->$_ ) }
-                                         qw(base_qty cusordnumber description discount lastcost longdescription marge_price_factor parts_id price_factor price_factor_id
-                                            project_id qty reqdate sellprice serialnumber transdate unit active_discount_source active_price_source
-                                         )),
-                                   custom_variables => \@custom_variables,
-                                   ordnumber        => ref($item_parent) eq 'SL::DB::Order' ? $item_parent->ordnumber : $source_item->ordnumber,
-                                 );
-    $current_do_item->{"converted_from_orderitems_id"} = $_->{id} if ref($item_parent) eq 'SL::DB::Order';
-    $current_do_item;
-  } @{ $items };
+
+  my $items = delete($params{items}) || $source->items_sorted;
+  my @items = ( $delivery_order->is_type(SUPPLIER_DELIVERY_ORDER_TYPE()) && ref($source) ne 'SL::DB::Reclamation' ) ?
+                ()
+              : map { SL::DB::DeliveryOrderItem->new_from($_, %params) } @{ $items };
 
   @items = grep { $params{item_filter}->($_) } @items if $params{item_filter};
   @items = grep { $_->qty * 1 } @items if $params{skip_items_zero_qty};
@@ -193,11 +316,18 @@ sub new_from {
 
   $delivery_order->items(\@items);
 
+  unless ($params{no_linked_records}) {
+    $delivery_order->{ RECORD_ID() } = $source->id;
+    $delivery_order->{ RECORD_TYPE_REF() } = ref $source;
+  }
+
   return $delivery_order;
 }
 
 sub new_from_time_recordings {
   my ($class, $sources, %params) = @_;
+  require SL::DB::Part;
+  require SL::DB::Unit;
 
   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;
@@ -303,8 +433,7 @@ sub new_from_time_recordings {
 
   } else {
     my %args = (
-      is_sales    => 1,
-      order_type  => 'sales_delivery_order',
+      record_type => SALES_DELIVERY_ORDER_TYPE,
       delivered   => 0,
       customer_id => $sources->[0]->customer_id,
       taxzone_id  => $sources->[0]->customer->taxzone_id,
@@ -323,14 +452,14 @@ sub new_from_time_recordings {
 # 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");
+  if ($_[0]->record_type) {
+   return SL::DB::DeliveryOrder::TypeData::get3($_[0]->record_type, "properties", "is_customer");
   }
   return $_[0]{is_sales};
 }
 
 sub customervendor {
-  SL::DB::DeliveryOrder::TypeData::get3($_[0]->order_type, "properties", "is_customer") ? $_[0]->customer : $_[0]->vendor;
+  SL::DB::DeliveryOrder::TypeData::get3($_[0]->record_type, "properties", "is_customer") ? $_[0]->customer : $_[0]->vendor;
 }
 
 sub convert_to_invoice {
@@ -342,22 +471,6 @@ sub convert_to_invoice {
   if (!$self->db->with_transaction(sub {
     require SL::DB::Invoice;
     $invoice = SL::DB::Invoice->new_from($self, %params)->post || die;
-    $self->link_to_record($invoice);
-    # TODO extend link_to_record for items, otherwise long-term no d.r.y.
-    foreach my $item (@{ $invoice->items }) {
-      foreach (qw(delivery_order_items)) {    # expand if needed (orderitems)
-        if ($item->{"converted_from_${_}_id"}) {
-          die unless $item->{id};
-          RecordLinks->create_links('mode'       => 'ids',
-                                    'from_table' => $_,
-                                    'from_ids'   => $item->{"converted_from_${_}_id"},
-                                    'to_table'   => 'invoice',
-                                    'to_id'      => $item->{id},
-          ) || die;
-          delete $item->{"converted_from_${_}_id"};
-        }
-      }
-    }
     $self->update_attributes(closed => 1);
     1;
   })) {
@@ -376,6 +489,10 @@ sub digest {
     $self->date->to_kivitendo;
 }
 
+sub type_data {
+  SL::DB::Helper::TypeDataProxy->new(ref $_[0], $_[0]->type);
+}
+
 1;
 __END__