c300bb5d43163f0dde329eee8d630d4db3261f65
[kivitendo-erp.git] / t / helper / shipped_qty.t
1 use strict;
2 use Test::More;
3
4 use lib 't';
5 use Support::TestSetup;
6 use Carp;
7 use Test::Exception;
8 use Data::Dumper;
9 use SL::DB::Part;
10 use SL::DB::Inventory;
11 use SL::DB::TransferType;
12 use SL::DB::Order;
13 use SL::DB::DeliveryOrder;
14 use SL::DB::Customer;
15 use SL::DB::Vendor;
16 use SL::DB::RecordLink;
17 use SL::DB::DeliveryOrderItemsStock;
18 use SL::DB::Bin;
19 use SL::WH;
20 use SL::AM;
21 use SL::Dev::ALL qw(:ALL);
22 use SL::Helper::ShippedQty;
23 use DateTime;
24
25 Support::TestSetup::login();
26
27 clear_up();
28
29 my ($customer, $vendor, @parts, $unit);
30
31 $customer = new_customer(name => 'Testkunde'    )->save;
32 $vendor   = new_vendor(  name => 'Testlieferant')->save;
33
34 my $default_sellprice = 10;
35 my $default_lastcost  =  4;
36
37 my ($wh) = create_warehouse_and_bins();
38 my $bin1 = SL::DB::Manager::Bin->find_by(description => "Bin 1");
39 my $bin2 = SL::DB::Manager::Bin->find_by(description => "Bin 2");
40
41 my %part_defaults = (
42     sellprice    => $default_sellprice,
43     warehouse_id => $wh->id,
44     bin_id       => $bin1->id
45 );
46
47 # create 3 parts to be used in test
48 for my $i ( 1 .. 4 ) {
49   new_part( %part_defaults, partnumber => $i, description => "part $i test" )->save;
50 };
51
52 my $part1 = SL::DB::Manager::Part->find_by( partnumber => '1' );
53 my $part2 = SL::DB::Manager::Part->find_by( partnumber => '2' );
54 my $part3 = SL::DB::Manager::Part->find_by( partnumber => '3' );
55 my $part4 = SL::DB::Manager::Part->find_by( partnumber => '4' );
56
57 my @part_ids; # list of all part_ids to run checks against
58 push( @part_ids, $_->id ) foreach ( $part1, $part2, $part3, $part4 );
59 my %default_transfer_params = ( wh => $wh, bin => $bin1, unit => 'Stck');
60
61
62 # test purchases first, so there is actually stock available when sales is tested
63
64 note("testing purchases, no fill_up");
65
66 my $purchase_order = create_purchase_order(
67   save       => 1,
68   orderitems => [ create_order_item(part => $part1, qty => 11),
69                   create_order_item(part => $part2, qty => 12),
70                   create_order_item(part => $part3, qty => 13),
71                 ]
72 );
73
74 Rose::DB::Object::Helpers::forget_related($purchase_order, 'orderitems');
75 $purchase_order->orderitems;
76
77 SL::Helper::ShippedQty
78   ->new(require_stock_out => 1)  # should make no difference while there is no delivery order
79   ->calculate($purchase_order)
80   ->write_to_objects;
81
82 is($purchase_order->items_sorted->[0]->{shipped_qty}, 0, "first purchase orderitem has no shipped_qty");
83 ok(!$purchase_order->items_sorted->[0]->{delivered},     "first purchase orderitem is not delivered");
84
85 my $purchase_orderitem_part1 = SL::DB::Manager::OrderItem->find_by( parts_id => $part1->id, trans_id => $purchase_order->id);
86
87 is($purchase_orderitem_part1->shipped_qty, 0, "OrderItem shipped_qty method ok");
88
89 is($purchase_order->closed,     0, 'purchase order is open');
90 ok(!$purchase_order->delivered,    'purchase order is not delivered');
91
92 note('converting purchase order to delivery order');
93 # create purchase delivery order from purchase order
94 my $purchase_delivery_order = $purchase_order->convert_to_delivery_order;
95 is($purchase_order->closed,    0, 'purchase order is open');
96 ok($purchase_order->delivered,    'purchase order is now delivered');
97
98 SL::Helper::ShippedQty
99   ->new(require_stock_out => 0)
100   ->calculate($purchase_order)
101   ->write_to_objects;
102
103 is($purchase_order->items_sorted->[0]->{shipped_qty}, 11, "require_stock_out => 0: first purchase orderitem has shipped_qty");
104 ok($purchase_order->items_sorted->[0]->{delivered},       "require_stock_out => 0: first purchase orderitem is delivered");
105
106 Rose::DB::Object::Helpers::forget_related($purchase_order, 'orderitems');
107 $purchase_order->orderitems;
108
109 SL::Helper::ShippedQty
110   ->new(require_stock_out => 1)
111   ->calculate($purchase_order)
112   ->write_to_objects;
113
114 is($purchase_order->items_sorted->[0]->{shipped_qty}, 0,  "require_stock_out => 1: first purchase orderitem has no shipped_qty");
115 ok(!$purchase_order->items_sorted->[0]->{delivered},      "require_stock_out => 1: first purchase orderitem is not delivered");
116
117 # ship items from delivery order
118 transfer_purchase_delivery_order($purchase_delivery_order);
119
120 Rose::DB::Object::Helpers::forget_related($purchase_order, 'orderitems');
121 $purchase_order->orderitems;
122
123 SL::Helper::ShippedQty
124   ->new(require_stock_out => 1, keep_matches => 1)  # shouldn't make a difference now after shipping
125   ->calculate($purchase_order)
126   ->write_to_objects;
127
128 is($purchase_order->items_sorted->[0]->{shipped_qty}, 11, "require_stock_out => 1: first purchase orderitem has shipped_qty");
129 ok($purchase_order->items_sorted->[0]->{delivered},       "require_stock_out => 1: first purchase orderitem is delivered");
130
131 my $purchase_orderitem_part2 = SL::DB::Manager::OrderItem->find_by(parts_id => $part1->id, trans_id => $purchase_order->id);
132
133 is($purchase_orderitem_part2->shipped_qty(require_stock_out => 1), 11, "OrderItem shipped_qty from helper ok");
134
135
136 note('testing sales, no fill_up');
137
138 my $sales_order = create_sales_order(
139   save       => 1,
140   orderitems => [ create_order_item(part => $part1, qty => 5),
141                   create_order_item(part => $part2, qty => 6),
142                   create_order_item(part => $part3, qty => 7),
143                 ]
144 );
145
146 Rose::DB::Object::Helpers::forget_related($sales_order, 'orderitems');
147 $sales_order->orderitems;
148
149 SL::Helper::ShippedQty
150   ->new(require_stock_out => 1)  # should make no difference while there is no delivery order
151   ->calculate($sales_order)
152   ->write_to_objects;
153
154 is($sales_order->items_sorted->[0]->{shipped_qty}, 0,  "first sales orderitem has no shipped_qty");
155 ok(!$sales_order->items_sorted->[0]->{delivered},      "first sales orderitem is not delivered");
156
157 my $orderitem_part1 = SL::DB::Manager::OrderItem->find_by(parts_id => $part1->id, trans_id => $sales_order->id);
158 my $orderitem_part2 = SL::DB::Manager::OrderItem->find_by(parts_id => $part2->id, trans_id => $sales_order->id);
159
160 is($orderitem_part1->shipped_qty, 0, "OrderItem shipped_qty method ok");
161
162 # create sales delivery order from sales order
163 my $sales_delivery_order = $sales_order->convert_to_delivery_order;
164
165 SL::Helper::ShippedQty
166   ->new(require_stock_out => 0)
167   ->calculate($sales_order)
168   ->write_to_objects;
169
170 is($sales_order->items_sorted->[0]->{shipped_qty}, 5, "require_stock_out => 0: first sales orderitem has shipped_qty");
171 ok($sales_order->items_sorted->[0]->{delivered},      "require_stock_out => 0: first sales orderitem is delivered");
172
173 Rose::DB::Object::Helpers::forget_related($sales_order, 'orderitems');
174 $sales_order->orderitems;
175
176 SL::Helper::ShippedQty
177   ->new(require_stock_out => 1)
178   ->calculate($sales_order)
179   ->write_to_objects;
180
181 is($sales_order->items_sorted->[0]->{shipped_qty}, 0,  "require_stock_out => 1: first sales orderitem has no shipped_qty");
182 ok(!$sales_order->items_sorted->[0]->{delivered},      "require_stock_out => 1: first sales orderitem is not delivered");
183
184 # ship items from delivery order
185 transfer_sales_delivery_order($sales_delivery_order);
186
187 Rose::DB::Object::Helpers::forget_related($sales_order, 'orderitems');
188 $sales_order->orderitems;
189
190 SL::Helper::ShippedQty
191   ->new(require_stock_out => 1)
192   ->calculate($sales_order)
193   ->write_to_objects;
194
195 is($sales_order->items_sorted->[0]->{shipped_qty}, 5, "require_stock_out => 1: first sales orderitem has no shipped_qty");
196 ok($sales_order->items_sorted->[0]->{delivered},      "require_stock_out => 1: first sales orderitem is not delivered");
197
198 $orderitem_part1 = SL::DB::Manager::OrderItem->find_by(parts_id => $part1->id, trans_id => $sales_order->id);
199
200 is($orderitem_part1->shipped_qty(require_stock_out => 1), 5, "OrderItem shipped_qty from helper ok");
201
202
203 note('misc tests');
204 my $number_of_linked_items = SL::DB::Manager::RecordLink->get_all_count( where => [ from_table => 'orderitems', to_table => 'delivery_order_items' ] );
205 is ($number_of_linked_items , 6, "6 record_links for items, 3 from sales order, 3 from purchase order");
206
207 note('testing optional orderitems');
208
209 my $item_optional = create_order_item(part => $part3, qty => 7, optional => 1);
210 ok($item_optional->{optional},       "optional order item");
211
212 my $sales_order_opt = create_sales_order(
213   save       => 1,
214   orderitems => [ create_order_item(part => $part1, qty => 5),
215                   create_order_item(part => $part2, qty => 6),
216                   $item_optional,
217                 ]
218 );
219
220
221 SL::Helper::ShippedQty
222   ->new(require_stock_out => 1)  # should make no difference while there is no delivery order
223   ->calculate($sales_order_opt)
224   ->write_to_objects;
225
226 is($sales_order_opt->items_sorted->[2]->{shipped_qty}, 0,  "third optional sales orderitem has no shipped_qty");
227 ok(!$sales_order_opt->items_sorted->[2]->{delivered},      "third optional sales orderitem is not delivered");
228 ok($sales_order_opt->items_sorted->[2]->{optional},        "third optional sales orderitem is optional");
229
230 my $orderitem_part3_opt = SL::DB::Manager::OrderItem->find_by(parts_id => $part3->id, trans_id => $sales_order_opt->id);
231 is($orderitem_part3_opt->shipped_qty, 0, "OrderItem shipped_qty method ok");
232
233 # create sales delivery order from sales order
234 my $sales_delivery_order_opt = $sales_order_opt->convert_to_delivery_order;
235 is(scalar @{ $sales_delivery_order_opt->items_sorted }, 3,   "third optional sales delivery orderitem is there");
236
237 # and delete third item
238 my $optional =  SL::DB::Manager::DeliveryOrderItem->find_by(parts_id => $part3->id, delivery_order_id => $sales_delivery_order_opt->id);
239 SL::DB::DeliveryOrderItem->new(id => $optional->id)->delete;
240 $sales_delivery_order_opt->save(cascade => 1);
241 my $new_sales_delivery_order_opt = SL::DB::Manager::DeliveryOrder->find_by(id => $sales_delivery_order_opt->id);
242 is(scalar @{ $new_sales_delivery_order_opt->items_sorted }, 2,   "third optional sales delivery orderitem is undef");
243
244 SL::Helper::ShippedQty
245   ->new(require_stock_out => 0)
246   ->calculate($sales_order_opt)
247   ->write_to_objects;
248
249 is($sales_order_opt->items_sorted->[0]->{shipped_qty}, 5,  "require_stock_out => 0: first sales orderitem has shipped_qty");
250 ok($sales_order_opt->items_sorted->[0]->{delivered},       "require_stock_out => 0: first sales orderitem is delivered");
251 ok($sales_order_opt->items_sorted->[1]->{delivered},       "require_stock_out => 0: second sales orderitem is delivered");
252 ok(!$sales_order_opt->items_sorted->[2]->{delivered},      "require_stock_out => 0: third sales orderitem is NOT delivered");
253 is($sales_order_opt->items_sorted->[2]->{shipped_qty}, 0,  "require_stock_out => 0: third sales orderitem has no shipped_qty");
254 ok($sales_order_opt->{delivered},                          "require_stock_out => 0: order IS delivered");
255
256 clear_up();
257
258 {
259 #  legacy unlinked scenario:
260 #
261 #  order with two positions of the same part, qtys: 5, 3.
262 #  3 linked delivery orders, with positions:
263 #    1:  3 unlinked
264 #    2:  1 linked to 1, 3 linked to 2
265 #    3:  1 linked to 1
266 #
267 #  should be resolved under fill_up as 5/3, but gets resolved as 4/4
268   my $part = new_part()->save;
269   my $order = create_sales_order(
270     orderitems => [
271       create_order_item(part => $part, qty => 5),
272       create_order_item(part => $part, qty => 3),
273     ],
274   )->save;
275   my $do1 = create_sales_delivery_order(
276     orderitems => [
277       create_delivery_order_item(part => $part, qty => 3),
278     ],
279   );
280   my $do2 = create_sales_delivery_order(
281     orderitems => [
282       create_delivery_order_item(part => $part, qty => 1),
283       create_delivery_order_item(part => $part, qty => 3),
284     ],
285   );
286   my $do3 = create_sales_delivery_order(
287     orderitems => [
288       create_delivery_order_item(part => $part, qty => 1),
289     ],
290   );
291   $order->link_to_record($do1);
292   $order->link_to_record($do2);
293   $order->items_sorted->[0]->link_to_record($do2->items_sorted->[0]);
294   $order->items_sorted->[1]->link_to_record($do2->items_sorted->[1]);
295   $order->link_to_record($do3);
296   $order->items_sorted->[0]->link_to_record($do3->items->[0]);
297
298   SL::Helper::ShippedQty
299     ->new(fill_up => 1, require_stock_out => 0)
300     ->calculate($order)
301     ->write_to_objects;
302
303   is $order->items_sorted->[0]->{shipped_qty}, 5, 'unlinked legacy position test 1';
304   is $order->items_sorted->[1]->{shipped_qty}, 3, 'unlinked legacy position test 2';
305 }
306
307 clear_up();
308
309 done_testing;
310
311 sub clear_up {
312   foreach ( qw(Inventory DeliveryOrderItem DeliveryOrder Price OrderItem Order Part Customer Vendor Bin Warehouse) ) {
313     "SL::DB::Manager::${_}"->delete_all(all => 1);
314   }
315 };