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