From 3d9938221a7fef7e08c0072364d8ebb855933d11 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 1 Nov 2023 15:40:54 +0530 Subject: [PATCH 1/9] fix: remove validation for negative outstanding invoices --- .../doctype/payment_entry/payment_entry.py | 34 +------------------ 1 file changed, 1 insertion(+), 33 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index e6403fddef..f98cd65950 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -83,7 +83,6 @@ class PaymentEntry(AccountsController): self.apply_taxes() self.set_amounts_after_tax() self.clear_unallocated_reference_document_rows() - self.validate_payment_against_negative_invoice() self.validate_transaction_reference() self.set_title() self.set_remarks() @@ -952,35 +951,6 @@ class PaymentEntry(AccountsController): self.name, ) - def validate_payment_against_negative_invoice(self): - if (self.payment_type != "Pay" or self.party_type != "Customer") and ( - self.payment_type != "Receive" or self.party_type != "Supplier" - ): - return - - total_negative_outstanding = sum( - abs(flt(d.outstanding_amount)) for d in self.get("references") if flt(d.outstanding_amount) < 0 - ) - - paid_amount = self.paid_amount if self.payment_type == "Receive" else self.received_amount - additional_charges = sum(flt(d.amount) for d in self.deductions) - - if not total_negative_outstanding: - if self.party_type == "Customer": - msg = _("Cannot pay to Customer without any negative outstanding invoice") - else: - msg = _("Cannot receive from Supplier without any negative outstanding invoice") - - frappe.throw(msg, InvalidPaymentEntry) - - elif paid_amount - additional_charges > total_negative_outstanding: - frappe.throw( - _("Paid Amount cannot be greater than total negative outstanding amount {0}").format( - fmt_money(total_negative_outstanding) - ), - InvalidPaymentEntry, - ) - def set_title(self): if frappe.flags.in_import and self.title: # do not set title dynamically if title exists during data import. @@ -1083,9 +1053,7 @@ class PaymentEntry(AccountsController): item=self, ) - dr_or_cr = ( - "credit" if erpnext.get_party_account_type(self.party_type) == "Receivable" else "debit" - ) + dr_or_cr = "credit" if self.payment_type == "Receive" else "debit" for d in self.get("references"): cost_center = self.cost_center From 1f4b38174899d99be2a3ac894a328c6b7ae7475f Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 2 Nov 2023 17:38:43 +0530 Subject: [PATCH 2/9] fix: test for invoice returns --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index f98cd65950..49ca76ed4f 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1073,8 +1073,8 @@ class PaymentEntry(AccountsController): gle.update( { - dr_or_cr: allocated_amount_in_company_currency, - dr_or_cr + "_in_account_currency": d.allocated_amount, + dr_or_cr: abs(allocated_amount_in_company_currency), + dr_or_cr + "_in_account_currency": abs(d.allocated_amount), "against_voucher_type": against_voucher_type, "against_voucher": against_voucher, "cost_center": cost_center, From cd1e016163141a4851b9128a2ff008809942482e Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 2 Nov 2023 17:39:27 +0530 Subject: [PATCH 3/9] test: receive payments from payable party --- .../payment_entry/test_payment_entry.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index edfec41918..3e8292946c 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -1262,6 +1262,39 @@ class TestPaymentEntry(FrappeTestCase): so.reload() self.assertEqual(so.advance_paid, so.rounded_total) + def test_receive_payment_from_payable_party_type(self): + pe = create_payment_entry( + party_type="Supplier", + party="_Test Supplier", + payment_type="Receive", + paid_from="Creditors - _TC", + paid_to="_Test Cash - _TC", + save=True, + submit=True, + ) + self.voucher_no = pe.name + self.expected_gle = [ + {"account": "_Test Cash - _TC", "debit": 1000.0, "credit": 0.0}, + {"account": "Creditors - _TC", "debit": 0.0, "credit": 1000.0}, + ] + self.check_gl_entries() + + def check_gl_entries(self): + gle = frappe.qb.DocType("GL Entry") + gl_entries = ( + frappe.qb.from_(gle) + .select( + gle.account, + gle.debit, + gle.credit, + ) + .where((gle.voucher_no == self.voucher_no) & (gle.is_cancelled == 0)) + .orderby(gle.account) + ).run(as_dict=True) + for row in range(len(self.expected_gle)): + for field in ["account", "debit", "credit"]: + self.assertEqual(self.expected_gle[row][field], gl_entries[row][field]) + def create_payment_entry(**args): payment_entry = frappe.new_doc("Payment Entry") From 40157235916578ae88d2ec9a85f481c92fec9278 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 3 Nov 2023 11:58:25 +0530 Subject: [PATCH 4/9] fix: credit note receive payment entry --- .../doctype/payment_entry/payment_entry.py | 19 +++++++++++++++++-- erpnext/accounts/utils.py | 4 ++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 49ca76ed4f..23b58947d3 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -33,6 +33,7 @@ from erpnext.accounts.utils import ( get_account_currency, get_balance_on, get_outstanding_invoices, + get_party_types_from_account_type, ) from erpnext.controllers.accounts_controller import ( AccountsController, @@ -1071,10 +1072,24 @@ class PaymentEntry(AccountsController): against_voucher_type = d.reference_doctype against_voucher = d.reference_name + reverse_dr_or_cr = 0 + if d.reference_doctype in ["Sales Invoice", "Purchase Invoice"]: + is_return = frappe.db.get_value(d.reference_doctype, d.reference_name, "is_return") + payable_party_types = get_party_types_from_account_type("Payable") + receivable_party_types = get_party_types_from_account_type("Receivable") + if is_return and self.party_type in receivable_party_types and self.payment_type == "Pay": + reverse_dr_or_cr = 1 + elif is_return and self.party_type in payable_party_types and self.payment_type == "Receive": + reverse_dr_or_cr = 1 + gle.update( { - dr_or_cr: abs(allocated_amount_in_company_currency), - dr_or_cr + "_in_account_currency": abs(d.allocated_amount), + dr_or_cr: abs(allocated_amount_in_company_currency) + if reverse_dr_or_cr + else allocated_amount_in_company_currency, + dr_or_cr + "_in_account_currency": abs(d.allocated_amount) + if reverse_dr_or_cr + else d.allocated_amount, "against_voucher_type": against_voucher_type, "against_voucher": against_voucher, "cost_center": cost_center, diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 1c7052f8ff..0a116a7345 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -2041,3 +2041,7 @@ def create_gain_loss_journal( journal_entry.save() journal_entry.submit() return journal_entry.name + + +def get_party_types_from_account_type(account_type): + return frappe.db.get_list("Party Type", {"account_type": account_type}, pluck="name") From 4867ca353c1ba199844830eb539d9dca5455949b Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Fri, 3 Nov 2023 12:00:56 +0530 Subject: [PATCH 5/9] refactor: move common util for fetching party types using account type --- .../report/accounts_receivable/accounts_receivable.py | 6 ++---- .../accounts_receivable_summary.py | 5 ++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 20444f9496..4cc0f0c6d1 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -14,7 +14,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_accounting_dimensions, get_dimension_with_children, ) -from erpnext.accounts.utils import get_currency_precision +from erpnext.accounts.utils import get_currency_precision, get_party_types_from_account_type # This report gives a summary of all Outstanding Invoices considering the following @@ -72,9 +72,7 @@ class ReceivablePayableReport(object): self.currency_precision = get_currency_precision() or 2 self.dr_or_cr = "debit" if self.filters.account_type == "Receivable" else "credit" self.account_type = self.filters.account_type - self.party_type = frappe.db.get_all( - "Party Type", {"account_type": self.account_type}, pluck="name" - ) + self.party_type = get_party_types_from_account_type(self.account_type) self.party_details = {} self.invoices = set() self.skip_total_row = 0 diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py index 60274cd8b1..d50cf0708e 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py @@ -8,6 +8,7 @@ from frappe.utils import cint, flt from erpnext.accounts.party import get_partywise_advanced_payment_amount from erpnext.accounts.report.accounts_receivable.accounts_receivable import ReceivablePayableReport +from erpnext.accounts.utils import get_party_types_from_account_type def execute(filters=None): @@ -22,9 +23,7 @@ def execute(filters=None): class AccountsReceivableSummary(ReceivablePayableReport): def run(self, args): self.account_type = args.get("account_type") - self.party_type = frappe.db.get_all( - "Party Type", {"account_type": self.account_type}, pluck="name" - ) + self.party_type = get_party_types_from_account_type(self.account_type) self.party_naming_by = frappe.db.get_value( args.get("naming_by")[0], None, args.get("naming_by")[1] ) From 0e100cd451892f3024d031ca7b45964c533cdc26 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 6 Nov 2023 09:51:26 +0530 Subject: [PATCH 6/9] fix: skip check for removed validation --- .../doctype/payment_entry/test_payment_entry.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 3e8292946c..b6b93b6b11 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -683,17 +683,6 @@ class TestPaymentEntry(FrappeTestCase): self.validate_gl_entries(pe.name, expected_gle) def test_payment_against_negative_sales_invoice(self): - pe1 = frappe.new_doc("Payment Entry") - pe1.payment_type = "Pay" - pe1.company = "_Test Company" - pe1.party_type = "Customer" - pe1.party = "_Test Customer" - pe1.paid_from = "_Test Cash - _TC" - pe1.paid_amount = 100 - pe1.received_amount = 100 - - self.assertRaises(InvalidPaymentEntry, pe1.validate) - si1 = create_sales_invoice() # create full payment entry against si1 @@ -751,8 +740,6 @@ class TestPaymentEntry(FrappeTestCase): # pay more than outstanding against si1 pe3 = get_payment_entry("Sales Invoice", si1.name, bank_account="_Test Cash - _TC") - pe3.paid_amount = pe3.received_amount = 300 - self.assertRaises(InvalidPaymentEntry, pe3.validate) # pay negative outstanding against si1 pe3.paid_to = "Debtors - _TC" From 98a8288da2e2516153e30a2e028104f220df3644 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 6 Nov 2023 09:53:11 +0530 Subject: [PATCH 7/9] fix: handle gle for standalone credit and debit notes --- .../doctype/payment_entry/payment_entry.py | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 23b58947d3..c0e3ab3ed4 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -1072,24 +1072,27 @@ class PaymentEntry(AccountsController): against_voucher_type = d.reference_doctype against_voucher = d.reference_name - reverse_dr_or_cr = 0 + reverse_dr_or_cr, standalone_note = 0, 0 if d.reference_doctype in ["Sales Invoice", "Purchase Invoice"]: - is_return = frappe.db.get_value(d.reference_doctype, d.reference_name, "is_return") + is_return, return_against = frappe.db.get_value( + d.reference_doctype, d.reference_name, ["is_return", "return_against"] + ) payable_party_types = get_party_types_from_account_type("Payable") receivable_party_types = get_party_types_from_account_type("Receivable") - if is_return and self.party_type in receivable_party_types and self.payment_type == "Pay": + if is_return and self.party_type in receivable_party_types and (self.payment_type == "Pay"): reverse_dr_or_cr = 1 - elif is_return and self.party_type in payable_party_types and self.payment_type == "Receive": + elif ( + is_return and self.party_type in payable_party_types and (self.payment_type == "Receive") + ): reverse_dr_or_cr = 1 + if is_return and not return_against and not reverse_dr_or_cr: + dr_or_cr = "debit" if dr_or_cr == "credit" else "credit" + gle.update( { - dr_or_cr: abs(allocated_amount_in_company_currency) - if reverse_dr_or_cr - else allocated_amount_in_company_currency, - dr_or_cr + "_in_account_currency": abs(d.allocated_amount) - if reverse_dr_or_cr - else d.allocated_amount, + dr_or_cr: abs(allocated_amount_in_company_currency), + dr_or_cr + "_in_account_currency": abs(d.allocated_amount), "against_voucher_type": against_voucher_type, "against_voucher": against_voucher, "cost_center": cost_center, From 84f0d1ff1ff231f0388033d2304d977b07411253 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Mon, 6 Nov 2023 10:30:49 +0530 Subject: [PATCH 8/9] chore: linting issues --- .../report/tax_withholding_details/tax_withholding_details.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py index e842d2e8dc..06c9e44b45 100644 --- a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py +++ b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py @@ -316,7 +316,7 @@ def get_tds_docs_query(filters, bank_accounts, tds_accounts): if not tds_accounts: frappe.throw( _("No {0} Accounts found for this company.").format(frappe.bold("Tax Withholding")), - title="Accounts Missing Error", + title=_("Accounts Missing Error"), ) gle = frappe.qb.DocType("GL Entry") query = ( From 2984a86f37e72bbdefd0348d7c456bd6ada540bb Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Wed, 8 Nov 2023 12:30:24 +0530 Subject: [PATCH 9/9] fix: use get_all instead of get_list --- erpnext/accounts/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 0a116a7345..aef0c38c63 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -2044,4 +2044,4 @@ def create_gain_loss_journal( def get_party_types_from_account_type(account_type): - return frappe.db.get_list("Party Type", {"account_type": account_type}, pluck="name") + return frappe.db.get_all("Party Type", {"account_type": account_type}, pluck="name")