Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions erpnext/controllers/buying_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ def validate(self):
self.validate_stock_or_nonstock_items()
self.validate_warehouse()
self.validate_asset_return()
self.validate_uom_convertability(
item_table_fieldname="items",
item_code_fieldname="item_code",
uom_fieldname="uom"
)

if self.doctype == "Purchase Invoice":
self.validate_purchase_receipt_if_update_stock()
Expand Down
10 changes: 10 additions & 0 deletions erpnext/controllers/selling_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,16 @@ def validate(self):
self.validate_selling_price()
self.set_qty_as_per_stock_uom()
self.set_alt_uom_qty()
self.validate_uom_convertability(
item_table_fieldname="items",
item_code_fieldname="item_code",
uom_fieldname="uom"
)
self.validate_uom_convertability(
item_table_fieldname="packed_items",
item_code_fieldname="item_code",
uom_fieldname="uom"
)
self.set_po_nos()
self.set_gross_profit()
self.validate_for_duplicate_items()
Expand Down
53 changes: 53 additions & 0 deletions erpnext/stock/doctype/item/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ def validate(self):
self.validate_fixed_asset()
self.validate_retain_sample()
self.validate_uom_conversion_factor()
self.validate_uom_convertible()
self.validate_weight()
self.validate_customer_provided_part()
self.validate_auto_reorder_enabled_in_stock_settings()
Expand Down Expand Up @@ -572,6 +573,58 @@ def validate_uom_conversion_factor(self):
frappe.msgprint("Setting conversion factor for UOM {0} from UOM Conversion Factor Master as {1}"
.format(d.uom, value), alert=True)
d.conversion_factor = value

def validate_uom_convertible(self):
throw_error = False
error_msg = None
if self.sales_uom:
sales_uom_not_convertible = self.get_conversion_factor(self.sales_uom).get("not_convertible")
if sales_uom_not_convertible:
throw_error = True
error_msg = "the Default Sales Unit of Measure not Convertible"
if self.purchase_uom:
purchase_uom_not_convertible = self.get_conversion_factor(self.purchase_uom).get("not_convertible")
if purchase_uom_not_convertible:
throw_error = True
error_msg = "Default Purchase Unit of Measure not Convertible"
if self.manufacture_uom:
manufacture_uom_not_convertible = self.get_conversion_factor(self.manufacture_uom).get("not_convertible")
if manufacture_uom_not_convertible:
throw_error = True
error_msg = "Default Raw Material UOM not Convertible"
if throw_error:
frappe.throw(error_msg)


def get_conversion_factor(self, uom):
# first look for direct conversion factor in item
item_conversion_factors = dict([(c.uom, c.conversion_factor) for c in self.uoms])
conversion_factor = flt(item_conversion_factors.get(uom))

# then look for conversion factor in template item if variant
if not conversion_factor and self.variant_of:
template_item = frappe.get_cached_doc("Item", self.variant_of)
template_item_conversion_factors = dict([(c.uom, c.conversion_factor) for c in template_item.uoms])
if uom in template_item_conversion_factors:
conversion_factor = flt(item_conversion_factors.get(uom))

# then look for global conversion factor for stock uom first then the rest of the item's convertible uoms
if not conversion_factor:
stock_uom = self.stock_uom
item_uoms = [stock_uom] + [cuom for cuom, cf in item_conversion_factors.items() if cuom != stock_uom and flt(cf)]

for item_uom in item_uoms:
conversion_factor = flt(get_uom_conv_factor(uom, item_uom))
if conversion_factor:
if item_uom != stock_uom:
# apply item_uom -> stock_uom conversion factor and then exit loop
conversion_factor *= flt(item_conversion_factors.get(item_uom))
break

return frappe._dict({
"conversion_factor": conversion_factor or 1.0,
"not_convertible": 1 if not conversion_factor else 0
})

def validate_weight(self):
weight_fields = ["net_weight_per_unit", "tare_weight_per_unit", "gross_weight_per_unit"]
Expand Down
10 changes: 10 additions & 0 deletions erpnext/stock/doctype/packing_slip/packing_slip.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,16 @@ def validate(self):
self.validate_warehouse()
self.validate_uom_is_integer("stock_uom", "stock_qty")
self.validate_uom_is_integer("uom", "qty")
self.validate_uom_convertability(
item_table_fieldname="items",
item_code_fieldname="item_code",
uom_fieldname="uom"
)
self.validate_uom_convertability(
item_table_fieldname="packaging_items",
item_code_fieldname="item_code",
uom_fieldname="uom"
)
self.calculate_totals()
self.validate_qty()
self.validate_weights()
Expand Down
5 changes: 5 additions & 0 deletions erpnext/stock/doctype/stock_entry/stock_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ def validate(self):
self.set_stock_qty()
self.validate_uom_is_integer("uom", "qty")
self.validate_uom_is_integer("stock_uom", "stock_qty")
self.validate_uom_convertability(
item_table_fieldname="items",
item_code_fieldname="item_code",
uom_fieldname="uom"
)
self.set_missing_warehouses()
self.validate_warehouse()
self.set_warehouse_address()
Expand Down
14 changes: 14 additions & 0 deletions erpnext/utilities/transaction_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from frappe.utils import cstr, now_datetime, cint, flt, get_time, get_link_to_form, date_diff, add_days, getdate
from erpnext.setup.doctype.terms_and_conditions.terms_and_conditions import get_terms_and_conditions
from erpnext.controllers.status_updater import StatusUpdaterERP
from erpnext.stock.get_item_details import get_conversion_factor
from frappe.model.document import Document


class UOMMustBeIntegerError(frappe.ValidationError): pass
Expand Down Expand Up @@ -78,6 +80,10 @@ def _add_calendar_event(self, opts):
def validate_uom_is_integer(self, uom_field, qty_fields):
validate_uom_is_integer(self, uom_field, qty_fields)

def validate_uom_convertability(self, item_table_fieldname, item_code_fieldname, uom_fieldname):
for each_item in self.get(item_table_fieldname):
validate_uom_convertability(each_item, each_item.get(item_code_fieldname), each_item.get(uom_fieldname))

def validate_with_previous_doc(self, ref, table_doctype=None):
self.exclude_fields = ["conversion_factor", "uom"] if self.get('is_return') else []

Expand Down Expand Up @@ -235,3 +241,11 @@ def validate_uom_is_integer(doc, uom_field, qty_fields, child_dt=None):
frappe.throw(_("Row {1}: Quantity ({0}) cannot be a fraction. To allow this, disable '{2}' in UOM {3}.") \
.format(qty, d.idx, frappe.bold(_("Must be Whole Number")), frappe.bold(d.get(uom_field))),
UOMMustBeIntegerError)

def validate_uom_convertability(item_row:Document, item_code: str, uom: str) -> None:
do_not_restrict_uom_selection = frappe.db.get_value("Item", item_code, "do_not_restrict_uom_selection")
if do_not_restrict_uom_selection:
return
not_convertible = get_conversion_factor(item_code, uom).get("not_convertible")
if not_convertible:
frappe.throw(f"the uom <b>{uom}</b> Not convertible at row {item_row.idx}")