From 15d2024b8e8349562cae0f979d1d483d4d969d32 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 29 Sep 2023 11:12:19 +0530 Subject: [PATCH 1/5] fix: ageing summary in AR (cherry picked from commit d9eb44e62d9307f4100ca8e8b500309ca6da6922) --- .../process_statement_of_accounts.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 3f8731afe65..1352c892a56 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -100,7 +100,8 @@ def set_ageing(doc, entry): "range2": 60, "range3": 90, "range4": 120, - "customer": entry.customer, + "party_type": "Customer", + "party": entry.customer, } ) col1, ageing = get_ageing(ageing_filters) From c29eab12dfe19cc3adfb41d9a0ffe287bd7e1164 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 29 Sep 2023 14:34:43 +0530 Subject: [PATCH 2/5] refactor: separate function for statement dict (cherry picked from commit 67f878ff8c49f0f2e3704aa7264b35fb3418a36e) # Conflicts: # erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py --- .../process_statement_of_accounts.py | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 1352c892a56..cc6a00c1954 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -47,6 +47,20 @@ class ProcessStatementOfAccounts(Document): def get_report_pdf(doc, consolidated=True): + statement_dict = get_statement_dict(doc) + if not bool(statement_dict): + return False + elif consolidated: + delimiter = '
' if doc.include_break else "" + result = delimiter.join(list(statement_dict.values())) + return get_pdf(result, {"orientation": doc.orientation}) + else: + for customer, statement_html in statement_dict.items(): + statement_dict[customer] = get_pdf(statement_html, {"orientation": doc.orientation}) + return statement_dict + + +def get_statement_dict(doc, get_statement_dict=False): statement_dict = {} ageing = "" @@ -77,8 +91,11 @@ def get_report_pdf(doc, consolidated=True): if not res: continue - statement_dict[entry.customer] = get_html(doc, filters, entry, col, res, ageing) + statement_dict[entry.customer] = ( + [res, ageing] if get_statement_dict else get_html(doc, filters, entry, col, res, ageing) + ) +<<<<<<< HEAD if not bool(statement_dict): return False elif consolidated: @@ -88,6 +105,9 @@ def get_report_pdf(doc, consolidated=True): for customer, statement_html in statement_dict.items(): statement_dict[customer] = get_pdf(statement_html, {"orientation": doc.orientation}) return statement_dict +======= + return statement_dict +>>>>>>> 67f878ff8c (refactor: separate function for statement dict) def set_ageing(doc, entry): @@ -101,7 +121,7 @@ def set_ageing(doc, entry): "range3": 90, "range4": 120, "party_type": "Customer", - "party": entry.customer, + "party": [entry.customer], } ) col1, ageing = get_ageing(ageing_filters) From 24b1100c8f007bb03e9fd89010ffc7487d30b71c Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 29 Sep 2023 14:35:43 +0530 Subject: [PATCH 3/5] test: process soa for gl and ar (cherry picked from commit 644e25e587affcf2a5fb821f148e468b154c261a) --- .../test_process_statement_of_accounts.py | 100 +++++++++++++++--- 1 file changed, 84 insertions(+), 16 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py index fb0d8d152f0..a3a74df4029 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py @@ -4,39 +4,107 @@ import unittest import frappe +from frappe.tests.utils import FrappeTestCase from frappe.utils import add_days, getdate, today from erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts import ( + get_statement_dict, send_emails, ) from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice +from erpnext.accounts.test.accounts_mixin import AccountsTestMixin -class TestProcessStatementOfAccounts(unittest.TestCase): +class TestProcessStatementOfAccounts(AccountsTestMixin, FrappeTestCase): def setUp(self): + self.create_company() + self.create_customer() + self.create_customer(customer_name="Other Customer") + self.clear_old_entries() self.si = create_sales_invoice() - self.process_soa = create_process_soa() + create_sales_invoice(customer="Other Customer") + + def test_process_soa_for_gl(self): + """Tests the utils for Statement of Accounts(General Ledger)""" + process_soa = create_process_soa( + name="_Test Process SOA for GL", + customers=[{"customer": "_Test Customer"}, {"customer": "Other Customer"}], + ) + statement_dict = get_statement_dict(process_soa, get_statement_dict=True) + + # Checks if the statements are filtered based on the Customer + self.assertIn("Other Customer", statement_dict) + self.assertIn("_Test Customer", statement_dict) + + # Checks if the correct number of receivable entries exist + # 3 rows for opening and closing and 1 row for SI + receivable_entries = statement_dict["_Test Customer"][0] + self.assertEqual(len(receivable_entries), 4) + + # Checks the amount for the receivable entry + self.assertEqual(receivable_entries[1].voucher_no, self.si.name) + self.assertEqual(receivable_entries[1].balance, 100) + + def test_process_soa_for_ar(self): + """Tests the utils for Statement of Accounts(Accounts Receivable)""" + process_soa = create_process_soa(name="_Test Process SOA for AR", report="Accounts Receivable") + statement_dict = get_statement_dict(process_soa, get_statement_dict=True) + + # Checks if the statements are filtered based on the Customer + self.assertNotIn("Other Customer", statement_dict) + self.assertIn("_Test Customer", statement_dict) + + # Checks if the correct number of receivable entries exist + receivable_entries = statement_dict["_Test Customer"][0] + self.assertEqual(len(receivable_entries), 1) + + # Checks the amount for the receivable entry + self.assertEqual(receivable_entries[0].voucher_no, self.si.name) + self.assertEqual(receivable_entries[0].total_due, 100) + + # Checks the ageing summary for AR + ageing_summary = statement_dict["_Test Customer"][1][0] + expected_summary = frappe._dict( + range1=100, + range2=0, + range3=0, + range4=0, + range5=0, + ) + self.check_ageing_summary(ageing_summary, expected_summary) def test_auto_email_for_process_soa_ar(self): - send_emails(self.process_soa.name, from_scheduler=True) - self.process_soa.load_from_db() - self.assertEqual(self.process_soa.posting_date, getdate(add_days(today(), 7))) + process_soa = create_process_soa( + name="_Test Process SOA", enable_auto_email=1, report="Accounts Receivable" + ) + send_emails(process_soa.name, from_scheduler=True) + process_soa.load_from_db() + self.assertEqual(process_soa.posting_date, getdate(add_days(today(), 7))) + + def check_ageing_summary(self, ageing, expected_ageing): + for age_range in expected_ageing: + self.assertEqual(expected_ageing[age_range], ageing.get(age_range)) def tearDown(self): - frappe.delete_doc_if_exists("Process Statement Of Accounts", "Test Process SOA") + frappe.db.rollback() -def create_process_soa(): - frappe.delete_doc_if_exists("Process Statement Of Accounts", "Test Process SOA") +def create_process_soa(**args): + args = frappe._dict(args) + frappe.delete_doc_if_exists("Process Statement Of Accounts", args.name) process_soa = frappe.new_doc("Process Statement Of Accounts") - soa_dict = { - "name": "Test Process SOA", - "company": "_Test Company", - } + soa_dict = frappe._dict( + name=args.name, + company=args.company or "_Test Company", + customers=args.customers or [{"customer": "_Test Customer"}], + enable_auto_email=1 if args.enable_auto_email else 0, + frequency=args.frequency or "Weekly", + report=args.report or "General Ledger", + from_date=args.from_date or getdate(today()), + to_date=args.to_date or getdate(today()), + posting_date=args.posting_date or getdate(today()), + include_ageing=1, + ) process_soa.update(soa_dict) - process_soa.set("customers", [{"customer": "_Test Customer"}]) - process_soa.enable_auto_email = 1 - process_soa.frequency = "Weekly" - process_soa.report = "Accounts Receivable" process_soa.save() return process_soa From 3f59518d01bb4fa81869fb10580f4da5bcf4bf19 Mon Sep 17 00:00:00 2001 From: Gursheen Kaur Anand <40693548+GursheenK@users.noreply.github.com> Date: Sat, 7 Oct 2023 15:21:50 +0530 Subject: [PATCH 4/5] chore: resolve conflicts --- .../process_statement_of_accounts.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index cc6a00c1954..16ca7d457fb 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -94,20 +94,8 @@ def get_statement_dict(doc, get_statement_dict=False): statement_dict[entry.customer] = ( [res, ageing] if get_statement_dict else get_html(doc, filters, entry, col, res, ageing) ) - -<<<<<<< HEAD - if not bool(statement_dict): - return False - elif consolidated: - result = "".join(list(statement_dict.values())) - return get_pdf(result, {"orientation": doc.orientation}) - else: - for customer, statement_html in statement_dict.items(): - statement_dict[customer] = get_pdf(statement_html, {"orientation": doc.orientation}) - return statement_dict -======= + return statement_dict ->>>>>>> 67f878ff8c (refactor: separate function for statement dict) def set_ageing(doc, entry): From 77d719af6e146ff243c264fabf1cc329c393c293 Mon Sep 17 00:00:00 2001 From: Gursheen Kaur Anand <40693548+GursheenK@users.noreply.github.com> Date: Sat, 7 Oct 2023 15:40:27 +0530 Subject: [PATCH 5/5] chore: linting issues --- .../process_statement_of_accounts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 16ca7d457fb..b7d6827f64c 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -94,7 +94,7 @@ def get_statement_dict(doc, get_statement_dict=False): statement_dict[entry.customer] = ( [res, ageing] if get_statement_dict else get_html(doc, filters, entry, col, res, ageing) ) - + return statement_dict