How to resolve permanatly Modification error

most time getting modifictaion error when i saving or submit doc, please let me know why comes this type of error and also let me provide best suggestion for resolving this error permanantly…

fpr more references i am attaching screenshot beow ….

You have some custom logic hooked that is causing a race condition. Share your doctype controller/server scripts or any method directly or indirectly affecting this doctype.

can you please let me know solution of this

Please share so i can further assist

this is my server side custom_script..

hooks.py

doc_events{

“Stock Entry”: {

    "before_insert": "custom_apaulsoft.custom_script.stock_entry.stock_entry.before_insert",

“before_submit”: “custom_apaulsoft.custom_script.stock_entry.stock_entry.before_submit”,

“validate”: “custom_apaulsoft.custom_script.stock_entry.stock_entry.validate”,

“on_update”: “custom_apaulsoft.custom_script.stock_entry.stock_entry.handle_transition”,

# “on_trash”: “custom_apaulsoft.custom_script.stock_entry.stock_entry.clear_tracker”,

},

}

doctype_js{

"Stock Entry": "custom_script/stock_entry/stock_entry.js",

}

import frappe

from frappe import _

from frappe.utils import flt

from frappe.utils import cint

from erpnext.stock.doctype.batch.batch import get_batch_qty as _get_batch_qty

from custom_apaulsoft import revised_todo_utils

from custom_apaulsoft.message_templates import show_error_message

from .stock_entry_helper.fatch_uid_from_incoming_iqc import fatch_uid_from_incoming_iqc

from custom_apaulsoft.custom_script.serial_no.serial_no import is_bypassing_user

def before_submit(doc, method=None):

work_order = doc.work_order

if not work_order:

return None

if is_bypassing_user():

return None

serial_numbers = frappe.get_all(

“Serial No”, filters={“work_order”: work_order}, pluck=“name”

)

# for num in serial_numbers:

# if not check_for_pqc_record(docname=num):

# show_error_message(

# message=f"PQC has not been done for Serial Number {num}, due to which this Stock Entry cannot be submitted. Please first perform the PQC for this serial number and then try submitting the Stock Entry",

# translate=True,

# )

# return None

def validate(doc, method=None):

current_workflow_state = frappe.db.get_value(

doc.doctype, doc.name, “workflow_state”

)

doc.custom_previous_workflow_state = current_workflow_state

def handle_transition(doc, method=None):

previous_workflow_state = doc.custom_previous_workflow_state

current_workflow_state = doc.workflow_state

if (

previous_workflow_state is None

and current_workflow_state == “Pending for Review”

) or (

previous_workflow_state == “Open”

and current_workflow_state == “Pending for Review”

):

create_todo_for_purpose(stock_entry=doc)

if previous_workflow_state == current_workflow_state:

return

if previous_workflow_state == “Pending for Review”:

if current_workflow_state in [“Pending for Manager Approval”, “Reviewed”]:

close_todo(

stock_entry=doc,

custom_corresponding_state_name=previous_workflow_state,

        )

create_todo_for_purpose(stock_entry=doc)

elif current_workflow_state in [“Rejected by Reviewer”, “Rejected”]:

close_todo_with_rejection(

stock_entry=doc,

rejected_from_state=“Pending for Review”,

rejected_to_state=current_workflow_state,

        )

elif previous_workflow_state in [“Pending for Manager Approval”, “Reviewed”]:

if current_workflow_state == “Approved”:

close_todo(

stock_entry=doc,

custom_corresponding_state_name=previous_workflow_state,

        )

doc.custom_previous_workflow_state = None

return

elif current_workflow_state == “Rejected by Approver”:

close_todo_with_rejection(

stock_entry=doc,

rejected_from_state=previous_workflow_state,

rejected_to_state=current_workflow_state,

        )

close_all_related_todos(stock_entry=doc)

elif previous_workflow_state == “Approved”:

if current_workflow_state == “Dispatched”:

if not check_shipment_logging_status(

stock_entry=doc.name, site_mr_reference=doc.custom_for_site_mr_number

        ):

show_error_message(

message=f"You cannot transition to Dispatched before filling out the Shipment Details. Please click on Create > Shipment to log the shipment details"

            )

return None

else:

doc.custom_dispatch_declaration_acknowledged = 1

elif previous_workflow_state == “Rejected by Reviewer”:

if current_workflow_state == “Pending for Review”:

create_todo_for_purpose(stock_entry=doc)

frappe.db.set_value(

doc.doctype, doc.name, “custom_previous_workflow_state”, current_workflow_state

)

def close_todo_with_rejection(stock_entry, rejected_from_state, rejected_to_state):

