Merge pull request #38007 from frappe/mergify/bp/version-15-hotfix/pr-37828
fix: payments irrespective of party types (backport #37828)
This commit is contained in:
commit
1b103faf05
@ -33,6 +33,7 @@ from erpnext.accounts.utils import (
|
|||||||
get_account_currency,
|
get_account_currency,
|
||||||
get_balance_on,
|
get_balance_on,
|
||||||
get_outstanding_invoices,
|
get_outstanding_invoices,
|
||||||
|
get_party_types_from_account_type,
|
||||||
)
|
)
|
||||||
from erpnext.controllers.accounts_controller import (
|
from erpnext.controllers.accounts_controller import (
|
||||||
AccountsController,
|
AccountsController,
|
||||||
@ -83,7 +84,6 @@ class PaymentEntry(AccountsController):
|
|||||||
self.apply_taxes()
|
self.apply_taxes()
|
||||||
self.set_amounts_after_tax()
|
self.set_amounts_after_tax()
|
||||||
self.clear_unallocated_reference_document_rows()
|
self.clear_unallocated_reference_document_rows()
|
||||||
self.validate_payment_against_negative_invoice()
|
|
||||||
self.validate_transaction_reference()
|
self.validate_transaction_reference()
|
||||||
self.set_title()
|
self.set_title()
|
||||||
self.set_remarks()
|
self.set_remarks()
|
||||||
@ -952,35 +952,6 @@ class PaymentEntry(AccountsController):
|
|||||||
self.name,
|
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):
|
def set_title(self):
|
||||||
if frappe.flags.in_import and self.title:
|
if frappe.flags.in_import and self.title:
|
||||||
# do not set title dynamically if title exists during data import.
|
# do not set title dynamically if title exists during data import.
|
||||||
@ -1083,9 +1054,7 @@ class PaymentEntry(AccountsController):
|
|||||||
item=self,
|
item=self,
|
||||||
)
|
)
|
||||||
|
|
||||||
dr_or_cr = (
|
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
|
||||||
"credit" if erpnext.get_party_account_type(self.party_type) == "Receivable" else "debit"
|
|
||||||
)
|
|
||||||
|
|
||||||
for d in self.get("references"):
|
for d in self.get("references"):
|
||||||
cost_center = self.cost_center
|
cost_center = self.cost_center
|
||||||
@ -1103,10 +1072,27 @@ class PaymentEntry(AccountsController):
|
|||||||
against_voucher_type = d.reference_doctype
|
against_voucher_type = d.reference_doctype
|
||||||
against_voucher = d.reference_name
|
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(
|
gle.update(
|
||||||
{
|
{
|
||||||
dr_or_cr: allocated_amount_in_company_currency,
|
dr_or_cr: abs(allocated_amount_in_company_currency),
|
||||||
dr_or_cr + "_in_account_currency": d.allocated_amount,
|
dr_or_cr + "_in_account_currency": abs(d.allocated_amount),
|
||||||
"against_voucher_type": against_voucher_type,
|
"against_voucher_type": against_voucher_type,
|
||||||
"against_voucher": against_voucher,
|
"against_voucher": against_voucher,
|
||||||
"cost_center": cost_center,
|
"cost_center": cost_center,
|
||||||
|
@ -683,17 +683,6 @@ class TestPaymentEntry(FrappeTestCase):
|
|||||||
self.validate_gl_entries(pe.name, expected_gle)
|
self.validate_gl_entries(pe.name, expected_gle)
|
||||||
|
|
||||||
def test_payment_against_negative_sales_invoice(self):
|
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()
|
si1 = create_sales_invoice()
|
||||||
|
|
||||||
# create full payment entry against si1
|
# create full payment entry against si1
|
||||||
@ -751,8 +740,6 @@ class TestPaymentEntry(FrappeTestCase):
|
|||||||
|
|
||||||
# pay more than outstanding against si1
|
# pay more than outstanding against si1
|
||||||
pe3 = get_payment_entry("Sales Invoice", si1.name, bank_account="_Test Cash - _TC")
|
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
|
# pay negative outstanding against si1
|
||||||
pe3.paid_to = "Debtors - _TC"
|
pe3.paid_to = "Debtors - _TC"
|
||||||
@ -1262,6 +1249,39 @@ class TestPaymentEntry(FrappeTestCase):
|
|||||||
so.reload()
|
so.reload()
|
||||||
self.assertEqual(so.advance_paid, so.rounded_total)
|
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):
|
def create_payment_entry(**args):
|
||||||
payment_entry = frappe.new_doc("Payment Entry")
|
payment_entry = frappe.new_doc("Payment Entry")
|
||||||
|
@ -14,7 +14,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
|||||||
get_accounting_dimensions,
|
get_accounting_dimensions,
|
||||||
get_dimension_with_children,
|
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
|
# 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.currency_precision = get_currency_precision() or 2
|
||||||
self.dr_or_cr = "debit" if self.filters.account_type == "Receivable" else "credit"
|
self.dr_or_cr = "debit" if self.filters.account_type == "Receivable" else "credit"
|
||||||
self.account_type = self.filters.account_type
|
self.account_type = self.filters.account_type
|
||||||
self.party_type = frappe.db.get_all(
|
self.party_type = get_party_types_from_account_type(self.account_type)
|
||||||
"Party Type", {"account_type": self.account_type}, pluck="name"
|
|
||||||
)
|
|
||||||
self.party_details = {}
|
self.party_details = {}
|
||||||
self.invoices = set()
|
self.invoices = set()
|
||||||
self.skip_total_row = 0
|
self.skip_total_row = 0
|
||||||
|
@ -8,6 +8,7 @@ from frappe.utils import cint, flt
|
|||||||
|
|
||||||
from erpnext.accounts.party import get_partywise_advanced_payment_amount
|
from erpnext.accounts.party import get_partywise_advanced_payment_amount
|
||||||
from erpnext.accounts.report.accounts_receivable.accounts_receivable import ReceivablePayableReport
|
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):
|
def execute(filters=None):
|
||||||
@ -22,9 +23,7 @@ def execute(filters=None):
|
|||||||
class AccountsReceivableSummary(ReceivablePayableReport):
|
class AccountsReceivableSummary(ReceivablePayableReport):
|
||||||
def run(self, args):
|
def run(self, args):
|
||||||
self.account_type = args.get("account_type")
|
self.account_type = args.get("account_type")
|
||||||
self.party_type = frappe.db.get_all(
|
self.party_type = get_party_types_from_account_type(self.account_type)
|
||||||
"Party Type", {"account_type": self.account_type}, pluck="name"
|
|
||||||
)
|
|
||||||
self.party_naming_by = frappe.db.get_value(
|
self.party_naming_by = frappe.db.get_value(
|
||||||
args.get("naming_by")[0], None, args.get("naming_by")[1]
|
args.get("naming_by")[0], None, args.get("naming_by")[1]
|
||||||
)
|
)
|
||||||
|
@ -316,7 +316,7 @@ def get_tds_docs_query(filters, bank_accounts, tds_accounts):
|
|||||||
if not tds_accounts:
|
if not tds_accounts:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("No {0} Accounts found for this company.").format(frappe.bold("Tax Withholding")),
|
_("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")
|
gle = frappe.qb.DocType("GL Entry")
|
||||||
query = (
|
query = (
|
||||||
|
@ -2040,3 +2040,7 @@ def create_gain_loss_journal(
|
|||||||
journal_entry.save()
|
journal_entry.save()
|
||||||
journal_entry.submit()
|
journal_entry.submit()
|
||||||
return journal_entry.name
|
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")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user