brotherton-erpnext/erpnext/tests/test_subcontracting.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1115 lines
36 KiB
Python
Raw Normal View History

import copy
import unittest
from collections import defaultdict
import frappe
from frappe.utils import cint
from erpnext.buying.doctype.purchase_order.purchase_order import (
get_materials_from_supplier,
make_purchase_invoice,
make_purchase_receipt,
make_rm_stock_entry,
)
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
class TestSubcontracting(unittest.TestCase):
def setUp(self):
make_subcontract_items()
make_raw_materials()
make_bom_for_subcontracted_items()
def test_po_with_bom(self):
2022-03-28 13:22:46 +00:00
"""
- Set backflush based on BOM
- Create subcontracted PO for the item Subcontracted Item SA1 and add same item two times.
- Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
- Create purchase receipt against the PO and check serial nos and batch no.
2022-03-28 13:22:46 +00:00
"""
set_backflush_based_on("BOM")
item_code = "Subcontracted Item SA1"
items = [
{"warehouse": "_Test Warehouse - _TC", "item_code": item_code, "qty": 5, "rate": 100},
{"warehouse": "_Test Warehouse - _TC", "item_code": item_code, "qty": 6, "rate": 100},
]
2022-03-28 13:22:46 +00:00
rm_items = [
{"item_code": "Subcontracted SRM Item 1", "qty": 5},
{"item_code": "Subcontracted SRM Item 2", "qty": 5},
{"item_code": "Subcontracted SRM Item 3", "qty": 5},
{"item_code": "Subcontracted SRM Item 1", "qty": 6},
{"item_code": "Subcontracted SRM Item 2", "qty": 6},
{"item_code": "Subcontracted SRM Item 3", "qty": 6},
]
itemwise_details = make_stock_in_entry(rm_items=rm_items)
po = create_purchase_order(
rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC"
)
for d in rm_items:
d["po_detail"] = po.items[0].name if d.get("qty") == 5 else po.items[1].name
make_stock_transfer_entry(
po_no=po.name,
main_item_code=item_code,
rm_items=rm_items,
itemwise_details=copy.deepcopy(itemwise_details),
)
pr1 = make_purchase_receipt(po.name)
pr1.submit()
for key, value in get_supplied_items(pr1).items():
transferred_detais = itemwise_details.get(key)
for field in ["qty", "serial_no", "batch_no"]:
if value.get(field):
transfer, consumed = (transferred_detais.get(field), value.get(field))
if field == "serial_no":
transfer, consumed = (sorted(transfer), sorted(consumed))
self.assertEqual(transfer, consumed)
def test_po_with_material_transfer(self):
2022-03-28 13:22:46 +00:00
"""
- Set backflush based on Material Transfer
- Create subcontracted PO for the item Subcontracted Item SA1 and Subcontracted Item SA5.
- Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
- Transfer extra item Subcontracted SRM Item 4 for the subcontract item Subcontracted Item SA5.
- Create partial purchase receipt against the PO and check serial nos and batch no.
2022-03-28 13:22:46 +00:00
"""
set_backflush_based_on("Material Transferred for Subcontract")
items = [
2022-03-28 13:22:46 +00:00
{
"warehouse": "_Test Warehouse - _TC",
"item_code": "Subcontracted Item SA1",
"qty": 5,
"rate": 100,
},
{
"warehouse": "_Test Warehouse - _TC",
"item_code": "Subcontracted Item SA5",
"qty": 6,
"rate": 100,
2022-03-28 13:22:46 +00:00
},
]
2022-03-28 13:22:46 +00:00
rm_items = [
{"item_code": "Subcontracted SRM Item 1", "qty": 5, "main_item_code": "Subcontracted Item SA1"},
{"item_code": "Subcontracted SRM Item 2", "qty": 5, "main_item_code": "Subcontracted Item SA1"},
{"item_code": "Subcontracted SRM Item 3", "qty": 5, "main_item_code": "Subcontracted Item SA1"},
{"item_code": "Subcontracted SRM Item 5", "qty": 6, "main_item_code": "Subcontracted Item SA5"},
{"item_code": "Subcontracted SRM Item 4", "qty": 6, "main_item_code": "Subcontracted Item SA5"},
]
itemwise_details = make_stock_in_entry(rm_items=rm_items)
po = create_purchase_order(
rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC"
)
for d in rm_items:
d["po_detail"] = po.items[0].name if d.get("qty") == 5 else po.items[1].name
make_stock_transfer_entry(
po_no=po.name, rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details)
2022-03-28 13:22:46 +00:00
)
pr1 = make_purchase_receipt(po.name)
pr1.remove(pr1.items[1])
pr1.submit()
for key, value in get_supplied_items(pr1).items():
transferred_detais = itemwise_details.get(key)
for field in ["qty", "serial_no", "batch_no"]:
if value.get(field):
self.assertEqual(value.get(field), transferred_detais.get(field))
pr2 = make_purchase_receipt(po.name)
pr2.submit()
for key, value in get_supplied_items(pr2).items():
transferred_detais = itemwise_details.get(key)
for field in ["qty", "serial_no", "batch_no"]:
if value.get(field):
self.assertEqual(value.get(field), transferred_detais.get(field))
def test_subcontract_with_same_components_different_fg(self):
2022-03-28 13:22:46 +00:00
"""
- Set backflush based on Material Transfer
- Create subcontracted PO for the item Subcontracted Item SA2 and Subcontracted Item SA3.
- Transfer the components from Stores to Supplier warehouse with serial nos.
- Transfer extra qty of components for the item Subcontracted Item SA2.
- Create partial purchase receipt against the PO and check serial nos and batch no.
2022-03-28 13:22:46 +00:00
"""
set_backflush_based_on("Material Transferred for Subcontract")
items = [
2022-03-28 13:22:46 +00:00
{
"warehouse": "_Test Warehouse - _TC",
"item_code": "Subcontracted Item SA2",
"qty": 5,
"rate": 100,
},
{
"warehouse": "_Test Warehouse - _TC",
"item_code": "Subcontracted Item SA3",
"qty": 6,
"rate": 100,
2022-03-28 13:22:46 +00:00
},
]
2022-03-28 13:22:46 +00:00
rm_items = [
{"item_code": "Subcontracted SRM Item 2", "qty": 6, "main_item_code": "Subcontracted Item SA2"},
{"item_code": "Subcontracted SRM Item 2", "qty": 6, "main_item_code": "Subcontracted Item SA3"},
]
itemwise_details = make_stock_in_entry(rm_items=rm_items)
po = create_purchase_order(
rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC"
)
for d in rm_items:
d["po_detail"] = po.items[0].name if d.get("qty") == 5 else po.items[1].name
make_stock_transfer_entry(
po_no=po.name, rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details)
2022-03-28 13:22:46 +00:00
)
pr1 = make_purchase_receipt(po.name)
pr1.items[0].qty = 3
pr1.remove(pr1.items[1])
pr1.submit()
for key, value in get_supplied_items(pr1).items():
transferred_detais = itemwise_details.get(key)
self.assertEqual(value.qty, 4)
self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[0:4]))
pr2 = make_purchase_receipt(po.name)
pr2.items[0].qty = 2
pr2.remove(pr2.items[1])
pr2.submit()
for key, value in get_supplied_items(pr2).items():
transferred_detais = itemwise_details.get(key)
self.assertEqual(value.qty, 2)
self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[4:6]))
pr3 = make_purchase_receipt(po.name)
pr3.submit()
for key, value in get_supplied_items(pr3).items():
transferred_detais = itemwise_details.get(key)
self.assertEqual(value.qty, 6)
self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[6:12]))
def test_return_non_consumed_materials(self):
2022-03-28 13:22:46 +00:00
"""
- Set backflush based on Material Transfer
- Create subcontracted PO for the item Subcontracted Item SA2.
- Transfer the components from Stores to Supplier warehouse with serial nos.
- Transfer extra qty of component for the subcontracted item Subcontracted Item SA2.
- Create purchase receipt for full qty against the PO and change the qty of raw material.
- After that return the non consumed material back to the store from supplier's warehouse.
2022-03-28 13:22:46 +00:00
"""
set_backflush_based_on("Material Transferred for Subcontract")
items = [
2022-03-28 13:22:46 +00:00
{
"warehouse": "_Test Warehouse - _TC",
"item_code": "Subcontracted Item SA2",
"qty": 5,
"rate": 100,
2022-03-28 13:22:46 +00:00
}
]
rm_items = [
{"item_code": "Subcontracted SRM Item 2", "qty": 6, "main_item_code": "Subcontracted Item SA2"}
]
itemwise_details = make_stock_in_entry(rm_items=rm_items)
po = create_purchase_order(
rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC"
)
for d in rm_items:
d["po_detail"] = po.items[0].name
make_stock_transfer_entry(
po_no=po.name, rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details)
2022-03-28 13:22:46 +00:00
)
pr1 = make_purchase_receipt(po.name)
pr1.save()
pr1.supplied_items[0].consumed_qty = 5
pr1.supplied_items[0].serial_no = "\n".join(
sorted(itemwise_details.get("Subcontracted SRM Item 2").get("serial_no")[0:5])
)
pr1.submit()
for key, value in get_supplied_items(pr1).items():
transferred_detais = itemwise_details.get(key)
self.assertEqual(value.qty, 5)
self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[0:5]))
po.load_from_db()
self.assertEqual(po.supplied_items[0].consumed_qty, 5)
doc = get_materials_from_supplier(po.name, [d.name for d in po.supplied_items])
self.assertEqual(doc.items[0].qty, 1)
self.assertEqual(doc.items[0].s_warehouse, "_Test Warehouse 1 - _TC")
self.assertEqual(doc.items[0].t_warehouse, "_Test Warehouse - _TC")
self.assertEqual(
get_serial_nos(doc.items[0].serial_no),
itemwise_details.get(doc.items[0].item_code)["serial_no"][5:6],
)
def test_item_with_batch_based_on_bom(self):
2022-03-28 13:22:46 +00:00
"""
- Set backflush based on BOM
- Create subcontracted PO for the item Subcontracted Item SA4 (has batch no).
- Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
- Transfer the components in multiple batches.
- Create the 3 purchase receipt against the PO and split Subcontracted Items into two batches.
- Keep the qty as 2 for Subcontracted Item in the purchase receipt.
2022-03-28 13:22:46 +00:00
"""
set_backflush_based_on("BOM")
item_code = "Subcontracted Item SA4"
items = [{"warehouse": "_Test Warehouse - _TC", "item_code": item_code, "qty": 10, "rate": 100}]
2022-03-28 13:22:46 +00:00
rm_items = [
{"item_code": "Subcontracted SRM Item 1", "qty": 10},
{"item_code": "Subcontracted SRM Item 2", "qty": 10},
{"item_code": "Subcontracted SRM Item 3", "qty": 3},
{"item_code": "Subcontracted SRM Item 3", "qty": 3},
{"item_code": "Subcontracted SRM Item 3", "qty": 3},
{"item_code": "Subcontracted SRM Item 3", "qty": 1},
]
itemwise_details = make_stock_in_entry(rm_items=rm_items)
po = create_purchase_order(
rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC"
)
for d in rm_items:
d["po_detail"] = po.items[0].name
make_stock_transfer_entry(
po_no=po.name,
main_item_code=item_code,
rm_items=rm_items,
itemwise_details=copy.deepcopy(itemwise_details),
)
pr1 = make_purchase_receipt(po.name)
pr1.items[0].qty = 2
add_second_row_in_pr(pr1)
pr1.save()
pr1.submit()
for key, value in get_supplied_items(pr1).items():
self.assertEqual(value.qty, 4)
pr1 = make_purchase_receipt(po.name)
pr1.items[0].qty = 2
add_second_row_in_pr(pr1)
pr1.save()
pr1.submit()
for key, value in get_supplied_items(pr1).items():
self.assertEqual(value.qty, 4)
pr1 = make_purchase_receipt(po.name)
pr1.items[0].qty = 2
pr1.save()
pr1.submit()
for key, value in get_supplied_items(pr1).items():
self.assertEqual(value.qty, 2)
def test_item_with_batch_based_on_material_transfer(self):
2022-03-28 13:22:46 +00:00
"""
- Set backflush based on Material Transferred for Subcontract
- Create subcontracted PO for the item Subcontracted Item SA4 (has batch no).
- Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
- Transfer the components in multiple batches with extra 2 qty for the batched item.
- Create the 3 purchase receipt against the PO and split Subcontracted Items into two batches.
- Keep the qty as 2 for Subcontracted Item in the purchase receipt.
- In the first purchase receipt the batched raw materials will be consumed 2 extra qty.
2022-03-28 13:22:46 +00:00
"""
set_backflush_based_on("Material Transferred for Subcontract")
item_code = "Subcontracted Item SA4"
items = [{"warehouse": "_Test Warehouse - _TC", "item_code": item_code, "qty": 10, "rate": 100}]
2022-03-28 13:22:46 +00:00
rm_items = [
{"item_code": "Subcontracted SRM Item 1", "qty": 10},
{"item_code": "Subcontracted SRM Item 2", "qty": 10},
{"item_code": "Subcontracted SRM Item 3", "qty": 3},
{"item_code": "Subcontracted SRM Item 3", "qty": 3},
{"item_code": "Subcontracted SRM Item 3", "qty": 3},
{"item_code": "Subcontracted SRM Item 3", "qty": 3},
]
itemwise_details = make_stock_in_entry(rm_items=rm_items)
po = create_purchase_order(
rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC"
)
for d in rm_items:
d["po_detail"] = po.items[0].name
make_stock_transfer_entry(
po_no=po.name,
main_item_code=item_code,
rm_items=rm_items,
itemwise_details=copy.deepcopy(itemwise_details),
)
pr1 = make_purchase_receipt(po.name)
pr1.items[0].qty = 2
add_second_row_in_pr(pr1)
pr1.save()
pr1.submit()
for key, value in get_supplied_items(pr1).items():
qty = 4 if key != "Subcontracted SRM Item 3" else 6
self.assertEqual(value.qty, qty)
pr1 = make_purchase_receipt(po.name)
pr1.items[0].qty = 2
add_second_row_in_pr(pr1)
pr1.save()
pr1.submit()
for key, value in get_supplied_items(pr1).items():
self.assertEqual(value.qty, 4)
pr1 = make_purchase_receipt(po.name)
pr1.items[0].qty = 2
pr1.save()
pr1.submit()
for key, value in get_supplied_items(pr1).items():
self.assertEqual(value.qty, 2)
def test_partial_transfer_serial_no_components_based_on_material_transfer(self):
2022-03-28 13:22:46 +00:00
"""
- Set backflush based on Material Transferred for Subcontract
- Create subcontracted PO for the item Subcontracted Item SA2.
- Transfer the partial components from Stores to Supplier warehouse with serial nos.
- Create partial purchase receipt against the PO and change the qty manually.
- Transfer the remaining components from Stores to Supplier warehouse with serial nos.
- Create purchase receipt for remaining qty against the PO and change the qty manually.
2022-03-28 13:22:46 +00:00
"""
set_backflush_based_on("Material Transferred for Subcontract")
item_code = "Subcontracted Item SA2"
items = [{"warehouse": "_Test Warehouse - _TC", "item_code": item_code, "qty": 10, "rate": 100}]
rm_items = [{"item_code": "Subcontracted SRM Item 2", "qty": 5}]
itemwise_details = make_stock_in_entry(rm_items=rm_items)
po = create_purchase_order(
rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC"
)
for d in rm_items:
d["po_detail"] = po.items[0].name
make_stock_transfer_entry(
po_no=po.name,
main_item_code=item_code,
rm_items=rm_items,
itemwise_details=copy.deepcopy(itemwise_details),
)
pr1 = make_purchase_receipt(po.name)
pr1.items[0].qty = 5
pr1.save()
for key, value in get_supplied_items(pr1).items():
details = itemwise_details.get(key)
self.assertEqual(value.qty, 3)
self.assertEqual(sorted(value.serial_no), sorted(details.serial_no[0:3]))
pr1.load_from_db()
pr1.supplied_items[0].consumed_qty = 5
pr1.supplied_items[0].serial_no = "\n".join(
itemwise_details[pr1.supplied_items[0].rm_item_code]["serial_no"]
)
pr1.save()
pr1.submit()
for key, value in get_supplied_items(pr1).items():
details = itemwise_details.get(key)
self.assertEqual(value.qty, details.qty)
self.assertEqual(sorted(value.serial_no), sorted(details.serial_no))
itemwise_details = make_stock_in_entry(rm_items=rm_items)
for d in rm_items:
d["po_detail"] = po.items[0].name
make_stock_transfer_entry(
po_no=po.name,
main_item_code=item_code,
rm_items=rm_items,
itemwise_details=copy.deepcopy(itemwise_details),
)
pr1 = make_purchase_receipt(po.name)
pr1.submit()
for key, value in get_supplied_items(pr1).items():
details = itemwise_details.get(key)
self.assertEqual(value.qty, details.qty)
self.assertEqual(sorted(value.serial_no), sorted(details.serial_no))
2021-06-18 15:07:42 +00:00
def test_incorrect_serial_no_components_based_on_material_transfer(self):
2022-03-28 13:22:46 +00:00
"""
2021-06-18 15:07:42 +00:00
- Set backflush based on Material Transferred for Subcontract
- Create subcontracted PO for the item Subcontracted Item SA2.
- Transfer the serialized componenets to the supplier.
- Create purchase receipt and change the serial no which is not transferred.
- System should throw the error and not allowed to save the purchase receipt.
2022-03-28 13:22:46 +00:00
"""
2021-06-18 15:07:42 +00:00
set_backflush_based_on("Material Transferred for Subcontract")
item_code = "Subcontracted Item SA2"
items = [{"warehouse": "_Test Warehouse - _TC", "item_code": item_code, "qty": 10, "rate": 100}]
rm_items = [{"item_code": "Subcontracted SRM Item 2", "qty": 10}]
itemwise_details = make_stock_in_entry(rm_items=rm_items)
po = create_purchase_order(
rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC"
2021-06-18 15:07:42 +00:00
)
for d in rm_items:
d["po_detail"] = po.items[0].name
make_stock_transfer_entry(
po_no=po.name,
main_item_code=item_code,
rm_items=rm_items,
itemwise_details=copy.deepcopy(itemwise_details),
)
pr1 = make_purchase_receipt(po.name)
pr1.save()
pr1.supplied_items[0].serial_no = "ABCD"
self.assertRaises(frappe.ValidationError, pr1.save)
pr1.delete()
def test_partial_transfer_batch_based_on_material_transfer(self):
2022-03-28 13:22:46 +00:00
"""
- Set backflush based on Material Transferred for Subcontract
- Create subcontracted PO for the item Subcontracted Item SA6.
- Transfer the partial components from Stores to Supplier warehouse with batch.
- Create partial purchase receipt against the PO and change the qty manually.
- Transfer the remaining components from Stores to Supplier warehouse with batch.
- Create purchase receipt for remaining qty against the PO and change the qty manually.
2022-03-28 13:22:46 +00:00
"""
set_backflush_based_on("Material Transferred for Subcontract")
item_code = "Subcontracted Item SA6"
items = [{"warehouse": "_Test Warehouse - _TC", "item_code": item_code, "qty": 10, "rate": 100}]
rm_items = [{"item_code": "Subcontracted SRM Item 3", "qty": 5}]
itemwise_details = make_stock_in_entry(rm_items=rm_items)
po = create_purchase_order(
rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC"
)
for d in rm_items:
d["po_detail"] = po.items[0].name
make_stock_transfer_entry(
po_no=po.name,
main_item_code=item_code,
rm_items=rm_items,
itemwise_details=copy.deepcopy(itemwise_details),
)
pr1 = make_purchase_receipt(po.name)
pr1.items[0].qty = 5
pr1.save()
transferred_batch_no = ""
for key, value in get_supplied_items(pr1).items():
details = itemwise_details.get(key)
self.assertEqual(value.qty, 3)
transferred_batch_no = details.batch_no
self.assertEqual(value.batch_no, details.batch_no)
pr1.load_from_db()
pr1.supplied_items[0].consumed_qty = 5
pr1.supplied_items[0].batch_no = list(transferred_batch_no.keys())[0]
pr1.save()
pr1.submit()
for key, value in get_supplied_items(pr1).items():
details = itemwise_details.get(key)
self.assertEqual(value.qty, details.qty)
self.assertEqual(value.batch_no, details.batch_no)
itemwise_details = make_stock_in_entry(rm_items=rm_items)
for d in rm_items:
d["po_detail"] = po.items[0].name
make_stock_transfer_entry(
po_no=po.name,
main_item_code=item_code,
rm_items=rm_items,
itemwise_details=copy.deepcopy(itemwise_details),
)
pr1 = make_purchase_receipt(po.name)
pr1.submit()
for key, value in get_supplied_items(pr1).items():
details = itemwise_details.get(key)
self.assertEqual(value.qty, details.qty)
self.assertEqual(value.batch_no, details.batch_no)
def test_item_with_batch_based_on_material_transfer_for_purchase_invoice(self):
2022-03-28 13:22:46 +00:00
"""
- Set backflush based on Material Transferred for Subcontract
- Create subcontracted PO for the item Subcontracted Item SA4 (has batch no).
- Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
- Transfer the components in multiple batches with extra 2 qty for the batched item.
- Create the 3 purchase receipt against the PO and split Subcontracted Items into two batches.
- Keep the qty as 2 for Subcontracted Item in the purchase receipt.
- In the first purchase receipt the batched raw materials will be consumed 2 extra qty.
2022-03-28 13:22:46 +00:00
"""
set_backflush_based_on("Material Transferred for Subcontract")
item_code = "Subcontracted Item SA4"
items = [{"warehouse": "_Test Warehouse - _TC", "item_code": item_code, "qty": 10, "rate": 100}]
2022-03-28 13:22:46 +00:00
rm_items = [
{"item_code": "Subcontracted SRM Item 1", "qty": 10},
{"item_code": "Subcontracted SRM Item 2", "qty": 10},
{"item_code": "Subcontracted SRM Item 3", "qty": 3},
{"item_code": "Subcontracted SRM Item 3", "qty": 3},
{"item_code": "Subcontracted SRM Item 3", "qty": 3},
{"item_code": "Subcontracted SRM Item 3", "qty": 3},
]
itemwise_details = make_stock_in_entry(rm_items=rm_items)
po = create_purchase_order(
rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC"
)
for d in rm_items:
d["po_detail"] = po.items[0].name
make_stock_transfer_entry(
po_no=po.name,
main_item_code=item_code,
rm_items=rm_items,
itemwise_details=copy.deepcopy(itemwise_details),
)
pr1 = make_purchase_invoice(po.name)
pr1.update_stock = 1
pr1.items[0].qty = 2
pr1.items[0].expense_account = "Stock Adjustment - _TC"
add_second_row_in_pr(pr1)
pr1.save()
pr1.submit()
for key, value in get_supplied_items(pr1).items():
qty = 4 if key != "Subcontracted SRM Item 3" else 6
self.assertEqual(value.qty, qty)
pr1 = make_purchase_invoice(po.name)
pr1.update_stock = 1
pr1.items[0].expense_account = "Stock Adjustment - _TC"
pr1.items[0].qty = 2
add_second_row_in_pr(pr1)
pr1.save()
pr1.submit()
for key, value in get_supplied_items(pr1).items():
self.assertEqual(value.qty, 4)
pr1 = make_purchase_invoice(po.name)
pr1.update_stock = 1
pr1.items[0].qty = 2
pr1.items[0].expense_account = "Stock Adjustment - _TC"
pr1.save()
pr1.submit()
for key, value in get_supplied_items(pr1).items():
self.assertEqual(value.qty, 2)
def test_partial_transfer_serial_no_components_based_on_material_transfer_for_purchase_invoice(
self,
):
2022-03-28 13:22:46 +00:00
"""
- Set backflush based on Material Transferred for Subcontract
- Create subcontracted PO for the item Subcontracted Item SA2.
- Transfer the partial components from Stores to Supplier warehouse with serial nos.
- Create partial purchase receipt against the PO and change the qty manually.
- Transfer the remaining components from Stores to Supplier warehouse with serial nos.
- Create purchase receipt for remaining qty against the PO and change the qty manually.
2022-03-28 13:22:46 +00:00
"""
set_backflush_based_on("Material Transferred for Subcontract")
item_code = "Subcontracted Item SA2"
items = [{"warehouse": "_Test Warehouse - _TC", "item_code": item_code, "qty": 10, "rate": 100}]
rm_items = [{"item_code": "Subcontracted SRM Item 2", "qty": 5}]
itemwise_details = make_stock_in_entry(rm_items=rm_items)
po = create_purchase_order(
rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC"
)
for d in rm_items:
d["po_detail"] = po.items[0].name
make_stock_transfer_entry(
po_no=po.name,
main_item_code=item_code,
rm_items=rm_items,
itemwise_details=copy.deepcopy(itemwise_details),
)
pr1 = make_purchase_invoice(po.name)
pr1.update_stock = 1
pr1.items[0].qty = 5
pr1.items[0].expense_account = "Stock Adjustment - _TC"
pr1.save()
for key, value in get_supplied_items(pr1).items():
details = itemwise_details.get(key)
self.assertEqual(value.qty, 3)
self.assertEqual(sorted(value.serial_no), sorted(details.serial_no[0:3]))
pr1.load_from_db()
pr1.supplied_items[0].consumed_qty = 5
pr1.supplied_items[0].serial_no = "\n".join(
itemwise_details[pr1.supplied_items[0].rm_item_code]["serial_no"]
)
pr1.save()
pr1.submit()
for key, value in get_supplied_items(pr1).items():
details = itemwise_details.get(key)
self.assertEqual(value.qty, details.qty)
self.assertEqual(sorted(value.serial_no), sorted(details.serial_no))
itemwise_details = make_stock_in_entry(rm_items=rm_items)
for d in rm_items:
d["po_detail"] = po.items[0].name
make_stock_transfer_entry(
po_no=po.name,
main_item_code=item_code,
rm_items=rm_items,
itemwise_details=copy.deepcopy(itemwise_details),
)
pr1 = make_purchase_invoice(po.name)
pr1.update_stock = 1
pr1.items[0].expense_account = "Stock Adjustment - _TC"
pr1.submit()
for key, value in get_supplied_items(pr1).items():
details = itemwise_details.get(key)
self.assertEqual(value.qty, details.qty)
self.assertEqual(sorted(value.serial_no), sorted(details.serial_no))
def test_partial_transfer_batch_based_on_material_transfer_for_purchase_invoice(self):
2022-03-28 13:22:46 +00:00
"""
- Set backflush based on Material Transferred for Subcontract
- Create subcontracted PO for the item Subcontracted Item SA6.
- Transfer the partial components from Stores to Supplier warehouse with batch.
- Create partial purchase receipt against the PO and change the qty manually.
- Transfer the remaining components from Stores to Supplier warehouse with batch.
- Create purchase receipt for remaining qty against the PO and change the qty manually.
2022-03-28 13:22:46 +00:00
"""
set_backflush_based_on("Material Transferred for Subcontract")
item_code = "Subcontracted Item SA6"
items = [{"warehouse": "_Test Warehouse - _TC", "item_code": item_code, "qty": 10, "rate": 100}]
rm_items = [{"item_code": "Subcontracted SRM Item 3", "qty": 5}]
itemwise_details = make_stock_in_entry(rm_items=rm_items)
po = create_purchase_order(
rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC"
)
for d in rm_items:
d["po_detail"] = po.items[0].name
make_stock_transfer_entry(
po_no=po.name,
main_item_code=item_code,
rm_items=rm_items,
itemwise_details=copy.deepcopy(itemwise_details),
)
pr1 = make_purchase_invoice(po.name)
pr1.update_stock = 1
pr1.items[0].qty = 5
pr1.items[0].expense_account = "Stock Adjustment - _TC"
pr1.save()
transferred_batch_no = ""
for key, value in get_supplied_items(pr1).items():
details = itemwise_details.get(key)
self.assertEqual(value.qty, 3)
transferred_batch_no = details.batch_no
self.assertEqual(value.batch_no, details.batch_no)
pr1.load_from_db()
pr1.supplied_items[0].consumed_qty = 5
pr1.supplied_items[0].batch_no = list(transferred_batch_no.keys())[0]
pr1.save()
pr1.submit()
for key, value in get_supplied_items(pr1).items():
details = itemwise_details.get(key)
self.assertEqual(value.qty, details.qty)
self.assertEqual(value.batch_no, details.batch_no)
itemwise_details = make_stock_in_entry(rm_items=rm_items)
for d in rm_items:
d["po_detail"] = po.items[0].name
make_stock_transfer_entry(
po_no=po.name,
main_item_code=item_code,
rm_items=rm_items,
itemwise_details=copy.deepcopy(itemwise_details),
)
pr1 = make_purchase_invoice(po.name)
pr1.update_stock = 1
pr1.items[0].expense_account = "Stock Adjustment - _TC"
pr1.submit()
for key, value in get_supplied_items(pr1).items():
details = itemwise_details.get(key)
self.assertEqual(value.qty, details.qty)
self.assertEqual(value.batch_no, details.batch_no)
def test_item_with_batch_based_on_bom_for_purchase_invoice(self):
2022-03-28 13:22:46 +00:00
"""
- Set backflush based on BOM
- Create subcontracted PO for the item Subcontracted Item SA4 (has batch no).
- Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
- Transfer the components in multiple batches.
- Create the 3 purchase receipt against the PO and split Subcontracted Items into two batches.
- Keep the qty as 2 for Subcontracted Item in the purchase receipt.
2022-03-28 13:22:46 +00:00
"""
set_backflush_based_on("BOM")
item_code = "Subcontracted Item SA4"
items = [{"warehouse": "_Test Warehouse - _TC", "item_code": item_code, "qty": 10, "rate": 100}]
2022-03-28 13:22:46 +00:00
rm_items = [
{"item_code": "Subcontracted SRM Item 1", "qty": 10},
{"item_code": "Subcontracted SRM Item 2", "qty": 10},
{"item_code": "Subcontracted SRM Item 3", "qty": 3},
{"item_code": "Subcontracted SRM Item 3", "qty": 3},
{"item_code": "Subcontracted SRM Item 3", "qty": 3},
{"item_code": "Subcontracted SRM Item 3", "qty": 1},
]
itemwise_details = make_stock_in_entry(rm_items=rm_items)
po = create_purchase_order(
rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC"
)
for d in rm_items:
d["po_detail"] = po.items[0].name
make_stock_transfer_entry(
po_no=po.name,
main_item_code=item_code,
rm_items=rm_items,
itemwise_details=copy.deepcopy(itemwise_details),
)
pr1 = make_purchase_invoice(po.name)
pr1.update_stock = 1
pr1.items[0].qty = 2
pr1.items[0].expense_account = "Stock Adjustment - _TC"
add_second_row_in_pr(pr1)
pr1.save()
pr1.submit()
for key, value in get_supplied_items(pr1).items():
self.assertEqual(value.qty, 4)
pr1 = make_purchase_invoice(po.name)
pr1.update_stock = 1
pr1.items[0].qty = 2
pr1.items[0].expense_account = "Stock Adjustment - _TC"
add_second_row_in_pr(pr1)
pr1.save()
pr1.submit()
for key, value in get_supplied_items(pr1).items():
self.assertEqual(value.qty, 4)
pr1 = make_purchase_invoice(po.name)
pr1.update_stock = 1
pr1.items[0].qty = 2
pr1.items[0].expense_account = "Stock Adjustment - _TC"
pr1.save()
pr1.submit()
for key, value in get_supplied_items(pr1).items():
self.assertEqual(value.qty, 2)
def test_po_supplied_qty(self):
"""
Check if 'Supplied Qty' in PO's Supplied Items table is reset on submit/cancel.
"""
set_backflush_based_on("Material Transferred for Subcontract")
items = [
{
"warehouse": "_Test Warehouse - _TC",
"item_code": "Subcontracted Item SA1",
"qty": 5,
"rate": 100,
},
{
"warehouse": "_Test Warehouse - _TC",
"item_code": "Subcontracted Item SA5",
"qty": 6,
"rate": 100,
},
]
rm_items = [
{"item_code": "Subcontracted SRM Item 1", "qty": 5, "main_item_code": "Subcontracted Item SA1"},
{"item_code": "Subcontracted SRM Item 2", "qty": 5, "main_item_code": "Subcontracted Item SA1"},
{"item_code": "Subcontracted SRM Item 3", "qty": 5, "main_item_code": "Subcontracted Item SA1"},
{"item_code": "Subcontracted SRM Item 5", "qty": 6, "main_item_code": "Subcontracted Item SA5"},
{"item_code": "Subcontracted SRM Item 4", "qty": 6, "main_item_code": "Subcontracted Item SA5"},
]
itemwise_details = make_stock_in_entry(rm_items=rm_items)
po = create_purchase_order(
rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC"
)
for d in rm_items:
d["po_detail"] = po.items[0].name if d.get("qty") == 5 else po.items[1].name
se = make_stock_transfer_entry(
po_no=po.name, rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details)
)
po.reload()
for row in po.get("supplied_items"):
self.assertIn(row.supplied_qty, [5.0, 6.0])
se.cancel()
po.reload()
for row in po.get("supplied_items"):
self.assertEqual(row.supplied_qty, 0.0)
2022-03-28 13:22:46 +00:00
def add_second_row_in_pr(pr):
item_dict = {}
for column in [
"item_code",
"item_name",
"qty",
"uom",
"warehouse",
"stock_uom",
"purchase_order",
"purchase_order_item",
"conversion_factor",
"rate",
"expense_account",
"po_detail",
]:
item_dict[column] = pr.items[0].get(column)
pr.append("items", item_dict)
pr.set_missing_values()
2022-03-28 13:22:46 +00:00
def get_supplied_items(pr_doc):
supplied_items = {}
for row in pr_doc.get("supplied_items"):
if row.rm_item_code not in supplied_items:
supplied_items.setdefault(
row.rm_item_code, frappe._dict({"qty": 0, "serial_no": [], "batch_no": defaultdict(float)})
2022-03-28 13:22:46 +00:00
)
details = supplied_items[row.rm_item_code]
update_item_details(row, details)
return supplied_items
2022-03-28 13:22:46 +00:00
def make_stock_in_entry(**args):
args = frappe._dict(args)
items = {}
for row in args.rm_items:
row = frappe._dict(row)
doc = make_stock_entry(
target=row.warehouse or "_Test Warehouse - _TC",
item_code=row.item_code,
qty=row.qty or 1,
basic_rate=row.rate or 100,
)
if row.item_code not in items:
items.setdefault(
row.item_code, frappe._dict({"qty": 0, "serial_no": [], "batch_no": defaultdict(float)})
2022-03-28 13:22:46 +00:00
)
child_row = doc.items[0]
details = items[child_row.item_code]
update_item_details(child_row, details)
return items
2022-03-28 13:22:46 +00:00
def update_item_details(child_row, details):
details.qty += (
child_row.get("qty")
if child_row.doctype == "Stock Entry Detail"
else child_row.get("consumed_qty")
2022-03-28 13:22:46 +00:00
)
if child_row.serial_no:
details.serial_no.extend(get_serial_nos(child_row.serial_no))
if child_row.batch_no:
details.batch_no[child_row.batch_no] += child_row.get("qty") or child_row.get("consumed_qty")
2022-03-28 13:22:46 +00:00
def make_stock_transfer_entry(**args):
args = frappe._dict(args)
items = []
for row in args.rm_items:
row = frappe._dict(row)
item = {
"item_code": row.main_item_code or args.main_item_code,
"rm_item_code": row.item_code,
"qty": row.qty or 1,
"item_name": row.item_code,
"rate": row.rate or 100,
"stock_uom": row.stock_uom or "Nos",
"warehouse": row.warehuose or "_Test Warehouse - _TC",
}
item_details = args.itemwise_details.get(row.item_code)
if item_details and item_details.serial_no:
serial_nos = item_details.serial_no[0 : cint(row.qty)]
item["serial_no"] = "\n".join(serial_nos)
item_details.serial_no = list(set(item_details.serial_no) - set(serial_nos))
if item_details and item_details.batch_no:
for batch_no, batch_qty in item_details.batch_no.items():
if batch_qty >= row.qty:
item["batch_no"] = batch_no
item_details.batch_no[batch_no] -= row.qty
break
items.append(item)
ste_dict = make_rm_stock_entry(args.po_no, items)
doc = frappe.get_doc(ste_dict)
doc.insert()
doc.submit()
return doc
2022-03-28 13:22:46 +00:00
def make_subcontract_items():
sub_contracted_items = {
"Subcontracted Item SA1": {},
"Subcontracted Item SA2": {},
"Subcontracted Item SA3": {},
"Subcontracted Item SA4": {
"has_batch_no": 1,
"create_new_batch": 1,
"batch_number_series": "SBAT.####",
},
"Subcontracted Item SA5": {},
"Subcontracted Item SA6": {},
}
for item, properties in sub_contracted_items.items():
if not frappe.db.exists("Item", item):
properties.update({"is_stock_item": 1, "is_sub_contracted_item": 1})
make_item(item, properties)
2022-03-28 13:22:46 +00:00
def make_raw_materials():
raw_materials = {
"Subcontracted SRM Item 1": {},
"Subcontracted SRM Item 2": {"has_serial_no": 1, "serial_no_series": "SRI.####"},
"Subcontracted SRM Item 3": {
"has_batch_no": 1,
"create_new_batch": 1,
"batch_number_series": "BAT.####",
},
"Subcontracted SRM Item 4": {"has_serial_no": 1, "serial_no_series": "SRII.####"},
"Subcontracted SRM Item 5": {"has_serial_no": 1, "serial_no_series": "SRII.####"},
}
for item, properties in raw_materials.items():
if not frappe.db.exists("Item", item):
properties.update({"is_stock_item": 1})
make_item(item, properties)
2022-03-28 13:22:46 +00:00
def make_bom_for_subcontracted_items():
boms = {
"Subcontracted Item SA1": [
"Subcontracted SRM Item 1",
"Subcontracted SRM Item 2",
"Subcontracted SRM Item 3",
],
"Subcontracted Item SA2": ["Subcontracted SRM Item 2"],
"Subcontracted Item SA3": ["Subcontracted SRM Item 2"],
"Subcontracted Item SA4": [
"Subcontracted SRM Item 1",
"Subcontracted SRM Item 2",
"Subcontracted SRM Item 3",
],
"Subcontracted Item SA5": ["Subcontracted SRM Item 5"],
"Subcontracted Item SA6": ["Subcontracted SRM Item 3"],
}
for item_code, raw_materials in boms.items():
if not frappe.db.exists("BOM", {"item": item_code}):
make_bom(item=item_code, raw_materials=raw_materials, rate=100)
2022-03-28 13:22:46 +00:00
def set_backflush_based_on(based_on):
frappe.db.set_value(
"Buying Settings", None, "backflush_raw_materials_of_subcontract_based_on", based_on
)