blob: fccfd0d1cf770cd1fe9e0812e8037171a48272be [file] [log] [blame]
Rohit Waghchaure7b7ceaa2021-05-24 20:11:15 +05301import copy
Chillar Anand915b3432021-09-02 16:44:59 +05302import unittest
Rohit Waghchaure7b7ceaa2021-05-24 20:11:15 +05303from collections import defaultdict
Chillar Anand915b3432021-09-02 16:44:59 +05304
5import frappe
6from frappe.utils import cint
7
8from erpnext.buying.doctype.purchase_order.purchase_order import (
9 get_materials_from_supplier,
10 make_purchase_invoice,
11 make_purchase_receipt,
12 make_rm_stock_entry,
13)
Rohit Waghchaure7b7ceaa2021-05-24 20:11:15 +053014from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
Chillar Anand915b3432021-09-02 16:44:59 +053015from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
16from erpnext.stock.doctype.item.test_item import make_item
17from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
18from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
19
Rohit Waghchaure7b7ceaa2021-05-24 20:11:15 +053020
21class TestSubcontracting(unittest.TestCase):
22 def setUp(self):
23 make_subcontract_items()
24 make_raw_materials()
25 make_bom_for_subcontracted_items()
26
27 def test_po_with_bom(self):
28 '''
29 - Set backflush based on BOM
30 - Create subcontracted PO for the item Subcontracted Item SA1 and add same item two times.
31 - Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
32 - Create purchase receipt against the PO and check serial nos and batch no.
33 '''
34
35 set_backflush_based_on('BOM')
36 item_code = 'Subcontracted Item SA1'
37 items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 5, 'rate': 100},
38 {'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 6, 'rate': 100}]
39
40 rm_items = [{'item_code': 'Subcontracted SRM Item 1', 'qty': 5},
41 {'item_code': 'Subcontracted SRM Item 2', 'qty': 5},
42 {'item_code': 'Subcontracted SRM Item 3', 'qty': 5},
43 {'item_code': 'Subcontracted SRM Item 1', 'qty': 6},
44 {'item_code': 'Subcontracted SRM Item 2', 'qty': 6},
45 {'item_code': 'Subcontracted SRM Item 3', 'qty': 6}
46 ]
47
48 itemwise_details = make_stock_in_entry(rm_items=rm_items)
49 po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
50 supplier_warehouse="_Test Warehouse 1 - _TC")
51
52 for d in rm_items:
53 d['po_detail'] = po.items[0].name if d.get('qty') == 5 else po.items[1].name
54
55 make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
56 rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
57
58 pr1 = make_purchase_receipt(po.name)
59 pr1.submit()
60
61 for key, value in get_supplied_items(pr1).items():
62 transferred_detais = itemwise_details.get(key)
63
64 for field in ['qty', 'serial_no', 'batch_no']:
65 if value.get(field):
66 transfer, consumed = (transferred_detais.get(field), value.get(field))
67 if field == 'serial_no':
68 transfer, consumed = (sorted(transfer), sorted(consumed))
69
70 self.assertEqual(transfer, consumed)
71
72 def test_po_with_material_transfer(self):
73 '''
74 - Set backflush based on Material Transfer
75 - Create subcontracted PO for the item Subcontracted Item SA1 and Subcontracted Item SA5.
76 - Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
77 - Transfer extra item Subcontracted SRM Item 4 for the subcontract item Subcontracted Item SA5.
78 - Create partial purchase receipt against the PO and check serial nos and batch no.
79 '''
80
81 set_backflush_based_on('Material Transferred for Subcontract')
82 items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': 'Subcontracted Item SA1', 'qty': 5, 'rate': 100},
83 {'warehouse': '_Test Warehouse - _TC', 'item_code': 'Subcontracted Item SA5', 'qty': 6, 'rate': 100}]
84
85 rm_items = [{'item_code': 'Subcontracted SRM Item 1', 'qty': 5, 'main_item_code': 'Subcontracted Item SA1'},
86 {'item_code': 'Subcontracted SRM Item 2', 'qty': 5, 'main_item_code': 'Subcontracted Item SA1'},
87 {'item_code': 'Subcontracted SRM Item 3', 'qty': 5, 'main_item_code': 'Subcontracted Item SA1'},
88 {'item_code': 'Subcontracted SRM Item 5', 'qty': 6, 'main_item_code': 'Subcontracted Item SA5'},
89 {'item_code': 'Subcontracted SRM Item 4', 'qty': 6, 'main_item_code': 'Subcontracted Item SA5'}
90 ]
91
92 itemwise_details = make_stock_in_entry(rm_items=rm_items)
93 po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
94 supplier_warehouse="_Test Warehouse 1 - _TC")
95
96 for d in rm_items:
97 d['po_detail'] = po.items[0].name if d.get('qty') == 5 else po.items[1].name
98
99 make_stock_transfer_entry(po_no = po.name,
100 rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
101
102 pr1 = make_purchase_receipt(po.name)
103 pr1.remove(pr1.items[1])
104 pr1.submit()
105
106 for key, value in get_supplied_items(pr1).items():
107 transferred_detais = itemwise_details.get(key)
108
109 for field in ['qty', 'serial_no', 'batch_no']:
110 if value.get(field):
111 self.assertEqual(value.get(field), transferred_detais.get(field))
112
113 pr2 = make_purchase_receipt(po.name)
114 pr2.submit()
115
116 for key, value in get_supplied_items(pr2).items():
117 transferred_detais = itemwise_details.get(key)
118
119 for field in ['qty', 'serial_no', 'batch_no']:
120 if value.get(field):
121 self.assertEqual(value.get(field), transferred_detais.get(field))
122
123 def test_subcontract_with_same_components_different_fg(self):
124 '''
125 - Set backflush based on Material Transfer
126 - Create subcontracted PO for the item Subcontracted Item SA2 and Subcontracted Item SA3.
127 - Transfer the components from Stores to Supplier warehouse with serial nos.
128 - Transfer extra qty of components for the item Subcontracted Item SA2.
129 - Create partial purchase receipt against the PO and check serial nos and batch no.
130 '''
131
132 set_backflush_based_on('Material Transferred for Subcontract')
133 items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': 'Subcontracted Item SA2', 'qty': 5, 'rate': 100},
134 {'warehouse': '_Test Warehouse - _TC', 'item_code': 'Subcontracted Item SA3', 'qty': 6, 'rate': 100}]
135
136 rm_items = [{'item_code': 'Subcontracted SRM Item 2', 'qty': 6, 'main_item_code': 'Subcontracted Item SA2'},
137 {'item_code': 'Subcontracted SRM Item 2', 'qty': 6, 'main_item_code': 'Subcontracted Item SA3'}
138 ]
139
140 itemwise_details = make_stock_in_entry(rm_items=rm_items)
141 po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
142 supplier_warehouse="_Test Warehouse 1 - _TC")
143
144 for d in rm_items:
145 d['po_detail'] = po.items[0].name if d.get('qty') == 5 else po.items[1].name
146
147 make_stock_transfer_entry(po_no = po.name,
148 rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
149
150 pr1 = make_purchase_receipt(po.name)
151 pr1.items[0].qty = 3
152 pr1.remove(pr1.items[1])
153 pr1.submit()
154
155 for key, value in get_supplied_items(pr1).items():
156 transferred_detais = itemwise_details.get(key)
157 self.assertEqual(value.qty, 4)
158 self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get('serial_no')[0:4]))
159
160 pr2 = make_purchase_receipt(po.name)
161 pr2.items[0].qty = 2
162 pr2.remove(pr2.items[1])
163 pr2.submit()
164
165 for key, value in get_supplied_items(pr2).items():
166 transferred_detais = itemwise_details.get(key)
167
168 self.assertEqual(value.qty, 2)
169 self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get('serial_no')[4:6]))
170
171 pr3 = make_purchase_receipt(po.name)
172 pr3.submit()
173 for key, value in get_supplied_items(pr3).items():
174 transferred_detais = itemwise_details.get(key)
175
176 self.assertEqual(value.qty, 6)
177 self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get('serial_no')[6:12]))
178
179 def test_return_non_consumed_materials(self):
180 '''
181 - Set backflush based on Material Transfer
182 - Create subcontracted PO for the item Subcontracted Item SA2.
183 - Transfer the components from Stores to Supplier warehouse with serial nos.
184 - Transfer extra qty of component for the subcontracted item Subcontracted Item SA2.
185 - Create purchase receipt for full qty against the PO and change the qty of raw material.
186 - After that return the non consumed material back to the store from supplier's warehouse.
187 '''
188
189 set_backflush_based_on('Material Transferred for Subcontract')
190 items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': 'Subcontracted Item SA2', 'qty': 5, 'rate': 100}]
191 rm_items = [{'item_code': 'Subcontracted SRM Item 2', 'qty': 6, 'main_item_code': 'Subcontracted Item SA2'}]
192
193 itemwise_details = make_stock_in_entry(rm_items=rm_items)
194 po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
195 supplier_warehouse="_Test Warehouse 1 - _TC")
196
197 for d in rm_items:
198 d['po_detail'] = po.items[0].name
199
200 make_stock_transfer_entry(po_no = po.name,
201 rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
202
203 pr1 = make_purchase_receipt(po.name)
204 pr1.save()
205 pr1.supplied_items[0].consumed_qty = 5
206 pr1.supplied_items[0].serial_no = '\n'.join(sorted(
207 itemwise_details.get('Subcontracted SRM Item 2').get('serial_no')[0:5]
208 ))
209 pr1.submit()
210
211 for key, value in get_supplied_items(pr1).items():
212 transferred_detais = itemwise_details.get(key)
213 self.assertEqual(value.qty, 5)
214 self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get('serial_no')[0:5]))
215
216 po.load_from_db()
217 self.assertEqual(po.supplied_items[0].consumed_qty, 5)
218 doc = get_materials_from_supplier(po.name, [d.name for d in po.supplied_items])
219 self.assertEqual(doc.items[0].qty, 1)
220 self.assertEqual(doc.items[0].s_warehouse, '_Test Warehouse 1 - _TC')
221 self.assertEqual(doc.items[0].t_warehouse, '_Test Warehouse - _TC')
222 self.assertEqual(get_serial_nos(doc.items[0].serial_no),
223 itemwise_details.get(doc.items[0].item_code)['serial_no'][5:6])
224
225 def test_item_with_batch_based_on_bom(self):
226 '''
227 - Set backflush based on BOM
228 - Create subcontracted PO for the item Subcontracted Item SA4 (has batch no).
229 - Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
230 - Transfer the components in multiple batches.
231 - Create the 3 purchase receipt against the PO and split Subcontracted Items into two batches.
232 - Keep the qty as 2 for Subcontracted Item in the purchase receipt.
233 '''
234
235 set_backflush_based_on('BOM')
236 item_code = 'Subcontracted Item SA4'
237 items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
238
239 rm_items = [{'item_code': 'Subcontracted SRM Item 1', 'qty': 10},
240 {'item_code': 'Subcontracted SRM Item 2', 'qty': 10},
241 {'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
242 {'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
243 {'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
244 {'item_code': 'Subcontracted SRM Item 3', 'qty': 1}
245 ]
246
247 itemwise_details = make_stock_in_entry(rm_items=rm_items)
248 po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
249 supplier_warehouse="_Test Warehouse 1 - _TC")
250
251 for d in rm_items:
252 d['po_detail'] = po.items[0].name
253
254 make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
255 rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
256
257 pr1 = make_purchase_receipt(po.name)
258 pr1.items[0].qty = 2
259 add_second_row_in_pr(pr1)
260 pr1.save()
261 pr1.submit()
262
263 for key, value in get_supplied_items(pr1).items():
264 self.assertEqual(value.qty, 4)
265
266 pr1 = make_purchase_receipt(po.name)
267 pr1.items[0].qty = 2
268 add_second_row_in_pr(pr1)
269 pr1.save()
270 pr1.submit()
271
272 for key, value in get_supplied_items(pr1).items():
273 self.assertEqual(value.qty, 4)
274
275 pr1 = make_purchase_receipt(po.name)
276 pr1.items[0].qty = 2
277 pr1.save()
278 pr1.submit()
279
280 for key, value in get_supplied_items(pr1).items():
281 self.assertEqual(value.qty, 2)
282
283 def test_item_with_batch_based_on_material_transfer(self):
284 '''
285 - Set backflush based on Material Transferred for Subcontract
286 - Create subcontracted PO for the item Subcontracted Item SA4 (has batch no).
287 - Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
288 - Transfer the components in multiple batches with extra 2 qty for the batched item.
289 - Create the 3 purchase receipt against the PO and split Subcontracted Items into two batches.
290 - Keep the qty as 2 for Subcontracted Item in the purchase receipt.
291 - In the first purchase receipt the batched raw materials will be consumed 2 extra qty.
292 '''
293
294 set_backflush_based_on('Material Transferred for Subcontract')
295 item_code = 'Subcontracted Item SA4'
296 items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
297
298 rm_items = [{'item_code': 'Subcontracted SRM Item 1', 'qty': 10},
299 {'item_code': 'Subcontracted SRM Item 2', 'qty': 10},
300 {'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
301 {'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
302 {'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
303 {'item_code': 'Subcontracted SRM Item 3', 'qty': 3}
304 ]
305
306 itemwise_details = make_stock_in_entry(rm_items=rm_items)
307 po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
308 supplier_warehouse="_Test Warehouse 1 - _TC")
309
310 for d in rm_items:
311 d['po_detail'] = po.items[0].name
312
313 make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
314 rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
315
316 pr1 = make_purchase_receipt(po.name)
317 pr1.items[0].qty = 2
318 add_second_row_in_pr(pr1)
319 pr1.save()
320 pr1.submit()
321
322 for key, value in get_supplied_items(pr1).items():
323 qty = 4 if key != 'Subcontracted SRM Item 3' else 6
324 self.assertEqual(value.qty, qty)
325
326 pr1 = make_purchase_receipt(po.name)
327 pr1.items[0].qty = 2
328 add_second_row_in_pr(pr1)
329 pr1.save()
330 pr1.submit()
331
332 for key, value in get_supplied_items(pr1).items():
333 self.assertEqual(value.qty, 4)
334
335 pr1 = make_purchase_receipt(po.name)
336 pr1.items[0].qty = 2
337 pr1.save()
338 pr1.submit()
339
340 for key, value in get_supplied_items(pr1).items():
341 self.assertEqual(value.qty, 2)
342
343 def test_partial_transfer_serial_no_components_based_on_material_transfer(self):
344 '''
345 - Set backflush based on Material Transferred for Subcontract
346 - Create subcontracted PO for the item Subcontracted Item SA2.
347 - Transfer the partial components from Stores to Supplier warehouse with serial nos.
348 - Create partial purchase receipt against the PO and change the qty manually.
349 - Transfer the remaining components from Stores to Supplier warehouse with serial nos.
350 - Create purchase receipt for remaining qty against the PO and change the qty manually.
351 '''
352
353 set_backflush_based_on('Material Transferred for Subcontract')
354 item_code = 'Subcontracted Item SA2'
355 items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
356
357 rm_items = [{'item_code': 'Subcontracted SRM Item 2', 'qty': 5}]
358
359 itemwise_details = make_stock_in_entry(rm_items=rm_items)
360 po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
361 supplier_warehouse="_Test Warehouse 1 - _TC")
362
363 for d in rm_items:
364 d['po_detail'] = po.items[0].name
365
366 make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
367 rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
368
369 pr1 = make_purchase_receipt(po.name)
370 pr1.items[0].qty = 5
371 pr1.save()
372
373 for key, value in get_supplied_items(pr1).items():
374 details = itemwise_details.get(key)
375 self.assertEqual(value.qty, 3)
376 self.assertEqual(sorted(value.serial_no), sorted(details.serial_no[0:3]))
377
378 pr1.load_from_db()
379 pr1.supplied_items[0].consumed_qty = 5
380 pr1.supplied_items[0].serial_no = '\n'.join(itemwise_details[pr1.supplied_items[0].rm_item_code]['serial_no'])
381 pr1.save()
382 pr1.submit()
383
384 for key, value in get_supplied_items(pr1).items():
385 details = itemwise_details.get(key)
386 self.assertEqual(value.qty, details.qty)
387 self.assertEqual(sorted(value.serial_no), sorted(details.serial_no))
388
389 itemwise_details = make_stock_in_entry(rm_items=rm_items)
390 for d in rm_items:
391 d['po_detail'] = po.items[0].name
392
393 make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
394 rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
395
396 pr1 = make_purchase_receipt(po.name)
397 pr1.submit()
398
399 for key, value in get_supplied_items(pr1).items():
400 details = itemwise_details.get(key)
401 self.assertEqual(value.qty, details.qty)
402 self.assertEqual(sorted(value.serial_no), sorted(details.serial_no))
403
Rohit Waghchauree5fb2392021-06-18 20:37:42 +0530404 def test_incorrect_serial_no_components_based_on_material_transfer(self):
405 '''
406 - Set backflush based on Material Transferred for Subcontract
407 - Create subcontracted PO for the item Subcontracted Item SA2.
408 - Transfer the serialized componenets to the supplier.
409 - Create purchase receipt and change the serial no which is not transferred.
410 - System should throw the error and not allowed to save the purchase receipt.
411 '''
412
413 set_backflush_based_on('Material Transferred for Subcontract')
414 item_code = 'Subcontracted Item SA2'
415 items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
416
417 rm_items = [{'item_code': 'Subcontracted SRM Item 2', 'qty': 10}]
418
419 itemwise_details = make_stock_in_entry(rm_items=rm_items)
420 po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
421 supplier_warehouse="_Test Warehouse 1 - _TC")
422
423 for d in rm_items:
424 d['po_detail'] = po.items[0].name
425
426 make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
427 rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
428
429 pr1 = make_purchase_receipt(po.name)
430 pr1.save()
431 pr1.supplied_items[0].serial_no = 'ABCD'
432 self.assertRaises(frappe.ValidationError, pr1.save)
433 pr1.delete()
434
Rohit Waghchaure7b7ceaa2021-05-24 20:11:15 +0530435 def test_partial_transfer_batch_based_on_material_transfer(self):
436 '''
437 - Set backflush based on Material Transferred for Subcontract
438 - Create subcontracted PO for the item Subcontracted Item SA6.
439 - Transfer the partial components from Stores to Supplier warehouse with batch.
440 - Create partial purchase receipt against the PO and change the qty manually.
441 - Transfer the remaining components from Stores to Supplier warehouse with batch.
442 - Create purchase receipt for remaining qty against the PO and change the qty manually.
443 '''
444
445 set_backflush_based_on('Material Transferred for Subcontract')
446 item_code = 'Subcontracted Item SA6'
447 items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
448
449 rm_items = [{'item_code': 'Subcontracted SRM Item 3', 'qty': 5}]
450
451 itemwise_details = make_stock_in_entry(rm_items=rm_items)
452 po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
453 supplier_warehouse="_Test Warehouse 1 - _TC")
454
455 for d in rm_items:
456 d['po_detail'] = po.items[0].name
457
458 make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
459 rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
460
461 pr1 = make_purchase_receipt(po.name)
462 pr1.items[0].qty = 5
463 pr1.save()
464
465 transferred_batch_no = ''
466 for key, value in get_supplied_items(pr1).items():
467 details = itemwise_details.get(key)
468 self.assertEqual(value.qty, 3)
469 transferred_batch_no = details.batch_no
470 self.assertEqual(value.batch_no, details.batch_no)
471
472 pr1.load_from_db()
473 pr1.supplied_items[0].consumed_qty = 5
474 pr1.supplied_items[0].batch_no = list(transferred_batch_no.keys())[0]
475 pr1.save()
476 pr1.submit()
477
478 for key, value in get_supplied_items(pr1).items():
479 details = itemwise_details.get(key)
480 self.assertEqual(value.qty, details.qty)
481 self.assertEqual(value.batch_no, details.batch_no)
482
483 itemwise_details = make_stock_in_entry(rm_items=rm_items)
484 for d in rm_items:
485 d['po_detail'] = po.items[0].name
486
487 make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
488 rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
489
490 pr1 = make_purchase_receipt(po.name)
491 pr1.submit()
492
493 for key, value in get_supplied_items(pr1).items():
494 details = itemwise_details.get(key)
495 self.assertEqual(value.qty, details.qty)
496 self.assertEqual(value.batch_no, details.batch_no)
497
Rohit Waghchaure110e1522021-06-15 17:29:52 +0530498
499 def test_item_with_batch_based_on_material_transfer_for_purchase_invoice(self):
500 '''
501 - Set backflush based on Material Transferred for Subcontract
502 - Create subcontracted PO for the item Subcontracted Item SA4 (has batch no).
503 - Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
504 - Transfer the components in multiple batches with extra 2 qty for the batched item.
505 - Create the 3 purchase receipt against the PO and split Subcontracted Items into two batches.
506 - Keep the qty as 2 for Subcontracted Item in the purchase receipt.
507 - In the first purchase receipt the batched raw materials will be consumed 2 extra qty.
508 '''
509
510 set_backflush_based_on('Material Transferred for Subcontract')
511 item_code = 'Subcontracted Item SA4'
512 items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
513
514 rm_items = [{'item_code': 'Subcontracted SRM Item 1', 'qty': 10},
515 {'item_code': 'Subcontracted SRM Item 2', 'qty': 10},
516 {'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
517 {'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
518 {'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
519 {'item_code': 'Subcontracted SRM Item 3', 'qty': 3}
520 ]
521
522 itemwise_details = make_stock_in_entry(rm_items=rm_items)
523 po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
524 supplier_warehouse="_Test Warehouse 1 - _TC")
525
526 for d in rm_items:
527 d['po_detail'] = po.items[0].name
528
529 make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
530 rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
531
532 pr1 = make_purchase_invoice(po.name)
533 pr1.update_stock = 1
534 pr1.items[0].qty = 2
535 pr1.items[0].expense_account = 'Stock Adjustment - _TC'
536 add_second_row_in_pr(pr1)
537 pr1.save()
538 pr1.submit()
539
540 for key, value in get_supplied_items(pr1).items():
541 qty = 4 if key != 'Subcontracted SRM Item 3' else 6
542 self.assertEqual(value.qty, qty)
543
544 pr1 = make_purchase_invoice(po.name)
545 pr1.update_stock = 1
546 pr1.items[0].expense_account = 'Stock Adjustment - _TC'
547 pr1.items[0].qty = 2
548 add_second_row_in_pr(pr1)
549 pr1.save()
550 pr1.submit()
551
552 for key, value in get_supplied_items(pr1).items():
553 self.assertEqual(value.qty, 4)
554
555 pr1 = make_purchase_invoice(po.name)
556 pr1.update_stock = 1
557 pr1.items[0].qty = 2
558 pr1.items[0].expense_account = 'Stock Adjustment - _TC'
559 pr1.save()
560 pr1.submit()
561
562 for key, value in get_supplied_items(pr1).items():
563 self.assertEqual(value.qty, 2)
564
565 def test_partial_transfer_serial_no_components_based_on_material_transfer_for_purchase_invoice(self):
566 '''
567 - Set backflush based on Material Transferred for Subcontract
568 - Create subcontracted PO for the item Subcontracted Item SA2.
569 - Transfer the partial components from Stores to Supplier warehouse with serial nos.
570 - Create partial purchase receipt against the PO and change the qty manually.
571 - Transfer the remaining components from Stores to Supplier warehouse with serial nos.
572 - Create purchase receipt for remaining qty against the PO and change the qty manually.
573 '''
574
575 set_backflush_based_on('Material Transferred for Subcontract')
576 item_code = 'Subcontracted Item SA2'
577 items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
578
579 rm_items = [{'item_code': 'Subcontracted SRM Item 2', 'qty': 5}]
580
581 itemwise_details = make_stock_in_entry(rm_items=rm_items)
582 po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
583 supplier_warehouse="_Test Warehouse 1 - _TC")
584
585 for d in rm_items:
586 d['po_detail'] = po.items[0].name
587
588 make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
589 rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
590
591 pr1 = make_purchase_invoice(po.name)
592 pr1.update_stock = 1
593 pr1.items[0].qty = 5
594 pr1.items[0].expense_account = 'Stock Adjustment - _TC'
595 pr1.save()
596
597 for key, value in get_supplied_items(pr1).items():
598 details = itemwise_details.get(key)
599 self.assertEqual(value.qty, 3)
600 self.assertEqual(sorted(value.serial_no), sorted(details.serial_no[0:3]))
601
602 pr1.load_from_db()
603 pr1.supplied_items[0].consumed_qty = 5
604 pr1.supplied_items[0].serial_no = '\n'.join(itemwise_details[pr1.supplied_items[0].rm_item_code]['serial_no'])
605 pr1.save()
606 pr1.submit()
607
608 for key, value in get_supplied_items(pr1).items():
609 details = itemwise_details.get(key)
610 self.assertEqual(value.qty, details.qty)
611 self.assertEqual(sorted(value.serial_no), sorted(details.serial_no))
612
613 itemwise_details = make_stock_in_entry(rm_items=rm_items)
614 for d in rm_items:
615 d['po_detail'] = po.items[0].name
616
617 make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
618 rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
619
620 pr1 = make_purchase_invoice(po.name)
621 pr1.update_stock = 1
622 pr1.items[0].expense_account = 'Stock Adjustment - _TC'
623 pr1.submit()
624
625 for key, value in get_supplied_items(pr1).items():
626 details = itemwise_details.get(key)
627 self.assertEqual(value.qty, details.qty)
628 self.assertEqual(sorted(value.serial_no), sorted(details.serial_no))
629
630 def test_partial_transfer_batch_based_on_material_transfer_for_purchase_invoice(self):
631 '''
632 - Set backflush based on Material Transferred for Subcontract
633 - Create subcontracted PO for the item Subcontracted Item SA6.
634 - Transfer the partial components from Stores to Supplier warehouse with batch.
635 - Create partial purchase receipt against the PO and change the qty manually.
636 - Transfer the remaining components from Stores to Supplier warehouse with batch.
637 - Create purchase receipt for remaining qty against the PO and change the qty manually.
638 '''
639
640 set_backflush_based_on('Material Transferred for Subcontract')
641 item_code = 'Subcontracted Item SA6'
642 items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
643
644 rm_items = [{'item_code': 'Subcontracted SRM Item 3', 'qty': 5}]
645
646 itemwise_details = make_stock_in_entry(rm_items=rm_items)
647 po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
648 supplier_warehouse="_Test Warehouse 1 - _TC")
649
650 for d in rm_items:
651 d['po_detail'] = po.items[0].name
652
653 make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
654 rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
655
656 pr1 = make_purchase_invoice(po.name)
657 pr1.update_stock = 1
658 pr1.items[0].qty = 5
659 pr1.items[0].expense_account = 'Stock Adjustment - _TC'
660 pr1.save()
661
662 transferred_batch_no = ''
663 for key, value in get_supplied_items(pr1).items():
664 details = itemwise_details.get(key)
665 self.assertEqual(value.qty, 3)
666 transferred_batch_no = details.batch_no
667 self.assertEqual(value.batch_no, details.batch_no)
668
669 pr1.load_from_db()
670 pr1.supplied_items[0].consumed_qty = 5
671 pr1.supplied_items[0].batch_no = list(transferred_batch_no.keys())[0]
672 pr1.save()
673 pr1.submit()
674
675 for key, value in get_supplied_items(pr1).items():
676 details = itemwise_details.get(key)
677 self.assertEqual(value.qty, details.qty)
678 self.assertEqual(value.batch_no, details.batch_no)
679
680 itemwise_details = make_stock_in_entry(rm_items=rm_items)
681 for d in rm_items:
682 d['po_detail'] = po.items[0].name
683
684 make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
685 rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
686
687 pr1 = make_purchase_invoice(po.name)
688 pr1.update_stock = 1
689 pr1.items[0].expense_account = 'Stock Adjustment - _TC'
690 pr1.submit()
691
692 for key, value in get_supplied_items(pr1).items():
693 details = itemwise_details.get(key)
694 self.assertEqual(value.qty, details.qty)
695 self.assertEqual(value.batch_no, details.batch_no)
696
697 def test_item_with_batch_based_on_bom_for_purchase_invoice(self):
698 '''
699 - Set backflush based on BOM
700 - Create subcontracted PO for the item Subcontracted Item SA4 (has batch no).
701 - Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
702 - Transfer the components in multiple batches.
703 - Create the 3 purchase receipt against the PO and split Subcontracted Items into two batches.
704 - Keep the qty as 2 for Subcontracted Item in the purchase receipt.
705 '''
706
707 set_backflush_based_on('BOM')
708 item_code = 'Subcontracted Item SA4'
709 items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
710
711 rm_items = [{'item_code': 'Subcontracted SRM Item 1', 'qty': 10},
712 {'item_code': 'Subcontracted SRM Item 2', 'qty': 10},
713 {'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
714 {'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
715 {'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
716 {'item_code': 'Subcontracted SRM Item 3', 'qty': 1}
717 ]
718
719 itemwise_details = make_stock_in_entry(rm_items=rm_items)
720 po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
721 supplier_warehouse="_Test Warehouse 1 - _TC")
722
723 for d in rm_items:
724 d['po_detail'] = po.items[0].name
725
726 make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
727 rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
728
729 pr1 = make_purchase_invoice(po.name)
730 pr1.update_stock = 1
731 pr1.items[0].qty = 2
732 pr1.items[0].expense_account = 'Stock Adjustment - _TC'
733 add_second_row_in_pr(pr1)
734 pr1.save()
735 pr1.submit()
736
737 for key, value in get_supplied_items(pr1).items():
738 self.assertEqual(value.qty, 4)
739
740 pr1 = make_purchase_invoice(po.name)
741 pr1.update_stock = 1
742 pr1.items[0].qty = 2
743 pr1.items[0].expense_account = 'Stock Adjustment - _TC'
744 add_second_row_in_pr(pr1)
745 pr1.save()
746 pr1.submit()
747
748 for key, value in get_supplied_items(pr1).items():
749 self.assertEqual(value.qty, 4)
750
751 pr1 = make_purchase_invoice(po.name)
752 pr1.update_stock = 1
753 pr1.items[0].qty = 2
754 pr1.items[0].expense_account = 'Stock Adjustment - _TC'
755 pr1.save()
756 pr1.submit()
757
758 for key, value in get_supplied_items(pr1).items():
759 self.assertEqual(value.qty, 2)
760
Rohit Waghchaure7b7ceaa2021-05-24 20:11:15 +0530761def add_second_row_in_pr(pr):
762 item_dict = {}
763 for column in ['item_code', 'item_name', 'qty', 'uom', 'warehouse', 'stock_uom',
Rohit Waghchaure110e1522021-06-15 17:29:52 +0530764 'purchase_order', 'purchase_order_item', 'conversion_factor', 'rate', 'expense_account', 'po_detail']:
Rohit Waghchaure7b7ceaa2021-05-24 20:11:15 +0530765 item_dict[column] = pr.items[0].get(column)
766
767 pr.append('items', item_dict)
768 pr.set_missing_values()
769
770def get_supplied_items(pr_doc):
771 supplied_items = {}
772 for row in pr_doc.get('supplied_items'):
773 if row.rm_item_code not in supplied_items:
774 supplied_items.setdefault(row.rm_item_code,
775 frappe._dict({'qty': 0, 'serial_no': [], 'batch_no': defaultdict(float)}))
776
777 details = supplied_items[row.rm_item_code]
778 update_item_details(row, details)
779
780 return supplied_items
781
782def make_stock_in_entry(**args):
783 args = frappe._dict(args)
784
785 items = {}
786 for row in args.rm_items:
787 row = frappe._dict(row)
788
789 doc = make_stock_entry(target=row.warehouse or '_Test Warehouse - _TC',
790 item_code=row.item_code, qty=row.qty or 1, basic_rate=row.rate or 100)
791
792 if row.item_code not in items:
793 items.setdefault(row.item_code, frappe._dict({'qty': 0, 'serial_no': [], 'batch_no': defaultdict(float)}))
794
795 child_row = doc.items[0]
796 details = items[child_row.item_code]
797 update_item_details(child_row, details)
798
799 return items
800
801def update_item_details(child_row, details):
802 details.qty += (child_row.get('qty') if child_row.doctype == 'Stock Entry Detail'
803 else child_row.get('consumed_qty'))
804
805 if child_row.serial_no:
806 details.serial_no.extend(get_serial_nos(child_row.serial_no))
807
808 if child_row.batch_no:
809 details.batch_no[child_row.batch_no] += (child_row.get('qty') or child_row.get('consumed_qty'))
810
811def make_stock_transfer_entry(**args):
812 args = frappe._dict(args)
813
814 items = []
815 for row in args.rm_items:
816 row = frappe._dict(row)
817
818 item = {'item_code': row.main_item_code or args.main_item_code, 'rm_item_code': row.item_code,
819 'qty': row.qty or 1, 'item_name': row.item_code, 'rate': row.rate or 100,
820 'stock_uom': row.stock_uom or 'Nos', 'warehouse': row.warehuose or '_Test Warehouse - _TC'}
821
822 item_details = args.itemwise_details.get(row.item_code)
823
824 if item_details and item_details.serial_no:
825 serial_nos = item_details.serial_no[0:cint(row.qty)]
826 item['serial_no'] = '\n'.join(serial_nos)
827 item_details.serial_no = list(set(item_details.serial_no) - set(serial_nos))
828
829 if item_details and item_details.batch_no:
830 for batch_no, batch_qty in item_details.batch_no.items():
831 if batch_qty >= row.qty:
832 item['batch_no'] = batch_no
833 item_details.batch_no[batch_no] -= row.qty
834 break
835
836 items.append(item)
837
838 ste_dict = make_rm_stock_entry(args.po_no, items)
839 doc = frappe.get_doc(ste_dict)
840 doc.insert()
841 doc.submit()
842
843 return doc
844
845def make_subcontract_items():
846 sub_contracted_items = {'Subcontracted Item SA1': {}, 'Subcontracted Item SA2': {}, 'Subcontracted Item SA3': {},
847 'Subcontracted Item SA4': {'has_batch_no': 1, 'create_new_batch': 1, 'batch_number_series': 'SBAT.####'},
848 'Subcontracted Item SA5': {}, 'Subcontracted Item SA6': {}}
849
850 for item, properties in sub_contracted_items.items():
851 if not frappe.db.exists('Item', item):
852 properties.update({'is_stock_item': 1, 'is_sub_contracted_item': 1})
853 make_item(item, properties)
854
855def make_raw_materials():
856 raw_materials = {'Subcontracted SRM Item 1': {},
857 'Subcontracted SRM Item 2': {'has_serial_no': 1, 'serial_no_series': 'SRI.####'},
858 'Subcontracted SRM Item 3': {'has_batch_no': 1, 'create_new_batch': 1, 'batch_number_series': 'BAT.####'},
859 'Subcontracted SRM Item 4': {'has_serial_no': 1, 'serial_no_series': 'SRII.####'},
860 'Subcontracted SRM Item 5': {'has_serial_no': 1, 'serial_no_series': 'SRII.####'}}
861
862 for item, properties in raw_materials.items():
863 if not frappe.db.exists('Item', item):
864 properties.update({'is_stock_item': 1})
865 make_item(item, properties)
866
867def make_bom_for_subcontracted_items():
868 boms = {
869 'Subcontracted Item SA1': ['Subcontracted SRM Item 1', 'Subcontracted SRM Item 2', 'Subcontracted SRM Item 3'],
870 'Subcontracted Item SA2': ['Subcontracted SRM Item 2'],
871 'Subcontracted Item SA3': ['Subcontracted SRM Item 2'],
872 'Subcontracted Item SA4': ['Subcontracted SRM Item 1', 'Subcontracted SRM Item 2', 'Subcontracted SRM Item 3'],
873 'Subcontracted Item SA5': ['Subcontracted SRM Item 5'],
874 'Subcontracted Item SA6': ['Subcontracted SRM Item 3']
875 }
876
877 for item_code, raw_materials in boms.items():
878 if not frappe.db.exists('BOM', {'item': item_code}):
879 make_bom(item=item_code, raw_materials=raw_materials, rate=100)
880
881def set_backflush_based_on(based_on):
882 frappe.db.set_value('Buying Settings', None,
Ankush Menat4551d7d2021-08-19 13:41:10 +0530883 'backflush_raw_materials_of_subcontract_based_on', based_on)