Merge branch 'develop' into fix-22-23-05686

This commit is contained in:
Sagar Sharma 2023-03-14 20:59:38 +05:30 committed by GitHub
commit 357d4994e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 90 additions and 15 deletions

View File

@ -31,7 +31,7 @@ class BOMTree:
# specifying the attributes to save resources # specifying the attributes to save resources
# ref: https://docs.python.org/3/reference/datamodel.html#slots # ref: https://docs.python.org/3/reference/datamodel.html#slots
__slots__ = ["name", "child_items", "is_bom", "item_code", "exploded_qty", "qty"] __slots__ = ["name", "child_items", "is_bom", "item_code", "qty", "exploded_qty", "bom_qty"]
def __init__( def __init__(
self, name: str, is_bom: bool = True, exploded_qty: float = 1.0, qty: float = 1 self, name: str, is_bom: bool = True, exploded_qty: float = 1.0, qty: float = 1
@ -50,9 +50,10 @@ class BOMTree:
def __create_tree(self): def __create_tree(self):
bom = frappe.get_cached_doc("BOM", self.name) bom = frappe.get_cached_doc("BOM", self.name)
self.item_code = bom.item self.item_code = bom.item
self.bom_qty = bom.quantity
for item in bom.get("items", []): for item in bom.get("items", []):
qty = item.qty / bom.quantity # quantity per unit qty = item.stock_qty / bom.quantity # quantity per unit
exploded_qty = self.exploded_qty * qty exploded_qty = self.exploded_qty * qty
if item.bom_no: if item.bom_no:
child = BOMTree(item.bom_no, exploded_qty=exploded_qty, qty=qty) child = BOMTree(item.bom_no, exploded_qty=exploded_qty, qty=qty)

View File

@ -6,7 +6,7 @@ from collections import deque
from functools import partial from functools import partial
import frappe import frappe
from frappe.tests.utils import FrappeTestCase from frappe.tests.utils import FrappeTestCase, timeout
from frappe.utils import cstr, flt from frappe.utils import cstr, flt
from erpnext.controllers.tests.test_subcontracting_controller import ( from erpnext.controllers.tests.test_subcontracting_controller import (
@ -27,6 +27,7 @@ test_dependencies = ["Item", "Quality Inspection Template"]
class TestBOM(FrappeTestCase): class TestBOM(FrappeTestCase):
@timeout
def test_get_items(self): def test_get_items(self):
from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict
@ -37,6 +38,7 @@ class TestBOM(FrappeTestCase):
self.assertTrue(test_records[2]["items"][1]["item_code"] in items_dict) self.assertTrue(test_records[2]["items"][1]["item_code"] in items_dict)
self.assertEqual(len(items_dict.values()), 2) self.assertEqual(len(items_dict.values()), 2)
@timeout
def test_get_items_exploded(self): def test_get_items_exploded(self):
from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict
@ -49,11 +51,13 @@ class TestBOM(FrappeTestCase):
self.assertTrue(test_records[0]["items"][1]["item_code"] in items_dict) self.assertTrue(test_records[0]["items"][1]["item_code"] in items_dict)
self.assertEqual(len(items_dict.values()), 3) self.assertEqual(len(items_dict.values()), 3)
@timeout
def test_get_items_list(self): def test_get_items_list(self):
from erpnext.manufacturing.doctype.bom.bom import get_bom_items from erpnext.manufacturing.doctype.bom.bom import get_bom_items
self.assertEqual(len(get_bom_items(bom=get_default_bom(), company="_Test Company")), 3) self.assertEqual(len(get_bom_items(bom=get_default_bom(), company="_Test Company")), 3)
@timeout
def test_default_bom(self): def test_default_bom(self):
def _get_default_bom_in_item(): def _get_default_bom_in_item():
return cstr(frappe.db.get_value("Item", "_Test FG Item 2", "default_bom")) return cstr(frappe.db.get_value("Item", "_Test FG Item 2", "default_bom"))
@ -71,6 +75,7 @@ class TestBOM(FrappeTestCase):
self.assertTrue(_get_default_bom_in_item(), bom.name) self.assertTrue(_get_default_bom_in_item(), bom.name)
@timeout
def test_update_bom_cost_in_all_boms(self): def test_update_bom_cost_in_all_boms(self):
# get current rate for '_Test Item 2' # get current rate for '_Test Item 2'
bom_rates = frappe.db.get_values( bom_rates = frappe.db.get_values(
@ -99,6 +104,7 @@ class TestBOM(FrappeTestCase):
): ):
self.assertEqual(d.base_rate, rm_base_rate + 10) self.assertEqual(d.base_rate, rm_base_rate + 10)
@timeout
def test_bom_cost(self): def test_bom_cost(self):
bom = frappe.copy_doc(test_records[2]) bom = frappe.copy_doc(test_records[2])
bom.insert() bom.insert()
@ -127,6 +133,7 @@ class TestBOM(FrappeTestCase):
self.assertAlmostEqual(bom.base_raw_material_cost, base_raw_material_cost) self.assertAlmostEqual(bom.base_raw_material_cost, base_raw_material_cost)
self.assertAlmostEqual(bom.base_total_cost, base_raw_material_cost + base_op_cost) self.assertAlmostEqual(bom.base_total_cost, base_raw_material_cost + base_op_cost)
@timeout
def test_bom_cost_with_batch_size(self): def test_bom_cost_with_batch_size(self):
bom = frappe.copy_doc(test_records[2]) bom = frappe.copy_doc(test_records[2])
bom.docstatus = 0 bom.docstatus = 0
@ -145,6 +152,7 @@ class TestBOM(FrappeTestCase):
self.assertAlmostEqual(bom.operating_cost, op_cost / 2) self.assertAlmostEqual(bom.operating_cost, op_cost / 2)
bom.delete() bom.delete()
@timeout
def test_bom_cost_multi_uom_multi_currency_based_on_price_list(self): def test_bom_cost_multi_uom_multi_currency_based_on_price_list(self):
frappe.db.set_value("Price List", "_Test Price List", "price_not_uom_dependent", 1) frappe.db.set_value("Price List", "_Test Price List", "price_not_uom_dependent", 1)
for item_code, rate in (("_Test Item", 3600), ("_Test Item Home Desktop Manufactured", 3000)): for item_code, rate in (("_Test Item", 3600), ("_Test Item Home Desktop Manufactured", 3000)):
@ -181,6 +189,7 @@ class TestBOM(FrappeTestCase):
self.assertEqual(bom.base_raw_material_cost, 27000) self.assertEqual(bom.base_raw_material_cost, 27000)
self.assertEqual(bom.base_total_cost, 33000) self.assertEqual(bom.base_total_cost, 33000)
@timeout
def test_bom_cost_multi_uom_based_on_valuation_rate(self): def test_bom_cost_multi_uom_based_on_valuation_rate(self):
bom = frappe.copy_doc(test_records[2]) bom = frappe.copy_doc(test_records[2])
bom.set_rate_of_sub_assembly_item_based_on_bom = 0 bom.set_rate_of_sub_assembly_item_based_on_bom = 0
@ -202,6 +211,7 @@ class TestBOM(FrappeTestCase):
self.assertEqual(bom.items[0].rate, 20) self.assertEqual(bom.items[0].rate, 20)
@timeout
def test_bom_cost_with_fg_based_operating_cost(self): def test_bom_cost_with_fg_based_operating_cost(self):
bom = frappe.copy_doc(test_records[4]) bom = frappe.copy_doc(test_records[4])
bom.insert() bom.insert()
@ -229,6 +239,7 @@ class TestBOM(FrappeTestCase):
self.assertAlmostEqual(bom.base_raw_material_cost, base_raw_material_cost) self.assertAlmostEqual(bom.base_raw_material_cost, base_raw_material_cost)
self.assertAlmostEqual(bom.base_total_cost, base_raw_material_cost + base_op_cost) self.assertAlmostEqual(bom.base_total_cost, base_raw_material_cost + base_op_cost)
@timeout
def test_subcontractor_sourced_item(self): def test_subcontractor_sourced_item(self):
item_code = "_Test Subcontracted FG Item 1" item_code = "_Test Subcontracted FG Item 1"
set_backflush_based_on("Material Transferred for Subcontract") set_backflush_based_on("Material Transferred for Subcontract")
@ -310,6 +321,7 @@ class TestBOM(FrappeTestCase):
supplied_items = sorted([d.rm_item_code for d in sco.supplied_items]) supplied_items = sorted([d.rm_item_code for d in sco.supplied_items])
self.assertEqual(bom_items, supplied_items) self.assertEqual(bom_items, supplied_items)
@timeout
def test_bom_tree_representation(self): def test_bom_tree_representation(self):
bom_tree = { bom_tree = {
"Assembly": { "Assembly": {
@ -335,6 +347,7 @@ class TestBOM(FrappeTestCase):
for reqd_item, created_item in zip(reqd_order, created_order): for reqd_item, created_item in zip(reqd_order, created_order):
self.assertEqual(reqd_item, created_item.item_code) self.assertEqual(reqd_item, created_item.item_code)
@timeout
def test_generated_variant_bom(self): def test_generated_variant_bom(self):
from erpnext.controllers.item_variant import create_variant from erpnext.controllers.item_variant import create_variant
@ -375,6 +388,7 @@ class TestBOM(FrappeTestCase):
self.assertEqual(reqd_item.qty, created_item.qty) self.assertEqual(reqd_item.qty, created_item.qty)
self.assertEqual(reqd_item.exploded_qty, created_item.exploded_qty) self.assertEqual(reqd_item.exploded_qty, created_item.exploded_qty)
@timeout
def test_bom_recursion_1st_level(self): def test_bom_recursion_1st_level(self):
"""BOM should not allow BOM item again in child""" """BOM should not allow BOM item again in child"""
item_code = make_item(properties={"is_stock_item": 1}).name item_code = make_item(properties={"is_stock_item": 1}).name
@ -387,6 +401,7 @@ class TestBOM(FrappeTestCase):
bom.items[0].bom_no = bom.name bom.items[0].bom_no = bom.name
bom.save() bom.save()
@timeout
def test_bom_recursion_transitive(self): def test_bom_recursion_transitive(self):
item1 = make_item(properties={"is_stock_item": 1}).name item1 = make_item(properties={"is_stock_item": 1}).name
item2 = make_item(properties={"is_stock_item": 1}).name item2 = make_item(properties={"is_stock_item": 1}).name
@ -408,6 +423,7 @@ class TestBOM(FrappeTestCase):
bom1.save() bom1.save()
bom2.save() bom2.save()
@timeout
def test_bom_with_process_loss_item(self): def test_bom_with_process_loss_item(self):
fg_item_non_whole, fg_item_whole, bom_item = create_process_loss_bom_items() fg_item_non_whole, fg_item_whole, bom_item = create_process_loss_bom_items()
@ -421,6 +437,7 @@ class TestBOM(FrappeTestCase):
# Items with whole UOMs can't be PL Items # Items with whole UOMs can't be PL Items
self.assertRaises(frappe.ValidationError, bom_doc.submit) self.assertRaises(frappe.ValidationError, bom_doc.submit)
@timeout
def test_bom_item_query(self): def test_bom_item_query(self):
query = partial( query = partial(
item_query, item_query,
@ -440,6 +457,7 @@ class TestBOM(FrappeTestCase):
) )
self.assertTrue(0 < len(filtered) <= 3, msg="Item filtering showing excessive results") self.assertTrue(0 < len(filtered) <= 3, msg="Item filtering showing excessive results")
@timeout
def test_exclude_exploded_items_from_bom(self): def test_exclude_exploded_items_from_bom(self):
bom_no = get_default_bom() bom_no = get_default_bom()
new_bom = frappe.copy_doc(frappe.get_doc("BOM", bom_no)) new_bom = frappe.copy_doc(frappe.get_doc("BOM", bom_no))
@ -458,6 +476,7 @@ class TestBOM(FrappeTestCase):
new_bom.delete() new_bom.delete()
@timeout
def test_valid_transfer_defaults(self): def test_valid_transfer_defaults(self):
bom_with_op = frappe.db.get_value( bom_with_op = frappe.db.get_value(
"BOM", {"item": "_Test FG Item 2", "with_operations": 1, "is_active": 1} "BOM", {"item": "_Test FG Item 2", "with_operations": 1, "is_active": 1}
@ -489,11 +508,13 @@ class TestBOM(FrappeTestCase):
self.assertEqual(bom.transfer_material_against, "Work Order") self.assertEqual(bom.transfer_material_against, "Work Order")
bom.delete() bom.delete()
@timeout
def test_bom_name_length(self): def test_bom_name_length(self):
"""test >140 char names""" """test >140 char names"""
bom_tree = {"x" * 140: {" ".join(["abc"] * 35): {}}} bom_tree = {"x" * 140: {" ".join(["abc"] * 35): {}}}
create_nested_bom(bom_tree, prefix="") create_nested_bom(bom_tree, prefix="")
@timeout
def test_version_index(self): def test_version_index(self):
bom = frappe.new_doc("BOM") bom = frappe.new_doc("BOM")
@ -515,6 +536,7 @@ class TestBOM(FrappeTestCase):
msg=f"Incorrect index for {existing_boms}", msg=f"Incorrect index for {existing_boms}",
) )
@timeout
def test_bom_versioning(self): def test_bom_versioning(self):
bom_tree = {frappe.generate_hash(length=10): {frappe.generate_hash(length=10): {}}} bom_tree = {frappe.generate_hash(length=10): {frappe.generate_hash(length=10): {}}}
bom = create_nested_bom(bom_tree, prefix="") bom = create_nested_bom(bom_tree, prefix="")
@ -547,6 +569,7 @@ class TestBOM(FrappeTestCase):
self.assertNotEqual(amendment.name, version.name) self.assertNotEqual(amendment.name, version.name)
self.assertEqual(int(version.name.split("-")[-1]), 2) self.assertEqual(int(version.name.split("-")[-1]), 2)
@timeout
def test_clear_inpection_quality(self): def test_clear_inpection_quality(self):
bom = frappe.copy_doc(test_records[2], ignore_no_copy=True) bom = frappe.copy_doc(test_records[2], ignore_no_copy=True)
@ -565,6 +588,7 @@ class TestBOM(FrappeTestCase):
self.assertEqual(bom.quality_inspection_template, None) self.assertEqual(bom.quality_inspection_template, None)
@timeout
def test_bom_pricing_based_on_lpp(self): def test_bom_pricing_based_on_lpp(self):
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
@ -585,6 +609,7 @@ class TestBOM(FrappeTestCase):
bom.submit() bom.submit()
self.assertEqual(bom.items[0].rate, 42) self.assertEqual(bom.items[0].rate, 42)
@timeout
def test_set_default_bom_for_item_having_single_bom(self): def test_set_default_bom_for_item_having_single_bom(self):
from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.item.test_item import make_item
@ -621,6 +646,7 @@ class TestBOM(FrappeTestCase):
bom.reload() bom.reload()
self.assertEqual(frappe.get_value("Item", fg_item.item_code, "default_bom"), bom.name) self.assertEqual(frappe.get_value("Item", fg_item.item_code, "default_bom"), bom.name)
@timeout
def test_exploded_items_rate(self): def test_exploded_items_rate(self):
rm_item = make_item( rm_item = make_item(
properties={"is_stock_item": 1, "valuation_rate": 99, "last_purchase_rate": 89} properties={"is_stock_item": 1, "valuation_rate": 99, "last_purchase_rate": 89}
@ -649,6 +675,7 @@ class TestBOM(FrappeTestCase):
bom.submit() bom.submit()
self.assertEqual(bom.exploded_items[0].rate, bom.items[0].base_rate) self.assertEqual(bom.exploded_items[0].rate, bom.items[0].base_rate)
@timeout
def test_bom_cost_update_flag(self): def test_bom_cost_update_flag(self):
rm_item = make_item( rm_item = make_item(
properties={"is_stock_item": 1, "valuation_rate": 99, "last_purchase_rate": 89} properties={"is_stock_item": 1, "valuation_rate": 99, "last_purchase_rate": 89}

View File

@ -2,7 +2,7 @@
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
import frappe import frappe
from frappe.tests.utils import FrappeTestCase from frappe.tests.utils import FrappeTestCase, timeout
from erpnext.manufacturing.doctype.bom_update_log.test_bom_update_log import ( from erpnext.manufacturing.doctype.bom_update_log.test_bom_update_log import (
update_cost_in_all_boms_in_test, update_cost_in_all_boms_in_test,
@ -20,6 +20,7 @@ class TestBOMUpdateTool(FrappeTestCase):
def tearDown(self): def tearDown(self):
frappe.db.rollback() frappe.db.rollback()
@timeout
def test_replace_bom(self): def test_replace_bom(self):
current_bom = "BOM-_Test Item Home Desktop Manufactured-001" current_bom = "BOM-_Test Item Home Desktop Manufactured-001"
@ -33,6 +34,7 @@ class TestBOMUpdateTool(FrappeTestCase):
self.assertFalse(frappe.db.exists("BOM Item", {"bom_no": current_bom, "docstatus": 1})) self.assertFalse(frappe.db.exists("BOM Item", {"bom_no": current_bom, "docstatus": 1}))
self.assertTrue(frappe.db.exists("BOM Item", {"bom_no": bom_doc.name, "docstatus": 1})) self.assertTrue(frappe.db.exists("BOM Item", {"bom_no": bom_doc.name, "docstatus": 1}))
@timeout
def test_bom_cost(self): def test_bom_cost(self):
for item in ["BOM Cost Test Item 1", "BOM Cost Test Item 2", "BOM Cost Test Item 3"]: for item in ["BOM Cost Test Item 1", "BOM Cost Test Item 2", "BOM Cost Test Item 3"]:
item_doc = create_item(item, valuation_rate=100) item_doc = create_item(item, valuation_rate=100)

View File

@ -682,7 +682,7 @@ class WorkOrder(Document):
for node in bom_traversal: for node in bom_traversal:
if node.is_bom: if node.is_bom:
operations.extend(_get_operations(node.name, qty=node.exploded_qty)) operations.extend(_get_operations(node.name, qty=node.exploded_qty / node.bom_qty))
bom_qty = frappe.get_cached_value("BOM", self.bom_no, "quantity") bom_qty = frappe.get_cached_value("BOM", self.bom_no, "quantity")
operations.extend(_get_operations(self.bom_no, qty=1.0 / bom_qty)) operations.extend(_get_operations(self.bom_no, qty=1.0 / bom_qty))

View File

@ -808,7 +808,7 @@ def get_default_company_address(name, sort_key="is_primary_address", existing_ad
return existing_address return existing_address
if out: if out:
return min(out, key=lambda x: x[1])[0] # find min by sort_key return max(out, key=lambda x: x[1])[0] # find max by sort_key
else: else:
return None return None

View File

@ -11,6 +11,7 @@ from frappe.utils import random_string
from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import ( from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import (
get_charts_for_country, get_charts_for_country,
) )
from erpnext.setup.doctype.company.company import get_default_company_address
test_ignore = ["Account", "Cost Center", "Payment Terms Template", "Salary Component", "Warehouse"] test_ignore = ["Account", "Cost Center", "Payment Terms Template", "Salary Component", "Warehouse"]
test_dependencies = ["Fiscal Year"] test_dependencies = ["Fiscal Year"]
@ -132,6 +133,38 @@ class TestCompany(unittest.TestCase):
self.assertTrue(lft >= min_lft) self.assertTrue(lft >= min_lft)
self.assertTrue(rgt <= max_rgt) self.assertTrue(rgt <= max_rgt)
def test_primary_address(self):
company = "_Test Company"
secondary = frappe.get_doc(
{
"address_title": "Non Primary",
"doctype": "Address",
"address_type": "Billing",
"address_line1": "Something",
"city": "Mumbai",
"state": "Maharashtra",
"country": "India",
"is_primary_address": 1,
"pincode": "400098",
"links": [
{
"link_doctype": "Company",
"link_name": company,
}
],
}
)
secondary.insert()
self.addCleanup(secondary.delete)
primary = frappe.copy_doc(secondary)
primary.is_primary_address = 1
primary.insert()
self.addCleanup(primary.delete)
self.assertEqual(get_default_company_address(company), primary.name)
def get_no_of_children(self, company): def get_no_of_children(self, company):
def get_no_of_children(companies, no_of_children): def get_no_of_children(companies, no_of_children):
children = [] children = []

View File

@ -377,7 +377,9 @@ class Item(Document):
"" if item_barcode.barcode_type not in options else item_barcode.barcode_type "" if item_barcode.barcode_type not in options else item_barcode.barcode_type
) )
if item_barcode.barcode_type: if item_barcode.barcode_type:
barcode_type = convert_erpnext_to_barcodenumber(item_barcode.barcode_type.upper()) barcode_type = convert_erpnext_to_barcodenumber(
item_barcode.barcode_type.upper(), item_barcode.barcode
)
if barcode_type in barcodenumber.barcodes(): if barcode_type in barcodenumber.barcodes():
if not barcodenumber.check_code(barcode_type, item_barcode.barcode): if not barcodenumber.check_code(barcode_type, item_barcode.barcode):
frappe.throw( frappe.throw(
@ -982,19 +984,28 @@ class Item(Document):
) )
def convert_erpnext_to_barcodenumber(erpnext_number): def convert_erpnext_to_barcodenumber(erpnext_number, barcode):
if erpnext_number == "EAN":
ean_type = {
8: "EAN8",
13: "EAN13",
}
barcode_length = len(barcode)
if barcode_length in ean_type:
return ean_type[barcode_length]
return erpnext_number
convert = { convert = {
"UPC-A": "UPCA", "UPC-A": "UPCA",
"CODE-39": "CODE39", "CODE-39": "CODE39",
"EAN": "EAN13",
"EAN-12": "EAN",
"EAN-8": "EAN8",
"ISBN-10": "ISBN10", "ISBN-10": "ISBN10",
"ISBN-13": "ISBN13", "ISBN-13": "ISBN13",
} }
if erpnext_number in convert: if erpnext_number in convert:
return convert[erpnext_number] return convert[erpnext_number]
else:
return erpnext_number return erpnext_number

View File

@ -581,8 +581,9 @@ class TestItem(FrappeTestCase):
}, },
{"barcode": "72527273070", "barcode_type": "UPC-A"}, {"barcode": "72527273070", "barcode_type": "UPC-A"},
{"barcode": "123456", "barcode_type": "CODE-39"}, {"barcode": "123456", "barcode_type": "CODE-39"},
{"barcode": "401268452363", "barcode_type": "EAN-12"}, {"barcode": "401268452363", "barcode_type": "EAN"},
{"barcode": "90311017", "barcode_type": "EAN-8"}, {"barcode": "90311017", "barcode_type": "EAN"},
{"barcode": "73513537", "barcode_type": "EAN"},
{"barcode": "0123456789012", "barcode_type": "GS1"}, {"barcode": "0123456789012", "barcode_type": "GS1"},
{"barcode": "2211564566668", "barcode_type": "GTIN"}, {"barcode": "2211564566668", "barcode_type": "GTIN"},
{"barcode": "0256480249", "barcode_type": "ISBN"}, {"barcode": "0256480249", "barcode_type": "ISBN"},