Merge branch 'b-3.6.1' of ../kivitendo-erp_20220811
[kivitendo-erp.git] / SL / Controller / CsvImport / Inventory.pm
index 16debc0..e6f6794 100644 (file)
@@ -28,11 +28,15 @@ sub init_class {
   $self->class('SL::DB::Inventory');
 }
 
+sub set_profile_defaults {
+};
+
 sub init_profile {
   my ($self) = @_;
 
   my $profile = $self->SUPER::init_profile;
-  delete @{$profile}{qw(trans_id oe_id delivery_order_items_stock_id bestbefore trans_type_id project_id)};
+  delete @{$profile}{qw(trans_id oe_id delivery_order_items_stock_id trans_type_id project_id)};
+  delete @{$profile}{qw(bestbefore)}    if !$::instance_conf->get_show_bestbefore;
 
   return $profile;
 }
@@ -49,7 +53,8 @@ sub init_parts_by {
   my ($self) = @_;
 
   my $all_parts = SL::DB::Manager::Part->get_all;
-  return { map { my $col = $_; ( $col => { map { ( $_->$col => $_ ) } @{ $all_parts } } ) } qw(id partnumber ean description) };
+  return { map { my $col = $_; ( $col =>
+         { map { ( $_->$col => $_ ) } grep { defined $_->$col } @{ $all_parts } } ) } qw(id partnumber ean description) };
 }
 
 sub init_warehouses_by {
@@ -66,7 +71,6 @@ sub init_bins_by {
   my $bins_by;
   $bins_by->{_wh_id_and_id_ident()}          = { map { ( _wh_id_and_id_maker($_->warehouse_id, $_->id)                   => $_ ) } @{ $all_bins } };
   $bins_by->{_wh_id_and_description_ident()} = { map { ( _wh_id_and_description_maker($_->warehouse_id, $_->description) => $_ ) } @{ $all_bins } };
-
   return $bins_by;
 }
 
@@ -75,7 +79,7 @@ sub check_objects {
 
   $self->controller->track_progress(phase => 'building data', progress => 0);
 
-  my $i;
+  my $i = 0;
   my $num_data = scalar @{ $self->controller->data };
   foreach my $entry (@{ $self->controller->data }) {
     $self->controller->track_progress(progress => $i/$num_data * 100) if $i % 100 == 0;
@@ -113,6 +117,9 @@ sub setup_displayable_columns {
                                  { name => 'warehouse',    description => $::locale->text('Warehouse')               },
                                  { name => 'warehouse_id', description => $::locale->text('Warehouse (database ID)') },
                                 );
+  if ($::instance_conf->get_show_bestbefore) {
+    $self->add_displayable_columns({ name => 'bestbefore', description => $::locale->text('Best Before') });
+  }
 }
 
 sub check_warehouse {
@@ -120,6 +127,8 @@ sub check_warehouse {
 
   my $object = $entry->{object};
 
+  $self->settings->{apply_warehouse} ||= '';  # avoid warnings if undefined
+
   # If warehouse from front-end is enforced for all transfers, use this, if valid.
   if ($self->settings->{apply_warehouse} eq 'all') {
     $object->warehouse_id(undef);
@@ -145,7 +154,7 @@ sub check_warehouse {
     $object->warehouse_id($wh->id);
   }
 
-  # Check wether or not warehouse ID is valid.
+  # Check whether or not warehouse ID is valid.
   if ($object->warehouse_id && !$self->warehouses_by->{id}->{ $object->warehouse_id }) {
     push @{ $entry->{errors} }, $::locale->text('Error: Invalid warehouse');
     return 0;
@@ -178,6 +187,8 @@ sub check_bin {
 
   my $object = $entry->{object};
 
+  $self->settings->{apply_bin} ||= '';  # avoid warnings if undefined
+
   # If bin from front-end is enforced for all transfers, use this, if valid.
   if ($self->settings->{apply_bin} eq 'all') {
     $object->bin_id(undef);
@@ -203,14 +214,14 @@ sub check_bin {
     $object->bin_id($bin->id);
   }
 
-  # Check wether or not bin ID is valid.
+  # Check whether or not bin ID is valid.
   if ($object->bin_id && !$self->bins_by->{_wh_id_and_id_ident()}->{ _wh_id_and_id_maker($object->warehouse_id, $object->bin_id) }) {
     push @{ $entry->{errors} }, $::locale->text('Error: Invalid bin');
     return 0;
   }
-  
+
   # Map description to ID if given.
-  if (!$object->bin_id && $entry->{raw_data}->{bin}) {
+  if (!$object->bin_id && $entry->{raw_data}->{bin} && $object->warehouse_id) {
     my $bin = $self->bins_by->{_wh_id_and_description_ident()}->{ _wh_id_and_description_maker($object->warehouse_id, $entry->{raw_data}->{bin}) };
     if (!$bin) {
       push @{ $entry->{errors} }, $::locale->text('Error: Invalid bin');
@@ -235,7 +246,7 @@ sub check_part {
 
   my $object = $entry->{object};
 
-  # Check wether or non part ID is valid.
+  # Check whether or not part ID is valid.
   if ($object->parts_id && !$self->parts_by->{id}->{ $object->parts_id }) {
     push @{ $entry->{errors} }, $::locale->text('Error: Invalid part');
     return 0;
@@ -265,11 +276,20 @@ sub check_part {
 # This imports inventories when target_qty is given, transfers else.
 # So we get the actual qty in stock and transfer the difference in case of
 # a given target_qty
-sub check_qty{
+sub check_qty {
   my ($self, $entry) = @_;
 
   my $object = $entry->{object};
 
+  # parse qty (may be float values)
+  if (exists $entry->{raw_data}->{target_qty}) {
+    $entry->{raw_data}->{target_qty} = $::form->parse_amount(\%::myconfig, $entry->{raw_data}->{target_qty});
+    # $object->target_qty($entry->{raw_data}->{target_qty});
+  }
+  if (exists $entry->{raw_data}->{qty}) {
+    $entry->{raw_data}->{qty}        = $::form->parse_amount(\%::myconfig, $entry->{raw_data}->{qty});
+    $object->qty($entry->{raw_data}->{qty});
+  }
   if (! exists $entry->{raw_data}->{target_qty} && ! exists $entry->{raw_data}->{qty}) {
     push @{ $entry->{errors} }, $::locale->text('Error: A quantity or a target quantity must be given.');
     return 0;
@@ -286,16 +306,13 @@ sub check_qty{
   }
 
   # Actual quantity is read from stock or is the result of transfers for the
-  # same part, warehouse and bin done before.
-  my $key = join '+', $object->parts_id, $object->warehouse_id, $object->bin_id;
+  # same part, warehouse, bin, chargenumber and bestbefore date (if
+  # show_bestbefore is enabled) done before.
+  my $key = join '+', $object->parts_id, $object->warehouse_id, $object->bin_id, $object->chargenumber;
+  $key   .= join '+', $key, $object->bestbefore    if $::instance_conf->get_show_bestbefore;
+
   if (!exists $self->{resulting_quantities}->{$key}) {
-    my $stock = $object->part->get_simple_stock;
-    my @stocked = grep { $_->{warehouse_id} == $object->warehouse_id && $_->{bin_id} == $object->bin_id } @$stock;
-    my $stocked_qty = 0;
-    foreach (@stocked) {
-      $stocked_qty += $stocked[0]->{sum} * 1;
-    }
-    $self->{resulting_quantities}->{$key} = $stocked_qty;
+    $self->{resulting_quantities}->{$key} = _get_stocked_qty($object);
   }
   my $actual_qty = $self->{resulting_quantities}->{$key};
 
@@ -366,7 +383,7 @@ sub handle_employee {
 
   # employee from login if not given
   if (!$object->employee_id) {
-    $object->employee_id(SL::DB::Manager::Employee->find_by(login => $::myconfig{login})->id);
+    $object->employee_id(SL::DB::Manager::Employee->current->id) if SL::DB::Manager::Employee->current;
   }
 
   if ($object->employee_id) {
@@ -391,19 +408,46 @@ sub save_objects {
   my $data = $params{data} || $self->controller->data;
 
   foreach my $entry (@{ $data }) {
-    my ($trans_id) = selectrow_query($::form, $::form->get_standard_dbh, qq|SELECT nextval('id')|);
+    my ($trans_id) = selectrow_query($::form,$entry->{object}->db->dbh, qq|SELECT nextval('id')|);
     $entry->{object}->trans_id($trans_id);
   }
 
   $self->SUPER::save_objects(%params);
 }
 
+sub _get_stocked_qty {
+  my ($object) = @_;
+
+  my $bestbefore_filter  = '';
+  my $bestbefore_val_cnt = 0;
+  if ($::instance_conf->get_show_bestbefore) {
+    $bestbefore_filter  = ($object->bestbefore) ? 'AND bestbefore = ?' : 'AND bestbefore IS NULL';
+    $bestbefore_val_cnt = ($object->bestbefore) ? 1                    : 0;
+  }
+
+  my $query = <<SQL;
+    SELECT sum(qty) FROM inventory
+      WHERE parts_id = ? AND warehouse_id = ? AND bin_id = ? AND chargenumber = ? $bestbefore_filter
+      GROUP BY warehouse_id, bin_id, chargenumber
+SQL
+
+  my @values = ($object->parts_id,
+                $object->warehouse_id,
+                $object->bin_id,
+                $object->chargenumber);
+  push @values, $object->bestbefore if $bestbefore_val_cnt;
+
+  my ($stocked_qty) = selectrow_query($::form, $object->db->dbh, $query, @values);
+
+  return $stocked_qty;
+}
+
 sub _wh_id_and_description_ident {
   return 'wh_id+description';
 }
 
 sub _wh_id_and_description_maker {
-    return join '+', $_[0], $_[1]
+  return join '+', $_[0], $_[1]
 }
 
 sub _wh_id_and_id_ident {
@@ -411,7 +455,7 @@ sub _wh_id_and_id_ident {
 }
 
 sub _wh_id_and_id_maker {
-    return join '+', $_[0], $_[1]
+  return join '+', $_[0], $_[1]
 }
 
 1;