Skip to content

Commit

Permalink
fix(accounts): validate payment entry references with latest data. (#…
Browse files Browse the repository at this point in the history
…31166)

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>
(cherry picked from commit 20de27d)

Co-authored-by: Devin Slauenwhite <devin.slauenwhite@gmail.com>
  • Loading branch information
mergify[bot] and dj12djdjs authored Jun 13, 2023
1 parent dffa682 commit 4add1b4
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 12 deletions.
68 changes: 56 additions & 12 deletions erpnext/accounts/doctype/payment_entry/payment_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,19 +148,57 @@ def validate_payment_type_with_outstanding(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) > flt(d.outstanding_amount):
frappe.throw(
_("Row #{0}: Allocated Amount cannot be greater than outstanding amount.").format(d.idx)
)
frappe.throw(fail_message.format(d.idx))

# Check for negative outstanding invoices as well
if flt(d.allocated_amount) < 0:
if flt(d.allocated_amount) < flt(d.outstanding_amount):
frappe.throw(
_("Row #{0}: Allocated Amount cannot be greater than outstanding amount.").format(d.idx)
)
frappe.throw(fail_message.format(d.idx))

def delink_advance_entry_references(self):
for reference in self.references:
Expand Down Expand Up @@ -373,7 +411,7 @@ def validate_paid_invoices(self):
for k, v in no_oustanding_refs.items():
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(
_(k),
frappe.bold(", ".join(d.reference_name for d in v)),
Expand Down Expand Up @@ -1449,7 +1487,7 @@ def get_orders_to_be_billed(
if voucher_type:
doc = frappe.get_doc({"doctype": voucher_type})
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

orders = []
Expand Down Expand Up @@ -1495,9 +1533,15 @@ def get_orders_to_be_billed(

order_list = []
for d in orders:
if not (
flt(d.outstanding_amount) >= flt(filters.get("outstanding_amt_greater_than"))
and flt(d.outstanding_amount) <= flt(filters.get("outstanding_amt_less_than"))
if (
filters
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

Expand Down
24 changes: 24 additions & 0 deletions erpnext/accounts/doctype/payment_entry/test_payment_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -1013,6 +1013,30 @@ def test_payment_entry_for_employee(self):
employee = make_employee("test_payment_entry@salary.com", company="_Test Company")
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):
payment_entry = frappe.new_doc("Payment Entry")
Expand Down

0 comments on commit 4add1b4

Please sign in to comment.