fix: BOM Creator by removing redundant permissions

Removed unnecessary permission checks for whitelist functions and cleaned up the code.
This commit is contained in:
rohitwaghchaure
2026-06-13 18:45:02 +05:30
committed by Rohit Waghchaure
parent 46b3e0c385
commit 08d9b8275d
2 changed files with 0 additions and 618 deletions

View File

@@ -288,8 +288,6 @@ class BOMCreator(Document):
@frappe.whitelist()
def edit_qty(self, docname: str, qty: float):
self.check_permission("write")
if not frappe.db.exists("BOM Creator Item", {"name": docname, "parent": self.name}):
frappe.throw(_("BOM Creator Item {0} does not exist").format(docname))
@@ -358,53 +356,12 @@ class BOMCreator(Document):
production_item_wise_rm[(row.item_code, row.name)].bom_no = bom.name
@frappe.whitelist()
<<<<<<< HEAD
def get_default_bom(self, item_code: str) -> str:
self.check_permission("read")
=======
def edit_bom_creator(self, docname: str, data: str | dict):
if not frappe.db.exists("BOM Creator Item", {"parent": self.name, "name": docname}):
frappe.throw(_("BOM Creator Item with name {0} does not exist").format(docname))
if isinstance(data, str):
data = frappe.parse_json(data)
updated = False
for row in self.items:
if row.name == docname:
for key, value in data.items():
if key in BOM_ITEM_FIELDS and row.get(key) != value:
row.set(key, value)
updated = True
break
if updated:
self.set_rate_for_items()
self.save()
frappe.msgprint(_("Updated successfully"), alert=True)
return self
def has_operations(self):
for row in self.items:
if row.operation:
return True
return False
@frappe.whitelist()
def get_default_bom(self, item_code) -> str:
>>>>>>> dd56e80512 (fix: pemission for whitelist functions)
return frappe.get_cached_value("Item", item_code, "default_bom")
@frappe.whitelist()
def add_item(self, **kwargs):
<<<<<<< HEAD
self.check_permission("write")
=======
>>>>>>> dd56e80512 (fix: pemission for whitelist functions)
if isinstance(kwargs, str):
kwargs = frappe.parse_json(kwargs)
@@ -439,11 +396,6 @@ class BOMCreator(Document):
@frappe.whitelist()
def add_sub_assembly(self, **kwargs):
<<<<<<< HEAD
self.check_permission("write")
=======
>>>>>>> dd56e80512 (fix: pemission for whitelist functions)
if isinstance(kwargs, str):
kwargs = frappe.parse_json(kwargs)
@@ -479,12 +431,6 @@ class BOMCreator(Document):
parent_row_no = item_row.idx
name = ""
else:
<<<<<<< HEAD
=======
if sbool(kwargs.phantom):
parent_row = next(item for item in self.items if item.name == kwargs.fg_reference_id)
parent_row.is_phantom_item = 1
>>>>>>> dd56e80512 (fix: pemission for whitelist functions)
parent_row_no = get_parent_row_no(self, kwargs.fg_reference_id)
for row in bom_item.get("items"):
@@ -512,11 +458,6 @@ class BOMCreator(Document):
@frappe.whitelist()
def delete_node(self, **kwargs):
<<<<<<< HEAD
self.check_permission("write")
=======
>>>>>>> dd56e80512 (fix: pemission for whitelist functions)
if isinstance(kwargs, str):
kwargs = frappe.parse_json(kwargs)

View File

