From b9b08c35efc13de0a362fe20f9290464d6d4ed20 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 21 Mar 2025 15:30:23 +0530 Subject: [PATCH 01/10] perf: timeout while renaming cost center (cherry picked from commit 92be7cbbbfbaf1bad0614e5b64e6de1152c98ece) # Conflicts: # erpnext/accounts/doctype/gl_entry/gl_entry.json --- erpnext/accounts/doctype/gl_entry/gl_entry.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.json b/erpnext/accounts/doctype/gl_entry/gl_entry.json index eb99768b05e..7aa67a9e1ce 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.json +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.json @@ -88,7 +88,8 @@ "label": "Cost Center", "oldfieldname": "cost_center", "oldfieldtype": "Link", - "options": "Cost Center" + "options": "Cost Center", + "search_index": 1 }, { "fieldname": "debit", @@ -258,7 +259,11 @@ "idx": 1, "in_create": 1, "links": [], +<<<<<<< HEAD "modified": "2024-03-19 18:30:49.613401", +======= + "modified": "2025-03-21 15:29:11.221890", +>>>>>>> 92be7cbbbf (perf: timeout while renaming cost center) "modified_by": "Administrator", "module": "Accounts", "name": "GL Entry", From 736b125e1445a20e069d80d454126e1126559108 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Sat, 22 Mar 2025 20:49:56 +0530 Subject: [PATCH 02/10] chore: fix conflicts --- erpnext/accounts/doctype/gl_entry/gl_entry.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.json b/erpnext/accounts/doctype/gl_entry/gl_entry.json index 7aa67a9e1ce..f6d36b94e35 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.json +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.json @@ -259,11 +259,7 @@ "idx": 1, "in_create": 1, "links": [], -<<<<<<< HEAD - "modified": "2024-03-19 18:30:49.613401", -======= "modified": "2025-03-21 15:29:11.221890", ->>>>>>> 92be7cbbbf (perf: timeout while renaming cost center) "modified_by": "Administrator", "module": "Accounts", "name": "GL Entry", @@ -297,4 +293,4 @@ "sort_field": "modified", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} From 331ecc196406d81b06aee097b47853e7033b9a43 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 18 Mar 2025 14:16:35 +0530 Subject: [PATCH 03/10] 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 c1d91c49d8f..dabf8b55037 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -433,8 +433,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 690a93957230438510df2e1ade3febe4bde31c31 Mon Sep 17 00:00:00 2001 From: Imesha Sudasingha Date: Mon, 24 Mar 2025 12:37:52 +0000 Subject: [PATCH 04/10] fix: assign dialog instance to a variable in update_child_items function `dialog` is being referred in `onchange` handlers of the fields. Without this fix, they are failing because `dialog` is not defined. --- erpnext/public/js/utils.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index c2b068b6e62..dc25a7bed57 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -750,7 +750,7 @@ erpnext.utils.update_child_items = function (opts) { }); } - new frappe.ui.Dialog({ + let dialog = new frappe.ui.Dialog({ title: __("Update Items"), size: "extra-large", fields: [ @@ -787,7 +787,9 @@ erpnext.utils.update_child_items = function (opts) { refresh_field("items"); }, primary_action_label: __("Update"), - }).show(); + }); + + dialog.show(); }; erpnext.utils.map_current_doc = function (opts) { From df64d2ef4ead4d414a30d0147cec1b035daaa178 Mon Sep 17 00:00:00 2001 From: Imesha Sudasingha Date: Mon, 24 Mar 2025 12:50:48 +0000 Subject: [PATCH 05/10] fix: fix lint issues with trailing whitespaces --- erpnext/public/js/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index dc25a7bed57..44cd291004b 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -788,7 +788,7 @@ erpnext.utils.update_child_items = function (opts) { }, primary_action_label: __("Update"), }); - + dialog.show(); }; From b311b6eb7fbf37cbfedfc0640efab2f0a4c0a599 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 24 Mar 2025 16:55:07 +0100 Subject: [PATCH 06/10] ci: apply label "skip-release-notes" based on PR title (backport #46694) (#46696) 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 88e664b79fd02eb4a29c11daec27cc382b548709 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Thu, 20 Mar 2025 13:25:53 +0530 Subject: [PATCH 07/10] 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 de8aecda72b..c76cf5d6e80 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py @@ -6,6 +6,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): def __init__(self, *args, **kwargs): @@ -77,6 +79,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() @@ -155,6 +160,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 f631ef437d6..c8718113c94 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): @@ -202,9 +204,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 c3447c030ac83b2b7f010f9981398e20583d7706 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Wed, 19 Mar 2025 16:52:13 +0530 Subject: [PATCH 08/10] fix: do not validate if conversion rate is 1 for different currency (cherry picked from commit e8a66d03bc617bdde4cedf1703190c15c31cbe91) # Conflicts: # erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py --- .../sales_invoice/test_sales_invoice.py | 44 +++++++++++++++++++ erpnext/controllers/accounts_controller.py | 17 ++++--- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index a407f90b51f..0de7af08692 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1773,16 +1773,60 @@ class TestSalesInvoice(FrappeTestCase): self.assertTrue(gle) +<<<<<<< HEAD def test_invoice_exchange_rate(self): +======= + def test_gle_in_transaction_currency(self): + # create multi currency sales invoice with 2 items with same income account +>>>>>>> e8a66d03bc (fix: do not validate if conversion rate is 1 for different currency) si = create_sales_invoice( customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC", currency="USD", +<<<<<<< HEAD conversion_rate=1, do_not_save=1, ) self.assertRaises(frappe.ValidationError, si.save) +======= + conversion_rate=50, + do_not_submit=True, + ) + # add 2nd item with same income account + si.append( + "items", + { + "item_code": "_Test Item", + "qty": 1, + "rate": 80, + "income_account": "Sales - _TC", + "cost_center": "_Test Cost Center - _TC", + }, + ) + si.submit() + + gl_entries = frappe.db.sql( + """select transaction_currency, transaction_exchange_rate, + debit_in_transaction_currency, credit_in_transaction_currency + from `tabGL Entry` + where voucher_type='Sales Invoice' and voucher_no=%s and account = 'Sales - _TC' + order by account asc""", + si.name, + as_dict=1, + ) + + expected_gle = { + "transaction_currency": "USD", + "transaction_exchange_rate": 50, + "debit_in_transaction_currency": 0, + "credit_in_transaction_currency": 180, + } + + for gle in gl_entries: + for field in expected_gle: + self.assertEqual(expected_gle[field], gle[field]) +>>>>>>> e8a66d03bc (fix: do not validate if conversion rate is 1 for different currency) def test_invalid_currency(self): # Customer currency = USD diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 1d4bd3b01f3..89fd7e30ef2 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2459,12 +2459,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 cb028b8740bc6f445ad2a7d59e178e691160c54b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 25 Mar 2025 14:26:14 +0530 Subject: [PATCH 09/10] chore: resolve conflict --- .../sales_invoice/test_sales_invoice.py | 44 ------------------- 1 file changed, 44 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 0de7af08692..a407f90b51f 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1773,60 +1773,16 @@ class TestSalesInvoice(FrappeTestCase): self.assertTrue(gle) -<<<<<<< HEAD def test_invoice_exchange_rate(self): -======= - def test_gle_in_transaction_currency(self): - # create multi currency sales invoice with 2 items with same income account ->>>>>>> e8a66d03bc (fix: do not validate if conversion rate is 1 for different currency) si = create_sales_invoice( customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC", currency="USD", -<<<<<<< HEAD conversion_rate=1, do_not_save=1, ) self.assertRaises(frappe.ValidationError, si.save) -======= - conversion_rate=50, - do_not_submit=True, - ) - # add 2nd item with same income account - si.append( - "items", - { - "item_code": "_Test Item", - "qty": 1, - "rate": 80, - "income_account": "Sales - _TC", - "cost_center": "_Test Cost Center - _TC", - }, - ) - si.submit() - - gl_entries = frappe.db.sql( - """select transaction_currency, transaction_exchange_rate, - debit_in_transaction_currency, credit_in_transaction_currency - from `tabGL Entry` - where voucher_type='Sales Invoice' and voucher_no=%s and account = 'Sales - _TC' - order by account asc""", - si.name, - as_dict=1, - ) - - expected_gle = { - "transaction_currency": "USD", - "transaction_exchange_rate": 50, - "debit_in_transaction_currency": 0, - "credit_in_transaction_currency": 180, - } - - for gle in gl_entries: - for field in expected_gle: - self.assertEqual(expected_gle[field], gle[field]) ->>>>>>> e8a66d03bc (fix: do not validate if conversion rate is 1 for different currency) def test_invalid_currency(self): # Customer currency = USD From 509c5c4d17f4f3b4637a6ddd3c7f0192d62e7d55 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 25 Mar 2025 15:22:19 +0530 Subject: [PATCH 10/10] fix: removed test case --- .../doctype/sales_invoice/test_sales_invoice.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index a407f90b51f..0f9e339a26e 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1773,17 +1773,6 @@ class TestSalesInvoice(FrappeTestCase): self.assertTrue(gle) - 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