Skip to content

Commit f6d405e

Browse files
committed
refactor: update_child_qty_rate function
1 parent 06ffe52 commit f6d405e

1 file changed

Lines changed: 124 additions & 112 deletions

File tree

erpnext/controllers/accounts_controller.py

Lines changed: 124 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -3856,6 +3856,122 @@ def validate_and_delete_children(parent, data, ordered_item=None) -> bool:
38563856
return bool(deleted_children)
38573857

38583858

3859+
def get_allow_zero_qty(parent_doctype: str) -> bool:
3860+
if parent_doctype == "Sales Order":
3861+
return frappe.db.get_single_value("Selling Settings", "allow_zero_qty_in_sales_order") or False
3862+
if parent_doctype == "Purchase Order":
3863+
return frappe.db.get_single_value("Buying Settings", "allow_zero_qty_in_purchase_order") or False
3864+
return False
3865+
3866+
3867+
def get_child_item_change_state(parent_doctype: str, child_item, new_data) -> frappe._dict:
3868+
prev_rate, new_rate = flt(child_item.get("rate")), flt(new_data.get("rate"))
3869+
prev_qty, new_qty = flt(child_item.get("qty")), flt(new_data.get("qty"))
3870+
prev_fg_qty, new_fg_qty = flt(child_item.get("fg_item_qty")), flt(new_data.get("fg_item_qty"))
3871+
prev_con_fac, new_con_fac = (
3872+
flt(child_item.get("conversion_factor")),
3873+
flt(new_data.get("conversion_factor")),
3874+
)
3875+
3876+
if parent_doctype == "Sales Order":
3877+
prev_date, new_date = child_item.get("delivery_date"), new_data.get("delivery_date")
3878+
elif parent_doctype == "Purchase Order":
3879+
prev_date, new_date = child_item.get("schedule_date"), new_data.get("schedule_date")
3880+
else:
3881+
prev_date, new_date = None, None
3882+
3883+
date_unchanged = (
3884+
(prev_date == getdate(new_date) if prev_date and new_date else False)
3885+
if parent_doctype not in ["Quotation", "Supplier Quotation"]
3886+
else None
3887+
)
3888+
3889+
return frappe._dict(
3890+
rate_unchanged=prev_rate == new_rate,
3891+
qty_unchanged=prev_qty == new_qty,
3892+
fg_qty_unchanged=prev_fg_qty == new_fg_qty,
3893+
uom_unchanged=child_item.get("uom") == new_data.get("uom"),
3894+
conversion_factor_unchanged=prev_con_fac == new_con_fac,
3895+
date_unchanged=date_unchanged,
3896+
description_unchanged=child_item.get("description") == new_data.get("description"),
3897+
)
3898+
3899+
3900+
def update_child_item_rate_and_discount(
3901+
parent_doctype: str, child_item, new_data, allow_zero_qty: bool
3902+
) -> None:
3903+
rate_precision = child_item.precision("rate") or 2
3904+
qty_precision = child_item.precision("qty") or 2
3905+
3906+
prev_rate, new_rate = flt(child_item.get("rate")), flt(new_data.get("rate"))
3907+
rate_unchanged = prev_rate == new_rate
3908+
if not rate_unchanged and not child_item.get("qty") and allow_zero_qty:
3909+
frappe.throw(_("Rate of '{}' items cannot be changed").format(frappe.bold(_("Unit Price"))))
3910+
3911+
# Amount cannot be lesser than billed amount, except for negative amounts
3912+
row_rate = flt(new_data.get("rate"), rate_precision)
3913+
3914+
if parent_doctype in ["Purchase Order", "Sales Order"]:
3915+
amount_below_billed_amt = flt(child_item.billed_amt, rate_precision) > flt(
3916+
row_rate * flt(new_data.get("qty"), qty_precision), rate_precision
3917+
)
3918+
if amount_below_billed_amt and row_rate > 0.0:
3919+
frappe.throw(
3920+
_(
3921+
"Row #{0}: Cannot set Rate if the billed amount is greater than the amount for Item {1}."
3922+
).format(child_item.idx, child_item.item_code)
3923+
)
3924+
3925+
child_item.rate = row_rate
3926+
3927+
if parent_doctype not in ["Sales Order", "Purchase Order"] or not flt(child_item.price_list_rate):
3928+
return
3929+
3930+
if flt(child_item.rate) > flt(child_item.price_list_rate):
3931+
# if rate is greater than price_list_rate, set margin or set discount
3932+
child_item.discount_percentage = 0
3933+
child_item.margin_type = "Amount"
3934+
child_item.margin_rate_or_amount = flt(
3935+
child_item.rate - child_item.price_list_rate,
3936+
child_item.precision("margin_rate_or_amount"),
3937+
)
3938+
child_item.rate_with_margin = child_item.rate
3939+
else:
3940+
child_item.discount_percentage = flt(
3941+
(1 - flt(child_item.rate) / flt(child_item.price_list_rate)) * 100.0,
3942+
child_item.precision("discount_percentage"),
3943+
)
3944+
child_item.discount_amount = flt(child_item.price_list_rate) - flt(child_item.rate)
3945+
child_item.margin_type = ""
3946+
child_item.margin_rate_or_amount = 0
3947+
child_item.rate_with_margin = 0
3948+
3949+
3950+
def update_child_item_uom_and_weight(child_item, new_data) -> None:
3951+
conv_fac_precision = child_item.precision("conversion_factor") or 2
3952+
3953+
if new_data.get("conversion_factor"):
3954+
if child_item.stock_uom == child_item.uom:
3955+
child_item.conversion_factor = 1
3956+
else:
3957+
child_item.conversion_factor = flt(new_data.get("conversion_factor"), conv_fac_precision)
3958+
3959+
if new_data.get("uom"):
3960+
child_item.uom = new_data.get("uom")
3961+
conversion_factor = flt(
3962+
get_conversion_factor(child_item.item_code, child_item.uom).get("conversion_factor")
3963+
)
3964+
child_item.conversion_factor = (
3965+
flt(new_data.get("conversion_factor"), conv_fac_precision) or conversion_factor
3966+
)
3967+
3968+
if child_item.get("total_weight") and child_item.get("weight_per_unit"):
3969+
child_item.total_weight = flt(
3970+
child_item.weight_per_unit * child_item.qty * child_item.conversion_factor,
3971+
child_item.precision("total_weight"),
3972+
)
3973+
3974+
38593975
@frappe.whitelist()
38603976
def update_child_qty_rate(
38613977
parent_doctype: str, trans_items: str, parent_doctype_name: str, child_docname: str = "items"
@@ -3904,15 +4020,8 @@ def get_new_child_item(item_row):
39044020
child_doctype = parent_doctype + " Item"
39054021
return set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child_docname, item_row)
39064022

