2 use Test::Deep qw(cmp_deeply ignore superhashof);
 
   8 use SL::Dev::Part qw(new_part new_assembly new_service);
 
   9 use SL::Dev::Inventory qw(create_warehouse_and_bins set_stock);
 
  10 use SL::Dev::Record qw(create_sales_order);
 
  12 use_ok 'Support::TestSetup';
 
  14 use_ok 'SL::DB::Part';
 
  15 use_ok 'SL::DB::Warehouse';
 
  16 use_ok 'SL::DB::Inventory';
 
  18 use_ok 'SL::Helper::Inventory';
 
  20 Support::TestSetup::login();
 
  22 my ($wh, $bin1, $bin2, $assembly1, $assembly_service, $part1, $part2, $wh_moon, $bin_moon, $service1);
 
  26 create_standard_stock();
 
  29 # simple stock in, get_stock, get_onhand
 
  36 is(SL::Helper::Inventory::get_stock(part => $part1), "25.00000", 'simple get_stock works');
 
  37 is(SL::Helper::Inventory::get_onhand(part => $part1), "25.00000", 'simple get_onhand works');
 
  39 # stock on some more, get_stock, get_onhand
 
  42   parts_id          => $part1->id,
 
  44   transfer_type     => 'stock',
 
  45   dst_warehouse_id  => $bin1->warehouse_id,
 
  46   dst_bin_id        => $bin1->id,
 
  51   parts_id          => $part1->id,
 
  53   transfer_type     => 'stock',
 
  54   chargenumber      => '298345',
 
  55   dst_warehouse_id  => $bin1->warehouse_id,
 
  56   dst_bin_id        => $bin1->id,
 
  60 is(SL::Helper::Inventory::get_stock(part => $part1), "60.00000", 'normal get_stock works');
 
  61 is(SL::Helper::Inventory::get_onhand(part => $part1), "60.00000", 'normal get_onhand works');
 
  65 my @allocations = SL::Helper::Inventory::allocate(
 
  70 is_deeply(\%{ $allocations[0] }, {
 
  74    parts_id          => $part1->id,
 
  76    warehouse_id      => $wh->id,
 
  77    comment           => undef, # comment is not a partition so is not set by allocate
 
  78    for_object_id     => undef,
 
  79  }, 'allocation works');
 
  81 # allocate something where more than one result will match
 
  83 @allocations = SL::Helper::Inventory::allocate(
 
  88 is_deeply(\@allocations, [
 
  93     parts_id          => $part1->id,
 
  95     warehouse_id      => $wh->id,
 
  97     for_object_id     => undef,
 
 102     chargenumber      => '298345',
 
 103     parts_id          => $part1->id,
 
 105     warehouse_id      => $wh->id,
 
 107     for_object_id     => undef,
 
 109 ], 'complex allocation works');
 
 111 # try to allocate too much
 
 114   SL::Helper::Inventory::allocate(part => $part1, qty => 100)
 
 116 "allocate too much dies");
 
 121 create_standard_stock();
 
 135 my @alloc1 = SL::Helper::Inventory::allocate(part => $part1, qty => 3);
 
 136 my @alloc2 = SL::Helper::Inventory::allocate(part => $part2, qty => 3);
 
 138 SL::Helper::Inventory::produce_assembly(
 
 141   allocations => [ @alloc1, @alloc2 ],
 
 145   chargenumber => "537",
 
 148 is(SL::Helper::Inventory::get_stock(part => $assembly1), "3.00000", 'produce works');
 
 149 is(SL::Helper::Inventory::get_stock(part => $part1), "2.00000", 'and consumes...');
 
 150 is(SL::Helper::Inventory::get_stock(part => $part2), "7.00000", '..the materials');
 
 152 # produce the same using auto_allocation
 
 154 local $::locale = Locale->new('en');
 
 156 create_standard_stock();
 
 169 SL::Helper::Inventory::produce_assembly(
 
 176   chargenumber => "537",
 
 179 is(SL::Helper::Inventory::get_stock(part => $assembly1), "3.00000", 'produce with auto allocation works');
 
 180 is(SL::Helper::Inventory::get_stock(part => $part1), "2.00000", 'and consumes...');
 
 181 is(SL::Helper::Inventory::get_stock(part => $part2), "7.00000", '..the materials');
 
 183 # check comments and warehouses
 
 184 $::form->{l_comment}        = 'Y';
 
 185 $::form->{l_warehouse_from} = 'Y';
 
 186 $::form->{l_warehouse_to}   = 'Y';
 
 187 local $::instance_conf->data->{produce_assembly_same_warehouse} = 1;
 
 189 @contents = WH->get_warehouse_journal(sort => 'date');
 
 191 cmp_deeply(\@contents,
 
 192            [ ignore(), ignore(),
 
 194                 'comment'        => 'Used for assembly '. $assembly1->partnumber .' Test Assembly',
 
 195                 'warehouse_from' => 'Warehouse'
 
 198                 'comment'        => 'Used for assembly '. $assembly1->partnumber .' Test Assembly',
 
 199                 'warehouse_from' => 'Warehouse'
 
 202                 'part_type'    => 'assembly',
 
 203                 'warehouse_to' => 'Warehouse'
 
 206           "Comments for assembly productions are ok"
 
 209 # try to produce something for our lunar warehouse, but parts are only available on earth
 
 211 SL::Helper::Inventory::produce_assembly(
 
 217   chargenumber => "Lunar Dust inside",
 
 219 }, "producing for wrong warehouse dies");
 
 221 # same test, but check exception class
 
 223 SL::Helper::Inventory::produce_assembly(
 
 229   chargenumber => "Lunar Dust inside",
 
 231  } "SL::X::Inventory::Allocation", "producing for wrong warehouse throws correct error class";
 
 233 # same test, but check user feedback for the error message
 
 235 SL::Helper::Inventory::produce_assembly(
 
 241   chargenumber => "Lunar Dust inside",
 
 243  } qr/Part ap (1|2) Testpart (1|2) exists in warehouse Warehouse, but not in warehouse Our warehouse location at the moon/, "producing for wrong warehouse throws correct error message";
 
 245 # try to produce without allocations dies
 
 248 SL::Helper::Inventory::produce_assembly(
 
 254   chargenumber => "537",
 
 256 }, "producing without allocations dies");
 
 258 # try to produce with insufficient allocations dies
 
 260 @alloc1 = SL::Helper::Inventory::allocate(part => $part1, qty => 1);
 
 261 @alloc2 = SL::Helper::Inventory::allocate(part => $part2, qty => 1);
 
 264 SL::Helper::Inventory::produce_assembly(
 
 267   allocations => [ @alloc1, @alloc2 ],
 
 271   chargenumber => "537",
 
 273 }, "producing with insufficient allocations dies");
 
 276 # assembly with service default tests (services won't be consumed)
 
 278 local $::locale = Locale->new('en');
 
 280 create_standard_stock();
 
 293 SL::Helper::Inventory::produce_assembly(
 
 294   part          => $assembly_service,
 
 301 is(SL::Helper::Inventory::get_stock(part => $assembly_service), "1.00000", 'produce with auto allocation works');
 
 302 is(SL::Helper::Inventory::get_stock(part => $part1), "0.00000", 'and consumes...');
 
 303 is(SL::Helper::Inventory::get_stock(part => $part2), "0.00000", '..the materials');
 
 305 # check comments and warehouses
 
 306 $::form->{l_comment}        = 'Y';
 
 307 $::form->{l_warehouse_from} = 'Y';
 
 308 $::form->{l_warehouse_to}   = 'Y';
 
 309 local $::instance_conf->data->{produce_assembly_same_warehouse} = 1;
 
 311 @contents = WH->get_warehouse_journal(sort => 'date');
 
 313 cmp_deeply(\@contents,
 
 314            [ ignore(), ignore(),
 
 316                 'comment'        => 'Used for assembly '. $assembly_service->partnumber .' Ein Erzeugnis mit Dienstleistungen',
 
 317                 'warehouse_from' => 'Warehouse'
 
 320                 'comment'        => 'Used for assembly '. $assembly_service->partnumber .' Ein Erzeugnis mit Dienstleistungen',
 
 321                 'warehouse_from' => 'Warehouse'
 
 324                 'part_type'    => 'assembly',
 
 325                 'warehouse_to' => 'Warehouse'
 
 328           "Comments for assembly with service productions are ok"
 
 331 # assembly with service non default tests (services will be consumed)
 
 333 local $::instance_conf->data->{produce_assembly_transfer_service} = 1;
 
 347   SL::Helper::Inventory::produce_assembly(
 
 348     part          => $assembly_service,
 
 354 } qr/can not allocate 1,2 units of service number 1 We really need this service, missing 1,2 units/, "producing assembly with services and unstocked service throws correct error message";
 
 356 is(SL::Helper::Inventory::get_stock(part => $assembly_service), "1.00000", 'produce without service does not work');
 
 357 is(SL::Helper::Inventory::get_stock(part => $part1), "12.00000", 'and does not consume...');
 
 358 is(SL::Helper::Inventory::get_stock(part => $part2), "6.34000", '..the materials');
 
 361 # ok, now add the missing service
 
 362 is('SL::DB::Part', ref $service1);
 
 369 SL::Helper::Inventory::produce_assembly(
 
 370   part          => $assembly_service,
 
 377 is(SL::Helper::Inventory::get_stock(part => $assembly_service), "2.00000", 'produce with service does work if services is needed and stocked');
 
 378 is(SL::Helper::Inventory::get_stock(part => $part1), "0.00000", 'and does consume...');
 
 379 is(SL::Helper::Inventory::get_stock(part => $part2), "0.00000", '..the materials');
 
 380 is(SL::Helper::Inventory::get_stock(part => $service1), "0.00000", '..and service');
 
 382 # check comments and warehouses for assembly with service
 
 383 $::form->{l_comment}        = 'Y';
 
 384 $::form->{l_warehouse_from} = 'Y';
 
 385 $::form->{l_warehouse_to}   = 'Y';
 
 386 local $::instance_conf->data->{produce_assembly_same_warehouse} = 1;
 
 388 @contents = WH->get_warehouse_journal(sort => 'date');
 
 390 #diag("hier" . Dumper(@contents));
 
 391 cmp_deeply(\@contents,
 
 392          [ ignore(), ignore(), ignore(), ignore(), ignore(), ignore(), ignore(), ignore(),
 
 394                 'comment'        => 'Used for assembly '. $assembly_service->partnumber .' Ein Erzeugnis mit Dienstleistungen',
 
 395                 'warehouse_from' => 'Warehouse'
 
 398                 'comment'        => 'Used for assembly '. $assembly_service->partnumber .' Ein Erzeugnis mit Dienstleistungen',
 
 399                 'warehouse_from' => 'Warehouse'
 
 402                 'comment'        => 'Used for assembly '. $assembly_service->partnumber .' Ein Erzeugnis mit Dienstleistungen',
 
 403                 'warehouse_from' => 'Warehouse',
 
 404                 'part_type'      => 'service',
 
 408                 'part_type'    => 'assembly',
 
 409                 'warehouse_to' => 'Warehouse'
 
 412           "Comments for assembly with service productions are ok"
 
 420 create_standard_stock();
 
 435 SL::Helper::Inventory::produce_assembly(
 
 441   chargenumber      => "537",
 
 442   bestbefore        => DateTime->today->clone->add(days => -14), # expired 2 weeks ago
 
 443   shippingdate      => DateTime->today->clone->add(days => 1),
 
 446 is(SL::Helper::Inventory::get_stock(part => $assembly1), "3.00000", 'produce with bestbefore works');
 
 447 is(SL::Helper::Inventory::get_onhand(part => $assembly1), "3.00000", 'produce with bestbefore works');
 
 448 is(SL::Helper::Inventory::get_stock(
 
 450   bestbefore => DateTime->today,
 
 451 ), undef, 'get_stock with bestbefore date skips expired');
 
 453   local $::instance_conf->data->{show_bestbefore} = 1;
 
 454   is(SL::Helper::Inventory::get_onhand(
 
 456   ), undef, 'get_onhand with bestbefore skips expired as of today');
 
 460   local $::instance_conf->data->{show_bestbefore} = 0;
 
 461   is(SL::Helper::Inventory::get_onhand(
 
 463   ), "3.00000", 'get_onhand without bestbefore finds all');
 
 468   SL::DB::Manager::Order->delete_all(all => 1);
 
 469   SL::DB::Manager::Inventory->delete_all(all => 1);
 
 470   SL::DB::Manager::Assembly->delete_all(all => 1);
 
 471   SL::DB::Manager::Part->delete_all(all => 1);
 
 472   SL::DB::Manager::Bin->delete_all(all => 1);
 
 473   SL::DB::Manager::Warehouse->delete_all(all => 1);
 
 476 sub create_standard_stock {
 
 477   ($wh, $bin1)          = create_warehouse_and_bins();
 
 478   ($wh_moon, $bin_moon) = create_warehouse_and_bins(
 
 479       warehouse_description => 'Our warehouse location at the moon',
 
 480       bin_description       => 'Lunar crater',
 
 482   $bin2 = SL::DB::Bin->new(description => "Bin 2", warehouse => $wh)->save;
 
 485   $assembly1  =  new_assembly(number_of_parts => 2)->save;
 
 486   ($part1, $part2) = map { $_->part } $assembly1->assemblies;
 
 488   $service1 = new_service(partnumber  => "service number 1",
 
 489                           description => "We really need this service",
 
 492   push( @{$assembly_items}, SL::DB::Assembly->new(parts_id => $part1->id,
 
 496   push( @{$assembly_items}, SL::DB::Assembly->new(parts_id => $part2->id,
 
 500   push( @{$assembly_items}, SL::DB::Assembly->new(parts_id => $service1->id,
 
 504   $assembly_service  =  new_assembly(description    => 'Ein Erzeugnis mit Dienstleistungen',
 
 505                                      assembly_items => $assembly_items