diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index e6403fddef..c0e3ab3ed4 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, @@ -83,7 +84,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 +952,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 +1054,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 @@ -1103,10 +1072,27 @@ class PaymentEntry(AccountsController): against_voucher_type = d.reference_doctype against_voucher = d.reference_name + reverse_dr_or_cr, standalone_note = 0, 0 + if d.reference_doctype in ["Sales Invoice", "Purchase Invoice"]: + 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"): + 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 + + 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: 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, diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index edfec41918..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" @@ -1262,6 +1249,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") diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 12e40037f1..ef28f6038f 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] ) 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 = ( diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index e0adac412b..31bc6fdef8 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -2046,3 +2046,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_all("Party Type", {"account_type": account_type}, pluck="name")