mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-30 07:08:24 +00:00
Merge branch 'develop' into fixed-serial-no-save-performance-issue
This commit is contained in:
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user