@@ -3856,6 +3856,142 @@ 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 == "Quotation" :
3861+ return frappe .db .get_single_value ("Selling Settings" , "allow_zero_qty_in_quotation" ) or False
3862+ if parent_doctype == "Sales Order" :
3863+ return frappe .db .get_single_value ("Selling Settings" , "allow_zero_qty_in_sales_order" ) or False
3864+ if parent_doctype == "Purchase Order" :
3865+ return frappe .db .get_single_value ("Buying Settings" , "allow_zero_qty_in_purchase_order" ) or False
3866+ return False
3867+
3868+
3869+ def get_child_item_change_state (parent_doctype : str , child_item , new_data ) -> frappe ._dict :
3870+ prev_rate , new_rate = flt (child_item .get ("rate" )), flt (new_data .get ("rate" ))
3871+ prev_qty , new_qty = flt (child_item .get ("qty" )), flt (new_data .get ("qty" ))
3872+ prev_fg_qty , new_fg_qty = flt (child_item .get ("fg_item_qty" )), flt (new_data .get ("fg_item_qty" ))
3873+ prev_con_fac , new_con_fac = (
3874+ flt (child_item .get ("conversion_factor" )),
3875+ flt (new_data .get ("conversion_factor" )),
3876+ )
3877+
3878+ if parent_doctype == "Sales Order" :
3879+ prev_date , new_date = child_item .get ("delivery_date" ), new_data .get ("delivery_date" )
3880+ elif parent_doctype == "Purchase Order" :
3881+ prev_date , new_date = child_item .get ("schedule_date" ), new_data .get ("schedule_date" )
3882+ else :
3883+ prev_date , new_date = None , None
3884+
3885+ if parent_doctype in ["Quotation" , "Supplier Quotation" ]:
3886+ date_unchanged = True
3887+ else :
3888+ prev_date = getdate (prev_date ) if prev_date else None
3889+ new_date = getdate (new_date ) if new_date else None
3890+ date_unchanged = prev_date == new_date
3891+
3892+ return frappe ._dict (
3893+ rate_unchanged = prev_rate == new_rate ,
3894+ qty_unchanged = prev_qty == new_qty ,
3895+ fg_qty_unchanged = prev_fg_qty == new_fg_qty ,
3896+ uom_unchanged = child_item .get ("uom" ) == new_data .get ("uom" ),
3897+ conversion_factor_unchanged = prev_con_fac == new_con_fac ,
3898+ date_unchanged = date_unchanged ,
3899+ description_unchanged = child_item .get ("description" ) == new_data .get ("description" ),
3900+ bom_unchanged = (parent_doctype != "Sales Order" or child_item .get ("bom_no" ) == new_data .get ("bom_no" )),
3901+ )
3902+
3903+
3904+ def is_child_item_unchanged (change_state : frappe ._dict ) -> bool :
3905+ return (
3906+ change_state .rate_unchanged
3907+ and change_state .qty_unchanged
3908+ and change_state .fg_qty_unchanged
3909+ and change_state .conversion_factor_unchanged
3910+ and change_state .uom_unchanged
3911+ and change_state .date_unchanged
3912+ and change_state .description_unchanged
3913+ and change_state .bom_unchanged
3914+ )
3915+
3916+
3917+ def update_child_item_rate_and_discount (
3918+ parent_doctype : str , child_item , new_data , allow_zero_qty : bool , rate_unchanged : bool | None = None
3919+ ) -> None :
3920+ rate_precision = child_item .precision ("rate" ) or 2
3921+ qty_precision = child_item .precision ("qty" ) or 2
3922+
3923+ if rate_unchanged is None :
3924+ prev_rate , new_rate = flt (child_item .get ("rate" )), flt (new_data .get ("rate" ))
3925+ rate_unchanged = prev_rate == new_rate
3926+
3927+ if not rate_unchanged and not child_item .get ("qty" ) and allow_zero_qty :
3928+ frappe .throw (_ ("Rate of '{}' items cannot be changed" ).format (frappe .bold (_ ("Unit Price" ))))
3929+
3930+ # Amount cannot be lesser than billed amount, except for negative amounts
3931+ row_rate = flt (new_data .get ("rate" ), rate_precision )
3932+
3933+ if parent_doctype in ["Purchase Order" , "Sales Order" ]:
3934+ amount_below_billed_amt = flt (child_item .billed_amt , rate_precision ) > flt (
3935+ row_rate * flt (new_data .get ("qty" ), qty_precision ), rate_precision
3936+ )
3937+ if amount_below_billed_amt and row_rate > 0.0 :
3938+ frappe .throw (
3939+ _ (
3940+ "Row #{0}: Cannot set Rate if the billed amount is greater than the amount for Item {1}."
3941+ ).format (child_item .idx , child_item .item_code )
3942+ )
3943+
3944+ child_item .rate = row_rate
3945+
3946+ if parent_doctype not in ["Sales Order" , "Purchase Order" ] or not flt (child_item .price_list_rate ):
3947+ return
3948+
3949+ if flt (child_item .rate ) > flt (child_item .price_list_rate ):
3950+ # if rate is greater than price_list_rate, set margin or set discount
3951+ child_item .discount_percentage = 0
3952+ child_item .discount_amount = 0
3953+ child_item .margin_type = "Amount"
3954+ child_item .margin_rate_or_amount = flt (
3955+ child_item .rate - child_item .price_list_rate ,
3956+ child_item .precision ("margin_rate_or_amount" ),
3957+ )
3958+ child_item .rate_with_margin = child_item .rate
3959+ else :
3960+ child_item .discount_percentage = flt (
3961+ (1 - flt (child_item .rate ) / flt (child_item .price_list_rate )) * 100.0 ,
3962+ child_item .precision ("discount_percentage" ),
3963+ )
3964+ child_item .discount_amount = flt (child_item .price_list_rate ) - flt (child_item .rate )
3965+ child_item .margin_type = ""
3966+ child_item .margin_rate_or_amount = 0
3967+ child_item .rate_with_margin = 0
3968+
3969+
3970+ def update_child_item_uom_and_weight (child_item , new_data ) -> None :
3971+ conv_fac_precision = child_item .precision ("conversion_factor" ) or 2
3972+
3973+ if new_data .get ("conversion_factor" ):
3974+ if child_item .stock_uom == child_item .uom :
3975+ child_item .conversion_factor = 1
3976+ else :
3977+ child_item .conversion_factor = flt (new_data .get ("conversion_factor" ), conv_fac_precision )
3978+
3979+ if new_data .get ("uom" ):
3980+ child_item .uom = new_data .get ("uom" )
3981+ conversion_factor = flt (
3982+ get_conversion_factor (child_item .item_code , child_item .uom ).get ("conversion_factor" )
3983+ )
3984+ child_item .conversion_factor = (
3985+ flt (new_data .get ("conversion_factor" ), conv_fac_precision ) or conversion_factor
3986+ )
3987+
3988+ if child_item .get ("total_weight" ) and child_item .get ("weight_per_unit" ):
3989+ child_item .total_weight = flt (
3990+ child_item .weight_per_unit * child_item .qty * child_item .conversion_factor ,
3991+ child_item .precision ("total_weight" ),
3992+ )
3993+
3994+
38593995@frappe .whitelist ()
38603996def update_child_qty_rate (
38613997 parent_doctype : str , trans_items : str , parent_doctype_name : str , child_docname : str = "items"
@@ -3904,15 +4040,8 @@ def get_new_child_item(item_row):
39044040 child_doctype = parent_doctype + " Item"
39054041 return set_order_defaults (parent_doctype , parent_doctype_name , child_doctype , child_docname , item_row )
39064042
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-
39144043 def validate_quantity_and_rate (child_item , new_data ):
3915- if not flt (new_data .get ("qty" )) and not is_allowed_zero_qty () :
4044+ if not flt (new_data .get ("qty" )) and not allow_zero_qty :
39164045 frappe .throw (
39174046 _ ("Row #{0}:Quantity for Item {1} cannot be zero." ).format (
39184047 new_data .get ("idx" ), frappe .bold (new_data .get ("item_code" ))
@@ -4004,6 +4133,7 @@ def validate_fg_item_for_subcontracting(new_data, is_new):
40044133 any_conversion_factor_changed = False
40054134
40064135 parent = frappe .get_doc (parent_doctype , parent_doctype_name )
4136+ allow_zero_qty = get_allow_zero_qty (parent_doctype )
40074137
40084138 check_doc_permissions (parent , "write" )
40094139
@@ -4020,6 +4150,7 @@ def validate_fg_item_for_subcontracting(new_data, is_new):
40204150
40214151 for d in data :
40224152 new_child_flag = False
4153+ rate_unchanged = None
40234154
40244155 if not d .get ("item_code" ):
40254156 # ignore empty rows
@@ -4034,42 +4165,10 @@ def validate_fg_item_for_subcontracting(new_data, is_new):
40344165 check_doc_permissions (parent , "write" )
40354166 child_item = frappe .get_doc (parent_doctype + " Item" , d .get ("docname" ))
40364167
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- ):
4168+ change_state = get_child_item_change_state (parent_doctype , child_item , d )
4169+ rate_unchanged = change_state .rate_unchanged
4170+ any_conversion_factor_changed |= not change_state .conversion_factor_unchanged
4171+ if is_child_item_unchanged (change_state ):
40734172 continue
40744173
40754174 validate_quantity_and_rate (child_item , d )
@@ -4090,52 +4189,10 @@ def validate_fg_item_for_subcontracting(new_data, is_new):
40904189
40914190 child_item .qty = flt (d .get ("qty" ))
40924191 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- )
4192+ update_child_item_rate_and_discount (
4193+ parent_doctype , child_item , d , allow_zero_qty , rate_unchanged = rate_unchanged
4194+ )
4195+ update_child_item_uom_and_weight (child_item , d )
41394196
41404197 if d .get ("delivery_date" ) and parent_doctype == "Sales Order" :
41414198 child_item .delivery_date = d .get ("delivery_date" )
@@ -4146,28 +4203,6 @@ def validate_fg_item_for_subcontracting(new_data, is_new):
41464203 if d .get ("bom_no" ) and parent_doctype == "Sales Order" :
41474204 child_item .bom_no = d .get ("bom_no" )
41484205
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-
41714206 child_item .flags .ignore_validate_update_after_submit = True
41724207 if new_child_flag :
41734208 parent .load_from_db ()
0 commit comments