mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-14 15:11:52 +00:00
Merge pull request #56212 from mihir-kandoi/pg-controllers-selling-status-website
refactor(controllers): selling/status_updater/website raw SQL → qb/ORM (Postgres)
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _, bold, throw
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.utils import cint, flt, get_link_to_form, nowtime
|
||||
|
||||
from erpnext.accounts.party import render_address
|
||||
@@ -439,22 +440,34 @@ class SellingController(StockController):
|
||||
product_bundle_items[item_code] = item_code in items_with_product_bundle
|
||||
|
||||
def get_already_delivered_qty(self, current_docname, so, so_detail):
|
||||
delivered_via_dn = frappe.db.sql(
|
||||
"""select sum(qty) from `tabDelivery Note Item`
|
||||
where so_detail = %s and docstatus = 1
|
||||
and against_sales_order = %s
|
||||
and parent != %s""",
|
||||
(so_detail, so, current_docname),
|
||||
dn_item = frappe.qb.DocType("Delivery Note Item")
|
||||
delivered_via_dn = (
|
||||
frappe.qb.from_(dn_item)
|
||||
.select(Sum(dn_item.qty))
|
||||
.where(
|
||||
(dn_item.so_detail == so_detail)
|
||||
& (dn_item.docstatus == 1)
|
||||
& (dn_item.against_sales_order == so)
|
||||
& (dn_item.parent != current_docname)
|
||||
)
|
||||
.run()
|
||||
)
|
||||
|
||||
delivered_via_si = frappe.db.sql(
|
||||
"""select sum(si_item.qty)
|
||||
from `tabSales Invoice Item` si_item, `tabSales Invoice` si
|
||||
where si_item.parent = si.name and si.update_stock = 1
|
||||
and si_item.so_detail = %s and si.docstatus = 1
|
||||
and si_item.sales_order = %s
|
||||
and si.name != %s""",
|
||||
(so_detail, so, current_docname),
|
||||
si = frappe.qb.DocType("Sales Invoice")
|
||||
si_item = frappe.qb.DocType("Sales Invoice Item")
|
||||
delivered_via_si = (
|
||||
frappe.qb.from_(si_item)
|
||||
.inner_join(si)
|
||||
.on(si_item.parent == si.name)
|
||||
.select(Sum(si_item.qty))
|
||||
.where(
|
||||
(si.update_stock == 1)
|
||||
& (si_item.so_detail == so_detail)
|
||||
& (si.docstatus == 1)
|
||||
& (si_item.sales_order == so)
|
||||
& (si.name != current_docname)
|
||||
)
|
||||
.run()
|
||||
)
|
||||
|
||||
total_delivered_qty = (flt(delivered_via_dn[0][0]) if delivered_via_dn else 0) + (
|
||||
@@ -464,14 +477,11 @@ class SellingController(StockController):
|
||||
return total_delivered_qty
|
||||
|
||||
def get_so_qty_and_warehouse(self, so_detail):
|
||||
so_item = frappe.db.sql(
|
||||
"""select qty, warehouse from `tabSales Order Item`
|
||||
where name = %s and docstatus = 1""",
|
||||
so_detail,
|
||||
as_dict=1,
|
||||
so_item = frappe.db.get_value(
|
||||
"Sales Order Item", {"name": so_detail, "docstatus": 1}, ["qty", "warehouse"], as_dict=True
|
||||
)
|
||||
so_qty = so_item and flt(so_item[0]["qty"]) or 0.0
|
||||
so_warehouse = so_item and so_item[0]["warehouse"] or ""
|
||||
so_qty = flt(so_item.qty) if so_item else 0.0
|
||||
so_warehouse = (so_item.warehouse if so_item else "") or ""
|
||||
return so_qty, so_warehouse
|
||||
|
||||
def check_sales_order_on_hold_or_close(self, ref_fieldname):
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.utils import comma_or, flt, get_link_to_form, getdate, now, nowdate, safe_div
|
||||
|
||||
|
||||
@@ -554,7 +555,7 @@ class StatusUpdater(Document):
|
||||
args["second_source_extra_cond"] = ""
|
||||
|
||||
args["second_source_condition"] = frappe.db.sql(
|
||||
""" select ifnull((select sum({second_source_field})
|
||||
""" select coalesce((select sum({second_source_field})
|
||||
from `tab{second_source_dt}`
|
||||
where `{second_join_field}`=%(detail_id)s
|
||||
and (`tab{second_source_dt}`.docstatus=1)
|
||||
@@ -569,7 +570,7 @@ class StatusUpdater(Document):
|
||||
args["source_dt_value"] = (
|
||||
frappe.db.sql(
|
||||
"""
|
||||
(select ifnull(sum({source_field}), 0)
|
||||
(select coalesce(sum({source_field}), 0)
|
||||
from `tab{source_dt}` where `{join_field}`=%(detail_id)s
|
||||
and (docstatus=1 {cond}) {extra_cond})
|
||||
""".format(**args),
|
||||
@@ -684,18 +685,10 @@ class StatusUpdater(Document):
|
||||
if not ref_docs:
|
||||
return
|
||||
|
||||
zero_amount_refdocs = frappe.db.sql_list(
|
||||
f"""
|
||||
SELECT
|
||||
name
|
||||
from
|
||||
`tab{ref_dt}`
|
||||
where
|
||||
docstatus = 1
|
||||
and base_net_total = 0
|
||||
and name in %(ref_docs)s
|
||||
""",
|
||||
{"ref_docs": ref_docs},
|
||||
zero_amount_refdocs = frappe.get_all(
|
||||
ref_dt,
|
||||
filters={"docstatus": 1, "base_net_total": 0, "name": ["in", ref_docs]},
|
||||
pluck="name",
|
||||
)
|
||||
|
||||
if zero_amount_refdocs:
|
||||
@@ -703,20 +696,20 @@ class StatusUpdater(Document):
|
||||
|
||||
def update_billing_status(self, zero_amount_refdoc, ref_dt, ref_fieldname):
|
||||
for ref_dn in zero_amount_refdoc:
|
||||
ref_item = frappe.qb.DocType(f"{ref_dt} Item")
|
||||
ref_doc_qty = flt(
|
||||
frappe.db.sql(
|
||||
"""select ifnull(sum(qty), 0) from `tab{} Item`
|
||||
where parent={}""".format(ref_dt, "%s"),
|
||||
(ref_dn),
|
||||
)[0][0]
|
||||
frappe.qb.from_(ref_item)
|
||||
.select(Sum(ref_item.qty))
|
||||
.where(ref_item.parent == ref_dn)
|
||||
.run()[0][0]
|
||||
)
|
||||
|
||||
doc_item = frappe.qb.DocType(f"{self.doctype} Item")
|
||||
billed_qty = flt(
|
||||
frappe.db.sql(
|
||||
"""select ifnull(sum(qty), 0)
|
||||
from `tab{} Item` where {}={} and docstatus=1""".format(self.doctype, ref_fieldname, "%s"),
|
||||
(ref_dn),
|
||||
)[0][0]
|
||||
frappe.qb.from_(doc_item)
|
||||
.select(Sum(doc_item.qty))
|
||||
.where((doc_item[ref_fieldname] == ref_dn) & (doc_item.docstatus == 1))
|
||||
.run()[0][0]
|
||||
)
|
||||
|
||||
per_billed = safe_div(min(ref_doc_qty, billed_qty), ref_doc_qty) * 100
|
||||
|
||||
39
erpnext/controllers/tests/test_selling_controller.py
Normal file
39
erpnext/controllers/tests/test_selling_controller.py
Normal file
@@ -0,0 +1,39 @@
|
||||
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import frappe
|
||||
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
|
||||
class TestSellingControllerConversions(ERPNextTestSuite):
|
||||
@staticmethod
|
||||
def _cancel_and_delete(doctype, name):
|
||||
if not frappe.db.exists(doctype, name):
|
||||
return
|
||||
doc = frappe.get_doc(doctype, name)
|
||||
if doc.docstatus == 1:
|
||||
doc.cancel()
|
||||
frappe.delete_doc(doctype, name, force=1)
|
||||
|
||||
def test_partial_delivery_updates_sales_order_status(self):
|
||||
# Submitting a Delivery Note against a Sales Order calls
|
||||
# SellingController.get_already_delivered_qty / get_so_qty_and_warehouse and StatusUpdater
|
||||
# (per_delivered via coalesce(sum(...))) -- all converted to query builder / ORM here.
|
||||
from erpnext.selling.doctype.sales_order.mapper import make_delivery_note
|
||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
|
||||
se = make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=20, basic_rate=100)
|
||||
self.addCleanup(self._cancel_and_delete, "Stock Entry", se.name)
|
||||
|
||||
so = make_sales_order(qty=10)
|
||||
|
||||
dn = make_delivery_note(so.name)
|
||||
dn.items[0].qty = 4
|
||||
dn.insert()
|
||||
dn.submit()
|
||||
self.addCleanup(self._cancel_and_delete, "Delivery Note", dn.name)
|
||||
|
||||
so.reload()
|
||||
self.assertEqual(so.per_delivered, 40.0)
|
||||
19
erpnext/controllers/tests/test_website_list_for_contact.py
Normal file
19
erpnext/controllers/tests/test_website_list_for_contact.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import json
|
||||
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
|
||||
class TestWebsiteListForContact(ERPNextTestSuite):
|
||||
def test_get_list_context_currency_symbols(self):
|
||||
# get_list_context builds the enabled-currency symbol map via frappe.get_all (converted from
|
||||
# raw SQL). Exercises that query and asserts a known enabled currency is present.
|
||||
from erpnext.controllers.website_list_for_contact import get_list_context
|
||||
|
||||
context = get_list_context()
|
||||
|
||||
symbols = json.loads(context["currency_symbols"])
|
||||
self.assertIsInstance(symbols, dict)
|
||||
self.assertIn("USD", symbols)
|
||||
@@ -17,9 +17,12 @@ def get_list_context(context=None):
|
||||
"currency": frappe.db.get_default("currency"),
|
||||
"currency_symbols": json.dumps(
|
||||
dict(
|
||||
frappe.db.sql(
|
||||
"""select name, symbol
|
||||
from tabCurrency where enabled=1"""
|
||||
frappe.get_all(
|
||||
"Currency",
|
||||
filters={"enabled": 1},
|
||||
fields=["name", "symbol"],
|
||||
as_list=True,
|
||||
limit_page_length=0, # all enabled currencies are needed for the symbol map
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user