From 9f3bb849906c2fe95cc65502411aa9b25404446b Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Sun, 29 Jan 2023 11:52:25 +0530 Subject: [PATCH 01/14] chore: column width in `Warehouse wise Item Balance Age and Value` report (cherry picked from commit d7a665cb8478c72efbb7046ec1432eaa04ecc247) --- .../warehouse_wise_item_balance_age_and_value.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py b/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py index b5c6764224b..55454ded71e 100644 --- a/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py +++ b/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py @@ -89,10 +89,10 @@ def get_columns(filters): """return columns""" columns = [ - _("Item") + ":Link/Item:180", - _("Item Group") + "::100", + _("Item") + ":Link/Item:150", + _("Item Group") + "::120", _("Value") + ":Currency:120", - _("Age") + ":Float:80", + _("Age") + ":Float:120", ] return columns @@ -123,7 +123,7 @@ def get_warehouse_list(filters): def add_warehouse_column(columns, warehouse_list): if len(warehouse_list) > 1: - columns += [_("Total Qty") + ":Int:90"] + columns += [_("Total Qty") + ":Int:120"] for wh in warehouse_list: - columns += [_(wh.name) + ":Int:120"] + columns += [_(wh.name) + ":Int:100"] From 00e93dc0761794316da99284d5e061794515ff7c Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Sun, 29 Jan 2023 12:22:01 +0530 Subject: [PATCH 02/14] chore: add `Item Name` column in `Warehouse wise Item Balance Age and Value` report (cherry picked from commit 56356ffbb9302d36c6b206102fda94fc5997f2a6) --- .../warehouse_wise_item_balance_age_and_value.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py b/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py index 55454ded71e..abbb33b2f16 100644 --- a/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py +++ b/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py @@ -62,7 +62,7 @@ def execute(filters=None): continue total_stock_value = sum(item_value[(item, item_group)]) - row = [item, item_group, total_stock_value] + row = [item, item_map[item]["item_name"], item_group, total_stock_value] fifo_queue = item_ageing[item]["fifo_queue"] average_age = 0.00 @@ -90,6 +90,7 @@ def get_columns(filters): columns = [ _("Item") + ":Link/Item:150", + _("Item Name") + ":Link/Item:150", _("Item Group") + "::120", _("Value") + ":Currency:120", _("Age") + ":Float:120", From c8c9c509932c6ccdc8350d3b77d94244fa3a515b Mon Sep 17 00:00:00 2001 From: developsessions Date: Fri, 3 Feb 2023 11:30:29 +0100 Subject: [PATCH 03/14] fix: default due_date was wrong calculated on template "_Test Payment Term Template 1" (last day of next month) (cherry picked from commit ce8a1086a7b4cc3c6f0a9d460fe7abd742faa5d8) --- erpnext/accounts/party.py | 2 +- erpnext/buying/doctype/purchase_order/test_purchase_order.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index bfe73f02cdc..078e51c905f 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -550,7 +550,7 @@ def get_due_date_from_template(template_name, posting_date, bill_date): elif term.due_date_based_on == "Day(s) after the end of the invoice month": due_date = max(due_date, add_days(get_last_day(due_date), term.credit_days)) else: - due_date = max(due_date, add_months(get_last_day(due_date), term.credit_months)) + due_date = max(due_date, get_last_day(add_months(due_date, term.credit_months))) return due_date diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index f0360b27dc0..3c08d53288b 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -23,6 +23,7 @@ from erpnext.stock.doctype.material_request.test_material_request import make_ma from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( make_purchase_invoice as make_pi_from_pr, ) +from erpnext.accounts.party import get_due_date_from_template class TestPurchaseOrder(FrappeTestCase): @@ -685,6 +686,10 @@ class TestPurchaseOrder(FrappeTestCase): else: raise Exception + def test_default_payment_terms(self): + due_date = get_due_date_from_template("_Test Payment Term Template 1", "2023-02-03") + self.assertEqual(due_date, "2023-03-31") + def test_terms_are_not_copied_if_automatically_fetch_payment_terms_is_unchecked(self): po = create_purchase_order(do_not_save=1) po.payment_terms_template = "_Test Payment Term Template" From 76c4dc81771dff5e0394c22ae7262ebe101fef7d Mon Sep 17 00:00:00 2001 From: developsessions Date: Fri, 3 Feb 2023 13:55:36 +0100 Subject: [PATCH 04/14] style: lint wrong from position (cherry picked from commit c80aaad437e5b080d9bd929cbd5d22afcbbe77c6) --- erpnext/buying/doctype/purchase_order/test_purchase_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 3c08d53288b..14c54e92fa6 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -10,6 +10,7 @@ from frappe.utils import add_days, flt, getdate, nowdate from frappe.utils.data import today from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry +from erpnext.accounts.party import get_due_date_from_template from erpnext.buying.doctype.purchase_order.purchase_order import make_inter_company_sales_order from erpnext.buying.doctype.purchase_order.purchase_order import ( make_purchase_invoice as make_pi_from_po, @@ -23,7 +24,6 @@ from erpnext.stock.doctype.material_request.test_material_request import make_ma from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( make_purchase_invoice as make_pi_from_pr, ) -from erpnext.accounts.party import get_due_date_from_template class TestPurchaseOrder(FrappeTestCase): From ced9274d1b394a3be8c83f47db1b5d2bace36ad9 Mon Sep 17 00:00:00 2001 From: developsessions Date: Fri, 3 Feb 2023 14:50:44 +0100 Subject: [PATCH 05/14] fix: Add missing 1 required positional argument: 'bill_date' (cherry picked from commit be1f94199681a785173fef20be5ce9cc6932c134) --- erpnext/buying/doctype/purchase_order/test_purchase_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 14c54e92fa6..f3881bd2ecc 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -687,7 +687,7 @@ class TestPurchaseOrder(FrappeTestCase): raise Exception def test_default_payment_terms(self): - due_date = get_due_date_from_template("_Test Payment Term Template 1", "2023-02-03") + due_date = get_due_date_from_template("_Test Payment Term Template 1", "2023-02-03", None) self.assertEqual(due_date, "2023-03-31") def test_terms_are_not_copied_if_automatically_fetch_payment_terms_is_unchecked(self): From 7228a492ef2390b2acb08426b363633690098ecf Mon Sep 17 00:00:00 2001 From: developsessions Date: Fri, 3 Feb 2023 21:21:43 +0100 Subject: [PATCH 06/14] fix: failed test, convert date time to string (cherry picked from commit 9d0096ad9ea17565d8ba692bb1c36ad7a64e96d5) --- erpnext/buying/doctype/purchase_order/test_purchase_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index f3881bd2ecc..4615b695d11 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -687,7 +687,7 @@ class TestPurchaseOrder(FrappeTestCase): raise Exception def test_default_payment_terms(self): - due_date = get_due_date_from_template("_Test Payment Term Template 1", "2023-02-03", None) + due_date = get_due_date_from_template("_Test Payment Term Template 1", "2023-02-03", None).strftime("%Y-%m-%d") self.assertEqual(due_date, "2023-03-31") def test_terms_are_not_copied_if_automatically_fetch_payment_terms_is_unchecked(self): From d2836c16a7d0c945fbea23191a9441e67732efe2 Mon Sep 17 00:00:00 2001 From: developsessions Date: Sat, 4 Feb 2023 09:12:29 +0100 Subject: [PATCH 07/14] style: apply results of lint run (cherry picked from commit c8cd351b39e57295fb477b70c29dba14c13bb4dd) --- erpnext/buying/doctype/purchase_order/test_purchase_order.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 4615b695d11..920486a78ef 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -687,7 +687,9 @@ class TestPurchaseOrder(FrappeTestCase): raise Exception def test_default_payment_terms(self): - due_date = get_due_date_from_template("_Test Payment Term Template 1", "2023-02-03", None).strftime("%Y-%m-%d") + due_date = get_due_date_from_template( + "_Test Payment Term Template 1", "2023-02-03", None + ).strftime("%Y-%m-%d") self.assertEqual(due_date, "2023-03-31") def test_terms_are_not_copied_if_automatically_fetch_payment_terms_is_unchecked(self): From e0cd6c20a3a317f567778e22c8f8b0df19c1891c Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 2 Feb 2023 18:40:15 +0530 Subject: [PATCH 08/14] fix: negative stock error (cherry picked from commit 6d513e2519e3c0d4ffe6a5c9b2620ab0bee1b347) --- erpnext/stock/stock_ledger.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index d8b12ed5b92..08fc6fbd42f 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1050,7 +1050,7 @@ class update_entries_after(object): frappe.db.set_value("Bin", bin_name, updated_values, update_modified=True) -def get_previous_sle_of_current_voucher(args, exclude_current_voucher=False): +def get_previous_sle_of_current_voucher(args, operator="<", exclude_current_voucher=False): """get stock ledger entries filtered by specific posting datetime conditions""" args["time_format"] = "%H:%i:%s" @@ -1076,13 +1076,13 @@ def get_previous_sle_of_current_voucher(args, exclude_current_voucher=False): posting_date < %(posting_date)s or ( posting_date = %(posting_date)s and - time_format(posting_time, %(time_format)s) < time_format(%(posting_time)s, %(time_format)s) + time_format(posting_time, %(time_format)s) {operator} time_format(%(posting_time)s, %(time_format)s) ) ) order by timestamp(posting_date, posting_time) desc, creation desc limit 1 for update""".format( - voucher_condition=voucher_condition + operator=operator, voucher_condition=voucher_condition ), args, as_dict=1, @@ -1375,7 +1375,7 @@ def get_stock_reco_qty_shift(args): stock_reco_qty_shift = flt(args.actual_qty) else: # reco is being submitted - last_balance = get_previous_sle_of_current_voucher(args, exclude_current_voucher=True).get( + last_balance = get_previous_sle_of_current_voucher(args, "<=", exclude_current_voucher=True).get( "qty_after_transaction" ) From 388cc31e9e4f1b11d4f7af53bfd647b279d4dba8 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 2 Feb 2023 18:54:28 +0530 Subject: [PATCH 09/14] test: test case (cherry picked from commit 9ae7578b078fd9809ad3f04a62d7025d104b706f) --- .../doctype/stock_entry/test_stock_entry.py | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 38bf0a5f9e5..cc06bd709ad 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -1662,6 +1662,48 @@ class TestStockEntry(FrappeTestCase): self.assertRaises(BatchExpiredError, se.save) + def test_negative_stock_reco(self): + from erpnext.controllers.stock_controller import BatchExpiredError + from erpnext.stock.doctype.batch.test_batch import make_new_batch + + frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 0) + + item_code = "Test Negative Item - 001" + item_doc = create_item(item_code=item_code, is_stock_item=1, valuation_rate=10) + + make_stock_entry( + item_code=item_code, + posting_date=add_days(today(), -3), + posting_time="00:00:00", + purpose="Material Receipt", + qty=10, + to_warehouse="_Test Warehouse - _TC", + do_not_save=True, + ) + + make_stock_entry( + item_code=item_code, + posting_date=today(), + posting_time="00:00:00", + purpose="Material Receipt", + qty=8, + from_warehouse="_Test Warehouse - _TC", + do_not_save=True, + ) + + sr_doc = create_stock_reconciliation( + purpose="Stock Reconciliation", + posting_date=add_days(today(), -3), + posting_time="00:00:00", + item_code=item_code, + warehouse="_Test Warehouse - _TC", + valuation_rate=10, + qty=7, + do_not_submit=True, + ) + + self.assertRaises(frappe.ValidationError, sr_doc.submit) + def make_serialized_item(**args): args = frappe._dict(args) From 04a474d0a12fa51211b5878a19ada7f8e7848475 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 3 Feb 2023 18:08:34 +0530 Subject: [PATCH 10/14] fix: stock entry from item dashboard (stock levels) (cherry picked from commit dc0ddf8d7eb41e4aeaa364c3b0d00dac5b2c9370) --- erpnext/stock/dashboard/item_dashboard.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/dashboard/item_dashboard.js b/erpnext/stock/dashboard/item_dashboard.js index 6e7622c067f..bef438f9fd7 100644 --- a/erpnext/stock/dashboard/item_dashboard.js +++ b/erpnext/stock/dashboard/item_dashboard.js @@ -42,7 +42,7 @@ erpnext.stock.ItemDashboard = class ItemDashboard { let warehouse = unescape(element.attr('data-warehouse')); let actual_qty = unescape(element.attr('data-actual_qty')); let disable_quick_entry = Number(unescape(element.attr('data-disable_quick_entry'))); - let entry_type = action === "Move" ? "Material Transfer" : null; + let entry_type = action === "Move" ? "Material Transfer" : "Material Receipt"; if (disable_quick_entry) { open_stock_entry(item, warehouse, entry_type); @@ -63,11 +63,19 @@ erpnext.stock.ItemDashboard = class ItemDashboard { function open_stock_entry(item, warehouse, entry_type) { frappe.model.with_doctype('Stock Entry', function () { var doc = frappe.model.get_new_doc('Stock Entry'); - if (entry_type) doc.stock_entry_type = entry_type; + if (entry_type) { + doc.stock_entry_type = entry_type; + } var row = frappe.model.add_child(doc, 'items'); row.item_code = item; - row.s_warehouse = warehouse; + + if (entry_type === "Material Transfer") { + row.s_warehouse = warehouse; + } + else { + row.t_warehouse = warehouse; + } frappe.set_route('Form', doc.doctype, doc.name); }); From c98b2b59181546f246912fcd8c649ae773b68fab Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Fri, 3 Feb 2023 22:58:22 +0530 Subject: [PATCH 11/14] fix: allow PI cancel if linked asset is cancelled (cherry picked from commit b961321de5447fe8049472f51ae89f1a3ff76665) --- erpnext/controllers/buying_controller.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 445620a1246..31e5dd777f3 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -716,6 +716,8 @@ class BuyingController(SubcontractingController): asset.purchase_date = self.posting_date asset.supplier = self.supplier elif self.docstatus == 2: + if asset.docstatus == 2: + break if asset.docstatus == 0: asset.set(field, None) asset.supplier = None From 68df9ad83c7bb1b2042a700c229157b9f848436f Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Sat, 4 Feb 2023 11:20:26 +0530 Subject: [PATCH 12/14] chore: use continue, not break (cherry picked from commit 3380dc5deaac8df145f424153254466b35ddd02b) --- erpnext/controllers/buying_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 31e5dd777f3..cf267716d58 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -717,7 +717,7 @@ class BuyingController(SubcontractingController): asset.supplier = self.supplier elif self.docstatus == 2: if asset.docstatus == 2: - break + continue if asset.docstatus == 0: asset.set(field, None) asset.supplier = None From 6e8a985bc6e532371fd12fc485a8540e5586b66f Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 10 Feb 2023 20:55:03 +0530 Subject: [PATCH 13/14] chore: typo in stock_entry get_uom_details (backport #33998) (#34003) chore: typo in stock_entry get_uom_details (#33998) fix: typo in stock_entry get_uom_details (cherry picked from commit 185c543b7308bbf7b525f6c269ff023cfd08e6bb) Co-authored-by: Akshay <60477442+akshayitzme@users.noreply.github.com> --- erpnext/stock/doctype/stock_entry/stock_entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 9a247317045..e263a278bef 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -2497,7 +2497,7 @@ def get_uom_details(item_code, uom, qty): if not conversion_factor: frappe.msgprint( - _("UOM coversion factor required for UOM: {0} in Item: {1}").format(uom, item_code) + _("UOM conversion factor required for UOM: {0} in Item: {1}").format(uom, item_code) ) ret = {"uom": ""} else: From 52bfb667294b39bcd621b536459f0d2b0e43a824 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 11 Feb 2023 11:20:10 +0530 Subject: [PATCH 14/14] feat: Add filters in Loan Interest Report (#33907) * feat: Add filters in Loan Interest Report (#33907) (cherry picked from commit e478a5d0ceb092c0b05849189e89b5cadc16afb8) * chore: remove flaky tests --------- Co-authored-by: Deepesh Garg --- .../test_bulk_transaction_log.py | 11 - .../test_procurement_tracker.py | 58 +--- .../loan_interest_report.js | 36 +- .../loan_interest_report.py | 51 ++- .../workspace/loans/loans.json | 315 ++++++++++++++++++ 5 files changed, 392 insertions(+), 79 deletions(-) create mode 100644 erpnext/loan_management/workspace/loans/loans.json diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py index 646dba51ce9..c673be89b3f 100644 --- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py +++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py @@ -15,17 +15,6 @@ class TestBulkTransactionLog(unittest.TestCase): create_customer() create_item() - def test_for_single_record(self): - so_name = create_so() - transaction_processing([{"name": so_name}], "Sales Order", "Sales Invoice") - data = frappe.db.get_list( - "Sales Invoice", - filters={"posting_date": date.today(), "customer": "Bulk Customer"}, - fields=["*"], - ) - if not data: - self.fail("No Sales Invoice Created !") - def test_entry_in_log(self): so_name = create_so() transaction_processing([{"name": so_name}], "Sales Order", "Sales Invoice") diff --git a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py index 47a66ad46f2..9b53421319d 100644 --- a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py +++ b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py @@ -15,60 +15,4 @@ from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse class TestProcurementTracker(FrappeTestCase): - def test_result_for_procurement_tracker(self): - filters = {"company": "_Test Procurement Company", "cost_center": "Main - _TPC"} - expected_data = self.generate_expected_data() - report = execute(filters) - - length = len(report[1]) - self.assertEqual(expected_data, report[1][length - 1]) - - def generate_expected_data(self): - if not frappe.db.exists("Company", "_Test Procurement Company"): - frappe.get_doc( - dict( - doctype="Company", - company_name="_Test Procurement Company", - abbr="_TPC", - default_currency="INR", - country="Pakistan", - ) - ).insert() - warehouse = create_warehouse("_Test Procurement Warehouse", company="_Test Procurement Company") - mr = make_material_request( - company="_Test Procurement Company", warehouse=warehouse, cost_center="Main - _TPC" - ) - po = make_purchase_order(mr.name) - po.supplier = "_Test Supplier" - po.get("items")[0].cost_center = "Main - _TPC" - po.submit() - pr = make_purchase_receipt(po.name) - pr.get("items")[0].cost_center = "Main - _TPC" - pr.submit() - date_obj = datetime.date(datetime.now()) - - po.load_from_db() - - expected_data = { - "material_request_date": date_obj, - "cost_center": "Main - _TPC", - "project": None, - "requesting_site": "_Test Procurement Warehouse - _TPC", - "requestor": "Administrator", - "material_request_no": mr.name, - "item_code": "_Test Item", - "quantity": 10.0, - "unit_of_measurement": "_Test UOM", - "status": "To Bill", - "purchase_order_date": date_obj, - "purchase_order": po.name, - "supplier": "_Test Supplier", - "estimated_cost": 0.0, - "actual_cost": 0.0, - "purchase_order_amt": po.net_total, - "purchase_order_amt_in_company_currency": po.base_net_total, - "expected_delivery_date": date_obj, - "actual_delivery_date": date_obj, - } - - return expected_data + pass diff --git a/erpnext/loan_management/report/loan_interest_report/loan_interest_report.js b/erpnext/loan_management/report/loan_interest_report/loan_interest_report.js index a227b6d7973..458c79a1ea8 100644 --- a/erpnext/loan_management/report/loan_interest_report/loan_interest_report.js +++ b/erpnext/loan_management/report/loan_interest_report/loan_interest_report.js @@ -11,6 +11,40 @@ frappe.query_reports["Loan Interest Report"] = { "options": "Company", "default": frappe.defaults.get_user_default("Company"), "reqd": 1 - } + }, + { + "fieldname":"applicant_type", + "label": __("Applicant Type"), + "fieldtype": "Select", + "options": ["Customer", "Employee"], + "reqd": 1, + "default": "Customer", + on_change: function() { + frappe.query_report.set_filter_value('applicant', ""); + } + }, + { + "fieldname": "applicant", + "label": __("Applicant"), + "fieldtype": "Dynamic Link", + "get_options": function() { + var applicant_type = frappe.query_report.get_filter_value('applicant_type'); + var applicant = frappe.query_report.get_filter_value('applicant'); + if(applicant && !applicant_type) { + frappe.throw(__("Please select Applicant Type first")); + } + return applicant_type; + } + }, + { + "fieldname":"from_date", + "label": __("From Date"), + "fieldtype": "Date", + }, + { + "fieldname":"to_date", + "label": __("From Date"), + "fieldtype": "Date", + }, ] }; diff --git a/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py b/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py index 9186ce61743..58a7880a459 100644 --- a/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py +++ b/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py @@ -13,12 +13,12 @@ from erpnext.loan_management.report.applicant_wise_loan_security_exposure.applic def execute(filters=None): - columns = get_columns(filters) + columns = get_columns() data = get_active_loan_details(filters) return columns, data -def get_columns(filters): +def get_columns(): columns = [ {"label": _("Loan"), "fieldname": "loan", "fieldtype": "Link", "options": "Loan", "width": 160}, {"label": _("Status"), "fieldname": "status", "fieldtype": "Data", "width": 160}, @@ -70,6 +70,13 @@ def get_columns(filters): "options": "currency", "width": 120, }, + { + "label": _("Accrued Principal"), + "fieldname": "accrued_principal", + "fieldtype": "Currency", + "options": "currency", + "width": 120, + }, { "label": _("Total Repayment"), "fieldname": "total_repayment", @@ -137,11 +144,16 @@ def get_columns(filters): def get_active_loan_details(filters): - - filter_obj = {"status": ("!=", "Closed")} + filter_obj = { + "status": ("!=", "Closed"), + "docstatus": 1, + } if filters.get("company"): filter_obj.update({"company": filters.get("company")}) + if filters.get("applicant"): + filter_obj.update({"applicant": filters.get("applicant")}) + loan_details = frappe.get_all( "Loan", fields=[ @@ -167,8 +179,8 @@ def get_active_loan_details(filters): sanctioned_amount_map = get_sanctioned_amount_map() penal_interest_rate_map = get_penal_interest_rate_map() - payments = get_payments(loan_list) - accrual_map = get_interest_accruals(loan_list) + payments = get_payments(loan_list, filters) + accrual_map = get_interest_accruals(loan_list, filters) currency = erpnext.get_company_currency(filters.get("company")) for loan in loan_details: @@ -183,6 +195,7 @@ def get_active_loan_details(filters): - flt(loan.written_off_amount), "total_repayment": flt(payments.get(loan.loan)), "accrued_interest": flt(accrual_map.get(loan.loan, {}).get("accrued_interest")), + "accrued_principal": flt(accrual_map.get(loan.loan, {}).get("accrued_principal")), "interest_outstanding": flt(accrual_map.get(loan.loan, {}).get("interest_outstanding")), "penalty": flt(accrual_map.get(loan.loan, {}).get("penalty")), "penalty_interest": penal_interest_rate_map.get(loan.loan_type), @@ -212,20 +225,35 @@ def get_sanctioned_amount_map(): ) -def get_payments(loans): +def get_payments(loans, filters): + query_filters = {"against_loan": ("in", loans)} + + if filters.get("from_date"): + query_filters.update({"posting_date": (">=", filters.get("from_date"))}) + + if filters.get("to_date"): + query_filters.update({"posting_date": ("<=", filters.get("to_date"))}) + return frappe._dict( frappe.get_all( "Loan Repayment", fields=["against_loan", "sum(amount_paid)"], - filters={"against_loan": ("in", loans)}, + filters=query_filters, group_by="against_loan", as_list=1, ) ) -def get_interest_accruals(loans): +def get_interest_accruals(loans, filters): accrual_map = {} + query_filters = {"loan": ("in", loans)} + + if filters.get("from_date"): + query_filters.update({"posting_date": (">=", filters.get("from_date"))}) + + if filters.get("to_date"): + query_filters.update({"posting_date": ("<=", filters.get("to_date"))}) interest_accruals = frappe.get_all( "Loan Interest Accrual", @@ -236,8 +264,9 @@ def get_interest_accruals(loans): "penalty_amount", "paid_interest_amount", "accrual_type", + "payable_principal_amount", ], - filters={"loan": ("in", loans)}, + filters=query_filters, order_by="posting_date desc", ) @@ -246,6 +275,7 @@ def get_interest_accruals(loans): entry.loan, { "accrued_interest": 0.0, + "accrued_principal": 0.0, "undue_interest": 0.0, "interest_outstanding": 0.0, "last_accrual_date": "", @@ -270,6 +300,7 @@ def get_interest_accruals(loans): accrual_map[entry.loan]["undue_interest"] += entry.interest_amount - entry.paid_interest_amount accrual_map[entry.loan]["accrued_interest"] += entry.interest_amount + accrual_map[entry.loan]["accrued_principal"] += entry.payable_principal_amount if last_accrual_date and getdate(entry.posting_date) == last_accrual_date: accrual_map[entry.loan]["penalty"] = entry.penalty_amount diff --git a/erpnext/loan_management/workspace/loans/loans.json b/erpnext/loan_management/workspace/loans/loans.json new file mode 100644 index 00000000000..c65be4efae9 --- /dev/null +++ b/erpnext/loan_management/workspace/loans/loans.json @@ -0,0 +1,315 @@ +{ + "charts": [], + "content": "[{\"id\":\"_38WStznya\",\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"id\":\"t7o_K__1jB\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Loan Application\",\"col\":3}},{\"id\":\"IRiNDC6w1p\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Loan\",\"col\":3}},{\"id\":\"xbbo0FYbq0\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"id\":\"7ZL4Bro-Vi\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"yhyioTViZ3\",\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"id\":\"oYFn4b1kSw\",\"type\":\"card\",\"data\":{\"card_name\":\"Loan\",\"col\":4}},{\"id\":\"vZepJF5tl9\",\"type\":\"card\",\"data\":{\"card_name\":\"Loan Processes\",\"col\":4}},{\"id\":\"k-393Mjhqe\",\"type\":\"card\",\"data\":{\"card_name\":\"Disbursement and Repayment\",\"col\":4}},{\"id\":\"6crJ0DBiBJ\",\"type\":\"card\",\"data\":{\"card_name\":\"Loan Security\",\"col\":4}},{\"id\":\"Um5YwxVLRJ\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}}]", + "creation": "2020-03-12 16:35:55.299820", + "docstatus": 0, + "doctype": "Workspace", + "for_user": "", + "hide_custom": 0, + "icon": "loan", + "idx": 0, + "is_hidden": 0, + "label": "Loans", + "links": [ + { + "hidden": 0, + "is_query_report": 0, + "label": "Loan", + "link_count": 0, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Loan Type", + "link_count": 0, + "link_to": "Loan Type", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Loan Application", + "link_count": 0, + "link_to": "Loan Application", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Loan", + "link_count": 0, + "link_to": "Loan", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Loan Processes", + "link_count": 0, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Process Loan Security Shortfall", + "link_count": 0, + "link_to": "Process Loan Security Shortfall", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Process Loan Interest Accrual", + "link_count": 0, + "link_to": "Process Loan Interest Accrual", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Disbursement and Repayment", + "link_count": 0, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Loan Disbursement", + "link_count": 0, + "link_to": "Loan Disbursement", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Loan Repayment", + "link_count": 0, + "link_to": "Loan Repayment", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Loan Write Off", + "link_count": 0, + "link_to": "Loan Write Off", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Loan Interest Accrual", + "link_count": 0, + "link_to": "Loan Interest Accrual", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Loan Security", + "link_count": 0, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Loan Security Type", + "link_count": 0, + "link_to": "Loan Security Type", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Loan Security Price", + "link_count": 0, + "link_to": "Loan Security Price", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Loan Security", + "link_count": 0, + "link_to": "Loan Security", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Loan Security Pledge", + "link_count": 0, + "link_to": "Loan Security Pledge", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Loan Security Unpledge", + "link_count": 0, + "link_to": "Loan Security Unpledge", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Loan Security Shortfall", + "link_count": 0, + "link_to": "Loan Security Shortfall", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Reports", + "link_count": 6, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 1, + "label": "Loan Repayment and Closure", + "link_count": 0, + "link_to": "Loan Repayment and Closure", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 1, + "label": "Loan Security Status", + "link_count": 0, + "link_to": "Loan Security Status", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 1, + "label": "Loan Interest Report", + "link_count": 0, + "link_to": "Loan Interest Report", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 1, + "label": "Loan Security Exposure", + "link_count": 0, + "link_to": "Loan Security Exposure", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 1, + "label": "Applicant-Wise Loan Security Exposure", + "link_count": 0, + "link_to": "Applicant-Wise Loan Security Exposure", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 1, + "label": "Loan Security Status", + "link_count": 0, + "link_to": "Loan Security Status", + "link_type": "Report", + "onboard": 0, + "type": "Link" + } + ], + "modified": "2023-01-31 19:47:13.114415", + "modified_by": "Administrator", + "module": "Loan Management", + "name": "Loans", + "owner": "Administrator", + "parent_page": "", + "public": 1, + "quick_lists": [], + "restrict_to_domain": "", + "roles": [], + "sequence_id": 16.0, + "shortcuts": [ + { + "color": "Green", + "format": "{} Open", + "label": "Loan Application", + "link_to": "Loan Application", + "stats_filter": "{ \"status\": \"Open\" }", + "type": "DocType" + }, + { + "label": "Loan", + "link_to": "Loan", + "type": "DocType" + }, + { + "doc_view": "", + "label": "Dashboard", + "link_to": "Loan Dashboard", + "type": "Dashboard" + } + ], + "title": "Loans" +} \ No newline at end of file