From a2b21c757043cddd48fb67394d6c09a41bbfc65c Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 8 Jul 2024 17:08:38 +0200 Subject: [PATCH 01/32] fix(Warehouse): add buttons only if the user can use them (cherry picked from commit 10ae5aaf520dbe5a2c3d38d227dd9a3103f356c3) --- erpnext/stock/doctype/warehouse/warehouse.js | 48 ++++++++++++-------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/erpnext/stock/doctype/warehouse/warehouse.js b/erpnext/stock/doctype/warehouse/warehouse.js index 96cac9c06b2..6606a4f11a0 100644 --- a/erpnext/stock/doctype/warehouse/warehouse.js +++ b/erpnext/stock/doctype/warehouse/warehouse.js @@ -40,32 +40,40 @@ frappe.ui.form.on("Warehouse", { if (!frm.is_new()) { frappe.contacts.render_address_and_contact(frm); - let enable_toggle = frm.doc.disabled ? "Enable" : "Disable"; - frm.add_custom_button(__(enable_toggle), () => { - frm.set_value("disabled", 1 - frm.doc.disabled); - frm.save(); - }); - - frm.add_custom_button(__("Stock Balance"), function () { - frappe.set_route("query-report", "Stock Balance", { - warehouse: frm.doc.name, - company: frm.doc.company, + if (frm.has_perm("write")) { + let enable_toggle = frm.doc.disabled ? "Enable" : "Disable"; + frm.add_custom_button(__(enable_toggle), () => { + frm.set_value("disabled", 1 - frm.doc.disabled); + frm.save(); }); - }); - frm.add_custom_button( - frm.doc.is_group - ? __("Convert to Ledger", null, "Warehouse") - : __("Convert to Group", null, "Warehouse"), - function () { - convert_to_group_or_ledger(frm); - } - ); + frm.add_custom_button( + frm.doc.is_group + ? __("Convert to Ledger", null, "Warehouse") + : __("Convert to Group", null, "Warehouse"), + function () { + convert_to_group_or_ledger(frm); + } + ); + } + + if ("Stock Balance" in frappe.boot.user.all_reports) { + frm.add_custom_button(__("Stock Balance"), function () { + frappe.set_route("query-report", "Stock Balance", { + warehouse: frm.doc.name, + company: frm.doc.company, + }); + }); + } } else { frappe.contacts.clear_address_and_contact(frm); } - if (!frm.doc.is_group && frm.doc.__onload && frm.doc.__onload.account) { + if ( + !frm.doc.is_group && + frm.doc.__onload?.account && + "General Ledger" in frappe.boot.user.all_reports + ) { frm.add_custom_button(__("General Ledger", null, "Warehouse"), function () { frappe.route_options = { account: frm.doc.__onload.account, From 4055ef92b53e1528b2c286ff4028f3c501d1ea30 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 11 Jul 2024 17:21:41 +0530 Subject: [PATCH 02/32] fix: missing discount on POS Credit Notes (cherry picked from commit 1049550951011c09fd705c4b01b925e02b6a84ee) --- erpnext/accounts/doctype/sales_invoice/sales_invoice.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index c7505ce007d..1df21b7ebc4 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -505,6 +505,9 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends ( return this.frm.call({ doc: me.frm.doc, method: "set_missing_values", + args: { + for_validate: true, + }, callback: function (r) { if (!r.exc) { if (r.message && r.message.print_format) { From 4f7e0d29551a344a9648bb02fe68ba4cbc69c0ea Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 5 Jul 2024 09:40:33 +0530 Subject: [PATCH 03/32] chore: rename test suite for payable report (cherry picked from commit 9474f727760da7592fec05331643b145e9f132f9) --- .../accounts/report/accounts_payable/test_accounts_payable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_payable/test_accounts_payable.py b/erpnext/accounts/report/accounts_payable/test_accounts_payable.py index f5c9d16073e..43856bf569f 100644 --- a/erpnext/accounts/report/accounts_payable/test_accounts_payable.py +++ b/erpnext/accounts/report/accounts_payable/test_accounts_payable.py @@ -7,7 +7,7 @@ from erpnext.accounts.report.accounts_payable.accounts_payable import execute from erpnext.accounts.test.accounts_mixin import AccountsTestMixin -class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase): +class TestAccountsPayable(AccountsTestMixin, FrappeTestCase): def setUp(self): self.create_company() self.create_customer() From cd79d33db2c634e5f65213eb62a6e1d2843660f1 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 5 Jul 2024 09:42:27 +0530 Subject: [PATCH 04/32] refactor: test suite for item-wise sales register (cherry picked from commit 3aaa22e672ae5363ca6347d0ce280aa7fba062f0) --- .../test_item_wise_sales_register.py | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 erpnext/accounts/report/item_wise_sales_register/test_item_wise_sales_register.py diff --git a/erpnext/accounts/report/item_wise_sales_register/test_item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/test_item_wise_sales_register.py new file mode 100644 index 00000000000..c26530ca564 --- /dev/null +++ b/erpnext/accounts/report/item_wise_sales_register/test_item_wise_sales_register.py @@ -0,0 +1,64 @@ +import frappe +from frappe.tests.utils import FrappeTestCase +from frappe.utils import getdate, today + +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice +from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import execute +from erpnext.accounts.test.accounts_mixin import AccountsTestMixin + + +class TestItemWiseSalesRegister(AccountsTestMixin, FrappeTestCase): + def setUp(self): + self.create_company() + self.create_customer() + self.create_item() + + def tearDown(self): + frappe.db.rollback() + + def create_sales_invoice(self, do_not_submit=False): + si = create_sales_invoice( + item=self.item, + company=self.company, + customer=self.customer, + debit_to=self.debit_to, + posting_date=today(), + parent_cost_center=self.cost_center, + cost_center=self.cost_center, + rate=100, + price_list_rate=100, + do_not_save=1, + ) + si = si.save() + if not do_not_submit: + si = si.submit() + return si + + def test_basic_report_output(self): + si = self.create_sales_invoice() + + filters = frappe._dict({"from_date": today(), "to_date": today(), "company": self.company}) + report = execute(filters) + + self.assertEqual(len(report[1]), 1) + + expected_result = { + "item_code": si.items[0].item_code, + "invoice": si.name, + "posting_date": getdate(), + "customer": si.customer, + "debit_to": si.debit_to, + "company": self.company, + "income_account": si.items[0].income_account, + "stock_qty": 1.0, + "stock_uom": "Nos", + "rate": 100.0, + "amount": 100.0, + "total_tax": 0, + "total_other_charges": 0, + "total": 100.0, + "currency": "INR", + } + + report_output = {k: v for k, v in report[1][0].items() if k in expected_result} + self.assertDictEqual(report_output, expected_result) From 61852bd3f61e9622dfec3f6db7a71176a374b39f Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 5 Jul 2024 10:30:16 +0530 Subject: [PATCH 05/32] refactor(test): use each instance UOM for assertion (cherry picked from commit cf4fbfb60150e5af44641a2dd0811fb64d003774) --- .../item_wise_sales_register/test_item_wise_sales_register.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/item_wise_sales_register/test_item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/test_item_wise_sales_register.py index c26530ca564..6fd5d601e84 100644 --- a/erpnext/accounts/report/item_wise_sales_register/test_item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/test_item_wise_sales_register.py @@ -51,7 +51,7 @@ class TestItemWiseSalesRegister(AccountsTestMixin, FrappeTestCase): "company": self.company, "income_account": si.items[0].income_account, "stock_qty": 1.0, - "stock_uom": "Nos", + "stock_uom": si.items[0].stock_uom, "rate": 100.0, "amount": 100.0, "total_tax": 0, From ef16313e0a4e09105dd1f0a3c004076675927b14 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 11 Jul 2024 19:24:09 +0530 Subject: [PATCH 06/32] fix: slowness in reposting dependent vouchers. (backport #42282) (#42292) fix: slowness in reposting dependent vouchers. (#42282) (cherry picked from commit b17696a8ae7f0ea1128887d29336de4ef67a220f) Co-authored-by: rohitwaghchaure --- erpnext/stock/stock_ledger.py | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index ff4798515c3..e14b0f87501 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -267,6 +267,8 @@ def repost_future_sle( "posting_time": args[i].get("posting_time"), "creation": args[i].get("creation"), "distinct_item_warehouses": distinct_item_warehouses, + "items_to_be_repost": args, + "current_index": i, }, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher, @@ -685,11 +687,20 @@ class update_entries_after: self.distinct_item_warehouses[key] = val self.new_items_found = True else: + # Check if the dependent voucher is reposted + # If not, then do not add it to the list + if not self.is_dependent_voucher_reposted(dependant_sle): + return + existing_sle_posting_date = self.distinct_item_warehouses[key].get("sle", {}).get("posting_date") dependent_voucher_detail_nos = self.get_dependent_voucher_detail_nos(key) - if getdate(dependant_sle.posting_date) < getdate(existing_sle_posting_date): + if dependent_voucher_detail_nos and dependant_sle.voucher_detail_no in set( + dependent_voucher_detail_nos + ): + return + val.sle_changed = True dependent_voucher_detail_nos.append(dependant_sle.voucher_detail_no) val.dependent_voucher_detail_nos = dependent_voucher_detail_nos @@ -703,6 +714,27 @@ class update_entries_after: val.dependent_voucher_detail_nos = dependent_voucher_detail_nos self.distinct_item_warehouses[key] = val + def is_dependent_voucher_reposted(self, dependant_sle) -> bool: + # Return False if the dependent voucher is not reposted + + if self.args.items_to_be_repost and self.args.current_index: + index = self.args.current_index + while index < len(self.args.items_to_be_repost): + if ( + self.args.items_to_be_repost[index].get("item_code") == dependant_sle.item_code + and self.args.items_to_be_repost[index].get("warehouse") == dependant_sle.warehouse + ): + if getdate(self.args.items_to_be_repost[index].get("posting_date")) > getdate( + dependant_sle.posting_date + ): + self.args.items_to_be_repost[index]["posting_date"] = dependant_sle.posting_date + + return False + + index += 1 + + return True + def get_dependent_voucher_detail_nos(self, key): if "dependent_voucher_detail_nos" not in self.distinct_item_warehouses[key]: self.distinct_item_warehouses[key].dependent_voucher_detail_nos = [] From 84a8bb3ce53c852e4c0dae8d2976145c9716a5be Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 11 Jul 2024 21:04:34 +0530 Subject: [PATCH 07/32] refactor(test): clear old records --- .../item_wise_sales_register/test_item_wise_sales_register.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/report/item_wise_sales_register/test_item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/test_item_wise_sales_register.py index 6fd5d601e84..4dfdf3058e4 100644 --- a/erpnext/accounts/report/item_wise_sales_register/test_item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/test_item_wise_sales_register.py @@ -12,6 +12,7 @@ class TestItemWiseSalesRegister(AccountsTestMixin, FrappeTestCase): self.create_company() self.create_customer() self.create_item() + self.clear_old_entries() def tearDown(self): frappe.db.rollback() From 1de66e56eeb97d2f1c589298400e79ee9aaf1b02 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 12 Jul 2024 12:14:55 +0530 Subject: [PATCH 08/32] fix: keep status as In Progress for RIV for Timeout Error (backport #42274) (#42296) fix: keep status as In Progress for RIV for Timeout Error (#42274) (cherry picked from commit 10280d6140837ffe2fcebb70a57311780c160e25) Co-authored-by: rohitwaghchaure --- .../doctype/repost_item_valuation/repost_item_valuation.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 717b1c31026..c20eadeb78d 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -289,6 +289,11 @@ def repost(doc): if isinstance(message, dict): message = message.get("message") + status = "Failed" + # If failed because of timeout, set status to In Progress + if traceback and "timeout" in traceback.lower(): + status = "In Progress" + if traceback: message += "

" + "Traceback:
" + traceback @@ -297,7 +302,7 @@ def repost(doc): doc.name, { "error_log": message, - "status": "Failed", + "status": status, }, ) From 4c9ce1b188672fe43f62735023b43694d71fa9f6 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 12 Jul 2024 13:40:16 +0530 Subject: [PATCH 09/32] fix: cost center filter by company (backport #42297) (#42299) fix: cost center filter by company (#42297) (cherry picked from commit 9838f7e6bad8c9e205ca8e8c67f77a8f156ba355) Co-authored-by: rohitwaghchaure --- .../subcontracting_order/subcontracting_order.js | 16 ++++++++++++++++ .../subcontracting_receipt.js | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js index 4ed73805314..c4eea3fda45 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js @@ -31,6 +31,22 @@ frappe.ui.form.on("Subcontracting Order", { }; }); + frm.set_query("cost_center", (doc) => { + return { + filters: { + company: doc.company, + }, + }; + }); + + frm.set_query("cost_center", "items", (doc) => { + return { + filters: { + company: doc.company, + }, + }; + }); + frm.set_query("set_warehouse", () => { return { filters: { diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js index 8dfd9bd486d..b4a127702e0 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js @@ -174,6 +174,22 @@ frappe.ui.form.on("Subcontracting Receipt", { }; }); + frm.set_query("cost_center", (doc) => { + return { + filters: { + company: doc.company, + }, + }; + }); + + frm.set_query("cost_center", "items", (doc) => { + return { + filters: { + company: doc.company, + }, + }; + }); + frm.set_query("supplier_warehouse", () => { return { filters: { From ddd1ca7f7c2e2bdf5af2e535889d5ba714240079 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Fri, 12 Jul 2024 14:25:57 +0530 Subject: [PATCH 10/32] feat: configurable depreciation calculation via accounts settings (#42276) * feat: configurable depreciation calculation via accounts settings * refactor: code optimization * style: changes in description and label (cherry picked from commit b04da63aad0bcf13c15c49d578d291f4d6f8f25b) --- .../accounts_settings/accounts_settings.json | 17 ++++- .../accounts_settings/accounts_settings.py | 1 + .../asset_depreciation_schedule.py | 51 ++++++++++++--- .../test_asset_depreciation_schedule.py | 62 +++++++++++++++++++ 4 files changed, 122 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 7bf3826e781..31991648158 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -55,6 +55,8 @@ "post_change_gl_entries", "assets_tab", "asset_settings_section", + "calculate_depr_using_total_days", + "column_break_gjcc", "book_asset_depreciation_entry_automatically", "closing_settings_tab", "period_closing_settings_section", @@ -462,6 +464,17 @@ "fieldname": "enable_immutable_ledger", "fieldtype": "Check", "label": "Enable Immutable Ledger" + }, + { + "fieldname": "column_break_gjcc", + "fieldtype": "Column Break" + }, + { + "default": "0", + "description": "Enable this option to calculate daily depreciation by considering the total number of days in the entire depreciation period, (including leap years) while using daily pro-rata based depreciation", + "fieldname": "calculate_depr_using_total_days", + "fieldtype": "Check", + "label": "Calculate daily depreciation using total days in depreciation period" } ], "icon": "icon-cog", @@ -469,7 +482,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2024-05-11 23:19:44.673975", + "modified": "2024-07-12 00:24:20.957726", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", @@ -498,4 +511,4 @@ "sort_order": "ASC", "states": [], "track_changes": 1 -} +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py index 34f0f24047b..93ff1e207c9 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py @@ -33,6 +33,7 @@ class AccountsSettings(Document): book_deferred_entries_based_on: DF.Literal["Days", "Months"] book_deferred_entries_via_journal_entry: DF.Check book_tax_discount_loss: DF.Check + calculate_depr_using_total_days: DF.Check check_supplier_invoice_uniqueness: DF.Check credit_controller: DF.Link | None delete_linked_ledger_entries: DF.Check diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py index bd67a173343..c533a634a5b 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py @@ -701,20 +701,57 @@ def get_straight_line_or_manual_depr_amount( def get_daily_prorata_based_straight_line_depr( asset, row, schedule_idx, number_of_pending_depreciations, amount ): - total_years = flt(number_of_pending_depreciations * row.frequency_of_depreciation) / 12 - every_year_depr = amount / total_years + daily_depr_amount = get_daily_depr_amount(asset, row, schedule_idx, amount) - year_start_date = add_years( - row.depreciation_start_date, (row.frequency_of_depreciation * schedule_idx) // 12 - ) - year_end_date = add_days(add_years(year_start_date, 1), -1) - daily_depr_amount = every_year_depr / (date_diff(year_end_date, year_start_date) + 1) from_date, total_depreciable_days = _get_total_days( row.depreciation_start_date, schedule_idx, row.frequency_of_depreciation ) return daily_depr_amount * total_depreciable_days +def get_daily_depr_amount(asset, row, schedule_idx, amount): + if cint(frappe.db.get_single_value("Accounts Settings", "calculate_depr_using_total_days")): + total_days = ( + date_diff( + get_last_day( + add_months( + row.depreciation_start_date, + flt( + row.total_number_of_depreciations + - asset.opening_number_of_booked_depreciations + - 1 + ) + * row.frequency_of_depreciation, + ) + ), + add_days( + get_last_day(add_months(row.depreciation_start_date, -1 * row.frequency_of_depreciation)), + 1, + ), + ) + + 1 + ) + + return amount / total_days + else: + total_years = ( + flt( + (row.total_number_of_depreciations - row.total_number_of_booked_depreciations) + * row.frequency_of_depreciation + ) + / 12 + ) + + every_year_depr = amount / total_years + + year_start_date = add_years( + row.depreciation_start_date, (row.frequency_of_depreciation * schedule_idx) // 12 + ) + year_end_date = add_days(add_years(year_start_date, 1), -1) + + return every_year_depr / (date_diff(year_end_date, year_start_date) + 1) + + def get_shift_depr_amount(asset_depr_schedule, asset, row, schedule_idx): if asset_depr_schedule.get("__islocal") and not asset.flags.shift_allocation: return ( diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py index c359715571e..107d38057a2 100644 --- a/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py +++ b/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py @@ -75,6 +75,68 @@ class TestAssetDepreciationSchedule(FrappeTestCase): ] self.assertEqual(schedules, expected_schedules) + # Enable Checkbox to Calculate depreciation using total days in depreciation period + def test_daily_prorata_based_depr_after_enabling_configuration(self): + frappe.db.set_single_value("Accounts Settings", "calculate_depr_using_total_days", 1) + + asset = create_asset( + calculate_depreciation=1, + depreciation_method="Straight Line", + daily_prorata_based=1, + gross_purchase_amount=1096, + available_for_use_date="2020-01-15", + depreciation_start_date="2020-01-31", + frequency_of_depreciation=1, + total_number_of_depreciations=36, + ) + + expected_schedule = [ + ["2020-01-31", 17.0, 17.0], + ["2020-02-29", 29.0, 46.0], + ["2020-03-31", 31.0, 77.0], + ["2020-04-30", 30.0, 107.0], + ["2020-05-31", 31.0, 138.0], + ["2020-06-30", 30.0, 168.0], + ["2020-07-31", 31.0, 199.0], + ["2020-08-31", 31.0, 230.0], + ["2020-09-30", 30.0, 260.0], + ["2020-10-31", 31.0, 291.0], + ["2020-11-30", 30.0, 321.0], + ["2020-12-31", 31.0, 352.0], + ["2021-01-31", 31.0, 383.0], + ["2021-02-28", 28.0, 411.0], + ["2021-03-31", 31.0, 442.0], + ["2021-04-30", 30.0, 472.0], + ["2021-05-31", 31.0, 503.0], + ["2021-06-30", 30.0, 533.0], + ["2021-07-31", 31.0, 564.0], + ["2021-08-31", 31.0, 595.0], + ["2021-09-30", 30.0, 625.0], + ["2021-10-31", 31.0, 656.0], + ["2021-11-30", 30.0, 686.0], + ["2021-12-31", 31.0, 717.0], + ["2022-01-31", 31.0, 748.0], + ["2022-02-28", 28.0, 776.0], + ["2022-03-31", 31.0, 807.0], + ["2022-04-30", 30.0, 837.0], + ["2022-05-31", 31.0, 868.0], + ["2022-06-30", 30.0, 898.0], + ["2022-07-31", 31.0, 929.0], + ["2022-08-31", 31.0, 960.0], + ["2022-09-30", 30.0, 990.0], + ["2022-10-31", 31.0, 1021.0], + ["2022-11-30", 30.0, 1051.0], + ["2022-12-31", 31.0, 1082.0], + ["2023-01-15", 14.0, 1096.0], + ] + + schedules = [ + [cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in get_depr_schedule(asset.name, "Draft") + ] + self.assertEqual(schedules, expected_schedule) + frappe.db.set_single_value("Accounts Settings", "calculate_depr_using_total_days", 0) + # Test for Written Down Value Method # Frequency of deprciation = 3 def test_for_daily_prorata_based_depreciation_wdv_method_frequency_3_months(self): From 6d098b7302f3409a7fa20e89611a6346f57d1463 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 12 Jul 2024 17:27:18 +0530 Subject: [PATCH 11/32] fix: not able to submit LCV entry (backport #42303) (#42304) fix: not able to submit LCV entry (#42303) (cherry picked from commit 9cf92eaeab359cb0f5ca46b84d36ebed60a68f1b) Co-authored-by: rohitwaghchaure --- .../journal_entry/test_journal_entry.py | 37 +++++++++++++++++++ erpnext/accounts/general_ledger.py | 12 ++++++ 2 files changed, 49 insertions(+) diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py index 5bfb65a3138..cf0aae96260 100644 --- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py @@ -481,6 +481,43 @@ class TestJournalEntry(unittest.TestCase): for field in self.fields: self.assertEqual(self.expected_gle[i][field], gl_entries[i][field]) + def test_negative_debit_and_credit_with_same_account_head(self): + from erpnext.accounts.general_ledger import process_gl_map + + # Create JV with defaut cost center - _Test Cost Center + frappe.db.set_single_value("Accounts Settings", "merge_similar_account_heads", 0) + + jv = make_journal_entry("_Test Bank - _TC", "_Test Bank - _TC", 100 * -1, save=True) + jv.append( + "accounts", + { + "account": "_Test Cash - _TC", + "debit": 100 * -1, + "credit": 100 * -1, + "debit_in_account_currency": 100 * -1, + "credit_in_account_currency": 100 * -1, + "exchange_rate": 1, + }, + ) + jv.flags.ignore_validate = True + jv.save() + + self.assertEqual(len(jv.accounts), 3) + + gl_map = jv.build_gl_map() + + for row in gl_map: + if row.account == "_Test Cash - _TC": + self.assertEqual(row.debit_in_account_currency, 100 * -1) + self.assertEqual(row.credit_in_account_currency, 100 * -1) + + gl_map = process_gl_map(gl_map, False) + + for row in gl_map: + if row.account == "_Test Cash - _TC": + self.assertEqual(row.debit_in_account_currency, 100) + self.assertEqual(row.credit_in_account_currency, 100) + def make_journal_entry( account1, diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 2fd7b5d3c81..a4d128a5845 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -310,6 +310,18 @@ def check_if_in_list(gle, gl_map): def toggle_debit_credit_if_negative(gl_map): for entry in gl_map: # toggle debit, credit if negative entry + if flt(entry.debit) < 0 and flt(entry.credit) < 0 and flt(entry.debit) == flt(entry.credit): + entry.credit *= -1 + entry.debit *= -1 + + if ( + flt(entry.debit_in_account_currency) < 0 + and flt(entry.credit_in_account_currency) < 0 + and flt(entry.debit_in_account_currency) == flt(entry.credit_in_account_currency) + ): + entry.credit_in_account_currency *= -1 + entry.debit_in_account_currency *= -1 + if flt(entry.debit) < 0: entry.credit = flt(entry.credit) - flt(entry.debit) entry.debit = 0.0 From e250dcc7c87e706055bfb92b72c7fa459d3208bc Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 12 Jul 2024 20:05:03 +0530 Subject: [PATCH 12/32] fix: While submitting PCV ensure previous FY is closed (backport #42284) (#42300) fix: While submitting PCV ensure previous FY is closed (#42284) (cherry picked from commit d0bbc8ca705f42a0a3c8be2508919f49b67fbdad) Co-authored-by: Nabin Hait --- .../period_closing_voucher.py | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py index e75057c7a7f..9bc110d243e 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py @@ -136,18 +136,28 @@ class PeriodClosingVoucher(AccountsController): def check_if_previous_year_closed(self): last_year_closing = add_days(self.year_start_date, -1) - previous_fiscal_year = get_fiscal_year(last_year_closing, company=self.company, boolean=True) + if not previous_fiscal_year: + return - if previous_fiscal_year and not frappe.db.exists( + previous_fiscal_year_start_date = previous_fiscal_year[0][1] + if not frappe.db.exists( "GL Entry", - {"posting_date": ("<=", last_year_closing), "company": self.company, "is_cancelled": 0}, + { + "posting_date": ("between", [previous_fiscal_year_start_date, last_year_closing]), + "company": self.company, + "is_cancelled": 0, + }, ): return - if previous_fiscal_year and not frappe.db.exists( + if not frappe.db.exists( "Period Closing Voucher", - {"posting_date": ("<=", last_year_closing), "docstatus": 1, "company": self.company}, + { + "posting_date": ("between", [previous_fiscal_year_start_date, last_year_closing]), + "docstatus": 1, + "company": self.company, + }, ): frappe.throw(_("Previous Year is not closed, please close it first")) From cb64c73c9e54dc6001a80fafb1f26faaa6352da9 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 13 Jul 2024 15:59:46 +0530 Subject: [PATCH 13/32] fix: address and contact filters for SCO and SCR (backport #42310) (#42312) fix: address and contact filters for SCO and SCR (#42310) (cherry picked from commit 7656220075d31e718ad21c10cb385877d37b9fdb) Co-authored-by: rohitwaghchaure --- .../subcontracting_order/subcontracting_order.js | 14 ++++++++++++++ .../subcontracting_receipt.js | 11 +++++++++++ 2 files changed, 25 insertions(+) diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js index c4eea3fda45..c6ee3a3e0e3 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js @@ -9,6 +9,7 @@ frappe.ui.form.on("Subcontracting Order", { setup: (frm) => { frm.get_field("items").grid.cannot_add_rows = true; frm.get_field("items").grid.only_sortable(); + frm.trigger("set_queries"); frm.set_indicator_formatter("item_code", (doc) => (doc.qty <= doc.received_qty ? "green" : "orange")); @@ -93,6 +94,17 @@ frappe.ui.form.on("Subcontracting Order", { }); }, + set_queries: (frm) => { + frm.set_query("contact_person", erpnext.queries.contact_query); + frm.set_query("supplier_address", erpnext.queries.address_query); + + frm.set_query("billing_address", erpnext.queries.company_address_query); + + frm.set_query("shipping_address", () => { + return erpnext.queries.company_address_query(frm.doc); + }); + }, + onload: (frm) => { if (!frm.doc.transaction_date) { frm.set_value("transaction_date", frappe.datetime.get_today()); @@ -116,6 +128,8 @@ frappe.ui.form.on("Subcontracting Order", { }, refresh: function (frm) { + frappe.dynamic_link = { doc: frm.doc, fieldname: "supplier", doctype: "Supplier" }; + if (frm.doc.docstatus == 1 && frm.has_perm("submit")) { if (frm.doc.status == "Closed") { frm.add_custom_button( diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js index b4a127702e0..83113a223c2 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js @@ -28,6 +28,8 @@ frappe.ui.form.on("Subcontracting Receipt", { }, refresh: (frm) => { + frappe.dynamic_link = { doc: frm.doc, fieldname: "supplier", doctype: "Supplier" }; + if (frm.doc.docstatus === 1) { frm.add_custom_button( __("Stock Ledger"), @@ -165,6 +167,15 @@ frappe.ui.form.on("Subcontracting Receipt", { }; }); + frm.set_query("contact_person", erpnext.queries.contact_query); + frm.set_query("supplier_address", erpnext.queries.address_query); + + frm.set_query("billing_address", erpnext.queries.company_address_query); + + frm.set_query("shipping_address", () => { + return erpnext.queries.company_address_query(frm.doc); + }); + frm.set_query("rejected_warehouse", () => { return { filters: { From 076bf174396e045f8e87af165e40dbea718111cc Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 13:28:06 +0530 Subject: [PATCH 14/32] fix: incoming rate zero for supplied items in returned SCR (backport #42314) (#42315) fix: incoming rate zero for supplied items in returned SCR (#42314) (cherry picked from commit 61daa318fe01d36a26470baa17043b7c853565fd) Co-authored-by: rohitwaghchaure --- erpnext/controllers/subcontracting_controller.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index d31ee258b27..a6727ef8826 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -908,6 +908,7 @@ class SubcontractingController(StockController): item, { "item_code": item.rm_item_code, + "incoming_rate": item.rate if self.is_return else 0, "warehouse": self.supplier_warehouse, "actual_qty": -1 * flt(item.consumed_qty, item.precision("consumed_qty")), "dependant_sle_voucher_detail_no": item.reference_name, From f161e59cd7de888e6dc1975c736f480b0e4964ae Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 15 Jul 2024 11:33:46 +0530 Subject: [PATCH 15/32] fix(gross profit): incorrect valuation rate on different warehouses (cherry picked from commit f9d2dd0a62a42681871af874d8f6093ee6fdd715) --- erpnext/accounts/report/gross_profit/gross_profit.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index fe2746660eb..a9039a9cada 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -713,7 +713,8 @@ class GrossProfitGenerator: def get_average_buying_rate(self, row, item_code): args = row - if item_code not in self.average_buying_rate: + key = (item_code, row.warehouse) + if key not in self.average_buying_rate: args.update( { "voucher_type": row.parenttype, @@ -727,9 +728,9 @@ class GrossProfitGenerator: args.update({"serial_and_batch_bundle": row.serial_and_batch_bundle}) average_buying_rate = get_incoming_rate(args) - self.average_buying_rate[item_code] = flt(average_buying_rate) + self.average_buying_rate[key] = flt(average_buying_rate) - return self.average_buying_rate[item_code] + return self.average_buying_rate[key] def get_last_purchase_rate(self, item_code, row): purchase_invoice = frappe.qb.DocType("Purchase Invoice") From 43eec001ee94e73752f7bef928ec5eb1428ee820 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 15 Jul 2024 11:55:56 +0530 Subject: [PATCH 16/32] test(gross profit): valuation rate from different warehouse (cherry picked from commit 577ce5ccd490c188915179493ed522ffd02c40b4) # Conflicts: # erpnext/accounts/report/gross_profit/test_gross_profit.py --- .../report/gross_profit/test_gross_profit.py | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/erpnext/accounts/report/gross_profit/test_gross_profit.py b/erpnext/accounts/report/gross_profit/test_gross_profit.py index 741ea46a516..a55461c03de 100644 --- a/erpnext/accounts/report/gross_profit/test_gross_profit.py +++ b/erpnext/accounts/report/gross_profit/test_gross_profit.py @@ -557,4 +557,55 @@ class TestGrossProfit(FrappeTestCase): "gross_profit_%": 12.5, } gp_entry = [x for x in data if x.parent_invoice == sinv.name] +<<<<<<< HEAD self.assertDictContainsSubset(expected_entry, gp_entry[0]) +======= + self.assertEqual(gp_entry[0], gp_entry[0] | expected_entry) + + def test_valuation_rate_without_previous_sle(self): + """ + Test Valuation rate calculation when stock ledger is empty and invoices are against different warehouses + """ + stock_settings = frappe.get_doc("Stock Settings") + stock_settings.valuation_method = "FIFO" + stock_settings.save() + + item = create_item( + item_code="_Test Wirebound Notebook", + is_stock_item=1, + ) + item.allow_negative_stock = True + item.save() + self.item = item.item_code + + item.reload() + item.valuation_rate = 1900 + item.save() + sinv1 = self.create_sales_invoice(qty=1, rate=2000, posting_date=nowdate(), do_not_submit=True) + sinv1.update_stock = 1 + sinv1.set_warehouse = self.warehouse + sinv1.items[0].warehouse = self.warehouse + sinv1.save().submit() + + item.reload() + item.valuation_rate = 1800 + item.save() + sinv2 = self.create_sales_invoice(qty=1, rate=2000, posting_date=nowdate(), do_not_submit=True) + sinv2.update_stock = 1 + sinv2.set_warehouse = self.finished_warehouse + sinv2.items[0].warehouse = self.finished_warehouse + sinv2.save().submit() + + filters = frappe._dict( + company=self.company, from_date=nowdate(), to_date=nowdate(), group_by="Invoice" + ) + columns, data = execute(filters=filters) + + item_from_sinv1 = [x for x in data if x.parent_invoice == sinv1.name] + self.assertEqual(len(item_from_sinv1), 1) + self.assertEqual(1900, item_from_sinv1[0].valuation_rate) + + item_from_sinv2 = [x for x in data if x.parent_invoice == sinv2.name] + self.assertEqual(len(item_from_sinv2), 1) + self.assertEqual(1800, item_from_sinv2[0].valuation_rate) +>>>>>>> 577ce5ccd4 (test(gross profit): valuation rate from different warehouse) From cc09d0d218a8c59d70d2aef11c43045a1c777a4f Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 15 Jul 2024 14:34:30 +0530 Subject: [PATCH 17/32] chore: resolve conflict --- erpnext/accounts/report/gross_profit/test_gross_profit.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/accounts/report/gross_profit/test_gross_profit.py b/erpnext/accounts/report/gross_profit/test_gross_profit.py index a55461c03de..83de93891fe 100644 --- a/erpnext/accounts/report/gross_profit/test_gross_profit.py +++ b/erpnext/accounts/report/gross_profit/test_gross_profit.py @@ -557,10 +557,7 @@ class TestGrossProfit(FrappeTestCase): "gross_profit_%": 12.5, } gp_entry = [x for x in data if x.parent_invoice == sinv.name] -<<<<<<< HEAD self.assertDictContainsSubset(expected_entry, gp_entry[0]) -======= - self.assertEqual(gp_entry[0], gp_entry[0] | expected_entry) def test_valuation_rate_without_previous_sle(self): """ @@ -608,4 +605,3 @@ class TestGrossProfit(FrappeTestCase): item_from_sinv2 = [x for x in data if x.parent_invoice == sinv2.name] self.assertEqual(len(item_from_sinv2), 1) self.assertEqual(1800, item_from_sinv2[0].valuation_rate) ->>>>>>> 577ce5ccd4 (test(gross profit): valuation rate from different warehouse) From 804f1d4772e80994be17b276d9a0af7b66dde20d Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 12 Jul 2024 10:04:54 +0530 Subject: [PATCH 18/32] refactor: make reposting implicit (cherry picked from commit 722ef9232484df34b8a3cbcc817cdc47bc9e442d) --- erpnext/accounts/doctype/journal_entry/journal_entry.py | 1 + erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py | 1 + erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 1 + 3 files changed, 3 insertions(+) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 39f9aaf7a6c..12b9800e688 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -204,6 +204,7 @@ class JournalEntry(AccountsController): if self.needs_repost: self.validate_for_repost() self.db_set("repost_required", self.needs_repost) + self.repost_accounting_entries() def on_cancel(self): # References for this Journal are removed on the `on_cancel` event in accounts_controller diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 0f64c9d697f..15f53d996da 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -808,6 +808,7 @@ class PurchaseInvoice(BuyingController): if self.needs_repost: self.validate_for_repost() self.db_set("repost_required", self.needs_repost) + self.repost_accounting_entries() def make_gl_entries(self, gl_entries=None, from_repost=False): update_outstanding = "No" if (cint(self.is_paid) or self.write_off_account) else "Yes" diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index babcb417f23..7694c23236b 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -724,6 +724,7 @@ class SalesInvoice(SellingController): if self.needs_repost: self.validate_for_repost() self.db_set("repost_required", self.needs_repost) + self.repost_accounting_entries() def set_paid_amount(self): paid_amount = 0.0 From 4ac174703c714c1e4d180fe9bdd936e4c5da0058 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 15 Jul 2024 14:21:47 +0530 Subject: [PATCH 19/32] refactor(test): no need to assert repost_required flag Reposting happens implicitly upon 'Update After Submit' (cherry picked from commit 8f135e9859bad9fc0bc295b2583b166ed3ca87d9) --- erpnext/accounts/doctype/journal_entry/test_journal_entry.py | 4 ---- .../doctype/purchase_invoice/test_purchase_invoice.py | 2 -- 2 files changed, 6 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py index cf0aae96260..a1527139157 100644 --- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py @@ -456,10 +456,6 @@ class TestJournalEntry(unittest.TestCase): jv.accounts[1].cost_center = "_Test Cost Center for BS Account - _TC" jv.save() - # Check if repost flag gets set on update after submit - self.assertTrue(jv.repost_required) - jv.repost_accounting_entries() - # Check GL entries after reposting jv.load_from_db() self.expected_gle[0]["cost_center"] = "_Test Cost Center for BS Account - _TC" diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 4980c22fac1..e669efd071b 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -2007,8 +2007,6 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): pi.items[0].expense_account = "Service - _TC" pi.save() pi.load_from_db() - self.assertTrue(pi.repost_required) - pi.repost_accounting_entries() expected_gle = [ ["Creditors - _TC", 0.0, 1000, nowdate()], From 8e70aeae4abee1381b25546539a6562bb82febf0 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 15 Jul 2024 14:27:16 +0530 Subject: [PATCH 20/32] refactor(test): reposting happens implicitly (cherry picked from commit c283cda1694847c9561f3eecc39d3977ee995d32) --- .../accounts/doctype/sales_invoice/test_sales_invoice.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index b0d62339be0..ee4e82b4dfa 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2940,13 +2940,9 @@ class TestSalesInvoice(FrappeTestCase): si.items[0].income_account = "Service - _TC" si.additional_discount_account = "_Test Account Sales - _TC" si.taxes[0].account_head = "TDS Payable - _TC" + # Ledger reposted implicitly upon 'Update After Submit' si.save() - si.load_from_db() - self.assertTrue(si.repost_required) - - si.repost_accounting_entries() - expected_gle = [ ["_Test Account Sales - _TC", 22.0, 0.0, nowdate()], ["Debtors - _TC", 88, 0.0, nowdate()], From 4968395372e4f8df0036d4b14f09927dfb87673c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 15 Jul 2024 14:29:07 +0530 Subject: [PATCH 21/32] chore: contextual comments (cherry picked from commit 794a62aecb1248d3ed0a9581c1d30c852cc3f67b) --- erpnext/accounts/doctype/journal_entry/test_journal_entry.py | 1 + .../accounts/doctype/purchase_invoice/test_purchase_invoice.py | 1 + 2 files changed, 2 insertions(+) diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py index a1527139157..c53faf9ff39 100644 --- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py @@ -454,6 +454,7 @@ class TestJournalEntry(unittest.TestCase): # Change cost center for bank account - _Test Cost Center for BS Account create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company") jv.accounts[1].cost_center = "_Test Cost Center for BS Account - _TC" + # Ledger reposted implicitly upon 'Update After Submit' jv.save() # Check GL entries after reposting diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index e669efd071b..68e9d2b0e00 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -2005,6 +2005,7 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): check_gl_entries(self, pi.name, expected_gle, nowdate()) pi.items[0].expense_account = "Service - _TC" + # Ledger reposted implicitly upon 'Update After Submit' pi.save() pi.load_from_db() From cf2651dd8597a4aacae9b6f89fea7d2ea138851d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 16:52:28 +0530 Subject: [PATCH 22/32] fix: not able to cancel the inter transfer DN (backport #42333) (#42340) fix: not able to cancel the inter transfer DN (#42333) (cherry picked from commit 6d42cd0f4c7762edd40b6bf0d2ab9c1c0d9ccb9e) Co-authored-by: rohitwaghchaure --- erpnext/controllers/buying_controller.py | 14 ++- erpnext/controllers/selling_controller.py | 4 +- .../purchase_receipt/test_purchase_receipt.py | 116 ++++++++++++++++++ 3 files changed, 131 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 30a5f38400e..958ac266e61 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -541,7 +541,9 @@ class BuyingController(SubcontractingController): "actual_qty": flt(pr_qty), "serial_and_batch_bundle": ( d.serial_and_batch_bundle - if not self.is_internal_transfer() or self.is_return + if not self.is_internal_transfer() + or self.is_return + or (self.is_internal_transfer() and self.docstatus == 2) else self.get_package_for_target_warehouse( d, type_of_transaction=type_of_transaction ) @@ -580,6 +582,14 @@ class BuyingController(SubcontractingController): (not cint(self.is_return) and self.docstatus == 2) or (cint(self.is_return) and self.docstatus == 1) ): + serial_and_batch_bundle = None + if self.is_internal_transfer() and self.docstatus == 2: + serial_and_batch_bundle = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_detail_no": d.name, "warehouse": d.warehouse}, + "serial_and_batch_bundle", + ) + from_warehouse_sle = self.get_sl_entries( d, { @@ -589,7 +599,7 @@ class BuyingController(SubcontractingController): "serial_and_batch_bundle": ( self.get_package_for_target_warehouse(d, d.from_warehouse, "Inward") if self.is_internal_transfer() and self.is_return - else None + else serial_and_batch_bundle ), }, ) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 71e40d509b2..7a85d2230f1 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -538,7 +538,9 @@ class SellingController(StockController): self.make_sl_entries(sl_entries) def get_sle_for_source_warehouse(self, item_row): - serial_and_batch_bundle = item_row.serial_and_batch_bundle + serial_and_batch_bundle = ( + item_row.serial_and_batch_bundle if not self.is_internal_transfer() else None + ) if serial_and_batch_bundle and self.is_internal_transfer() and self.is_return: if self.docstatus == 1: serial_and_batch_bundle = self.make_package_for_transfer( diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 869d3ed3cce..736ced72f33 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -3350,6 +3350,122 @@ class TestPurchaseReceipt(FrappeTestCase): self.assertEqual(pr.grand_total, 0.0) self.assertEqual(pr.status, "Completed") + def test_internal_transfer_for_batch_items_with_cancel(self): + from erpnext.controllers.sales_and_purchase_return import make_return_doc + from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt + from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note + + frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0) + + prepare_data_for_internal_transfer() + + customer = "_Test Internal Customer 2" + company = "_Test Company with perpetual inventory" + + batch_item_doc = make_item( + "_Test Batch Item For Stock Transfer Cancel Case", + {"has_batch_no": 1, "create_new_batch": 1, "batch_number_series": "USBF-BT-CANBIFST-.####"}, + ) + + serial_item_doc = make_item( + "_Test Serial No Item For Stock Transfer Cancel Case", + {"has_serial_no": 1, "serial_no_series": "USBF-BT-CANBIFST-.####"}, + ) + + inward_entry = make_purchase_receipt( + item_code=batch_item_doc.name, + qty=10, + rate=150, + warehouse="Stores - TCP1", + company="_Test Company with perpetual inventory", + use_serial_batch_fields=0, + do_not_submit=1, + ) + + inward_entry.append( + "items", + { + "item_code": serial_item_doc.name, + "qty": 15, + "rate": 250, + "item_name": serial_item_doc.item_name, + "conversion_factor": 1.0, + "uom": serial_item_doc.stock_uom, + "stock_uom": serial_item_doc.stock_uom, + "warehouse": "Stores - TCP1", + "use_serial_batch_fields": 0, + }, + ) + + inward_entry.submit() + inward_entry.reload() + + for row in inward_entry.items: + self.assertTrue(row.serial_and_batch_bundle) + + inter_transfer_dn = create_delivery_note( + item_code=inward_entry.items[0].item_code, + company=company, + customer=customer, + cost_center="Main - TCP1", + expense_account="Cost of Goods Sold - TCP1", + qty=10, + rate=500, + warehouse="Stores - TCP1", + target_warehouse="Work In Progress - TCP1", + batch_no=get_batch_from_bundle(inward_entry.items[0].serial_and_batch_bundle), + use_serial_batch_fields=0, + do_not_submit=1, + ) + + inter_transfer_dn.append( + "items", + { + "item_code": serial_item_doc.name, + "qty": 15, + "rate": 350, + "item_name": serial_item_doc.item_name, + "conversion_factor": 1.0, + "uom": serial_item_doc.stock_uom, + "stock_uom": serial_item_doc.stock_uom, + "warehouse": "Stores - TCP1", + "target_warehouse": "Work In Progress - TCP1", + "serial_no": "\n".join( + get_serial_nos_from_bundle(inward_entry.items[1].serial_and_batch_bundle) + ), + "use_serial_batch_fields": 0, + }, + ) + + inter_transfer_dn.submit() + inter_transfer_dn.reload() + for row in inter_transfer_dn.items: + if row.item_code == batch_item_doc.name: + self.assertEqual(row.rate, 150.0) + else: + self.assertEqual(row.rate, 250.0) + + self.assertTrue(row.serial_and_batch_bundle) + + inter_transfer_pr = make_inter_company_purchase_receipt(inter_transfer_dn.name) + for row in inter_transfer_pr.items: + row.from_warehouse = "Work In Progress - TCP1" + row.warehouse = "Stores - TCP1" + inter_transfer_pr.submit() + + for row in inter_transfer_pr.items: + if row.item_code == batch_item_doc.name: + self.assertEqual(row.rate, 150.0) + else: + self.assertEqual(row.rate, 250.0) + + self.assertTrue(row.serial_and_batch_bundle) + + inter_transfer_pr.cancel() + inter_transfer_dn.cancel() + + frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1) + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier From 5f1d6ede31fd37454fd9de5f6500481ad3b4267a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 22:58:30 +0530 Subject: [PATCH 23/32] feat: create variant with/without image (backport #41317) (#42343) feat: create variant with/without image (#41317) * feat: create variant with/without image * feat: create variant with/without image * feat: create variant with/without image * feat: create variant with/without image * feat: create variant with/without image * feat: create variant with/without image * fix: change the variable name use_same_image to use_template_image (cherry picked from commit 66b35ec9fb21723d237ce04c1976aa2caed58871) Co-authored-by: Nihantra C. Patel <141945075+Nihantra-Patel@users.noreply.github.com> --- erpnext/controllers/item_variant.py | 21 ++++++++++++++++----- erpnext/stock/doctype/item/item.js | 24 +++++++++++++++++++++++- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/erpnext/controllers/item_variant.py b/erpnext/controllers/item_variant.py index 7a1db6d2653..cc6870f892a 100644 --- a/erpnext/controllers/item_variant.py +++ b/erpnext/controllers/item_variant.py @@ -41,7 +41,8 @@ def get_variant(template, args=None, variant=None, manufacturer=None, manufactur if isinstance(args, str): args = json.loads(args) - if not args: + attribute_args = {k: v for k, v in args.items() if k != "use_template_image"} + if not attribute_args: frappe.throw(_("Please specify at least one attribute in the Attributes table")) return find_variant(template, args, variant) @@ -197,7 +198,8 @@ def find_variant(template, args, variant_item_code=None): @frappe.whitelist() -def create_variant(item, args): +def create_variant(item, args, use_template_image=False): + use_template_image = frappe.parse_json(use_template_image) if isinstance(args, str): args = json.loads(args) @@ -211,13 +213,18 @@ def create_variant(item, args): variant.set("attributes", variant_attributes) copy_attributes_to_variant(template, variant) + + if use_template_image and template.image: + variant.image = template.image + make_variant_item_code(template.item_code, template.item_name, variant) return variant @frappe.whitelist() -def enqueue_multiple_variant_creation(item, args): +def enqueue_multiple_variant_creation(item, args, use_template_image=False): + use_template_image = frappe.parse_json(use_template_image) # There can be innumerable attribute combinations, enqueue if isinstance(args, str): variants = json.loads(args) @@ -228,27 +235,31 @@ def enqueue_multiple_variant_creation(item, args): frappe.throw(_("Please do not create more than 500 items at a time")) return if total_variants < 10: - return create_multiple_variants(item, args) + return create_multiple_variants(item, args, use_template_image) else: frappe.enqueue( "erpnext.controllers.item_variant.create_multiple_variants", item=item, args=args, + use_template_image=use_template_image, now=frappe.flags.in_test, ) return "queued" -def create_multiple_variants(item, args): +def create_multiple_variants(item, args, use_template_image=False): count = 0 if isinstance(args, str): args = json.loads(args) + template_item = frappe.get_doc("Item", item) args_set = generate_keyed_value_combinations(args) for attribute_values in args_set: if not get_variant(item, args=attribute_values): variant = create_variant(item, attribute_values) + if use_template_image and template_item.image: + variant.image = template_item.image variant.save() count += 1 diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index d92a998a471..81eeb914af0 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -587,6 +587,14 @@ $.extend(erpnext.item, { me.multiple_variant_dialog = new frappe.ui.Dialog({ title: __("Select Attribute Values"), fields: [ + frm.doc.image + ? { + fieldtype: "Check", + label: __("Create a variant with the template image."), + fieldname: "use_template_image", + default: 0, + } + : null, { fieldtype: "HTML", fieldname: "help", @@ -594,11 +602,14 @@ $.extend(erpnext.item, { ${__("Select at least one value from each of the attributes.")} `, }, - ].concat(fields), + ] + .concat(fields) + .filter(Boolean), }); me.multiple_variant_dialog.set_primary_action(__("Create Variants"), () => { let selected_attributes = get_selected_attributes(); + let use_template_image = me.multiple_variant_dialog.get_value("use_template_image"); me.multiple_variant_dialog.hide(); frappe.call({ @@ -606,6 +617,7 @@ $.extend(erpnext.item, { args: { item: frm.doc.name, args: selected_attributes, + use_template_image: use_template_image, }, callback: function (r) { if (r.message === "queued") { @@ -720,6 +732,15 @@ $.extend(erpnext.item, { }); } + if (frm.doc.image) { + fields.push({ + fieldtype: "Check", + label: __("Create a variant with the template image."), + fieldname: "use_template_image", + default: 0, + }); + } + var d = new frappe.ui.Dialog({ title: __("Create Variant"), fields: fields, @@ -761,6 +782,7 @@ $.extend(erpnext.item, { args: { item: frm.doc.name, args: d.get_values(), + use_template_image: args.use_template_image, }, callback: function (r) { var doclist = frappe.model.sync(r.message); From 1754adfcd64985866f0cf8d3aa5b2a6414ebc374 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 16 Jul 2024 15:46:44 +0530 Subject: [PATCH 24/32] fix: extra qty pick in pick list (backport #42345) (#42349) fix: extra qty pick in pick list (#42345) (cherry picked from commit 6a50b4097685c9aa8ddf99e88990b87d7eb794d5) Co-authored-by: rohitwaghchaure --- .../doctype/sales_order/sales_order.py | 11 +++++++ .../stock/doctype/pick_list/test_pick_list.py | 31 +++++++++++++++++++ .../stock_settings/stock_settings.json | 9 +++++- .../doctype/stock_settings/stock_settings.py | 1 + 4 files changed, 51 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index a00b08ab097..38a089fdb03 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -603,6 +603,17 @@ class SalesOrder(SellingController): if total_picked_qty and total_qty: per_picked = total_picked_qty / total_qty * 100 + pick_percentage = frappe.db.get_single_value("Stock Settings", "over_picking_allowance") + if pick_percentage: + total_qty += flt(total_qty) * (pick_percentage / 100) + + if total_picked_qty > total_qty: + frappe.throw( + _( + "Total Picked Quantity {0} is more than ordered qty {1}. You can set the Over Picking Allowance in Stock Settings." + ).format(total_picked_qty, total_qty) + ) + self.db_set("per_picked", flt(per_picked), update_modified=False) def set_indicator(self): diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py index b1c03bf8453..9fc304e4e21 100644 --- a/erpnext/stock/doctype/pick_list/test_pick_list.py +++ b/erpnext/stock/doctype/pick_list/test_pick_list.py @@ -1174,3 +1174,34 @@ class TestPickList(FrappeTestCase): row.qty = row.qty + 10 self.assertRaises(frappe.ValidationError, pl.save) + + def test_over_allowance_picking(self): + warehouse = "_Test Warehouse - _TC" + item = make_item( + "Test Over Allowance Picking Item", + properties={ + "is_stock_item": 1, + }, + ).name + + make_stock_entry(item=item, to_warehouse=warehouse, qty=100) + + so = make_sales_order(item_code=item, qty=10, rate=100) + + pl_doc = create_pick_list(so.name) + pl_doc.save() + self.assertEqual(pl_doc.locations[0].qty, 10) + + pl_doc.locations[0].qty = 15 + pl_doc.locations[0].stock_qty = 15 + pl_doc.save() + + self.assertEqual(pl_doc.locations[0].qty, 15) + self.assertRaises(frappe.ValidationError, pl_doc.submit) + + frappe.db.set_single_value("Stock Settings", "over_picking_allowance", 50) + + pl_doc.reload() + pl_doc.submit() + + frappe.db.set_single_value("Stock Settings", "over_picking_allowance", 0) diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index 5d9ddaccedf..431fdf681ff 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -26,6 +26,7 @@ "section_break_9", "over_delivery_receipt_allowance", "mr_qty_allowance", + "over_picking_allowance", "column_break_121", "role_allowed_to_over_deliver_receive", "allow_negative_stock", @@ -446,6 +447,12 @@ "fieldname": "do_not_use_batchwise_valuation", "fieldtype": "Check", "label": "Do Not Use Batch-wise Valuation" + }, + { + "description": "The percentage you are allowed to pick more items in the pick list than the ordered quantity.", + "fieldname": "over_picking_allowance", + "fieldtype": "Percent", + "label": "Over Picking Allowance" } ], "icon": "icon-cog", @@ -453,7 +460,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2024-07-04 12:45:09.811280", + "modified": "2024-07-15 17:18:23.872161", "modified_by": "Administrator", "module": "Stock", "name": "Stock Settings", diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.py b/erpnext/stock/doctype/stock_settings/stock_settings.py index e786b1f67c6..fae75f49777 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.py +++ b/erpnext/stock/doctype/stock_settings/stock_settings.py @@ -47,6 +47,7 @@ class StockSettings(Document): mr_qty_allowance: DF.Float naming_series_prefix: DF.Data | None over_delivery_receipt_allowance: DF.Float + over_picking_allowance: DF.Percent pick_serial_and_batch_based_on: DF.Literal["FIFO", "LIFO", "Expiry"] reorder_email_notify: DF.Check role_allowed_to_create_edit_back_dated_transactions: DF.Link | None From b741b2a2858e0d106635c6be40ee2a6781af4d57 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 16 Jul 2024 16:08:11 +0530 Subject: [PATCH 25/32] fix: remove doctype link from serial no ledger report (backport #42327) (#42348) fix: remove doctype link from serial no ledger report (#42327) * fix: remove doctype link from serial no ledger report * fix: remove doctype link from serial no ledger report * fix: remove doctype link from serial no ledger report --update (cherry picked from commit 17b437709c3b34d102947964bf800e24285d7a47) Co-authored-by: Nihantra C. Patel <141945075+Nihantra-Patel@users.noreply.github.com> --- erpnext/stock/report/serial_no_ledger/serial_no_ledger.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py index 6ef02724f65..894a740c177 100644 --- a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py +++ b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py @@ -77,9 +77,8 @@ def get_columns(filters): }, { "label": _("Party Type"), - "fieldtype": "Link", + "fieldtype": "Data", "fieldname": "party_type", - "options": "DocType", "width": 90, }, { From 29ee2d46f0e493a3f22775a0cb7646555496c668 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 16 Jul 2024 16:30:11 +0530 Subject: [PATCH 26/32] fix: bin deadlock issue (backport #42342) (#42357) fix: bin deadlock issue (#42342) (cherry picked from commit 21df38bf18e51ec4a0def01278196cadb08ad005) Co-authored-by: rohitwaghchaure --- 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 51d2708be24..ee5893eb826 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -200,7 +200,7 @@ def get_bin(item_code, warehouse): if not bin: bin_obj = _create_bin(item_code, warehouse) else: - bin_obj = frappe.get_doc("Bin", bin, for_update=True) + bin_obj = frappe.get_doc("Bin", bin) bin_obj.flags.ignore_permissions = True return bin_obj From 62fc42803f69a6e8a99b88f993276d0207a30792 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 16 Jul 2024 16:30:24 +0530 Subject: [PATCH 27/32] fix: same posting date and time causing incorrect valuation rate (backport #42351) (#42356) fix: same posting date and time causing incorrect valuation rate (#42351) (cherry picked from commit 85d2d3411683832e8f4fbe54790da3327f03cc2a) Co-authored-by: rohitwaghchaure --- .../delivery_note/test_delivery_note.py | 87 +++++++++++++++++++ .../serial_and_batch_bundle.py | 1 + erpnext/stock/serial_batch_bundle.py | 12 ++- 3 files changed, 97 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 5f44548a873..f27db868a07 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -1918,6 +1918,93 @@ class TestDeliveryNote(FrappeTestCase): returned_serial_nos = get_serial_nos_from_bundle(dn_return.items[0].serial_and_batch_bundle) self.assertEqual(serial_nos, returned_serial_nos) + def test_same_posting_date_and_posting_time(self): + item_code = make_item( + "Test Same Posting Datetime Item", + properties={ + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "SS-ART11-TESTBATCH.#####", + "is_stock_item": 1, + }, + ).name + + se = make_stock_entry( + item_code=item_code, + target="_Test Warehouse - _TC", + qty=100, + basic_rate=50, + posting_date=add_days(nowdate(), -1), + ) + + batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle) + + posting_date = today() + posting_time = nowtime() + dn1 = create_delivery_note( + posting_date=posting_date, + posting_time=posting_time, + item_code=item_code, + rate=300, + qty=25, + batch_no=batch_no, + use_serial_batch_fields=1, + ) + + dn2 = create_delivery_note( + posting_date=posting_date, + posting_time=posting_time, + item_code=item_code, + rate=300, + qty=25, + batch_no=batch_no, + use_serial_batch_fields=1, + ) + + dn3 = create_delivery_note( + posting_date=posting_date, + posting_time=posting_time, + item_code=item_code, + rate=300, + qty=25, + batch_no=batch_no, + use_serial_batch_fields=1, + ) + + dn4 = create_delivery_note( + posting_date=posting_date, + posting_time=posting_time, + item_code=item_code, + rate=300, + qty=25, + batch_no=batch_no, + use_serial_batch_fields=1, + ) + + for dn in [dn1, dn2, dn3, dn4]: + sles = frappe.get_all( + "Stock Ledger Entry", + fields=["stock_value_difference", "actual_qty"], + filters={"is_cancelled": 0, "voucher_no": dn.name, "docstatus": 1}, + ) + + for sle in sles: + self.assertEqual(sle.actual_qty, 25.0 * -1) + self.assertEqual(sle.stock_value_difference, 25.0 * 50 * -1) + + dn5 = create_delivery_note( + posting_date=posting_date, + posting_time=posting_time, + item_code=item_code, + rate=300, + qty=25, + batch_no=batch_no, + use_serial_batch_fields=1, + do_not_submit=True, + ) + + self.assertRaises(frappe.ValidationError, dn5.submit) + def create_delivery_note(**args): dn = frappe.new_doc("Delivery Note") diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index b8b17cb4c81..741d1fdfae9 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -300,6 +300,7 @@ class SerialandBatchBundle(Document): "batch_nos": {row.batch_no: row for row in self.entries if row.batch_no}, "voucher_type": self.voucher_type, "voucher_detail_no": self.voucher_detail_no, + "creation": self.creation, } ) diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index c5346d2b0a3..2207b2e3c74 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -599,9 +599,15 @@ class BatchNoValuation(DeprecatedBatchNoValuation): timestamp_condition = "" if self.sle.posting_date and self.sle.posting_time: - timestamp_condition = CombineDatetime( - parent.posting_date, parent.posting_time - ) <= CombineDatetime(self.sle.posting_date, self.sle.posting_time) + timestamp_condition = CombineDatetime(parent.posting_date, parent.posting_time) < CombineDatetime( + self.sle.posting_date, self.sle.posting_time + ) + + if self.sle.creation: + timestamp_condition |= ( + CombineDatetime(parent.posting_date, parent.posting_time) + == CombineDatetime(self.sle.posting_date, self.sle.posting_time) + ) & (parent.creation < self.sle.creation) query = ( frappe.qb.from_(parent) From 2ffe7d5838464a222047cd26a045bc522da86edd Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Tue, 16 Jul 2024 17:45:29 +0530 Subject: [PATCH 28/32] fix: service item capitalization (#42188) * feat: capitalize with service expenses only * chore: added test * refactor: removed Capitalized In field from asset doc (cherry picked from commit 81e0b96c308d41455adc2ee4909fccadb613233e) # Conflicts: # erpnext/assets/doctype/asset/asset.py --- erpnext/assets/doctype/asset/asset.js | 4 +- erpnext/assets/doctype/asset/asset.json | 15 ++---- erpnext/assets/doctype/asset/asset.py | 17 +++++-- .../asset_capitalization.py | 22 +++----- .../test_asset_capitalization.py | 50 +++++++++++++++++++ ...ncelled_asset_capitalization_from_asset.py | 11 ---- 6 files changed, 76 insertions(+), 43 deletions(-) delete mode 100644 erpnext/patches/v15_0/remove_cancelled_asset_capitalization_from_asset.py diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 5ed62cb132f..c05adf6bb86 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -187,7 +187,7 @@ frappe.ui.form.on("Asset", { if (frm.doc.docstatus == 0) { frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation); - if (frm.doc.is_composite_asset && !frm.doc.capitalized_in) { + if (frm.doc.is_composite_asset) { $(".primary-action").prop("hidden", true); $(".form-message").text("Capitalize this asset to confirm"); @@ -511,6 +511,8 @@ frappe.ui.form.on("Asset", { frappe.call({ args: { asset: frm.doc.name, + asset_name: frm.doc.asset_name, + item_code: frm.doc.item_code, }, method: "erpnext.assets.doctype.asset.asset.create_asset_capitalization", callback: function (r) { diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index 99a430cbb40..152c40c00b3 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -75,8 +75,7 @@ "purchase_amount", "default_finance_book", "depr_entry_posting_status", - "amended_from", - "capitalized_in" + "amended_from" ], "fields": [ { @@ -222,7 +221,7 @@ "read_only": 1 }, { - "depends_on": "eval:!(doc.is_composite_asset && !doc.capitalized_in)", + "depends_on": "eval:!doc.is_composite_asset", "fieldname": "gross_purchase_amount", "fieldtype": "Currency", "label": "Gross Purchase Amount", @@ -508,14 +507,6 @@ "fieldtype": "Check", "label": "Is Composite Asset" }, - { - "fieldname": "capitalized_in", - "fieldtype": "Link", - "hidden": 1, - "label": "Capitalized In", - "options": "Asset Capitalization", - "read_only": 1 - }, { "depends_on": "eval:doc.docstatus > 0", "fieldname": "total_asset_cost", @@ -589,7 +580,7 @@ "link_fieldname": "target_asset" } ], - "modified": "2024-05-21 13:46:21.066483", + "modified": "2024-07-07 22:27:14.733839", "modified_by": "Administrator", "module": "Assets", "name": "Asset", diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 8641bb33fad..864b0ebf0dd 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -60,7 +60,6 @@ class Asset(AccountsController): available_for_use_date: DF.Date | None booked_fixed_asset: DF.Check calculate_depreciation: DF.Check - capitalized_in: DF.Link | None company: DF.Link comprehensive_insurance: DF.Data | None cost_center: DF.Link | None @@ -162,7 +161,11 @@ class Asset(AccountsController): def on_cancel(self): self.validate_cancellation() self.cancel_movement_entries() +<<<<<<< HEAD self.cancel_capitalization() +======= + self.reload() +>>>>>>> 81e0b96c30 (fix: service item capitalization (#42188)) self.delete_depreciation_entries() cancel_asset_depr_schedules(self) self.set_status() @@ -524,6 +527,7 @@ class Asset(AccountsController): movement = frappe.get_doc("Asset Movement", movement.get("name")) movement.cancel() +<<<<<<< HEAD def cancel_capitalization(self): asset_capitalization = frappe.db.get_value( "Asset Capitalization", @@ -534,6 +538,8 @@ class Asset(AccountsController): asset_capitalization = frappe.get_doc("Asset Capitalization", asset_capitalization) asset_capitalization.cancel() +======= +>>>>>>> 81e0b96c30 (fix: service item capitalization (#42188)) def delete_depreciation_entries(self): if self.calculate_depreciation: for row in self.get("finance_books"): @@ -872,10 +878,15 @@ def create_asset_repair(asset, asset_name): @frappe.whitelist() -def create_asset_capitalization(asset): +def create_asset_capitalization(asset, asset_name, item_code): asset_capitalization = frappe.new_doc("Asset Capitalization") asset_capitalization.update( - {"target_asset": asset, "capitalization_method": "Choose a WIP composite asset"} + { + "target_asset": asset, + "capitalization_method": "Choose a WIP composite asset", + "target_asset_name": asset_name, + "target_item_code": item_code, + } ) return asset_capitalization diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index 573dd92c585..4c22f7ddd7a 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -138,22 +138,10 @@ class AssetCapitalization(StockController): "Asset", "Asset Movement", ) - self.cancel_target_asset() self.update_stock_ledger() self.make_gl_entries() self.restore_consumed_asset_items() - def on_trash(self): - frappe.db.set_value("Asset", self.target_asset, "capitalized_in", None) - super().on_trash() - - def cancel_target_asset(self): - if self.entry_type == "Capitalization" and self.target_asset: - asset_doc = frappe.get_doc("Asset", self.target_asset) - asset_doc.db_set("capitalized_in", None) - if asset_doc.docstatus == 1: - asset_doc.cancel() - def set_title(self): self.title = self.target_asset_name or self.target_item_name or self.target_item_code @@ -329,8 +317,12 @@ class AssetCapitalization(StockController): if not self.target_is_fixed_asset and not self.get("asset_items"): frappe.throw(_("Consumed Asset Items is mandatory for Decapitalization")) - if not self.get("stock_items") and not self.get("asset_items"): - frappe.throw(_("Consumed Stock Items or Consumed Asset Items is mandatory for Capitalization")) + if not (self.get("stock_items") or self.get("asset_items") or self.get("service_items")): + frappe.throw( + _( + "Consumed Stock Items, Consumed Asset Items or Consumed Service Items is mandatory for Capitalization" + ) + ) def validate_item(self, item): from erpnext.stock.doctype.item.item import validate_end_of_life @@ -617,7 +609,6 @@ class AssetCapitalization(StockController): asset_doc.purchase_date = self.posting_date asset_doc.gross_purchase_amount = total_target_asset_value asset_doc.purchase_amount = total_target_asset_value - asset_doc.capitalized_in = self.name asset_doc.flags.ignore_validate = True asset_doc.flags.asset_created_via_asset_capitalization = True asset_doc.insert() @@ -653,7 +644,6 @@ class AssetCapitalization(StockController): asset_doc = frappe.get_doc("Asset", self.target_asset) asset_doc.gross_purchase_amount = total_target_asset_value asset_doc.purchase_amount = total_target_asset_value - asset_doc.capitalized_in = self.name asset_doc.flags.ignore_validate = True asset_doc.save() diff --git a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py index 31723ef3be3..5508bdcbef2 100644 --- a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py @@ -386,6 +386,56 @@ class TestAssetCapitalization(unittest.TestCase): self.assertFalse(get_actual_gle_dict(asset_capitalization.name)) self.assertFalse(get_actual_sle_dict(asset_capitalization.name)) + def test_capitalize_only_service_item(self): + company = "_Test Company" + # Variables + + service_rate = 500 + service_qty = 2 + service_amount = 1000 + + total_amount = 1000 + + wip_composite_asset = create_asset( + asset_name="Asset Capitalization WIP Composite Asset", + is_composite_asset=1, + warehouse="Stores - TCP1", + company=company, + ) + + # Create and submit Asset Captitalization + asset_capitalization = create_asset_capitalization( + entry_type="Capitalization", + capitalization_method="Choose a WIP composite asset", + target_asset=wip_composite_asset.name, + target_asset_location="Test Location", + service_qty=service_qty, + service_rate=service_rate, + service_expense_account="Expenses Included In Asset Valuation - _TC", + company=company, + submit=1, + ) + + self.assertEqual(asset_capitalization.service_items[0].amount, service_amount) + self.assertEqual(asset_capitalization.service_items_total, service_amount) + + target_asset = frappe.get_doc("Asset", asset_capitalization.target_asset) + self.assertEqual(target_asset.gross_purchase_amount, total_amount) + self.assertEqual(target_asset.purchase_amount, total_amount) + + expected_gle = { + "_Test Fixed Asset - _TC": 1000.0, + "Expenses Included In Asset Valuation - _TC": -1000.0, + } + + actual_gle = get_actual_gle_dict(asset_capitalization.name) + self.assertEqual(actual_gle, expected_gle) + + # Cancel Asset Capitalization and make test entries and status are reversed + asset_capitalization.cancel() + self.assertFalse(get_actual_gle_dict(asset_capitalization.name)) + self.assertFalse(get_actual_sle_dict(asset_capitalization.name)) + def create_asset_capitalization_data(): create_item("Capitalization Target Stock Item", is_stock_item=1, is_fixed_asset=0, is_purchase_item=0) diff --git a/erpnext/patches/v15_0/remove_cancelled_asset_capitalization_from_asset.py b/erpnext/patches/v15_0/remove_cancelled_asset_capitalization_from_asset.py deleted file mode 100644 index cb39a9280e4..00000000000 --- a/erpnext/patches/v15_0/remove_cancelled_asset_capitalization_from_asset.py +++ /dev/null @@ -1,11 +0,0 @@ -import frappe - - -def execute(): - cancelled_asset_capitalizations = frappe.get_all( - "Asset Capitalization", - filters={"docstatus": 2}, - fields=["name", "target_asset"], - ) - for asset_capitalization in cancelled_asset_capitalizations: - frappe.db.set_value("Asset", asset_capitalization.target_asset, "capitalized_in", None) From b5a2e5a375b39edf5f22a4d2276faeb3a4f85ced Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 16 Jul 2024 18:45:18 +0530 Subject: [PATCH 29/32] fix: items not fetching in End Transit entry (backport #42358) (#42361) fix: items not fetching in End Transit entry (#42358) (cherry picked from commit 001e5b612bec6aa8e2f9c9a5b2a7d8cfc2d632f5) Co-authored-by: rohitwaghchaure --- erpnext/stock/doctype/stock_entry/stock_entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index f08ae6c286c..36f6f354bdf 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -2756,7 +2756,7 @@ def make_stock_in_entry(source_name, target_doc=None): "batch_no": "batch_no", }, "postprocess": update_item, - "condition": lambda doc: flt(doc.qty) - flt(doc.transferred_qty) > 0.01, + "condition": lambda doc: flt(doc.qty) - flt(doc.transferred_qty) > 0.00001, }, }, target_doc, From 0d2ef0df7dbc48d759fccd7516b5382eb4f45324 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 16 Jul 2024 14:33:52 +0530 Subject: [PATCH 30/32] fix: show total rows credit row in balance sheet (cherry picked from commit 327b19cba68e610be7f0ae47ef5a1e1bd452bcdc) --- .../report/balance_sheet/balance_sheet.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.py b/erpnext/accounts/report/balance_sheet/balance_sheet.py index aa78f4b6204..e89a177a867 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.py +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.py @@ -123,19 +123,15 @@ def get_provisional_profit_loss( for period in period_list: key = period if consolidated else period.key total_assets = flt(asset[0].get(key)) + effective_liability = 0.00 - if liability or equity: - effective_liability = 0.0 - if liability: - effective_liability += flt(liability[0].get(key)) - if equity: - effective_liability += flt(equity[0].get(key)) + if liability: + effective_liability += flt(liability[0].get(key)) + if equity: + effective_liability += flt(equity[0].get(key)) - provisional_profit_loss[key] = total_assets - effective_liability - else: - provisional_profit_loss[key] = total_assets - - total_row[key] = provisional_profit_loss[key] + provisional_profit_loss[key] = total_assets - effective_liability + total_row[key] = provisional_profit_loss[key] + effective_liability if provisional_profit_loss[key]: has_value = True From cfab9568112653c94b69fdb306e677c3ac7b7987 Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Tue, 16 Jul 2024 22:46:54 +0530 Subject: [PATCH 31/32] chore: resolved conflicts --- erpnext/assets/doctype/asset/asset.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 864b0ebf0dd..e69fb728520 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -161,11 +161,7 @@ class Asset(AccountsController): def on_cancel(self): self.validate_cancellation() self.cancel_movement_entries() -<<<<<<< HEAD - self.cancel_capitalization() -======= self.reload() ->>>>>>> 81e0b96c30 (fix: service item capitalization (#42188)) self.delete_depreciation_entries() cancel_asset_depr_schedules(self) self.set_status() @@ -527,19 +523,6 @@ class Asset(AccountsController): movement = frappe.get_doc("Asset Movement", movement.get("name")) movement.cancel() -<<<<<<< HEAD - def cancel_capitalization(self): - asset_capitalization = frappe.db.get_value( - "Asset Capitalization", - {"target_asset": self.name, "docstatus": 1, "entry_type": "Capitalization"}, - ) - - if asset_capitalization: - asset_capitalization = frappe.get_doc("Asset Capitalization", asset_capitalization) - asset_capitalization.cancel() - -======= ->>>>>>> 81e0b96c30 (fix: service item capitalization (#42188)) def delete_depreciation_entries(self): if self.calculate_depreciation: for row in self.get("finance_books"): From c45d11cd60d21064564d8266d70db6c08a25a91d Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Wed, 17 Jul 2024 01:35:03 +0530 Subject: [PATCH 32/32] fix: removed patch from patches.txt --- erpnext/patches.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index b661e6f99a4..158d68ff275 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -362,7 +362,6 @@ erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2 erpnext.patches.v14_0.set_maintain_stock_for_bom_item erpnext.patches.v15_0.delete_orphaned_asset_movement_item_records erpnext.patches.v15_0.fix_debit_credit_in_transaction_currency -erpnext.patches.v15_0.remove_cancelled_asset_capitalization_from_asset erpnext.patches.v15_0.rename_purchase_receipt_amount_to_purchase_amount erpnext.patches.v14_0.enable_set_priority_for_pricing_rules #1 erpnext.patches.v15_0.rename_number_of_depreciations_booked_to_opening_booked_depreciations