Merge branch 'test' of ../kivitendo-erp_20220811
[kivitendo-erp.git] / t / controllers / csvimport / inventory.t
1 use strict;
2
3 use Data::Dumper; # maybe in Tests available?
4 use Test::Deep qw(cmp_deeply superhashof ignore);
5 use Test::More;
6 use Test::Exception;
7
8 use lib 't';
9
10 use SL::Dev::Part qw(new_part new_assembly new_service);
11 use SL::Dev::Inventory qw(create_warehouse_and_bins set_stock);
12
13 use_ok 'Support::TestSetup';
14 use_ok 'SL::Controller::CsvImport';
15 use_ok 'SL::DB::Bin';
16 use_ok 'SL::DB::Part';
17 use_ok 'SL::DB::Warehouse';
18 use_ok 'SL::DB::Inventory';
19 use_ok 'SL::WH';
20 use_ok 'SL::Helper::Inventory';
21
22 Support::TestSetup::login();
23
24 my ($wh, $bin1, $bin2, $assembly1, $assembly_service, $part1, $part2, $wh_moon, $bin_moon, $service1);
25
26 sub reset_state {
27   # Create test data
28
29   clear_up();
30   create_standard_stock();
31
32 }
33 reset_state();
34
35 #####
36 sub test_import {
37   my ($file,$settings) = @_;
38
39   my $controller = SL::Controller::CsvImport->new(
40     type => 'inventories'
41   );
42   $controller->load_default_profile;
43   $controller->profile->set(
44     charset      => 'utf-8',
45     sep_char     => ',',
46     quote_char   => '"',
47     numberformat => $::myconfig{numberformat},
48   );
49   my $csv_inventory_import = SL::Controller::CsvImport::Inventory->new(
50     settings   => $settings,
51     controller => $controller,
52     file       => $file,
53   );
54   #print "profile param type=".$csv_part_import->settings->{parts_type}."\n";
55
56   $csv_inventory_import->run(test => 0);
57
58   # don't try and save objects that have errors
59   $csv_inventory_import->save_objects unless scalar @{$csv_inventory_import->controller->data->[0]->{errors}};
60
61   return $csv_inventory_import->controller->data;
62 }
63
64 $::myconfig{numberformat} = '1000.00';
65 my $old_locale = $::locale;
66 # set locale to en so we can match errors
67 $::locale = Locale->new('en');
68
69
70 my ($entries, $entry, $file);
71
72 # different settings for tests
73 #
74
75 my $settings1 = {
76                   apply_comment => 'missing',
77                   comment       => 'Lager Inventur Standard',
78                 };
79 #
80 #
81 # starting test of csv imports
82 # to debug errors in certain tests, run after test_import:
83 #   die Dumper($entry->{errors});
84
85
86 ##### create complete bullshit
87 $file = \<<EOL;
88 bin,chargenumber,comment,employee_id,partnumber,qty,shippingdate,target_qty,warehouse
89 P1000;100.10;90.20;95.30;kg;111.11;122.22;133.33
90 EOL
91 $entries = test_import($file, $settings1);
92 $entry = $entries->[0];
93 is scalar @{ $entry->{errors} }, 3, "Three errors occurred";
94
95 cmp_deeply(\@{ $entry->{errors} }, [
96                                     'Error: Warehouse not found',
97                                     'Error: Bin not found',
98                                     'Error: Part not found'
99                                    ],
100           "Errors for bullshit import are ok"
101 );
102
103 ##### create minor bullshit
104 $file = \<<EOL;
105 warehouse,bin,partnumber,qty,chargenumber,comment,employee_id,qty,shippingdate,target_qty
106 Warehouse,"Bin 1","ap 1",3.4
107 EOL
108
109 $entries = test_import($file, $settings1);
110 $entry = $entries->[0];
111 is scalar @{ $entry->{errors} }, 1, "One error for minor bullshit occurred";
112
113 cmp_deeply(\@{ $entry->{errors} }, [
114                                     'Error: A quantity and a target quantity could not be given both.'
115                                    ],
116           "Error for minor bullshit import are ok"
117 );
118
119
120 ##### add some qty on earth, but we have something already stocked
121 set_stock(
122   part => $part1,
123   qty => 25,
124   bin => $bin1,
125 );
126
127 is(SL::Helper::Inventory::get_stock(part => $part1), "25.00000", 'simple get_stock works');
128 is(SL::Helper::Inventory::get_onhand(part => $part1), "25.00000", 'simple get_onhand works');
129
130 my ($trans_id, $inv_obj, $tt);
131 # add some stuff
132
133 $file = \<<EOL;
134 warehouse,bin,partnumber,qty,chargenumber,comment,employee_id,shippingdate
135 Warehouse,"Bin 1","ap 1",3.4
136 EOL
137 $entries = test_import($file, $settings1);
138 $entry = $entries->[0];
139 is scalar @{ $entry->{errors} }, 0, "No error for valid data occurred";
140 is $entry->{object}->qty, "3.4", "Valid qty accepted";  # evals to text
141 is(SL::Helper::Inventory::get_stock(part => $part1),  "28.40000",  'simple add (stock) qty works');
142 is(SL::Helper::Inventory::get_onhand(part => $part1), "28.40000", 'simple add (onhand) qty works');
143
144 # now check the real Inventory entry
145 $trans_id = $entry->{object}->trans_id;
146 $inv_obj = SL::DB::Manager::Inventory->find_by(trans_id => $trans_id);
147
148 # we expect one entry for one trans_id
149 is ref $inv_obj, "SL::DB::Inventory",             "One inventory object, no array or undef";
150 is $inv_obj->qty == 3.4, 1,                       "Valid qty accepted";  # evals to text
151 is $inv_obj->comment, 'Lager Inventur Standard',  "Valid comment accepted";  # evals to text
152 is $inv_obj->employee_id, 1,                      "Employee valid";  # evals to text
153 is ref $inv_obj->shippingdate, 'DateTime',        "Valid DateTime for shippingdate";
154 is $inv_obj->shippingdate, DateTime->today_local, "Default shippingdate set";
155
156 $tt = SL::DB::Manager::TransferType->find_by(id => $inv_obj->trans_type_id);
157
158 is ref $tt, 'SL::DB::TransferType',       "Valid TransferType, no undef";
159 is $tt->direction, 'in',                  "Transfer direction correct";
160 is $tt->description, 'correction',        "Transfer description correct";
161
162 # remove some stuff
163
164 $file = \<<EOL;
165 warehouse,bin,partnumber,qty,chargenumber,comment,employee_id,shippingdate
166 Warehouse,"Bin 1","ap 1",-13.4
167 EOL
168 $entries = test_import($file, $settings1);
169 $entry = $entries->[0];
170 is scalar @{ $entry->{errors} }, 0, "No error for valid data occurred";
171 is $entry->{object}->qty, "-13.4", "Valid qty accepted";  # evals to text
172 is(SL::Helper::Inventory::get_stock(part => $part1),  "15.00000",  'simple add (stock) qty works');
173 is(SL::Helper::Inventory::get_onhand(part => $part1), "15.00000", 'simple add (onhand) qty works');
174
175 # now check the real Inventory entry
176 $trans_id = $entry->{object}->trans_id;
177 $inv_obj = SL::DB::Manager::Inventory->find_by(trans_id => $trans_id);
178
179 # we expect one entry for one trans_id
180 is ref $inv_obj, "SL::DB::Inventory",             "One inventory object, no array or undef";
181 is $inv_obj->qty == -13.4, 1,                       "Valid qty accepted";  # evals to text
182 is $inv_obj->comment, 'Lager Inventur Standard',  "Valid comment accepted";  # evals to text
183 is $inv_obj->employee_id, 1,                      "Employee valid";  # evals to text
184 is ref $inv_obj->shippingdate, 'DateTime',        "Valid DateTime for shippingdate";
185 is $inv_obj->shippingdate, DateTime->today_local, "Default shippingdate set";
186
187 $tt = SL::DB::Manager::TransferType->find_by(id => $inv_obj->trans_type_id);
188
189 is ref $tt, 'SL::DB::TransferType',       "Valid TransferType, no undef";
190 is $tt->direction, 'out',                  "Transfer direction correct";
191 is $tt->description, 'correction',        "Transfer description correct";
192
193 # repeat both test cases but with target qty instead of qty (should throw an error for neg. case)
194 # and customise comment
195 # add some stuff
196
197 $file = \<<EOL;
198 warehouse,bin,partnumber,target_qty,comment
199 Warehouse,"Bin 1","ap 1",3.4,"Alter, wir haben uns voll verhauen bei der aktuellen Zielmenge!"
200 EOL
201 $entries = test_import($file, $settings1);
202 $entry = $entries->[0];
203 is scalar @{ $entry->{errors} }, 0, "No error for valid data occurred";
204 is $entry->{object}->qty, "-11.6", "Valid qty accepted";  # evals to text qty = target_qty - actual_qty
205 is(SL::Helper::Inventory::get_stock(part => $part1),  "3.40000",  'simple add (stock) qty works');
206 is(SL::Helper::Inventory::get_onhand(part => $part1), "3.40000", 'simple add (onhand) qty works');
207
208 # now check the real Inventory entry
209 $trans_id = $entry->{object}->trans_id;
210 $inv_obj = SL::DB::Manager::Inventory->find_by(trans_id => $trans_id);
211
212 # we expect one entry for one trans_id
213 is ref $inv_obj, "SL::DB::Inventory",             "One inventory object, no array or undef";
214 is $inv_obj->qty == -11.6, 1,                       "Valid qty accepted";
215 is $inv_obj->comment,
216   "Alter, wir haben uns voll verhauen bei der aktuellen Zielmenge!",  "Valid comment accepted";
217 is $inv_obj->employee_id, 1,                      "Employee valid";
218 is ref $inv_obj->shippingdate, 'DateTime',        "Valid DateTime for shippingdate";
219 is $inv_obj->shippingdate, DateTime->today_local, "Default shippingdate set";
220
221 $tt = SL::DB::Manager::TransferType->find_by(id => $inv_obj->trans_type_id);
222
223 is ref $tt, 'SL::DB::TransferType',       "Valid TransferType, no undef";
224 is $tt->direction, 'out',                  "Transfer direction correct";
225 is $tt->description, 'correction',        "Transfer description correct";
226
227 # remove some stuff, but too much
228
229 $file = \<<EOL;
230 warehouse,bin,partnumber,target_qty,comment
231 Warehouse,"Bin 1","ap 1",-13.4,"Jetzt stimmt aber alles"
232 EOL
233 $entries = test_import($file, $settings1);
234 $entry = $entries->[0];
235 is scalar @{ $entry->{errors} }, 1, "One error for invalid data occurred";
236 is $entry->{object}->qty, undef, "No data accepted";  # evals to text
237 is(SL::Helper::Inventory::get_stock(part => $part1),  "3.40000",  'simple add (stock) qty works');
238 is(SL::Helper::Inventory::get_onhand(part => $part1), "3.40000", 'simple add (onhand) qty works');
239
240 # now check the real Inventory entry
241 $trans_id = $entry->{object}->trans_id;
242 $inv_obj = SL::DB::Manager::Inventory->find_by(trans_id => $trans_id);
243
244 is ref $trans_id, '',         "No trans_id -> undef";
245 is ref $inv_obj,  '',         "No inventory object -> undef";
246
247 # add some stuff, but realistic value
248
249 $file = \<<EOL;
250 warehouse,bin,partnumber,target_qty,comment
251 Warehouse,"Bin 1","ap 1",33.75,"Jetzt wirklich"
252 EOL
253 $entries = test_import($file, $settings1);
254 $entry = $entries->[0];
255 is scalar @{ $entry->{errors} }, 0, "No error for valid data occurred";
256 is $entry->{object}->qty, "30.35", "Valid qty accepted";  # evals to text qty = target_qty - actual_qty
257 is(SL::Helper::Inventory::get_stock(part => $part1),  "33.75000",  'simple add (stock) qty works');
258 is(SL::Helper::Inventory::get_onhand(part => $part1), "33.75000", 'simple add (onhand) qty works');
259
260 # now check the real Inventory entry
261 $trans_id = $entry->{object}->trans_id;
262 $inv_obj = SL::DB::Manager::Inventory->find_by(trans_id => $trans_id);
263
264 # we expect one entry for one trans_id
265 is ref $inv_obj, "SL::DB::Inventory",             "One inventory object, no array or undef";
266 is $inv_obj->qty == 30.35, 1,                     "Valid qty accepted";
267 is $inv_obj->comment, "Jetzt wirklich",           "Valid comment accepted";
268 is $inv_obj->employee_id, 1,                      "Employee valid";
269 is ref $inv_obj->shippingdate, 'DateTime',        "Valid DateTime for shippingdate";
270 is $inv_obj->shippingdate, DateTime->today_local, "Default shippingdate set";
271
272 $tt = SL::DB::Manager::TransferType->find_by(id => $inv_obj->trans_type_id);
273
274 is ref $tt, 'SL::DB::TransferType',       "Valid TransferType, no undef";
275 is $tt->direction, 'in',                  "Transfer direction correct";
276 is $tt->description, 'correction',        "Transfer description correct";
277
278 # target_qty is 0
279
280 $file = \<<EOL;
281 warehouse,bin,partnumber,target_qty,comment
282 Warehouse,"Bin 1","ap 1",0,"Jetzt wirklich"
283 EOL
284 $entries = test_import($file, $settings1);
285 $entry = $entries->[0];
286 is scalar @{ $entry->{errors} }, 0, "No error for valid data occurred";
287 is $entry->{object}->qty, "-33.75", "Valid qty accepted";  # evals to text qty = target_qty - actual_qty
288 is(SL::Helper::Inventory::get_stock(part => $part1),  "0.00000",  'simple add (stock) qty works');
289 is(SL::Helper::Inventory::get_onhand(part => $part1), undef, 'simple add (onhand) qty works'); # hmm good return?
290
291 # now check the real Inventory entry
292 $trans_id = $entry->{object}->trans_id;
293 $inv_obj = SL::DB::Manager::Inventory->find_by(trans_id => $trans_id);
294
295 # we expect one entry for one trans_id
296 is ref $inv_obj, "SL::DB::Inventory",             "One inventory object, no array or undef";
297 is $inv_obj->qty == -33.75000, 1,                       "Valid qty accepted";
298 is $inv_obj->comment,
299   "Jetzt wirklich",  "Valid comment accepted";
300 is $inv_obj->employee_id, 1,                      "Employee valid";
301 is ref $inv_obj->shippingdate, 'DateTime',        "Valid DateTime for shippingdate";
302 is $inv_obj->shippingdate, DateTime->today_local, "Default shippingdate set";
303
304 $tt = SL::DB::Manager::TransferType->find_by(id => $inv_obj->trans_type_id);
305
306 is ref $tt, 'SL::DB::TransferType',       "Valid TransferType, no undef";
307 is $tt->direction, 'out',                  "Transfer direction correct";
308 is $tt->description, 'correction',        "Transfer description correct";
309
310 # add some stuff with a different numberformat
311
312 $::myconfig{numberformat} = '1.000,00';
313 $file = \<<EOL;
314 warehouse,bin,partnumber,target_qty,comment
315 Warehouse,"Bin 1","ap 1","31,2","Jetzt wirklich"
316 EOL
317 $entries = test_import($file, $settings1);
318 $entry = $entries->[0];
319 is scalar @{ $entry->{errors} }, 0, "No error for valid data occurred";
320 is $entry->{object}->qty, "31.2",  "Valid qty accepted";  # evals to text qty = target_qty - actual_qty
321 is(SL::Helper::Inventory::get_stock(part => $part1),  "31.20000",  'simple add (stock) qty works');
322 is(SL::Helper::Inventory::get_onhand(part => $part1), "31.20000", 'simple add (onhand) qty works');
323
324 # now check the real Inventory entry
325 $trans_id = $entry->{object}->trans_id;
326 $inv_obj = SL::DB::Manager::Inventory->find_by(trans_id => $trans_id);
327
328 # we expect one entry for one trans_id
329 is ref $inv_obj, "SL::DB::Inventory",             "One inventory object, no array or undef";
330 is $inv_obj->qty == 31.2, 1,                     "Valid qty calculated";
331
332
333
334 clear_up(); # remove all data at end of tests
335
336 # end of tests
337
338 done_testing();
339
340 sub clear_up {
341   SL::DB::Manager::Inventory->delete_all(all => 1);
342   SL::DB::Manager::Assembly->delete_all(all => 1);
343   SL::DB::Manager::Part->delete_all(all => 1);
344   SL::DB::Manager::Bin->delete_all(all => 1);
345   SL::DB::Manager::Warehouse->delete_all(all => 1);
346 }
347
348 sub create_standard_stock {
349   ($wh, $bin1)          = create_warehouse_and_bins();
350   ($wh_moon, $bin_moon) = create_warehouse_and_bins(
351       warehouse_description => 'Our warehouse location at the moon',
352       bin_description       => 'Lunar crater',
353     );
354   $bin2 = SL::DB::Bin->new(description => "Bin 2", warehouse => $wh)->save;
355   $wh->load;
356
357   $assembly1  =  new_assembly(number_of_parts => 2)->save;
358   ($part1, $part2) = map { $_->part } $assembly1->assemblies;
359
360   $service1 = new_service(partnumber  => "service number 1",
361                           description => "We really need this service",
362                          )->save;
363   my $assembly_items;
364   push( @{$assembly_items}, SL::DB::Assembly->new(parts_id => $part1->id,
365                                                   qty      => 12,
366                                                   position => 1,
367                                                   ));
368   push( @{$assembly_items}, SL::DB::Assembly->new(parts_id => $part2->id,
369                                                   qty      => 6.34,
370                                                   position => 2,
371                                                   ));
372   push( @{$assembly_items}, SL::DB::Assembly->new(parts_id => $service1->id,
373                                                   qty      => 1.2,
374                                                   position => 3,
375                                                   ));
376   $assembly_service  =  new_assembly(description    => 'Ein Erzeugnis mit Dienstleistungen',
377                                      assembly_items => $assembly_items
378                                     )->save;
379 }
380
381
382
383
384 1;