Merge branch 'test' of ../kivitendo-erp_20220811
[kivitendo-erp.git] / SL / Helper / Inventory.pm
index da3e5b1..944a410 100644 (file)
@@ -197,8 +197,8 @@ sub allocate {
   }
   if ($rest_qty > 0) {
     die SL::X::Inventory::Allocation->new(
-      error => 'not enough to allocate',
-      msg => t8("can not allocate #1 units of #2, missing #3 units", _format_number($qty), $part->displayable_name, _format_number($rest_qty)),
+      code    => 'not enough to allocate',
+      message => t8("can not allocate #1 units of #2, missing #3 units", _format_number($qty), $part->displayable_name, _format_number($rest_qty)),
     );
   } else {
     if ($params{constraints}) {
@@ -213,12 +213,17 @@ sub allocate_for_assembly {
 
   my $part = $params{part} or Carp::croak('allocate needs a part');
   my $qty  = $params{qty}  or Carp::croak('allocate needs a qty');
+  my $wh   = $params{warehouse};
+  my $wh_strict       = $::instance_conf->get_produce_assembly_same_warehouse;
+  my $consume_service = $::instance_conf->get_produce_assembly_transfer_service;
 
-  Carp::croak('not an assembly') unless $part->is_assembly;
+  Carp::croak('not an assembly')       unless $part->is_assembly;
+  Carp::croak('No warehouse selected') if $wh_strict && !$wh;
 
   my %parts_to_allocate;
 
   for my $assembly ($part->assemblies) {
+    next if $assembly->part->type eq 'service' && !$consume_service;
     $parts_to_allocate{ $assembly->part->id } //= 0;
     $parts_to_allocate{ $assembly->part->id } += $assembly->qty * $qty;
   }
@@ -228,6 +233,15 @@ sub allocate_for_assembly {
   for my $part_id (keys %parts_to_allocate) {
     my $part = SL::DB::Part->load_cached($part_id);
     push @allocations, allocate(%params, part => $part, qty => $parts_to_allocate{$part_id});
+    if ($wh_strict) {
+      die SL::X::Inventory::Allocation->new(
+        code    => "wrong warehouse for part",
+        message => t8('Part #1 exists in warehouse #2, but not in warehouse #3 ',
+                        $part->partnumber . ' ' . $part->description,
+                        SL::DB::Manager::Warehouse->find_by(id => $allocations[-1]->{warehouse_id})->description,
+                        $wh->description),
+      ) unless $allocations[-1]->{warehouse_id} == $wh->id;
+    }
   }
 
   @allocations;
@@ -238,8 +252,8 @@ sub check_constraints {
   if ('CODE' eq ref $constraints) {
     if (!$constraints->(@$allocations)) {
       die SL::X::Inventory::Allocation->new(
-        error => 'allocation constraints failure',
-        msg => t8("Allocations didn't pass constraints"),
+        code    => 'allocation constraints failure',
+        message => t8("Allocations didn't pass constraints"),
       );
     }
   } else {
@@ -272,8 +286,8 @@ sub check_constraints {
               SL::DB::Bin->load_cached($_->bin_id)->full_description,
               _format_number($_->qty), _format_number($needed), $_->chargenumber ? $_->chargenumber : '--') for @allocs;
         die SL::X::Inventory::Allocation->new(
-          error => 'allocation constraints failure',
-          msg   => $err,
+          code    => 'allocation constraints failure',
+          message => $err,
         );
       }
     }
@@ -285,32 +299,31 @@ sub produce_assembly {
 
   my $part = $params{part} or Carp::croak('produce_assembly needs a part');
   my $qty  = $params{qty}  or Carp::croak('produce_assembly needs a qty');
+  my $bin  = $params{bin}  or Carp::croak("need target bin");
 
   my $allocations = $params{allocations};
+  my $strict_wh = $::instance_conf->get_produce_assembly_same_warehouse ? $bin->warehouse : undef;
   if ($params{auto_allocate}) {
     Carp::croak("produce_assembly: can't have both allocations and auto_allocate") if $params{allocations};
-    $allocations = [ allocate_for_assembly(part => $part, qty => $qty) ];
+    $allocations = [ allocate_for_assembly(part => $part, qty => $qty, warehouse => $strict_wh, chargenumber => $params{chargenumber}) ];
   } else {
     Carp::croak("produce_assembly: need allocations or auto_allocate to produce something") if !$params{allocations};
     $allocations = $params{allocations};
   }
 
-  my $bin          = $params{bin} or Carp::croak("need target bin");
-  my $chargenumber = $params{chargenumber};
-  my $bestbefore   = $params{bestbefore};
+  my $chargenumber  = $params{chargenumber};
+  my $bestbefore    = $params{bestbefore};
   my $for_object_id = $params{for_object_id};
-  my $comment      = $params{comment} // '';
-
-  my $invoice               = $params{invoice};
-  my $project               = $params{project};
+  my $comment       = $params{comment} // '';
+  my $invoice       = $params{invoice};
+  my $project       = $params{project};
+  my $shippingdate  = $params{shippingsdate} // DateTime->now_local;
+  my $trans_id      = $params{trans_id};
 
-  my $shippingdate = $params{shippingsdate} // DateTime->now_local;
-
-  my $trans_id              = $params{trans_id};
   ($trans_id) = selectrow_query($::form, SL::DB->client->dbh, qq|SELECT nextval('id')| ) unless $trans_id;
 
   my $trans_type_out = SL::DB::Manager::TransferType->find_by(direction => 'out', description => 'used');
-  my $trans_type_in  = SL::DB::Manager::TransferType->find_by(direction => 'in', description => 'assembled');
+  my $trans_type_in  = SL::DB::Manager::TransferType->find_by(direction => 'in',  description => 'assembled');
 
   # check whether allocations are sane
   if (!$params{no_check_allocations} && !$params{auto_allocate}) {
@@ -319,7 +332,10 @@ sub produce_assembly {
       $allocations_by_part{ $assembly->parts_id } -= $assembly->qty * $qty;
     }
 
-    die "allocations are insufficient for production" if any { $_ < 0 } values %allocations_by_part;
+    die SL::X::Inventory::Allocation->new(
+      code    => "allocations are insufficient for production",
+      message => t8('can not allocate enough resources for production'),
+    ) if any { $_ < 0 } values %allocations_by_part;
   }
 
   my @transfers;
@@ -331,6 +347,7 @@ sub produce_assembly {
       trans_type   => $trans_type_out,
       shippingdate => $shippingdate,
       employee     => SL::DB::Manager::Employee->current,
+      comment      => t8('Used for assembly #1 #2', $part->partnumber, $part->description),
     );
   }