@@ -1,559 +0,0 @@
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.model.mapper import get_mapped_doc
from frappe.utils import comma_and, flt, get_link_to_form
from erpnext.buying.utils import check_on_hold_or_closed_status
from erpnext.controllers.subcontracting_controller import SubcontractingController
class SubcontractingInwardOrder(SubcontractingController):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
from erpnext.subcontracting.doctype.subcontracting_inward_order_item.subcontracting_inward_order_item import (
SubcontractingInwardOrderItem,
)
from erpnext.subcontracting.doctype.subcontracting_inward_order_received_item.subcontracting_inward_order_received_item import (
SubcontractingInwardOrderReceivedItem,
)
from erpnext.subcontracting.doctype.subcontracting_inward_order_secondary_item.subcontracting_inward_order_secondary_item import (
SubcontractingInwardOrderSecondaryItem,
)
from erpnext.subcontracting.doctype.subcontracting_inward_order_service_item.subcontracting_inward_order_service_item import (
SubcontractingInwardOrderServiceItem,
)
amended_from: DF.Link | None
company: DF.Link
currency: DF.Link | None
customer: DF.Link
customer_name: DF.Data
customer_warehouse: DF.Link
items: DF.Table[SubcontractingInwardOrderItem]
naming_series: DF.Literal["SCI-ORD-.YYYY.-"]
per_delivered: DF.Percent
per_process_loss: DF.Percent
per_produced: DF.Percent
per_raw_material_received: DF.Percent
per_raw_material_returned: DF.Percent
per_returned: DF.Percent
received_items: DF.Table[SubcontractingInwardOrderReceivedItem]
sales_order: DF.Link
secondary_items: DF.Table[SubcontractingInwardOrderSecondaryItem]
service_items: DF.Table[SubcontractingInwardOrderServiceItem]
set_delivery_warehouse: DF.Link | None
status: DF.Literal[
"Draft", "Open", "Ongoing", "Produced", "Delivered", "Returned", "Cancelled", "Closed"
]
title: DF.Data | None
transaction_date: DF.Date
# end: auto-generated types
pass
def validate(self):
super().validate()
self.set_is_customer_provided_item()
self.validate_customer_provided_items()
self.validate_customer_warehouse()
self.validate_service_items()
self.set_missing_values()
def on_submit(self):
self.update_status()
self.update_subcontracted_quantity_in_so()
def on_cancel(self):
self.update_status()
self.update_subcontracted_quantity_in_so()
def update_status(self, status=None, update_modified=True):
if self.status == "Closed" and self.status != status:
check_on_hold_or_closed_status("Sales Order", self.sales_order)
total_to_be_received = total_received = total_rm_returned = 0
for rm in self.get("received_items"):
if rm.get("is_customer_provided_item"):
total_to_be_received += flt(rm.required_qty)
total_received += flt(rm.received_qty)
total_rm_returned += flt(rm.returned_qty)
total_to_be_produced = total_produced = total_process_loss = total_delivered = total_fg_returned = 0
for item in self.get("items"):
total_to_be_produced += flt(item.qty)
total_produced += flt(item.produced_qty)
total_process_loss += flt(item.process_loss_qty)
total_delivered += flt(item.delivered_qty)
total_fg_returned += flt(item.returned_qty)
per_raw_material_received = flt(total_received / total_to_be_received * 100, 2)
per_raw_material_returned = flt(total_rm_returned / total_received * 100, 2) if total_received else 0
per_produced = flt(total_produced / total_to_be_produced * 100, 2)
per_process_loss = flt(total_process_loss / total_produced * 100, 2) if total_produced else 0
per_delivered = flt(total_delivered / total_to_be_produced * 100, 2)
per_returned = flt(total_fg_returned / total_delivered * 100, 2) if total_delivered else 0
self.db_set("per_raw_material_received", per_raw_material_received, update_modified=update_modified)
self.db_set("per_raw_material_returned", per_raw_material_returned, update_modified=update_modified)
self.db_set("per_produced", per_produced, update_modified=update_modified)
self.db_set("per_process_loss", per_process_loss, update_modified=update_modified)
self.db_set("per_delivered", per_delivered, update_modified=update_modified)
self.db_set("per_returned", per_returned, update_modified=update_modified)
if self.docstatus >= 1 and not status:
if self.docstatus == 1:
if self.status == "Draft":
status = "Open"
elif self.per_returned == 100:
status = "Returned"
elif self.per_delivered == 100:
status = "Delivered"
elif self.per_produced == 100:
status = "Produced"
elif self.per_raw_material_received > 0:
status = "Ongoing"
else:
status = "Open"
elif self.docstatus == 2:
status = "Cancelled"
if status and self.status != status:
self.db_set("status", status, update_modified=update_modified)
def update_subcontracted_quantity_in_so(self):
for service_item in self.service_items:
doc = frappe.get_doc("Sales Order Item", service_item.sales_order_item)
doc.subcontracted_qty = (
(doc.subcontracted_qty + service_item.qty)
if self._action == "submit"
else (doc.subcontracted_qty - service_item.qty)
)
doc.save()
def validate_customer_warehouse(self):
if frappe.get_cached_value("Warehouse", self.customer_warehouse, "customer") != self.customer:
frappe.throw(
_("Customer Warehouse {0} does not belong to Customer {1}.").format(
frappe.bold(self.customer_warehouse), frappe.bold(self.customer)
)
)
def validate_service_items(self):
sales_order_items = [item.sales_order_item for item in self.items]
self.service_items = [
service_item
for service_item in self.service_items
if service_item.sales_order_item in sales_order_items
]
for service_item in self.service_items:
item = next(item for item in self.items if item.sales_order_item == service_item.sales_order_item)
service_item.qty = item.qty * item.subcontracting_conversion_factor
service_item.fg_item_qty = item.qty
service_item.amount = service_item.qty * service_item.rate
def populate_items_table(self):
items = []
for si in self.service_items:
if si.fg_item:
item = frappe.get_doc("Item", si.fg_item)
so_item = frappe.get_doc("Sales Order Item", si.sales_order_item)
available_qty = so_item.stock_qty - so_item.subcontracted_qty
if available_qty == 0:
continue
si.required_qty = available_qty
conversion_factor = so_item.stock_qty / so_item.fg_item_qty
si.fg_item_qty = flt(
available_qty / conversion_factor, frappe.get_precision("Sales Order Item", "qty")
)
si.amount = available_qty * si.rate
bom = (
frappe.db.get_value(
"Subcontracting BOM",
{"finished_good": item.name, "is_active": 1},
"finished_good_bom",
)
or item.default_bom
)
items.append(
{
"item_code": item.name,
"item_name": item.item_name,
"expected_delivery_date": frappe.get_cached_value(
"Sales Order Item", si.sales_order_item, "delivery_date"
),
"description": item.description,
"qty": si.fg_item_qty,
"subcontracting_conversion_factor": conversion_factor,
"stock_uom": item.stock_uom,
"bom": bom,
"sales_order_item": si.sales_order_item,
}
)
else:
frappe.throw(
_("Please select Finished Good Item for Service Item {0}").format(
si.item_name or si.item_code
)
)
if items:
for item in items:
self.append("items", item)
def validate_customer_provided_items(self):
"""Check if atleast one raw material is customer provided"""
for item in self.get("items"):
raw_materials = [rm for rm in self.get("received_items") if rm.main_item_code == item.item_code]
if not any([rm.is_customer_provided_item for rm in raw_materials]):
frappe.throw(
_(
"Atleast one raw material for Finished Good Item {0} should be customer provided."
).format(frappe.bold(item.item_code))
)
def set_is_customer_provided_item(self):
for item in self.get("received_items"):
item.is_customer_provided_item = frappe.get_cached_value(
"Item", item.rm_item_code, "is_customer_provided_item"
)
@frappe.whitelist()
def make_work_order(self):
"""Create Work Order from Subcontracting Inward Order."""
wo_list = []
for item in self.get_production_items():
work_order = self.create_work_order(item)
if work_order:
wo_list.append(work_order)
self.show_list_created_message("Work Order", wo_list)
if not wo_list:
frappe.msgprint(_("No Work Orders were created"))
return wo_list
def get_production_items(self):
item_list = []
for d in self.items:
if d.produced_qty >= d.qty:
continue
item_details = {
"production_item": d.item_code,
"use_multi_level_bom": d.include_exploded_items,
"subcontracting_inward_order": self.name,
"bom_no": d.bom,
"stock_uom": d.stock_uom,
"company": self.company,
"project": frappe.get_cached_value("Sales Order", self.sales_order, "project"),
"source_warehouse": self.customer_warehouse,
"subcontracting_inward_order_item": d.name,
"reserve_stock": 1,
"fg_warehouse": d.delivery_warehouse,
}
qty = min(
[
flt(
(item.received_qty - item.returned_qty - item.work_order_qty)
/ flt(item.required_qty / d.qty, d.precision("qty")),
d.precision("qty"),
)
for item in self.get("received_items")
if item.reference_name == d.name and item.is_customer_provided_item and item.required_qty
]
)
qty = min(
int(qty) if frappe.get_cached_value("UOM", d.stock_uom, "must_be_whole_number") else qty,
d.qty - d.produced_qty,
)
item_details.update({"qty": qty, "max_producible_qty": qty})
item_list.append(item_details)
return item_list
def create_work_order(self, item):
from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError
if flt(item.get("qty")) <= 0:
return
wo = frappe.new_doc("Work Order")
wo.update(item)
wo.set_work_order_operations()
wo.set_required_items()
try:
wo.flags.ignore_mandatory = True
wo.flags.ignore_validate = True
wo.insert()
return wo.name
except OverProductionError:
pass
def show_list_created_message(self, doctype, doc_list=None):
if not doc_list:
return
frappe.flags.mute_messages = False
if doc_list:
doc_list = [get_link_to_form(doctype, p) for p in doc_list]
frappe.msgprint(_("{0} created").format(comma_and(doc_list)))
@frappe.whitelist()
def make_rm_stock_entry_inward(self, target_doc=None):
def calculate_qty_as_per_bom(rm_item):
data = frappe.get_value(
"Subcontracting Inward Order Item",
{"name": rm_item.reference_name},
["process_loss_qty", "include_exploded_items"],
as_dict=True,
)
stock_qty = frappe.get_value(
"BOM Explosion Item" if data.include_exploded_items else "BOM Item",
{"name": rm_item.bom_detail_no},
"stock_qty",
)
qty = flt(
stock_qty * data.process_loss_qty,
frappe.get_precision("Subcontracting Inward Order Received Item", "required_qty"),
)
return rm_item.required_qty - rm_item.received_qty + rm_item.returned_qty + qty
if target_doc and target_doc.get("items"):
target_doc.items = []
stock_entry = get_mapped_doc(
"Subcontracting Inward Order",
self.name,
{
"Subcontracting Inward Order": {
"doctype": "Stock Entry",
"validation": {
"docstatus": ["=", 1],
},
},
},
target_doc,
ignore_child_tables=True,
)
stock_entry.purpose = "Receive from Customer"
stock_entry.subcontracting_inward_order = self.name
stock_entry.set_stock_entry_type()
for rm_item in self.received_items:
if not rm_item.required_qty or not rm_item.is_customer_provided_item:
continue
items_dict = {
rm_item.get("rm_item_code"): {
"scio_detail": rm_item.get("name"),
"qty": calculate_qty_as_per_bom(rm_item),
"to_warehouse": rm_item.get("warehouse"),
"stock_uom": rm_item.get("stock_uom"),
}
}
stock_entry.add_to_stock_entry_detail(items_dict)
if target_doc:
return stock_entry
else:
return stock_entry.as_dict()
@frappe.whitelist()
def make_rm_return(self, target_doc=None):
if target_doc and target_doc.get("items"):
target_doc.items = []
stock_entry = get_mapped_doc(
"Subcontracting Inward Order",
self.name,
{
"Subcontracting Inward Order": {
"doctype": "Stock Entry",
"validation": {
"docstatus": ["=", 1],
},
},
},
target_doc,
ignore_child_tables=True,
)
stock_entry.purpose = "Return Raw Material to Customer"
stock_entry.set_stock_entry_type()
stock_entry.subcontracting_inward_order = self.name
for rm_item in self.received_items:
items_dict = {
rm_item.get("rm_item_code"): {
"scio_detail": rm_item.get("name"),
"qty": rm_item.received_qty - rm_item.work_order_qty - rm_item.returned_qty,
"from_warehouse": rm_item.get("warehouse"),
"stock_uom": rm_item.get("stock_uom"),
}
}
stock_entry.add_to_stock_entry_detail(items_dict)
if target_doc:
return stock_entry
else:
return stock_entry.as_dict()
@frappe.whitelist()
def make_subcontracting_delivery(self, target_doc=None):
if target_doc and target_doc.get("items"):
target_doc.items = []
stock_entry = get_mapped_doc(
"Subcontracting Inward Order",
self.name,
{
"Subcontracting Inward Order": {
"doctype": "Stock Entry",
"validation": {
"docstatus": ["=", 1],
},
},
},
target_doc,
ignore_child_tables=True,
)
stock_entry.purpose = "Subcontracting Delivery"
stock_entry.set_stock_entry_type()
stock_entry.subcontracting_inward_order = self.name
scio_details = []
allow_over = frappe.get_single_value("Selling Settings", "allow_delivery_of_overproduced_qty")
for fg_item in self.items:
qty = (
fg_item.produced_qty
if allow_over
else min(fg_item.qty, fg_item.produced_qty) - fg_item.delivered_qty
)
if qty < 0:
continue
scio_details.append(fg_item.name)
items_dict = {
fg_item.item_code: {
"qty": qty,
"from_warehouse": fg_item.delivery_warehouse,
"stock_uom": fg_item.stock_uom,
"scio_detail": fg_item.name,
"is_finished_item": 1,
}
}
stock_entry.add_to_stock_entry_detail(items_dict)
if (
frappe.get_single_value("Selling Settings", "deliver_secondary_items")
and self.secondary_items
and scio_details
):
secondary_items = [
secondary_item
for secondary_item in self.secondary_items
if secondary_item.reference_name in scio_details
]
for secondary_item in secondary_items:
qty = secondary_item.produced_qty - secondary_item.delivered_qty
if qty > 0:
items_dict = {
secondary_item.item_code: {
"qty": secondary_item.produced_qty - secondary_item.delivered_qty,
"from_warehouse": secondary_item.warehouse,
"stock_uom": secondary_item.stock_uom,
"scio_detail": secondary_item.name,
"type": secondary_item.type,
}
}
stock_entry.add_to_stock_entry_detail(items_dict)
if target_doc:
return stock_entry
else:
return stock_entry.as_dict()
@frappe.whitelist()
def make_subcontracting_return(self, target_doc=None):
if target_doc and target_doc.get("items"):
target_doc.items = []
stock_entry = get_mapped_doc(
"Subcontracting Inward Order",
self.name,
{
"Subcontracting Inward Order": {
"doctype": "Stock Entry",
"validation": {
"docstatus": ["=", 1],
},
"field_map": {"name": "subcontracting_inward_order"},
},
},
target_doc,
ignore_child_tables=True,
)
stock_entry.purpose = "Subcontracting Return"
stock_entry.set_stock_entry_type()
for fg_item in self.items:
qty = fg_item.delivered_qty - fg_item.returned_qty
if qty < 0:
continue
items_dict = {
fg_item.item_code: {
"qty": qty,
"stock_uom": fg_item.stock_uom,
"scio_detail": fg_item.name,
"is_finished_item": 1,
}
}
stock_entry.add_to_stock_entry_detail(items_dict)
if target_doc:
return stock_entry
else:
return stock_entry.as_dict()
@frappe.whitelist()
def update_subcontracting_inward_order_status(scio: str | Document, status: str | None = None):
if isinstance(scio, str):
scio = frappe.get_doc("Subcontracting Inward Order", scio)
scio.check_permission("write")
scio.update_status(status)