fix(accounts): validate payment entry references with latest data. (#31166)
* test: payment entry over allocation. * fix: validate allocated_amount against latest outstanding amount. * fix: payment entry get outstanding documents for advance payments * fix: only fetch latest outstanding_amount. * fix: throw if reference is allocated * test: throw error if a reference has been partially allocated after inital creation. * chore: test name * fix: remove unused part of test * chore: linter * chore: more user friendly error messages * fix: only validate outstanding amount if partly paid and don't filter by cost center * chore: minor refactor for doc.cost_center Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com> --------- Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com> Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
This commit is contained in:
parent
a3ea985348
commit
20de27d480
@ -148,19 +148,57 @@ class PaymentEntry(AccountsController):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def validate_allocated_amount(self):
|
def validate_allocated_amount(self):
|
||||||
for d in self.get("references"):
|
if self.payment_type == "Internal Transfer":
|
||||||
|
return
|
||||||
|
|
||||||
|
latest_references = get_outstanding_reference_documents(
|
||||||
|
{
|
||||||
|
"posting_date": self.posting_date,
|
||||||
|
"company": self.company,
|
||||||
|
"party_type": self.party_type,
|
||||||
|
"payment_type": self.payment_type,
|
||||||
|
"party": self.party,
|
||||||
|
"party_account": self.paid_from if self.payment_type == "Receive" else self.paid_to,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Group latest_references by (voucher_type, voucher_no)
|
||||||
|
latest_lookup = {}
|
||||||
|
for d in latest_references:
|
||||||
|
d = frappe._dict(d)
|
||||||
|
latest_lookup.update({(d.voucher_type, d.voucher_no): d})
|
||||||
|
|
||||||
|
for d in self.get("references").copy():
|
||||||
|
latest = latest_lookup.get((d.reference_doctype, d.reference_name))
|
||||||
|
|
||||||
|
# The reference has already been fully paid
|
||||||
|
if not latest:
|
||||||
|
frappe.throw(
|
||||||
|
_("{0} {1} has already been fully paid.").format(d.reference_doctype, d.reference_name)
|
||||||
|
)
|
||||||
|
# The reference has already been partly paid
|
||||||
|
elif (
|
||||||
|
latest.outstanding_amount < latest.invoice_amount
|
||||||
|
and d.outstanding_amount != latest.outstanding_amount
|
||||||
|
):
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' button to get the latest outstanding amount."
|
||||||
|
).format(d.reference_doctype, d.reference_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
d.outstanding_amount = latest.outstanding_amount
|
||||||
|
|
||||||
|
fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.")
|
||||||
|
|
||||||
if (flt(d.allocated_amount)) > 0:
|
if (flt(d.allocated_amount)) > 0:
|
||||||
if flt(d.allocated_amount) > flt(d.outstanding_amount):
|
if flt(d.allocated_amount) > flt(d.outstanding_amount):
|
||||||
frappe.throw(
|
frappe.throw(fail_message.format(d.idx))
|
||||||
_("Row #{0}: Allocated Amount cannot be greater than outstanding amount.").format(d.idx)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check for negative outstanding invoices as well
|
# Check for negative outstanding invoices as well
|
||||||
if flt(d.allocated_amount) < 0:
|
if flt(d.allocated_amount) < 0:
|
||||||
if flt(d.allocated_amount) < flt(d.outstanding_amount):
|
if flt(d.allocated_amount) < flt(d.outstanding_amount):
|
||||||
frappe.throw(
|
frappe.throw(fail_message.format(d.idx))
|
||||||
_("Row #{0}: Allocated Amount cannot be greater than outstanding amount.").format(d.idx)
|
|
||||||
)
|
|
||||||
|
|
||||||
def delink_advance_entry_references(self):
|
def delink_advance_entry_references(self):
|
||||||
for reference in self.references:
|
for reference in self.references:
|
||||||
@ -373,7 +411,7 @@ class PaymentEntry(AccountsController):
|
|||||||
for k, v in no_oustanding_refs.items():
|
for k, v in no_oustanding_refs.items():
|
||||||
frappe.msgprint(
|
frappe.msgprint(
|
||||||
_(
|
_(
|
||||||
"{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry."
|
"{} - {} now has {} as it had no outstanding amount left before submitting the Payment Entry."
|
||||||
).format(
|
).format(
|
||||||
_(k),
|
_(k),
|
||||||
frappe.bold(", ".join(d.reference_name for d in v)),
|
frappe.bold(", ".join(d.reference_name for d in v)),
|
||||||
@ -1449,7 +1487,7 @@ def get_orders_to_be_billed(
|
|||||||
if voucher_type:
|
if voucher_type:
|
||||||
doc = frappe.get_doc({"doctype": voucher_type})
|
doc = frappe.get_doc({"doctype": voucher_type})
|
||||||
condition = ""
|
condition = ""
|
||||||
if doc and hasattr(doc, "cost_center"):
|
if doc and hasattr(doc, "cost_center") and doc.cost_center:
|
||||||
condition = " and cost_center='%s'" % cost_center
|
condition = " and cost_center='%s'" % cost_center
|
||||||
|
|
||||||
orders = []
|
orders = []
|
||||||
@ -1495,9 +1533,15 @@ def get_orders_to_be_billed(
|
|||||||
|
|
||||||
order_list = []
|
order_list = []
|
||||||
for d in orders:
|
for d in orders:
|
||||||
if not (
|
if (
|
||||||
flt(d.outstanding_amount) >= flt(filters.get("outstanding_amt_greater_than"))
|
filters
|
||||||
and flt(d.outstanding_amount) <= flt(filters.get("outstanding_amt_less_than"))
|
and filters.get("outstanding_amt_greater_than")
|
||||||
|
and filters.get("outstanding_amt_less_than")
|
||||||
|
and not (
|
||||||
|
flt(filters.get("outstanding_amt_greater_than"))
|
||||||
|
<= flt(d.outstanding_amount)
|
||||||
|
<= flt(filters.get("outstanding_amt_less_than"))
|
||||||
|
)
|
||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -1013,6 +1013,30 @@ class TestPaymentEntry(FrappeTestCase):
|
|||||||
employee = make_employee("test_payment_entry@salary.com", company="_Test Company")
|
employee = make_employee("test_payment_entry@salary.com", company="_Test Company")
|
||||||
create_payment_entry(party_type="Employee", party=employee, save=True)
|
create_payment_entry(party_type="Employee", party=employee, save=True)
|
||||||
|
|
||||||
|
def test_duplicate_payment_entry_allocate_amount(self):
|
||||||
|
si = create_sales_invoice()
|
||||||
|
|
||||||
|
pe_draft = get_payment_entry("Sales Invoice", si.name)
|
||||||
|
pe_draft.insert()
|
||||||
|
|
||||||
|
pe = get_payment_entry("Sales Invoice", si.name)
|
||||||
|
pe.submit()
|
||||||
|
|
||||||
|
self.assertRaises(frappe.ValidationError, pe_draft.submit)
|
||||||
|
|
||||||
|
def test_duplicate_payment_entry_partial_allocate_amount(self):
|
||||||
|
si = create_sales_invoice()
|
||||||
|
|
||||||
|
pe_draft = get_payment_entry("Sales Invoice", si.name)
|
||||||
|
pe_draft.insert()
|
||||||
|
|
||||||
|
pe = get_payment_entry("Sales Invoice", si.name)
|
||||||
|
pe.received_amount = si.total / 2
|
||||||
|
pe.references[0].allocated_amount = si.total / 2
|
||||||
|
pe.submit()
|
||||||
|
|
||||||
|
self.assertRaises(frappe.ValidationError, pe_draft.submit)
|
||||||
|
|
||||||
|
|
||||||
def create_payment_entry(**args):
|
def create_payment_entry(**args):
|
||||||
payment_entry = frappe.new_doc("Payment Entry")
|
payment_entry = frappe.new_doc("Payment Entry")
|
||||||
|
Loading…
Reference in New Issue
Block a user