3907-
def is_allowed_zero_qty():
3908-
if parent_doctype == "Sales Order":
3909-
return frappe.db.get_single_value("Selling Settings", "allow_zero_qty_in_sales_order") or False
3910-
elif parent_doctype == "Purchase Order":
3911-
return frappe.db.get_single_value("Buying Settings", "allow_zero_qty_in_purchase_order") or False
3912-
return False
3913-
39144023
def validate_quantity_and_rate(child_item, new_data):
3915-
if not flt(new_data.get("qty")) and not is_allowed_zero_qty():
4024+
if not flt(new_data.get("qty")) and not allow_zero_qty:
39164025
frappe.throw(
39174026
_("Row #{0}:Quantity for Item {1} cannot be zero.").format(
39184027
new_data.get("idx"), frappe.bold(new_data.get("item_code"))
@@ -4004,6 +4113,7 @@ def validate_fg_item_for_subcontracting(new_data, is_new):
40044113
any_conversion_factor_changed = False
40054114

40064115
parent = frappe.get_doc(parent_doctype, parent_doctype_name)
4116+
allow_zero_qty = get_allow_zero_qty(parent_doctype)
40074117

40084118
check_doc_permissions(parent, "write")
40094119

@@ -4034,42 +4144,10 @@ def validate_fg_item_for_subcontracting(new_data, is_new):
40344144
check_doc_permissions(parent, "write")
40354145
child_item = frappe.get_doc(parent_doctype + " Item", d.get("docname"))
40364146

4037-
prev_rate, new_rate = flt(child_item.get("rate")), flt(d.get("rate"))
4038-
prev_qty, new_qty = flt(child_item.get("qty")), flt(d.get("qty"))
4039-
prev_fg_qty, new_fg_qty = flt(child_item.get("fg_item_qty")), flt(d.get("fg_item_qty"))
4040-
prev_con_fac, new_con_fac = (
4041-
flt(child_item.get("conversion_factor")),
4042-
flt(d.get("conversion_factor")),
4043-
)
4044-
prev_uom, new_uom = child_item.get("uom"), d.get("uom")
4045-
4046-
if parent_doctype == "Sales Order":
4047-
prev_date, new_date = child_item.get("delivery_date"), d.get("delivery_date")
4048-
elif parent_doctype == "Purchase Order":
4049-
prev_date, new_date = child_item.get("schedule_date"), d.get("schedule_date")
4050-
4051-
prev_description, new_description = (child_item.get("description"), d.get("description"))
4052-
description_unchanged = prev_description == new_description
4053-
rate_unchanged = prev_rate == new_rate
4054-
qty_unchanged = prev_qty == new_qty
4055-
fg_qty_unchanged = prev_fg_qty == new_fg_qty
4056-
uom_unchanged = prev_uom == new_uom
4057-
conversion_factor_unchanged = prev_con_fac == new_con_fac
4058-
any_conversion_factor_changed |= not conversion_factor_unchanged
4059-
date_unchanged = (
4060-
(prev_date == getdate(new_date) if prev_date and new_date else False)
4061-
if parent_doctype not in ["Quotation", "Supplier Quotation"]
4062-
else None
4063-
) # in case of delivery note etc
4064-
if (
4065-
rate_unchanged
4066-
and qty_unchanged
4067-
and fg_qty_unchanged
4068-
and conversion_factor_unchanged
4069-
and uom_unchanged
4070-
and date_unchanged
4071-
and description_unchanged
4072-
):
4147+
change_state = get_child_item_change_state(parent_doctype, child_item, d)
4148+
rate_unchanged = change_state.rate_unchanged
4149+
any_conversion_factor_changed |= not change_state.conversion_factor_unchanged
4150+
if all(change_state.values()):
40734151
continue
40744152

40754153
validate_quantity_and_rate(child_item, d)
@@ -4090,52 +4168,8 @@ def validate_fg_item_for_subcontracting(new_data, is_new):
40904168

40914169
child_item.qty = flt(d.get("qty"))
40924170
child_item.description = d.get("description")
4093-
rate_precision = child_item.precision("rate") or 2
4094-
conv_fac_precision = child_item.precision("conversion_factor") or 2
4095-
qty_precision = child_item.precision("qty") or 2
4096-
4097-
prev_rate, new_rate = flt(child_item.get("rate")), flt(d.get("rate"))
4098-
rate_unchanged = prev_rate == new_rate
4099-
if not rate_unchanged and not child_item.get("qty") and is_allowed_zero_qty():
4100-
frappe.throw(_("Rate of '{}' items cannot be changed").format(frappe.bold(_("Unit Price"))))
4101-
# Amount cannot be lesser than billed amount, except for negative amounts
4102-
row_rate = flt(d.get("rate"), rate_precision)
4103-
4104-
if parent_doctype in ["Purchase Order", "Sales Order"]:
4105-
amount_below_billed_amt = flt(child_item.billed_amt, rate_precision) > flt(
4106-
row_rate * flt(d.get("qty"), qty_precision), rate_precision
4107-
)
4108-
if amount_below_billed_amt and row_rate > 0.0:
4109-
frappe.throw(
4110-
_(
4111-
"Row #{0}: Cannot set Rate if the billed amount is greater than the amount for Item {1}."
4112-
).format(child_item.idx, child_item.item_code)
4113-
)
4114-
else:
4115-
child_item.rate = row_rate
4116-
else:
4117-
child_item.rate = row_rate
4118-
4119-
if d.get("conversion_factor"):
4120-
if child_item.stock_uom == child_item.uom:
4121-
child_item.conversion_factor = 1
4122-
else:
4123-
child_item.conversion_factor = flt(d.get("conversion_factor"), conv_fac_precision)
4124-
4125-
if d.get("uom"):
4126-
child_item.uom = d.get("uom")
4127-
conversion_factor = flt(
4128-
get_conversion_factor(child_item.item_code, child_item.uom).get("conversion_factor")
4129-
)
4130-
child_item.conversion_factor = (
4131-
flt(d.get("conversion_factor"), conv_fac_precision) or conversion_factor
4132-
)
4133-
4134-
if child_item.get("total_weight") and child_item.get("weight_per_unit"):
4135-
child_item.total_weight = flt(
4136-
child_item.weight_per_unit * child_item.qty * child_item.conversion_factor,
4137-
child_item.precision("total_weight"),
4138-
)
4171+
update_child_item_rate_and_discount(parent_doctype, child_item, d, allow_zero_qty)
4172+
update_child_item_uom_and_weight(child_item, d)
41394173

41404174
if d.get("delivery_date") and parent_doctype == "Sales Order":
41414175
child_item.delivery_date = d.get("delivery_date")
@@ -4146,28 +4180,6 @@ def validate_fg_item_for_subcontracting(new_data, is_new):
41464180
if d.get("bom_no") and parent_doctype == "Sales Order":
41474181
child_item.bom_no = d.get("bom_no")
41484182

4149-
if parent_doctype in ["Sales Order", "Purchase Order"]:
4150-
if flt(child_item.price_list_rate):
4151-
if flt(child_item.rate) > flt(child_item.price_list_rate):
4152-
# if rate is greater than price_list_rate, set margin
4153-
# or set discount
4154-
child_item.discount_percentage = 0
4155-
child_item.margin_type = "Amount"
4156-
child_item.margin_rate_or_amount = flt(
4157-
child_item.rate - child_item.price_list_rate,
4158-
child_item.precision("margin_rate_or_amount"),
4159-
)
4160-
child_item.rate_with_margin = child_item.rate
4161-
else:
4162-
child_item.discount_percentage = flt(
4163-
(1 - flt(child_item.rate) / flt(child_item.price_list_rate)) * 100.0,
4164-
child_item.precision("discount_percentage"),
4165-
)
4166-
child_item.discount_amount = flt(child_item.price_list_rate) - flt(child_item.rate)
4167-
child_item.margin_type = ""
4168-
child_item.margin_rate_or_amount = 0
4169-
child_item.rate_with_margin = 0
4170-
41714183
child_item.flags.ignore_validate_update_after_submit = True
41724184
if new_child_flag:
41734185
parent.load_from_db()

0 commit comments

Comments
 (0)