From 164a2ad28db541e1f68b0e9f4913991b067c721a Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Mon, 20 Sep 2021 04:33:30 +0530 Subject: [PATCH 01/67] fix: Calculate depreciation_left accurately --- erpnext/assets/doctype/asset/asset.py | 20 +++++++++++++++++--- erpnext/regional/india/utils.py | 5 +++-- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 8ff4f9790a..7861c7e371 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -221,7 +221,7 @@ class Asset(AccountsController): # If depreciation is already completed (for double declining balance) if skip_row: continue - depreciation_amount = get_depreciation_amount(self, value_after_depreciation, d) + depreciation_amount = get_depreciation_amount(self, value_after_depreciation, d, date_of_sale) if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1: schedule_date = add_months(d.depreciation_start_date, @@ -835,8 +835,8 @@ def get_total_days(date, frequency): return date_diff(date, period_start_date) @erpnext.allow_regional -def get_depreciation_amount(asset, depreciable_value, row): - depreciation_left = flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked) +def get_depreciation_amount(asset, depreciable_value, row, date_of_sale=None): + depreciation_left = get_depreciation_left(asset, row, date_of_sale) if row.depreciation_method in ("Straight Line", "Manual"): # if the Depreciation Schedule is being prepared for the first time @@ -852,3 +852,17 @@ def get_depreciation_amount(asset, depreciable_value, row): depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100)) return depreciation_amount + +def get_depreciation_left(asset, row, date_of_sale): + if not date_of_sale: + return flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked) + else: + if len(asset.finance_books) == 1: + return (flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked)) - len(asset.schedules) + else: + depreciation_left = flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked) + for schedule in asset.get('schedules'): + if schedule.finance_book == row.finance_book: + depreciation_left -= 1 + + return depreciation_left diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 903168d009..b1cbe70d14 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -14,6 +14,7 @@ from erpnext.controllers.taxes_and_totals import get_itemised_tax, get_itemised_ from erpnext.hr.utils import get_salary_assignment from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip from erpnext.regional.india import number_state_mapping, state_numbers, states +from erpnext.assets.doctype.asset.asset import get_depreciation_left GST_INVOICE_NUMBER_FORMAT = re.compile(r"^[a-zA-Z0-9\-/]+$") #alphanumeric and - / GSTIN_FORMAT = re.compile("^[0-9]{2}[A-Z]{4}[0-9A-Z]{1}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}[1-9A-Z]{1}[0-9A-Z]{1}$") @@ -842,8 +843,8 @@ def update_taxable_values(doc, method): diff = additional_taxes - total_charges doc.get('items')[item_count - 1].taxable_value += diff -def get_depreciation_amount(asset, depreciable_value, row): - depreciation_left = flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked) +def get_depreciation_amount(asset, depreciable_value, row, date_of_sale=None): + depreciation_left = get_depreciation_left(asset, row, date_of_sale) if row.depreciation_method in ("Straight Line", "Manual"): # if the Depreciation Schedule is being prepared for the first time From 244d9dee044091ad3f27c6ddec32d2e45165363f Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 21 Sep 2021 06:06:21 +0530 Subject: [PATCH 02/67] fix: Correct expected_values --- erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 3720ac33bb..e9eb6ca36a 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2214,7 +2214,7 @@ class TestSalesInvoice(unittest.TestCase): expected_values = [ ["2020-06-30", 1311.48, 1311.48], ["2021-06-30", 20000.0, 21311.48], - ["2021-09-30", 3966.76, 25278.24] + ["2021-09-30", 5041.1, 26352.58] ] for i, schedule in enumerate(asset.schedules): From 3c8879e777eccd5b4a0c61f9d932e421662848eb Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 21 Sep 2021 06:07:06 +0530 Subject: [PATCH 03/67] fix: Calculate depreciation_amount accurately --- erpnext/assets/doctype/asset/asset.py | 22 ++++------------------ erpnext/regional/india/utils.py | 7 +++---- 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 7861c7e371..01f4935fee 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -221,7 +221,7 @@ class Asset(AccountsController): # If depreciation is already completed (for double declining balance) if skip_row: continue - depreciation_amount = get_depreciation_amount(self, value_after_depreciation, d, date_of_sale) + depreciation_amount = get_depreciation_amount(self, value_after_depreciation, d) if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1: schedule_date = add_months(d.depreciation_start_date, @@ -835,13 +835,13 @@ def get_total_days(date, frequency): return date_diff(date, period_start_date) @erpnext.allow_regional -def get_depreciation_amount(asset, depreciable_value, row, date_of_sale=None): - depreciation_left = get_depreciation_left(asset, row, date_of_sale) +def get_depreciation_amount(asset, depreciable_value, row): + depreciation_left = flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked) if row.depreciation_method in ("Straight Line", "Manual"): # if the Depreciation Schedule is being prepared for the first time if not asset.flags.increase_in_asset_life: - depreciation_amount = (flt(row.value_after_depreciation) - + depreciation_amount = ((flt(asset.gross_purchase_amount) - flt(asset.opening_accumulated_depreciation)) - flt(row.expected_value_after_useful_life)) / depreciation_left # if the Depreciation Schedule is being modified after Asset Repair @@ -852,17 +852,3 @@ def get_depreciation_amount(asset, depreciable_value, row, date_of_sale=None): depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100)) return depreciation_amount - -def get_depreciation_left(asset, row, date_of_sale): - if not date_of_sale: - return flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked) - else: - if len(asset.finance_books) == 1: - return (flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked)) - len(asset.schedules) - else: - depreciation_left = flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked) - for schedule in asset.get('schedules'): - if schedule.finance_book == row.finance_book: - depreciation_left -= 1 - - return depreciation_left diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index b1cbe70d14..bfccb48c5a 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -14,7 +14,6 @@ from erpnext.controllers.taxes_and_totals import get_itemised_tax, get_itemised_ from erpnext.hr.utils import get_salary_assignment from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip from erpnext.regional.india import number_state_mapping, state_numbers, states -from erpnext.assets.doctype.asset.asset import get_depreciation_left GST_INVOICE_NUMBER_FORMAT = re.compile(r"^[a-zA-Z0-9\-/]+$") #alphanumeric and - / GSTIN_FORMAT = re.compile("^[0-9]{2}[A-Z]{4}[0-9A-Z]{1}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}[1-9A-Z]{1}[0-9A-Z]{1}$") @@ -843,13 +842,13 @@ def update_taxable_values(doc, method): diff = additional_taxes - total_charges doc.get('items')[item_count - 1].taxable_value += diff -def get_depreciation_amount(asset, depreciable_value, row, date_of_sale=None): - depreciation_left = get_depreciation_left(asset, row, date_of_sale) +def get_depreciation_amount(asset, depreciable_value, row): + depreciation_left = flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked) if row.depreciation_method in ("Straight Line", "Manual"): # if the Depreciation Schedule is being prepared for the first time if not asset.flags.increase_in_asset_life: - depreciation_amount = (flt(row.value_after_depreciation) - + depreciation_amount = ((flt(asset.gross_purchase_amount) - flt(asset.opening_accumulated_depreciation)) - flt(row.expected_value_after_useful_life)) / depreciation_left # if the Depreciation Schedule is being modified after Asset Repair From 700e78d69b2e4e8f12dafc20e536b08c99cd2852 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 21 Sep 2021 07:03:12 +0530 Subject: [PATCH 04/67] fix: Remove extra brackets --- erpnext/assets/doctype/asset/asset.py | 2 +- erpnext/regional/india/utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 01f4935fee..5c552f9850 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -841,7 +841,7 @@ def get_depreciation_amount(asset, depreciable_value, row): if row.depreciation_method in ("Straight Line", "Manual"): # if the Depreciation Schedule is being prepared for the first time if not asset.flags.increase_in_asset_life: - depreciation_amount = ((flt(asset.gross_purchase_amount) - flt(asset.opening_accumulated_depreciation)) - + depreciation_amount = (flt(asset.gross_purchase_amount) - flt(asset.opening_accumulated_depreciation) - flt(row.expected_value_after_useful_life)) / depreciation_left # if the Depreciation Schedule is being modified after Asset Repair diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 01742d11c3..5c3a48e77b 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -845,7 +845,7 @@ def get_depreciation_amount(asset, depreciable_value, row): if row.depreciation_method in ("Straight Line", "Manual"): # if the Depreciation Schedule is being prepared for the first time if not asset.flags.increase_in_asset_life: - depreciation_amount = ((flt(asset.gross_purchase_amount) - flt(asset.opening_accumulated_depreciation)) - + depreciation_amount = (flt(asset.gross_purchase_amount) - flt(asset.opening_accumulated_depreciation) - flt(row.expected_value_after_useful_life)) / depreciation_left # if the Depreciation Schedule is being modified after Asset Repair From 249672c35db9278e1c4027b2331e1b6b5ef8f7d2 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 21 Sep 2021 07:04:39 +0530 Subject: [PATCH 05/67] fix: Add depreciation_schedule details in create_asset() --- erpnext/assets/doctype/asset/test_asset.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 4cc9be5b05..b4e891e06c 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -704,9 +704,8 @@ def create_asset(**args): "calculate_depreciation": args.calculate_depreciation or 0, "gross_purchase_amount": 100000, "purchase_receipt_amount": 100000, - "expected_value_after_useful_life": 10000, "warehouse": args.warehouse or "_Test Warehouse - _TC", - "available_for_use_date": "2020-06-06", + "available_for_use_date": args.available_for_use_date or "2020-06-06", "location": "Test Location", "asset_owner": "Company", "is_existing_asset": 1 @@ -715,8 +714,10 @@ def create_asset(**args): if asset.calculate_depreciation: asset.append("finance_books", { "depreciation_method": "Straight Line", - "frequency_of_depreciation": 12, - "total_number_of_depreciations": 5 + "frequency_of_depreciation": args.frequency_of_depreciation or 12, + "total_number_of_depreciations": args.total_number_of_depreciations or 5, + "expected_value_after_useful_life": args.expected_value_after_useful_life or 0, + "depreciation_start_date": args.depreciation_start_date }) try: From 7ab3b9dd5a8877b1c1dff6eb1d5ab453b0a4c8f5 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 21 Sep 2021 07:07:00 +0530 Subject: [PATCH 06/67] fix: Add test for depreciation on sale of a depreciated Asset --- .../sales_invoice/test_sales_invoice.py | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index e9eb6ca36a..1c75f5cc51 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2201,7 +2201,7 @@ class TestSalesInvoice(unittest.TestCase): def test_asset_depreciation_on_sale(self): """ - Tests if an Asset set to depreciate yearly on June 30, that gets sold on Sept 30, creates an additional depreciation entry on Sept 30. + Tests if an Asset set to depreciate yearly on June 30, that gets sold on Sept 30, creates an additional depreciation entry on its date of sale. """ create_asset_data() @@ -2223,6 +2223,32 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount) self.assertTrue(schedule.journal_entry) + def test_depreciation_on_sale_for_depreciated_asset(self): + """ + Tests if an Asset set to depreciate yearly on Dec 31, that gets sold on Dec 31 after two years, created an additional depreciation entry on its date of sale. + """ + + create_asset_data() + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, + available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3, + expected_value_after_useful_life=10000, depreciation_start_date=getdate("2020-12-31"), submit=1) + + post_depreciation_entries(getdate("2021-09-30")) + + create_sales_invoice(item_code="Macbook Pro", asset=asset.name, qty=1, rate=90000, posting_date=getdate("2021-12-31")) + asset.load_from_db() + + expected_values = [ + ["2020-12-31", 30000, 30000], + ["2021-12-31", 30000, 60000] + ] + + for i, schedule in enumerate(asset.schedules): + self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date) + self.assertEqual(expected_values[i][1], schedule.depreciation_amount) + self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount) + self.assertTrue(schedule.journal_entry) + def test_sales_invoice_against_supplier(self): from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import ( make_customer, From b9fb59da58aa4ac5f0e287fcdce7d2993d9af9c7 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Sat, 25 Sep 2021 19:04:16 +0530 Subject: [PATCH 07/67] fix: Reset depreciation schedule on returning asset --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 4 ++-- erpnext/assets/doctype/asset/asset.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index ca6a77a2af..64cbd24e9f 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1015,7 +1015,7 @@ class SalesInvoice(SellingController): def depreciate_asset(self, asset): asset.flags.ignore_validate_update_after_submit = True - asset.prepare_depreciation_data(self.posting_date) + asset.prepare_depreciation_data(date_of_sale=self.posting_date) asset.save() post_depreciation_entries(self.posting_date) @@ -1024,7 +1024,7 @@ class SalesInvoice(SellingController): asset.flags.ignore_validate_update_after_submit = True # recreate original depreciation schedule of the asset - asset.prepare_depreciation_data() + asset.prepare_depreciation_data(date_of_return=self.posting_date) self.modify_depreciation_schedule_for_asset_repairs(asset) asset.save() diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 5c552f9850..653fa662fd 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -75,12 +75,12 @@ class Asset(AccountsController): if self.is_existing_asset and self.purchase_invoice: frappe.throw(_("Purchase Invoice cannot be made against an existing asset {0}").format(self.name)) - def prepare_depreciation_data(self, date_of_sale=None): + def prepare_depreciation_data(self, date_of_sale=None, date_of_return=None): if self.calculate_depreciation: self.value_after_depreciation = 0 self.set_depreciation_rate() self.make_depreciation_schedule(date_of_sale) - self.set_accumulated_depreciation(date_of_sale) + self.set_accumulated_depreciation(date_of_sale, date_of_return) else: self.finance_books = [] self.value_after_depreciation = (flt(self.gross_purchase_amount) - @@ -406,7 +406,7 @@ class Asset(AccountsController): frappe.throw(_("Depreciation Row {0}: Next Depreciation Date cannot be before Available-for-use Date") .format(row.idx)) - def set_accumulated_depreciation(self, date_of_sale=None, ignore_booked_entry = False): + def set_accumulated_depreciation(self, date_of_sale=None, date_of_return=None, ignore_booked_entry = False): straight_line_idx = [d.idx for d in self.get("schedules") if d.depreciation_method == 'Straight Line'] finance_books = [] @@ -423,7 +423,7 @@ class Asset(AccountsController): value_after_depreciation -= flt(depreciation_amount) # for the last row, if depreciation method = Straight Line - if straight_line_idx and i == max(straight_line_idx) - 1 and not date_of_sale: + if straight_line_idx and i == max(straight_line_idx) - 1 and not date_of_sale and not date_of_return: book = self.get('finance_books')[cint(d.finance_book_id) - 1] depreciation_amount += flt(value_after_depreciation - flt(book.expected_value_after_useful_life), d.precision("depreciation_amount")) From 796ed947ce2217496706bf00e7c3ddf8c4fc2e3a Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Sat, 25 Sep 2021 19:11:29 +0530 Subject: [PATCH 08/67] fix: Reverse depreciation entry made on sale if asset that was set to be sold in the future gets returned --- .../doctype/sales_invoice/sales_invoice.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 64cbd24e9f..e969aa6cc4 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -952,6 +952,7 @@ class SalesInvoice(SellingController): asset.db_set("disposal_date", None) if asset.calculate_depreciation: + self.reverse_depreciation_entry_made_after_sale(asset) self.reset_depreciation_schedule(asset) else: @@ -1029,8 +1030,6 @@ class SalesInvoice(SellingController): self.modify_depreciation_schedule_for_asset_repairs(asset) asset.save() - self.delete_depreciation_entry_made_after_sale(asset) - def modify_depreciation_schedule_for_asset_repairs(self, asset): asset_repairs = frappe.get_all( 'Asset Repair', @@ -1044,7 +1043,7 @@ class SalesInvoice(SellingController): asset_repair.modify_depreciation_schedule() asset.prepare_depreciation_data() - def delete_depreciation_entry_made_after_sale(self, asset): + def reverse_depreciation_entry_made_after_sale(self, asset): from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry posting_date_of_original_invoice = self.get_posting_date_of_sales_invoice() @@ -1059,7 +1058,8 @@ class SalesInvoice(SellingController): row += 1 if schedule.schedule_date == posting_date_of_original_invoice: - if not self.sale_was_made_on_original_schedule_date(asset, schedule, row, posting_date_of_original_invoice): + if not self.sale_was_made_on_original_schedule_date(asset, schedule, row, posting_date_of_original_invoice) \ + or self.sale_happens_in_the_future(posting_date_of_original_invoice): reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry) reverse_journal_entry.posting_date = nowdate() reverse_journal_entry.submit() @@ -1078,6 +1078,12 @@ class SalesInvoice(SellingController): return True return False + def sale_happens_in_the_future(self, posting_date_of_original_invoice): + if posting_date_of_original_invoice > getdate(): + return True + + return False + @property def enable_discount_accounting(self): if not hasattr(self, "_enable_discount_accounting"): From fdd9e6cc3c62a85573b7f02ff4940046d572e92b Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Mon, 27 Sep 2021 22:14:16 +0530 Subject: [PATCH 09/67] fix: Replace asset.schedules with asset.get('schedules') --- erpnext/assets/doctype/asset/asset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 653fa662fd..0138a1282a 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -187,7 +187,7 @@ class Asset(AccountsController): d.precision("rate_of_depreciation")) def make_depreciation_schedule(self, date_of_sale): - if 'Manual' not in [d.depreciation_method for d in self.finance_books] and not self.schedules: + if 'Manual' not in [d.depreciation_method for d in self.finance_books] and not self.get('schedules'): self.schedules = [] if not self.available_for_use_date: From 40ec2d622baeb43f45086fcb7298457100a94f40 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Mon, 27 Sep 2021 22:14:42 +0530 Subject: [PATCH 10/67] fix: Add tests for depreciation --- erpnext/assets/doctype/asset/test_asset.py | 256 ++++++++++++++++++++- 1 file changed, 252 insertions(+), 4 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index b4e891e06c..e747204a69 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -276,6 +276,7 @@ class TestAsset(unittest.TestCase): self.assertEqual(gle, expected_gle) self.assertEqual(asset.get("value_after_depreciation"), 0) + # WDV: Written Down Value def test_depreciation_entry_for_wdv_without_pro_rata(self): pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=8000.0, location="Test Location") @@ -479,6 +480,7 @@ class TestAsset(unittest.TestCase): self.assertTrue(asset.finance_books[0].expected_value_after_useful_life >= asset_value_after_full_schedule) + # CWIP: Capital Work In Progress def test_cwip_accounting(self): pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=5000, do_not_submit=True, location="Test Location") @@ -676,6 +678,249 @@ class TestAsset(unittest.TestCase): # reset indian company frappe.flags.company = company_flag + def test_depreciation_without_pro_rata(self): + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, + available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3, + expected_value_after_useful_life=10000, depreciation_start_date=getdate("2020-12-31"), submit=1) + + expected_values = [ + ["2020-12-31", 30000, 30000], + ["2021-12-31", 30000, 60000], + ["2022-12-31", 30000, 90000] + ] + + for i, schedule in enumerate(asset.schedules): + self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date) + self.assertEqual(expected_values[i][1], schedule.depreciation_amount) + self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount) + + def test_depreciation_with_pro_rata(self): + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, + available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3, + expected_value_after_useful_life=10000, depreciation_start_date=getdate("2020-07-01"), submit=1) + + expected_values = [ + ["2020-07-01", 15000, 15000], + ["2021-07-01", 30000, 45000], + ["2022-07-01", 30000, 75000], + ["2022-12-31", 15000, 90000] + ] + + for i, schedule in enumerate(asset.schedules): + self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date) + self.assertEqual(expected_values[i][1], schedule.depreciation_amount) + self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount) + + def test_get_depreciation_amount(self): + """Tests if get_depreciation_amount() returns the right value.""" + + from erpnext.assets.doctype.asset.asset import get_depreciation_amount + + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, + available_for_use_date=getdate("2019-12-31")) + + asset.finance_books = [] + asset.append("finance_books", { + "depreciation_method": "Straight Line", + "frequency_of_depreciation": 12, + "total_number_of_depreciations": 3, + "expected_value_after_useful_life": 10000, + "depreciation_start_date": getdate("2020-12-31") + }) + + depreciation_amount = get_depreciation_amount(asset, 100000, asset.finance_books[0]) + self.assertEqual(depreciation_amount, 30000) + + def test_make_depreciation_schedule(self): + """Tests if make_depreciation_schedule() returns the right values.""" + + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, + available_for_use_date=getdate("2019-12-31"), do_not_save=1) + + asset.finance_books = [] + asset.append("finance_books", { + "depreciation_method": "Straight Line", + "frequency_of_depreciation": 12, + "total_number_of_depreciations": 3, + "expected_value_after_useful_life": 10000, + "depreciation_start_date": getdate("2020-12-31") + }) + + asset.make_depreciation_schedule(date_of_sale=None) + + expected_values = [ + ['2020-12-31', 30000.0], + ['2021-12-31', 30000.0], + ['2022-12-31', 30000.0] + ] + + for i, schedule in enumerate(asset.schedules): + self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date) + self.assertEqual(expected_values[i][1], schedule.depreciation_amount) + + def test_set_accumulated_depreciation(self): + """Tests if set_accumulated_depreciation() returns the right values.""" + + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, + available_for_use_date=getdate("2019-12-31"), do_not_save=1) + + asset.finance_books = [] + asset.append("finance_books", { + "depreciation_method": "Straight Line", + "frequency_of_depreciation": 12, + "total_number_of_depreciations": 3, + "expected_value_after_useful_life": 10000, + "depreciation_start_date": getdate("2020-12-31") + }) + + asset.make_depreciation_schedule(date_of_sale=None) + asset.set_accumulated_depreciation() + + expected_values = [30000.0, 60000.0, 90000.0] + + for i, schedule in enumerate(asset.schedules): + self.assertEqual(expected_values[i], schedule.accumulated_depreciation_amount) + + def test_check_is_pro_rata(self): + """Tests if check_is_pro_rata() returns the right value(i.e. checks if has_pro_rata is accurate).""" + + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, + available_for_use_date=getdate("2019-12-31"), do_not_save=1) + + asset.finance_books = [] + asset.append("finance_books", { + "depreciation_method": "Straight Line", + "frequency_of_depreciation": 12, + "total_number_of_depreciations": 3, + "expected_value_after_useful_life": 10000, + "depreciation_start_date": getdate("2020-12-31") + }) + + has_pro_rata = asset.check_is_pro_rata(asset.finance_books[0]) + self.assertFalse(has_pro_rata) + + asset.finance_books = [] + asset.append("finance_books", { + "depreciation_method": "Straight Line", + "frequency_of_depreciation": 12, + "total_number_of_depreciations": 3, + "expected_value_after_useful_life": 10000, + "depreciation_start_date": getdate("2020-07-01") + }) + + has_pro_rata = asset.check_is_pro_rata(asset.finance_books[0]) + self.assertTrue(has_pro_rata) + + def test_expected_value_after_useful_life(self): + """Tests if an error is raised when expected_value_after_useful_life(110,000) > gross_purchase_amount(100,000).""" + + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, + available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3, + expected_value_after_useful_life=110000, depreciation_start_date=getdate("2020-07-01"), do_not_save=1) + + self.assertRaises(frappe.ValidationError, asset.save) + + def test_depreciation_start_date(self): + """Tests if an error is raised when neither depreciation_start_date nor available_for_use_date are specified.""" + + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, + total_number_of_depreciations=3, expected_value_after_useful_life=110000, do_not_save=1) + + self.assertRaises(frappe.ValidationError, asset.save) + + def test_opening_accumulated_depreciation(self): + """Tests if an error is raised when opening_accumulated_depreciation > (gross_purchase_amount - expected_value_after_useful_life).""" + + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, + available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3, + expected_value_after_useful_life=10000, depreciation_start_date=getdate("2020-07-01"), + opening_accumulated_depreciation=100000, do_not_save=1) + + self.assertRaises(frappe.ValidationError, asset.save) + + def test_number_of_depreciations_booked(self): + """Tests if an error is raised when number_of_depreciations_booked is not specified when opening_accumulated_depreciation is.""" + + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, + available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3, + expected_value_after_useful_life=10000, depreciation_start_date=getdate("2020-07-01"), + opening_accumulated_depreciation=10000, do_not_save=1) + + self.assertRaises(frappe.ValidationError, asset.save) + + def test_number_of_depreciations(self): + """Tests if an error is raised when number_of_depreciations_booked > total_number_of_depreciations.""" + + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, + available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3, + expected_value_after_useful_life=10000, depreciation_start_date=getdate("2020-07-01"), + opening_accumulated_depreciation=10000, number_of_depreciations_booked=5, + do_not_save=1) + + self.assertRaises(frappe.ValidationError, asset.save) + + def test_depreciation_start_date_is_before_purchase_date(self): + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, + available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3, + expected_value_after_useful_life=10000, depreciation_start_date=getdate("2014-07-01"), + do_not_save=1) + + self.assertRaises(frappe.ValidationError, asset.save) + + def test_depreciation_start_date_is_before_available_for_use_date(self): + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, + available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3, + expected_value_after_useful_life=10000, depreciation_start_date=getdate("2018-07-01"), + do_not_save=1) + + self.assertRaises(frappe.ValidationError, asset.save) + + def test_post_depreciation_entries(self): + """Tests if post_depreciation_entries() works as expected.""" + + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, + available_for_use_date=getdate("2019-12-31"), do_not_save=1) + + asset.finance_books = [] + asset.append("finance_books", { + "depreciation_method": "Straight Line", + "frequency_of_depreciation": 12, + "total_number_of_depreciations": 3, + "expected_value_after_useful_life": 10000, + "depreciation_start_date": getdate("2020-12-31") + }) + asset.submit() + + post_depreciation_entries(date="2021-06-01") + asset.load_from_db() + + self.assertTrue(asset.schedules[0].journal_entry) + self.assertFalse(asset.schedules[1].journal_entry) + self.assertFalse(asset.schedules[2].journal_entry) + + def test_clear_depreciation_schedule(self): + """Tests if clear_depreciation_schedule() works as expected.""" + + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, + available_for_use_date=getdate("2019-12-31"), do_not_save=1) + + asset.finance_books = [] + asset.append("finance_books", { + "depreciation_method": "Straight Line", + "frequency_of_depreciation": 12, + "total_number_of_depreciations": 3, + "expected_value_after_useful_life": 10000, + "depreciation_start_date": getdate("2020-12-31") + }) + asset.submit() + + post_depreciation_entries(date="2021-06-01") + asset.load_from_db() + + asset.clear_depreciation_schedule() + + self.assertEqual(len(asset.schedules), 1) + def create_asset_data(): if not frappe.db.exists("Asset Category", "Computers"): create_asset_category() @@ -702,6 +947,8 @@ def create_asset(**args): "company": args.company or"_Test Company", "purchase_date": "2015-01-01", "calculate_depreciation": args.calculate_depreciation or 0, + "opening_accumulated_depreciation": args.opening_accumulated_depreciation or 0, + "number_of_depreciations_booked": args.number_of_depreciations_booked or 0, "gross_purchase_amount": 100000, "purchase_receipt_amount": 100000, "warehouse": args.warehouse or "_Test Warehouse - _TC", @@ -720,10 +967,11 @@ def create_asset(**args): "depreciation_start_date": args.depreciation_start_date }) - try: - asset.save() - except frappe.DuplicateEntryError: - pass + if not args.do_not_save: + try: + asset.save() + except frappe.DuplicateEntryError: + pass if args.submit: asset.submit() From c84c983073943ae711100b265a981dfdc73c5fd6 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 28 Sep 2021 01:22:38 +0530 Subject: [PATCH 11/67] fix: Categorize into test suites --- erpnext/assets/doctype/asset/test_asset.py | 621 +++++++++++---------- 1 file changed, 312 insertions(+), 309 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index e747204a69..b5488750eb 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -20,13 +20,13 @@ from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( ) from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt - -class TestAsset(unittest.TestCase): +class AssetSetup(unittest.TestCase): def setUp(self): set_depreciation_settings_in_company() create_asset_data() frappe.db.sql("delete from `tabTax Rule`") +class TestAsset(AssetSetup): def test_purchase_asset(self): pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location") @@ -89,287 +89,6 @@ class TestAsset(unittest.TestCase): doc.set_missing_values() self.assertEqual(doc.items[0].is_fixed_asset, 1) - def test_schedule_for_straight_line_method(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2030-01-01' - asset.purchase_date = '2030-01-01' - - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) - asset.save() - - self.assertEqual(asset.status, "Draft") - expected_schedules = [ - ["2030-12-31", 30000.00, 30000.00], - ["2031-12-31", 30000.00, 60000.00], - ["2032-12-31", 30000.00, 90000.00] - ] - - schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] - for d in asset.get("schedules")] - - self.assertEqual(schedules, expected_schedules) - - def test_schedule_for_straight_line_method_for_existing_asset(self): - create_asset(is_existing_asset=1) - asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"}) - asset.calculate_depreciation = 1 - asset.number_of_depreciations_booked = 1 - asset.opening_accumulated_depreciation = 40000 - asset.available_for_use_date = "2030-06-06" - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) - self.assertEqual(asset.status, "Draft") - asset.save() - expected_schedules = [ - ["2030-12-31", 14246.58, 54246.58], - ["2031-12-31", 25000.00, 79246.58], - ["2032-06-06", 10753.42, 90000.00] - ] - schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount] - for d in asset.get("schedules")] - - self.assertEqual(schedules, expected_schedules) - - def test_schedule_for_double_declining_method(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2030-01-01' - asset.purchase_date = '2030-01-01' - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Double Declining Balance", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": '2030-12-31' - }) - asset.save() - self.assertEqual(asset.status, "Draft") - - expected_schedules = [ - ['2030-12-31', 66667.00, 66667.00], - ['2031-12-31', 22222.11, 88889.11], - ['2032-12-31', 1110.89, 90000.0] - ] - - schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] - for d in asset.get("schedules")] - - self.assertEqual(schedules, expected_schedules) - - def test_schedule_for_double_declining_method_for_existing_asset(self): - create_asset(is_existing_asset = 1) - asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"}) - asset.calculate_depreciation = 1 - asset.is_existing_asset = 1 - asset.number_of_depreciations_booked = 1 - asset.opening_accumulated_depreciation = 50000 - asset.available_for_use_date = '2030-01-01' - asset.purchase_date = '2029-11-30' - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Double Declining Balance", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) - asset.save() - self.assertEqual(asset.status, "Draft") - - expected_schedules = [ - ["2030-12-31", 33333.50, 83333.50], - ["2031-12-31", 6666.50, 90000.0] - ] - - schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] - for d in asset.get("schedules")] - - self.assertEqual(schedules, expected_schedules) - - def test_schedule_for_prorated_straight_line_method(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.purchase_date = '2030-01-30' - asset.is_existing_asset = 0 - asset.available_for_use_date = "2030-01-30" - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) - - asset.save() - - expected_schedules = [ - ["2030-12-31", 27534.25, 27534.25], - ["2031-12-31", 30000.0, 57534.25], - ["2032-12-31", 30000.0, 87534.25], - ["2033-01-30", 2465.75, 90000.0] - ] - - schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] - for d in asset.get("schedules")] - - self.assertEqual(schedules, expected_schedules) - - def test_depreciation(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.purchase_date = '2020-01-30' - asset.available_for_use_date = "2020-01-30" - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10, - "depreciation_start_date": "2020-12-31" - }) - asset.submit() - asset.load_from_db() - self.assertEqual(asset.status, "Submitted") - - frappe.db.set_value("Company", "_Test Company", "series_for_depreciation_entry", "DEPR-") - post_depreciation_entries(date="2021-01-01") - asset.load_from_db() - - # check depreciation entry series - self.assertEqual(asset.get("schedules")[0].journal_entry[:4], "DEPR") - - expected_gle = ( - ("_Test Accumulated Depreciations - _TC", 0.0, 30000.0), - ("_Test Depreciations - _TC", 30000.0, 0.0) - ) - - gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` - where against_voucher_type='Asset' and against_voucher = %s - order by account""", asset.name) - - self.assertEqual(gle, expected_gle) - self.assertEqual(asset.get("value_after_depreciation"), 0) - - # WDV: Written Down Value - def test_depreciation_entry_for_wdv_without_pro_rata(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=8000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2030-01-01' - asset.purchase_date = '2030-01-01' - asset.append("finance_books", { - "expected_value_after_useful_life": 1000, - "depreciation_method": "Written Down Value", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) - asset.save(ignore_permissions=True) - - self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) - - expected_schedules = [ - ["2030-12-31", 4000.00, 4000.00], - ["2031-12-31", 2000.00, 6000.00], - ["2032-12-31", 1000.00, 7000.0], - ] - - schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] - for d in asset.get("schedules")] - - self.assertEqual(schedules, expected_schedules) - - def test_pro_rata_depreciation_entry_for_wdv(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=8000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2030-06-06' - asset.purchase_date = '2030-01-01' - asset.append("finance_books", { - "expected_value_after_useful_life": 1000, - "depreciation_method": "Written Down Value", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) - asset.save(ignore_permissions=True) - - self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) - - expected_schedules = [ - ["2030-12-31", 2279.45, 2279.45], - ["2031-12-31", 2860.28, 5139.73], - ["2032-12-31", 1430.14, 6569.87], - ["2033-06-06", 430.13, 7000.0], - ] - - schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] - for d in asset.get("schedules")] - - self.assertEqual(schedules, expected_schedules) - - def test_depreciation_entry_cancellation(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2020-06-06' - asset.purchase_date = '2020-06-06' - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10, - "depreciation_start_date": "2020-12-31" - }) - asset.submit() - post_depreciation_entries(date="2021-01-01") - - asset.load_from_db() - - # cancel depreciation entry - depr_entry = asset.get("schedules")[0].journal_entry - self.assertTrue(depr_entry) - frappe.get_doc("Journal Entry", depr_entry).cancel() - - asset.load_from_db() - depr_entry = asset.get("schedules")[0].journal_entry - self.assertFalse(depr_entry) - def test_scrap_asset(self): pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location") @@ -412,7 +131,7 @@ class TestAsset(unittest.TestCase): self.assertFalse(asset.journal_entry_for_scrap) self.assertEqual(asset.status, "Partially Depreciated") - def test_asset_sale(self): + def test_gle_made_by_asset_sale(self): pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location") @@ -456,30 +175,6 @@ class TestAsset(unittest.TestCase): si.cancel() self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated") - def test_asset_expected_value_after_useful_life(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2020-06-06' - asset.purchase_date = '2020-06-06' - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10 - }) - asset.save() - accumulated_depreciation_after_full_schedule = \ - max(d.accumulated_depreciation_amount for d in asset.get("schedules")) - - asset_value_after_full_schedule = (flt(asset.gross_purchase_amount) - - flt(accumulated_depreciation_after_full_schedule)) - - self.assertTrue(asset.finance_books[0].expected_value_after_useful_life >= asset_value_after_full_schedule) - # CWIP: Capital Work In Progress def test_cwip_accounting(self): pr = make_purchase_receipt(item_code="Macbook Pro", @@ -639,6 +334,220 @@ class TestAsset(unittest.TestCase): frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", cwip_acc) frappe.db.get_value("Company", "_Test Company", "capital_work_in_progress_account", cwip_acc) +class TestDepreciationMethods(AssetSetup): + def test_schedule_for_straight_line_method(self): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) + asset.calculate_depreciation = 1 + asset.available_for_use_date = '2030-01-01' + asset.purchase_date = '2030-01-01' + + asset.append("finance_books", { + "expected_value_after_useful_life": 10000, + "depreciation_method": "Straight Line", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 12, + "depreciation_start_date": "2030-12-31" + }) + asset.save() + + self.assertEqual(asset.status, "Draft") + expected_schedules = [ + ["2030-12-31", 30000.00, 30000.00], + ["2031-12-31", 30000.00, 60000.00], + ["2032-12-31", 30000.00, 90000.00] + ] + + schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in asset.get("schedules")] + + self.assertEqual(schedules, expected_schedules) + + def test_schedule_for_straight_line_method_for_existing_asset(self): + create_asset(is_existing_asset=1) + asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"}) + asset.calculate_depreciation = 1 + asset.number_of_depreciations_booked = 1 + asset.opening_accumulated_depreciation = 40000 + asset.available_for_use_date = "2030-06-06" + asset.append("finance_books", { + "expected_value_after_useful_life": 10000, + "depreciation_method": "Straight Line", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 12, + "depreciation_start_date": "2030-12-31" + }) + self.assertEqual(asset.status, "Draft") + asset.save() + expected_schedules = [ + ["2030-12-31", 14246.58, 54246.58], + ["2031-12-31", 25000.00, 79246.58], + ["2032-06-06", 10753.42, 90000.00] + ] + schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount] + for d in asset.get("schedules")] + + self.assertEqual(schedules, expected_schedules) + + def test_schedule_for_double_declining_method(self): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) + asset.calculate_depreciation = 1 + asset.available_for_use_date = '2030-01-01' + asset.purchase_date = '2030-01-01' + asset.append("finance_books", { + "expected_value_after_useful_life": 10000, + "depreciation_method": "Double Declining Balance", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 12, + "depreciation_start_date": '2030-12-31' + }) + asset.save() + self.assertEqual(asset.status, "Draft") + + expected_schedules = [ + ['2030-12-31', 66667.00, 66667.00], + ['2031-12-31', 22222.11, 88889.11], + ['2032-12-31', 1110.89, 90000.0] + ] + + schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in asset.get("schedules")] + + self.assertEqual(schedules, expected_schedules) + + def test_schedule_for_double_declining_method_for_existing_asset(self): + create_asset(is_existing_asset = 1) + asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"}) + asset.calculate_depreciation = 1 + asset.is_existing_asset = 1 + asset.number_of_depreciations_booked = 1 + asset.opening_accumulated_depreciation = 50000 + asset.available_for_use_date = '2030-01-01' + asset.purchase_date = '2029-11-30' + asset.append("finance_books", { + "expected_value_after_useful_life": 10000, + "depreciation_method": "Double Declining Balance", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 12, + "depreciation_start_date": "2030-12-31" + }) + asset.save() + self.assertEqual(asset.status, "Draft") + + expected_schedules = [ + ["2030-12-31", 33333.50, 83333.50], + ["2031-12-31", 6666.50, 90000.0] + ] + + schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in asset.get("schedules")] + + self.assertEqual(schedules, expected_schedules) + + def test_schedule_for_prorated_straight_line_method(self): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) + asset.calculate_depreciation = 1 + asset.purchase_date = '2030-01-30' + asset.is_existing_asset = 0 + asset.available_for_use_date = "2030-01-30" + asset.append("finance_books", { + "expected_value_after_useful_life": 10000, + "depreciation_method": "Straight Line", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 12, + "depreciation_start_date": "2030-12-31" + }) + + asset.save() + + expected_schedules = [ + ["2030-12-31", 27534.25, 27534.25], + ["2031-12-31", 30000.0, 57534.25], + ["2032-12-31", 30000.0, 87534.25], + ["2033-01-30", 2465.75, 90000.0] + ] + + schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] + for d in asset.get("schedules")] + + self.assertEqual(schedules, expected_schedules) + + # WDV: Written Down Value method + def test_depreciation_entry_for_wdv_without_pro_rata(self): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=8000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) + asset.calculate_depreciation = 1 + asset.available_for_use_date = '2030-01-01' + asset.purchase_date = '2030-01-01' + asset.append("finance_books", { + "expected_value_after_useful_life": 1000, + "depreciation_method": "Written Down Value", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 12, + "depreciation_start_date": "2030-12-31" + }) + asset.save(ignore_permissions=True) + + self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) + + expected_schedules = [ + ["2030-12-31", 4000.00, 4000.00], + ["2031-12-31", 2000.00, 6000.00], + ["2032-12-31", 1000.00, 7000.0], + ] + + schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] + for d in asset.get("schedules")] + + self.assertEqual(schedules, expected_schedules) + + # WDV: Written Down Value method + def test_pro_rata_depreciation_entry_for_wdv(self): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=8000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) + asset.calculate_depreciation = 1 + asset.available_for_use_date = '2030-06-06' + asset.purchase_date = '2030-01-01' + asset.append("finance_books", { + "expected_value_after_useful_life": 1000, + "depreciation_method": "Written Down Value", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 12, + "depreciation_start_date": "2030-12-31" + }) + asset.save(ignore_permissions=True) + + self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) + + expected_schedules = [ + ["2030-12-31", 2279.45, 2279.45], + ["2031-12-31", 2860.28, 5139.73], + ["2032-12-31", 1430.14, 6569.87], + ["2033-06-06", 430.13, 7000.0], + ] + + schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] + for d in asset.get("schedules")] + + self.assertEqual(schedules, expected_schedules) + def test_discounted_wdv_depreciation_rate_for_indian_region(self): # set indian company company_flag = frappe.flags.company @@ -678,6 +587,7 @@ class TestAsset(unittest.TestCase): # reset indian company frappe.flags.company = company_flag +class TestDepreciationBasics(AssetSetup): def test_depreciation_without_pro_rata(self): asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3, @@ -811,7 +721,7 @@ class TestAsset(unittest.TestCase): has_pro_rata = asset.check_is_pro_rata(asset.finance_books[0]) self.assertTrue(has_pro_rata) - def test_expected_value_after_useful_life(self): + def test_expected_value_after_useful_life_greater_than_purchase_amount(self): """Tests if an error is raised when expected_value_after_useful_life(110,000) > gross_purchase_amount(100,000).""" asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, @@ -921,6 +831,99 @@ class TestAsset(unittest.TestCase): self.assertEqual(len(asset.schedules), 1) + def test_depreciation_entry_cancellation(self): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) + asset.calculate_depreciation = 1 + asset.available_for_use_date = '2020-06-06' + asset.purchase_date = '2020-06-06' + asset.append("finance_books", { + "expected_value_after_useful_life": 10000, + "depreciation_method": "Straight Line", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 10, + "depreciation_start_date": "2020-12-31" + }) + asset.submit() + post_depreciation_entries(date="2021-01-01") + + asset.load_from_db() + + # cancel depreciation entry + depr_entry = asset.get("schedules")[0].journal_entry + self.assertTrue(depr_entry) + frappe.get_doc("Journal Entry", depr_entry).cancel() + + asset.load_from_db() + depr_entry = asset.get("schedules")[0].journal_entry + self.assertFalse(depr_entry) + + def test_asset_expected_value_after_useful_life(self): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) + asset.calculate_depreciation = 1 + asset.available_for_use_date = '2020-06-06' + asset.purchase_date = '2020-06-06' + asset.append("finance_books", { + "expected_value_after_useful_life": 10000, + "depreciation_method": "Straight Line", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 10 + }) + asset.save() + accumulated_depreciation_after_full_schedule = \ + max(d.accumulated_depreciation_amount for d in asset.get("schedules")) + + asset_value_after_full_schedule = (flt(asset.gross_purchase_amount) - + flt(accumulated_depreciation_after_full_schedule)) + + self.assertTrue(asset.finance_books[0].expected_value_after_useful_life >= asset_value_after_full_schedule) + + def test_gle_made_by_depreciation_entries(self): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) + asset.calculate_depreciation = 1 + asset.purchase_date = '2020-01-30' + asset.available_for_use_date = "2020-01-30" + asset.append("finance_books", { + "expected_value_after_useful_life": 10000, + "depreciation_method": "Straight Line", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 10, + "depreciation_start_date": "2020-12-31" + }) + asset.submit() + asset.load_from_db() + self.assertEqual(asset.status, "Submitted") + + frappe.db.set_value("Company", "_Test Company", "series_for_depreciation_entry", "DEPR-") + post_depreciation_entries(date="2021-01-01") + asset.load_from_db() + + # check depreciation entry series + self.assertEqual(asset.get("schedules")[0].journal_entry[:4], "DEPR") + + expected_gle = ( + ("_Test Accumulated Depreciations - _TC", 0.0, 30000.0), + ("_Test Depreciations - _TC", 30000.0, 0.0) + ) + + gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` + where against_voucher_type='Asset' and against_voucher = %s + order by account""", asset.name) + + self.assertEqual(gle, expected_gle) + self.assertEqual(asset.get("value_after_depreciation"), 0) + def create_asset_data(): if not frappe.db.exists("Asset Category", "Computers"): create_asset_category() From 62fea8a5aa02b14e05eeb7aa5eb6496d65ceef2d Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 5 Oct 2021 21:38:39 +0530 Subject: [PATCH 12/67] fix: Rename tests --- erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 31f911203d..79d46c5e56 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2202,7 +2202,7 @@ class TestSalesInvoice(unittest.TestCase): check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1)) enable_discount_accounting(enable=0) - def test_asset_depreciation_on_sale(self): + def test_asset_depreciation_on_sale_with_pro_rata(self): """ Tests if an Asset set to depreciate yearly on June 30, that gets sold on Sept 30, creates an additional depreciation entry on its date of sale. """ @@ -2226,7 +2226,7 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount) self.assertTrue(schedule.journal_entry) - def test_depreciation_on_sale_for_depreciated_asset(self): + def test_asset_depreciation_on_sale_without_pro_rata(self): """ Tests if an Asset set to depreciate yearly on Dec 31, that gets sold on Dec 31 after two years, created an additional depreciation entry on its date of sale. """ From f51bd44929275949652dfc97f1b5a8107f64e6cf Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 6 Oct 2021 01:18:05 +0530 Subject: [PATCH 13/67] fix: Unlink Depreciation Entry made on sale if the Asset is returned --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index e135490cad..44a5fae6fb 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1060,10 +1060,15 @@ class SalesInvoice(SellingController): if schedule.schedule_date == posting_date_of_original_invoice: if not self.sale_was_made_on_original_schedule_date(asset, schedule, row, posting_date_of_original_invoice) \ or self.sale_happens_in_the_future(posting_date_of_original_invoice): + reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry) reverse_journal_entry.posting_date = nowdate() reverse_journal_entry.submit() + asset.flags.ignore_validate_update_after_submit = True + schedule.journal_entry = None + asset.save() + def get_posting_date_of_sales_invoice(self): return frappe.db.get_value('Sales Invoice', self.return_against, 'posting_date') From adebf2d71b2336037e45e85b214920e1f91deaae Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 6 Oct 2021 02:04:05 +0530 Subject: [PATCH 14/67] fix: Adjust depreciation_amount in final row --- erpnext/assets/doctype/asset/asset.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 0138a1282a..2a1d51beb4 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -262,11 +262,15 @@ class Asset(AccountsController): self.to_date = add_months(self.available_for_use_date, n * cint(d.frequency_of_depreciation)) + depreciation_amount_without_pro_rata = depreciation_amount + depreciation_amount, days, months = self.get_pro_rata_amt(d, depreciation_amount, schedule_date, self.to_date) - monthly_schedule_date = add_months(schedule_date, 1) + depreciation_amount = self.get_adjusted_depreciation_amount(depreciation_amount_without_pro_rata, + depreciation_amount, d.finance_book) + monthly_schedule_date = add_months(schedule_date, 1) schedule_date = add_days(schedule_date, days) last_schedule_date = schedule_date @@ -406,6 +410,27 @@ class Asset(AccountsController): frappe.throw(_("Depreciation Row {0}: Next Depreciation Date cannot be before Available-for-use Date") .format(row.idx)) + # to ensure that final accumulated depreciation amount is accurate + def get_adjusted_depreciation_amount(self, depreciation_amount_without_pro_rata, depreciation_amount_for_last_row, finance_book): + depreciation_amount_for_first_row = self.get_depreciation_amount_for_first_row(finance_book) + + if depreciation_amount_for_first_row + depreciation_amount_for_last_row != depreciation_amount_without_pro_rata: + depreciation_amount_for_last_row = depreciation_amount_without_pro_rata - depreciation_amount_for_first_row + + return depreciation_amount_for_last_row + + def get_depreciation_amount_for_first_row(self, finance_book): + if self.has_only_one_finance_book(): + return self.schedules[0].depreciation_amount + else: + for schedule in self.schedules: + if schedule.finance_book == finance_book: + return schedule.depreciation_amount + + def has_only_one_finance_book(self): + if len(self.finance_books) == 1: + return True + def set_accumulated_depreciation(self, date_of_sale=None, date_of_return=None, ignore_booked_entry = False): straight_line_idx = [d.idx for d in self.get("schedules") if d.depreciation_method == 'Straight Line'] finance_books = [] From 273fccf0ddfd5e6d79150b0a2c7e010b12753e21 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 6 Oct 2021 02:08:28 +0530 Subject: [PATCH 15/67] fix: Add test for depreciation on return of sold Asset --- .../sales_invoice/test_sales_invoice.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 79d46c5e56..0e9ceead7f 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2252,6 +2252,33 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount) self.assertTrue(schedule.journal_entry) + def test_depreciation_on_return_of_sold_asset(self): + from erpnext.controllers.sales_and_purchase_return import make_return_doc + + create_asset_data() + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, submit=1) + post_depreciation_entries(getdate("2021-09-30")) + + si = create_sales_invoice(item_code="Macbook Pro", asset=asset.name, qty=1, rate=90000, posting_date=getdate("2021-09-30")) + return_si = make_return_doc("Sales Invoice", si.name) + return_si.submit() + asset.load_from_db() + + expected_values = [ + ["2020-06-30", 1311.48, 1311.48, True], + ["2021-06-30", 20000.0, 21311.48, True], + ["2022-06-30", 20000.0, 41311.48, False], + ["2023-06-30", 20000.0, 61311.48, False], + ["2024-06-30", 20000.0, 81311.48, False], + ["2025-06-06", 18688.52, 100000.0, False] + ] + + for i, schedule in enumerate(asset.schedules): + self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date) + self.assertEqual(expected_values[i][1], schedule.depreciation_amount) + self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount) + self.assertEqual(schedule.journal_entry, schedule.journal_entry) + def test_sales_invoice_against_supplier(self): from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import ( make_customer, From ef3f2fcb3a9ccd4be37b9359b31458ba48e91865 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Sun, 10 Oct 2021 03:50:45 +0530 Subject: [PATCH 16/67] fix: Replace setUp() with setUpClass() --- erpnext/assets/doctype/asset/test_asset.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index b5488750eb..461e110a99 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -21,7 +21,8 @@ from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt class AssetSetup(unittest.TestCase): - def setUp(self): + @classmethod + def setUpClass(cls): set_depreciation_settings_in_company() create_asset_data() frappe.db.sql("delete from `tabTax Rule`") From 4918e9533b10bbc56461a5514d9e8556352d7dd2 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Sun, 10 Oct 2021 03:51:35 +0530 Subject: [PATCH 17/67] fix: Add tearDownClass() --- erpnext/assets/doctype/asset/test_asset.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 461e110a99..45a1ae4a7c 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -27,6 +27,10 @@ class AssetSetup(unittest.TestCase): create_asset_data() frappe.db.sql("delete from `tabTax Rule`") + @classmethod + def tearDownClass(cls): + frappe.db.rollback() + class TestAsset(AssetSetup): def test_purchase_asset(self): pr = make_purchase_receipt(item_code="Macbook Pro", From d8aaf3d389366544b39c05d26e0bc29312f0e797 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 12 Oct 2021 01:07:11 +0530 Subject: [PATCH 18/67] fix: Add tests to validate Asset values --- erpnext/assets/doctype/asset/test_asset.py | 62 +++++++++++++++++++--- 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 45a1ae4a7c..6a283df9f4 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -32,6 +32,49 @@ class AssetSetup(unittest.TestCase): frappe.db.rollback() class TestAsset(AssetSetup): + def test_asset_category_is_fetched(self): + """Tests if the Item's Asset Category value is assigned to the Asset, if the field is empty.""" + + asset = create_asset(item_code="Macbook Pro", do_not_save=1) + asset.asset_category = None + asset.save() + + self.assertEqual(asset.asset_category, "Computers") + + def test_gross_purchase_amount_is_mandatory(self): + asset = create_asset(item_code="Macbook Pro", do_not_save=1) + asset.gross_purchase_amount = 0 + + self.assertRaises(frappe.MandatoryError, asset.save) + + def test_pr_or_pi_mandatory_if_not_existing_asset(self): + """Tests if either PI or PR is present if CWIP is enabled and is_existing_asset=0.""" + + asset = create_asset(item_code="Macbook Pro", do_not_save=1) + enable_cwip_accounting(asset.asset_category) + asset.is_existing_asset=0 + + self.assertRaises(frappe.ValidationError, asset.save) + + enable_cwip_accounting(asset.asset_category, enable=0) + + def test_finance_books_are_present_if_calculate_depreciation_is_enabled(self): + asset = create_asset(item_code="Macbook Pro", do_not_save=1) + asset.calculate_depreciation = 1 + + self.assertRaises(frappe.ValidationError, asset.save) + + # def test_available_for_use_date_is_after_purchase_date(self): + # pr = make_purchase_receipt(item_code="Macbook Pro", + # qty=1, rate=100000.0, location="Test Location", posting_date=add_days(nowdate(), -15)) + + # asset = create_asset(item_code="Macbook Pro", do_not_save=1) + # asset.is_existing_asset = 0 + # asset.purchase_receipt = pr + # asset.available_for_use_date = nowdate() + + # self.assertRaises(frappe.ValidationError, asset.save) + def test_purchase_asset(self): pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location") @@ -950,20 +993,20 @@ def create_asset(**args): asset = frappe.get_doc({ "doctype": "Asset", "asset_name": args.asset_name or "Macbook Pro 1", - "asset_category": "Computers", + "asset_category": args.asset_category or "Computers", "item_code": args.item_code or "Macbook Pro", - "company": args.company or"_Test Company", - "purchase_date": "2015-01-01", + "company": args.company or "_Test Company", + "purchase_date": args.purchase_date or "2015-01-01", "calculate_depreciation": args.calculate_depreciation or 0, "opening_accumulated_depreciation": args.opening_accumulated_depreciation or 0, "number_of_depreciations_booked": args.number_of_depreciations_booked or 0, - "gross_purchase_amount": 100000, - "purchase_receipt_amount": 100000, + "gross_purchase_amount": args.gross_purchase_amount or 100000, + "purchase_receipt_amount": args.purchase_receipt_amount or 100000, "warehouse": args.warehouse or "_Test Warehouse - _TC", "available_for_use_date": args.available_for_use_date or "2020-06-06", - "location": "Test Location", - "asset_owner": "Company", - "is_existing_asset": 1 + "location": args.location or "Test Location", + "asset_owner": args.asset_owner or "Company", + "is_existing_asset": args.is_existing_asset or 1 }) if asset.calculate_depreciation: @@ -1030,3 +1073,6 @@ def set_depreciation_settings_in_company(): # Enable booking asset depreciation entry automatically frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1) + +def enable_cwip_accounting(asset_category, enable=1): + frappe.db.set_value("Asset Category", asset_category, "enable_cwip_accounting", enable) From a7ec007dcff77f99d1af9e8f8c473cba629f6efe Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 12 Oct 2021 01:33:46 +0530 Subject: [PATCH 19/67] fix: Add tests to validate item --- erpnext/assets/doctype/asset/test_asset.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 6a283df9f4..ce1d8d5194 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -75,6 +75,27 @@ class TestAsset(AssetSetup): # self.assertRaises(frappe.ValidationError, asset.save) + def test_item_exists(self): + asset = create_asset(item_code="MacBook", do_not_save=1) + + self.assertRaises(frappe.DoesNotExistError, asset.save) + + def test_validate_item(self): + asset = create_asset(item_code="MacBook Pro", do_not_save=1) + item = frappe.get_doc("Item", "MacBook Pro") + + item.disabled = 1 + item.save() + self.assertRaises(frappe.ValidationError, asset.save) + item.disabled = 0 + + item.is_fixed_asset = 0 + self.assertRaises(frappe.ValidationError, asset.save) + item.is_fixed_asset = 1 + + item.is_stock_item = 1 + self.assertRaises(frappe.ValidationError, asset.save) + def test_purchase_asset(self): pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location") From 8ea1ad9232ceac388c3fc4117752af3fa6880276 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 13 Oct 2021 20:47:39 +0530 Subject: [PATCH 20/67] fix: Only validate against JV if it's not a reverse depreciation entry --- erpnext/accounts/doctype/journal_entry/journal_entry.py | 6 ++++-- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index e568a82761..d04e4a5d8b 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -58,7 +58,10 @@ class JournalEntry(AccountsController): if not frappe.flags.in_import: self.validate_total_debit_and_credit() - self.validate_against_jv() + if not self.flags.is_reverse_depr_entry: + self.validate_against_jv() + self.validate_stock_accounts() + self.validate_reference_doc() if self.docstatus == 0: self.set_against_account() @@ -69,7 +72,6 @@ class JournalEntry(AccountsController): self.validate_empty_accounts_table() self.set_account_and_party_balance() self.validate_inter_company_accounts() - self.validate_stock_accounts() if self.docstatus == 0: self.apply_tax_withholding() diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 44a5fae6fb..2744e0d310 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1063,6 +1063,7 @@ class SalesInvoice(SellingController): reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry) reverse_journal_entry.posting_date = nowdate() + reverse_journal_entry.flags.is_reverse_depr_entry = True reverse_journal_entry.submit() asset.flags.ignore_validate_update_after_submit = True From 749d1b6ee68603984c99045c3d4115678523893e Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 13 Oct 2021 20:49:07 +0530 Subject: [PATCH 21/67] fix: Enable cwip accounting --- erpnext/assets/doctype/asset/test_asset.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index ce1d8d5194..878a31ef93 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -25,6 +25,7 @@ class AssetSetup(unittest.TestCase): def setUpClass(cls): set_depreciation_settings_in_company() create_asset_data() + enable_cwip_accounting("Computers") frappe.db.sql("delete from `tabTax Rule`") @classmethod @@ -51,13 +52,10 @@ class TestAsset(AssetSetup): """Tests if either PI or PR is present if CWIP is enabled and is_existing_asset=0.""" asset = create_asset(item_code="Macbook Pro", do_not_save=1) - enable_cwip_accounting(asset.asset_category) asset.is_existing_asset=0 self.assertRaises(frappe.ValidationError, asset.save) - enable_cwip_accounting(asset.asset_category, enable=0) - def test_finance_books_are_present_if_calculate_depreciation_is_enabled(self): asset = create_asset(item_code="Macbook Pro", do_not_save=1) asset.calculate_depreciation = 1 @@ -330,7 +328,6 @@ class TestAsset(AssetSetup): def test_expense_head(self): pr = make_purchase_receipt(item_code="Macbook Pro", qty=2, rate=200000.0, location="Test Location") - doc = make_invoice(pr.name) self.assertEqual('Asset Received But Not Billed - _TC', doc.items[0].expense_account) From 83ec9879ee963c3e8b3b7551df93679eb3643689 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 13 Oct 2021 21:21:50 +0530 Subject: [PATCH 22/67] fix: Add test to validate available_for_use_date --- erpnext/assets/doctype/asset/test_asset.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 878a31ef93..964451dc2f 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -62,16 +62,17 @@ class TestAsset(AssetSetup): self.assertRaises(frappe.ValidationError, asset.save) - # def test_available_for_use_date_is_after_purchase_date(self): - # pr = make_purchase_receipt(item_code="Macbook Pro", - # qty=1, rate=100000.0, location="Test Location", posting_date=add_days(nowdate(), -15)) + def test_available_for_use_date_is_after_purchase_date(self): + pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=100000.0, + location="Test Location", posting_date=getdate("2021-10-10")) - # asset = create_asset(item_code="Macbook Pro", do_not_save=1) - # asset.is_existing_asset = 0 - # asset.purchase_receipt = pr - # asset.available_for_use_date = nowdate() + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, do_not_save=1) + asset.is_existing_asset = 0 + asset.purchase_receipt = pr.name + asset.purchase_date = getdate("2021-10-10") + asset.available_for_use_date = getdate("2021-10-1") - # self.assertRaises(frappe.ValidationError, asset.save) + self.assertRaises(frappe.ValidationError, asset.save) def test_item_exists(self): asset = create_asset(item_code="MacBook", do_not_save=1) From e8986df3cafa49d1b317429036f4c145ca4f8d86 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 13 Oct 2021 21:36:10 +0530 Subject: [PATCH 23/67] fix: Move test for Finance Books to Depreciation test suite --- erpnext/assets/doctype/asset/test_asset.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 964451dc2f..cb984a70bc 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -56,12 +56,6 @@ class TestAsset(AssetSetup): self.assertRaises(frappe.ValidationError, asset.save) - def test_finance_books_are_present_if_calculate_depreciation_is_enabled(self): - asset = create_asset(item_code="Macbook Pro", do_not_save=1) - asset.calculate_depreciation = 1 - - self.assertRaises(frappe.ValidationError, asset.save) - def test_available_for_use_date_is_after_purchase_date(self): pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location", posting_date=getdate("2021-10-10")) @@ -852,6 +846,12 @@ class TestDepreciationBasics(AssetSetup): self.assertRaises(frappe.ValidationError, asset.save) + def test_finance_books_are_present_if_calculate_depreciation_is_enabled(self): + asset = create_asset(item_code="Macbook Pro", do_not_save=1) + asset.calculate_depreciation = 1 + + self.assertRaises(frappe.ValidationError, asset.save) + def test_post_depreciation_entries(self): """Tests if post_depreciation_entries() works as expected.""" From 4bf01bb4b7b6770b5f957824d912397b1556f168 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Thu, 14 Oct 2021 20:28:57 +0530 Subject: [PATCH 24/67] fix: Move Purchase Receipt creation to setUpClass --- erpnext/assets/doctype/asset/test_asset.py | 56 ++++++---------------- 1 file changed, 15 insertions(+), 41 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 773ecc7841..a94f84f969 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -26,6 +26,7 @@ class AssetSetup(unittest.TestCase): set_depreciation_settings_in_company() create_asset_data() enable_cwip_accounting("Computers") + make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location") frappe.db.sql("delete from `tabTax Rule`") @classmethod @@ -57,12 +58,8 @@ class TestAsset(AssetSetup): self.assertRaises(frappe.ValidationError, asset.save) def test_available_for_use_date_is_after_purchase_date(self): - pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=100000.0, - location="Test Location", posting_date=getdate("2021-10-10")) - asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, do_not_save=1) asset.is_existing_asset = 0 - asset.purchase_receipt = pr.name asset.purchase_date = getdate("2021-10-10") asset.available_for_use_date = getdate("2021-10-1") @@ -152,21 +149,9 @@ class TestAsset(AssetSetup): self.assertEqual(doc.items[0].is_fixed_asset, 1) def test_scrap_asset(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2020-01-01' - asset.purchase_date = '2020-01-01' - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 10, - "frequency_of_depreciation": 1 - }) - asset.submit() + asset = create_asset(calculate_depreciation=1, available_for_use_date='2020-01-01', + purchase_date = '2020-01-01', expected_value_after_useful_life=10000, + total_number_of_depreciations=10, frequency_of_depreciation=1, submit=1) post_depreciation_entries(date=add_months('2020-01-01', 4)) @@ -194,22 +179,11 @@ class TestAsset(AssetSetup): self.assertEqual(asset.status, "Partially Depreciated") def test_gle_made_by_asset_sale(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") + asset = create_asset(calculate_depreciation=1, available_for_use_date='2020-06-06', + purchase_date = '2020-01-01', expected_value_after_useful_life=10000, + total_number_of_depreciations=3, frequency_of_depreciation=10, + depreciation_start_date='2020-12-31', submit=1) - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2020-06-06' - asset.purchase_date = '2020-06-06' - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10, - "depreciation_start_date": "2020-12-31" - }) - asset.submit() post_depreciation_entries(date="2021-01-01") si = make_sales_invoice(asset=asset.name, item_code="Macbook Pro", company="_Test Company") @@ -237,6 +211,13 @@ class TestAsset(AssetSetup): si.cancel() self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated") + def test_expense_head(self): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=2, rate=200000.0, location="Test Location") + doc = make_invoice(pr.name) + + self.assertEqual('Asset Received But Not Billed - _TC', doc.items[0].expense_account) + # CWIP: Capital Work In Progress def test_cwip_accounting(self): pr = make_purchase_receipt(item_code="Macbook Pro", @@ -320,13 +301,6 @@ class TestAsset(AssetSetup): self.assertEqual(gle, expected_gle) - def test_expense_head(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=2, rate=200000.0, location="Test Location") - doc = make_invoice(pr.name) - - self.assertEqual('Asset Received But Not Billed - _TC', doc.items[0].expense_account) - def test_asset_cwip_toggling_cases(self): cwip = frappe.db.get_value("Asset Category", "Computers", "enable_cwip_accounting") name = frappe.db.get_value("Asset Category Account", filters={"parent": "Computers"}, fieldname=["name"]) From 09215a9781379e78b78b3d7e5a5c6546558256a6 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Thu, 14 Oct 2021 22:15:10 +0530 Subject: [PATCH 25/67] fix: Remove PR creation from all tests for Depreciation Methods --- erpnext/assets/doctype/asset/test_asset.py | 194 ++++++--------------- 1 file changed, 54 insertions(+), 140 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index a94f84f969..8795b0223e 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -371,23 +371,10 @@ class TestAsset(AssetSetup): class TestDepreciationMethods(AssetSetup): def test_schedule_for_straight_line_method(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2030-01-01' - asset.purchase_date = '2030-01-01' - - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) - asset.save() + asset = create_asset(calculate_depreciation=1, + available_for_use_date="2030-01-01", purchase_date="2030-01-01", + expected_value_after_useful_life=10000, depreciation_start_date="2030-12-31", + total_number_of_depreciations=3, frequency_of_depreciation=12) self.assertEqual(asset.status, "Draft") expected_schedules = [ @@ -402,21 +389,13 @@ class TestDepreciationMethods(AssetSetup): self.assertEqual(schedules, expected_schedules) def test_schedule_for_straight_line_method_for_existing_asset(self): - create_asset(is_existing_asset=1) - asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"}) - asset.calculate_depreciation = 1 - asset.number_of_depreciations_booked = 1 - asset.opening_accumulated_depreciation = 40000 - asset.available_for_use_date = "2030-06-06" - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) + asset = create_asset(calculate_depreciation=1, + available_for_use_date="2030-06-06", is_existing_asset=1, + number_of_depreciations_booked = 1, opening_accumulated_depreciation=40000, + expected_value_after_useful_life=10000, depreciation_start_date="2030-12-31", + total_number_of_depreciations=3, frequency_of_depreciation=12) + self.assertEqual(asset.status, "Draft") - asset.save() expected_schedules = [ ["2030-12-31", 14246.58, 54246.58], ["2031-12-31", 25000.00, 79246.58], @@ -428,22 +407,12 @@ class TestDepreciationMethods(AssetSetup): self.assertEqual(schedules, expected_schedules) def test_schedule_for_double_declining_method(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") + asset = create_asset(calculate_depreciation=1, + available_for_use_date="2030-01-01", purchase_date="2030-01-01", + depreciation_method="Double Declining Balance", + expected_value_after_useful_life=10000, depreciation_start_date="2030-12-31", + total_number_of_depreciations=3, frequency_of_depreciation=12) - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2030-01-01' - asset.purchase_date = '2030-01-01' - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Double Declining Balance", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": '2030-12-31' - }) - asset.save() self.assertEqual(asset.status, "Draft") expected_schedules = [ @@ -458,22 +427,13 @@ class TestDepreciationMethods(AssetSetup): self.assertEqual(schedules, expected_schedules) def test_schedule_for_double_declining_method_for_existing_asset(self): - create_asset(is_existing_asset = 1) - asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"}) - asset.calculate_depreciation = 1 - asset.is_existing_asset = 1 - asset.number_of_depreciations_booked = 1 - asset.opening_accumulated_depreciation = 50000 - asset.available_for_use_date = '2030-01-01' - asset.purchase_date = '2029-11-30' - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Double Declining Balance", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) - asset.save() + asset = create_asset(calculate_depreciation=1, + available_for_use_date="2030-01-01", is_existing_asset=1, + depreciation_method="Double Declining Balance", + number_of_depreciations_booked = 1, opening_accumulated_depreciation=50000, + expected_value_after_useful_life=10000, depreciation_start_date="2030-12-31", + total_number_of_depreciations=3, frequency_of_depreciation=12) + self.assertEqual(asset.status, "Draft") expected_schedules = [ @@ -487,24 +447,11 @@ class TestDepreciationMethods(AssetSetup): self.assertEqual(schedules, expected_schedules) def test_schedule_for_prorated_straight_line_method(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.purchase_date = '2030-01-30' - asset.is_existing_asset = 0 - asset.available_for_use_date = "2030-01-30" - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) - - asset.save() + asset = create_asset(calculate_depreciation=1, + available_for_use_date="2030-01-30", purchase_date="2030-01-30", + depreciation_method="Straight Line", + expected_value_after_useful_life=10000, depreciation_start_date="2030-12-31", + total_number_of_depreciations=3, frequency_of_depreciation=12) expected_schedules = [ ["2030-12-31", 27534.25, 27534.25], @@ -520,29 +467,18 @@ class TestDepreciationMethods(AssetSetup): # WDV: Written Down Value method def test_depreciation_entry_for_wdv_without_pro_rata(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=8000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2030-01-01' - asset.purchase_date = '2030-01-01' - asset.append("finance_books", { - "expected_value_after_useful_life": 1000, - "depreciation_method": "Written Down Value", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) - asset.save(ignore_permissions=True) + asset = create_asset(calculate_depreciation=1, + available_for_use_date="2030-01-01", purchase_date="2030-01-01", + depreciation_method="Written Down Value", + expected_value_after_useful_life=12500, depreciation_start_date="2030-12-31", + total_number_of_depreciations=3, frequency_of_depreciation=12) self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) expected_schedules = [ - ["2030-12-31", 4000.00, 4000.00], - ["2031-12-31", 2000.00, 6000.00], - ["2032-12-31", 1000.00, 7000.0], + ["2030-12-31", 50000.0, 50000.0], + ["2031-12-31", 25000.0, 75000.0], + ["2032-12-31", 12500.0, 87500.0], ] schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] @@ -552,30 +488,19 @@ class TestDepreciationMethods(AssetSetup): # WDV: Written Down Value method def test_pro_rata_depreciation_entry_for_wdv(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=8000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2030-06-06' - asset.purchase_date = '2030-01-01' - asset.append("finance_books", { - "expected_value_after_useful_life": 1000, - "depreciation_method": "Written Down Value", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) - asset.save(ignore_permissions=True) + asset = create_asset(calculate_depreciation=1, + available_for_use_date="2030-06-06", purchase_date="2030-01-01", + depreciation_method="Written Down Value", + expected_value_after_useful_life=12500, depreciation_start_date="2030-12-31", + total_number_of_depreciations=3, frequency_of_depreciation=12) self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) expected_schedules = [ - ["2030-12-31", 2279.45, 2279.45], - ["2031-12-31", 2860.28, 5139.73], - ["2032-12-31", 1430.14, 6569.87], - ["2033-06-06", 430.13, 7000.0], + ["2030-12-31", 28493.15, 28493.15], + ["2031-12-31", 35753.43, 64246.58], + ["2032-12-31", 17876.71, 82123.29], + ["2033-06-06", 5376.71, 87500.0] ] schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] @@ -588,30 +513,19 @@ class TestDepreciationMethods(AssetSetup): company_flag = frappe.flags.company frappe.flags.company = "_Test Company" - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=8000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2030-07-12' - asset.purchase_date = '2030-01-01' - asset.append("finance_books", { - "expected_value_after_useful_life": 1000, - "depreciation_method": "Written Down Value", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) - asset.save(ignore_permissions=True) + asset = create_asset(calculate_depreciation=1, + available_for_use_date="2030-07-12", purchase_date="2030-01-01", + depreciation_method="Written Down Value", + expected_value_after_useful_life=12500, depreciation_start_date="2030-12-31", + total_number_of_depreciations=3, frequency_of_depreciation=12) self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) expected_schedules = [ - ["2030-12-31", 942.47, 942.47], - ["2031-12-31", 3528.77, 4471.24], - ["2032-12-31", 1764.38, 6235.62], - ["2033-07-12", 764.38, 7000.00] + ["2030-12-31", 11780.82, 11780.82], + ["2031-12-31", 44109.59, 55890.41], + ["2032-12-31", 22054.8, 77945.21], + ["2033-07-12", 9554.79, 87500.0] ] schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] @@ -1009,7 +923,7 @@ def create_asset(**args): if asset.calculate_depreciation: asset.append("finance_books", { - "depreciation_method": "Straight Line", + "depreciation_method": args.depreciation_method or "Straight Line", "frequency_of_depreciation": args.frequency_of_depreciation or 12, "total_number_of_depreciations": args.total_number_of_depreciations or 5, "expected_value_after_useful_life": args.expected_value_after_useful_life or 0, From 968be70bd1c0011792ea2c3887dc2f794b15d300 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Thu, 14 Oct 2021 22:32:27 +0530 Subject: [PATCH 26/67] fix: Remove PR creation from all tests in TestDepreciationBasics --- erpnext/assets/doctype/asset/test_asset.py | 92 +++++----------------- 1 file changed, 20 insertions(+), 72 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 8795b0223e..cbfadc0470 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -744,17 +744,9 @@ class TestDepreciationBasics(AssetSetup): """Tests if post_depreciation_entries() works as expected.""" asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, - available_for_use_date=getdate("2019-12-31"), do_not_save=1) - - asset.finance_books = [] - asset.append("finance_books", { - "depreciation_method": "Straight Line", - "frequency_of_depreciation": 12, - "total_number_of_depreciations": 3, - "expected_value_after_useful_life": 10000, - "depreciation_start_date": getdate("2020-12-31") - }) - asset.submit() + available_for_use_date="2019-12-31", depreciation_start_date="2020-12-31", + frequency_of_depreciation=12, total_number_of_depreciations=3, + expected_value_after_useful_life=10000, submit=1) post_depreciation_entries(date="2021-06-01") asset.load_from_db() @@ -767,17 +759,9 @@ class TestDepreciationBasics(AssetSetup): """Tests if clear_depreciation_schedule() works as expected.""" asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, - available_for_use_date=getdate("2019-12-31"), do_not_save=1) - - asset.finance_books = [] - asset.append("finance_books", { - "depreciation_method": "Straight Line", - "frequency_of_depreciation": 12, - "total_number_of_depreciations": 3, - "expected_value_after_useful_life": 10000, - "depreciation_start_date": getdate("2020-12-31") - }) - asset.submit() + available_for_use_date="2019-12-31", depreciation_start_date="2020-12-31", + frequency_of_depreciation=12, total_number_of_depreciations=3, + expected_value_after_useful_life=10000, submit=1) post_depreciation_entries(date="2021-06-01") asset.load_from_db() @@ -787,22 +771,12 @@ class TestDepreciationBasics(AssetSetup): self.assertEqual(len(asset.schedules), 1) def test_depreciation_entry_cancellation(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") + asset = create_asset(item_code="Macbook Pro", + calculate_depreciation=1, purchase_date="2020-06-06", + available_for_use_date="2020-06-06", depreciation_start_date="2020-12-31", + frequency_of_depreciation=10, total_number_of_depreciations=3, + expected_value_after_useful_life=10000, submit=1) - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2020-06-06' - asset.purchase_date = '2020-06-06' - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10, - "depreciation_start_date": "2020-12-31" - }) - asset.submit() post_depreciation_entries(date="2021-01-01") asset.load_from_db() @@ -817,21 +791,11 @@ class TestDepreciationBasics(AssetSetup): self.assertFalse(depr_entry) def test_asset_expected_value_after_useful_life(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, + available_for_use_date="2020-06-06", purchase_date="2020-06-06", + frequency_of_depreciation=10, total_number_of_depreciations=3, + expected_value_after_useful_life=10000) - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2020-06-06' - asset.purchase_date = '2020-06-06' - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10 - }) - asset.save() accumulated_depreciation_after_full_schedule = \ max(d.accumulated_depreciation_amount for d in asset.get("schedules")) @@ -841,28 +805,12 @@ class TestDepreciationBasics(AssetSetup): self.assertTrue(asset.finance_books[0].expected_value_after_useful_life >= asset_value_after_full_schedule) def test_gle_made_by_depreciation_entries(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") + asset = create_asset(item_code="Macbook Pro", + calculate_depreciation=1, purchase_date="2020-01-30", + available_for_use_date="2020-01-30", depreciation_start_date="2020-12-31", + frequency_of_depreciation=10, total_number_of_depreciations=3, + expected_value_after_useful_life=10000, submit=1) - finance_book = frappe.new_doc('Finance Book') - finance_book.finance_book_name = 'Income Tax' - finance_book.for_income_tax = 1 - finance_book.insert(ignore_if_duplicate=1) - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.purchase_date = '2020-01-30' - asset.available_for_use_date = "2020-01-30" - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10, - "depreciation_start_date": "2020-12-31" - }) - asset.submit() - asset.load_from_db() self.assertEqual(asset.status, "Submitted") frappe.db.set_value("Company", "_Test Company", "series_for_depreciation_entry", "DEPR-") From e9d310a13e902d7208a925bb577faba2604cd684 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Fri, 15 Oct 2021 01:45:53 +0530 Subject: [PATCH 27/67] fix: Format tests --- erpnext/assets/doctype/asset/test_asset.py | 406 ++++++++++++++------- 1 file changed, 269 insertions(+), 137 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 070a3e9f55..523c8195ae 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -149,9 +149,15 @@ class TestAsset(AssetSetup): self.assertEqual(doc.items[0].is_fixed_asset, 1) def test_scrap_asset(self): - asset = create_asset(calculate_depreciation=1, available_for_use_date='2020-01-01', - purchase_date = '2020-01-01', expected_value_after_useful_life=10000, - total_number_of_depreciations=10, frequency_of_depreciation=1, submit=1) + asset = create_asset( + calculate_depreciation = 1, + available_for_use_date = '2020-01-01', + purchase_date = '2020-01-01', + expected_value_after_useful_life = 10000, + total_number_of_depreciations = 10, + frequency_of_depreciation = 1, + submit = 1 + ) post_depreciation_entries(date=add_months('2020-01-01', 4)) @@ -179,10 +185,16 @@ class TestAsset(AssetSetup): self.assertEqual(asset.status, "Partially Depreciated") def test_gle_made_by_asset_sale(self): - asset = create_asset(calculate_depreciation=1, available_for_use_date='2020-06-06', - purchase_date = '2020-01-01', expected_value_after_useful_life=10000, - total_number_of_depreciations=3, frequency_of_depreciation=10, - depreciation_start_date='2020-12-31', submit=1) + asset = create_asset( + calculate_depreciation = 1, + available_for_use_date = '2020-06-06', + purchase_date = '2020-01-01', + expected_value_after_useful_life = 10000, + total_number_of_depreciations = 3, + frequency_of_depreciation = 10, + depreciation_start_date = '2020-12-31', + submit = 1 + ) post_depreciation_entries(date="2021-01-01") @@ -371,10 +383,15 @@ class TestAsset(AssetSetup): class TestDepreciationMethods(AssetSetup): def test_schedule_for_straight_line_method(self): - asset = create_asset(calculate_depreciation=1, - available_for_use_date="2030-01-01", purchase_date="2030-01-01", - expected_value_after_useful_life=10000, depreciation_start_date="2030-12-31", - total_number_of_depreciations=3, frequency_of_depreciation=12) + asset = create_asset( + calculate_depreciation = 1, + available_for_use_date = "2030-01-01", + purchase_date = "2030-01-01", + expected_value_after_useful_life = 10000, + depreciation_start_date = "2030-12-31", + total_number_of_depreciations = 3, + frequency_of_depreciation = 12 + ) self.assertEqual(asset.status, "Draft") expected_schedules = [ @@ -389,11 +406,17 @@ class TestDepreciationMethods(AssetSetup): self.assertEqual(schedules, expected_schedules) def test_schedule_for_straight_line_method_for_existing_asset(self): - asset = create_asset(calculate_depreciation=1, - available_for_use_date="2030-06-06", is_existing_asset=1, - number_of_depreciations_booked = 1, opening_accumulated_depreciation=40000, - expected_value_after_useful_life=10000, depreciation_start_date="2030-12-31", - total_number_of_depreciations=3, frequency_of_depreciation=12) + asset = create_asset( + calculate_depreciation = 1, + available_for_use_date = "2030-06-06", + is_existing_asset = 1, + number_of_depreciations_booked = 1, + opening_accumulated_depreciation = 40000, + expected_value_after_useful_life = 10000, + depreciation_start_date = "2030-12-31", + total_number_of_depreciations = 3, + frequency_of_depreciation = 12 + ) self.assertEqual(asset.status, "Draft") expected_schedules = [ @@ -407,11 +430,16 @@ class TestDepreciationMethods(AssetSetup): self.assertEqual(schedules, expected_schedules) def test_schedule_for_double_declining_method(self): - asset = create_asset(calculate_depreciation=1, - available_for_use_date="2030-01-01", purchase_date="2030-01-01", - depreciation_method="Double Declining Balance", - expected_value_after_useful_life=10000, depreciation_start_date="2030-12-31", - total_number_of_depreciations=3, frequency_of_depreciation=12) + asset = create_asset( + calculate_depreciation = 1, + available_for_use_date = "2030-01-01", + purchase_date = "2030-01-01", + depreciation_method = "Double Declining Balance", + expected_value_after_useful_life = 10000, + depreciation_start_date = "2030-12-31", + total_number_of_depreciations = 3, + frequency_of_depreciation = 12 + ) self.assertEqual(asset.status, "Draft") @@ -427,12 +455,18 @@ class TestDepreciationMethods(AssetSetup): self.assertEqual(schedules, expected_schedules) def test_schedule_for_double_declining_method_for_existing_asset(self): - asset = create_asset(calculate_depreciation=1, - available_for_use_date="2030-01-01", is_existing_asset=1, - depreciation_method="Double Declining Balance", - number_of_depreciations_booked = 1, opening_accumulated_depreciation=50000, - expected_value_after_useful_life=10000, depreciation_start_date="2030-12-31", - total_number_of_depreciations=3, frequency_of_depreciation=12) + asset = create_asset( + calculate_depreciation = 1, + available_for_use_date = "2030-01-01", + is_existing_asset = 1, + depreciation_method = "Double Declining Balance", + number_of_depreciations_booked = 1, + opening_accumulated_depreciation = 50000, + expected_value_after_useful_life = 10000, + depreciation_start_date = "2030-12-31", + total_number_of_depreciations = 3, + frequency_of_depreciation = 12 + ) self.assertEqual(asset.status, "Draft") @@ -447,11 +481,16 @@ class TestDepreciationMethods(AssetSetup): self.assertEqual(schedules, expected_schedules) def test_schedule_for_prorated_straight_line_method(self): - asset = create_asset(calculate_depreciation=1, - available_for_use_date="2030-01-30", purchase_date="2030-01-30", - depreciation_method="Straight Line", - expected_value_after_useful_life=10000, depreciation_start_date="2030-12-31", - total_number_of_depreciations=3, frequency_of_depreciation=12) + asset = create_asset( + calculate_depreciation = 1, + available_for_use_date = "2030-01-30", + purchase_date = "2030-01-30", + depreciation_method = "Straight Line", + expected_value_after_useful_life = 10000, + depreciation_start_date = "2030-12-31", + total_number_of_depreciations = 3, + frequency_of_depreciation = 12 + ) expected_schedules = [ ["2030-12-31", 27534.25, 27534.25], @@ -467,11 +506,16 @@ class TestDepreciationMethods(AssetSetup): # WDV: Written Down Value method def test_depreciation_entry_for_wdv_without_pro_rata(self): - asset = create_asset(calculate_depreciation=1, - available_for_use_date="2030-01-01", purchase_date="2030-01-01", - depreciation_method="Written Down Value", - expected_value_after_useful_life=12500, depreciation_start_date="2030-12-31", - total_number_of_depreciations=3, frequency_of_depreciation=12) + asset = create_asset( + calculate_depreciation = 1, + available_for_use_date = "2030-01-01", + purchase_date = "2030-01-01", + depreciation_method = "Written Down Value", + expected_value_after_useful_life = 12500, + depreciation_start_date = "2030-12-31", + total_number_of_depreciations = 3, + frequency_of_depreciation = 12 + ) self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) @@ -488,11 +532,16 @@ class TestDepreciationMethods(AssetSetup): # WDV: Written Down Value method def test_pro_rata_depreciation_entry_for_wdv(self): - asset = create_asset(calculate_depreciation=1, - available_for_use_date="2030-06-06", purchase_date="2030-01-01", - depreciation_method="Written Down Value", - expected_value_after_useful_life=12500, depreciation_start_date="2030-12-31", - total_number_of_depreciations=3, frequency_of_depreciation=12) + asset = create_asset( + calculate_depreciation = 1, + available_for_use_date = "2030-06-06", + purchase_date = "2030-01-01", + depreciation_method = "Written Down Value", + expected_value_after_useful_life = 12500, + depreciation_start_date = "2030-12-31", + total_number_of_depreciations = 3, + frequency_of_depreciation = 12 + ) self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) @@ -513,11 +562,16 @@ class TestDepreciationMethods(AssetSetup): company_flag = frappe.flags.company frappe.flags.company = "_Test Company" - asset = create_asset(calculate_depreciation=1, - available_for_use_date="2030-07-12", purchase_date="2030-01-01", - depreciation_method="Written Down Value", - expected_value_after_useful_life=12500, depreciation_start_date="2030-12-31", - total_number_of_depreciations=3, frequency_of_depreciation=12) + asset = create_asset( + calculate_depreciation = 1, + available_for_use_date = "2030-07-12", + purchase_date = "2030-01-01", + depreciation_method = "Written Down Value", + expected_value_after_useful_life = 12500, + depreciation_start_date = "2030-12-31", + total_number_of_depreciations = 3, + frequency_of_depreciation = 12 + ) self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) @@ -538,9 +592,15 @@ class TestDepreciationMethods(AssetSetup): class TestDepreciationBasics(AssetSetup): def test_depreciation_without_pro_rata(self): - asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, - available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3, - expected_value_after_useful_life=10000, depreciation_start_date=getdate("2020-12-31"), submit=1) + asset = create_asset( + item_code = "Macbook Pro", + calculate_depreciation = 1, + available_for_use_date = getdate("2019-12-31"), + total_number_of_depreciations = 3, + expected_value_after_useful_life = 10000, + depreciation_start_date = getdate("2020-12-31"), + submit = 1 + ) expected_values = [ ["2020-12-31", 30000, 30000], @@ -554,9 +614,15 @@ class TestDepreciationBasics(AssetSetup): self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount) def test_depreciation_with_pro_rata(self): - asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, - available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3, - expected_value_after_useful_life=10000, depreciation_start_date=getdate("2020-07-01"), submit=1) + asset = create_asset( + item_code = "Macbook Pro", + calculate_depreciation = 1, + available_for_use_date = getdate("2019-12-31"), + total_number_of_depreciations = 3, + expected_value_after_useful_life = 10000, + depreciation_start_date = getdate("2020-07-01"), + submit = 1 + ) expected_values = [ ["2020-07-01", 15000, 15000], @@ -575,16 +641,18 @@ class TestDepreciationBasics(AssetSetup): from erpnext.assets.doctype.asset.asset import get_depreciation_amount - asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, - available_for_use_date=getdate("2019-12-31")) + asset = create_asset( + item_code = "Macbook Pro", + available_for_use_date = "2019-12-31" + ) - asset.finance_books = [] + asset.calculate_depreciation = 1 asset.append("finance_books", { "depreciation_method": "Straight Line", "frequency_of_depreciation": 12, "total_number_of_depreciations": 3, "expected_value_after_useful_life": 10000, - "depreciation_start_date": getdate("2020-12-31") + "depreciation_start_date": "2020-12-31" }) depreciation_amount = get_depreciation_amount(asset, 100000, asset.finance_books[0]) @@ -593,19 +661,16 @@ class TestDepreciationBasics(AssetSetup): def test_make_depreciation_schedule(self): """Tests if make_depreciation_schedule() returns the right values.""" - asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, - available_for_use_date=getdate("2019-12-31"), do_not_save=1) - - asset.finance_books = [] - asset.append("finance_books", { - "depreciation_method": "Straight Line", - "frequency_of_depreciation": 12, - "total_number_of_depreciations": 3, - "expected_value_after_useful_life": 10000, - "depreciation_start_date": getdate("2020-12-31") - }) - - asset.make_depreciation_schedule(date_of_sale=None) + asset = create_asset( + item_code = "Macbook Pro", + calculate_depreciation = 1, + available_for_use_date = "2019-12-31", + depreciation_method = "Straight Line", + frequency_of_depreciation = 12, + total_number_of_depreciations = 3, + expected_value_after_useful_life = 1000, + depreciation_start_date = "2020-12-31" + ) expected_values = [ ['2020-12-31', 30000.0], @@ -620,20 +685,16 @@ class TestDepreciationBasics(AssetSetup): def test_set_accumulated_depreciation(self): """Tests if set_accumulated_depreciation() returns the right values.""" - asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, - available_for_use_date=getdate("2019-12-31"), do_not_save=1) - - asset.finance_books = [] - asset.append("finance_books", { - "depreciation_method": "Straight Line", - "frequency_of_depreciation": 12, - "total_number_of_depreciations": 3, - "expected_value_after_useful_life": 10000, - "depreciation_start_date": getdate("2020-12-31") - }) - - asset.make_depreciation_schedule(date_of_sale=None) - asset.set_accumulated_depreciation() + asset = create_asset( + item_code = "Macbook Pro", + calculate_depreciation = 1, + available_for_use_date = "2019-12-31", + depreciation_method = "Straight Line", + frequency_of_depreciation = 12, + total_number_of_depreciations = 3, + expected_value_after_useful_life = 1000, + depreciation_start_date = "2020-12-31" + ) expected_values = [30000.0, 60000.0, 90000.0] @@ -643,16 +704,19 @@ class TestDepreciationBasics(AssetSetup): def test_check_is_pro_rata(self): """Tests if check_is_pro_rata() returns the right value(i.e. checks if has_pro_rata is accurate).""" - asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, - available_for_use_date=getdate("2019-12-31"), do_not_save=1) + asset = create_asset( + item_code = "Macbook Pro", + available_for_use_date = "2019-12-31", + do_not_save = 1 + ) - asset.finance_books = [] + asset.calculate_depreciation = 1 asset.append("finance_books", { "depreciation_method": "Straight Line", "frequency_of_depreciation": 12, "total_number_of_depreciations": 3, "expected_value_after_useful_life": 10000, - "depreciation_start_date": getdate("2020-12-31") + "depreciation_start_date": "2020-12-31" }) has_pro_rata = asset.check_is_pro_rata(asset.finance_books[0]) @@ -664,7 +728,7 @@ class TestDepreciationBasics(AssetSetup): "frequency_of_depreciation": 12, "total_number_of_depreciations": 3, "expected_value_after_useful_life": 10000, - "depreciation_start_date": getdate("2020-07-01") + "depreciation_start_date": "2020-07-01" }) has_pro_rata = asset.check_is_pro_rata(asset.finance_books[0]) @@ -673,64 +737,103 @@ class TestDepreciationBasics(AssetSetup): def test_expected_value_after_useful_life_greater_than_purchase_amount(self): """Tests if an error is raised when expected_value_after_useful_life(110,000) > gross_purchase_amount(100,000).""" - asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, - available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3, - expected_value_after_useful_life=110000, depreciation_start_date=getdate("2020-07-01"), do_not_save=1) + asset = create_asset( + item_code = "Macbook Pro", + calculate_depreciation = 1, + available_for_use_date = "2019-12-31", + total_number_of_depreciations = 3, + expected_value_after_useful_life = 110000, + depreciation_start_date = "2020-07-01", + do_not_save = 1 + ) self.assertRaises(frappe.ValidationError, asset.save) def test_depreciation_start_date(self): """Tests if an error is raised when neither depreciation_start_date nor available_for_use_date are specified.""" - asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, - total_number_of_depreciations=3, expected_value_after_useful_life=110000, do_not_save=1) + asset = create_asset( + item_code = "Macbook Pro", + calculate_depreciation = 1, + total_number_of_depreciations = 3, + expected_value_after_useful_life = 110000, + do_not_save = 1 + ) self.assertRaises(frappe.ValidationError, asset.save) def test_opening_accumulated_depreciation(self): """Tests if an error is raised when opening_accumulated_depreciation > (gross_purchase_amount - expected_value_after_useful_life).""" - asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, - available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3, - expected_value_after_useful_life=10000, depreciation_start_date=getdate("2020-07-01"), - opening_accumulated_depreciation=100000, do_not_save=1) + asset = create_asset( + item_code = "Macbook Pro", + calculate_depreciation = 1, + available_for_use_date = "2019-12-31", + total_number_of_depreciations = 3, + expected_value_after_useful_life = 10000, + depreciation_start_date = "2020-07-01", + opening_accumulated_depreciation = 100000, + do_not_save = 1 + ) self.assertRaises(frappe.ValidationError, asset.save) def test_number_of_depreciations_booked(self): """Tests if an error is raised when number_of_depreciations_booked is not specified when opening_accumulated_depreciation is.""" - asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, - available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3, - expected_value_after_useful_life=10000, depreciation_start_date=getdate("2020-07-01"), - opening_accumulated_depreciation=10000, do_not_save=1) + asset = create_asset( + item_code = "Macbook Pro", + calculate_depreciation = 1, + available_for_use_date = "2019-12-31", + total_number_of_depreciations = 3, + expected_value_after_useful_life = 10000, + depreciation_start_date = "2020-07-01", + opening_accumulated_depreciation = 10000, + do_not_save = 1 + ) self.assertRaises(frappe.ValidationError, asset.save) def test_number_of_depreciations(self): """Tests if an error is raised when number_of_depreciations_booked > total_number_of_depreciations.""" - asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, - available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3, - expected_value_after_useful_life=10000, depreciation_start_date=getdate("2020-07-01"), - opening_accumulated_depreciation=10000, number_of_depreciations_booked=5, - do_not_save=1) + asset = create_asset( + item_code = "Macbook Pro", + calculate_depreciation = 1, + available_for_use_date = "2019-12-31", + total_number_of_depreciations = 3, + expected_value_after_useful_life = 10000, + depreciation_start_date = "2020-07-01", + opening_accumulated_depreciation = 10000, + number_of_depreciations_booked = 5, + do_not_save = 1 + ) self.assertRaises(frappe.ValidationError, asset.save) def test_depreciation_start_date_is_before_purchase_date(self): - asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, - available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3, - expected_value_after_useful_life=10000, depreciation_start_date=getdate("2014-07-01"), - do_not_save=1) + asset = create_asset( + item_code = "Macbook Pro", + calculate_depreciation = 1, + available_for_use_date = "2019-12-31", + total_number_of_depreciations = 3, + expected_value_after_useful_life = 10000, + depreciation_start_date = "2014-07-01", + do_not_save = 1 + ) self.assertRaises(frappe.ValidationError, asset.save) def test_depreciation_start_date_is_before_available_for_use_date(self): - asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, - available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3, - expected_value_after_useful_life=10000, depreciation_start_date=getdate("2018-07-01"), - do_not_save=1) + asset = create_asset( + item_code = "Macbook Pro", + calculate_depreciation = 1, + available_for_use_date = "2019-12-31", + total_number_of_depreciations = 3, + expected_value_after_useful_life = 10000, + depreciation_start_date = "2018-07-01", + do_not_save = 1 + ) self.assertRaises(frappe.ValidationError, asset.save) @@ -743,10 +846,16 @@ class TestDepreciationBasics(AssetSetup): def test_post_depreciation_entries(self): """Tests if post_depreciation_entries() works as expected.""" - asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, - available_for_use_date="2019-12-31", depreciation_start_date="2020-12-31", - frequency_of_depreciation=12, total_number_of_depreciations=3, - expected_value_after_useful_life=10000, submit=1) + asset = create_asset( + item_code = "Macbook Pro", + calculate_depreciation = 1, + available_for_use_date = "2019-12-31", + depreciation_start_date = "2020-12-31", + frequency_of_depreciation = 12, + total_number_of_depreciations = 3, + expected_value_after_useful_life = 10000, + submit = 1 + ) post_depreciation_entries(date="2021-06-01") asset.load_from_db() @@ -758,10 +867,16 @@ class TestDepreciationBasics(AssetSetup): def test_clear_depreciation_schedule(self): """Tests if clear_depreciation_schedule() works as expected.""" - asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, - available_for_use_date="2019-12-31", depreciation_start_date="2020-12-31", - frequency_of_depreciation=12, total_number_of_depreciations=3, - expected_value_after_useful_life=10000, submit=1) + asset = create_asset( + item_code = "Macbook Pro", + calculate_depreciation = 1, + available_for_use_date = "2019-12-31", + depreciation_start_date = "2020-12-31", + frequency_of_depreciation = 12, + total_number_of_depreciations = 3, + expected_value_after_useful_life = 10000, + submit = 1 + ) post_depreciation_entries(date="2021-06-01") asset.load_from_db() @@ -771,11 +886,17 @@ class TestDepreciationBasics(AssetSetup): self.assertEqual(len(asset.schedules), 1) def test_depreciation_entry_cancellation(self): - asset = create_asset(item_code="Macbook Pro", - calculate_depreciation=1, purchase_date="2020-06-06", - available_for_use_date="2020-06-06", depreciation_start_date="2020-12-31", - frequency_of_depreciation=10, total_number_of_depreciations=3, - expected_value_after_useful_life=10000, submit=1) + asset = create_asset( + item_code = "Macbook Pro", + calculate_depreciation = 1, + purchase_date = "2020-06-06", + available_for_use_date = "2020-06-06", + depreciation_start_date = "2020-12-31", + frequency_of_depreciation = 10, + total_number_of_depreciations = 3, + expected_value_after_useful_life = 10000, + submit = 1 + ) post_depreciation_entries(date="2021-01-01") @@ -791,10 +912,15 @@ class TestDepreciationBasics(AssetSetup): self.assertFalse(depr_entry) def test_asset_expected_value_after_useful_life(self): - asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, - available_for_use_date="2020-06-06", purchase_date="2020-06-06", - frequency_of_depreciation=10, total_number_of_depreciations=3, - expected_value_after_useful_life=10000) + asset = create_asset( + item_code = "Macbook Pro", + calculate_depreciation = 1, + available_for_use_date = "2020-06-06", + purchase_date = "2020-06-06", + frequency_of_depreciation = 10, + total_number_of_depreciations = 3, + expected_value_after_useful_life = 10000 + ) accumulated_depreciation_after_full_schedule = \ max(d.accumulated_depreciation_amount for d in asset.get("schedules")) @@ -805,11 +931,17 @@ class TestDepreciationBasics(AssetSetup): self.assertTrue(asset.finance_books[0].expected_value_after_useful_life >= asset_value_after_full_schedule) def test_gle_made_by_depreciation_entries(self): - asset = create_asset(item_code="Macbook Pro", - calculate_depreciation=1, purchase_date="2020-01-30", - available_for_use_date="2020-01-30", depreciation_start_date="2020-12-31", - frequency_of_depreciation=10, total_number_of_depreciations=3, - expected_value_after_useful_life=10000, submit=1) + asset = create_asset( + item_code = "Macbook Pro", + calculate_depreciation = 1, + purchase_date = "2020-01-30", + available_for_use_date = "2020-01-30", + depreciation_start_date = "2020-12-31", + frequency_of_depreciation = 10, + total_number_of_depreciations = 3, + expected_value_after_useful_life = 10000, + submit = 1 + ) self.assertEqual(asset.status, "Submitted") From 371b62136454f720a02b12644d6e57e7d9840a34 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Fri, 15 Oct 2021 04:02:27 +0530 Subject: [PATCH 28/67] fix: Compare date strings --- erpnext/assets/doctype/asset/test_asset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 523c8195ae..c694677d8d 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -679,7 +679,7 @@ class TestDepreciationBasics(AssetSetup): ] for i, schedule in enumerate(asset.schedules): - self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date) + self.assertEqual(expected_values[i][0], schedule.schedule_date) self.assertEqual(expected_values[i][1], schedule.depreciation_amount) def test_set_accumulated_depreciation(self): From 60aae4423dcf9e26557f0a44c2f7c206d54eb07b Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Fri, 15 Oct 2021 04:03:30 +0530 Subject: [PATCH 29/67] fix: Add missing digit --- erpnext/assets/doctype/asset/test_asset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index c694677d8d..c28add8065 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -692,7 +692,7 @@ class TestDepreciationBasics(AssetSetup): depreciation_method = "Straight Line", frequency_of_depreciation = 12, total_number_of_depreciations = 3, - expected_value_after_useful_life = 1000, + expected_value_after_useful_life = 10000, depreciation_start_date = "2020-12-31" ) From fdeb273fa06670d802847199d57ecd40dbecd476 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Fri, 15 Oct 2021 04:53:23 +0530 Subject: [PATCH 30/67] fix: Sider issues --- erpnext/assets/doctype/asset/test_asset.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index c28add8065..6dd7a5be71 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -410,7 +410,7 @@ class TestDepreciationMethods(AssetSetup): calculate_depreciation = 1, available_for_use_date = "2030-06-06", is_existing_asset = 1, - number_of_depreciations_booked = 1, + number_of_depreciations_booked = 1, opening_accumulated_depreciation = 40000, expected_value_after_useful_life = 10000, depreciation_start_date = "2030-12-31", @@ -460,7 +460,7 @@ class TestDepreciationMethods(AssetSetup): available_for_use_date = "2030-01-01", is_existing_asset = 1, depreciation_method = "Double Declining Balance", - number_of_depreciations_booked = 1, + number_of_depreciations_booked = 1, opening_accumulated_depreciation = 50000, expected_value_after_useful_life = 10000, depreciation_start_date = "2030-12-31", From 0b8cb5dd47816b87cfea2176ffa76c889bb9d383 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Fri, 15 Oct 2021 04:56:00 +0530 Subject: [PATCH 31/67] fix: Add missing digit --- erpnext/assets/doctype/asset/test_asset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 6dd7a5be71..c44c63ae9e 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -668,7 +668,7 @@ class TestDepreciationBasics(AssetSetup): depreciation_method = "Straight Line", frequency_of_depreciation = 12, total_number_of_depreciations = 3, - expected_value_after_useful_life = 1000, + expected_value_after_useful_life = 10000, depreciation_start_date = "2020-12-31" ) From 1aa34d178051c9b1ec20bf45171bd12d6df4d151 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Mon, 18 Oct 2021 18:46:45 +0530 Subject: [PATCH 32/67] fix: incorrect VAT Amount in UAT VAT 201 report --- erpnext/regional/report/uae_vat_201/uae_vat_201.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/report/uae_vat_201/uae_vat_201.py b/erpnext/regional/report/uae_vat_201/uae_vat_201.py index f4c049d162..8507b0effb 100644 --- a/erpnext/regional/report/uae_vat_201/uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.py @@ -122,7 +122,7 @@ def get_total_emiratewise(filters): try: return frappe.db.sql(""" select - s.vat_emirate as emirate, sum(i.base_amount) as total, sum(s.total_taxes_and_charges) + s.vat_emirate as emirate, sum(i.base_amount) as total, s.total_taxes_and_charges from `tabSales Invoice Item` i inner join `tabSales Invoice` s on From 4c499e804a29ac6639de6df9cfd6704f338a58c3 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Tue, 19 Oct 2021 14:12:59 +0530 Subject: [PATCH 33/67] fix: wrong vat amount --- erpnext/regional/report/uae_vat_201/uae_vat_201.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/report/uae_vat_201/uae_vat_201.py b/erpnext/regional/report/uae_vat_201/uae_vat_201.py index 8507b0effb..2b5ecc3b18 100644 --- a/erpnext/regional/report/uae_vat_201/uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.py @@ -122,7 +122,7 @@ def get_total_emiratewise(filters): try: return frappe.db.sql(""" select - s.vat_emirate as emirate, sum(i.base_amount) as total, s.total_taxes_and_charges + s.vat_emirate as emirate, sum(i.base_amount) as total, sum(i.tax_amount) from `tabSales Invoice Item` i inner join `tabSales Invoice` s on From 9e7022830e058c3ba95f034942cad054e045e8f6 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Mon, 25 Oct 2021 01:34:42 +0530 Subject: [PATCH 34/67] fix: Only add additional depreciation schedule row on sale if depreciation_amount > 0 --- erpnext/assets/doctype/asset/asset.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index f6c2f4caa2..cf62f496ea 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -232,13 +232,15 @@ class Asset(AccountsController): depreciation_amount, days, months = self.get_pro_rata_amt(d, depreciation_amount, from_date, date_of_sale) - self.append("schedules", { - "schedule_date": date_of_sale, - "depreciation_amount": depreciation_amount, - "depreciation_method": d.depreciation_method, - "finance_book": d.finance_book, - "finance_book_id": d.idx - }) + if depreciation_amount > 0: + self.append("schedules", { + "schedule_date": date_of_sale, + "depreciation_amount": depreciation_amount, + "depreciation_method": d.depreciation_method, + "finance_book": d.finance_book, + "finance_book_id": d.idx + }) + break # For first row From 82bf5e55393520f7681615e1addf4ceee4319ee5 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 26 Oct 2021 20:53:47 +0530 Subject: [PATCH 35/67] fix: Replace post_depreciation_entries() with make_depreciation_entry() --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 379bbbe911..1f0b3c244b 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -37,7 +37,7 @@ from erpnext.assets.doctype.asset.depreciation import ( get_disposal_account_and_cost_center, get_gl_entries_on_asset_disposal, get_gl_entries_on_asset_regain, - post_depreciation_entries, + make_depreciation_entry ) from erpnext.controllers.selling_controller import SellingController from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timesheet_data @@ -1001,7 +1001,7 @@ class SalesInvoice(SellingController): asset.prepare_depreciation_data(date_of_sale=self.posting_date) asset.save() - post_depreciation_entries(self.posting_date) + make_depreciation_entry(asset.name, self.posting_date) def reset_depreciation_schedule(self, asset): asset.flags.ignore_validate_update_after_submit = True From cde0dae98733563c02c7e364de2b97d9831a3e97 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 26 Oct 2021 21:04:06 +0530 Subject: [PATCH 36/67] fix: Add flag for reverse depreciation entries --- erpnext/accounts/doctype/gl_entry/gl_entry.py | 3 ++- erpnext/accounts/doctype/journal_entry/journal_entry.py | 2 +- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index 1e983b1d42..60015f6ec8 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -58,7 +58,8 @@ class GLEntry(Document): # Update outstanding amt on against voucher if (self.against_voucher_type in ['Journal Entry', 'Sales Invoice', 'Purchase Invoice', 'Fees'] - and self.against_voucher and self.flags.update_outstanding == 'Yes'): + and self.against_voucher and self.flags.update_outstanding == 'Yes' + and not frappe.flags.is_reverse_depr_entry): update_outstanding_amt(self.account, self.party_type, self.party, self.against_voucher_type, self.against_voucher) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index d04e4a5d8b..f3a0bdbec4 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -58,7 +58,7 @@ class JournalEntry(AccountsController): if not frappe.flags.in_import: self.validate_total_debit_and_credit() - if not self.flags.is_reverse_depr_entry: + if not frappe.flags.is_reverse_depr_entry: self.validate_against_jv() self.validate_stock_accounts() diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 1f0b3c244b..ef72decaa2 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1045,7 +1045,7 @@ class SalesInvoice(SellingController): reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry) reverse_journal_entry.posting_date = nowdate() - reverse_journal_entry.flags.is_reverse_depr_entry = True + frappe.flags.is_reverse_depr_entry = True reverse_journal_entry.submit() asset.flags.ignore_validate_update_after_submit = True From 06c505ddc2c3529703a35a986896112de7107ae3 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 27 Oct 2021 05:23:01 +0530 Subject: [PATCH 37/67] fix: Linters --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 2 +- erpnext/assets/doctype/asset/test_asset.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index ef72decaa2..1c287d3f8c 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -37,7 +37,7 @@ from erpnext.assets.doctype.asset.depreciation import ( get_disposal_account_and_cost_center, get_gl_entries_on_asset_disposal, get_gl_entries_on_asset_regain, - make_depreciation_entry + make_depreciation_entry, ) from erpnext.controllers.selling_controller import SellingController from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timesheet_data diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index c44c63ae9e..81c679f851 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -20,6 +20,7 @@ from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( ) from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt + class AssetSetup(unittest.TestCase): @classmethod def setUpClass(cls): From a261d08dd868ff6c306a2c8392dddd73d006ab85 Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 27 Oct 2021 18:30:37 +0530 Subject: [PATCH 38/67] fix: reset temporary flag after use --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 0ca11fd451..cd204ba523 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1048,6 +1048,7 @@ class SalesInvoice(SellingController): frappe.flags.is_reverse_depr_entry = True reverse_journal_entry.submit() + frappe.flags.is_reverse_depr_entry = False asset.flags.ignore_validate_update_after_submit = True schedule.journal_entry = None asset.save() From aa9e78bed19b72584a5248afd83ca82011727e13 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 29 Oct 2021 12:45:19 +0530 Subject: [PATCH 39/67] fix: Accounting Dimension filters not honouring user permissions --- erpnext/controllers/queries.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index eeb659dee5..05ece4defe 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -566,7 +566,7 @@ def get_filtered_dimensions(doctype, txt, searchfield, start, page_len, filters) query_filters.append(['name', query_selector, dimensions]) - output = frappe.get_all(doctype, filters=query_filters) + output = frappe.get_list(doctype, filters=query_filters) result = [d.name for d in output] return [(d,) for d in set(result)] From afe09d4e80c3000ce866f0b43ecced878f715273 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 29 Oct 2021 13:45:07 +0530 Subject: [PATCH 40/67] test: remove unnecessary creation of new company (#28137) --- erpnext/tests/test_woocommerce.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/erpnext/tests/test_woocommerce.py b/erpnext/tests/test_woocommerce.py index 881f286bae..3ce68d89bc 100644 --- a/erpnext/tests/test_woocommerce.py +++ b/erpnext/tests/test_woocommerce.py @@ -12,12 +12,6 @@ from erpnext.erpnext_integrations.connectors.woocommerce_connection import order class TestWoocommerce(unittest.TestCase): def setUp(self): - if not frappe.db.exists('Company', 'Woocommerce'): - company = frappe.new_doc("Company") - company.company_name = "Woocommerce" - company.abbr = "W" - company.default_currency = "INR" - company.save() woo_settings = frappe.get_doc("Woocommerce Settings") if not woo_settings.secret: @@ -26,14 +20,14 @@ class TestWoocommerce(unittest.TestCase): woo_settings.api_consumer_key = "ck_fd43ff5756a6abafd95fadb6677100ce95a758a1" woo_settings.api_consumer_secret = "cs_94360a1ad7bef7fa420a40cf284f7b3e0788454e" woo_settings.enable_sync = 1 - woo_settings.company = "Woocommerce" - woo_settings.tax_account = "Sales Expenses - W" - woo_settings.f_n_f_account = "Expenses - W" + woo_settings.company = "_Test Company" + woo_settings.tax_account = "Sales Expenses - _TC" + woo_settings.f_n_f_account = "Expenses - _TC" woo_settings.creation_user = "Administrator" woo_settings.save(ignore_permissions=True) def test_sales_order_for_woocommerce(self): - frappe.flags.woocomm_test_order_data = {"id":75,"parent_id":0,"number":"74","order_key":"wc_order_5aa1281c2dacb","created_via":"checkout","version":"3.3.3","status":"processing","currency":"INR","date_created":"2018-03-08T12:10:04","date_created_gmt":"2018-03-08T12:10:04","date_modified":"2018-03-08T12:10:04","date_modified_gmt":"2018-03-08T12:10:04","discount_total":"0.00","discount_tax":"0.00","shipping_total":"150.00","shipping_tax":"0.00","cart_tax":"0.00","total":"649.00","total_tax":"0.00","prices_include_tax":False,"customer_id":12,"customer_ip_address":"103.54.99.5","customer_user_agent":"mozilla\\/5.0 (x11; linux x86_64) applewebkit\\/537.36 (khtml, like gecko) chrome\\/64.0.3282.186 safari\\/537.36","customer_note":"","billing":{"first_name":"Tony","last_name":"Stark","company":"Woocommerce","address_1":"Mumbai","address_2":"","city":"Dadar","state":"MH","postcode":"123","country":"IN","email":"tony@gmail.com","phone":"123457890"},"shipping":{"first_name":"Tony","last_name":"Stark","company":"","address_1":"Mumbai","address_2":"","city":"Dadar","state":"MH","postcode":"123","country":"IN"},"payment_method":"cod","payment_method_title":"Cash on delivery","transaction_id":"","date_paid":"","date_paid_gmt":"","date_completed":"","date_completed_gmt":"","cart_hash":"8e76b020d5790066496f244860c4703f","meta_data":[],"line_items":[{"id":80,"name":"Marvel","product_id":56,"variation_id":0,"quantity":1,"tax_class":"","subtotal":"499.00","subtotal_tax":"0.00","total":"499.00","total_tax":"0.00","taxes":[],"meta_data":[],"sku":"","price":499}],"tax_lines":[],"shipping_lines":[{"id":81,"method_title":"Flat rate","method_id":"flat_rate:1","total":"150.00","total_tax":"0.00","taxes":[],"meta_data":[{"id":623,"key":"Items","value":"Marvel × 1"}]}],"fee_lines":[],"coupon_lines":[],"refunds":[]} + frappe.flags.woocomm_test_order_data = {"id":75,"parent_id":0,"number":"74","order_key":"wc_order_5aa1281c2dacb","created_via":"checkout","version":"3.3.3","status":"processing","currency":"INR","date_created":"2018-03-08T12:10:04","date_created_gmt":"2018-03-08T12:10:04","date_modified":"2018-03-08T12:10:04","date_modified_gmt":"2018-03-08T12:10:04","discount_total":"0.00","discount_tax":"0.00","shipping_total":"150.00","shipping_tax":"0.00","cart_tax":"0.00","total":"649.00","total_tax":"0.00","prices_include_tax":False,"customer_id":12,"customer_ip_address":"103.54.99.5","customer_user_agent":"mozilla\\/5.0 (x11; linux x86_64) applewebkit\\/537.36 (khtml, like gecko) chrome\\/64.0.3282.186 safari\\/537.36","customer_note":"","billing":{"first_name":"Tony","last_name":"Stark","company":"_Test Company","address_1":"Mumbai","address_2":"","city":"Dadar","state":"MH","postcode":"123","country":"IN","email":"tony@gmail.com","phone":"123457890"},"shipping":{"first_name":"Tony","last_name":"Stark","company":"","address_1":"Mumbai","address_2":"","city":"Dadar","state":"MH","postcode":"123","country":"IN"},"payment_method":"cod","payment_method_title":"Cash on delivery","transaction_id":"","date_paid":"","date_paid_gmt":"","date_completed":"","date_completed_gmt":"","cart_hash":"8e76b020d5790066496f244860c4703f","meta_data":[],"line_items":[{"id":80,"name":"Marvel","product_id":56,"variation_id":0,"quantity":1,"tax_class":"","subtotal":"499.00","subtotal_tax":"0.00","total":"499.00","total_tax":"0.00","taxes":[],"meta_data":[],"sku":"","price":499}],"tax_lines":[],"shipping_lines":[{"id":81,"method_title":"Flat rate","method_id":"flat_rate:1","total":"150.00","total_tax":"0.00","taxes":[],"meta_data":[{"id":623,"key":"Items","value":"Marvel × 1"}]}],"fee_lines":[],"coupon_lines":[],"refunds":[]} order() self.assertTrue(frappe.get_value("Customer",{"woocommerce_email":"tony@gmail.com"})) From 292419bc9ebc405ef8a4861125070abb0d321db1 Mon Sep 17 00:00:00 2001 From: Marica Date: Fri, 29 Oct 2021 13:49:27 +0530 Subject: [PATCH 41/67] fix: Skip empty rows while updating unsaved BOM cost (#28136) - Dont try to get valuation rate if row has no item code - Dont try to add exploded items if row has no item code --- erpnext/manufacturing/doctype/bom/bom.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 232e3a0b0f..2cd8f8c15a 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -307,6 +307,9 @@ class BOM(WebsiteGenerator): existing_bom_cost = self.total_cost for d in self.get("items"): + if not d.item_code: + continue + rate = self.get_rm_rate({ "company": self.company, "item_code": d.item_code, @@ -599,7 +602,7 @@ class BOM(WebsiteGenerator): for d in self.get('items'): if d.bom_no: self.get_child_exploded_items(d.bom_no, d.stock_qty) - else: + elif d.item_code: self.add_to_cur_exploded_items(frappe._dict({ 'item_code' : d.item_code, 'item_name' : d.item_name, From 8ccd3fee9e7092d6bb7d81e8ef6d59e25c13a29d Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 29 Oct 2021 16:08:52 +0530 Subject: [PATCH 42/67] fix: COA importer importing all accounts as group --- .../doctype/account/chart_of_accounts/chart_of_accounts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py index 05caafe1c4..3596c34017 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py +++ b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py @@ -81,7 +81,7 @@ def add_suffix_if_duplicate(account_name, account_number, accounts): def identify_is_group(child): if child.get("is_group"): is_group = child.get("is_group") - elif len(set(child.keys()) - set(["account_type", "root_type", "is_group", "tax_rate", "account_number"])): + elif len(set(child.keys()) - set(["account_name", "account_type", "root_type", "is_group", "tax_rate", "account_number"])): is_group = 1 else: is_group = 0 From 75a76e634d17507a568ff007d20226ffa8f7dff3 Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Fri, 29 Oct 2021 16:45:04 +0530 Subject: [PATCH 43/67] fix: POS consolidated invoice rounded total issue (#28006) --- .../pos_invoice_merge_log.py | 11 +++++ erpnext/controllers/taxes_and_totals.py | 40 ++++++++++++------- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py index 4f26ed43db..28bd10283e 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py @@ -114,6 +114,8 @@ class POSInvoiceMergeLog(Document): def merge_pos_invoice_into(self, invoice, data): items, payments, taxes = [], [], [] loyalty_amount_sum, loyalty_points_sum = 0, 0 + rounding_adjustment, base_rounding_adjustment = 0, 0 + rounded_total, base_rounded_total = 0, 0 for doc in data: map_doc(doc, invoice, table_map={ "doctype": invoice.doctype }) @@ -162,6 +164,11 @@ class POSInvoiceMergeLog(Document): found = True if not found: payments.append(payment) + rounding_adjustment += doc.rounding_adjustment + rounded_total += doc.rounded_total + base_rounding_adjustment += doc.rounding_adjustment + base_rounded_total += doc.rounded_total + if loyalty_points_sum: invoice.redeem_loyalty_points = 1 @@ -171,6 +178,10 @@ class POSInvoiceMergeLog(Document): invoice.set('items', items) invoice.set('payments', payments) invoice.set('taxes', taxes) + invoice.set('rounding_adjustment',rounding_adjustment) + invoice.set('rounding_adjustment',base_rounding_adjustment) + invoice.set('base_rounded_total',base_rounded_total) + invoice.set('rounded_total',rounded_total) invoice.additional_discount_percentage = 0 invoice.discount_amount = 0.0 invoice.taxes_and_charges = None diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 70cc8a58bf..7e7f598bc4 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -260,7 +260,9 @@ class calculate_taxes_and_totals(object): self.doc.round_floats_in(self.doc, ["total", "base_total", "net_total", "base_net_total"]) def calculate_taxes(self): - self.doc.rounding_adjustment = 0 + if not self.doc.get('is_consolidated'): + self.doc.rounding_adjustment = 0 + # maintain actual tax rate based on idx actual_tax_dict = dict([[tax.idx, flt(tax.tax_amount, tax.precision("tax_amount"))] for tax in self.doc.get("taxes") if tax.charge_type == "Actual"]) @@ -312,7 +314,9 @@ class calculate_taxes_and_totals(object): # adjust Discount Amount loss in last tax iteration if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \ - and self.doc.discount_amount and self.doc.apply_discount_on == "Grand Total": + and self.doc.discount_amount \ + and self.doc.apply_discount_on == "Grand Total" \ + and not self.doc.get('is_consolidated'): self.doc.rounding_adjustment = flt(self.doc.grand_total - flt(self.doc.discount_amount) - tax.total, self.doc.precision("rounding_adjustment")) @@ -405,11 +409,16 @@ class calculate_taxes_and_totals(object): self.doc.rounding_adjustment = diff def calculate_totals(self): - self.doc.grand_total = flt(self.doc.get("taxes")[-1].total) + flt(self.doc.rounding_adjustment) \ - if self.doc.get("taxes") else flt(self.doc.net_total) + if self.doc.get("taxes"): + self.doc.grand_total = flt(self.doc.get("taxes")[-1].total) + flt(self.doc.rounding_adjustment) + else: + self.doc.grand_total = flt(self.doc.net_total) - self.doc.total_taxes_and_charges = flt(self.doc.grand_total - self.doc.net_total + if self.doc.get("taxes"): + self.doc.total_taxes_and_charges = flt(self.doc.grand_total - self.doc.net_total - flt(self.doc.rounding_adjustment), self.doc.precision("total_taxes_and_charges")) + else: + self.doc.total_taxes_and_charges = 0.0 self._set_in_company_currency(self.doc, ["total_taxes_and_charges", "rounding_adjustment"]) @@ -446,19 +455,20 @@ class calculate_taxes_and_totals(object): self.doc.total_net_weight += d.total_weight def set_rounded_total(self): - if self.doc.meta.get_field("rounded_total"): - if self.doc.is_rounded_total_disabled(): - self.doc.rounded_total = self.doc.base_rounded_total = 0 - return + if not self.doc.get('is_consolidated'): + if self.doc.meta.get_field("rounded_total"): + if self.doc.is_rounded_total_disabled(): + self.doc.rounded_total = self.doc.base_rounded_total = 0 + return - self.doc.rounded_total = round_based_on_smallest_currency_fraction(self.doc.grand_total, - self.doc.currency, self.doc.precision("rounded_total")) + self.doc.rounded_total = round_based_on_smallest_currency_fraction(self.doc.grand_total, + self.doc.currency, self.doc.precision("rounded_total")) - #if print_in_rate is set, we would have already calculated rounding adjustment - self.doc.rounding_adjustment += flt(self.doc.rounded_total - self.doc.grand_total, - self.doc.precision("rounding_adjustment")) + #if print_in_rate is set, we would have already calculated rounding adjustment + self.doc.rounding_adjustment += flt(self.doc.rounded_total - self.doc.grand_total, + self.doc.precision("rounding_adjustment")) - self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"]) + self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"]) def _cleanup(self): if not self.doc.get('is_consolidated'): From b44945380dd0af192b12cdf96df7ac23b77a79c3 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Wed, 20 Oct 2021 17:37:52 +0530 Subject: [PATCH 44/67] fix: incorrect amount of serial_nos fetched --- erpnext/stock/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index c4a0497b74..b131125c67 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -104,7 +104,7 @@ def get_stock_balance(item_code, warehouse, posting_date=None, posting_time=None serial_nos = last_entry.get("serial_no") if (serial_nos and - len(get_serial_nos_data(serial_nos)) < last_entry.qty_after_transaction): + len(get_serial_nos_data(serial_nos)) <= last_entry.qty_after_transaction): serial_nos = get_serial_nos_data_after_transactions(args) return ((last_entry.qty_after_transaction, last_entry.valuation_rate, serial_nos) @@ -121,6 +121,7 @@ def get_serial_nos_data_after_transactions(args): WHERE item_code = %(item_code)s and warehouse = %(warehouse)s and timestamp(posting_date, posting_time) < timestamp(%(posting_date)s, %(posting_time)s) + and is_cancelled = 0 order by posting_date, posting_time asc """, args, as_dict=1) for d in data: From deb6b38fab47339098c903b253a5e478a2a63b65 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Thu, 21 Oct 2021 17:17:11 +0530 Subject: [PATCH 45/67] refactor: replaced db.sql with qb --- erpnext/stock/utils.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index b131125c67..518bdf11e3 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -115,14 +115,25 @@ def get_stock_balance(item_code, warehouse, posting_date=None, posting_time=None return last_entry.qty_after_transaction if last_entry else 0.0 def get_serial_nos_data_after_transactions(args): + from pypika import CustomFunction + serial_nos = [] - data = frappe.db.sql(""" SELECT serial_no, actual_qty - FROM `tabStock Ledger Entry` - WHERE - item_code = %(item_code)s and warehouse = %(warehouse)s - and timestamp(posting_date, posting_time) < timestamp(%(posting_date)s, %(posting_time)s) - and is_cancelled = 0 - order by posting_date, posting_time asc """, args, as_dict=1) + args = frappe._dict(args) + sle = frappe.qb.DocType('Stock Ledger Entry') + Timestamp = CustomFunction('timestamp', ['date', 'time']) + + data = frappe.qb.from_( + sle + ).select( + 'serial_no','actual_qty' + ).where( + (sle.item_code == args.item_code) + & (sle.warehouse == args.warehouse) + & (Timestamp(sle.posting_date, sle.posting_time) < Timestamp(args.posting_date, args.posting_time)) + & (sle.is_cancelled == 0) + ).orderby( + sle.posting_date, sle.posting_time + ).run(as_dict=1) for d in data: if d.actual_qty > 0: From 2aa019ae4c148a01ed57b8cffb461cb90748791a Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 29 Oct 2021 14:32:13 +0530 Subject: [PATCH 46/67] fix: fetch serial nos from ledger unconditionally --- erpnext/stock/utils.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index 518bdf11e3..463b314291 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -101,11 +101,7 @@ def get_stock_balance(item_code, warehouse, posting_date=None, posting_time=None if with_valuation_rate: if with_serial_no: - serial_nos = last_entry.get("serial_no") - - if (serial_nos and - len(get_serial_nos_data(serial_nos)) <= last_entry.qty_after_transaction): - serial_nos = get_serial_nos_data_after_transactions(args) + serial_nos = get_serial_nos_data_after_transactions(args) return ((last_entry.qty_after_transaction, last_entry.valuation_rate, serial_nos) if last_entry else (0.0, 0.0, 0.0)) From f4b60a48f5bb8b8b9e8446aa0ab441f84b321e55 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 29 Oct 2021 14:56:54 +0530 Subject: [PATCH 47/67] refactor: simplify sr no fetching --- erpnext/stock/utils.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index 463b314291..38ca25ee54 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -113,12 +113,12 @@ def get_stock_balance(item_code, warehouse, posting_date=None, posting_time=None def get_serial_nos_data_after_transactions(args): from pypika import CustomFunction - serial_nos = [] + serial_nos = set() args = frappe._dict(args) sle = frappe.qb.DocType('Stock Ledger Entry') Timestamp = CustomFunction('timestamp', ['date', 'time']) - data = frappe.qb.from_( + stock_ledger_entries = frappe.qb.from_( sle ).select( 'serial_no','actual_qty' @@ -131,11 +131,12 @@ def get_serial_nos_data_after_transactions(args): sle.posting_date, sle.posting_time ).run(as_dict=1) - for d in data: - if d.actual_qty > 0: - serial_nos.extend(get_serial_nos_data(d.serial_no)) + for stock_ledger_entry in stock_ledger_entries: + changed_serial_no = get_serial_nos_data(stock_ledger_entry.serial_no) + if stock_ledger_entry.actual_qty > 0: + serial_nos.update(changed_serial_no) else: - serial_nos = list(set(serial_nos) - set(get_serial_nos_data(d.serial_no))) + serial_nos.difference_update(changed_serial_no) return '\n'.join(serial_nos) From ff9cfe0d14849d2103700ce84244c25b22075581 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 29 Oct 2021 16:30:12 +0530 Subject: [PATCH 48/67] fix: sort by creation to break tie --- erpnext/stock/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index 38ca25ee54..e1d5a89082 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -128,7 +128,7 @@ def get_serial_nos_data_after_transactions(args): & (Timestamp(sle.posting_date, sle.posting_time) < Timestamp(args.posting_date, args.posting_time)) & (sle.is_cancelled == 0) ).orderby( - sle.posting_date, sle.posting_time + sle.posting_date, sle.posting_time, sle.creation ).run(as_dict=1) for stock_ledger_entry in stock_ledger_entries: From 15e9b5170d9c17e25d96ba142ca3be3bb7f68156 Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Fri, 29 Oct 2021 18:07:11 +0530 Subject: [PATCH 49/67] fix: Make status filter optional (#28126) --- .../fixed_asset_register/fixed_asset_register.js | 5 ++--- .../fixed_asset_register/fixed_asset_register.py | 11 ++++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js index 75f42a9f78..06989a95da 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js @@ -16,9 +16,8 @@ frappe.query_reports["Fixed Asset Register"] = { fieldname:"status", label: __("Status"), fieldtype: "Select", - options: "In Location\nDisposed", - default: 'In Location', - reqd: 1 + options: "\nIn Location\nDisposed", + default: 'In Location' }, { "fieldname":"filter_based_on", diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py index e370b9d0cb..63685fef46 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py @@ -45,12 +45,13 @@ def get_conditions(filters): if filters.get('cost_center'): conditions["cost_center"] = filters.get('cost_center') - # In Store assets are those that are not sold or scrapped - operand = 'not in' - if status not in 'In Location': - operand = 'in' + if status: + # In Store assets are those that are not sold or scrapped + operand = 'not in' + if status not in 'In Location': + operand = 'in' - conditions['status'] = (operand, ['Sold', 'Scrapped']) + conditions['status'] = (operand, ['Sold', 'Scrapped']) return conditions From 1a6e98ed488e5ed2e55f14b9f1d5166abfecb0f4 Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Fri, 29 Oct 2021 20:52:47 +0530 Subject: [PATCH 50/67] fix(Payment Reconciliation): clear child tables on company/party change (#28008) --- .../payment_reconciliation.js | 132 +++++++++--------- erpnext/accounts/utils.py | 3 +- 2 files changed, 70 insertions(+), 65 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index 412833bd19..ad5a84094e 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -4,9 +4,14 @@ frappe.provide("erpnext.accounts"); erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationController extends frappe.ui.form.Controller { onload() { - var me = this; + const default_company = frappe.defaults.get_default('company'); + this.frm.set_value('company', default_company); - this.frm.set_query("party_type", function() { + this.frm.set_value('party_type', ''); + this.frm.set_value('party', ''); + this.frm.set_value('receivable_payable_account', ''); + + this.frm.set_query("party_type", () => { return { "filters": { "name": ["in", Object.keys(frappe.boot.party_account_types)], @@ -14,44 +19,30 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo } }); - this.frm.set_query('receivable_payable_account', function() { - check_mandatory(me.frm); + this.frm.set_query('receivable_payable_account', () => { return { filters: { - "company": me.frm.doc.company, + "company": this.frm.doc.company, "is_group": 0, - "account_type": frappe.boot.party_account_types[me.frm.doc.party_type] + "account_type": frappe.boot.party_account_types[this.frm.doc.party_type] } }; }); - this.frm.set_query('bank_cash_account', function() { - check_mandatory(me.frm, true); + this.frm.set_query('bank_cash_account', () => { return { filters:[ - ['Account', 'company', '=', me.frm.doc.company], + ['Account', 'company', '=', this.frm.doc.company], ['Account', 'is_group', '=', 0], ['Account', 'account_type', 'in', ['Bank', 'Cash']] ] }; }); - - this.frm.set_value('party_type', ''); - this.frm.set_value('party', ''); - this.frm.set_value('receivable_payable_account', ''); - - var check_mandatory = (frm, only_company=false) => { - var title = __("Mandatory"); - if (only_company && !frm.doc.company) { - frappe.throw({message: __("Please Select a Company First"), title: title}); - } else if (!frm.doc.company || !frm.doc.party_type) { - frappe.throw({message: __("Please Select Both Company and Party Type First"), title: title}); - } - }; } refresh() { this.frm.disable_save(); + this.frm.set_df_property('invoices', 'cannot_delete_rows', true); this.frm.set_df_property('payments', 'cannot_delete_rows', true); this.frm.set_df_property('allocation', 'cannot_delete_rows', true); @@ -85,76 +76,92 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo } company() { - var me = this; + this.frm.set_value('party', ''); this.frm.set_value('receivable_payable_account', ''); - me.frm.clear_table("allocation"); - me.frm.clear_table("invoices"); - me.frm.clear_table("payments"); - me.frm.refresh_fields(); - me.frm.trigger('party'); + } + + party_type() { + this.frm.set_value('party', ''); } party() { - var me = this; - if (!me.frm.doc.receivable_payable_account && me.frm.doc.party_type && me.frm.doc.party) { + this.frm.set_value('receivable_payable_account', ''); + this.frm.trigger("clear_child_tables"); + + if (!this.frm.doc.receivable_payable_account && this.frm.doc.party_type && this.frm.doc.party) { return frappe.call({ method: "erpnext.accounts.party.get_party_account", args: { - company: me.frm.doc.company, - party_type: me.frm.doc.party_type, - party: me.frm.doc.party + company: this.frm.doc.company, + party_type: this.frm.doc.party_type, + party: this.frm.doc.party }, - callback: function(r) { + callback: (r) => { if (!r.exc && r.message) { - me.frm.set_value("receivable_payable_account", r.message); + this.frm.set_value("receivable_payable_account", r.message); } - me.frm.refresh(); + this.frm.refresh(); + } }); } } + receivable_payable_account() { + this.frm.trigger("clear_child_tables"); + this.frm.refresh(); + } + + clear_child_tables() { + this.frm.clear_table("invoices"); + this.frm.clear_table("payments"); + this.frm.clear_table("allocation"); + this.frm.refresh_fields(); + } + get_unreconciled_entries() { - var me = this; + this.frm.clear_table("allocation"); return this.frm.call({ - doc: me.frm.doc, + doc: this.frm.doc, method: 'get_unreconciled_entries', - callback: function(r, rt) { - if (!(me.frm.doc.payments.length || me.frm.doc.invoices.length)) { - frappe.throw({message: __("No invoice and payment records found for this party")}); + callback: () => { + if (!(this.frm.doc.payments.length || this.frm.doc.invoices.length)) { + frappe.throw({message: __("No Unreconciled Invoices and Payments found for this party and account")}); + } else if (!(this.frm.doc.invoices.length)) { + frappe.throw({message: __("No Outstanding Invoices found for this party")}); + } else if (!(this.frm.doc.payments.length)) { + frappe.throw({message: __("No Unreconciled Payments found for this party")}); } - me.frm.refresh(); + this.frm.refresh(); } }); } allocate() { - var me = this; - let payments = me.frm.fields_dict.payments.grid.get_selected_children(); + let payments = this.frm.fields_dict.payments.grid.get_selected_children(); if (!(payments.length)) { - payments = me.frm.doc.payments; + payments = this.frm.doc.payments; } - let invoices = me.frm.fields_dict.invoices.grid.get_selected_children(); + let invoices = this.frm.fields_dict.invoices.grid.get_selected_children(); if (!(invoices.length)) { - invoices = me.frm.doc.invoices; + invoices = this.frm.doc.invoices; } - return me.frm.call({ - doc: me.frm.doc, + return this.frm.call({ + doc: this.frm.doc, method: 'allocate_entries', args: { payments: payments, invoices: invoices }, - callback: function() { - me.frm.refresh(); + callback: () => { + this.frm.refresh(); } }); } reconcile() { - var me = this; - var show_dialog = me.frm.doc.allocation.filter(d => d.difference_amount && !d.difference_account); + var show_dialog = this.frm.doc.allocation.filter(d => d.difference_amount && !d.difference_account); if (show_dialog && show_dialog.length) { @@ -186,10 +193,10 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo label: __("Difference Account"), fieldname: 'difference_account', reqd: 1, - get_query: function() { + get_query: () => { return { filters: { - company: me.frm.doc.company, + company: this.frm.doc.company, is_group: 0 } } @@ -203,7 +210,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo }] }, ], - primary_action: function() { + primary_action: () => { const args = dialog.get_values()["allocation"]; args.forEach(d => { @@ -211,7 +218,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo "difference_account", d.difference_account); }); - me.reconcile_payment_entries(); + this.reconcile_payment_entries(); dialog.hide(); }, primary_action_label: __('Reconcile Entries') @@ -237,15 +244,12 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo } reconcile_payment_entries() { - var me = this; - return this.frm.call({ - doc: me.frm.doc, + doc: this.frm.doc, method: 'reconcile', - callback: function(r, rt) { - me.frm.clear_table("allocation"); - me.frm.refresh_fields(); - me.frm.refresh(); + callback: () => { + this.frm.clear_table("allocation"); + this.frm.refresh(); } }); } diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index fdd8d092eb..fb23d6fc49 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -450,7 +450,8 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False): # new row with references new_row = journal_entry.append("accounts") - new_row.update(jv_detail.as_dict().copy()) + + new_row.update((frappe.copy_doc(jv_detail)).as_dict()) new_row.set(d["dr_or_cr"], d["allocated_amount"]) new_row.set('debit' if d['dr_or_cr'] == 'debit_in_account_currency' else 'credit', From ad5cf467c03b695fe3f12a98ab05367189db969e Mon Sep 17 00:00:00 2001 From: Kenneth Sequeira Date: Sat, 30 Oct 2021 13:07:36 +0530 Subject: [PATCH 51/67] fix: update tax template name for 18% GST --- erpnext/setup/setup_wizard/data/country_wise_tax.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index b7e895db36..8a1338583b 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -1195,7 +1195,7 @@ "*": { "item_tax_templates": [ { - "title": "GST 9%", + "title": "GST 18%", "taxes": [ { "tax_type": { From 541c892f9757f01a4ce7b72e5bc537260e65a3f8 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 30 Oct 2021 18:22:46 +0530 Subject: [PATCH 52/67] fix: Error for missing PAN no field --- .../tax_withholding_category.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index c36f3cb201..6ab0f9618a 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -58,15 +58,24 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None): pan_no = '' parties = [] party_type, party = get_party_details(inv) + has_pan_field = frappe.get_meta(party_type).has_field("pan") if not tax_withholding_category: - tax_withholding_category, pan_no = frappe.db.get_value(party_type, party, ['tax_withholding_category', 'pan']) + if has_pan_field: + fields = ['tax_withholding_category', 'pan'] + else: + fields = ['tax_withholding_category'] + + tax_withholding_details = frappe.db.get_value(party_type, party, fields, as_dict=1) + + tax_withholding_category = tax_withholding_details.get('tax_withholding_category') + pan_no = tax_withholding_details.get('pan') if not tax_withholding_category: return # if tax_withholding_category passed as an argument but not pan_no - if not pan_no: + if not pan_no and has_pan_field: pan_no = frappe.db.get_value(party_type, party, 'pan') # Get others suppliers with the same PAN No From cae29b71d863daf102fd002b7f32592817633026 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sun, 31 Oct 2021 09:24:07 +0530 Subject: [PATCH 53/67] fix: patch update_category_in_ltds_certificate --- erpnext/patches/v13_0/update_category_in_ltds_certificate.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/patches/v13_0/update_category_in_ltds_certificate.py b/erpnext/patches/v13_0/update_category_in_ltds_certificate.py index 4d4645269c..a5f5a23449 100644 --- a/erpnext/patches/v13_0/update_category_in_ltds_certificate.py +++ b/erpnext/patches/v13_0/update_category_in_ltds_certificate.py @@ -6,6 +6,8 @@ def execute(): if not company: return + frappe.reload_doc('regional', 'doctype', 'lower_deduction_certificate') + ldc = frappe.qb.DocType("Lower Deduction Certificate").as_("ldc") supplier = frappe.qb.DocType("Supplier") From 623776dd48325399441175ed04c2fd2a1d3f34c1 Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Mon, 1 Nov 2021 13:37:31 +0530 Subject: [PATCH 54/67] feat: added company field in prospect (#28139) * feat: added company field in prospect * fix: review changes --- erpnext/crm/doctype/prospect/prospect.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/prospect/prospect.json b/erpnext/crm/doctype/prospect/prospect.json index 3d6fba5123..0e872ace04 100644 --- a/erpnext/crm/doctype/prospect/prospect.json +++ b/erpnext/crm/doctype/prospect/prospect.json @@ -20,6 +20,7 @@ "website", "column_break_13", "prospect_owner", + "company", "leads_section", "prospect_lead", "address_and_contact_section", @@ -153,14 +154,23 @@ "fieldname": "address_and_contact_section", "fieldtype": "Section Break", "label": "Address and Contact" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "reqd": 1 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-08-27 16:24:42.961967", + "migration_hash": "f39fb8f4e18a0e7fd391f0b4b52d8375", + "modified": "2021-11-01 13:10:36.759249", "modified_by": "Administrator", "module": "CRM", "name": "Prospect", + "naming_rule": "By fieldname", "owner": "Administrator", "permissions": [ { From a0727b2e824e5299a893197624872122e3d6eb74 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 1 Nov 2021 13:17:40 +0530 Subject: [PATCH 55/67] fix: pass company while fetching valuation rate If company is not supplied and valuation rate is 0, then default company is used for checking if perpetual inventory is enabled or not. This makes little sense as different companies can have different setting for perpetual inventory. --- erpnext/stock/stock_ledger.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index bdbec52f7e..ce82f089d2 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -600,7 +600,7 @@ class update_entries_after(object): if not allow_zero_rate: self.wh_data.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse, sle.voucher_type, sle.voucher_no, self.allow_zero_rate, - currency=erpnext.get_company_currency(sle.company)) + currency=erpnext.get_company_currency(sle.company), company=sle.company) def get_incoming_value_for_serial_nos(self, sle, serial_nos): # get rate from serial nos within same company @@ -667,7 +667,7 @@ class update_entries_after(object): if not allow_zero_valuation_rate: self.wh_data.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse, sle.voucher_type, sle.voucher_no, self.allow_zero_rate, - currency=erpnext.get_company_currency(sle.company)) + currency=erpnext.get_company_currency(sle.company), company=sle.company) def get_fifo_values(self, sle): incoming_rate = flt(sle.incoming_rate) @@ -700,7 +700,7 @@ class update_entries_after(object): if not allow_zero_valuation_rate: _rate = get_valuation_rate(sle.item_code, sle.warehouse, sle.voucher_type, sle.voucher_no, self.allow_zero_rate, - currency=erpnext.get_company_currency(sle.company)) + currency=erpnext.get_company_currency(sle.company), company=sle.company) else: _rate = 0 From f7ffe04a4b0a65b02f20f6ca180a86c1d5a3874f Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 1 Nov 2021 13:21:14 +0530 Subject: [PATCH 56/67] fix: use warehouse to find company --- erpnext/stock/stock_ledger.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index ce82f089d2..70f4bcaef7 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -911,10 +911,11 @@ def get_sle_by_voucher_detail_no(voucher_detail_no, excluded_sle=None): def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no, allow_zero_rate=False, currency=None, company=None, raise_error_if_no_rate=True): - # Get valuation rate from last sle for the same item and warehouse - if not company: - company = erpnext.get_default_company() + if not company: + company = frappe.get_cached_value("Warehouse", warehouse, "company") + + # Get valuation rate from last sle for the same item and warehouse last_valuation_rate = frappe.db.sql("""select valuation_rate from `tabStock Ledger Entry` force index (item_warehouse) where From 1eab3a44f6721dbbc0d8213147a57c0da68acf47 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 1 Nov 2021 14:16:57 +0530 Subject: [PATCH 57/67] fix(ux): stock levels take time after item merge Item merge creates a repost and depending on number of entries it can take from 1 to n hours for it to finish. (depending upon queued up reposts) Added message so users don't feel confused till this operation is finished. --- erpnext/stock/doctype/item/item.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 04e4653d93..fa42c7d934 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -676,6 +676,8 @@ class Item(WebsiteGenerator): def after_rename(self, old_name, new_name, merge): if merge: self.validate_duplicate_item_in_stock_reconciliation(old_name, new_name) + frappe.msgprint(_("It can take upto few hours for accurate stock values to be visible after merging items."), + indicator="orange", title="Note") if self.route: invalidate_cache_for_item(self) From 27cbeb920e90f976145e8ca5c696dbd49bd7ec0d Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 26 Sep 2021 14:10:52 +0530 Subject: [PATCH 58/67] test(patch): run patch tests for major releases Run patch tests one at a time v10 db -> v12-> v13 -> .... -> frappe:corresponding base branch and PR. --- .github/workflows/patch.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml index 92a19621d1..f8abb6c774 100644 --- a/.github/workflows/patch.yml +++ b/.github/workflows/patch.yml @@ -86,4 +86,27 @@ jobs: cd ~/frappe-bench/ wget https://erpnext.com/files/v10-erpnext.sql.gz bench --site test_site --force restore ~/frappe-bench/v10-erpnext.sql.gz + + git -C "apps/frappe" remote set-url upstream https://github.com/frappe/frappe.git + git -C "apps/erpnext" remote set-url upstream https://github.com/frappe/erpnext.git + + for version in $(seq 12 13) + do + echo "Updating to v$version" + branch_name="version-$version" + + git -C "apps/frappe" fetch --depth 1 upstream $branch_name:$branch_name + git -C "apps/erpnext" fetch --depth 1 upstream $branch_name:$branch_name + + git -C "apps/frappe" checkout -q -f $branch_name + git -C "apps/erpnext" checkout -q -f $branch_name + + bench setup requirements --python + bench --site test_site migrate + done + + + echo "Updating to latest version" + git -C "apps/frappe" checkout -q -f "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}" + git -C "apps/erpnext" checkout -q -f "$GITHUB_SHA" bench --site test_site migrate From 048210a8f67fe0544216361d7342619581318570 Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 2 Nov 2021 15:49:58 +0530 Subject: [PATCH 59/67] fix: Remove warehouse filter on Batch field for Material Receipt --- erpnext/stock/doctype/stock_entry/stock_entry.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index ac8303eda3..80203a9ef4 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -88,7 +88,9 @@ frappe.ui.form.on('Stock Entry', { } } - filters["warehouse"] = item.s_warehouse || item.t_warehouse; + if (frm.doc.purpose != "Material Receipt") { + filters["warehouse"] = item.s_warehouse || item.t_warehouse; + } return { query : "erpnext.controllers.queries.get_batch_no", From 48886ee70524132d1f4cf6751b56ccbc96d765a5 Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 2 Nov 2021 15:57:41 +0530 Subject: [PATCH 60/67] chore: Add comment above fix for future reference --- erpnext/stock/doctype/stock_entry/stock_entry.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 80203a9ef4..c4b8131305 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -88,6 +88,8 @@ frappe.ui.form.on('Stock Entry', { } } + // User could want to select a manually created empty batch (no warehouse) + // or a pre-existing batch if (frm.doc.purpose != "Material Receipt") { filters["warehouse"] = item.s_warehouse || item.t_warehouse; } From d72709dd817b879fa3bac2387689e0a9d4728e8f Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Tue, 2 Nov 2021 16:28:46 +0530 Subject: [PATCH 61/67] fix(Payment Entry): splitting outstanding rows as per payment terms (#27946) --- .../doctype/payment_entry/payment_entry.py | 44 +++++++++++-------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 9b4a91d4e9..8bbe3db914 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -389,7 +389,7 @@ class PaymentEntry(AccountsController): invoice_paid_amount_map[invoice_key]['outstanding'] = term.outstanding invoice_paid_amount_map[invoice_key]['discounted_amt'] = ref.total_amount * (term.discount / 100) - for key, allocated_amount in iteritems(invoice_payment_amount_map): + for idx, (key, allocated_amount) in enumerate(iteritems(invoice_payment_amount_map), 1): if not invoice_paid_amount_map.get(key): frappe.throw(_('Payment term {0} not used in {1}').format(key[0], key[1])) @@ -407,7 +407,7 @@ class PaymentEntry(AccountsController): (allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0])) else: if allocated_amount > outstanding: - frappe.throw(_('Cannot allocate more than {0} against payment term {1}').format(outstanding, key[0])) + frappe.throw(_('Row #{0}: Cannot allocate more than {1} against payment term {2}').format(idx, outstanding, key[0])) if allocated_amount and outstanding: frappe.db.sql(""" @@ -1053,12 +1053,6 @@ def get_outstanding_reference_documents(args): party_account_currency = get_account_currency(args.get("party_account")) company_currency = frappe.get_cached_value('Company', args.get("company"), "default_currency") - # Get negative outstanding sales /purchase invoices - negative_outstanding_invoices = [] - if args.get("party_type") not in ["Student", "Employee"] and not args.get("voucher_no"): - negative_outstanding_invoices = get_negative_outstanding_invoices(args.get("party_type"), args.get("party"), - args.get("party_account"), args.get("company"), party_account_currency, company_currency) - # Get positive outstanding sales /purchase invoices/ Fees condition = "" if args.get("voucher_type") and args.get("voucher_no"): @@ -1105,6 +1099,12 @@ def get_outstanding_reference_documents(args): orders_to_be_billed = get_orders_to_be_billed(args.get("posting_date"),args.get("party_type"), args.get("party"), args.get("company"), party_account_currency, company_currency, filters=args) + # Get negative outstanding sales /purchase invoices + negative_outstanding_invoices = [] + if args.get("party_type") not in ["Student", "Employee"] and not args.get("voucher_no"): + negative_outstanding_invoices = get_negative_outstanding_invoices(args.get("party_type"), args.get("party"), + args.get("party_account"), party_account_currency, company_currency, condition=condition) + data = negative_outstanding_invoices + outstanding_invoices + orders_to_be_billed if not data: @@ -1137,22 +1137,26 @@ def split_invoices_based_on_payment_terms(outstanding_invoices): 'invoice_amount': flt(d.invoice_amount), 'outstanding_amount': flt(d.outstanding_amount), 'payment_amount': payment_term.payment_amount, - 'payment_term': payment_term.payment_term, - 'allocated_amount': payment_term.outstanding + 'payment_term': payment_term.payment_term })) + outstanding_invoices_after_split = [] if invoice_ref_based_on_payment_terms: for idx, ref in invoice_ref_based_on_payment_terms.items(): - voucher_no = outstanding_invoices[idx]['voucher_no'] - voucher_type = outstanding_invoices[idx]['voucher_type'] + voucher_no = ref[0]['voucher_no'] + voucher_type = ref[0]['voucher_type'] - frappe.msgprint(_("Spliting {} {} into {} rows as per payment terms").format( + frappe.msgprint(_("Spliting {} {} into {} row(s) as per Payment Terms").format( voucher_type, voucher_no, len(ref)), alert=True) - outstanding_invoices.pop(idx - 1) - outstanding_invoices += invoice_ref_based_on_payment_terms[idx] + outstanding_invoices_after_split += invoice_ref_based_on_payment_terms[idx] - return outstanding_invoices + existing_row = list(filter(lambda x: x.get('voucher_no') == voucher_no, outstanding_invoices)) + index = outstanding_invoices.index(existing_row[0]) + outstanding_invoices.pop(index) + + outstanding_invoices_after_split += outstanding_invoices + return outstanding_invoices_after_split def get_orders_to_be_billed(posting_date, party_type, party, company, party_account_currency, company_currency, cost_center=None, filters=None): @@ -1219,7 +1223,7 @@ def get_orders_to_be_billed(posting_date, party_type, party, return order_list def get_negative_outstanding_invoices(party_type, party, party_account, - company, party_account_currency, company_currency, cost_center=None): + party_account_currency, company_currency, cost_center=None, condition=None): voucher_type = "Sales Invoice" if party_type == "Customer" else "Purchase Invoice" supplier_condition = "" if voucher_type == "Purchase Invoice": @@ -1241,19 +1245,21 @@ def get_negative_outstanding_invoices(party_type, party, party_account, `tab{voucher_type}` where {party_type} = %s and {party_account} = %s and docstatus = 1 and - company = %s and outstanding_amount < 0 + outstanding_amount < 0 {supplier_condition} + {condition} order by posting_date, name """.format(**{ "supplier_condition": supplier_condition, + "condition": condition, "rounded_total_field": rounded_total_field, "grand_total_field": grand_total_field, "voucher_type": voucher_type, "party_type": scrub(party_type), "party_account": "debit_to" if party_type == "Customer" else "credit_to", "cost_center": cost_center - }), (party, party_account, company), as_dict=True) + }), (party, party_account), as_dict=True) @frappe.whitelist() From f047c6ffc80775789be50707d9a1c0320c87a4fe Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 2 Nov 2021 16:43:31 +0530 Subject: [PATCH 62/67] fix: Test for WDV --- erpnext/assets/doctype/asset/test_asset.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 81c679f851..170122527a 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -563,10 +563,16 @@ class TestDepreciationMethods(AssetSetup): company_flag = frappe.flags.company frappe.flags.company = "_Test Company" + finance_book = frappe.new_doc("Finance Book") + finance_book.finance_book_name = "Income Tax" + finance_book.for_income_tax = 1 + finance_book.insert(ignore_if_duplicate = True) + asset = create_asset( calculate_depreciation = 1, available_for_use_date = "2030-07-12", purchase_date = "2030-01-01", + finance_book = finance_book.name, depreciation_method = "Written Down Value", expected_value_after_useful_life = 12500, depreciation_start_date = "2030-12-31", From 890a7868584db345028420c4148e41dc0f33cbe3 Mon Sep 17 00:00:00 2001 From: hrwx Date: Mon, 1 Nov 2021 14:16:51 +0000 Subject: [PATCH 63/67] fix: do not generate multiple invoices --- .../doctype/subscription/subscription.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index de9550233f..b75530c4b0 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -502,9 +502,11 @@ class Subscription(Document): # Check invoice dates and make sure it doesn't have outstanding invoices return getdate() >= getdate(self.current_invoice_start) - def is_current_invoice_generated(self): + def is_current_invoice_generated(self, _current_start_date=None, _current_end_date=None): invoice = self.get_current_invoice() - _current_start_date, _current_end_date = self.update_subscription_period(date=add_days(self.current_invoice_end, 1), return_date=True) + + if not (_current_start_date and _current_end_date): + _current_start_date, _current_end_date = self.update_subscription_period(date=add_days(self.current_invoice_end, 1), return_date=True) if invoice and getdate(_current_start_date) <= getdate(invoice.posting_date) <= getdate(_current_end_date): return True @@ -523,7 +525,9 @@ class Subscription(Document): if getdate() > getdate(self.current_invoice_end) and self.is_prepaid_to_invoice(): self.update_subscription_period(add_days(self.current_invoice_end, 1)) - if not self.is_current_invoice_generated() and (self.is_postpaid_to_invoice() or self.is_prepaid_to_invoice()): + if not self.is_current_invoice_generated(self.current_invoice_start, self.current_invoice_end) \ + and (self.is_postpaid_to_invoice() or self.is_prepaid_to_invoice()): + prorate = frappe.db.get_single_value('Subscription Settings', 'prorate') self.generate_invoice(prorate) @@ -560,8 +564,10 @@ class Subscription(Document): self.set_status_grace_period() # Generate invoices periodically even if current invoice are unpaid - if self.generate_new_invoices_past_due_date and not self.is_current_invoice_generated() and (self.is_postpaid_to_invoice() - or self.is_prepaid_to_invoice()): + if self.generate_new_invoices_past_due_date and not \ + self.is_current_invoice_generated(self.current_invoice_start, self.current_invoice_end) \ + and (self.is_postpaid_to_invoice() or self.is_prepaid_to_invoice()): + prorate = frappe.db.get_single_value('Subscription Settings', 'prorate') self.generate_invoice(prorate) From 7681600b5e6e2421dfdfee39661aaf87d8327e3b Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 2 Nov 2021 17:47:44 +0530 Subject: [PATCH 64/67] fix: test wdv method for indian region --- erpnext/assets/doctype/asset/test_asset.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 170122527a..f162d9f39d 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -1030,6 +1030,7 @@ def create_asset(**args): if asset.calculate_depreciation: asset.append("finance_books", { + "finance_book": args.finance_book, "depreciation_method": args.depreciation_method or "Straight Line", "frequency_of_depreciation": args.frequency_of_depreciation or 12, "total_number_of_depreciations": args.total_number_of_depreciations or 5, From 857d87da97d230367e1e5ac1e3fe8baa13b8130a Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Tue, 2 Nov 2021 17:52:45 +0530 Subject: [PATCH 65/67] fix: replaced "=" with "in" for multiple statuses in query #28193 fix: replaced "=" with "in" for multiple statuses in query --- .../purchase_order_analysis/purchase_order_analysis.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py index 1b25dd45d2..a566d56811 100644 --- a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py +++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py @@ -41,10 +41,13 @@ def get_conditions(filters): if filters.get("from_date") and filters.get("to_date"): conditions += " and po.transaction_date between %(from_date)s and %(to_date)s" - for field in ['company', 'name', 'status']: + for field in ['company', 'name']: if filters.get(field): conditions += f" and po.{field} = %({field})s" + if filters.get('status'): + conditions += " and po.status in %(status)s" + if filters.get('project'): conditions += " and poi.project = %(project)s" From 734b57deec069fc3f573604c60a3157da81eff45 Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 2 Nov 2021 18:34:55 +0530 Subject: [PATCH 66/67] fix: Serial Nos not set in the row after scanning in popup - Avoid whitspaces while calculating length of serial nos --- erpnext/selling/sales_common.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index ddd4c4e6a5..a86e60494e 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -206,8 +206,10 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran var me = this; var item = frappe.get_doc(cdt, cdn); - if (item.serial_no && item.qty === item.serial_no.split(`\n`).length) { - return; + // check if serial nos entered are as much as qty in row + if (item.serial_no) { + let serial_nos = item.serial_no.split(`\n`).filter(sn => sn.trim()); // filter out whitespaces + if (item.qty === serial_nos.length) return; } if (item.serial_no && !item.batch_no) { From 66348e1a035d98e91cb7228e9bd2b7286c5cc274 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 2 Nov 2021 20:26:40 +0530 Subject: [PATCH 67/67] fix: Error on LDC creation --- .../lower_deduction_certificate/lower_deduction_certificate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py index 7afbc00980..821e0171bb 100644 --- a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py +++ b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py @@ -31,7 +31,7 @@ class LowerDeductionCertificate(Document): <= fiscal_year.year_end_date): frappe.throw(_("Valid Upto date not in Fiscal Year {0}").format(frappe.bold(self.fiscal_year))) - def tax_withholding_category(self): + def validate_supplier_against_tax_category(self): duplicate_certificate = frappe.db.get_value('Lower Deduction Certificate', {'supplier': self.supplier, 'tax_withholding_category': self.tax_withholding_category, 'name': ("!=", self.name)}, ['name', 'valid_from', 'valid_upto'], as_dict=True)