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