diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index fdd68c6e3ad..ad61c76c8af 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -1174,6 +1174,7 @@ "oldfieldtype": "Section Break", "options": "fa fa-file-text", "print_hide": 1 +<<<<<<< HEAD }, { "default": "0", @@ -1183,6 +1184,8 @@ "ignore_user_permissions": 1, "label": "Is Internal Supplier", "read_only": 1 +======= +>>>>>>> c8b9a55e96 (feat: add `Partly Paid` status in Invoices (#27625)) }, { "fieldname": "credit_to", @@ -1310,6 +1313,17 @@ { "fieldname": "dimension_col_break", "fieldtype": "Column Break" +<<<<<<< HEAD +======= + }, + { + "default": "0", + "fetch_from": "supplier.is_internal_supplier", + "fieldname": "is_internal_supplier", + "fieldtype": "Check", + "label": "Is Internal Supplier", + "read_only": 1 +>>>>>>> c8b9a55e96 (feat: add `Partly Paid` status in Invoices (#27625)) }, { "fieldname": "tax_withholding_category", @@ -1399,6 +1413,7 @@ "hidden": 1, "label": "Ignore Default Payment Terms Template", "read_only": 1 +<<<<<<< HEAD }, { "collapsible": 1, @@ -1431,13 +1446,19 @@ "no_copy": 1, "options": "Tax Withheld Vouchers", "read_only": 1 +======= +>>>>>>> c8b9a55e96 (feat: add `Partly Paid` status in Invoices (#27625)) } ], "icon": "fa fa-file-text", "idx": 204, "is_submittable": 1, "links": [], +<<<<<<< HEAD "modified": "2022-10-07 14:19:14.214157", +======= + "modified": "2021-09-21 09:27:39.967811", +>>>>>>> c8b9a55e96 (feat: add `Partly Paid` status in Invoices (#27625)) "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 94792e6eb13..16f79a75ae7 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -13,7 +13,10 @@ from erpnext.accounts.deferred_revenue import validate_service_stop_date from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt from erpnext.accounts.doctype.sales_invoice.sales_invoice import ( check_if_return_invoice_linked_with_payment_entry, +<<<<<<< HEAD get_total_in_party_account_currency, +======= +>>>>>>> c8b9a55e96 (feat: add `Partly Paid` status in Invoices (#27625)) is_overdue, unlink_inter_company_doc, update_linked_doc, @@ -1567,17 +1570,27 @@ class PurchaseInvoice(BuyingController): return outstanding_amount = flt(self.outstanding_amount, self.precision("outstanding_amount")) +<<<<<<< HEAD total = get_total_in_party_account_currency(self) +======= +>>>>>>> c8b9a55e96 (feat: add `Partly Paid` status in Invoices (#27625)) if not status: if self.docstatus == 2: status = "Cancelled" elif self.docstatus == 1: if self.is_internal_transfer(): +<<<<<<< HEAD self.status = "Internal Transfer" elif is_overdue(self, total): self.status = "Overdue" elif 0 < outstanding_amount < total: +======= + self.status = 'Internal Transfer' + elif is_overdue(self): + self.status = "Overdue" + elif 0 < outstanding_amount < flt(self.grand_total, self.precision("grand_total")): +>>>>>>> c8b9a55e96 (feat: add `Partly Paid` status in Invoices (#27625)) self.status = "Partly Paid" elif outstanding_amount > 0 and getdate(self.due_date) >= getdate(): self.status = "Unpaid" diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index c0b1c596007..f403bea877b 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -2047,7 +2047,11 @@ "link_fieldname": "consolidated_invoice" } ], +<<<<<<< HEAD "modified": "2023-01-28 19:45:47.538163", +======= + "modified": "2021-09-21 09:27:50.191854", +>>>>>>> c8b9a55e96 (feat: add `Partly Paid` status in Invoices (#27625)) "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 4a4a0f839d2..72552a09ebd 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1685,21 +1685,32 @@ class SalesInvoice(SellingController): return outstanding_amount = flt(self.outstanding_amount, self.precision("outstanding_amount")) +<<<<<<< HEAD total = get_total_in_party_account_currency(self) +======= +>>>>>>> c8b9a55e96 (feat: add `Partly Paid` status in Invoices (#27625)) if not status: if self.docstatus == 2: status = "Cancelled" elif self.docstatus == 1: if self.is_internal_transfer(): +<<<<<<< HEAD self.status = "Internal Transfer" elif is_overdue(self, total): self.status = "Overdue" elif 0 < outstanding_amount < total: +======= + self.status = 'Internal Transfer' + elif is_overdue(self): + self.status = "Overdue" + elif 0 < outstanding_amount < flt(self.grand_total, self.precision("grand_total")): +>>>>>>> c8b9a55e96 (feat: add `Partly Paid` status in Invoices (#27625)) self.status = "Partly Paid" elif outstanding_amount > 0 and getdate(self.due_date) >= getdate(): self.status = "Unpaid" # Check if outstanding amount is 0 due to credit note issued against invoice +<<<<<<< HEAD elif ( outstanding_amount <= 0 and self.is_return == 0 @@ -1707,6 +1718,9 @@ class SalesInvoice(SellingController): "Sales Invoice", {"is_return": 1, "return_against": self.name, "docstatus": 1} ) ): +======= + elif outstanding_amount <= 0 and self.is_return == 0 and frappe.db.get_value('Sales Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}): +>>>>>>> c8b9a55e96 (feat: add `Partly Paid` status in Invoices (#27625)) self.status = "Credit Note Issued" elif self.is_return == 1: self.status = "Return" @@ -1760,6 +1774,28 @@ def is_overdue(doc, total): return (total - outstanding_amount) < payable_amount +def is_overdue(doc): + outstanding_amount = flt(doc.outstanding_amount, doc.precision("outstanding_amount")) + + if outstanding_amount <= 0: + return + + grand_total = flt(doc.grand_total, doc.precision("grand_total")) + nowdate = getdate() + if doc.payment_schedule: + # calculate payable amount till date + payable_amount = sum( + payment.payment_amount + for payment in doc.payment_schedule + if getdate(payment.due_date) < nowdate + ) + + if (grand_total - outstanding_amount) < payable_amount: + return True + + elif getdate(doc.due_date) < nowdate: + return True + def get_discounting_status(sales_invoice): status = None diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index c0587e43ddb..2dda2e4551e 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2216,6 +2216,11 @@ class TestSalesInvoice(unittest.TestCase): def test_credit_note(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry +<<<<<<< HEAD +======= + + si = create_sales_invoice(item_code = "_Test Item", qty = (5 * -1), rate=500, is_return = 1) +>>>>>>> c8b9a55e96 (feat: add `Partly Paid` status in Invoices (#27625)) si = create_sales_invoice(item_code="_Test Item", qty=(5 * -1), rate=500, is_return=1) @@ -3568,6 +3573,54 @@ class TestSalesInvoice(unittest.TestCase): self.assertTrue(return_si.docstatus == 1) + def test_payment_statuses(self): + from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry + + today = nowdate() + + # Test Overdue + si = create_sales_invoice(do_not_submit=True) + si.payment_schedule = [] + si.append("payment_schedule", { + "due_date": add_days(today, -5), + "invoice_portion": 50, + "payment_amount": si.grand_total / 2 + }) + si.append("payment_schedule", { + "due_date": add_days(today, 5), + "invoice_portion": 50, + "payment_amount": si.grand_total / 2 + }) + si.submit() + self.assertEqual(si.status, "Overdue") + + # Test payment less than due amount + pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC") + pe.reference_no = "1" + pe.reference_date = nowdate() + pe.paid_amount = 1 + pe.references[0].allocated_amount = pe.paid_amount + pe.submit() + si.reload() + self.assertEqual(si.status, "Overdue") + + # Test Partly Paid + pe = frappe.copy_doc(pe) + pe.paid_amount = si.grand_total / 2 + pe.references[0].allocated_amount = pe.paid_amount + pe.submit() + si.reload() + self.assertEqual(si.status, "Partly Paid") + + # Test Paid + pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC") + pe.reference_no = "1" + pe.reference_date = nowdate() + pe.paid_amount = si.outstanding_amount + pe.submit() + si.reload() + self.assertEqual(si.status, "Paid") + def get_sales_invoice_for_e_invoice(): si = make_sales_invoice_for_ewaybill() si.naming_series = "INV-2020-.#####" diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 98aa28abbea..c0a53d5b673 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2209,6 +2209,7 @@ def get_advance_payment_entries( def update_invoice_status(): """Updates status as Overdue for applicable invoices. Runs daily.""" +<<<<<<< HEAD today = getdate() payment_schedule = frappe.qb.DocType("Payment Schedule") for doctype in ("Sales Invoice", "Purchase Invoice"): @@ -2261,7 +2262,19 @@ def update_invoice_status(): ) frappe.qb.update(invoice).set("status", status).where(conditions).run() +======= +>>>>>>> c8b9a55e96 (feat: add `Partly Paid` status in Invoices (#27625)) + for doctype in ("Sales Invoice", "Purchase Invoice"): + frappe.db.sql(""" + update `tab{}` as dt set dt.status = 'Overdue' + where dt.docstatus = 1 + and dt.status != 'Overdue' + and dt.outstanding_amount > 0 + and (dt.grand_total - dt.outstanding_amount) < + (select sum(payment_amount) from `tabPayment Schedule` as ps + where ps.parent = dt.name and ps.due_date < %s) + """.format(doctype), getdate()) @frappe.whitelist() def get_payment_terms(