mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-29 18:04:46 +00:00
feat: add Partly Paid status in Invoices (#27625)
(cherry picked from commit c8b9a55e96)
# Conflicts:
# erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
# erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
# erpnext/accounts/doctype/sales_invoice/sales_invoice.json
# erpnext/accounts/doctype/sales_invoice/sales_invoice.py
# erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
# erpnext/controllers/accounts_controller.py
This commit is contained in:
@@ -1174,6 +1174,7 @@
|
|||||||
"oldfieldtype": "Section Break",
|
"oldfieldtype": "Section Break",
|
||||||
"options": "fa fa-file-text",
|
"options": "fa fa-file-text",
|
||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
|
<<<<<<< HEAD
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
@@ -1183,6 +1184,8 @@
|
|||||||
"ignore_user_permissions": 1,
|
"ignore_user_permissions": 1,
|
||||||
"label": "Is Internal Supplier",
|
"label": "Is Internal Supplier",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
=======
|
||||||
|
>>>>>>> c8b9a55e96 (feat: add `Partly Paid` status in Invoices (#27625))
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "credit_to",
|
"fieldname": "credit_to",
|
||||||
@@ -1310,6 +1313,17 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "dimension_col_break",
|
"fieldname": "dimension_col_break",
|
||||||
"fieldtype": "Column 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",
|
"fieldname": "tax_withholding_category",
|
||||||
@@ -1399,6 +1413,7 @@
|
|||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"label": "Ignore Default Payment Terms Template",
|
"label": "Ignore Default Payment Terms Template",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
<<<<<<< HEAD
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
@@ -1431,13 +1446,19 @@
|
|||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Tax Withheld Vouchers",
|
"options": "Tax Withheld Vouchers",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
=======
|
||||||
|
>>>>>>> c8b9a55e96 (feat: add `Partly Paid` status in Invoices (#27625))
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"idx": 204,
|
"idx": 204,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
|
<<<<<<< HEAD
|
||||||
"modified": "2022-10-07 14:19:14.214157",
|
"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",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice",
|
"name": "Purchase Invoice",
|
||||||
|
|||||||
@@ -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.gl_entry.gl_entry import update_outstanding_amt
|
||||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
|
from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
|
||||||
check_if_return_invoice_linked_with_payment_entry,
|
check_if_return_invoice_linked_with_payment_entry,
|
||||||
|
<<<<<<< HEAD
|
||||||
get_total_in_party_account_currency,
|
get_total_in_party_account_currency,
|
||||||
|
=======
|
||||||
|
>>>>>>> c8b9a55e96 (feat: add `Partly Paid` status in Invoices (#27625))
|
||||||
is_overdue,
|
is_overdue,
|
||||||
unlink_inter_company_doc,
|
unlink_inter_company_doc,
|
||||||
update_linked_doc,
|
update_linked_doc,
|
||||||
@@ -1567,17 +1570,27 @@ class PurchaseInvoice(BuyingController):
|
|||||||
return
|
return
|
||||||
|
|
||||||
outstanding_amount = flt(self.outstanding_amount, self.precision("outstanding_amount"))
|
outstanding_amount = flt(self.outstanding_amount, self.precision("outstanding_amount"))
|
||||||
|
<<<<<<< HEAD
|
||||||
total = get_total_in_party_account_currency(self)
|
total = get_total_in_party_account_currency(self)
|
||||||
|
=======
|
||||||
|
>>>>>>> c8b9a55e96 (feat: add `Partly Paid` status in Invoices (#27625))
|
||||||
|
|
||||||
if not status:
|
if not status:
|
||||||
if self.docstatus == 2:
|
if self.docstatus == 2:
|
||||||
status = "Cancelled"
|
status = "Cancelled"
|
||||||
elif self.docstatus == 1:
|
elif self.docstatus == 1:
|
||||||
if self.is_internal_transfer():
|
if self.is_internal_transfer():
|
||||||
|
<<<<<<< HEAD
|
||||||
self.status = "Internal Transfer"
|
self.status = "Internal Transfer"
|
||||||
elif is_overdue(self, total):
|
elif is_overdue(self, total):
|
||||||
self.status = "Overdue"
|
self.status = "Overdue"
|
||||||
elif 0 < outstanding_amount < total:
|
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"
|
self.status = "Partly Paid"
|
||||||
elif outstanding_amount > 0 and getdate(self.due_date) >= getdate():
|
elif outstanding_amount > 0 and getdate(self.due_date) >= getdate():
|
||||||
self.status = "Unpaid"
|
self.status = "Unpaid"
|
||||||
|
|||||||
@@ -2047,7 +2047,11 @@
|
|||||||
"link_fieldname": "consolidated_invoice"
|
"link_fieldname": "consolidated_invoice"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
<<<<<<< HEAD
|
||||||
"modified": "2023-01-28 19:45:47.538163",
|
"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",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice",
|
"name": "Sales Invoice",
|
||||||
|
|||||||
@@ -1685,21 +1685,32 @@ class SalesInvoice(SellingController):
|
|||||||
return
|
return
|
||||||
|
|
||||||
outstanding_amount = flt(self.outstanding_amount, self.precision("outstanding_amount"))
|
outstanding_amount = flt(self.outstanding_amount, self.precision("outstanding_amount"))
|
||||||
|
<<<<<<< HEAD
|
||||||
total = get_total_in_party_account_currency(self)
|
total = get_total_in_party_account_currency(self)
|
||||||
|
=======
|
||||||
|
>>>>>>> c8b9a55e96 (feat: add `Partly Paid` status in Invoices (#27625))
|
||||||
|
|
||||||
if not status:
|
if not status:
|
||||||
if self.docstatus == 2:
|
if self.docstatus == 2:
|
||||||
status = "Cancelled"
|
status = "Cancelled"
|
||||||
elif self.docstatus == 1:
|
elif self.docstatus == 1:
|
||||||
if self.is_internal_transfer():
|
if self.is_internal_transfer():
|
||||||
|
<<<<<<< HEAD
|
||||||
self.status = "Internal Transfer"
|
self.status = "Internal Transfer"
|
||||||
elif is_overdue(self, total):
|
elif is_overdue(self, total):
|
||||||
self.status = "Overdue"
|
self.status = "Overdue"
|
||||||
elif 0 < outstanding_amount < total:
|
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"
|
self.status = "Partly Paid"
|
||||||
elif outstanding_amount > 0 and getdate(self.due_date) >= getdate():
|
elif outstanding_amount > 0 and getdate(self.due_date) >= getdate():
|
||||||
self.status = "Unpaid"
|
self.status = "Unpaid"
|
||||||
# Check if outstanding amount is 0 due to credit note issued against invoice
|
# Check if outstanding amount is 0 due to credit note issued against invoice
|
||||||
|
<<<<<<< HEAD
|
||||||
elif (
|
elif (
|
||||||
outstanding_amount <= 0
|
outstanding_amount <= 0
|
||||||
and self.is_return == 0
|
and self.is_return == 0
|
||||||
@@ -1707,6 +1718,9 @@ class SalesInvoice(SellingController):
|
|||||||
"Sales Invoice", {"is_return": 1, "return_against": self.name, "docstatus": 1}
|
"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"
|
self.status = "Credit Note Issued"
|
||||||
elif self.is_return == 1:
|
elif self.is_return == 1:
|
||||||
self.status = "Return"
|
self.status = "Return"
|
||||||
@@ -1760,6 +1774,28 @@ def is_overdue(doc, total):
|
|||||||
return (total - outstanding_amount) < payable_amount
|
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):
|
def get_discounting_status(sales_invoice):
|
||||||
status = None
|
status = None
|
||||||
|
|
||||||
|
|||||||
@@ -2216,6 +2216,11 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
|
|
||||||
def test_credit_note(self):
|
def test_credit_note(self):
|
||||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
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)
|
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)
|
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():
|
def get_sales_invoice_for_e_invoice():
|
||||||
si = make_sales_invoice_for_ewaybill()
|
si = make_sales_invoice_for_ewaybill()
|
||||||
si.naming_series = "INV-2020-.#####"
|
si.naming_series = "INV-2020-.#####"
|
||||||
|
|||||||
@@ -2209,6 +2209,7 @@ def get_advance_payment_entries(
|
|||||||
|
|
||||||
def update_invoice_status():
|
def update_invoice_status():
|
||||||
"""Updates status as Overdue for applicable invoices. Runs daily."""
|
"""Updates status as Overdue for applicable invoices. Runs daily."""
|
||||||
|
<<<<<<< HEAD
|
||||||
today = getdate()
|
today = getdate()
|
||||||
payment_schedule = frappe.qb.DocType("Payment Schedule")
|
payment_schedule = frappe.qb.DocType("Payment Schedule")
|
||||||
for doctype in ("Sales Invoice", "Purchase Invoice"):
|
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()
|
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()
|
@frappe.whitelist()
|
||||||
def get_payment_terms(
|
def get_payment_terms(
|
||||||
|
|||||||
Reference in New Issue
Block a user