Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: payments irrespective of party types (backport #37828) #43040

Open
wants to merge 13 commits into
base: version-14-hotfix
Choose a base branch
from
15 changes: 1 addition & 14 deletions erpnext/accounts/doctype/payment_entry/payment_entry.js
Original file line number Diff line number Diff line change
Expand Up @@ -886,20 +886,7 @@ frappe.ui.form.on('Payment Entry', {
var allocated_positive_outstanding = paid_amount + allocated_negative_outstanding;
} else if (["Customer", "Supplier"].includes(frm.doc.party_type)) {
total_negative_outstanding = flt(total_negative_outstanding, precision("outstanding_amount"))
if(paid_amount > total_negative_outstanding) {
if(total_negative_outstanding == 0) {
frappe.msgprint(
__("Cannot {0} {1} {2} without any negative outstanding invoice", [frm.doc.payment_type,
(frm.doc.party_type=="Customer" ? "to" : "from"), frm.doc.party_type])
);
return false
} else {
frappe.msgprint(
__("Paid Amount cannot be greater than total negative outstanding amount {0}", [total_negative_outstanding])
);
return false;
}
} else {
if(paid_amount < total_negative_outstanding) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@barredterra I think it makes sense to reverse the original if condition since we're removing the path that triggered the validation. I haven't spent much time reading this section of PE logic. Do you have any initial doubts about this?

allocated_positive_outstanding = total_negative_outstanding - paid_amount;
allocated_negative_outstanding = paid_amount +
(total_positive_outstanding_including_order < allocated_positive_outstanding ?
Expand Down
79 changes: 30 additions & 49 deletions erpnext/accounts/doctype/payment_entry/payment_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
get_account_currency,
get_balance_on,
get_outstanding_invoices,
get_party_types_from_account_type,
)
from erpnext.controllers.accounts_controller import (
AccountsController,
Expand Down Expand Up @@ -79,7 +80,6 @@ def validate(self):
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()
Expand Down Expand Up @@ -948,40 +948,6 @@ def clear_unallocated_reference_document_rows(self):
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 = flt(
sum(
abs(flt(d.outstanding_amount))
for d in self.get("references")
if flt(d.outstanding_amount) < 0
),
self.references[0].precision("outstanding_amount") if self.references else None,
)

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.
Expand Down Expand Up @@ -1086,40 +1052,55 @@ def add_party_gl_entries(self, gl_entries):
item=self,
)

dr_or_cr = (
"credit" if erpnext.get_party_account_type(self.party_type) == "Receivable" else "debit"
)

for d in self.get("references"):
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
cost_center = self.cost_center
if d.reference_doctype == "Sales Invoice" and not cost_center:
cost_center = frappe.db.get_value(d.reference_doctype, d.reference_name, "cost_center")

gle = party_gl_dict.copy()
gle.update(
{
"against_voucher_type": d.reference_doctype,
"against_voucher": d.reference_name,
"cost_center": cost_center,
}
)

allocated_amount_in_company_currency = self.calculate_base_allocated_amount_for_reference(d)
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

if is_return and not reverse_dr_or_cr:
dr_or_cr = "debit" if dr_or_cr == "credit" else "credit"

gle.update(
{
dr_or_cr + "_in_account_currency": d.allocated_amount,
dr_or_cr: allocated_amount_in_company_currency,
dr_or_cr: abs(allocated_amount_in_company_currency),
dr_or_cr + "_in_account_currency": abs(d.allocated_amount),
"against_voucher_type": d.reference_doctype,
"against_voucher": d.reference_name,
"cost_center": cost_center,
}
)

gl_entries.append(gle)

if self.unallocated_amount:
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
exchange_rate = self.get_exchange_rate()
base_unallocated_amount = self.unallocated_amount * exchange_rate

gle = party_gl_dict.copy()

gle.update(
{
dr_or_cr + "_in_account_currency": self.unallocated_amount,
Expand Down
46 changes: 33 additions & 13 deletions erpnext/accounts/doctype/payment_entry/test_payment_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -681,17 +681,6 @@ def test_internal_transfer_usd_to_inr(self):
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
Expand Down Expand Up @@ -749,8 +738,6 @@ def test_payment_against_negative_sales_invoice(self):

# 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"
Expand Down Expand Up @@ -1438,6 +1425,39 @@ def test_delete_linked_exchange_gain_loss_journal(self):
self.assertRaises(frappe.DoesNotExistError, frappe.get_doc, pe.doctype, pe.name)
self.assertRaises(frappe.DoesNotExistError, frappe.get_doc, "Journal Entry", jv[0])

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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
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

Expand Down Expand Up @@ -68,7 +68,7 @@ def set_defaults(self):
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

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_currency_precision
from erpnext.accounts.utils import get_currency_precision, get_party_types_from_account_type


def execute(filters=None):
Expand All @@ -23,7 +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])
self.get_columns()
self.get_data(args)
Expand Down
4 changes: 4 additions & 0 deletions erpnext/accounts/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2133,6 +2133,10 @@ def create_gain_loss_journal(
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")


def run_ledger_health_checks():
health_monitor_settings = frappe.get_doc("Ledger Health Monitor")
if health_monitor_settings.enable_health_monitor:
Expand Down
Loading