Merge branch 'develop' into fixed-serial-no-save-performance-issue

This commit is contained in:
rohitwaghchaure
2026-01-09 16:57:24 +05:30
committed by GitHub
300 changed files with 27114 additions and 21626 deletions

View File

@@ -3748,9 +3748,9 @@ def validate_child_on_delete(row, parent, ordered_item=None):
)
if flt(row.ordered_qty):
frappe.throw(
_("Row #{0}: Cannot delete item {1} which is assigned to customer's purchase order.").format(
row.idx, row.item_code
)
_(
"Row #{0}: Cannot delete item {1} which is already ordered against this Sales Order."
).format(row.idx, row.item_code)
)
if parent.doctype == "Purchase Order" and flt(row.received_qty):

View File

@@ -51,7 +51,7 @@ class BuyingController(SubcontractingController):
self.validate_purchase_receipt_if_update_stock()
if self.doctype == "Purchase Receipt" or (self.doctype == "Purchase Invoice" and self.update_stock):
# self.validate_purchase_return()
self.validate_purchase_return()
self.validate_rejected_warehouse()
self.validate_accepted_rejected_qty()
validate_for_items(self)
@@ -682,15 +682,8 @@ class BuyingController(SubcontractingController):
def validate_purchase_return(self):
for d in self.get("items"):
if self.is_return and flt(d.rejected_qty) != 0:
frappe.throw(
_("Row #{idx}: {field_label} is not allowed in Purchase Return.").format(
idx=d.idx,
field_label=_(d.meta.get_label("rejected_qty")),
)
)
# validate rate with ref PR
if self.is_return and not flt(d.rejected_qty) and d.rejected_warehouse:
d.rejected_warehouse = None
# validate accepted and rejected qty
def validate_accepted_rejected_qty(self):

View File

@@ -188,7 +188,7 @@ def find_variant(template, args, variant_item_code=None):
for attribute, value in args.items():
for row in variant.attributes:
if row.attribute == attribute and row.attribute_value == cstr(value):
if row.attribute == _(attribute) and row.attribute_value == cstr(value):
# this row matches
match_count += 1
break
@@ -209,7 +209,7 @@ def create_variant(item, args, use_template_image=False):
variant_attributes = []
for d in template.attributes:
variant_attributes.append({"attribute": d.attribute, "attribute_value": args.get(d.attribute)})
variant_attributes.append({"attribute": d.attribute, "attribute_value": args.get(_(d.attribute))})
variant.set("attributes", variant_attributes)
copy_attributes_to_variant(template, variant)

View File

