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'] + }); + } diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.json b/erpnext/accounts/doctype/gl_entry/gl_entry.json index eb99768b05e..f6d36b94e35 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,7 @@ "idx": 1, "in_create": 1, "links": [], - "modified": "2024-03-19 18:30:49.613401", + "modified": "2025-03-21 15:29:11.221890", "modified_by": "Administrator", "module": "Accounts", "name": "GL Entry", @@ -292,4 +293,4 @@ "sort_field": "modified", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} 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"]: 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}) 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 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 ( diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index c2b068b6e62..44cd291004b 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) {