2 use Test::Deep qw(cmp_deeply ignore superhashof);
 
   8 use SL::Dev::Part qw(new_part new_assembly);
 
   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, $part1, $part2, $wh_moon, $bin_moon);
 
  25 create_standard_stock();
 
  28 # simple stock in, get_stock, get_onhand
 
  35 is(SL::Helper::Inventory::get_stock(part => $part1), "25.00000", 'simple get_stock works');
 
  36 is(SL::Helper::Inventory::get_onhand(part => $part1), "25.00000", 'simple get_onhand works');
 
  38 # stock on some more, get_stock, get_onhand
 
  41   parts_id          => $part1->id,
 
  43   transfer_type     => 'stock',
 
  44   dst_warehouse_id  => $bin1->warehouse_id,
 
  45   dst_bin_id        => $bin1->id,
 
  50   parts_id          => $part1->id,
 
  52   transfer_type     => 'stock',
 
  53   chargenumber      => '298345',
 
  54   dst_warehouse_id  => $bin1->warehouse_id,
 
  55   dst_bin_id        => $bin1->id,
 
  59 is(SL::Helper::Inventory::get_stock(part => $part1), "60.00000", 'normal get_stock works');
 
  60 is(SL::Helper::Inventory::get_onhand(part => $part1), "60.00000", 'normal get_onhand works');
 
  64 my @allocations = SL::Helper::Inventory::allocate(
 
  69 is_deeply(\%{ $allocations[0] }, {
 
  73    parts_id          => $part1->id,
 
  75    warehouse_id      => $wh->id,
 
  76    comment           => undef, # comment is not a partition so is not set by allocate
 
  77    for_object_id     => undef,
 
  78  }, 'allocation works');
 
  80 # allocate something where more than one result will match
 
  82 @allocations = SL::Helper::Inventory::allocate(
 
  87 is_deeply(\@allocations, [
 
  92     parts_id          => $part1->id,
 
  94     warehouse_id      => $wh->id,
 
  96     for_object_id     => undef,
 
 101     chargenumber      => '298345',
 
 102     parts_id          => $part1->id,
 
 104     warehouse_id      => $wh->id,
 
 106     for_object_id     => undef,
 
 108 ], 'complex allocation works');
 
 110 # try to allocate too much
 
 113   SL::Helper::Inventory::allocate(part => $part1, qty => 100)
 
 115 "allocate too much dies");
 
 120 create_standard_stock();
 
 134 my @alloc1 = SL::Helper::Inventory::allocate(part => $part1, qty => 3);
 
 135 my @alloc2 = SL::Helper::Inventory::allocate(part => $part2, qty => 3);
 
 137 SL::Helper::Inventory::produce_assembly(
 
 140   allocations => [ @alloc1, @alloc2 ],
 
 144   chargenumber => "537",
 
 147 is(SL::Helper::Inventory::get_stock(part => $assembly1), "3.00000", 'produce works');
 
 148 is(SL::Helper::Inventory::get_stock(part => $part1), "2.00000", 'and consumes...');
 
 149 is(SL::Helper::Inventory::get_stock(part => $part2), "7.00000", '..the materials');
 
 151 # produce the same using auto_allocation
 
 153 local $::locale = Locale->new('en');
 
 155 create_standard_stock();
 
 168 SL::Helper::Inventory::produce_assembly(
 
 175   chargenumber => "537",
 
 178 is(SL::Helper::Inventory::get_stock(part => $assembly1), "3.00000", 'produce with auto allocation works');
 
 179 is(SL::Helper::Inventory::get_stock(part => $part1), "2.00000", 'and consumes...');
 
 180 is(SL::Helper::Inventory::get_stock(part => $part2), "7.00000", '..the materials');
 
 182 # check comments and warehouses
 
 183 $::form->{l_comment}        = 'Y';
 
 184 $::form->{l_warehouse_from} = 'Y';
 
 185 $::form->{l_warehouse_to}   = 'Y';
 
 186 local $::instance_conf->data->{produce_assembly_same_warehouse} = 1;
 
 188 my @contents = WH->get_warehouse_journal(sort => 'date');
 
 190 cmp_deeply(\@contents,
 
 191            [ ignore(), ignore(),
 
 193                 'comment'        => 'Used for assembly '. $assembly1->partnumber .' Test Assembly',
 
 194                 'warehouse_from' => 'Warehouse'
 
 197                 'comment'        => 'Used for assembly '. $assembly1->partnumber .' Test Assembly',
 
 198                 'warehouse_from' => 'Warehouse'
 
 201                 'part_type'    => 'assembly',
 
 202                 'warehouse_to' => 'Warehouse'
 
 205           "Comments for assembly productions are ok"
 
 213 # try to produce something for our lunar warehouse, but parts are only available on earth
 
 215 SL::Helper::Inventory::produce_assembly(
 
 221   chargenumber => "Lunar Dust inside",
 
 223 }, "producing for wrong warehouse dies");
 
 225 # same test, but check exception class
 
 227 SL::Helper::Inventory::produce_assembly(
 
 233   chargenumber => "Lunar Dust inside",
 
 235  } "SL::X::Inventory::Allocation", "producing for wrong warehouse throws correct error class";
 
 237 # same test, but check user feedback for the error message
 
 239 SL::Helper::Inventory::produce_assembly(
 
 245   chargenumber => "Lunar Dust inside",
 
 247  } 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";
 
 249 # try to produce without allocations dies
 
 252 SL::Helper::Inventory::produce_assembly(
 
 258   chargenumber => "537",
 
 260 }, "producing without allocations dies");
 
 262 # try to produce with insufficient allocations dies
 
 264 @alloc1 = SL::Helper::Inventory::allocate(part => $part1, qty => 1);
 
 265 @alloc2 = SL::Helper::Inventory::allocate(part => $part2, qty => 1);
 
 268 SL::Helper::Inventory::produce_assembly(
 
 271   allocations => [ @alloc1, @alloc2 ],
 
 275   chargenumber => "537",
 
 277 }, "producing with insufficient allocations dies");
 
 284 create_standard_stock();
 
 299 SL::Helper::Inventory::produce_assembly(
 
 305   chargenumber      => "537",
 
 306   bestbefore        => DateTime->today->clone->add(days => -14), # expired 2 weeks ago
 
 307   shippingdate      => DateTime->today->clone->add(days => 1),
 
 310 is(SL::Helper::Inventory::get_stock(part => $assembly1), "3.00000", 'produce with bestbefore works');
 
 311 is(SL::Helper::Inventory::get_onhand(part => $assembly1), "3.00000", 'produce with bestbefore works');
 
 312 is(SL::Helper::Inventory::get_stock(
 
 314   bestbefore => DateTime->today,
 
 315 ), undef, 'get_stock with bestbefore date skips expired');
 
 317   local $::instance_conf->data->{show_bestbefore} = 1;
 
 318   is(SL::Helper::Inventory::get_onhand(
 
 320   ), undef, 'get_onhand with bestbefore skips expired as of today');
 
 324   local $::instance_conf->data->{show_bestbefore} = 0;
 
 325   is(SL::Helper::Inventory::get_onhand(
 
 327   ), "3.00000", 'get_onhand without bestbefore finds all');
 
 332   SL::DB::Manager::Order->delete_all(all => 1);
 
 333   SL::DB::Manager::Inventory->delete_all(all => 1);
 
 334   SL::DB::Manager::Assembly->delete_all(all => 1);
 
 335   SL::DB::Manager::Part->delete_all(all => 1);
 
 336   SL::DB::Manager::Bin->delete_all(all => 1);
 
 337   SL::DB::Manager::Warehouse->delete_all(all => 1);
 
 340 sub create_standard_stock {
 
 341   ($wh, $bin1)          = create_warehouse_and_bins();
 
 342   ($wh_moon, $bin_moon) = create_warehouse_and_bins(
 
 343       warehouse_description => 'Our warehouse location at the moon',
 
 344       bin_description       => 'Lunar crater',
 
 346   $bin2 = SL::DB::Bin->new(description => "Bin 2", warehouse => $wh)->save;
 
 349   $assembly1  =  new_assembly(number_of_parts => 2)->save;
 
 350   ($part1, $part2) = map { $_->part } $assembly1->assemblies;