zu 72ab222ccb9b Testfall korrigiert
[kivitendo-erp.git] / t / wh / inventory.t
1 use strict;
2 use Test::Deep qw(cmp_deeply ignore superhashof);
3 use Test::More;
4 use Test::Exception;
5
6 use lib 't';
7
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);
11
12 use_ok 'Support::TestSetup';
13 use_ok 'SL::DB::Bin';
14 use_ok 'SL::DB::Part';
15 use_ok 'SL::DB::Warehouse';
16 use_ok 'SL::DB::Inventory';
17 use_ok 'SL::WH';
18 use_ok 'SL::Helper::Inventory';
19
20 Support::TestSetup::login();
21
22 my ($wh, $bin1, $bin2, $assembly1, $part1, $part2);
23
24 reset_db();
25 create_standard_stock();
26
27
28 # simple stock in, get_stock, get_onhand
29 set_stock(
30   part => $part1,
31   qty => 25,
32   bin => $bin1,
33 );
34
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');
37
38 # stock on some more, get_stock, get_onhand
39
40 WH->transfer({
41   parts_id          => $part1->id,
42   qty               => 15,
43   transfer_type     => 'stock',
44   dst_warehouse_id  => $bin1->warehouse_id,
45   dst_bin_id        => $bin1->id,
46   comment           => 'more',
47 });
48
49 WH->transfer({
50   parts_id          => $part1->id,
51   qty               => 20,
52   transfer_type     => 'stock',
53   chargenumber      => '298345',
54   dst_warehouse_id  => $bin1->warehouse_id,
55   dst_bin_id        => $bin1->id,
56   comment           => 'more',
57 });
58
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');
61
62 # allocate some stuff
63
64 my @allocations = SL::Helper::Inventory::allocate(
65   part => $part1,
66   qty  => 12,
67 );
68
69 is_deeply(\%{ $allocations[0] }, {
70    bestbefore        => undef,
71    bin_id            => $bin1->id,
72    chargenumber      => '',
73    parts_id          => $part1->id,
74    qty               => 12,
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');
79
80 # allocate something where more than one result will match
81
82 @allocations = SL::Helper::Inventory::allocate(
83   part => $part1,
84   qty  => 55,
85 );
86
87 is_deeply(\@allocations, [
88   {
89     bestbefore        => undef,
90     bin_id            => $bin1->id,
91     chargenumber      => '',
92     parts_id          => $part1->id,
93     qty               => '40.00000',
94     warehouse_id      => $wh->id,
95     comment           => undef,
96     for_object_id     => undef,
97   },
98   {
99     bestbefore        => undef,
100     bin_id            => $bin1->id,
101     chargenumber      => '298345',
102     parts_id          => $part1->id,
103     qty               => '15',
104     warehouse_id      => $wh->id,
105     comment           => undef,
106     for_object_id     => undef,
107   }
108 ], 'complex allocation works');
109
110 # try to allocate too much
111
112 dies_ok(sub {
113   SL::Helper::Inventory::allocate(part => $part1, qty => 100)
114 },
115 "allocate too much dies");
116
117 # produce something
118
119 reset_db();
120 create_standard_stock();
121
122 set_stock(
123   part => $part1,
124   qty => 5,
125   bin => $bin1,
126 );
127 set_stock(
128   part => $part2,
129   qty => 10,
130   bin => $bin1,
131 );
132
133
134 my @alloc1 = SL::Helper::Inventory::allocate(part => $part1, qty => 3);
135 my @alloc2 = SL::Helper::Inventory::allocate(part => $part2, qty => 3);
136
137 SL::Helper::Inventory::produce_assembly(
138   part          => $assembly1,
139   qty           => 3,
140   allocations => [ @alloc1, @alloc2 ],
141
142   # where to put it
143   bin          => $bin1,
144   chargenumber => "537",
145 );
146
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');
150
151 # produce the same using auto_allocation
152
153 reset_db();
154 create_standard_stock();
155
156 set_stock(
157   part => $part1,
158   qty => 5,
159   bin => $bin1,
160 );
161 set_stock(
162   part => $part2,
163   qty => 10,
164   bin => $bin1,
165 );
166
167 SL::Helper::Inventory::produce_assembly(
168   part          => $assembly1,
169   qty           => 3,
170   auto_allocate => 1,
171
172   # where to put it
173   bin          => $bin1,
174   chargenumber => "537",
175 );
176
177 is(SL::Helper::Inventory::get_stock(part => $assembly1), "3.00000", 'produce with auto allocation works');
178 is(SL::Helper::Inventory::get_stock(part => $part1), "2.00000", 'and consumes...');
179 is(SL::Helper::Inventory::get_stock(part => $part2), "7.00000", '..the materials');
180
181 # check comments and warehouses
182 $::form->{l_comment}        = 'Y';
183 $::form->{l_warehouse_from} = 'Y';
184 $::form->{l_warehouse_to}   = 'Y';
185 local $::instance_conf->data->{produce_assembly_same_warehouse} = 1;
186
187 my @contents = WH->get_warehouse_journal(sort => 'date');
188
189 cmp_deeply(\@contents,
190            [ ignore(), ignore(),
191               superhashof({
192                 'comment'        => 'Used for assembly '. $assembly1->partnumber .' Test Assembly',
193                 'warehouse_from' => 'Warehouse'
194               }),
195               superhashof({
196                 'comment'        => 'Used for assembly '. $assembly1->partnumber .' Test Assembly',
197                 'warehouse_from' => 'Warehouse'
198               }),
199               superhashof({
200                 'part_type'    => 'assembly',
201                 'warehouse_to' => 'Warehouse'
202               }),
203            ],
204           "Comments for assembly productions are ok"
205 );
206
207
208
209
210
211
212 # try to produce without allocations dies
213
214 dies_ok(sub {
215 SL::Helper::Inventory::produce_assembly(
216   part          => $assembly1,
217   qty           => 3,
218
219   # where to put it
220   bin          => $bin1,
221   chargenumber => "537",
222 );
223 }, "producing without allocations dies");
224
225 # try to produce with insufficient allocations dies
226
227 @alloc1 = SL::Helper::Inventory::allocate(part => $part1, qty => 1);
228 @alloc2 = SL::Helper::Inventory::allocate(part => $part2, qty => 1);
229
230 dies_ok(sub {
231 SL::Helper::Inventory::produce_assembly(
232   part          => $assembly1,
233   qty           => 3,
234   allocations => [ @alloc1, @alloc2 ],
235
236   # where to put it
237   bin          => $bin1,
238   chargenumber => "537",
239 );
240 }, "producing with insufficient allocations dies");
241
242
243
244 # bestbefore tests
245
246 reset_db();
247 create_standard_stock();
248
249 set_stock(
250   part => $part1,
251   qty => 5,
252   bin => $bin1,
253 );
254 set_stock(
255   part => $part2,
256   qty => 10,
257   bin => $bin1,
258 );
259
260
261
262 SL::Helper::Inventory::produce_assembly(
263   part          => $assembly1,
264   qty           => 3,
265   auto_allocate => 1,
266
267   bin               => $bin1,
268   chargenumber      => "537",
269   bestbefore        => DateTime->today->clone->add(days => -14), # expired 2 weeks ago
270   shippingdate      => DateTime->today->clone->add(days => 1),
271 );
272
273 is(SL::Helper::Inventory::get_stock(part => $assembly1), "3.00000", 'produce with bestbefore works');
274 is(SL::Helper::Inventory::get_onhand(part => $assembly1), "3.00000", 'produce with bestbefore works');
275 is(SL::Helper::Inventory::get_stock(
276   part       => $assembly1,
277   bestbefore => DateTime->today,
278 ), undef, 'get_stock with bestbefore date skips expired');
279 {
280   local $::instance_conf->data->{show_bestbefore} = 1;
281   is(SL::Helper::Inventory::get_onhand(
282     part       => $assembly1,
283   ), undef, 'get_onhand with bestbefore skips expired as of today');
284 }
285
286 {
287   local $::instance_conf->data->{show_bestbefore} = 0;
288   is(SL::Helper::Inventory::get_onhand(
289     part       => $assembly1,
290   ), "3.00000", 'get_onhand without bestbefore finds all');
291 }
292
293
294 sub reset_db {
295   SL::DB::Manager::Order->delete_all(all => 1);
296   SL::DB::Manager::Inventory->delete_all(all => 1);
297   SL::DB::Manager::Assembly->delete_all(all => 1);
298   SL::DB::Manager::Part->delete_all(all => 1);
299   SL::DB::Manager::Bin->delete_all(all => 1);
300   SL::DB::Manager::Warehouse->delete_all(all => 1);
301 }
302
303 sub create_standard_stock {
304   ($wh, $bin1) = create_warehouse_and_bins();
305   $bin2 = SL::DB::Bin->new(description => "Bin 2", warehouse => $wh)->save;
306   $wh->load;
307
308   $assembly1  =  new_assembly(number_of_parts => 2)->save;
309   ($part1, $part2) = map { $_->part } $assembly1->assemblies;
310 }
311
312
313 reset_db();
314
315 done_testing();
316
317 1;