mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-27 17:04:47 +00:00
Merge pull request #52229 from frappe/mergify/bp/version-15-hotfix/pr-52222
This commit is contained in:
@@ -341,14 +341,17 @@ class StatusUpdater(Document):
|
|||||||
):
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
if qty_or_amount == "qty":
|
if args["target_dt"] != "Quotation Item":
|
||||||
action_msg = _(
|
if qty_or_amount == "qty":
|
||||||
'To allow over receipt / delivery, update "Over Receipt/Delivery Allowance" in Stock Settings or the Item.'
|
action_msg = _(
|
||||||
)
|
'To allow over receipt / delivery, update "Over Receipt/Delivery Allowance" in Stock Settings or the Item.'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
action_msg = _(
|
||||||
|
'To allow over billing, update "Over Billing Allowance" in Accounts Settings or the Item.'
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
action_msg = _(
|
action_msg = None
|
||||||
'To allow over billing, update "Over Billing Allowance" in Accounts Settings or the Item.'
|
|
||||||
)
|
|
||||||
|
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
|
|||||||
@@ -428,3 +428,4 @@ execute:frappe.db.set_single_value("Accounts Settings", "show_party_balance", 1)
|
|||||||
execute:frappe.db.set_single_value("Accounts Settings", "show_account_balance", 1)
|
execute:frappe.db.set_single_value("Accounts Settings", "show_account_balance", 1)
|
||||||
erpnext.patches.v16_0.update_currency_exchange_settings_for_frankfurter #2025-12-11
|
erpnext.patches.v16_0.update_currency_exchange_settings_for_frankfurter #2025-12-11
|
||||||
erpnext.patches.v15_0.create_accounting_dimensions_in_advance_taxes_and_charges
|
erpnext.patches.v15_0.create_accounting_dimensions_in_advance_taxes_and_charges
|
||||||
|
erpnext.patches.v16_0.set_ordered_qty_in_quotation_item
|
||||||
|
|||||||
16
erpnext/patches/v16_0/set_ordered_qty_in_quotation_item.py
Normal file
16
erpnext/patches/v16_0/set_ordered_qty_in_quotation_item.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
data = frappe.get_all(
|
||||||
|
"Sales Order Item",
|
||||||
|
filters={"quotation_item": ["is", "set"], "docstatus": 1},
|
||||||
|
fields=["quotation_item", "sum(stock_qty) as ordered_qty"],
|
||||||
|
group_by="quotation_item",
|
||||||
|
)
|
||||||
|
if data:
|
||||||
|
frappe.db.auto_commit_on_many_writes = 1
|
||||||
|
frappe.db.bulk_update(
|
||||||
|
"Quotation Item", {d.quotation_item: {"ordered_qty": d.ordered_qty} for d in data}
|
||||||
|
)
|
||||||
|
frappe.db.auto_commit_on_many_writes = 0
|
||||||
@@ -446,7 +446,10 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False, ar
|
|||||||
"Quotation",
|
"Quotation",
|
||||||
source_name,
|
source_name,
|
||||||
{
|
{
|
||||||
"Quotation": {"doctype": "Sales Order", "validation": {"docstatus": ["=", 1]}},
|
"Quotation": {
|
||||||
|
"doctype": "Sales Order",
|
||||||
|
"validation": {"docstatus": ["=", 1]},
|
||||||
|
},
|
||||||
"Quotation Item": {
|
"Quotation Item": {
|
||||||
"doctype": "Sales Order Item",
|
"doctype": "Sales Order Item",
|
||||||
"field_map": {"parent": "prevdoc_docname", "name": "quotation_item"},
|
"field_map": {"parent": "prevdoc_docname", "name": "quotation_item"},
|
||||||
@@ -549,6 +552,8 @@ def _make_customer(source_name, ignore_permissions=False):
|
|||||||
|
|
||||||
if quotation.quotation_to == "Customer":
|
if quotation.quotation_to == "Customer":
|
||||||
return frappe.get_doc("Customer", quotation.party_name)
|
return frappe.get_doc("Customer", quotation.party_name)
|
||||||
|
elif quotation.quotation_to == "CRM Deal":
|
||||||
|
return frappe.get_doc("Customer", {"crm_deal": quotation.party_name})
|
||||||
|
|
||||||
# Check if a Customer already exists for the Lead or Prospect.
|
# Check if a Customer already exists for the Lead or Prospect.
|
||||||
existing_customer = None
|
existing_customer = None
|
||||||
@@ -610,25 +615,8 @@ def handle_mandatory_error(e, customer, lead_name):
|
|||||||
|
|
||||||
|
|
||||||
def get_ordered_items(quotation: str):
|
def get_ordered_items(quotation: str):
|
||||||
"""
|
|
||||||
Returns a dict of ordered items with their total qty based on quotation row name.
|
|
||||||
|
|
||||||
In `Sales Order Item`, `quotation_item` is the row name of `Quotation Item`.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"refsdjhd2": 10,
|
|
||||||
"ygdhdshrt": 5,
|
|
||||||
}
|
|
||||||
```
|
|
||||||
"""
|
|
||||||
return frappe._dict(
|
return frappe._dict(
|
||||||
frappe.get_all(
|
frappe.get_all(
|
||||||
"Sales Order Item",
|
"Quotation Item", {"docstatus": 1, "parent": quotation}, ["name", "ordered_qty"], as_list=True
|
||||||
filters={"prevdoc_docname": quotation, "docstatus": 1},
|
|
||||||
fields=["quotation_item", "sum(qty)"],
|
|
||||||
group_by="quotation_item",
|
|
||||||
as_list=1,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -828,7 +828,7 @@ class TestQuotation(FrappeTestCase):
|
|||||||
# item code same but description different
|
# item code same but description different
|
||||||
make_item("_Test Item 2", {"is_stock_item": 1})
|
make_item("_Test Item 2", {"is_stock_item": 1})
|
||||||
|
|
||||||
quotation = make_quotation(qty=1, rate=100, do_not_submit=1)
|
quotation = make_quotation(qty=10, rate=100, do_not_submit=1)
|
||||||
|
|
||||||
# duplicate items
|
# duplicate items
|
||||||
for qty in [1, 1, 2, 3]:
|
for qty in [1, 1, 2, 3]:
|
||||||
@@ -842,7 +842,7 @@ class TestQuotation(FrappeTestCase):
|
|||||||
sales_order.delivery_date = nowdate()
|
sales_order.delivery_date = nowdate()
|
||||||
|
|
||||||
self.assertEqual(len(sales_order.items), 6)
|
self.assertEqual(len(sales_order.items), 6)
|
||||||
self.assertEqual(sales_order.items[0].qty, 1)
|
self.assertEqual(sales_order.items[0].qty, 10)
|
||||||
self.assertEqual(sales_order.items[-1].qty, 5)
|
self.assertEqual(sales_order.items[-1].qty, 5)
|
||||||
|
|
||||||
# Row 1: 10, Row 4: 1, Row 5: 1
|
# Row 1: 10, Row 4: 1, Row 5: 1
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
"uom",
|
"uom",
|
||||||
"conversion_factor",
|
"conversion_factor",
|
||||||
"stock_qty",
|
"stock_qty",
|
||||||
|
"ordered_qty",
|
||||||
"available_quantity_section",
|
"available_quantity_section",
|
||||||
"actual_qty",
|
"actual_qty",
|
||||||
"column_break_ylrv",
|
"column_break_ylrv",
|
||||||
@@ -694,12 +695,23 @@
|
|||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"report_hide": 1
|
"report_hide": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "ordered_qty",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Ordered Qty",
|
||||||
|
"no_copy": 1,
|
||||||
|
"non_negative": 1,
|
||||||
|
"read_only": 1,
|
||||||
|
"reqd": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-08-26 20:31:47.775890",
|
"modified": "2026-01-30 12:56:08.320190",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Quotation Item",
|
"name": "Quotation Item",
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ class QuotationItem(Document):
|
|||||||
margin_type: DF.Literal["", "Percentage", "Amount"]
|
margin_type: DF.Literal["", "Percentage", "Amount"]
|
||||||
net_amount: DF.Currency
|
net_amount: DF.Currency
|
||||||
net_rate: DF.Currency
|
net_rate: DF.Currency
|
||||||
|
ordered_qty: DF.Float
|
||||||
page_break: DF.Check
|
page_break: DF.Check
|
||||||
parent: DF.Data
|
parent: DF.Data
|
||||||
parentfield: DF.Data
|
parentfield: DF.Data
|
||||||
|
|||||||
@@ -185,6 +185,16 @@ class SalesOrder(SellingController):
|
|||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
self.status_updater = [
|
||||||
|
{
|
||||||
|
"source_dt": "Sales Order Item",
|
||||||
|
"target_dt": "Quotation Item",
|
||||||
|
"join_field": "quotation_item",
|
||||||
|
"target_field": "ordered_qty",
|
||||||
|
"target_ref_field": "stock_qty",
|
||||||
|
"source_field": "stock_qty",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
def onload(self) -> None:
|
def onload(self) -> None:
|
||||||
super().onload()
|
super().onload()
|
||||||
@@ -419,6 +429,7 @@ class SalesOrder(SellingController):
|
|||||||
frappe.throw(_("Row #{0}: Set Supplier for item {1}").format(d.idx, d.item_code))
|
frappe.throw(_("Row #{0}: Set Supplier for item {1}").format(d.idx, d.item_code))
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
|
super().update_prevdoc_status()
|
||||||
self.check_credit_limit()
|
self.check_credit_limit()
|
||||||
self.update_reserved_qty()
|
self.update_reserved_qty()
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase):
|
|||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
frappe.set_user("Administrator")
|
frappe.set_user("Administrator")
|
||||||
|
|
||||||
|
@change_settings("Selling Settings", {"allow_negative_rates_for_items": 1})
|
||||||
def test_sales_order_with_negative_rate(self):
|
def test_sales_order_with_negative_rate(self):
|
||||||
"""
|
"""
|
||||||
Test if negative rate is allowed in Sales Order via doc submission and update items
|
Test if negative rate is allowed in Sales Order via doc submission and update items
|
||||||
|
|||||||
Reference in New Issue
Block a user