return revised_todo_utils.close_todo_with_rejection(

reference_document=stock_entry,

rejected_from_state=rejected_from_state,

rejected_to_state=rejected_to_state,

)

def close_all_related_todos(stock_entry):

return revised_todo_utils.close_all_related_todos(reference_document=stock_entry)

def create_todo_for_purpose(stock_entry):

return revised_todo_utils.create_todo_for_purpose(reference_document=stock_entry)

def close_todo(stock_entry, custom_corresponding_state_name):

return revised_todo_utils.close_todo(

reference_document=stock_entry,

custom_corresponding_state_name=custom_corresponding_state_name,

)

# ---------------------------------------------------------------------#

# This code is for the implementation of the Dispactched feature and other aligning features

def check_shipment_logging_status(stock_entry=None, site_mr_reference=None):

if not stock_entry or not site_mr_reference:

return False

documents = frappe.get_all(

“Shipment Stock Entry”,

filters={“stock_entry”: stock_entry, “site_mr_reference”: site_mr_reference},

fields=[“parent”],

)

for doc in documents:

name = doc[“parent”]

shipment = frappe.get_doc(“Shipment”, name)

if shipment.docstatus != 1:

return False

return True

@frappe.whitelist()

def acknowledge_dispatch_received(docname, field_return_number):

user = frappe.session.user

try:

doc = frappe.get_doc(“Stock Entry”, docname)

doc.add_comment(

“Comment”,

text=(

f"{user} acknowledged the receipt of items against the Field Return Entry number {field_return_number}"

f"at {frappe.utils.now_datetime()} on {frappe.utils.nowdate()}"

        ),

    )

doc.workflow_state = “Incoming Dispatch Received”

doc.save()

return True

except Exception as e:

frappe.throw(

f"An error occured while adding acknowledgement comment : {str(e)}"

    )

‘’’

Automatic quntity fatched when select manually batch in “Update Batch Nos” dialog

‘’’

@frappe.whitelist()

def get_batch_available_qty(batch_no, warehouse, posting_date=None, posting_time=None):

“”“Return available qty for a batch in a warehouse (as-of posting date/time if given).”“”

if not batch_no or not warehouse:

return 0.0

item_code = frappe.db.get_value(“Batch”, batch_no, “item”)

qty = _get_batch_qty(

batch_no=batch_no,

warehouse=warehouse,

item_code=item_code,

posting_date=posting_date,

posting_time=posting_time,

)

return flt(qty or 0)

@frappe.whitelist()

def get_all_batches_for_item(item_code, warehouse, required_qty=None, stock_entry_name=None):

“”“Return limited batches up to required_qty, matching item table qty (not dialog Qty to Fetch).”“”

from frappe import _dict

from frappe.utils import flt

from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import \

get_auto_batch_nos

try:

required_qty = flt(required_qty or 0)

if required_qty <= 0:

return {“total_qty”: 0, “batches”: }

kwargs = _dict({

“item_code”: item_code,

“warehouse”: warehouse,

“has_batch_no”: 1,

“consider_negative_batches”: False,

“based_on”: “FIFO”,

    })

batches = get_auto_batch_nos(kwargs)

# batches.sort(key=lambda b: flt(b.qty))

import re

def natural_key(text):

