mirror of
https://github.com/frappe/erpnext.git
synced 2026-03-23 16:55:37 +00:00
* refactor: `split_invoices_based_on_payment_terms` - Invoices were in the wrong order due to the logic. The invoices with payment terms were added first and the rest after. - Overly long function with unnecessary loops (reduced to one main loop) and complexity - The split row as per payment terms was not ordered. So the second installment was allocated first (cherry picked from commit6bd56d2d5f) * test: `get_outstanding_reference_documents` (triggered via UI) (cherry picked from commit162c0497d1) # Conflicts: # erpnext/accounts/doctype/payment_entry/test_payment_entry.py * fix: Alert message and make sure invoice due dates are different for effective test - Make invoice due dates are different so that the invoice with the earliest due date is allocated first in the test - Translate voucher type, simplify alert message. The invoice could be "split" into 1 row, no. of rows in the message seems unnecessary. (cherry picked from commit56ac3424d2) * style: Remove spaces introduced via merge conflict (cherry picked from commit4b4b176fcf) # Conflicts: # erpnext/accounts/doctype/payment_entry/test_payment_entry.py * fix: Re-add no.of rows split in alert message (cherry picked from commit1fc5844025) * fix: Merge conflicts in tests --------- Co-authored-by: marination <maricadsouza221197@gmail.com>
This commit is contained in:
@@ -1689,13 +1689,42 @@ def get_outstanding_reference_documents(args, validate=False):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def split_invoices_based_on_payment_terms(outstanding_invoices, company):
|
def split_invoices_based_on_payment_terms(outstanding_invoices, company) -> list:
|
||||||
invoice_ref_based_on_payment_terms = {}
|
"""Split a list of invoices based on their payment terms."""
|
||||||
|
exc_rates = get_currency_data(outstanding_invoices, company)
|
||||||
|
|
||||||
|
outstanding_invoices_after_split = []
|
||||||
|
for entry in outstanding_invoices:
|
||||||
|
if entry.voucher_type in ["Sales Invoice", "Purchase Invoice"]:
|
||||||
|
if payment_term_template := frappe.db.get_value(
|
||||||
|
entry.voucher_type, entry.voucher_no, "payment_terms_template"
|
||||||
|
):
|
||||||
|
split_rows = get_split_invoice_rows(entry, payment_term_template, exc_rates)
|
||||||
|
if not split_rows:
|
||||||
|
continue
|
||||||
|
|
||||||
|
frappe.msgprint(
|
||||||
|
_("Splitting {0} {1} into {2} rows as per Payment Terms").format(
|
||||||
|
_(entry.voucher_type), frappe.bold(entry.voucher_no), len(split_rows)
|
||||||
|
),
|
||||||
|
alert=True,
|
||||||
|
)
|
||||||
|
outstanding_invoices_after_split += split_rows
|
||||||
|
continue
|
||||||
|
|
||||||
|
# If not an invoice or no payment terms template, add as it is
|
||||||
|
outstanding_invoices_after_split.append(entry)
|
||||||
|
|
||||||
|
return outstanding_invoices_after_split
|
||||||
|
|
||||||
|
|
||||||
|
def get_currency_data(outstanding_invoices: list, company: str = None) -> dict:
|
||||||
|
"""Get currency and conversion data for a list of invoices."""
|
||||||
|
exc_rates = frappe._dict()
|
||||||
company_currency = (
|
company_currency = (
|
||||||
frappe.db.get_value("Company", company, "default_currency") if company else None
|
frappe.db.get_value("Company", company, "default_currency") if company else None
|
||||||
)
|
)
|
||||||
exc_rates = frappe._dict()
|
|
||||||
for doctype in ["Sales Invoice", "Purchase Invoice"]:
|
for doctype in ["Sales Invoice", "Purchase Invoice"]:
|
||||||
invoices = [x.voucher_no for x in outstanding_invoices if x.voucher_type == doctype]
|
invoices = [x.voucher_no for x in outstanding_invoices if x.voucher_type == doctype]
|
||||||
for x in frappe.db.get_all(
|
for x in frappe.db.get_all(
|
||||||
@@ -1710,72 +1739,54 @@ def split_invoices_based_on_payment_terms(outstanding_invoices, company):
|
|||||||
company_currency=company_currency,
|
company_currency=company_currency,
|
||||||
)
|
)
|
||||||
|
|
||||||
for idx, d in enumerate(outstanding_invoices):
|
return exc_rates
|
||||||
if d.voucher_type in ["Sales Invoice", "Purchase Invoice"]:
|
|
||||||
payment_term_template = frappe.db.get_value(
|
|
||||||
d.voucher_type, d.voucher_no, "payment_terms_template"
|
def get_split_invoice_rows(invoice: dict, payment_term_template: str, exc_rates: dict) -> list:
|
||||||
|
"""Split invoice based on its payment schedule table."""
|
||||||
|
split_rows = []
|
||||||
|
allocate_payment_based_on_payment_terms = frappe.db.get_value(
|
||||||
|
"Payment Terms Template", payment_term_template, "allocate_payment_based_on_payment_terms"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not allocate_payment_based_on_payment_terms:
|
||||||
|
return [invoice]
|
||||||
|
|
||||||
|
payment_schedule = frappe.get_all(
|
||||||
|
"Payment Schedule", filters={"parent": invoice.voucher_no}, fields=["*"], order_by="due_date"
|
||||||
|
)
|
||||||
|
for payment_term in payment_schedule:
|
||||||
|
if not payment_term.outstanding > 0.1:
|
||||||
|
continue
|
||||||
|
|
||||||
|
doc_details = exc_rates.get(payment_term.parent, None)
|
||||||
|
is_multi_currency_acc = (doc_details.currency != doc_details.company_currency) and (
|
||||||
|
doc_details.party_account_currency != doc_details.company_currency
|
||||||
|
)
|
||||||
|
payment_term_outstanding = flt(payment_term.outstanding)
|
||||||
|
if not is_multi_currency_acc:
|
||||||
|
payment_term_outstanding = doc_details.conversion_rate * flt(payment_term.outstanding)
|
||||||
|
|
||||||
|
split_rows.append(
|
||||||
|
frappe._dict(
|
||||||
|
{
|
||||||
|
"due_date": invoice.due_date,
|
||||||
|
"currency": invoice.currency,
|
||||||
|
"voucher_no": invoice.voucher_no,
|
||||||
|
"voucher_type": invoice.voucher_type,
|
||||||
|
"posting_date": invoice.posting_date,
|
||||||
|
"invoice_amount": flt(invoice.invoice_amount),
|
||||||
|
"outstanding_amount": payment_term_outstanding
|
||||||
|
if payment_term_outstanding
|
||||||
|
else invoice.outstanding_amount,
|
||||||
|
"payment_term_outstanding": payment_term_outstanding,
|
||||||
|
"payment_amount": payment_term.payment_amount,
|
||||||
|
"payment_term": payment_term.payment_term,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
if payment_term_template:
|
)
|
||||||
allocate_payment_based_on_payment_terms = frappe.get_cached_value(
|
|
||||||
"Payment Terms Template", payment_term_template, "allocate_payment_based_on_payment_terms"
|
|
||||||
)
|
|
||||||
if allocate_payment_based_on_payment_terms:
|
|
||||||
payment_schedule = frappe.get_all(
|
|
||||||
"Payment Schedule", filters={"parent": d.voucher_no}, fields=["*"]
|
|
||||||
)
|
|
||||||
|
|
||||||
for payment_term in payment_schedule:
|
return split_rows
|
||||||
if payment_term.outstanding > 0.1:
|
|
||||||
doc_details = exc_rates.get(payment_term.parent, None)
|
|
||||||
is_multi_currency_acc = (doc_details.currency != doc_details.company_currency) and (
|
|
||||||
doc_details.party_account_currency != doc_details.company_currency
|
|
||||||
)
|
|
||||||
payment_term_outstanding = flt(payment_term.outstanding)
|
|
||||||
if not is_multi_currency_acc:
|
|
||||||
payment_term_outstanding = doc_details.conversion_rate * flt(payment_term.outstanding)
|
|
||||||
|
|
||||||
invoice_ref_based_on_payment_terms.setdefault(idx, [])
|
|
||||||
invoice_ref_based_on_payment_terms[idx].append(
|
|
||||||
frappe._dict(
|
|
||||||
{
|
|
||||||
"due_date": d.due_date,
|
|
||||||
"currency": d.currency,
|
|
||||||
"voucher_no": d.voucher_no,
|
|
||||||
"voucher_type": d.voucher_type,
|
|
||||||
"posting_date": d.posting_date,
|
|
||||||
"invoice_amount": flt(d.invoice_amount),
|
|
||||||
"outstanding_amount": payment_term_outstanding
|
|
||||||
if payment_term_outstanding
|
|
||||||
else d.outstanding_amount,
|
|
||||||
"payment_term_outstanding": payment_term_outstanding,
|
|
||||||
"payment_amount": payment_term.payment_amount,
|
|
||||||
"payment_term": payment_term.payment_term,
|
|
||||||
"account": d.account,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
outstanding_invoices_after_split = []
|
|
||||||
if invoice_ref_based_on_payment_terms:
|
|
||||||
for idx, ref in invoice_ref_based_on_payment_terms.items():
|
|
||||||
voucher_no = ref[0]["voucher_no"]
|
|
||||||
voucher_type = ref[0]["voucher_type"]
|
|
||||||
|
|
||||||
frappe.msgprint(
|
|
||||||
_("Spliting {} {} into {} row(s) as per Payment Terms").format(
|
|
||||||
voucher_type, voucher_no, len(ref)
|
|
||||||
),
|
|
||||||
alert=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
outstanding_invoices_after_split += invoice_ref_based_on_payment_terms[idx]
|
|
||||||
|
|
||||||
existing_row = list(filter(lambda x: x.get("voucher_no") == voucher_no, outstanding_invoices))
|
|
||||||
index = outstanding_invoices.index(existing_row[0])
|
|
||||||
outstanding_invoices.pop(index)
|
|
||||||
|
|
||||||
outstanding_invoices_after_split += outstanding_invoices
|
|
||||||
return outstanding_invoices_after_split
|
|
||||||
|
|
||||||
|
|
||||||
def get_orders_to_be_billed(
|
def get_orders_to_be_billed(
|
||||||
|
|||||||
@@ -6,11 +6,12 @@ import unittest
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import qb
|
from frappe import qb
|
||||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||||
from frappe.utils import flt, nowdate
|
from frappe.utils import add_days, flt, nowdate
|
||||||
|
|
||||||
from erpnext.accounts.doctype.account.test_account import create_account
|
from erpnext.accounts.doctype.account.test_account import create_account
|
||||||
from erpnext.accounts.doctype.payment_entry.payment_entry import (
|
from erpnext.accounts.doctype.payment_entry.payment_entry import (
|
||||||
InvalidPaymentEntry,
|
InvalidPaymentEntry,
|
||||||
|
get_outstanding_reference_documents,
|
||||||
get_payment_entry,
|
get_payment_entry,
|
||||||
get_reference_details,
|
get_reference_details,
|
||||||
)
|
)
|
||||||
@@ -1471,6 +1472,45 @@ class TestPaymentEntry(FrappeTestCase):
|
|||||||
for field in ["account", "debit", "credit"]:
|
for field in ["account", "debit", "credit"]:
|
||||||
self.assertEqual(self.expected_gle[row][field], gl_entries[row][field])
|
self.assertEqual(self.expected_gle[row][field], gl_entries[row][field])
|
||||||
|
|
||||||
|
def test_outstanding_invoices_api(self):
|
||||||
|
"""
|
||||||
|
Test if `get_outstanding_reference_documents` fetches invoices in the right order.
|
||||||
|
"""
|
||||||
|
customer = create_customer("Max Mustermann", "INR")
|
||||||
|
create_payment_terms_template()
|
||||||
|
|
||||||
|
# SI has an earlier due date and SI2 has a later due date
|
||||||
|
si = create_sales_invoice(
|
||||||
|
qty=1, rate=100, customer=customer, posting_date=add_days(nowdate(), -4)
|
||||||
|
)
|
||||||
|
si2 = create_sales_invoice(do_not_save=1, qty=1, rate=100, customer=customer)
|
||||||
|
si2.payment_terms_template = "Test Receivable Template"
|
||||||
|
si2.submit()
|
||||||
|
|
||||||
|
args = {
|
||||||
|
"posting_date": nowdate(),
|
||||||
|
"company": "_Test Company",
|
||||||
|
"party_type": "Customer",
|
||||||
|
"payment_type": "Pay",
|
||||||
|
"party": customer,
|
||||||
|
"party_account": "Debtors - _TC",
|
||||||
|
}
|
||||||
|
args.update(
|
||||||
|
{
|
||||||
|
"get_outstanding_invoices": True,
|
||||||
|
"from_posting_date": add_days(nowdate(), -4),
|
||||||
|
"to_posting_date": add_days(nowdate(), 2),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
references = get_outstanding_reference_documents(args)
|
||||||
|
|
||||||
|
self.assertEqual(len(references), 3)
|
||||||
|
self.assertEqual(references[0].voucher_no, si.name)
|
||||||
|
self.assertEqual(references[1].voucher_no, si2.name)
|
||||||
|
self.assertEqual(references[2].voucher_no, si2.name)
|
||||||
|
self.assertEqual(references[1].payment_term, "Basic Amount Receivable")
|
||||||
|
self.assertEqual(references[2].payment_term, "Tax Receivable")
|
||||||
|
|
||||||
|
|
||||||
def create_payment_entry(**args):
|
def create_payment_entry(**args):
|
||||||
payment_entry = frappe.new_doc("Payment Entry")
|
payment_entry = frappe.new_doc("Payment Entry")
|
||||||
@@ -1531,6 +1571,9 @@ def create_payment_terms_template():
|
|||||||
def create_payment_terms_template_with_discount(
|
def create_payment_terms_template_with_discount(
|
||||||
name=None, discount_type=None, discount=None, template_name=None
|
name=None, discount_type=None, discount=None, template_name=None
|
||||||
):
|
):
|
||||||
|
"""
|
||||||
|
Create a Payment Terms Template with % or amount discount.
|
||||||
|
"""
|
||||||
create_payment_term(name or "30 Credit Days with 10% Discount")
|
create_payment_term(name or "30 Credit Days with 10% Discount")
|
||||||
template_name = template_name or "Test Discount Template"
|
template_name = template_name or "Test Discount Template"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user