From 3834d6fbce4a52f356c36a1915566aa7a43d99be Mon Sep 17 00:00:00 2001 From: Marc Ramser Date: Wed, 19 Mar 2025 09:04:15 +0100 Subject: [PATCH 01/20] feat(projects): add option to hide timesheets for project users (#46173) * feat: add option to hide timesheets for project users * Added a new "Hide timesheets" checkbox field to Project User doctype that allows to control timesheet visibility for specific users. When enabled, the timesheets section will not be displayed on the project page for that user. * Update projects.html (cherry picked from commit f4aba561ce894bc6d9fded0330b265d07414c330) --- erpnext/projects/doctype/project_user/project_user.json | 8 ++++++++ erpnext/templates/pages/projects.html | 6 ++---- erpnext/templates/pages/projects.py | 5 +++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/erpnext/projects/doctype/project_user/project_user.json b/erpnext/projects/doctype/project_user/project_user.json index 2f452cc2d75..e82bbfab7bf 100644 --- a/erpnext/projects/doctype/project_user/project_user.json +++ b/erpnext/projects/doctype/project_user/project_user.json @@ -12,6 +12,7 @@ "full_name", "welcome_email_sent", "view_attachments", + "hide_timesheets", "section_break_5", "project_status" ], @@ -64,6 +65,13 @@ "in_list_view": 1, "label": "View attachments" }, + { + "columns": 2, + "default": "0", + "fieldname": "hide_timesheets", + "fieldtype": "Check", + "label": "Hide timesheets" + }, { "fieldname": "section_break_5", "fieldtype": "Section Break" diff --git a/erpnext/templates/pages/projects.html b/erpnext/templates/pages/projects.html index 3b8698f4ab2..d88088c9819 100644 --- a/erpnext/templates/pages/projects.html +++ b/erpnext/templates/pages/projects.html @@ -56,8 +56,8 @@ {{ empty_state(_("Task")) }} {% endif %} -

{{ _("Timesheets") }}

{% if doc.timesheets %} +

{{ _("Timesheets") }}

@@ -73,8 +73,6 @@ {% include "erpnext/templates/includes/projects/project_timesheets.html" %}
- {% else %} - {{ empty_state(_("Timesheet")) }} {% endif %} {% if doc.attachments %} @@ -136,4 +134,4 @@
-{% endmacro %} \ No newline at end of file +{% endmacro %} diff --git a/erpnext/templates/pages/projects.py b/erpnext/templates/pages/projects.py index 787c7c0069b..446437bbb32 100644 --- a/erpnext/templates/pages/projects.py +++ b/erpnext/templates/pages/projects.py @@ -9,7 +9,7 @@ def get_context(context): project_user = frappe.db.get_value( "Project User", {"parent": frappe.form_dict.project, "user": frappe.session.user}, - ["user", "view_attachments"], + ["user", "view_attachments", "hide_timesheets"], as_dict=True, ) if frappe.session.user != "Administrator" and (not project_user or frappe.session.user == "Guest"): @@ -25,7 +25,8 @@ def get_context(context): project.name, start=0, item_status="open", search=frappe.form_dict.get("search") ) - project.timesheets = get_timesheets(project.name, start=0, search=frappe.form_dict.get("search")) + if project_user and not project_user.hide_timesheets: + project.timesheets = get_timesheets(project.name, start=0, search=frappe.form_dict.get("search")) if project_user and project_user.view_attachments: project.attachments = get_attachments(project.name) From 2bfaf64fffed8e9e7eeb43c3f35175bc068c8fd1 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Thu, 20 Mar 2025 15:55:33 +0530 Subject: [PATCH 02/20] fix: date added to wrong patch (cherry picked from commit dc45c3b39caacd4c37b9c0035b4178c78f364944) # Conflicts: # erpnext/patches.txt --- erpnext/patches.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 835ee5b6133..da54fecd43e 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -395,9 +395,14 @@ execute:frappe.db.set_single_value("Accounts Settings", "exchange_gain_loss_post erpnext.patches.v14_0.disable_add_row_in_gross_profit erpnext.patches.v15_0.set_difference_amount_in_asset_value_adjustment erpnext.patches.v14_0.update_posting_datetime +<<<<<<< HEAD erpnext.stock.doctype.stock_ledger_entry.patches.ensure_sle_indexes erpnext.patches.v15_0.update_query_report erpnext.patches.v15_0.rename_field_from_rate_difference_to_amount_difference #2025-03-18 erpnext.patches.v15_0.recalculate_amount_difference_field +======= +erpnext.patches.v15_0.rename_field_from_rate_difference_to_amount_difference +erpnext.patches.v15_0.recalculate_amount_difference_field #2025-03-18 +>>>>>>> dc45c3b39c (fix: date added to wrong patch) erpnext.patches.v15_0.rename_sla_fields #2025-03-12 erpnext.patches.v15_0.set_purchase_receipt_row_item_to_capitalization_stock_item From f964178008a961ed4cd42d74e46e27cc9ce6c738 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Mon, 25 Mar 2024 20:12:44 +0100 Subject: [PATCH 03/20] fix(Payment Entry): get contact details from existing contact (#40556) (cherry picked from commit 462204fc650c38abc24e58d8749ad34818967a23) # Conflicts: # erpnext/accounts/doctype/payment_entry/payment_entry.py --- .../accounts/doctype/payment_entry/payment_entry.py | 13 ++++++++++++- erpnext/accounts/party.py | 9 ++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index fe9e6e5f107..073f8679335 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -37,7 +37,11 @@ from erpnext.accounts.general_ledger import ( make_reverse_gl_entries, process_gl_map, ) +<<<<<<< HEAD from erpnext.accounts.party import get_party_account +======= +from erpnext.accounts.party import complete_contact_details, get_party_account, set_contact_details +>>>>>>> 462204fc65 (fix(Payment Entry): get contact details from existing contact (#40556)) from erpnext.accounts.utils import ( cancel_exchange_gain_loss_journal, get_account_currency, @@ -439,6 +443,13 @@ class PaymentEntry(AccountsController): self.party_name = frappe.db.get_value(self.party_type, self.party, "name") if self.party: +<<<<<<< HEAD +======= + if not self.contact_person: + set_contact_details(self, party=frappe._dict({"name": self.party}), party_type=self.party_type) + else: + complete_contact_details(self) +>>>>>>> 462204fc65 (fix(Payment Entry): get contact details from existing contact (#40556)) if not self.party_balance: self.party_balance = get_balance_on( party_type=self.party_type, party=self.party, date=self.posting_date, company=self.company @@ -2879,7 +2890,7 @@ def get_payment_entry( pe.party_type = party_type pe.party = doc.get(scrub(party_type)) pe.contact_person = doc.get("contact_person") - pe.contact_email = doc.get("contact_email") + complete_contact_details(pe) pe.ensure_supplier_is_not_blocked() pe.paid_from = party_account if payment_type == "Receive" else bank.account diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 959e3429095..37b5b884234 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -279,9 +279,7 @@ def get_regional_address_details(party_details, doctype, company): pass -def set_contact_details(party_details, party, party_type): - party_details.contact_person = get_default_contact(party_type, party.name) - +def complete_contact_details(party_details): if not party_details.contact_person: party_details.update( { @@ -310,6 +308,11 @@ def set_contact_details(party_details, party, party_type): party_details.update(contact_details) +def set_contact_details(party_details, party, party_type): + party_details.contact_person = get_default_contact(party_type, party.name) + complete_contact_details(party_details) + + def set_other_values(party_details, party, party_type): # copy if party_type == "Customer": From 7dc23d97337cac80adf3c2150322591448c861be Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Fri, 21 Mar 2025 14:09:21 +0530 Subject: [PATCH 04/20] chore: resolve conflicts #39748 --- .../accounts/doctype/payment_entry/payment_entry.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 073f8679335..18d98a40504 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -37,11 +37,7 @@ from erpnext.accounts.general_ledger import ( make_reverse_gl_entries, process_gl_map, ) -<<<<<<< HEAD -from erpnext.accounts.party import get_party_account -======= from erpnext.accounts.party import complete_contact_details, get_party_account, set_contact_details ->>>>>>> 462204fc65 (fix(Payment Entry): get contact details from existing contact (#40556)) from erpnext.accounts.utils import ( cancel_exchange_gain_loss_journal, get_account_currency, @@ -443,13 +439,12 @@ class PaymentEntry(AccountsController): self.party_name = frappe.db.get_value(self.party_type, self.party, "name") if self.party: -<<<<<<< HEAD -======= if not self.contact_person: - set_contact_details(self, party=frappe._dict({"name": self.party}), party_type=self.party_type) + set_contact_details( + self, party=frappe._dict({"name": self.party}), party_type=self.party_type + ) else: complete_contact_details(self) ->>>>>>> 462204fc65 (fix(Payment Entry): get contact details from existing contact (#40556)) if not self.party_balance: self.party_balance = get_balance_on( party_type=self.party_type, party=self.party, date=self.posting_date, company=self.company From 2ebea8866a54da4e09babd58b9c6f306e5c2ecf9 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 21 Mar 2025 16:27:05 +0530 Subject: [PATCH 05/20] fix: unwired order_by argument in get_transaction_list (backport #46636) (#46643) * fix: unwired order_by argument * lol on how it was updated from modified in both the places (version 15), but wasn't fixed (cherry picked from commit 2c1077d3326a785b66b5ca486ee8176ca3eb6b84) # Conflicts: # erpnext/controllers/website_list_for_contact.py * fix: merge conflicts * fix: sort by creation only --------- Co-authored-by: Hussain Nagaria Co-authored-by: Md Hussain Nagaria <34810212+NagariaHussain@users.noreply.github.com> --- erpnext/controllers/website_list_for_contact.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/website_list_for_contact.py b/erpnext/controllers/website_list_for_contact.py index c5708d0e4b4..4d141d346eb 100644 --- a/erpnext/controllers/website_list_for_contact.py +++ b/erpnext/controllers/website_list_for_contact.py @@ -69,7 +69,7 @@ def get_transaction_list( filters=None, limit_start=0, limit_page_length=20, - order_by="modified", + order_by="creation desc", custom=False, ): user = frappe.session.user @@ -115,7 +115,7 @@ def get_transaction_list( limit_page_length, fields="name", ignore_permissions=ignore_permissions, - order_by="modified desc", + order_by=order_by, ) if custom: From 58eb1849d79a4d1721621f26982f0145cae3f0e3 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 21 Mar 2025 15:30:23 +0530 Subject: [PATCH 06/20] perf: timeout while renaming cost center (cherry picked from commit 92be7cbbbfbaf1bad0614e5b64e6de1152c98ece) --- erpnext/accounts/doctype/gl_entry/gl_entry.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.json b/erpnext/accounts/doctype/gl_entry/gl_entry.json index b438dbbe4ec..769fbbc427a 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.json +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.json @@ -105,7 +105,8 @@ "label": "Cost Center", "oldfieldname": "cost_center", "oldfieldtype": "Link", - "options": "Cost Center" + "options": "Cost Center", + "search_index": 1 }, { "fieldname": "debit", @@ -358,7 +359,7 @@ "idx": 1, "in_create": 1, "links": [], - "modified": "2025-02-21 14:36:49.431166", + "modified": "2025-03-21 15:29:11.221890", "modified_by": "Administrator", "module": "Accounts", "name": "GL Entry", From 4df5f18d850a2d4b9f595825c1290b3392ba1778 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sun, 23 Mar 2025 18:53:49 +0530 Subject: [PATCH 07/20] fix: correct accumulated depreciation calculation for disposed assets (backport #46660) (#46661) fix: correct accumulated depreciation calculation for disposed assets (#46660) (cherry picked from commit eec2e7e833197d85a88a825e988922a0d64437ff) Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> --- .../asset_depreciations_and_balances.py | 250 +++++++++--------- 1 file changed, 125 insertions(+), 125 deletions(-) diff --git a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py index 5229839bec6..cdeddf3d38b 100644 --- a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py +++ b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py @@ -145,6 +145,130 @@ def get_asset_categories_for_grouped_by_category(filters): ) +def get_assets_for_grouped_by_category(filters): + condition = "" + if filters.get("asset_category"): + condition = f" and a.asset_category = '{filters.get('asset_category')}'" + finance_book_filter = "" + if filters.get("finance_book"): + finance_book_filter += " and ifnull(gle.finance_book, '')=%(finance_book)s" + condition += " and exists (select 1 from `tabAsset Depreciation Schedule` ads where ads.asset = a.name and ads.finance_book = %(finance_book)s)" + + # nosemgrep + return frappe.db.sql( + f""" + SELECT results.asset_category, + sum(results.accumulated_depreciation_as_on_from_date) as accumulated_depreciation_as_on_from_date, + sum(results.depreciation_eliminated_via_reversal) as depreciation_eliminated_via_reversal, + sum(results.depreciation_eliminated_during_the_period) as depreciation_eliminated_during_the_period, + sum(results.depreciation_amount_during_the_period) as depreciation_amount_during_the_period + from (SELECT a.asset_category, + ifnull(sum(case when gle.posting_date < %(from_date)s and (ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s) then + gle.debit + else + 0 + end), 0) as accumulated_depreciation_as_on_from_date, + ifnull(sum(case when gle.posting_date <= %(to_date)s and ifnull(a.disposal_date, 0) = 0 then + gle.credit + else + 0 + end), 0) as depreciation_eliminated_via_reversal, + ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date >= %(from_date)s + and a.disposal_date <= %(to_date)s and gle.posting_date <= a.disposal_date then + gle.debit + else + 0 + end), 0) as depreciation_eliminated_during_the_period, + ifnull(sum(case when gle.posting_date >= %(from_date)s and gle.posting_date <= %(to_date)s + and (ifnull(a.disposal_date, 0) = 0 or gle.posting_date <= a.disposal_date) then + gle.debit + else + 0 + end), 0) as depreciation_amount_during_the_period + from `tabGL Entry` gle + join `tabAsset` a on + gle.against_voucher = a.name + join `tabAsset Category Account` aca on + aca.parent = a.asset_category and aca.company_name = %(company)s + join `tabCompany` company on + company.name = %(company)s + where + a.docstatus=1 + and a.company=%(company)s + and a.purchase_date <= %(to_date)s + and gle.is_cancelled = 0 + and gle.account = ifnull(aca.depreciation_expense_account, company.depreciation_expense_account) + {condition} {finance_book_filter} + group by a.asset_category + union + SELECT a.asset_category, + ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date < %(from_date)s then + 0 + else + a.opening_accumulated_depreciation + end), 0) as accumulated_depreciation_as_on_from_date, + 0 as depreciation_eliminated_via_reversal, + ifnull(sum(case when a.disposal_date >= %(from_date)s and a.disposal_date <= %(to_date)s then + a.opening_accumulated_depreciation + else + 0 + end), 0) as depreciation_eliminated_during_the_period, + 0 as depreciation_amount_during_the_period + from `tabAsset` a + where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s {condition} + group by a.asset_category) as results + group by results.asset_category + """, + { + "to_date": filters.to_date, + "from_date": filters.from_date, + "company": filters.company, + "finance_book": filters.get("finance_book", ""), + }, + as_dict=1, + ) + + +def get_group_by_asset_data(filters): + data = [] + + asset_details = get_asset_details_for_grouped_by_category(filters) + assets = get_assets_for_grouped_by_asset(filters) + + for asset_detail in asset_details: + row = frappe._dict() + row.update(asset_detail) + + row.value_as_on_to_date = ( + flt(row.value_as_on_from_date) + + flt(row.value_of_new_purchase) + - flt(row.value_of_sold_asset) + - flt(row.value_of_scrapped_asset) + - flt(row.value_of_capitalized_asset) + ) + + row.update(next(asset for asset in assets if asset["asset"] == asset_detail.get("name", ""))) + + row.accumulated_depreciation_as_on_to_date = ( + flt(row.accumulated_depreciation_as_on_from_date) + + flt(row.depreciation_amount_during_the_period) + - flt(row.depreciation_eliminated_during_the_period) + - flt(row.depreciation_eliminated_via_reversal) + ) + + row.net_asset_value_as_on_from_date = flt(row.value_as_on_from_date) - flt( + row.accumulated_depreciation_as_on_from_date + ) + + row.net_asset_value_as_on_to_date = flt(row.value_as_on_to_date) - flt( + row.accumulated_depreciation_as_on_to_date + ) + + data.append(row) + + return data + + def get_asset_details_for_grouped_by_category(filters): condition = "" if filters.get("asset"): @@ -224,130 +348,6 @@ def get_asset_details_for_grouped_by_category(filters): ) -def get_group_by_asset_data(filters): - data = [] - - asset_details = get_asset_details_for_grouped_by_category(filters) - assets = get_assets_for_grouped_by_asset(filters) - - for asset_detail in asset_details: - row = frappe._dict() - row.update(asset_detail) - - row.value_as_on_to_date = ( - flt(row.value_as_on_from_date) - + flt(row.value_of_new_purchase) - - flt(row.value_of_sold_asset) - - flt(row.value_of_scrapped_asset) - - flt(row.value_of_capitalized_asset) - ) - - row.update(next(asset for asset in assets if asset["asset"] == asset_detail.get("name", ""))) - - row.accumulated_depreciation_as_on_to_date = ( - flt(row.accumulated_depreciation_as_on_from_date) - + flt(row.depreciation_amount_during_the_period) - - flt(row.depreciation_eliminated_during_the_period) - - flt(row.depreciation_eliminated_via_reversal) - ) - - row.net_asset_value_as_on_from_date = flt(row.value_as_on_from_date) - flt( - row.accumulated_depreciation_as_on_from_date - ) - - row.net_asset_value_as_on_to_date = flt(row.value_as_on_to_date) - flt( - row.accumulated_depreciation_as_on_to_date - ) - - data.append(row) - - return data - - -def get_assets_for_grouped_by_category(filters): - condition = "" - if filters.get("asset_category"): - condition = f" and a.asset_category = '{filters.get('asset_category')}'" - finance_book_filter = "" - if filters.get("finance_book"): - finance_book_filter += " and ifnull(gle.finance_book, '')=%(finance_book)s" - condition += " and exists (select 1 from `tabAsset Depreciation Schedule` ads where ads.asset = a.name and ads.finance_book = %(finance_book)s)" - - # nosemgrep - return frappe.db.sql( - f""" - SELECT results.asset_category, - sum(results.accumulated_depreciation_as_on_from_date) as accumulated_depreciation_as_on_from_date, - sum(results.depreciation_eliminated_via_reversal) as depreciation_eliminated_via_reversal, - sum(results.depreciation_eliminated_during_the_period) as depreciation_eliminated_during_the_period, - sum(results.depreciation_amount_during_the_period) as depreciation_amount_during_the_period - from (SELECT a.asset_category, - ifnull(sum(case when gle.posting_date < %(from_date)s and (ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s) then - gle.debit - else - 0 - end), 0) as accumulated_depreciation_as_on_from_date, - ifnull(sum(case when gle.posting_date <= %(to_date)s and ifnull(a.disposal_date, 0) = 0 then - gle.credit - else - 0 - end), 0) as depreciation_eliminated_via_reversal, - ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date >= %(from_date)s - and a.disposal_date <= %(to_date)s and gle.posting_date <= a.disposal_date then - gle.debit - else - 0 - end), 0) as depreciation_eliminated_during_the_period, - ifnull(sum(case when gle.posting_date >= %(from_date)s and gle.posting_date <= %(to_date)s - and (ifnull(a.disposal_date, 0) = 0 or gle.posting_date <= a.disposal_date) then - gle.debit - else - 0 - end), 0) as depreciation_amount_during_the_period - from `tabGL Entry` gle - join `tabAsset` a on - gle.against_voucher = a.name - join `tabAsset Category Account` aca on - aca.parent = a.asset_category and aca.company_name = %(company)s - join `tabCompany` company on - company.name = %(company)s - where - a.docstatus=1 - and a.company=%(company)s - and a.purchase_date <= %(to_date)s - and gle.is_cancelled = 0 - and gle.account = ifnull(aca.depreciation_expense_account, company.depreciation_expense_account) - {condition} {finance_book_filter} - group by a.asset_category - union - SELECT a.asset_category, - ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and (a.disposal_date < %(from_date)s or a.disposal_date > %(to_date)s) then - 0 - else - a.opening_accumulated_depreciation - end), 0) as accumulated_depreciation_as_on_from_date, - 0 as depreciation_eliminated_via_reversal, - ifnull(sum(case when a.disposal_date >= %(from_date)s and a.disposal_date <= %(to_date)s then - a.opening_accumulated_depreciation - else - 0 - end), 0) as depreciation_eliminated_during_the_period, - 0 as depreciation_amount_during_the_period - from `tabAsset` a - where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s {condition} - group by a.asset_category) as results - group by results.asset_category - """, - { - "to_date": filters.to_date, - "from_date": filters.from_date, - "company": filters.company, - "finance_book": filters.get("finance_book", ""), - }, - as_dict=1, - ) - - def get_assets_for_grouped_by_asset(filters): condition = "" if filters.get("asset"): @@ -405,7 +405,7 @@ def get_assets_for_grouped_by_asset(filters): group by a.name union SELECT a.name as name, - ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and (a.disposal_date < %(from_date)s or a.disposal_date > %(to_date)s) then + ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date < %(from_date)s then 0 else a.opening_accumulated_depreciation From 412e6be5025d5d451902ced7848f9602ae1786ab Mon Sep 17 00:00:00 2001 From: Sugesh393 Date: Tue, 11 Mar 2025 11:49:35 +0530 Subject: [PATCH 08/20] fix: add base_outstanding and base_paid_amount in payment schedule table (cherry picked from commit 6c2f9a563e8d6b7bec0c846ec755ab7bc8b7a3d2) # Conflicts: # erpnext/accounts/doctype/payment_schedule/payment_schedule.json --- .../doctype/payment_entry/payment_entry.py | 42 +++++++++++++++++-- .../payment_schedule/payment_schedule.json | 28 ++++++++++++- .../payment_schedule/payment_schedule.py | 2 + .../accounts_receivable.py | 26 +++++++----- erpnext/controllers/accounts_controller.py | 3 ++ 5 files changed, 85 insertions(+), 16 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 18d98a40504..6c7b1ad5f5a 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -7,6 +7,7 @@ from functools import reduce import frappe from frappe import ValidationError, _, qb, scrub, throw +from frappe.model.meta import get_field_precision from frappe.query_builder import Tuple from frappe.query_builder.functions import Count from frappe.utils import cint, comma_or, flt, getdate, nowdate @@ -742,16 +743,39 @@ class PaymentEntry(AccountsController): outstanding = flt(invoice_paid_amount_map.get(key, {}).get("outstanding")) discounted_amt = flt(invoice_paid_amount_map.get(key, {}).get("discounted_amt")) + conversion_rate = frappe.db.get_value(key[2], {"name": key[1]}, "conversion_rate") + base_paid_amount_precision = get_field_precision( + frappe.get_meta("Payment Schedule").get_field("base_paid_amount") + ) + base_outstanding_precision = get_field_precision( + frappe.get_meta("Payment Schedule").get_field("base_outstanding") + ) + + base_paid_amount = flt( + (allocated_amount - discounted_amt) * conversion_rate, base_paid_amount_precision + ) + base_outstanding = flt(allocated_amount * conversion_rate, base_outstanding_precision) + if cancel: frappe.db.sql( """ UPDATE `tabPayment Schedule` SET paid_amount = `paid_amount` - %s, + base_paid_amount = `base_paid_amount` - %s, discounted_amount = `discounted_amount` - %s, - outstanding = `outstanding` + %s + outstanding = `outstanding` + %s, + base_outstanding = `base_outstanding` - %s WHERE parent = %s and payment_term = %s""", - (allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0]), + ( + allocated_amount - discounted_amt, + base_paid_amount, + discounted_amt, + allocated_amount, + base_outstanding, + key[1], + key[0], + ), ) else: if allocated_amount > outstanding: @@ -767,10 +791,20 @@ class PaymentEntry(AccountsController): UPDATE `tabPayment Schedule` SET paid_amount = `paid_amount` + %s, + base_paid_amount = `base_paid_amount` + %s, discounted_amount = `discounted_amount` + %s, - outstanding = `outstanding` - %s + outstanding = `outstanding` - %s, + base_outstanding = `base_outstanding` - %s WHERE parent = %s and payment_term = %s""", - (allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0]), + ( + allocated_amount - discounted_amt, + base_paid_amount, + discounted_amt, + allocated_amount, + base_outstanding, + key[1], + key[0], + ), ) def get_allocated_amount_in_transaction_currency( diff --git a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json index dde9980ce53..0e1e7a84a4c 100644 --- a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json +++ b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json @@ -24,7 +24,9 @@ "paid_amount", "discounted_amount", "column_break_3", - "base_payment_amount" + "base_payment_amount", + "base_outstanding", + "base_paid_amount" ], "fields": [ { @@ -155,19 +157,43 @@ "fieldtype": "Currency", "label": "Payment Amount (Company Currency)", "options": "Company:company:default_currency" + }, + { + "fieldname": "base_outstanding", + "fieldtype": "Currency", + "label": "Outstanding (Company Currency)", + "options": "Company:company:default_currency", + "read_only": 1 + }, + { + "depends_on": "base_paid_amount", + "fieldname": "base_paid_amount", + "fieldtype": "Currency", + "label": "Paid Amount (Company Currency)", + "options": "Company:company:default_currency", + "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], +<<<<<<< HEAD "modified": "2022-09-16 13:57:06.382859", +======= + "modified": "2025-03-11 11:06:51.792982", +>>>>>>> 6c2f9a563e (fix: add base_outstanding and base_paid_amount in payment schedule table) "modified_by": "Administrator", "module": "Accounts", "name": "Payment Schedule", "owner": "Administrator", "permissions": [], "quick_entry": 1, +<<<<<<< HEAD "sort_field": "modified", +======= + "row_format": "Dynamic", + "sort_field": "creation", +>>>>>>> 6c2f9a563e (fix: add base_outstanding and base_paid_amount in payment schedule table) "sort_order": "DESC", "states": [], "track_changes": 1 diff --git a/erpnext/accounts/doctype/payment_schedule/payment_schedule.py b/erpnext/accounts/doctype/payment_schedule/payment_schedule.py index 8a292fd3aba..a3d1dbe5564 100644 --- a/erpnext/accounts/doctype/payment_schedule/payment_schedule.py +++ b/erpnext/accounts/doctype/payment_schedule/payment_schedule.py @@ -14,6 +14,8 @@ class PaymentSchedule(Document): if TYPE_CHECKING: from frappe.types import DF + base_outstanding: DF.Currency + base_paid_amount: DF.Currency base_payment_amount: DF.Currency description: DF.SmallText | None discount: DF.Float diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 5a140ec3f2d..9625a86f05f 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -517,7 +517,7 @@ class ReceivablePayableReport: select si.name, si.party_account_currency, si.currency, si.conversion_rate, si.total_advance, ps.due_date, ps.payment_term, ps.payment_amount, ps.base_payment_amount, - ps.description, ps.paid_amount, ps.discounted_amount + ps.description, ps.paid_amount, ps.base_paid_amount, ps.discounted_amount from `tab{row.voucher_type}` si, `tabPayment Schedule` ps where si.name = ps.parent and ps.parenttype = '{row.voucher_type}' and @@ -540,20 +540,24 @@ class ReceivablePayableReport: # Deduct that from paid amount pre allocation row.paid -= flt(payment_terms_details[0].total_advance) + company_currency = frappe.get_value("Company", self.filters.get("company"), "default_currency") + # If single payment terms, no need to split the row if len(payment_terms_details) == 1 and payment_terms_details[0].payment_term: - self.append_payment_term(row, payment_terms_details[0], original_row) + self.append_payment_term(row, payment_terms_details[0], original_row, company_currency) return for d in payment_terms_details: term = frappe._dict(original_row) - self.append_payment_term(row, d, term) + self.append_payment_term(row, d, term, company_currency) - def append_payment_term(self, row, d, term): - if d.currency == d.party_account_currency: + def append_payment_term(self, row, d, term, company_currency): + invoiced = d.base_payment_amount + paid_amount = d.base_paid_amount + + if company_currency == d.party_account_currency or self.filters.get("in_party_currency"): invoiced = d.payment_amount - else: - invoiced = d.base_payment_amount + paid_amount = d.paid_amount row.payment_terms.append( term.update( @@ -562,15 +566,15 @@ class ReceivablePayableReport: "invoiced": invoiced, "invoice_grand_total": row.invoiced, "payment_term": d.description or d.payment_term, - "paid": d.paid_amount + d.discounted_amount, + "paid": paid_amount + d.discounted_amount, "credit_note": 0.0, - "outstanding": invoiced - d.paid_amount - d.discounted_amount, + "outstanding": invoiced - paid_amount - d.discounted_amount, } ) ) - if d.paid_amount: - row["paid"] -= d.paid_amount + d.discounted_amount + if paid_amount: + row["paid"] -= paid_amount + d.discounted_amount def allocate_closing_to_term(self, row, term, key): if row[key]: diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index eb483bdb868..ff0e0434309 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2363,6 +2363,9 @@ class AccountsController(TransactionBase): base_grand_total * flt(d.invoice_portion) / 100, d.precision("base_payment_amount") ) d.outstanding = d.payment_amount + d.base_outstanding = flt( + d.payment_amount * self.get("conversion_rate"), d.precision("base_outstanding") + ) elif not d.invoice_portion: d.base_payment_amount = flt( d.payment_amount * self.get("conversion_rate"), d.precision("base_payment_amount") From c3221c4e9319ba1e960d8aa272484a4c629d96dd Mon Sep 17 00:00:00 2001 From: Sugesh393 Date: Tue, 11 Mar 2025 11:52:42 +0530 Subject: [PATCH 09/20] fix: add patch to update base_outstanding and base_paid_amount (cherry picked from commit 7e92e4967af8a25df2d193604ddd494ec07b1f77) --- erpnext/patches.txt | 1 + ...date_payment_schedule_fields_in_invoices.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 erpnext/patches/v15_0/update_payment_schedule_fields_in_invoices.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 835ee5b6133..656ff0ce001 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -401,3 +401,4 @@ erpnext.patches.v15_0.rename_field_from_rate_difference_to_amount_difference #20 erpnext.patches.v15_0.recalculate_amount_difference_field erpnext.patches.v15_0.rename_sla_fields #2025-03-12 erpnext.patches.v15_0.set_purchase_receipt_row_item_to_capitalization_stock_item +erpnext.patches.v15_0.update_payment_schedule_fields_in_invoices diff --git a/erpnext/patches/v15_0/update_payment_schedule_fields_in_invoices.py b/erpnext/patches/v15_0/update_payment_schedule_fields_in_invoices.py new file mode 100644 index 00000000000..74d8ac38f72 --- /dev/null +++ b/erpnext/patches/v15_0/update_payment_schedule_fields_in_invoices.py @@ -0,0 +1,18 @@ +import frappe +from frappe.query_builder import DocType + + +def execute(): + invoice_types = ["Sales Invoice", "Purchase Invoice"] + for invoice_type in invoice_types: + invoice = DocType(invoice_type) + invoice_details = frappe.qb.from_(invoice).select(invoice.conversion_rate, invoice.name) + update_payment_schedule(invoice_details) + + +def update_payment_schedule(invoice_details): + ps = DocType("Payment Schedule") + + frappe.qb.update(ps).join(invoice_details).on(ps.parent == invoice_details.name).set( + ps.base_paid_amount, ps.paid_amount * invoice_details.conversion_rate + ).set(ps.base_outstanding, ps.outstanding * invoice_details.conversion_rate).run() From 4a7d401dc5401867cad55f7820d19b4215041e31 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 24 Mar 2025 11:53:14 +0530 Subject: [PATCH 10/20] chore: resolve conflict --- .../doctype/payment_schedule/payment_schedule.json | 8 -------- 1 file changed, 8 deletions(-) diff --git a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json index 0e1e7a84a4c..b72281b6314 100644 --- a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json +++ b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json @@ -177,23 +177,15 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], -<<<<<<< HEAD - "modified": "2022-09-16 13:57:06.382859", -======= "modified": "2025-03-11 11:06:51.792982", ->>>>>>> 6c2f9a563e (fix: add base_outstanding and base_paid_amount in payment schedule table) "modified_by": "Administrator", "module": "Accounts", "name": "Payment Schedule", "owner": "Administrator", "permissions": [], "quick_entry": 1, -<<<<<<< HEAD - "sort_field": "modified", -======= "row_format": "Dynamic", "sort_field": "creation", ->>>>>>> 6c2f9a563e (fix: add base_outstanding and base_paid_amount in payment schedule table) "sort_order": "DESC", "states": [], "track_changes": 1 From 2a70791bbaeaff33f469cf3014932367c8762059 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 18 Mar 2025 12:49:29 +0530 Subject: [PATCH 11/20] fix: correct invoice order in payment reconcillaiton (cherry picked from commit 5c34a5aaed2c7f10c3d5686bdaefec5e2bff7f7f) # Conflicts: # erpnext/accounts/utils.py --- erpnext/accounts/utils.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 2758ff0e26f..6e93d3771e3 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -9,8 +9,13 @@ import frappe import frappe.defaults from frappe import _, qb, throw from frappe.model.meta import get_field_precision +<<<<<<< HEAD from frappe.query_builder import AliasedQuery, Criterion, Table from frappe.query_builder.functions import Count, Sum +======= +from frappe.query_builder import AliasedQuery, Case, Criterion, Table +from frappe.query_builder.functions import Count, Max, Round, Sum +>>>>>>> 5c34a5aaed (fix: correct invoice order in payment reconcillaiton) from frappe.query_builder.utils import DocType from frappe.utils import ( add_days, @@ -1974,6 +1979,15 @@ class QueryPaymentLedger: .select( ple.against_voucher_no.as_("voucher_no"), Sum(ple.amount_in_account_currency).as_("amount_in_account_currency"), + Max( + Case().when( + ( + (ple.voucher_no == ple.against_voucher_no) + & (ple.voucher_type == ple.against_voucher_type) + ), + (ple.posting_date), + ) + ).as_("invoice_date"), ) .where(ple.delinked == 0) .where(Criterion.all(filter_on_against_voucher_no)) @@ -1981,7 +1995,7 @@ class QueryPaymentLedger: .where(Criterion.all(self.dimensions_filter)) .where(Criterion.all(self.voucher_posting_date)) .groupby(ple.against_voucher_type, ple.against_voucher_no, ple.party_type, ple.party) - .orderby(ple.posting_date, ple.voucher_no) + .orderby(ple.invoice_date, ple.voucher_no) .having(qb.Field("amount_in_account_currency") > 0) .limit(self.limit) .run() From 326c37a051940a20d26f3256c22f608e170a8e28 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 24 Mar 2025 12:29:52 +0530 Subject: [PATCH 12/20] chore: resolve conflict --- erpnext/accounts/utils.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 6e93d3771e3..ac37775f45f 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -9,13 +9,8 @@ import frappe import frappe.defaults from frappe import _, qb, throw from frappe.model.meta import get_field_precision -<<<<<<< HEAD -from frappe.query_builder import AliasedQuery, Criterion, Table -from frappe.query_builder.functions import Count, Sum -======= from frappe.query_builder import AliasedQuery, Case, Criterion, Table -from frappe.query_builder.functions import Count, Max, Round, Sum ->>>>>>> 5c34a5aaed (fix: correct invoice order in payment reconcillaiton) +from frappe.query_builder.functions import Count, Max, Sum from frappe.query_builder.utils import DocType from frappe.utils import ( add_days, From e69c7225342c8db4653aefae99e043c5f9af4e14 Mon Sep 17 00:00:00 2001 From: Florian HENRY Date: Fri, 7 Mar 2025 12:29:51 +0100 Subject: [PATCH 13/20] feat(accounting): allow chart_of_account.get_chart to be whilelist (cherry picked from commit 49dcd96909fa3c1c5c4978d3e5ce0dd24f05cc99) --- .../doctype/account/chart_of_accounts/chart_of_accounts.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py index 2768f321ff6..1c81f07cee3 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py +++ b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py @@ -116,6 +116,7 @@ def identify_is_group(child): return is_group +@frappe.whitelist() def get_chart(chart_template, existing_company=None): chart = {} if existing_company: From 6c443bd85aba5cc8e911219da4924ab63223d2ed Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 18 Mar 2025 14:16:35 +0530 Subject: [PATCH 14/20] fix: customer credit limit check based on `bypass_credit_limit_check` in Journal Entry (cherry picked from commit 8a84faebedaaddc4ff6b1210a15420ede0c2c51b) --- .../doctype/journal_entry/journal_entry.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 9e1eaf25442..e8ac493d659 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -576,8 +576,22 @@ class JournalEntry(AccountsController): if customers: from erpnext.selling.doctype.customer.customer import check_credit_limit + customer_details = frappe._dict( + frappe.db.get_all( + "Customer Credit Limit", + filters={ + "parent": ["in", customers], + "parenttype": ["=", "Customer"], + "company": ["=", self.company], + }, + fields=["parent", "bypass_credit_limit_check"], + as_list=True, + ) + ) + for customer in customers: - check_credit_limit(customer, self.company) + ignore_outstanding_sales_order = bool(customer_details.get(customer)) + check_credit_limit(customer, self.company, ignore_outstanding_sales_order) def validate_cheque_info(self): if self.voucher_type in ["Bank Entry"]: From dc3b5e2f3af271640715ac97e326272471bcb93d Mon Sep 17 00:00:00 2001 From: vishakhdesai Date: Mon, 24 Mar 2025 11:23:53 +0530 Subject: [PATCH 15/20] fix: don't filter payment entries on Bank Account in Payment Clearance (cherry picked from commit fa2fd5bf88668524b64f905c663c0e8602557414) --- erpnext/accounts/doctype/bank_clearance/bank_clearance.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py index 7f08653b15b..55ddfa698b7 100644 --- a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py +++ b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py @@ -159,9 +159,6 @@ def get_payment_entries_for_bank_clearance( as_dict=1, ) - if bank_account: - condition += "and bank_account = %(bank_account)s" - payment_entries = frappe.db.sql( f""" select @@ -183,7 +180,6 @@ def get_payment_entries_for_bank_clearance( "account": account, "from": from_date, "to": to_date, - "bank_account": bank_account, }, as_dict=1, ) From 98df0614abb5af17a1e3889254a1bbd009b12d5a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 24 Mar 2025 16:55:11 +0100 Subject: [PATCH 16/20] ci: apply label "skip-release-notes" based on PR title (backport #46694) (#46697) ci: apply label "skip-release-notes" based on PR title (#46694) Workflow copied from frappe/frappe (cherry picked from commit eb350012b0ae640924e6e36ec66f9653e8f6e407) Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --- .github/workflows/label-base-on-title.yml | 30 +++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/label-base-on-title.yml diff --git a/.github/workflows/label-base-on-title.yml b/.github/workflows/label-base-on-title.yml new file mode 100644 index 00000000000..4e811edf99a --- /dev/null +++ b/.github/workflows/label-base-on-title.yml @@ -0,0 +1,30 @@ +name: "Auto-label PRs based on title" + +on: + pull_request_target: + types: [opened, reopened] + +jobs: + add-label-if-prefix-matches: + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - name: Check PR title and add label if it matches prefixes + uses: actions/github-script@v7 + continue-on-error: true + with: + script: | + const title = context.payload.pull_request.title.toLowerCase(); + const prefixes = ['chore', 'ci', 'style', 'test', 'refactor']; + + // Check if the PR title starts with any of the prefixes + if (prefixes.some(prefix => title.startsWith(prefix))) { + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + labels: ['skip-release-notes'] + }); + } From 4edfc6f1258c7d98db125894338e951228f94cc1 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Thu, 20 Mar 2025 13:25:53 +0530 Subject: [PATCH 17/20] feat: repost accounting ledger for purchase receipt (cherry picked from commit b36e3564696f87f227ffa9851e6b67318df8924b) --- .../repost_accounting_ledger.py | 13 ++++ .../test_repost_accounting_ledger.py | 76 ++++++++++++++++++- 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py index 73977f5d560..800f1647471 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py @@ -8,6 +8,8 @@ from frappe import _, qb from frappe.model.document import Document from frappe.utils.data import comma_and +from erpnext.stock import get_warehouse_account_map + class RepostAccountingLedger(Document): # begin: auto-generated types @@ -97,6 +99,9 @@ class RepostAccountingLedger(Document): doc = frappe.get_doc(x.voucher_type, x.voucher_no) if doc.doctype in ["Payment Entry", "Journal Entry"]: gle_map = doc.build_gl_map() + elif doc.doctype == "Purchase Receipt": + warehouse_account_map = get_warehouse_account_map(doc.company) + gle_map = doc.get_gl_entries(warehouse_account_map) else: gle_map = doc.get_gl_entries() @@ -177,6 +182,14 @@ def start_repost(account_repost_doc=str) -> None: doc.force_set_against_expense_account() doc.make_gl_entries() + elif doc.doctype == "Purchase Receipt": + if not repost_doc.delete_cancelled_entries: + doc.docstatus = 2 + doc.make_gl_entries_on_cancel() + + doc.docstatus = 1 + doc.make_gl_entries(from_repost=True) + elif doc.doctype in ["Payment Entry", "Journal Entry", "Expense Claim"]: if not repost_doc.delete_cancelled_entries: doc.make_gl_entries(1) diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py index 9f906bb7647..7ed999831cd 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py @@ -12,6 +12,8 @@ from erpnext.accounts.doctype.payment_request.payment_request import make_paymen from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.test.accounts_mixin import AccountsTestMixin from erpnext.accounts.utils import get_fiscal_year +from erpnext.stock.doctype.item.test_item import make_item +from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries, make_purchase_receipt class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase): @@ -204,9 +206,81 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase): self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1})) self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1})) + def test_06_repost_purchase_receipt(self): + from erpnext.accounts.doctype.account.test_account import create_account + + provisional_account = create_account( + account_name="Provision Account", + parent_account="Current Liabilities - _TC", + company=self.company, + ) + + another_provisional_account = create_account( + account_name="Another Provision Account", + parent_account="Current Liabilities - _TC", + company=self.company, + ) + + company = frappe.get_doc("Company", self.company) + company.enable_provisional_accounting_for_non_stock_items = 1 + company.default_provisional_account = provisional_account + company.save() + + test_cc = company.cost_center + default_expense_account = company.default_expense_account + + item = make_item(properties={"is_stock_item": 0}) + + pr = make_purchase_receipt(company=self.company, item_code=item.name, rate=1000.0, qty=1.0) + pr_gl_entries = get_gl_entries(pr.doctype, pr.name, skip_cancelled=True) + expected_pr_gles = [ + {"account": provisional_account, "debit": 0.0, "credit": 1000.0, "cost_center": test_cc}, + {"account": default_expense_account, "debit": 1000.0, "credit": 0.0, "cost_center": test_cc}, + ] + self.assertEqual(expected_pr_gles, pr_gl_entries) + + # change the provisional account + frappe.db.set_value( + "Purchase Receipt Item", + pr.items[0].name, + "provisional_expense_account", + another_provisional_account, + ) + + repost_doc = frappe.new_doc("Repost Accounting Ledger") + repost_doc.company = self.company + repost_doc.delete_cancelled_entries = True + repost_doc.append("vouchers", {"voucher_type": pr.doctype, "voucher_no": pr.name}) + repost_doc.save().submit() + + pr_gles_after_repost = get_gl_entries(pr.doctype, pr.name, skip_cancelled=True) + expected_pr_gles_after_repost = [ + {"account": default_expense_account, "debit": 1000.0, "credit": 0.0, "cost_center": test_cc}, + {"account": another_provisional_account, "debit": 0.0, "credit": 1000.0, "cost_center": test_cc}, + ] + self.assertEqual(len(pr_gles_after_repost), len(expected_pr_gles_after_repost)) + self.assertEqual(expected_pr_gles_after_repost, pr_gles_after_repost) + + # teardown + repost_doc.cancel() + repost_doc.delete() + + pr.reload() + pr.cancel() + + company.enable_provisional_accounting_for_non_stock_items = 0 + company.default_provisional_account = None + company.save() + def update_repost_settings(): - allowed_types = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"] + allowed_types = [ + "Sales Invoice", + "Purchase Invoice", + "Payment Entry", + "Journal Entry", + "Purchase Receipt", + ] repost_settings = frappe.get_doc("Repost Accounting Ledger Settings") for x in allowed_types: repost_settings.append("allowed_types", {"document_type": x, "allowed": True}) From f93feb18fbbd6cc4c3b954db6aef89dd71279bc5 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Wed, 19 Mar 2025 17:20:25 +0530 Subject: [PATCH 18/20] refactor: removed redundant message display for each item row cost center update (cherry picked from commit 4376ca5f1d52c836681fcdb567df1aa034b8d8e7) --- erpnext/public/js/utils/sales_common.js | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/erpnext/public/js/utils/sales_common.js b/erpnext/public/js/utils/sales_common.js index ca2bed20c7f..bbb2a88e629 100644 --- a/erpnext/public/js/utils/sales_common.js +++ b/erpnext/public/js/utils/sales_common.js @@ -447,22 +447,21 @@ erpnext.sales_common = { args: { project: this.frm.doc.project }, callback: function (r, rt) { if (!r.exc) { - $.each(me.frm.doc["items"] || [], function (i, row) { - if (r.message) { + if (r.message) { + $.each(me.frm.doc["items"] || [], function (i, row) { frappe.model.set_value( row.doctype, row.name, "cost_center", r.message ); - frappe.msgprint( - __( - "Cost Center For Item with Item Code {0} has been Changed to {1}", - [row.item_name, r.message] - ) - ); - } - }); + }); + frappe.msgprint( + __("Cost Center for Item rows has been updated to {0}", [ + r.message, + ]) + ); + } } }, }); From 391b5c4226616d3e2194fbf93a496b48383a0013 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Wed, 19 Mar 2025 16:52:13 +0530 Subject: [PATCH 19/20] fix: do not validate if conversion rate is 1 for different currency (cherry picked from commit e8a66d03bc617bdde4cedf1703190c15c31cbe91) --- .../doctype/sales_invoice/test_sales_invoice.py | 11 ----------- erpnext/controllers/accounts_controller.py | 17 +++++++++++------ 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index f428eb50a3c..fe8342d78bd 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1819,17 +1819,6 @@ class TestSalesInvoice(FrappeTestCase): for field in expected_gle: self.assertEqual(expected_gle[field], gle[field]) - def test_invoice_exchange_rate(self): - si = create_sales_invoice( - customer="_Test Customer USD", - debit_to="_Test Receivable USD - _TC", - currency="USD", - conversion_rate=1, - do_not_save=1, - ) - - self.assertRaises(frappe.ValidationError, si.save) - def test_invalid_currency(self): # Customer currency = USD diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index ff0e0434309..e0d7e2a36df 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2692,12 +2692,17 @@ class AccountsController(TransactionBase): default_currency = erpnext.get_company_currency(self.company) if not default_currency: throw(_("Please enter default currency in Company Master")) - if ( - (self.currency == default_currency and flt(self.conversion_rate) != 1.00) - or not self.conversion_rate - or (self.currency != default_currency and flt(self.conversion_rate) == 1.00) - ): - throw(_("Conversion rate cannot be 0 or 1")) + + if not self.conversion_rate: + throw(_("Conversion rate cannot be 0")) + + if self.currency == default_currency and flt(self.conversion_rate) != 1.00: + throw(_("Conversion rate must be 1.00 if document currency is same as company currency")) + + if self.currency != default_currency and flt(self.conversion_rate) == 1.00: + frappe.msgprint( + _("Conversion rate is 1.00, but document currency is different from company currency") + ) def check_finance_books(self, item, asset): if ( From b3c37332868dbcf143b4260bc56946dfbed5099e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 25 Mar 2025 17:53:29 +0530 Subject: [PATCH 20/20] chore: resolve conflict --- erpnext/patches.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index da54fecd43e..d6d7cf18704 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -395,14 +395,9 @@ execute:frappe.db.set_single_value("Accounts Settings", "exchange_gain_loss_post erpnext.patches.v14_0.disable_add_row_in_gross_profit erpnext.patches.v15_0.set_difference_amount_in_asset_value_adjustment erpnext.patches.v14_0.update_posting_datetime -<<<<<<< HEAD erpnext.stock.doctype.stock_ledger_entry.patches.ensure_sle_indexes erpnext.patches.v15_0.update_query_report -erpnext.patches.v15_0.rename_field_from_rate_difference_to_amount_difference #2025-03-18 -erpnext.patches.v15_0.recalculate_amount_difference_field -======= erpnext.patches.v15_0.rename_field_from_rate_difference_to_amount_difference erpnext.patches.v15_0.recalculate_amount_difference_field #2025-03-18 ->>>>>>> dc45c3b39c (fix: date added to wrong patch) erpnext.patches.v15_0.rename_sla_fields #2025-03-12 erpnext.patches.v15_0.set_purchase_receipt_row_item_to_capitalization_stock_item