Skip to content

Commit 7e09bf3

Browse files
fix(accounts receivable): include invoice payment terms template (backport #51940) (#53106)
Co-authored-by: Ravibharathi <[email protected]>
1 parent 109433f commit 7e09bf3

3 files changed

Lines changed: 200 additions & 26 deletions

File tree

erpnext/accounts/report/accounts_payable/test_accounts_payable.py

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import frappe
22
from frappe.tests import IntegrationTestCase
3-
from frappe.utils import today
3+
from frappe.utils import add_days, today
44

55
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
66
from erpnext.accounts.report.accounts_payable.accounts_payable import execute
@@ -57,3 +57,66 @@ def create_purchase_invoice(self, do_not_submit=False):
5757
if not do_not_submit:
5858
pi = pi.submit()
5959
return pi
60+
61+
def test_payment_terms_template_filters(self):
62+
from erpnext.controllers.accounts_controller import get_payment_terms
63+
64+
payment_term1 = frappe.get_doc(
65+
{"doctype": "Payment Term", "payment_term_name": "_Test 50% on 15 Days"}
66+
).insert()
67+
payment_term2 = frappe.get_doc(
68+
{"doctype": "Payment Term", "payment_term_name": "_Test 50% on 30 Days"}
69+
).insert()
70+
71+
template = frappe.get_doc(
72+
{
73+
"doctype": "Payment Terms Template",
74+
"template_name": "_Test 50-50",
75+
"terms": [
76+
{
77+
"doctype": "Payment Terms Template Detail",
78+
"due_date_based_on": "Day(s) after invoice date",
79+
"payment_term": payment_term1.name,
80+
"description": "_Test 50-50",
81+
"invoice_portion": 50,
82+
"credit_days": 15,
83+
},
84+
{
85+
"doctype": "Payment Terms Template Detail",
86+
"due_date_based_on": "Day(s) after invoice date",
87+
"payment_term": payment_term2.name,
88+
"description": "_Test 50-50",
89+
"invoice_portion": 50,
90+
"credit_days": 30,
91+
},
92+
],
93+
}
94+
)
95+
template.insert()
96+
97+
filters = {
98+
"company": self.company,
99+
"report_date": today(),
100+
"range": "30, 60, 90, 120",
101+
"based_on_payment_terms": 1,
102+
"payment_terms_template": template.name,
103+
"ageing_based_on": "Posting Date",
104+
}
105+
106+
pi = self.create_purchase_invoice(do_not_submit=True)
107+
pi.payment_terms_template = template.name
108+
schedule = get_payment_terms(template.name)
109+
pi.set("payment_schedule", [])
110+
111+
for row in schedule:
112+
row["due_date"] = add_days(pi.posting_date, row.get("credit_days", 0))
113+
pi.append("payment_schedule", row)
114+
115+
pi.save()
116+
pi.submit()
117+
118+
report = execute(filters)
119+
row = report[1][0]
120+
121+
self.assertEqual(len(report[1]), 2)
122+
self.assertEqual([pi.name, payment_term1.payment_term_name], [row.voucher_no, row.payment_term])

erpnext/accounts/report/accounts_receivable/accounts_receivable.py

Lines changed: 73 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1035,9 +1035,8 @@ def add_customer_filters(
10351035
self,
10361036
):
10371037
self.customer = qb.DocType("Customer")
1038-
10391038
if self.filters.get("customer_group"):
1040-
groups = get_customer_group_with_children(self.filters.customer_group)
1039+
groups = get_party_group_with_children("Customer", self.filters.customer_group)
10411040
customers = (
10421041
qb.from_(self.customer)
10431042
.select(self.customer.name)
@@ -1049,14 +1048,18 @@ def add_customer_filters(
10491048
self.get_hierarchical_filters("Territory", "territory")
10501049

10511050
if self.filters.get("payment_terms_template"):
1052-
self.qb_selection_filter.append(
1053-
self.ple.party.isin(
1054-
qb.from_(self.customer)
1055-
.select(self.customer.name)
1056-
.where(self.customer.payment_terms == self.filters.get("payment_terms_template"))
1057-
)
1051+
customer_ptt = self.ple.party.isin(
1052+
qb.from_(self.customer)
1053+
.select(self.customer.name)
1054+
.where(self.customer.payment_terms == self.filters.get("payment_terms_template"))
10581055
)
10591056

1057+
si_ptt = self.add_payment_term_template_filters("Sales Invoice")
1058+
1059+
sales_ptt = self.ple.against_voucher_no.isin(si_ptt)
1060+
1061+
self.qb_selection_filter.append(Criterion.any([customer_ptt, sales_ptt]))
1062+
10601063
if self.filters.get("sales_partner"):
10611064
self.qb_selection_filter.append(
10621065
self.ple.party.isin(
@@ -1081,14 +1084,53 @@ def add_supplier_filters(self):
10811084
)
10821085

10831086
if self.filters.get("payment_terms_template"):
1084-
self.qb_selection_filter.append(
1085-
self.ple.party.isin(
1086-
qb.from_(supplier)
1087-
.select(supplier.name)
1088-
.where(supplier.payment_terms == self.filters.get("supplier_group"))
1089-
)
1087+
supplier_ptt = self.ple.party.isin(
1088+
qb.from_(supplier)
1089+
.select(supplier.name)
1090+
.where(supplier.payment_terms == self.filters.get("payment_terms_template"))
10901091
)
10911092

1093+
pi_ptt = self.add_payment_term_template_filters("Purchase Invoice")
1094+
1095+
purchase_ptt = self.ple.against_voucher_no.isin(pi_ptt)
1096+
1097+
self.qb_selection_filter.append(Criterion.any([supplier_ptt, purchase_ptt]))
1098+
1099+
def add_payment_term_template_filters(self, dtype):
1100+
voucher_type = qb.DocType(dtype)
1101+
1102+
ptt = (
1103+
qb.from_(voucher_type)
1104+
.select(voucher_type.name)
1105+
.where(voucher_type.payment_terms_template == self.filters.get("payment_terms_template"))
1106+
.where(voucher_type.company == self.filters.company)
1107+
)
1108+
1109+
if dtype == "Purchase Invoice":
1110+
party = "Supplier"
1111+
party_group_type = "supplier_group"
1112+
acc_type = "credit_to"
1113+
else:
1114+
party = "Customer"
1115+
party_group_type = "customer_group"
1116+
acc_type = "debit_to"
1117+
1118+
if self.filters.get(party_group_type):
1119+
party_groups = get_party_group_with_children(party, self.filters.get(party_group_type))
1120+
ptt = ptt.where((voucher_type[party_group_type]).isin(party_groups))
1121+
1122+
if self.filters.party:
1123+
ptt = ptt.where((voucher_type[party.lower()]).isin(self.filters.party))
1124+
1125+
if self.filters.cost_center:
1126+
cost_centers = get_cost_centers_with_children(self.filters.cost_center)
1127+
ptt = ptt.where(voucher_type.cost_center.isin(cost_centers))
1128+
1129+
if self.filters.party_account:
1130+
ptt = ptt.where(voucher_type[acc_type] == self.filters.party_account)
1131+
1132+
return ptt
1133+
10921134
def get_hierarchical_filters(self, doctype, key):
10931135
lft, rgt = frappe.db.get_value(doctype, self.filters.get(key), ["lft", "rgt"])
10941136

@@ -1330,20 +1372,26 @@ def get_exchange_rate_revaluations(self):
13301372
self.err_journals = [x[0] for x in results] if results else []
13311373

13321374

1333-
def get_customer_group_with_children(customer_groups):
1334-
if not isinstance(customer_groups, list):
1335-
customer_groups = [d.strip() for d in customer_groups.strip().split(",") if d]
1375+
def get_party_group_with_children(party, party_groups):
1376+
if party not in ("Customer", "Supplier"):
1377+
return []
13361378

1337-
all_customer_groups = []
1338-
for d in customer_groups:
1339-
if frappe.db.exists("Customer Group", d):
1340-
lft, rgt = frappe.db.get_value("Customer Group", d, ["lft", "rgt"])
1341-
children = frappe.get_all("Customer Group", filters={"lft": [">=", lft], "rgt": ["<=", rgt]})
1342-
all_customer_groups += [c.name for c in children]
1379+
group_dtype = f"{party} Group"
1380+
if not isinstance(party_groups, list):
1381+
party_groups = [d.strip() for d in party_groups.strip().split(",") if d]
1382+
1383+
all_party_groups = []
1384+
for d in party_groups:
1385+
if frappe.db.exists(group_dtype, d):
1386+
lft, rgt = frappe.db.get_value(group_dtype, d, ["lft", "rgt"])
1387+
children = frappe.get_all(
1388+
group_dtype, filters={"lft": [">=", lft], "rgt": ["<=", rgt]}, pluck="name"
1389+
)
1390+
all_party_groups += children
13431391
else:
1344-
frappe.throw(_("Customer Group: {0} does not exist").format(d))
1392+
frappe.throw(_("{0}: {1} does not exist").format(group_dtype, d))
13451393

1346-
return list(set(all_customer_groups))
1394+
return list(set(all_party_groups))
13471395

13481396

13491397
class InitSQLProceduresForAR:

erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,3 +1139,66 @@ def test_cost_center_on_report_output(self):
11391139
self.assertEqual(len(report[1]), 1)
11401140
row = report[1][0]
11411141
self.assertEqual(expected_data_after_payment, [row.voucher_no, row.cost_center, row.outstanding])
1142+
1143+
def test_payment_terms_template_filters(self):
1144+
from erpnext.controllers.accounts_controller import get_payment_terms
1145+
1146+
payment_term1 = frappe.get_doc(
1147+
{"doctype": "Payment Term", "payment_term_name": "_Test 50% on 15 Days"}
1148+
).insert()
1149+
payment_term2 = frappe.get_doc(
1150+
{"doctype": "Payment Term", "payment_term_name": "_Test 50% on 30 Days"}
1151+
).insert()
1152+
1153+
template = frappe.get_doc(
1154+
{
1155+
"doctype": "Payment Terms Template",
1156+
"template_name": "_Test 50-50",
1157+
"terms": [
1158+
{
1159+
"doctype": "Payment Terms Template Detail",
1160+
"due_date_based_on": "Day(s) after invoice date",
1161+
"payment_term": payment_term1.name,
1162+
"description": "_Test 50-50",
1163+
"invoice_portion": 50,
1164+
"credit_days": 15,
1165+
},
1166+
{
1167+
"doctype": "Payment Terms Template Detail",
1168+
"due_date_based_on": "Day(s) after invoice date",
1169+
"payment_term": payment_term2.name,
1170+
"description": "_Test 50-50",
1171+
"invoice_portion": 50,
1172+
"credit_days": 30,
1173+
},
1174+
],
1175+
}
1176+
)
1177+
template.insert()
1178+
1179+
filters = {
1180+
"company": self.company,
1181+
"report_date": today(),
1182+
"range": "30, 60, 90, 120",
1183+
"based_on_payment_terms": 1,
1184+
"payment_terms_template": template.name,
1185+
"ageing_based_on": "Posting Date",
1186+
}
1187+
1188+
si = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
1189+
si.payment_terms_template = template.name
1190+
schedule = get_payment_terms(template.name)
1191+
si.set("payment_schedule", [])
1192+
1193+
for row in schedule:
1194+
row["due_date"] = add_days(si.posting_date, row.get("credit_days", 0))
1195+
si.append("payment_schedule", row)
1196+
1197+
si.save()
1198+
si.submit()
1199+
1200+
report = execute(filters)
1201+
row = report[1][0]
1202+
1203+
self.assertEqual(len(report[1]), 2)
1204+
self.assertEqual([si.name, payment_term1.payment_term_name], [row.voucher_no, row.payment_term])

0 commit comments

Comments
 (0)