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