fix: filter overdue purchase order items by company (backport #54099) (#54611)

Co-authored-by: Ravibharathi <131471282+ravibharathi656@users.noreply.github.com>
fix: filter overdue purchase order items by company (#54099)
This commit is contained in:
mergify[bot]
2026-04-29 01:25:31 +05:30
committed by GitHub
parent 11117710d3
commit 8f8bf13b41
3 changed files with 133 additions and 55 deletions

View File

@@ -158,10 +158,7 @@ class EmailDigest(Document):
context.quote = {"text": quote[0], "author": quote[1]}
if self.get("purchase_orders_items_overdue"):
(
context.purchase_order_list,
context.purchase_orders_items_overdue_list,
) = self.get_purchase_orders_items_overdue_list()
context.purchase_orders_items_overdue_map = self.get_purchase_orders_items_overdue_list()
if not context:
return None
@@ -860,30 +857,42 @@ class EmailDigest(Document):
return fmt_money(value, currency=self.currency)
def get_purchase_orders_items_overdue_list(self):
fields_po = "distinct `tabPurchase Order Item`.parent as po"
fields_poi = (
"`tabPurchase Order Item`.parent, `tabPurchase Order Item`.schedule_date, item_code,"
"received_qty, qty - received_qty as missing_qty, rate, amount"
po = frappe.qb.DocType("Purchase Order")
poi = frappe.qb.DocType("Purchase Order Item")
query = (
frappe.qb.from_(poi)
.select(
poi.parent,
poi.schedule_date,
poi.item_code,
poi.received_qty,
(poi.qty - poi.received_qty).as_("missing_qty"),
poi.rate,
poi.amount,
po.currency,
)
.inner_join(po)
.on(po.name == poi.parent)
.where(po.status != "Closed")
.where(poi.docstatus == 1)
.where(poi.schedule_date < today())
.where(poi.received_qty < poi.qty)
.where(po.company == self.company)
.orderby(poi.parent, order=frappe.qb.desc)
.orderby(poi.idx)
)
sql_po = f"""select {fields_po} from `tabPurchase Order Item`
left join `tabPurchase Order` on `tabPurchase Order`.name = `tabPurchase Order Item`.parent
where status<>'Closed' and `tabPurchase Order Item`.docstatus=1 and CURRENT_DATE > `tabPurchase Order Item`.schedule_date
and received_qty < qty order by `tabPurchase Order Item`.parent DESC,
`tabPurchase Order Item`.schedule_date DESC"""
items_by_parent = frappe._dict()
sql_poi = f"""select {fields_poi} from `tabPurchase Order Item`
left join `tabPurchase Order` on `tabPurchase Order`.name = `tabPurchase Order Item`.parent
where status<>'Closed' and `tabPurchase Order Item`.docstatus=1 and CURRENT_DATE > `tabPurchase Order Item`.schedule_date
and received_qty < qty order by `tabPurchase Order Item`.idx"""
purchase_order_list = frappe.db.sql(sql_po, as_dict=True)
purchase_order_items_overdue_list = frappe.db.sql(sql_poi, as_dict=True)
for row in query.run(as_dict=True):
row.link = get_url_to_form("Purchase Order", row.parent)
row.rate = fmt_money(row.rate, 2, row.currency)
row.amount = fmt_money(row.amount, 2, row.currency)
for t in purchase_order_items_overdue_list:
t.link = get_url_to_form("Purchase Order", t.parent)
t.rate = fmt_money(t.rate, 2, t.currency)
t.amount = fmt_money(t.amount, 2, t.currency)
return purchase_order_list, purchase_order_items_overdue_list
items_by_parent.setdefault(row.parent, []).append(row)
return items_by_parent
def send():

View File

@@ -182,7 +182,7 @@
{% endif %}
<!-- Purchase Order Items Overdue -->
{% if purchase_orders_items_overdue_list %}
{% if purchase_orders_items_overdue_map %}
<h4 style="{{ section_head }}" class="text-center">{{ _("Purchase Order Items not received on time") }}</h4>
<div>
<div style="background-color: #fafbfc;">
@@ -206,43 +206,41 @@
<hr>
</div>
<div>
{% for po in purchase_order_list %}
{% for po, po_items in purchase_orders_items_overdue_map.items() %}
<div style="{{ line_item }}">
<table style="width: 100%;">
<tr>
<th>
<span style="padding: 3px 7px; margin-right: 7px; font-weight: bold;">{{ po.po }}</span>
<span style="padding: 3px 7px; margin-right: 7px; font-weight: bold;">{{ po | e }}</span>
</th>
</tr>
<tr>
<td>
{% for t in purchase_orders_items_overdue_list %}
{% if t.parent == po.po %}
<div >
<table style="width: 100%;">
<tr>
<td style="padding-left: 7px;">
<a style="width: 40%; {{ link_css }}" href="{{ t.link }}">{{ _(t.item_code) }}</a>
</td>
<td style="width: 20%; text-align: right">
<span style="{{ label_css }}">
{{ t.missing_qty }}
</span>
</td>
<td style="width: 20%; text-align: right">
<span style="{{ label_css }}">
{{ t.rate }}
</span>
</td>
<td style="width: 20%; text-align: right">
<span style="{{ label_css }}">
{{ t.amount }}
</span>
</td>
</tr>
</table>
</div>
{% endif %}
{% for row in po_items %}
<div >
<table style="width: 100%; table-layout: fixed;">
<tr>
<td style="width: 40%; padding-left: 7px; vertical-align: top;">
<a style="{{ link_css }}" href="{{ row.link | e }}">{{ _(row.item_code) | e }}</a>
</td>
<td style="width: 20%; text-align: right; white-space: nowrap; vertical-align: top;">
<span style="{{ label_css }}">
{{ row.missing_qty | e }}
</span>
</td>
<td style="width: 20%; text-align: right; white-space: nowrap; vertical-align: top;">
<span style="{{ label_css }}">
{{ row.rate | e }}
</span>
</td>
<td style="width: 20%; text-align: right; white-space: nowrap; vertical-align: top;">
<span style="{{ label_css }}">
{{ row.amount | e }}
</span>
</td>
</tr>
</table>
</div>
{% endfor %}
</td>
</tr>

View File

@@ -2,8 +2,79 @@
# See license.txt
import unittest
import frappe
from frappe.utils import add_days, today
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
from erpnext.tests.utils import ERPNextTestSuite
class TestEmailDigest(ERPNextTestSuite):
pass
def test_purchase_orders_items_overdue_list_is_filtered_by_company(self):
digest = create_email_digest(
company="_Test Company",
frequency="Daily",
purchase_orders_items_overdue=1,
name="Test Email Digest PO Company Filter",
)
backdate = add_days(today(), -1)
po1 = create_purchase_order(transaction_date=backdate, do_not_save=True)
po1.schedule_date = backdate
po1.items[0].schedule_date = backdate
po1.insert()
po1.submit()
po2 = create_purchase_order(
company="_Test Company 1",
warehouse="Stores - _TC1",
transaction_date=backdate,
do_not_save=True,
)
po2.schedule_date = backdate
po2.items[0].schedule_date = backdate
po2.insert()
po2.submit()
overdue_items = digest.get_purchase_orders_items_overdue_list()
self.assertIn(po1.name, overdue_items)
self.assertNotIn(po2.name, overdue_items)
def create_email_digest(**args):
args = frappe._dict(args)
doc = frappe.new_doc("Email Digest")
doc.name = args.name or "Test Email Digest"
doc.company = args.company or "_Test Company"
doc.frequency = args.frequency or "Daily"
doc.enabled = args.enabled or 0
doc.bank_balance = args.bank_balance or 0
doc.credit_balance = args.credit_balance or 0
doc.invoiced_amount = args.invoiced_amount or 0
doc.payables = args.payables or 0
doc.sales_orders_to_bill = args.sales_orders_to_bill or 0
doc.purchase_orders_to_bill = args.purchase_orders_to_bill or 0
doc.sales_order = args.sales_order or 0
doc.purchase_order = args.purchase_order or 0
doc.sales_orders_to_deliver = args.sales_orders_to_deliver or 0
doc.purchase_orders_to_receive = args.purchase_orders_to_receive or 0
doc.sales_invoice = args.sales_invoice or 0
doc.purchase_invoice = args.purchase_invoice or 0
doc.new_quotations = args.new_quotations or 0
doc.pending_quotations = args.pending_quotations or 0
doc.issue = args.issue or 0
doc.project = args.project or 0
doc.purchase_orders_items_overdue = args.purchase_orders_items_overdue or 0
doc.calendar_events = args.calendar_events or 0
doc.todo_list = args.todo_list or 0
doc.notifications = args.notifications or 0
doc.add_quote = args.add_quote or 0
for recipient in args.recipients or ["Administrator"]:
doc.append("recipients", {"recipient": recipient})
if not args.do_not_save:
doc.insert()
return doc