return [int(t) if t.isdigit() else t for t in re.split(r’(\d+)', text)]

batches.sort(key=lambda b: natural_key(b.get(“batch_no”)))

selected_batches =

total_picked = 0.0

cache = frappe.cache()

cache_key = f"batch_usage_{stock_entry_name}_{item_code}"

cache.delete_value(cache_key)

usage_cache = {}

usage_cache = cache.get_value(cache_key) or {}

if len(batches) == 1:

b = batches[0]

batch_qty = flt(b.qty)

pick_qty = required_qty if batch_qty > required_qty else batch_qty

selected_batches.append({

“batch_no”: b.get(“batch_no”),

“picked_qty”: flt(pick_qty, 3),

“available_qty”: batch_qty,

        })

return {

“total_qty”: flt(pick_qty, 3),

“batches”: selected_batches,

        }

# for b in batches:

# if total_picked >= required_qty:

# break

# batch_qty = flt(b.qty)

# remaining = required_qty - total_picked

# pick_qty = batch_qty if batch_qty <= remaining else remaining

# selected_batches.append({

# “batch_no”: b.get(“batch_no”),

# “picked_qty”: flt(pick_qty, 3),

# “available_qty”: batch_qty,

# })

# total_picked += pick_qty

# print(“batchhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh”,selected_batches)

# return {

# “total_qty”: flt(total_picked, 3),

# “batches”: selected_batches,

# }

for b in batches:

if total_picked >= required_qty:

print(f"STOP → Target fulfilled ({total_picked})")

break

batch_no = b.get(“batch_no”)

available_qty = flt(b.get(“qty”))

already_used_db = frappe.db.sql(“”"

            SELECT IFNULL(SUM(sbe.qty), 0)

            FROM \`tabSerial and Batch Entry\` sbe

            INNER JOIN \`tabSerial and Batch Bundle\` sb

            ON sb.name = sbe.parent

            WHERE sbe.batch_no = %s

            AND sb.voucher_type='Stock Entry'

            AND sb.voucher_no=%s

            AND sb.item_code=%s

            AND sb.docstatus=1

        """, (batch_no, stock_entry_name, item_code))\[0\]\[0\]

already_used_temp = usage_cache.get(batch_no, 0)

effective_used = already_used_db + already_used_temp

remaining_qty = available_qty - effective_used

if remaining_qty <= 0:

print(" SKIPPING (no qty left)\n")

continue

pending_required = required_qty - total_picked

pick_qty = min(pending_required, remaining_qty)

selected_batches.append({

“batch_no”: batch_no,

“picked_qty”: pick_qty,

“available_qty”: available_qty,

        })

total_picked += pick_qty

usage_cache[batch_no] = already_used_temp + pick_qty

cache.set_value(cache_key, usage_cache)

return {

“total_qty”: flt(total_picked, 3),

“batches”: selected_batches,

    }

except Exception as e:

msg = f"Error fetching batches for item {item_code}: {e}"

frappe.log_error(msg, “Batch Auto Fetch Error”)

frappe.throw(msg)

@frappe.whitelist()

def create_serial_batch_bundle_from_js(parent_doc, child_row, entries):

import frappe

from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import \

create_serial_batch_no_ledgers

try:

parent_doc = frappe.parse_json(parent_doc)

child_row = frappe.parse_json(child_row)

entries = frappe.parse_json(entries)

item_code = child_row.get(“item_code”) or “Unknown Item”

existing = child_row.get(“serial_and_batch_bundle”)

warehouse = child_row.get(“s_warehouse”) or child_row.get(“t_warehouse”)

if existing and frappe.db.exists(“Serial and Batch Bundle”, existing):

return {“name”: existing}

bundle_doc = create_serial_batch_no_ledgers(

entries, child_row, parent_doc, warehouse=warehouse

    )

frappe.db.set_value(

child_row.get(“doctype”),

child_row.get(“name”),

“serial_and_batch_bundle”,

bundle_doc.name

    )

frappe.db.commit()

fatch_uid_from_incoming_iqc(bundle_doc.name, child_row, item_code)

return {“name”: bundle_doc.name}

except Exception as e:

msg = f"Error creating Serial & Batch Bundle for {item_code}: {e}"

frappe.log_error(msg, “Create Bundle Error”)

frappe.throw(msg)

@frappe.whitelist()

def get_all_serials_for_item(item_code, warehouse, required_qty=None):

from frappe.utils import flt

required_qty = flt(required_qty or 0)

if required_qty <= 0:

return {“total_qty”: 0, “serials”: }

serials = frappe.db.sql(“”"

    SELECT name AS serial_no, batch_no

    FROM \`tabSerial No\`

    WHERE item_code = %s

    AND warehouse = %s

    ORDER BY creation ASC

    LIMIT %s

""", (item_code, warehouse, int(required_qty)), as_dict=True)

serial_list =

for s in serials:

serial_list.append({

“serial_no”: s.serial_no,

“batch_no”: s.batch_no,

“picked_qty”: 1

    })

return {

“total_qty”: len(serial_list),

“serials”: serial_list

}

# def before_insert(doc, method=None):

# if doc.stock_entry_type:

# if doc.stock_entry_type == “Material Transfer for Manufacture”:

# doc.inspection_required = 0

# elif doc.stock_entry_type == “Manufacture”:

# doc.inspection_required = 1

def before_insert(doc, method=None):

doc.stock_entry_type == “Manufacture”

for row in doc.items:

if row.is_finished_item ==1:

if row.item_code and row.item_code.upper().endswith((“FA”, “FB”, “FC”)):

doc.inspection_required = 1

elif row.item_code and row.item_code.upper().startswith((“FGI”, “FGS”)):

doc.inspection_required = 1

break

def clean_cache_on_delete(doc, method):

KEY_PATTERN = “*STET-25-26-02572*”

cache = frappe.cache()

keys = cache.get_keys(KEY_PATTERN)

if not keys:

return

for key in keys:

real_key = key.decode() if isinstance(key, bytes) else key

cache.delete(real_key)

Could you edit your message and wrap them in code blocks