From 7392f9c6624db295dda6060c52a269cad7c8bc5b Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 22 Nov 2023 04:16:31 +0000 Subject: [PATCH 1/8] chore(release): Bumped to Version 14.49.0 # [14.49.0](https://github.com/frappe/erpnext/compare/v14.48.1...v14.49.0) (2023-11-22) ### Bug Fixes * add revaluation journal filter in Payable report ([d0698b3](https://github.com/frappe/erpnext/commit/d0698b32bbde5ac4d4e0e33d3c8ca2f10f0d7cc3)) * attributes were mandatory for manufacturers ([430980a](https://github.com/frappe/erpnext/commit/430980a836611e0d2a738aab1e4a3b73d47481c9)) * duplicate field in `Closing Stock Balance` ([#38105](https://github.com/frappe/erpnext/issues/38105)) ([1f16c47](https://github.com/frappe/erpnext/commit/1f16c47a2c4562bf5c2e7d7b4bafe7604efbb36a)) * incorrect incoming rate for serial and batch items in standalone debit note ([#38121](https://github.com/frappe/erpnext/issues/38121)) ([9a34518](https://github.com/frappe/erpnext/commit/9a34518e543692201c1166c154f91976e6ec989a)) * pass check permission in render_address ([1ccd5e4](https://github.com/frappe/erpnext/commit/1ccd5e4ff55f24aed69484a19cf86bc4304a127d)) * payment entry rounding error ([49735bc](https://github.com/frappe/erpnext/commit/49735bc1204ff3ca349f8dff168666a6cbe7b14d)) * remove ESS role when not mapped to employee (backport [#37867](https://github.com/frappe/erpnext/issues/37867)) ([#38132](https://github.com/frappe/erpnext/issues/38132)) ([bc01007](https://github.com/frappe/erpnext/commit/bc01007c160ee4f0bb987fab77630c441f38a08b)) * round `unreconciled_amount` before asserting ([392ee2e](https://github.com/frappe/erpnext/commit/392ee2e0fdad345429b11f6c126cd13cc244e83b)) * set asset's valuation_rate according to asset quantity (backport [#38254](https://github.com/frappe/erpnext/issues/38254)) ([#38255](https://github.com/frappe/erpnext/issues/38255)) ([00def82](https://github.com/frappe/erpnext/commit/00def82843e09a4683ab3ef2308665919071e9c8)) * set default asset quantity as 1 [v14] ([#38224](https://github.com/frappe/erpnext/issues/38224)) ([3daf6f8](https://github.com/frappe/erpnext/commit/3daf6f822a8c45779eeffc799c7eb2a05b25d7cf)) * show party values when naming by is not naming series ([dd76695](https://github.com/frappe/erpnext/commit/dd76695d3a263f75ae39808a62f14f32afab6f34)) * Supplier Quotation fields ([#37963](https://github.com/frappe/erpnext/issues/37963)) ([883eaee](https://github.com/frappe/erpnext/commit/883eaee0143ae22998a86ab2b9e011b398a677c3)) * test case for rounded total with cash disc ([9b5185a](https://github.com/frappe/erpnext/commit/9b5185adc930d59e9c58246f7c51a403e32c6062)) * **Timesheet:** reset billing hours equal to hours if they exceed actual hours (backport [#38134](https://github.com/frappe/erpnext/issues/38134)) ([#38152](https://github.com/frappe/erpnext/issues/38152)) ([c7c751e](https://github.com/frappe/erpnext/commit/c7c751ecd0d16c568c26ab473df6167f84a9a2f3)) * **Timesheet:** warn user if billing hours > actual hours instead of resetting (backport [#38239](https://github.com/frappe/erpnext/issues/38239)) ([#38240](https://github.com/frappe/erpnext/issues/38240)) ([e08f114](https://github.com/frappe/erpnext/commit/e08f1145c9e220d75720834b28e6351375b2592a)) * update modified timestamp ([2849e0d](https://github.com/frappe/erpnext/commit/2849e0daed1f7690d1c30abf4644736decc9711d)) * valuation rate in report Item Prices ([#38161](https://github.com/frappe/erpnext/issues/38161)) ([a70696e](https://github.com/frappe/erpnext/commit/a70696ea777258cfcf41ccddae9dd0ddba98ce8e)) * wrong round off and rounded total ([296433a](https://github.com/frappe/erpnext/commit/296433a1ddeaa8874d45c05b6c8f649503eeb3cc)) ### Features * add `Supplier Delivery Note` field in SCR (backport [#38127](https://github.com/frappe/erpnext/issues/38127)) ([#38155](https://github.com/frappe/erpnext/issues/38155)) ([8d4a19c](https://github.com/frappe/erpnext/commit/8d4a19cecfd992cf5522c7b976c26b556db2e47b)) * Add accounting dimensions to Supplier Quotation ([51e33e1](https://github.com/frappe/erpnext/commit/51e33e1556302a4f5f006ceac02303c8e18dea6e)) * virtual parent doctype ([8dbf2ce](https://github.com/frappe/erpnext/commit/8dbf2ced797ff8fb9a9ed5ffbbbbdcb4968ef4e7)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 34711be4002..8a1f277003d 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -3,7 +3,7 @@ import inspect import frappe -__version__ = "14.48.1" +__version__ = "14.49.0" def get_default_company(user=None): From 436ba6f244a62d0486a534585814192182506180 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Fri, 24 Nov 2023 08:47:31 +0000 Subject: [PATCH 2/8] chore(release): Bumped to Version 14.50.0 # [14.50.0](https://github.com/frappe/erpnext/compare/v14.49.0...v14.50.0) (2023-11-24) ### Bug Fixes * don't depreciate non-depreciable assets on scrapping [v14] ([#38294](https://github.com/frappe/erpnext/issues/38294)) ([5261aba](https://github.com/frappe/erpnext/commit/5261aba81fb72154c14d96ce976e24a60cf948c2)) * partial cancel of gle and ple (backport [#35609](https://github.com/frappe/erpnext/issues/35609)) ([80bc235](https://github.com/frappe/erpnext/commit/80bc235d6e6064756031ea7cf320b001e773e89f)) * patch - Duplicate entry quality inspection parameter (backport [#38262](https://github.com/frappe/erpnext/issues/38262)) ([#38263](https://github.com/frappe/erpnext/issues/38263)) ([756f08b](https://github.com/frappe/erpnext/commit/756f08b9bad7631d8d25172b73a0a2f8d182c036)) * Supplier `Primary Contact` (backport [#38268](https://github.com/frappe/erpnext/issues/38268)) ([#38285](https://github.com/frappe/erpnext/issues/38285)) ([f170cb9](https://github.com/frappe/erpnext/commit/f170cb921453bf7e2b59198f60a81e442df1fae2)) ### Features * add Bank Transaction to connections in Journal and Payment Entry ([#38297](https://github.com/frappe/erpnext/issues/38297)) ([06c6da5](https://github.com/frappe/erpnext/commit/06c6da5c0dc6bc9c1ba2375e93738fbb84af66e7)) ### Performance Improvements * optimize total_purchase_cost update ([a88d322](https://github.com/frappe/erpnext/commit/a88d322e4f394382984492ba263fb73e3513672b)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 8a1f277003d..f82bc47106b 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -3,7 +3,7 @@ import inspect import frappe -__version__ = "14.49.0" +__version__ = "14.50.0" def get_default_company(user=None): From 47d37aa628981f0cb14ed0c54a7a58b7178d78cc Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Tue, 28 Nov 2023 16:24:55 +0000 Subject: [PATCH 3/8] chore(release): Bumped to Version 14.51.0 # [14.51.0](https://github.com/frappe/erpnext/compare/v14.50.0...v14.51.0) (2023-11-28) ### Bug Fixes * Alert message and make sure invoice due dates are different for effective test ([0813f02](https://github.com/frappe/erpnext/commit/0813f02ca5a9da156d02f8ece054d93cf415e554)) * allow on submit for child table fields ([d72fbe9](https://github.com/frappe/erpnext/commit/d72fbe912577ff33287cdb27e7953cd3b6466ca2)) * annual income and expenses in digest ([7dce68b](https://github.com/frappe/erpnext/commit/7dce68bdccb73e9e0b5e0d92abeba9ce7b474b61)) * check reposting settings before allowing editable si ([f777d24](https://github.com/frappe/erpnext/commit/f777d24679927355da164f7599fe526effa9e759)) * condition in other bundle utils ([e96ef73](https://github.com/frappe/erpnext/commit/e96ef7338ed4fb3eacef1da702d8211ecb7c9e4b)) * create contact if existing customer doesn't have contact ([bb2aa90](https://github.com/frappe/erpnext/commit/bb2aa904b28a9220899bdbe4a9b3d43612db9092)) * do not set repost flag without validating voucher ([3ad6d08](https://github.com/frappe/erpnext/commit/3ad6d0890ae872aaad4c5f9a09763f2ba1538c91)) * filter bundle items based on disabled check ([8a3a068](https://github.com/frappe/erpnext/commit/8a3a0688ba1a3c38837e2f1ec4a822b1139b14f3)) * fiscal year using future date ([b0aa4ef](https://github.com/frappe/erpnext/commit/b0aa4efd3da478fb6940687406c744a0698dd0b9)) * has_product_bundle util to only check for enabled bundles ([fd19ac5](https://github.com/frappe/erpnext/commit/fd19ac55e3e5f3a9a2b9c8d212ac39dd29986b1b)) * job card overlap validation (backport [#38345](https://github.com/frappe/erpnext/issues/38345)) ([#38347](https://github.com/frappe/erpnext/issues/38347)) ([9cf8955](https://github.com/frappe/erpnext/commit/9cf8955d24bc22fc91c6e5a29df358e8ee40202e)) * Merge conflicts ([e7183e3](https://github.com/frappe/erpnext/commit/e7183e3ea935638a7737dc20f48e1aa7624936cb)) * Negative Qty and Rates in SO/PO ([#38253](https://github.com/frappe/erpnext/issues/38253)) ([50f3b01](https://github.com/frappe/erpnext/commit/50f3b01eb70962b995bb541c7751140c78a562ee)) * Payment Reco Issue and chart of account importer ([730a3bb](https://github.com/frappe/erpnext/commit/730a3bb7cec09781897d545fc6d777fdd51a2cf9)) * Re-add no.of rows split in alert message ([fb19e7f](https://github.com/frappe/erpnext/commit/fb19e7f15eb22bab4a778fd0dc922733b0dd00ae)) * **regional:** item wise tax calc issue ([0448d56](https://github.com/frappe/erpnext/commit/0448d56a4f06a5dea426d1038edbc82aa8a141ed)) * Server Error while creating Product Bundle (backport [#38377](https://github.com/frappe/erpnext/issues/38377)) ([#38379](https://github.com/frappe/erpnext/issues/38379)) ([ca6a788](https://github.com/frappe/erpnext/commit/ca6a78882c2146734c214510a669e11451edb5d4)) * skip disabled bundles for non-report utils ([445e9c9](https://github.com/frappe/erpnext/commit/445e9c90413db00d1edf22c3edd1616e8a380c92)) * skip fixed assets in parent ([5dcd806](https://github.com/frappe/erpnext/commit/5dcd8061761b95c1e01bdaff5d019a2ab66615c4)) * stock availability not showing ([#38382](https://github.com/frappe/erpnext/issues/38382)) ([3929cc3](https://github.com/frappe/erpnext/commit/3929cc30cab5e3840e46ef1673df72eca95320ab)) * validation for existing bundles ([ae960e7](https://github.com/frappe/erpnext/commit/ae960e735aa76bd3f76c65450576b0d72e609355)) ### Features * add disabled field in product bundle ([8d5efc9](https://github.com/frappe/erpnext/commit/8d5efc901d1f454348efba7c94cc167362d0a16f)) * new Report "Lost Quotations" ([#38309](https://github.com/frappe/erpnext/issues/38309)) ([1773e31](https://github.com/frappe/erpnext/commit/1773e31499724602cf69b2976a1d0023fab6f202)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index f82bc47106b..bfef4c35484 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -3,7 +3,7 @@ import inspect import frappe -__version__ = "14.50.0" +__version__ = "14.51.0" def get_default_company(user=None): From 53d083a1210436135de3973a87c7b3d88b781f17 Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Wed, 29 Nov 2023 06:15:22 +0530 Subject: [PATCH 4/8] feat: shift depreciation for assets [v14] [backport of #38327] (#38404) feat: shift depreciation for assets (cherry picked from commit d8c3935e64f240b938f90558554ef57d6b9f46f0) --- erpnext/assets/doctype/asset/asset.js | 11 +- erpnext/assets/doctype/asset/asset.py | 96 +++-- erpnext/assets/doctype/asset/test_asset.py | 1 + .../asset_finance_book.json | 10 +- .../asset_shift_allocation/__init__.py | 0 .../asset_shift_allocation.js | 15 + .../asset_shift_allocation.json | 115 +++++ .../asset_shift_allocation.py | 247 +++++++++++ .../test_asset_shift_allocation.py | 112 +++++ .../doctype/asset_shift_factor/__init__.py | 0 .../asset_shift_factor/asset_shift_factor.js | 8 + .../asset_shift_factor.json | 76 ++++ .../asset_shift_factor/asset_shift_factor.py | 24 ++ .../test_asset_shift_factor.py | 9 + .../depreciation_schedule.json | 407 +++++------------- 15 files changed, 796 insertions(+), 335 deletions(-) create mode 100644 erpnext/assets/doctype/asset_shift_allocation/__init__.py create mode 100644 erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.js create mode 100644 erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.json create mode 100644 erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.py create mode 100644 erpnext/assets/doctype/asset_shift_allocation/test_asset_shift_allocation.py create mode 100644 erpnext/assets/doctype/asset_shift_factor/__init__.py create mode 100644 erpnext/assets/doctype/asset_shift_factor/asset_shift_factor.js create mode 100644 erpnext/assets/doctype/asset_shift_factor/asset_shift_factor.json create mode 100644 erpnext/assets/doctype/asset_shift_factor/asset_shift_factor.py create mode 100644 erpnext/assets/doctype/asset_shift_factor/test_asset_shift_factor.py diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 951e612bea0..afe532d345d 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -323,12 +323,15 @@ frappe.ui.form.on('Asset', { make_schedules_editable: function(frm) { if (frm.doc.finance_books) { - var is_editable = frm.doc.finance_books.filter(d => d.depreciation_method == "Manual").length > 0 + var is_manual_hence_editable = frm.doc.finance_books.filter(d => d.depreciation_method == "Manual").length > 0 + ? true : false; + var is_shift_hence_editable = frm.doc.finance_books.filter(d => d.shift_based).length > 0 ? true : false; - frm.toggle_enable("schedules", is_editable); - frm.fields_dict["schedules"].grid.toggle_enable("schedule_date", is_editable); - frm.fields_dict["schedules"].grid.toggle_enable("depreciation_amount", is_editable); + frm.toggle_enable("depreciation_schedule", is_manual_hence_editable || is_shift_hence_editable); + frm.fields_dict["depreciation_schedule"].grid.toggle_enable("schedule_date", is_manual_hence_editable); + frm.fields_dict["depreciation_schedule"].grid.toggle_enable("depreciation_amount", is_manual_hence_editable); + frm.fields_dict["depreciation_schedule"].grid.toggle_enable("shift", is_shift_hence_editable); } }, diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 9708c77f824..e9c1e14b94c 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -43,6 +43,7 @@ class Asset(AccountsController): self.validate_finance_books() if not self.split_from: self.prepare_depreciation_data() + self.update_shift_depr_schedule() self.validate_gross_and_purchase_amount() if self.get("schedules"): self.validate_expected_value_after_useful_life() @@ -104,6 +105,13 @@ class Asset(AccountsController): self.opening_accumulated_depreciation ) + def update_shift_depr_schedule(self): + if not any(fb.get("shift_based") for fb in self.finance_books) or self.docstatus != 0: + return + + self.make_depreciation_schedule() + self.set_accumulated_depreciation() + def should_prepare_depreciation_schedule(self): if not self.get("schedules"): return True @@ -318,7 +326,7 @@ class Asset(AccountsController): self.get_depreciation_rate(d, on_validate=True), d.precision("rate_of_depreciation") ) - def make_depreciation_schedule(self, date_of_disposal, value_after_depreciation=None): + def make_depreciation_schedule(self, date_of_disposal=None, value_after_depreciation=None): if not self.get("schedules"): self.schedules = [] @@ -410,13 +418,7 @@ class Asset(AccountsController): ) if depreciation_amount > 0: - self._add_depreciation_row( - date_of_disposal, - depreciation_amount, - finance_book.depreciation_method, - finance_book.finance_book, - finance_book.idx, - ) + self._add_depreciation_row(date_of_disposal, depreciation_amount, finance_book, n) break @@ -498,25 +500,27 @@ class Asset(AccountsController): skip_row = True if flt(depreciation_amount, self.precision("gross_purchase_amount")) > 0: - self._add_depreciation_row( - schedule_date, - depreciation_amount, - finance_book.depreciation_method, - finance_book.finance_book, - finance_book.idx, - ) + self._add_depreciation_row(schedule_date, depreciation_amount, finance_book, n) + + def _add_depreciation_row(self, schedule_date, depreciation_amount, finance_book, schedule_idx): + if finance_book.shift_based: + shift = ( + self.schedules_before_clearing[schedule_idx].shift + if self.schedules_before_clearing and len(self.schedules_before_clearing) > schedule_idx + else frappe.get_cached_value("Asset Shift Factor", {"default": 1}, "shift_name") + ) + else: + shift = None - def _add_depreciation_row( - self, schedule_date, depreciation_amount, depreciation_method, finance_book, finance_book_id - ): self.append( "schedules", { "schedule_date": schedule_date, "depreciation_amount": depreciation_amount, - "depreciation_method": depreciation_method, - "finance_book": finance_book, - "finance_book_id": finance_book_id, + "depreciation_method": finance_book.depreciation_method, + "finance_book": finance_book.finance_book, + "finance_book_id": finance_book.idx, + "shift": shift, }, ) @@ -545,6 +549,8 @@ class Asset(AccountsController): num_of_depreciations_completed = 0 depr_schedule = [] + self.schedules_before_clearing = self.get("schedules") + for schedule in self.get("schedules"): # to update start when there are JEs linked with all the schedule rows corresponding to an FB if len(start) == (int(schedule.finance_book_id) - 2): @@ -752,10 +758,12 @@ class Asset(AccountsController): 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"), - ) + + if not book.shift_based: + depreciation_amount += flt( + value_after_depreciation - flt(book.expected_value_after_useful_life), + d.precision("depreciation_amount"), + ) d.depreciation_amount = depreciation_amount accumulated_depreciation += d.depreciation_amount @@ -1217,6 +1225,7 @@ def get_item_details(item_code, asset_category, gross_purchase_amount): "total_number_of_depreciations": d.total_number_of_depreciations, "frequency_of_depreciation": d.frequency_of_depreciation, "daily_prorata_based": d.daily_prorata_based, + "shift_based": d.shift_based, "salvage_value_percentage": d.salvage_value_percentage, "expected_value_after_useful_life": flt(gross_purchase_amount) * flt(d.salvage_value_percentage / 100), @@ -1385,6 +1394,9 @@ def get_updated_rate_of_depreciation_for_wdv_and_dd(asset, depreciable_value, fb def get_straight_line_or_manual_depr_amount( asset, row, schedule_idx, number_of_pending_depreciations ): + if row.shift_based: + return get_shift_depr_amount(asset, row, schedule_idx) + # if the Depreciation Schedule is being modified after Asset Repair due to increase in asset life and value if asset.flags.increase_in_asset_life: return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / ( @@ -1479,6 +1491,40 @@ def get_straight_line_or_manual_depr_amount( ) / flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked) +def get_shift_depr_amount(asset, row, schedule_idx): + if asset.get("__islocal") and not asset.flags.shift_allocation: + return ( + flt(asset.gross_purchase_amount) + - flt(asset.opening_accumulated_depreciation) + - flt(row.expected_value_after_useful_life) + ) / flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked) + + asset_shift_factors_map = get_asset_shift_factors_map() + shift = ( + asset.schedules_before_clearing[schedule_idx].shift + if len(asset.schedules_before_clearing) > schedule_idx + else None + ) + shift_factor = asset_shift_factors_map.get(shift) if shift else 0 + + shift_factors_sum = sum( + flt(asset_shift_factors_map.get(schedule.shift)) for schedule in asset.schedules_before_clearing + ) + + return ( + ( + flt(asset.gross_purchase_amount) + - flt(asset.opening_accumulated_depreciation) + - flt(row.expected_value_after_useful_life) + ) + / flt(shift_factors_sum) + ) * shift_factor + + +def get_asset_shift_factors_map(): + return dict(frappe.db.get_all("Asset Shift Factor", ["shift_name", "shift_factor"], as_list=True)) + + def get_wdv_or_dd_depr_amount( depreciable_value, rate_of_depreciation, diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 3ab53e57ae3..3c7c33607b0 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -1706,6 +1706,7 @@ def create_asset(**args): "expected_value_after_useful_life": args.expected_value_after_useful_life or 0, "depreciation_start_date": args.depreciation_start_date, "daily_prorata_based": args.daily_prorata_based or 0, + "shift_based": args.shift_based or 0, }, ) diff --git a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json index 7c059fde2df..172b81d98b0 100644 --- a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json +++ b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json @@ -9,6 +9,7 @@ "depreciation_method", "total_number_of_depreciations", "daily_prorata_based", + "shift_based", "column_break_5", "frequency_of_depreciation", "depreciation_start_date", @@ -93,12 +94,19 @@ "fieldname": "daily_prorata_based", "fieldtype": "Check", "label": "Depreciate based on daily pro-rata" + }, + { + "default": "0", + "depends_on": "eval:doc.depreciation_method == \"Straight Line\"", + "fieldname": "shift_based", + "fieldtype": "Check", + "label": "Depreciate based on shifts" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-11-03 22:21:52.090191", + "modified": "2023-11-29 03:53:03.591098", "modified_by": "Administrator", "module": "Assets", "name": "Asset Finance Book", diff --git a/erpnext/assets/doctype/asset_shift_allocation/__init__.py b/erpnext/assets/doctype/asset_shift_allocation/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.js b/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.js new file mode 100644 index 00000000000..2bd41c13773 --- /dev/null +++ b/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.js @@ -0,0 +1,15 @@ +// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Asset Shift Allocation', { + onload: function(frm) { + frm.events.make_schedules_editable(frm); + }, + + make_schedules_editable: function(frm) { + frm.toggle_enable("depreciation_schedule", true); + frm.fields_dict["depreciation_schedule"].grid.toggle_enable("schedule_date", false); + frm.fields_dict["depreciation_schedule"].grid.toggle_enable("depreciation_amount", false); + frm.fields_dict["depreciation_schedule"].grid.toggle_enable("shift", true); + } +}); \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.json b/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.json new file mode 100644 index 00000000000..d0dc6fcbcf0 --- /dev/null +++ b/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.json @@ -0,0 +1,115 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "naming_series:", + "creation": "2023-11-29 04:01:33.796458", + "default_view": "List", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "section_break_esaa", + "asset", + "naming_series", + "column_break_tdae", + "finance_book", + "amended_from", + "depreciation_schedule_section", + "depreciation_schedule" + ], + "fields": [ + { + "fieldname": "section_break_esaa", + "fieldtype": "Section Break" + }, + { + "fieldname": "asset", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Asset", + "options": "Asset", + "reqd": 1 + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Naming Series", + "options": "ACC-ASA-.YYYY.-", + "reqd": 1 + }, + { + "fieldname": "column_break_tdae", + "fieldtype": "Column Break" + }, + { + "fieldname": "finance_book", + "fieldtype": "Link", + "label": "Finance Book", + "options": "Finance Book" + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "depreciation_schedule_section", + "fieldtype": "Section Break", + "label": "Depreciation Schedule" + }, + { + "fieldname": "depreciation_schedule", + "fieldtype": "Table", + "label": "Depreciation Schedule", + "options": "Depreciation Schedule" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Asset Shift Allocation", + "print_hide": 1, + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2023-11-29 04:06:20.586168", + "modified_by": "Administrator", + "module": "Assets", + "name": "Asset Shift Allocation", + "naming_rule": "By \"Naming Series\" field", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.py b/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.py new file mode 100644 index 00000000000..d9797b0c24c --- /dev/null +++ b/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.py @@ -0,0 +1,247 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.utils import add_months, cint, flt, get_last_day + +from erpnext.assets.doctype.asset.asset import get_asset_shift_factors_map +from erpnext.assets.doctype.asset.depreciation import is_last_day_of_the_month + + +class AssetShiftAllocation(Document): + def after_insert(self): + self.fetch_and_set_depr_schedule() + + def validate(self): + self.asset_doc = frappe.get_doc("Asset", self.asset) + + self.validate_invalid_shift_change() + self.update_depr_schedule() + + def on_submit(self): + self.update_asset_schedule() + + def fetch_and_set_depr_schedule(self): + if len(self.asset_doc.finance_books) != 1: + frappe.throw(_("Only assets with one finance book allowed in v14.")) + + if not any(fb.get("shift_based") for fb in self.asset_doc.finance_books): + frappe.throw(_("Asset {0} is not using shift based depreciation").format(self.asset)) + + for schedule in self.asset_doc.get("schedules"): + self.append( + "depreciation_schedule", + { + "schedule_date": schedule.schedule_date, + "depreciation_amount": schedule.depreciation_amount, + "accumulated_depreciation_amount": schedule.accumulated_depreciation_amount, + "journal_entry": schedule.journal_entry, + "shift": schedule.shift, + "depreciation_method": self.asset_doc.finance_books[0].depreciation_method, + "finance_book": self.asset_doc.finance_books[0].finance_book, + "finance_book_id": self.asset_doc.finance_books[0].idx, + }, + ) + + self.flags.ignore_validate = True + self.save() + + def validate_invalid_shift_change(self): + if not self.get("depreciation_schedule") or self.docstatus == 1: + return + + for i, sch in enumerate(self.depreciation_schedule): + if sch.journal_entry and self.asset_doc.schedules[i].shift != sch.shift: + frappe.throw( + _( + "Row {0}: Shift cannot be changed since the depreciation has already been processed" + ).format(i) + ) + + def update_depr_schedule(self): + if not self.get("depreciation_schedule") or self.docstatus == 1: + return + + self.allocate_shift_diff_in_depr_schedule() + + temp_asset_doc = frappe.copy_doc(self.asset_doc) + + temp_asset_doc.flags.shift_allocation = True + + temp_asset_doc.schedules = [] + + for schedule in self.depreciation_schedule: + temp_asset_doc.append( + "schedules", + { + "schedule_date": schedule.schedule_date, + "depreciation_amount": schedule.depreciation_amount, + "accumulated_depreciation_amount": schedule.accumulated_depreciation_amount, + "journal_entry": schedule.journal_entry, + "shift": schedule.shift, + "depreciation_method": self.asset_doc.finance_books[0].depreciation_method, + "finance_book": self.asset_doc.finance_books[0].finance_book, + "finance_book_id": self.asset_doc.finance_books[0].idx, + }, + ) + + temp_asset_doc.prepare_depreciation_data() + + self.depreciation_schedule = [] + + for schedule in temp_asset_doc.get("schedules"): + self.append( + "depreciation_schedule", + { + "schedule_date": schedule.schedule_date, + "depreciation_amount": schedule.depreciation_amount, + "accumulated_depreciation_amount": schedule.accumulated_depreciation_amount, + "journal_entry": schedule.journal_entry, + "shift": schedule.shift, + "depreciation_method": self.asset_doc.finance_books[0].depreciation_method, + "finance_book": self.asset_doc.finance_books[0].finance_book, + "finance_book_id": self.asset_doc.finance_books[0].idx, + }, + ) + + def allocate_shift_diff_in_depr_schedule(self): + asset_shift_factors_map = get_asset_shift_factors_map() + reverse_asset_shift_factors_map = { + asset_shift_factors_map[k]: k for k in asset_shift_factors_map + } + + original_shift_factors_sum = sum( + flt(asset_shift_factors_map.get(schedule.shift)) for schedule in self.asset_doc.schedules + ) + + new_shift_factors_sum = sum( + flt(asset_shift_factors_map.get(schedule.shift)) for schedule in self.depreciation_schedule + ) + + diff = new_shift_factors_sum - original_shift_factors_sum + + if diff > 0: + for i, schedule in reversed(list(enumerate(self.depreciation_schedule))): + if diff <= 0: + break + + shift_factor = flt(asset_shift_factors_map.get(schedule.shift)) + + if shift_factor <= diff: + self.depreciation_schedule.pop() + diff -= shift_factor + else: + try: + self.depreciation_schedule[i].shift = reverse_asset_shift_factors_map.get( + shift_factor - diff + ) + diff = 0 + except Exception: + frappe.throw(_("Could not auto update shifts. Shift with shift factor {0} needed.")).format( + shift_factor - diff + ) + elif diff < 0: + shift_factors = list(asset_shift_factors_map.values()) + desc_shift_factors = sorted(shift_factors, reverse=True) + depr_schedule_len_diff = self.asset_doc.total_number_of_depreciations - len( + self.depreciation_schedule + ) + subsets_result = [] + + if depr_schedule_len_diff > 0: + num_rows_to_add = depr_schedule_len_diff + + while not subsets_result and num_rows_to_add > 0: + find_subsets_with_sum(shift_factors, num_rows_to_add, abs(diff), [], subsets_result) + if subsets_result: + break + num_rows_to_add -= 1 + + if subsets_result: + for i in range(num_rows_to_add): + schedule_date = add_months( + self.depreciation_schedule[-1].schedule_date, + cint(self.asset_doc.frequency_of_depreciation), + ) + + if is_last_day_of_the_month(self.depreciation_schedule[-1].schedule_date): + schedule_date = get_last_day(schedule_date) + + self.append( + "depreciation_schedule", + { + "schedule_date": schedule_date, + "shift": reverse_asset_shift_factors_map.get(subsets_result[0][i]), + "depreciation_method": self.asset_doc.finance_books[0].depreciation_method, + "finance_book": self.asset_doc.finance_books[0].finance_book, + "finance_book_id": self.asset_doc.finance_books[0].idx, + }, + ) + + if depr_schedule_len_diff <= 0 or not subsets_result: + for i, schedule in reversed(list(enumerate(self.depreciation_schedule))): + diff = abs(diff) + + if diff <= 0: + break + + shift_factor = flt(asset_shift_factors_map.get(schedule.shift)) + + if shift_factor <= diff: + for sf in desc_shift_factors: + if sf - shift_factor <= diff: + self.depreciation_schedule[i].shift = reverse_asset_shift_factors_map.get(sf) + diff -= sf - shift_factor + break + else: + try: + self.depreciation_schedule[i].shift = reverse_asset_shift_factors_map.get( + shift_factor + diff + ) + diff = 0 + except Exception: + frappe.throw(_("Could not auto update shifts. Shift with shift factor {0} needed.")).format( + shift_factor + diff + ) + + def update_asset_schedule(self): + self.asset_doc.flags.shift_allocation = True + + self.asset_doc.schedules = [] + + for schedule in self.depreciation_schedule: + self.asset_doc.append( + "schedules", + { + "schedule_date": schedule.schedule_date, + "depreciation_amount": schedule.depreciation_amount, + "accumulated_depreciation_amount": schedule.accumulated_depreciation_amount, + "journal_entry": schedule.journal_entry, + "shift": schedule.shift, + "depreciation_method": self.asset_doc.finance_books[0].depreciation_method, + "finance_book": self.asset_doc.finance_books[0].finance_book, + "finance_book_id": self.asset_doc.finance_books[0].idx, + }, + ) + + self.asset_doc.flags.ignore_validate_update_after_submit = True + self.asset_doc.prepare_depreciation_data() + self.asset_doc.save() + + +def find_subsets_with_sum(numbers, k, target_sum, current_subset, result): + if k == 0 and target_sum == 0: + result.append(current_subset.copy()) + return + if k <= 0 or target_sum <= 0 or not numbers: + return + + # Include the current number in the subset + find_subsets_with_sum( + numbers, k - 1, target_sum - numbers[0], current_subset + [numbers[0]], result + ) + + # Exclude the current number from the subset + find_subsets_with_sum(numbers[1:], k, target_sum, current_subset, result) diff --git a/erpnext/assets/doctype/asset_shift_allocation/test_asset_shift_allocation.py b/erpnext/assets/doctype/asset_shift_allocation/test_asset_shift_allocation.py new file mode 100644 index 00000000000..078f4327ea7 --- /dev/null +++ b/erpnext/assets/doctype/asset_shift_allocation/test_asset_shift_allocation.py @@ -0,0 +1,112 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +import frappe +from frappe.tests.utils import FrappeTestCase +from frappe.utils import cstr + +from erpnext.assets.doctype.asset.test_asset import create_asset + + +class TestAssetShiftAllocation(FrappeTestCase): + @classmethod + def setUpClass(cls): + create_asset_shift_factors() + + @classmethod + def tearDownClass(cls): + frappe.db.rollback() + + def test_asset_shift_allocation(self): + asset = create_asset( + calculate_depreciation=1, + available_for_use_date="2023-01-01", + purchase_date="2023-01-01", + gross_purchase_amount=120000, + depreciation_start_date="2023-01-31", + total_number_of_depreciations=12, + frequency_of_depreciation=1, + shift_based=1, + submit=1, + ) + + expected_schedules = [ + ["2023-01-31", 10000.0, 10000.0, "Single"], + ["2023-02-28", 10000.0, 20000.0, "Single"], + ["2023-03-31", 10000.0, 30000.0, "Single"], + ["2023-04-30", 10000.0, 40000.0, "Single"], + ["2023-05-31", 10000.0, 50000.0, "Single"], + ["2023-06-30", 10000.0, 60000.0, "Single"], + ["2023-07-31", 10000.0, 70000.0, "Single"], + ["2023-08-31", 10000.0, 80000.0, "Single"], + ["2023-09-30", 10000.0, 90000.0, "Single"], + ["2023-10-31", 10000.0, 100000.0, "Single"], + ["2023-11-30", 10000.0, 110000.0, "Single"], + ["2023-12-31", 10000.0, 120000.0, "Single"], + ] + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount, d.shift] + for d in asset.get("schedules") + ] + + self.assertEqual(schedules, expected_schedules) + + asset_shift_allocation = frappe.get_doc( + {"doctype": "Asset Shift Allocation", "asset": asset.name} + ).insert() + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount, d.shift] + for d in asset_shift_allocation.get("depreciation_schedule") + ] + + self.assertEqual(schedules, expected_schedules) + + asset_shift_allocation = frappe.get_doc("Asset Shift Allocation", asset_shift_allocation.name) + asset_shift_allocation.depreciation_schedule[4].shift = "Triple" + asset_shift_allocation.save() + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount, d.shift] + for d in asset_shift_allocation.get("depreciation_schedule") + ] + + expected_schedules = [ + ["2023-01-31", 10000.0, 10000.0, "Single"], + ["2023-02-28", 10000.0, 20000.0, "Single"], + ["2023-03-31", 10000.0, 30000.0, "Single"], + ["2023-04-30", 10000.0, 40000.0, "Single"], + ["2023-05-31", 20000.0, 60000.0, "Triple"], + ["2023-06-30", 10000.0, 70000.0, "Single"], + ["2023-07-31", 10000.0, 80000.0, "Single"], + ["2023-08-31", 10000.0, 90000.0, "Single"], + ["2023-09-30", 10000.0, 100000.0, "Single"], + ["2023-10-31", 10000.0, 110000.0, "Single"], + ["2023-11-30", 10000.0, 120000.0, "Single"], + ] + + self.assertEqual(schedules, expected_schedules) + + asset_shift_allocation.submit() + + asset.reload() + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount, d.shift] + for d in asset.get("schedules") + ] + + self.assertEqual(schedules, expected_schedules) + + +def create_asset_shift_factors(): + shifts = [ + {"doctype": "Asset Shift Factor", "shift_name": "Half", "shift_factor": 0.5, "default": 0}, + {"doctype": "Asset Shift Factor", "shift_name": "Single", "shift_factor": 1, "default": 1}, + {"doctype": "Asset Shift Factor", "shift_name": "Double", "shift_factor": 1.5, "default": 0}, + {"doctype": "Asset Shift Factor", "shift_name": "Triple", "shift_factor": 2, "default": 0}, + ] + + for s in shifts: + frappe.get_doc(s).insert() diff --git a/erpnext/assets/doctype/asset_shift_factor/__init__.py b/erpnext/assets/doctype/asset_shift_factor/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/assets/doctype/asset_shift_factor/asset_shift_factor.js b/erpnext/assets/doctype/asset_shift_factor/asset_shift_factor.js new file mode 100644 index 00000000000..e6552d8370d --- /dev/null +++ b/erpnext/assets/doctype/asset_shift_factor/asset_shift_factor.js @@ -0,0 +1,8 @@ +// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Asset Shift Factor', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/assets/doctype/asset_shift_factor/asset_shift_factor.json b/erpnext/assets/doctype/asset_shift_factor/asset_shift_factor.json new file mode 100644 index 00000000000..75cbc1d2523 --- /dev/null +++ b/erpnext/assets/doctype/asset_shift_factor/asset_shift_factor.json @@ -0,0 +1,76 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "field:shift_name", + "creation": "2023-11-29 03:45:13.247372", + "default_view": "List", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "shift_name", + "shift_factor", + "default" + ], + "fields": [ + { + "fieldname": "shift_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Shift Name", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "shift_factor", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Shift Factor", + "reqd": 1 + }, + { + "default": "0", + "fieldname": "default", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Default" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2023-11-29 04:06:31.723038", + "modified_by": "Administrator", + "module": "Assets", + "name": "Asset Shift Factor", + "naming_rule": "By fieldname", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_shift_factor/asset_shift_factor.py b/erpnext/assets/doctype/asset_shift_factor/asset_shift_factor.py new file mode 100644 index 00000000000..4c275ce092c --- /dev/null +++ b/erpnext/assets/doctype/asset_shift_factor/asset_shift_factor.py @@ -0,0 +1,24 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe import _ +from frappe.model.document import Document + + +class AssetShiftFactor(Document): + def validate(self): + self.validate_default() + + def validate_default(self): + if self.default: + existing_default_shift_factor = frappe.db.get_value( + "Asset Shift Factor", {"default": 1}, "name" + ) + + if existing_default_shift_factor: + frappe.throw( + _("Asset Shift Factor {0} is set as default currently. Please change it first.").format( + frappe.bold(existing_default_shift_factor) + ) + ) diff --git a/erpnext/assets/doctype/asset_shift_factor/test_asset_shift_factor.py b/erpnext/assets/doctype/asset_shift_factor/test_asset_shift_factor.py new file mode 100644 index 00000000000..75073673c0c --- /dev/null +++ b/erpnext/assets/doctype/asset_shift_factor/test_asset_shift_factor.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestAssetShiftFactor(FrappeTestCase): + pass diff --git a/erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.json b/erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.json index 35a2c9dd7f3..21f5ae9edb6 100644 --- a/erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.json +++ b/erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.json @@ -1,318 +1,115 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 1, - "autoname": "", - "beta": 0, - "creation": "2016-03-02 15:11:01.278862", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "allow_rename": 1, + "creation": "2016-03-02 15:11:01.278862", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "finance_book", + "schedule_date", + "depreciation_amount", + "column_break_3", + "accumulated_depreciation_amount", + "journal_entry", + "shift", + "make_depreciation_entry", + "finance_book_id", + "depreciation_method" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "finance_book", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Finance Book", - "length": 0, - "no_copy": 0, - "options": "Finance Book", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "finance_book", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Finance Book", + "options": "Finance Book", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "schedule_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Schedule Date", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "schedule_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Schedule Date", + "no_copy": 1, + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "depreciation_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Depreciation Amount", - "length": 0, - "no_copy": 1, - "options": "Company:company:default_currency", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "depreciation_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Depreciation Amount", + "no_copy": 1, + "options": "Company:company:default_currency", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "accumulated_depreciation_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Accumulated Depreciation Amount", - "length": 0, - "no_copy": 1, - "options": "Company:company:default_currency", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "accumulated_depreciation_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Accumulated Depreciation Amount", + "no_copy": 1, + "options": "Company:company:default_currency", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.docstatus==1", - "fieldname": "journal_entry", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Journal Entry", - "length": 0, - "no_copy": 1, - "options": "Journal Entry", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "depends_on": "eval:doc.docstatus==1", + "fieldname": "journal_entry", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Journal Entry", + "no_copy": 1, + "options": "Journal Entry", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:(doc.docstatus==1 && !doc.journal_entry && doc.schedule_date <= get_today())", - "fieldname": "make_depreciation_entry", - "fieldtype": "Button", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Make Depreciation Entry", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "allow_on_submit": 1, + "depends_on": "eval:(doc.docstatus==1 && !doc.journal_entry && doc.schedule_date <= get_today())", + "fieldname": "make_depreciation_entry", + "fieldtype": "Button", + "label": "Make Depreciation Entry" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "finance_book_id", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Finance Book Id", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "finance_book_id", + "fieldtype": "Data", + "hidden": 1, + "label": "Finance Book Id", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "depreciation_method", - "fieldtype": "Select", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Depreciation Method", - "length": 0, - "no_copy": 1, - "options": "\nStraight Line\nDouble Declining Balance\nWritten Down Value\nManual", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 + "fieldname": "depreciation_method", + "fieldtype": "Select", + "hidden": 1, + "label": "Depreciation Method", + "no_copy": 1, + "options": "\nStraight Line\nDouble Declining Balance\nWritten Down Value\nManual", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "shift", + "fieldtype": "Link", + "label": "Shift", + "options": "Asset Shift Factor" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-05-10 15:12:41.679436", - "modified_by": "Administrator", - "module": "Assets", - "name": "Depreciation Schedule", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0 + ], + "istable": 1, + "links": [], + "modified": "2023-11-29 04:43:04.218580", + "modified_by": "Administrator", + "module": "Assets", + "name": "Depreciation Schedule", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "states": [] } \ No newline at end of file From 9170ffc371f1ec0343d3c5bdf9b7324e99d91ae8 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 29 Nov 2023 00:48:28 +0000 Subject: [PATCH 5/8] chore(release): Bumped to Version 14.52.0 # [14.52.0](https://github.com/frappe/erpnext/compare/v14.51.0...v14.52.0) (2023-11-29) ### Features * shift depreciation for assets [v14] [backport of [#38327](https://github.com/frappe/erpnext/issues/38327)] ([#38404](https://github.com/frappe/erpnext/issues/38404)) ([53d083a](https://github.com/frappe/erpnext/commit/53d083a1210436135de3973a87c7b3d88b781f17)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index bfef4c35484..a589fba0f76 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -3,7 +3,7 @@ import inspect import frappe -__version__ = "14.51.0" +__version__ = "14.52.0" def get_default_company(user=None): From 44b11750d3290188de0e765f5da8e1482cba02f0 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 29 Nov 2023 10:20:53 +0530 Subject: [PATCH 6/8] chore: v14 patch release (#38408) * feat: shift depreciation for assets [v14] [backport of #38327] (#38404) feat: shift depreciation for assets * fix: no fstring in translation (#38381) fix: no fstring in translation (#38381) (cherry picked from commit 8f00481c5f7742b120a232622fae7b3f7e3d2e86) Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> * fix: debit credit mismatch in multi-currecy asset purchase receipt (#38342) fix: debit credit mismatch in multi-currecy asset purchase receipt (#38342) * fix: Debit credit mimatch in multicurrecy asset purchase receipt * test: multi currency purchase receipt * chore: update init files * test: roolback (cherry picked from commit add238c892364ce25f3e7483cb5fd295f9666a56) Co-authored-by: Deepesh Garg * fix(regional): use net figures for sales calc (#38260) fix(regional): use net figures for sales calc (#38260) (cherry picked from commit 663bb8726c25005ab83a6ca4c82814b82a98a1e2) Co-authored-by: Dany Robert --------- Co-authored-by: Anand Baburajan Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Co-authored-by: Dany Robert --- erpnext/assets/doctype/asset/test_asset.py | 103 ++++++------------ .../report/uae_vat_201/uae_vat_201.py | 6 +- .../purchase_receipt/purchase_receipt.py | 2 +- .../stock_and_account_value_comparison.py | 2 +- 4 files changed, 41 insertions(+), 72 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 3c7c33607b0..21afd5df851 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -142,12 +142,7 @@ class TestAsset(AssetSetup): ("Creditors - _TC", 0.0, 100000.0), ) - gle = frappe.db.sql( - """select account, debit, credit from `tabGL Entry` - where voucher_type='Purchase Invoice' and voucher_no = %s - order by account""", - pi.name, - ) + gle = get_gl_entries("Purchase Invoice", pi.name) self.assertSequenceEqual(gle, expected_gle) pi.cancel() @@ -249,12 +244,7 @@ class TestAsset(AssetSetup): ), ) - gle = frappe.db.sql( - """select account, debit, credit from `tabGL Entry` - where voucher_type='Journal Entry' and voucher_no = %s - order by account""", - asset.journal_entry_for_scrap, - ) + gle = get_gl_entries("Journal Entry", asset.journal_entry_for_scrap) self.assertSequenceEqual(gle, expected_gle) restore_asset(asset.name) @@ -316,13 +306,7 @@ class TestAsset(AssetSetup): ("Debtors - _TC", 25000.0, 0.0), ) - gle = frappe.db.sql( - """select account, debit, credit from `tabGL Entry` - where voucher_type='Sales Invoice' and voucher_no = %s - order by account""", - si.name, - ) - + gle = get_gl_entries("Sales Invoice", si.name) self.assertSequenceEqual(gle, expected_gle) si.cancel() @@ -392,13 +376,7 @@ class TestAsset(AssetSetup): ("Debtors - _TC", 40000.0, 0.0), ) - gle = frappe.db.sql( - """select account, debit, credit from `tabGL Entry` - where voucher_type='Sales Invoice' and voucher_no = %s - order by account""", - si.name, - ) - + gle = get_gl_entries("Sales Invoice", si.name) self.assertSequenceEqual(gle, expected_gle) def test_asset_with_maintenance_required_status_after_sale(self): @@ -526,13 +504,7 @@ class TestAsset(AssetSetup): ("CWIP Account - _TC", 5250.0, 0.0), ) - pr_gle = frappe.db.sql( - """select account, debit, credit from `tabGL Entry` - where voucher_type='Purchase Receipt' and voucher_no = %s - order by account""", - pr.name, - ) - + pr_gle = get_gl_entries("Purchase Receipt", pr.name) self.assertSequenceEqual(pr_gle, expected_gle) pi = make_invoice(pr.name) @@ -545,13 +517,7 @@ class TestAsset(AssetSetup): ("Creditors - _TC", 0.0, 5500.0), ) - pi_gle = frappe.db.sql( - """select account, debit, credit from `tabGL Entry` - where voucher_type='Purchase Invoice' and voucher_no = %s - order by account""", - pi.name, - ) - + pi_gle = get_gl_entries("Purchase Invoice", pi.name) self.assertSequenceEqual(pi_gle, expected_gle) asset = frappe.db.get_value("Asset", {"purchase_receipt": pr.name, "docstatus": 0}, "name") @@ -578,13 +544,7 @@ class TestAsset(AssetSetup): expected_gle = (("_Test Fixed Asset - _TC", 5250.0, 0.0), ("CWIP Account - _TC", 0.0, 5250.0)) - gle = frappe.db.sql( - """select account, debit, credit from `tabGL Entry` - where voucher_type='Asset' and voucher_no = %s - order by account""", - asset_doc.name, - ) - + gle = get_gl_entries("Asset", asset_doc.name) self.assertSequenceEqual(gle, expected_gle) def test_asset_cwip_toggling_cases(self): @@ -607,10 +567,7 @@ class TestAsset(AssetSetup): asset_doc.available_for_use_date = nowdate() asset_doc.calculate_depreciation = 0 asset_doc.submit() - gle = frappe.db.sql( - """select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", - asset_doc.name, - ) + gle = get_gl_entries("Asset", asset_doc.name) self.assertFalse(gle) # case 1 -- PR with cwip disabled, Asset with cwip enabled @@ -624,10 +581,7 @@ class TestAsset(AssetSetup): asset_doc.available_for_use_date = nowdate() asset_doc.calculate_depreciation = 0 asset_doc.submit() - gle = frappe.db.sql( - """select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", - asset_doc.name, - ) + gle = get_gl_entries("Asset", asset_doc.name) self.assertFalse(gle) # case 2 -- PR with cwip enabled, Asset with cwip disabled @@ -640,10 +594,7 @@ class TestAsset(AssetSetup): asset_doc.available_for_use_date = nowdate() asset_doc.calculate_depreciation = 0 asset_doc.submit() - gle = frappe.db.sql( - """select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", - asset_doc.name, - ) + gle = get_gl_entries("Asset", asset_doc.name) self.assertTrue(gle) # case 3 -- PI with cwip disabled, Asset with cwip enabled @@ -656,10 +607,7 @@ class TestAsset(AssetSetup): asset_doc.available_for_use_date = nowdate() asset_doc.calculate_depreciation = 0 asset_doc.submit() - gle = frappe.db.sql( - """select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", - asset_doc.name, - ) + gle = get_gl_entries("Asset", asset_doc.name) self.assertFalse(gle) # case 4 -- PI with cwip enabled, Asset with cwip disabled @@ -672,10 +620,7 @@ class TestAsset(AssetSetup): asset_doc.available_for_use_date = nowdate() asset_doc.calculate_depreciation = 0 asset_doc.submit() - gle = frappe.db.sql( - """select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", - asset_doc.name, - ) + gle = get_gl_entries("Asset", asset_doc.name) self.assertTrue(gle) frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", cwip) @@ -1644,6 +1589,30 @@ class TestDepreciationBasics(AssetSetup): self.assertRaises(frappe.ValidationError, jv.insert) + def test_multi_currency_asset_pr_creation(self): + pr = make_purchase_receipt( + item_code="Macbook Pro", + qty=1, + rate=100.0, + location="Test Location", + supplier="_Test Supplier USD", + currency="USD", + ) + + pr.submit() + self.assertTrue(get_gl_entries("Purchase Receipt", pr.name)) + + +def get_gl_entries(doctype, docname): + gl_entry = frappe.qb.DocType("GL Entry") + return ( + frappe.qb.from_(gl_entry) + .select(gl_entry.account, gl_entry.debit, gl_entry.credit) + .where((gl_entry.voucher_type == doctype) & (gl_entry.voucher_no == docname)) + .orderby(gl_entry.account) + .run() + ) + def create_asset_data(): if not frappe.db.exists("Asset Category", "Computers"): 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 59ef58bfde3..6ef21e52ca1 100644 --- a/erpnext/regional/report/uae_vat_201/uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.py @@ -141,7 +141,7 @@ def get_total_emiratewise(filters): return frappe.db.sql( """ select - s.vat_emirate as emirate, sum(i.base_amount) as total, sum(i.tax_amount) + s.vat_emirate as emirate, sum(i.base_net_amount) as total, sum(i.tax_amount) from `tabSales Invoice Item` i inner join `tabSales Invoice` s on @@ -356,7 +356,7 @@ def get_zero_rated_total(filters): frappe.db.sql( """ select - sum(i.base_amount) as total + sum(i.base_net_amount) as total from `tabSales Invoice Item` i inner join `tabSales Invoice` s on @@ -383,7 +383,7 @@ def get_exempt_total(filters): frappe.db.sql( """ select - sum(i.base_amount) as total + sum(i.base_net_amount) as total from `tabSales Invoice Item` i inner join `tabSales Invoice` s on diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index d8c678737ea..bb3cbd2fa6a 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -572,7 +572,7 @@ class PurchaseReceipt(BuyingController): ) stock_value_diff = ( - flt(d.net_amount) + flt(d.base_net_amount) + flt(d.item_tax_amount / self.conversion_rate) + flt(d.landed_cost_voucher_amount) ) diff --git a/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py b/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py index b1da3ec1bd1..416cf48871a 100644 --- a/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py +++ b/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py @@ -166,4 +166,4 @@ def create_reposting_entries(rows, company): if entries: entries = ", ".join(entries) - frappe.msgprint(_(f"Reposting entries created: {entries}")) + frappe.msgprint(_("Reposting entries created: {0}").format(entries)) From 161b0da29a8facac1c31d4aa064590ad94c43ba5 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 30 Nov 2023 15:57:57 +0530 Subject: [PATCH 7/8] fix: use flt on outstanding on AR/AP summary report (cherry picked from commit e4bdd3a28d7fa29b0460ad6661ff39a219c1fc5c) --- .../accounts_receivable_summary.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py index 60274cd8b10..a92f960fdf0 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py @@ -8,6 +8,7 @@ from frappe.utils import cint, flt from erpnext.accounts.party import get_partywise_advanced_payment_amount from erpnext.accounts.report.accounts_receivable.accounts_receivable import ReceivablePayableReport +from erpnext.accounts.utils import get_currency_precision def execute(filters=None): @@ -35,6 +36,7 @@ class AccountsReceivableSummary(ReceivablePayableReport): def get_data(self, args): self.data = [] self.receivables = ReceivablePayableReport(self.filters).run(args)[1] + self.currency_precision = get_currency_precision() or 2 self.get_party_total(args) @@ -58,7 +60,7 @@ class AccountsReceivableSummary(ReceivablePayableReport): gl_balance_map = get_gl_balance(self.filters.report_date, self.filters.company) for party, party_dict in self.party_total.items(): - if party_dict.outstanding == 0: + if flt(party_dict.outstanding, self.currency_precision) == 0: continue row = frappe._dict() From 7b6f25cb9e02e8f6e624ba7751c68e66ffc28a55 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Thu, 30 Nov 2023 11:36:02 +0000 Subject: [PATCH 8/8] chore(release): Bumped to Version 14.52.1 ## [14.52.1](https://github.com/frappe/erpnext/compare/v14.52.0...v14.52.1) (2023-11-30) ### Bug Fixes * use flt on outstanding on AR/AP summary report ([161b0da](https://github.com/frappe/erpnext/commit/161b0da29a8facac1c31d4aa064590ad94c43ba5)) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index a589fba0f76..ca5062d835e 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -3,7 +3,7 @@ import inspect import frappe -__version__ = "14.52.0" +__version__ = "14.52.1" def get_default_company(user=None):