@@ -7,7 +7,7 @@ import frappe
from frappe import _, bold
from frappe.model.meta import get_field_precision
from frappe.query_builder import DocType
from frappe.query_builder.functions import Abs
from frappe.query_builder.functions import Abs, Sum
from frappe.utils import cint, flt, format_datetime, get_datetime
import erpnext
@@ -313,6 +313,68 @@ def get_already_returned_items(doc):
return items
def get_returned_qty_map_for_purchase_flow(return_against, supplier, row_name, doctype):
# return map of warehouses with qty and stock qty
# Example: {'_Test Rejected Warehouse - _TC': {'qty': 5.0, 'stock_qty': 5.0}, '_Test Warehouse - _TC': {'qty': 8.0, 'stock_qty': 8.0}}
parent_doc = frappe.qb.DocType(doctype)
child_doc = frappe.qb.DocType(doctype + " Item")
query = (
frappe.qb.from_(parent_doc)
.inner_join(child_doc)
.on(child_doc.parent == parent_doc.name)
.select(
child_doc.qty,
child_doc.rejected_qty,
child_doc.warehouse,
child_doc.rejected_warehouse,
child_doc.conversion_factor,
)
.where(
(parent_doc.return_against == return_against)
& (parent_doc.supplier == supplier)
& (parent_doc.docstatus == 1)
& (parent_doc.is_return == 1)
)
)
if doctype != "Subcontracting Receipt":
query = query.select(child_doc.stock_qty)
doctype_field_map = {
"Purchase Receipt": child_doc.purchase_receipt_item,
"Subcontracting Receipt": child_doc.subcontracting_receipt_item,
}
field = doctype_field_map.get(doctype)
if field:
query = query.where(field == row_name)
data = query.run(as_dict=True)
_return_map = frappe._dict({})
for row in data:
if row.warehouse and row.warehouse not in _return_map:
_return_map[row.warehouse] = frappe._dict({"qty": 0, "stock_qty": 0})
if row.rejected_warehouse and row.rejected_warehouse not in _return_map:
_return_map[row.rejected_warehouse] = frappe._dict({"qty": 0, "stock_qty": 0})
if row.warehouse:
qty_map = _return_map.get(row.warehouse)
qty_map.qty += abs(flt(row.qty))
qty_map.stock_qty += abs(flt(row.stock_qty))
if row.rejected_warehouse:
rejected_qty_map = _return_map.get(row.rejected_warehouse)
rejected_qty_map.qty += abs(flt(row.rejected_qty))
rejected_qty_map.stock_qty += abs(flt(row.rejected_qty) * flt(row.conversion_factor))
return _return_map
def get_returned_qty_map_for_row(return_against, party, row_name, doctype):
child_doctype = doctype + " Item"
reference_field = "dn_detail" if doctype == "Delivery Note" else frappe.scrub(child_doctype)
@@ -459,29 +521,22 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None, return_agai
target_doc.pricing_rules = None
if doctype in ["Purchase Receipt", "Subcontracting Receipt"]:
returned_qty_map = get_returned_qty_map_for_row(
returned_qty_map = get_returned_qty_map_for_purchase_flow(
source_parent.name, source_parent.supplier, source_doc.name, doctype
)
wh_map = returned_qty_map.get(source_doc.warehouse) or frappe._dict()
rejected_wh_map = returned_qty_map.get(source_doc.rejected_warehouse) or frappe._dict()
if doctype == "Subcontracting Receipt":
target_doc.received_qty = -1 * flt(source_doc.qty)
else:
target_doc.received_qty = -1 * flt(
source_doc.received_qty - (returned_qty_map.get("received_qty") or 0)
)
target_doc.rejected_qty = -1 * flt(
source_doc.rejected_qty - (returned_qty_map.get("rejected_qty") or 0)
)
target_doc.rejected_qty = -1 * flt(source_doc.rejected_qty - (rejected_wh_map.qty or 0))
target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get("qty") or 0))
target_doc.qty = -1 * flt(source_doc.qty - (wh_map.qty or 0))
if hasattr(target_doc, "stock_qty") and not return_against_rejected_qty:
target_doc.stock_qty = -1 * flt(
source_doc.stock_qty - (returned_qty_map.get("stock_qty") or 0)
)
target_doc.received_stock_qty = -1 * flt(
source_doc.received_stock_qty - (returned_qty_map.get("received_stock_qty") or 0)
)
target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (flt(wh_map.stock_qty) or 0))
if doctype == "Subcontracting Receipt":
target_doc.subcontracting_order = source_doc.subcontracting_order
@@ -489,7 +544,7 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None, return_agai
target_doc.rejected_warehouse = source_doc.rejected_warehouse
target_doc.subcontracting_receipt_item = source_doc.name
if return_against_rejected_qty:
target_doc.qty = -1 * flt(source_doc.rejected_qty - (returned_qty_map.get("qty") or 0))
target_doc.qty = -1 * flt(source_doc.rejected_qty - (rejected_wh_map.qty or 0))
target_doc.rejected_qty = 0.0
target_doc.rejected_warehouse = ""
target_doc.warehouse = source_doc.rejected_warehouse
@@ -502,7 +557,7 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None, return_agai
target_doc.purchase_receipt_item = source_doc.name
if doctype == "Purchase Receipt" and return_against_rejected_qty:
target_doc.qty = -1 * flt(source_doc.rejected_qty - (returned_qty_map.get("qty") or 0))
target_doc.qty = -1 * flt(source_doc.rejected_qty - (rejected_wh_map.qty or 0))
target_doc.rejected_qty = 0.0
target_doc.rejected_warehouse = ""
target_doc.warehouse = source_doc.rejected_warehouse
@@ -580,6 +635,14 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None, return_agai
):
target_doc.set("use_serial_batch_fields", 1)
if (
not source_doc.serial_no
and not source_doc.batch_no
and source_doc.serial_and_batch_bundle
and source_doc.use_serial_batch_fields
):
target_doc.set("use_serial_batch_fields", 0)
if source_doc.item_code and target_doc.get("use_serial_batch_fields"):
item_details = frappe.get_cached_value(
"Item", source_doc.item_code, ["has_batch_no", "has_serial_no"], as_dict=1

View File

@@ -1022,10 +1022,19 @@ class SellingController(StockController):
def set_default_income_account_for_item(obj):
for d in obj.get("items"):
if d.item_code:
if getattr(d, "income_account", None):
set_item_default(d.item_code, obj.company, "income_account", d.income_account)
"""Set income account as default for items in the transaction.
Updates the item default income account for each item in the transaction
if it differs from the company's default income account.
Args:
obj: Transaction document containing items table with income_account field
"""
company_default = frappe.get_cached_value("Company", obj.company, "default_income_account")
for d in obj.get("items", default=[]):
income_account = getattr(d, "income_account", None)
if d.item_code and income_account and income_account != company_default:
set_item_default(d.item_code, obj.company, "income_account", income_account)
def get_serial_and_batch_bundle(child, parent, delivery_note_child=None):

View File

@@ -184,6 +184,9 @@ class StatusUpdater(Document):
Installation Note: Update Installed Qty, Update Percent Qty and Validate over installation
"""
def on_discard(self):
self.db_set("status", "Cancelled")
def update_prevdoc_status(self):
self.update_qty()
self.validate_qty()

View File

@@ -1401,6 +1401,7 @@ def make_rm_stock_entry(
stock_entry.set_stock_entry_type()
over_transfer_allowance = frappe.get_single_value("Buying Settings", "over_transfer_allowance")
for fg_item_code in fg_item_code_list:
for rm_item in rm_items:
if (
@@ -1408,14 +1409,27 @@ def make_rm_stock_entry(
or rm_item.get("item_code") == fg_item_code
):
rm_item_code = rm_item.get("rm_item_code")
qty = rm_item.get("qty") or max(
rm_item.get("required_qty") - rm_item.get("total_supplied_qty"), 0
)
if qty <= 0 and rm_item.get("total_supplied_qty"):
per_transferred = (
flt(
rm_item.get("total_supplied_qty") / rm_item.get("required_qty"),
frappe.db.get_default("float_precision"),
)
* 100
)
if per_transferred >= 100 + over_transfer_allowance:
continue
items_dict = {
rm_item_code: {
rm_detail_field: rm_item.get("name"),
"item_name": rm_item.get("item_name")
or item_wh.get(rm_item_code, {}).get("item_name", ""),
"description": item_wh.get(rm_item_code, {}).get("description", ""),
"qty": rm_item.get("qty")
or max(rm_item.get("required_qty") - rm_item.get("total_supplied_qty"), 0),
"qty": qty,
"from_warehouse": rm_item.get("warehouse")
or rm_item.get("reserve_warehouse"),
"to_warehouse": subcontract_order.supplier_warehouse,