From 320159628f8cdd9adb662642206188cb2bd71c00 Mon Sep 17 00:00:00 2001 From: Tyler Matteson Date: Fri, 14 Jun 2024 12:45:46 -0400 Subject: [PATCH 1/5] fix: fix payment schedule outstanding --- check_run/hooks.py | 6 +- check_run/overrides/payment_entry.py | 41 ++++---- check_run/tests/fixtures.py | 2 +- check_run/tests/setup.py | 2 + check_run/tests/test_check_run.py | 21 ++-- check_run/tests/test_payment_entry.py | 135 ++++++++++++++++++++++++++ 6 files changed, 173 insertions(+), 34 deletions(-) create mode 100644 check_run/tests/test_payment_entry.py diff --git a/check_run/hooks.py b/check_run/hooks.py index 1fa96f17..1f22dad5 100644 --- a/check_run/hooks.py +++ b/check_run/hooks.py @@ -110,9 +110,11 @@ "Payment Entry": { "validate": [ "check_run.overrides.payment_entry.validate_duplicate_check_number", - "check_run.overrides.payment_entry.validate_add_payment_term", ], - "on_submit": ["check_run.overrides.payment_entry.update_check_number"], + "on_submit": [ + "check_run.overrides.payment_entry.update_outstanding_amount", + "check_run.overrides.payment_entry.update_check_number", + ], }, "Purchase Invoice": { "before_cancel": ["check_run.check_run.disallow_cancellation_if_in_check_run"] diff --git a/check_run/overrides/payment_entry.py b/check_run/overrides/payment_entry.py index 75efc9b1..f21823ef 100644 --- a/check_run/overrides/payment_entry.py +++ b/check_run/overrides/payment_entry.py @@ -2,7 +2,7 @@ # For license information, please see license.txt import frappe -from frappe.utils import get_link_to_form, comma_and, flt +from frappe.utils import get_link_to_form, flt from erpnext.accounts.general_ledger import make_gl_entries, process_gl_map from frappe.utils.data import getdate from erpnext.accounts.doctype.payment_entry.payment_entry import ( @@ -10,7 +10,6 @@ get_outstanding_reference_documents, ) from frappe import _ -import json class CheckRunPaymentEntry(PaymentEntry): @@ -278,28 +277,28 @@ def validate_duplicate_check_number(doc: PaymentEntry, method: str | None = None @frappe.whitelist() -def validate_add_payment_term(doc: PaymentEntry, method: str | None = None): - doc = frappe._dict(json.loads(doc)) if isinstance(doc, str) else doc - if doc.check_run: - return - adjusted_refs = [] +def update_outstanding_amount(doc: PaymentEntry, method: str | None = None): + paid_amount = doc.paid_amount for r in doc.get("references"): - if r.reference_doctype == "Purchase Invoice" and not r.payment_term: - pmt_term = frappe.get_all( + if r.reference_doctype == "Purchase Invoice": + payment_schedule = frappe.get_all( "Payment Schedule", {"parent": r.reference_name, "outstanding": [">", 0.0]}, - ["payment_term"], + ["name", "outstanding", "payment_term"], order_by="due_date ASC", limit=1, ) - if pmt_term: - r.payment_term = pmt_term[0].get("payment_term") - adjusted_refs.append(r.reference_name) - if adjusted_refs: - frappe.msgprint( - msg=frappe._( - f"An outstanding Payment Schedule term was detected and added for {comma_and(adjusted_refs)} in the references table.
Please review - " - "this field must be filled in for the Payment Schedule to synchronize and to prevent a paid invoice portion from showing up in a Check Run." - ), - title=frappe._("Payment Schedule Term Added"), - ) + for term in payment_schedule: + if r.payment_term and term.payment_term != r.payment_term: + continue + if paid_amount <= 0.0: + break + if term.outstanding > 0.0: + if term.outstanding > paid_amount: + frappe.db.set_value( + "Payment Schedule", term.name, "outstanding", flt(term.outstanding - paid_amount) + ) + break + else: + paid_amount = paid_amount - term.outstanding + frappe.db.set_value("Payment Schedule", term.name, "outstanding", 0) diff --git a/check_run/tests/fixtures.py b/check_run/tests/fixtures.py index 2129869c..b8d9707f 100644 --- a/check_run/tests/fixtures.py +++ b/check_run/tests/fixtures.py @@ -74,7 +74,7 @@ "Phone Services", "ACH/EFT", 250.00, - "Net 30", + "", { "address_line1": "1198 Carpenter Road", "city": "Rolla", diff --git a/check_run/tests/setup.py b/check_run/tests/setup.py index 5b4afc7c..76f9b442 100644 --- a/check_run/tests/setup.py +++ b/check_run/tests/setup.py @@ -400,6 +400,8 @@ def create_invoices(settings): "qty": 1, }, ) + if supplier[0].startswith("Sphere"): + pi.payment_terms_template = None pi.save() pi.submit() # two electric meters / test invoice aggregation diff --git a/check_run/tests/test_check_run.py b/check_run/tests/test_check_run.py index 5bfac7ac..a2926fa3 100644 --- a/check_run/tests/test_check_run.py +++ b/check_run/tests/test_check_run.py @@ -4,23 +4,24 @@ import frappe -from check_run.check_run.doctype.check_run.check_run import get_check_run_settings, get_entries +from check_run.check_run.doctype.check_run.check_run import ( + get_check_run_settings, + get_entries, + check_for_draft_check_run, +) year = datetime.date.today().year @pytest.fixture def cr(): # return draft check run - if ( - frappe.db.exists("Check Run", f"ACC-CR-{year}-00001") - and frappe.get_value("Check Run", f"ACC-CR-{year}-00001", "docstatus") == 0 - ): - return frappe.get_doc("Check Run", f"ACC-CR-{year}-00001") - cr = frappe.new_doc("Check Run") + cr_name = check_for_draft_check_run( + company="Chelsea Fruit Co", + bank_account="Primary Checking - Local Bank", + payable_account="2110 - Accounts Payable - CFC", + ) + cr = frappe.get_doc("Check Run", cr_name) cr.flags.in_test = True - cr.company = "Chelsea Fruit Co" - cr.bank_account = "Primary Checking - Local Bank" - cr.pay_to_account = "2110 - Accounts Payable - CFC" cr.posting_date = cr.end_date = datetime.date(year, 12, 31) cr.set_last_check_number() cr.set_default_payable_account() diff --git a/check_run/tests/test_payment_entry.py b/check_run/tests/test_payment_entry.py new file mode 100644 index 00000000..739f65c4 --- /dev/null +++ b/check_run/tests/test_payment_entry.py @@ -0,0 +1,135 @@ +import datetime +import pytest +import frappe + +from check_run.check_run.doctype.check_run.check_run import ( + get_check_run_settings, + get_entries, + check_for_draft_check_run, +) +from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry +from check_run.tests.test_check_run import cr + + +year = datetime.date.today().year + +# test partial payment with and without multiple payment terms + + +def test_partial_payment_payment_entry_with_terms(): + pi_name = frappe.get_all( + "Purchase Invoice", + {"supplier": "Exceptional Grid"}, + pluck="name", + order_by="posting_date ASC", + limit=1, + )[0] + pe0 = get_payment_entry("Purchase Invoice", pi_name) + pe0.mode_of_payment = "Check" + pe0.paid_amount = 30.00 + pe0.bank_account = "Primary Checking - Local Bank" + pe0.reference_no = frappe.get_value("Bank Account", pe0.bank_account, "check_number") + pe0.references[0].allocated_amount = 30.00 + pe0.save() + pe0.submit() + + pi = frappe.get_doc("Purchase Invoice", pi_name) + assert pi.payment_schedule[0].outstanding == 120.00 + + pe1 = get_payment_entry("Purchase Invoice", pi_name) + pe1.mode_of_payment = "Check" + pe1.paid_amount = 120.00 + pe1.bank_account = "Primary Checking - Local Bank" + pe1.reference_no = frappe.get_value("Bank Account", pe1.bank_account, "check_number") + pe1.references[0].allocated_amount = 120.00 + pe1.save() + pe1.submit() + + pi = frappe.get_doc("Purchase Invoice", pi_name) + assert pi.payment_schedule[0].outstanding == 0.00 + + +def test_partial_payment_payment_entry_without_terms(): + pi_name = frappe.get_all( + "Purchase Invoice", + {"supplier": "Sphere Cellular"}, + pluck="name", + order_by="posting_date ASC", + limit=1, + )[0] + pe0 = get_payment_entry("Purchase Invoice", pi_name) + pe0.mode_of_payment = "Check" + pe0.paid_amount = 100.00 + pe0.bank_account = "Primary Checking - Local Bank" + pe0.reference_no = frappe.get_value("Bank Account", pe0.bank_account, "check_number") + pe0.references[0].allocated_amount = 100.00 + pe0.save() + pe0.submit() + + pi = frappe.get_doc("Purchase Invoice", pi_name) + assert pi.payment_schedule[0].outstanding == 150.00 + + pe1 = get_payment_entry("Purchase Invoice", pi_name) + pe1.mode_of_payment = "Check" + pe1.paid_amount = 100.00 + pe1.bank_account = "Primary Checking - Local Bank" + pe1.reference_no = frappe.get_value("Bank Account", pe1.bank_account, "check_number") + pe1.references[0].allocated_amount = 100.00 + pe1.save() + pe1.submit() + + pi = frappe.get_doc("Purchase Invoice", pi_name) + assert pi.payment_schedule[0].outstanding == 50.00 + + pe2 = get_payment_entry("Purchase Invoice", pi_name) + pe2.mode_of_payment = "Check" + pe2.paid_amount = 100.00 + pe2.bank_account = "Primary Checking - Local Bank" + pe2.reference_no = frappe.get_value("Bank Account", pe2.bank_account, "check_number") + pe2.references[0].allocated_amount = 100.00 + + pi = frappe.get_doc("Purchase Invoice", pi_name) + with pytest.raises( + frappe.exceptions.ValidationError, + match=f"Row #1 Sphere Cellular / ACC-PINV-{year}-00007: Allocated Amount of 100.0 cannot be greater than outstanding amount of 50.0.", + ): + pe2.save() + + pe2.paid_amount = 50.00 + pe2.references[0].allocated_amount = 50.00 + pe2.save() + pe2.submit() + + pi.reload() + assert pi.payment_schedule[0].outstanding == 00.00 + + +def test_outstanding_amount_in_check_run(cr): + pi_name = frappe.get_all( + "Purchase Invoice", + {"supplier": "Mare Digitalis"}, + pluck="name", + order_by="posting_date ASC", + limit=1, + )[0] + pe0 = get_payment_entry("Purchase Invoice", pi_name) + pe0.mode_of_payment = "Check" + pe0.paid_amount = 110.00 + pe0.bank_account = "Primary Checking - Local Bank" + pe0.reference_no = frappe.get_value("Bank Account", pe0.bank_account, "check_number") + pe0.references[0].allocated_amount = 110.00 + pe0.save() + pe0.submit() + + pi = frappe.get_doc("Purchase Invoice", pi_name) + assert pi.payment_schedule[0].outstanding == 90.00 + + cr.transactions = None + cr.save() + entries = get_entries(cr) + for row in entries.get("transactions"): + row["pay"] = False + transactions = frappe.utils.safe_json_loads(entries.get("transactions")) + + t = list(filter(lambda x: x.get("name") == f"ACC-PINV-{year}-00004", transactions)) + assert t[0].get("amount") == 90.00 From 4065ebbd0c69bc316f730c537b4aa5220adfa749 Mon Sep 17 00:00:00 2001 From: Tyler Matteson Date: Fri, 14 Jun 2024 13:05:32 -0400 Subject: [PATCH 2/5] test: assert parent doc amount matches --- check_run/tests/test_payment_entry.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/check_run/tests/test_payment_entry.py b/check_run/tests/test_payment_entry.py index 739f65c4..28e67437 100644 --- a/check_run/tests/test_payment_entry.py +++ b/check_run/tests/test_payment_entry.py @@ -35,6 +35,7 @@ def test_partial_payment_payment_entry_with_terms(): pi = frappe.get_doc("Purchase Invoice", pi_name) assert pi.payment_schedule[0].outstanding == 120.00 + assert pi.outstanding_amount == 120.00 pe1 = get_payment_entry("Purchase Invoice", pi_name) pe1.mode_of_payment = "Check" @@ -47,6 +48,7 @@ def test_partial_payment_payment_entry_with_terms(): pi = frappe.get_doc("Purchase Invoice", pi_name) assert pi.payment_schedule[0].outstanding == 0.00 + assert pi.outstanding_amount == 0.0 def test_partial_payment_payment_entry_without_terms(): @@ -68,6 +70,7 @@ def test_partial_payment_payment_entry_without_terms(): pi = frappe.get_doc("Purchase Invoice", pi_name) assert pi.payment_schedule[0].outstanding == 150.00 + assert pi.outstanding_amount == 150 pe1 = get_payment_entry("Purchase Invoice", pi_name) pe1.mode_of_payment = "Check" @@ -80,6 +83,7 @@ def test_partial_payment_payment_entry_without_terms(): pi = frappe.get_doc("Purchase Invoice", pi_name) assert pi.payment_schedule[0].outstanding == 50.00 + assert pi.outstanding_amount == 50.00 pe2 = get_payment_entry("Purchase Invoice", pi_name) pe2.mode_of_payment = "Check" @@ -102,6 +106,7 @@ def test_partial_payment_payment_entry_without_terms(): pi.reload() assert pi.payment_schedule[0].outstanding == 00.00 + assert pi.outstanding_amount == 0.00 def test_outstanding_amount_in_check_run(cr): @@ -123,6 +128,7 @@ def test_outstanding_amount_in_check_run(cr): pi = frappe.get_doc("Purchase Invoice", pi_name) assert pi.payment_schedule[0].outstanding == 90.00 + assert pi.outstanding_amount == 90.00 cr.transactions = None cr.save() From de7c3b1586b6d1e6c4ab3ebf8fc9aec7d79c6cac Mon Sep 17 00:00:00 2001 From: Tyler Matteson Date: Fri, 14 Jun 2024 13:15:33 -0400 Subject: [PATCH 3/5] tests: fix matching error message --- check_run/tests/test_payment_entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/check_run/tests/test_payment_entry.py b/check_run/tests/test_payment_entry.py index 28e67437..d2ffa842 100644 --- a/check_run/tests/test_payment_entry.py +++ b/check_run/tests/test_payment_entry.py @@ -95,7 +95,7 @@ def test_partial_payment_payment_entry_without_terms(): pi = frappe.get_doc("Purchase Invoice", pi_name) with pytest.raises( frappe.exceptions.ValidationError, - match=f"Row #1 Sphere Cellular / ACC-PINV-{year}-00007: Allocated Amount of 100.0 cannot be greater than outstanding amount of 50.0.", + match=f'Row #1 Sphere Cellular / ACC-PINV-{year}-00007: Allocated Amount of 100.0 cannot be greater than outstanding amount of 50.0.', ): pe2.save() From a6afc2f0e74190b5ddd2c06381c749c8d5aadeb4 Mon Sep 17 00:00:00 2001 From: Tyler Matteson Date: Tue, 18 Jun 2024 12:02:09 -0400 Subject: [PATCH 4/5] feat: on_cancel hook and tests --- check_run/hooks.py | 3 ++ check_run/overrides/payment_entry.py | 54 +++++++++++++++++------- check_run/tests/test_payment_entry.py | 61 ++++++++++++++++++++++++--- 3 files changed, 97 insertions(+), 21 deletions(-) diff --git a/check_run/hooks.py b/check_run/hooks.py index 1f22dad5..d89fb393 100644 --- a/check_run/hooks.py +++ b/check_run/hooks.py @@ -115,6 +115,9 @@ "check_run.overrides.payment_entry.update_outstanding_amount", "check_run.overrides.payment_entry.update_check_number", ], + "on_cancel": [ + "check_run.overrides.payment_entry.update_outstanding_amount", + ], }, "Purchase Invoice": { "before_cancel": ["check_run.check_run.disallow_cancellation_if_in_check_run"] diff --git a/check_run/overrides/payment_entry.py b/check_run/overrides/payment_entry.py index f21823ef..f8042225 100644 --- a/check_run/overrides/payment_entry.py +++ b/check_run/overrides/payment_entry.py @@ -278,27 +278,51 @@ def validate_duplicate_check_number(doc: PaymentEntry, method: str | None = None @frappe.whitelist() def update_outstanding_amount(doc: PaymentEntry, method: str | None = None): - paid_amount = doc.paid_amount + paid_amount = doc.paid_amount if method == "on_submit" else 0.0 for r in doc.get("references"): if r.reference_doctype == "Purchase Invoice": payment_schedule = frappe.get_all( "Payment Schedule", - {"parent": r.reference_name, "outstanding": [">", 0.0]}, - ["name", "outstanding", "payment_term"], + {"parent": r.reference_name}, + ["name", "outstanding", "payment_term", "payment_amount"], order_by="due_date ASC", - limit=1, ) + payment_schedule = payment_schedule if method == "on_submit" else reversed(payment_schedule) + for term in payment_schedule: - if r.payment_term and term.payment_term != r.payment_term: - continue - if paid_amount <= 0.0: - break - if term.outstanding > 0.0: - if term.outstanding > paid_amount: + if method == "on_submit": + if r.payment_term and term.payment_term != r.payment_term: + continue + if term.outstanding > 0.0 and paid_amount > 0.0: + if term.outstanding > paid_amount: + frappe.db.set_value( + "Payment Schedule", + term.name, + "outstanding", + flt(term.outstanding - paid_amount), # TODO: add precision + ) + break + else: + paid_amount = flt(paid_amount - term.outstanding) # TODO: add precision + frappe.db.set_value("Payment Schedule", term.name, "outstanding", 0) + if paid_amount <= 0.0: + break + + if method == "on_cancel": + if r.payment_term and term.payment_term != r.payment_term: + continue + if term.outstanding != term.payment_amount: + # if this payment term had previously been allocated against + paid_amount += flt( + paid_amount + (term.payment_amount - term.outstanding) + ) # TODO: add precision + reverse = ( + flt(paid_amount + term.outstanding) + if paid_amount < term.payment_amount + else term.payment_amount + ) frappe.db.set_value( - "Payment Schedule", term.name, "outstanding", flt(term.outstanding - paid_amount) + "Payment Schedule", term.name, "outstanding", reverse # TODO: add precision ) - break - else: - paid_amount = paid_amount - term.outstanding - frappe.db.set_value("Payment Schedule", term.name, "outstanding", 0) + if paid_amount >= doc.paid_amount: + break diff --git a/check_run/tests/test_payment_entry.py b/check_run/tests/test_payment_entry.py index d2ffa842..d89b2ead 100644 --- a/check_run/tests/test_payment_entry.py +++ b/check_run/tests/test_payment_entry.py @@ -13,8 +13,6 @@ year = datetime.date.today().year -# test partial payment with and without multiple payment terms - def test_partial_payment_payment_entry_with_terms(): pi_name = frappe.get_all( @@ -51,6 +49,35 @@ def test_partial_payment_payment_entry_with_terms(): assert pi.outstanding_amount == 0.0 +def test_payment_payment_entry_of_multiple_terms(): + pi_name = frappe.get_all( + "Purchase Invoice", + {"supplier": "Tireless Equipment Rental, Inc"}, + pluck="name", + order_by="posting_date ASC", + limit=1, + )[0] + pe0 = get_payment_entry("Purchase Invoice", pi_name) + pe0.mode_of_payment = "Check" + pe0.paid_amount = 4500.00 + pe0.bank_account = "Primary Checking - Local Bank" + pe0.reference_no = frappe.get_value("Bank Account", pe0.bank_account, "check_number") + pe0.references[0].allocated_amount = 4500 + pe0.save() + pe0.submit() + + pi = frappe.get_doc("Purchase Invoice", pi_name) + assert pi.payment_schedule[0].outstanding == 0.0 + assert pi.payment_schedule[1].outstanding == 0.0 + assert pi.payment_schedule[2].outstanding == 500.01 + + pe0.cancel() + pi.reload() + assert pi.payment_schedule[2].outstanding == 1666.67 + assert pi.payment_schedule[1].outstanding == 1666.67 + assert pi.payment_schedule[0].outstanding == 1666.67 + + def test_partial_payment_payment_entry_without_terms(): pi_name = frappe.get_all( "Purchase Invoice", @@ -59,6 +86,10 @@ def test_partial_payment_payment_entry_without_terms(): order_by="posting_date ASC", limit=1, )[0] + pi = frappe.get_doc("Purchase Invoice", pi_name) + assert pi.payment_schedule[0].outstanding == 250.00 + assert pi.outstanding_amount == 250.00 + pe0 = get_payment_entry("Purchase Invoice", pi_name) pe0.mode_of_payment = "Check" pe0.paid_amount = 100.00 @@ -68,7 +99,7 @@ def test_partial_payment_payment_entry_without_terms(): pe0.save() pe0.submit() - pi = frappe.get_doc("Purchase Invoice", pi_name) + pi.reload() assert pi.payment_schedule[0].outstanding == 150.00 assert pi.outstanding_amount == 150 @@ -95,7 +126,7 @@ def test_partial_payment_payment_entry_without_terms(): pi = frappe.get_doc("Purchase Invoice", pi_name) with pytest.raises( frappe.exceptions.ValidationError, - match=f'Row #1 Sphere Cellular / ACC-PINV-{year}-00007: Allocated Amount of 100.0 cannot be greater than outstanding amount of 50.0.', + # match='Allocated Amount of 100.0 cannot be greater than outstanding amount of 50.0', ): pe2.save() @@ -117,6 +148,10 @@ def test_outstanding_amount_in_check_run(cr): order_by="posting_date ASC", limit=1, )[0] + pi = frappe.get_doc("Purchase Invoice", pi_name) + assert pi.outstanding_amount == 200.00 + assert pi.payment_schedule[0].outstanding == 200.00 + pe0 = get_payment_entry("Purchase Invoice", pi_name) pe0.mode_of_payment = "Check" pe0.paid_amount = 110.00 @@ -125,8 +160,7 @@ def test_outstanding_amount_in_check_run(cr): pe0.references[0].allocated_amount = 110.00 pe0.save() pe0.submit() - - pi = frappe.get_doc("Purchase Invoice", pi_name) + pi.reload() assert pi.payment_schedule[0].outstanding == 90.00 assert pi.outstanding_amount == 90.00 @@ -139,3 +173,18 @@ def test_outstanding_amount_in_check_run(cr): t = list(filter(lambda x: x.get("name") == f"ACC-PINV-{year}-00004", transactions)) assert t[0].get("amount") == 90.00 + + pe0.cancel() + pi.reload() + assert pi.payment_schedule[0].outstanding == 200.00 + assert pi.outstanding_amount == 200.00 + + cr.transactions = None + cr.save() + entries = get_entries(cr) + for row in entries.get("transactions"): + row["pay"] = False + transactions = frappe.utils.safe_json_loads(entries.get("transactions")) + + t = list(filter(lambda x: x.get("name") == f"ACC-PINV-{year}-00004", transactions)) + assert t[0].get("amount") == 200.00 From 12c63f0f276b41c883dc7bab9ba6243b67b16b2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Rold=C3=A1n?= Date: Wed, 19 Jun 2024 11:15:43 -0300 Subject: [PATCH 5/5] feat: minor changes, add precision (#253) --- check_run/overrides/payment_entry.py | 87 ++++++++++++++-------------- 1 file changed, 44 insertions(+), 43 deletions(-) diff --git a/check_run/overrides/payment_entry.py b/check_run/overrides/payment_entry.py index f8042225..8461bc64 100644 --- a/check_run/overrides/payment_entry.py +++ b/check_run/overrides/payment_entry.py @@ -280,49 +280,50 @@ def validate_duplicate_check_number(doc: PaymentEntry, method: str | None = None def update_outstanding_amount(doc: PaymentEntry, method: str | None = None): paid_amount = doc.paid_amount if method == "on_submit" else 0.0 for r in doc.get("references"): - if r.reference_doctype == "Purchase Invoice": - payment_schedule = frappe.get_all( - "Payment Schedule", - {"parent": r.reference_name}, - ["name", "outstanding", "payment_term", "payment_amount"], - order_by="due_date ASC", - ) - payment_schedule = payment_schedule if method == "on_submit" else reversed(payment_schedule) - - for term in payment_schedule: - if method == "on_submit": - if r.payment_term and term.payment_term != r.payment_term: - continue - if term.outstanding > 0.0 and paid_amount > 0.0: - if term.outstanding > paid_amount: - frappe.db.set_value( - "Payment Schedule", - term.name, - "outstanding", - flt(term.outstanding - paid_amount), # TODO: add precision - ) - break - else: - paid_amount = flt(paid_amount - term.outstanding) # TODO: add precision - frappe.db.set_value("Payment Schedule", term.name, "outstanding", 0) - if paid_amount <= 0.0: - break - - if method == "on_cancel": - if r.payment_term and term.payment_term != r.payment_term: - continue - if term.outstanding != term.payment_amount: - # if this payment term had previously been allocated against - paid_amount += flt( - paid_amount + (term.payment_amount - term.outstanding) - ) # TODO: add precision - reverse = ( - flt(paid_amount + term.outstanding) - if paid_amount < term.payment_amount - else term.payment_amount - ) + if r.reference_doctype != "Purchase Invoice": + continue + payment_schedules = frappe.get_all( + "Payment Schedule", + {"parent": r.reference_name}, + ["name", "outstanding", "payment_term", "payment_amount"], + order_by="due_date ASC", + ) + if not payment_schedules: + continue + + payment_schedule = frappe.get_doc("Payment Schedule", payment_schedules[0]["name"]) + precision = payment_schedule.precision("outstanding") + payment_schedules = payment_schedules if method == "on_submit" else reversed(payment_schedules) + + for term in payment_schedules: + if r.payment_term and term.payment_term != r.payment_term: + continue + + if method == "on_submit": + if term.outstanding > 0.0 and paid_amount > 0.0: + if term.outstanding > paid_amount: frappe.db.set_value( - "Payment Schedule", term.name, "outstanding", reverse # TODO: add precision + "Payment Schedule", + term.name, + "outstanding", + flt(term.outstanding - paid_amount, precision), ) - if paid_amount >= doc.paid_amount: + break + else: + paid_amount = flt(paid_amount - term.outstanding, precision) + frappe.db.set_value("Payment Schedule", term.name, "outstanding", 0) + if paid_amount <= 0.0: break + + if method == "on_cancel": + if term.outstanding != term.payment_amount: + # if this payment term had previously been allocated against + paid_amount += flt(paid_amount + (term.payment_amount - term.outstanding), precision) + reverse = ( + flt(paid_amount + term.outstanding, precision) + if paid_amount < term.payment_amount + else term.payment_amount + ) + frappe.db.set_value("Payment Schedule", term.name, "outstanding", reverse) + if paid_amount >= doc.paid_amount: + break