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(accounts): validate payment entry references with latest data. #31166

Merged
merged 15 commits into from
Jun 13, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 is not None:
anandbaburajan marked this conversation as resolved.
Show resolved Hide resolved
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