mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-28 06:08:25 +00:00
fix(buying): allow purchase returns against a closed purchase order (#58140)
This commit is contained in:
@@ -396,6 +396,9 @@ class PurchaseInvoice(BuyingController):
|
|||||||
self.party_account_currency = account.account_currency
|
self.party_account_currency = account.account_currency
|
||||||
|
|
||||||
def check_on_hold_or_closed_status(self):
|
def check_on_hold_or_closed_status(self):
|
||||||
|
if self.get("is_return"):
|
||||||
|
return
|
||||||
|
|
||||||
check_list = []
|
check_list = []
|
||||||
|
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
|
|||||||
@@ -2609,6 +2609,39 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
|||||||
self.assertEqual(row.serial_no, "\n".join(serial_nos[:2]))
|
self.assertEqual(row.serial_no, "\n".join(serial_nos[:2]))
|
||||||
self.assertEqual(row.rejected_serial_no, serial_nos[2])
|
self.assertEqual(row.rejected_serial_no, serial_nos[2])
|
||||||
|
|
||||||
|
def test_purchase_invoice_return_against_closed_purchase_order(self):
|
||||||
|
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||||
|
|
||||||
|
po = create_purchase_order(qty=2, rate=100)
|
||||||
|
|
||||||
|
invoices = []
|
||||||
|
for _ in range(2):
|
||||||
|
pi = make_pi_from_po(po.name)
|
||||||
|
pi.items[0].qty = 1
|
||||||
|
pi.submit()
|
||||||
|
invoices.append(pi)
|
||||||
|
|
||||||
|
make_return_doc("Purchase Invoice", invoices[0].name).submit()
|
||||||
|
|
||||||
|
po.reload()
|
||||||
|
po.update_status("Closed")
|
||||||
|
|
||||||
|
# a debit note against a closed Purchase Order should still go through,
|
||||||
|
# the same way a Sales Invoice return does against a closed Sales Order
|
||||||
|
debit_note = make_return_doc("Purchase Invoice", invoices[1].name)
|
||||||
|
debit_note.submit()
|
||||||
|
|
||||||
|
self.assertEqual(debit_note.docstatus, 1)
|
||||||
|
self.assertEqual(frappe.db.get_value("Purchase Order", po.name, "status"), "Closed")
|
||||||
|
|
||||||
|
# cancelling the debit note runs the same check on the closed order
|
||||||
|
debit_note.reload()
|
||||||
|
debit_note.cancel()
|
||||||
|
|
||||||
|
# a regular invoice against the closed order must still be blocked
|
||||||
|
blocked_pi = make_pi_from_po(po.name)
|
||||||
|
self.assertRaisesRegex(frappe.InvalidStatusError, "Closed", blocked_pi.save)
|
||||||
|
|
||||||
def test_make_pr_and_pi_from_po(self):
|
def test_make_pr_and_pi_from_po(self):
|
||||||
from erpnext.assets.doctype.asset.test_asset import create_asset_category
|
from erpnext.assets.doctype.asset.test_asset import create_asset_category
|
||||||
|
|
||||||
|
|||||||
@@ -755,7 +755,7 @@ class BuyingController(SubcontractingController):
|
|||||||
if po and po_item_rows:
|
if po and po_item_rows:
|
||||||
po_obj = frappe.get_doc("Purchase Order", po)
|
po_obj = frappe.get_doc("Purchase Order", po)
|
||||||
|
|
||||||
if po_obj.status in ["Closed", "Cancelled"]:
|
if po_obj.status == "Cancelled" or (po_obj.status == "Closed" and not self.get("is_return")):
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("{0} {1} is cancelled or closed").format(_("Purchase Order"), po),
|
_("{0} {1} is cancelled or closed").format(_("Purchase Order"), po),
|
||||||
frappe.InvalidStatusError,
|
frappe.InvalidStatusError,
|
||||||
|
|||||||
@@ -372,6 +372,9 @@ class PurchaseReceipt(BuyingController):
|
|||||||
|
|
||||||
# Check for Closed status
|
# Check for Closed status
|
||||||
def check_on_hold_or_closed_status(self):
|
def check_on_hold_or_closed_status(self):
|
||||||
|
if self.get("is_return"):
|
||||||
|
return
|
||||||
|
|
||||||
check_list = []
|
check_list = []
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
if d.meta.get_field("purchase_order") and d.purchase_order and d.purchase_order not in check_list:
|
if d.meta.get_field("purchase_order") and d.purchase_order and d.purchase_order not in check_list:
|
||||||
|
|||||||
@@ -708,6 +708,43 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
update_purchase_receipt_status(pr.name, "Closed")
|
update_purchase_receipt_status(pr.name, "Closed")
|
||||||
self.assertEqual(frappe.db.get_value("Purchase Receipt", pr.name, "status"), "Closed")
|
self.assertEqual(frappe.db.get_value("Purchase Receipt", pr.name, "status"), "Closed")
|
||||||
|
|
||||||
|
def test_purchase_return_against_closed_purchase_order(self):
|
||||||
|
from erpnext.buying.doctype.purchase_order.purchase_order import (
|
||||||
|
make_purchase_receipt as make_pr_from_po,
|
||||||
|
)
|
||||||
|
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
||||||
|
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||||
|
|
||||||
|
po = create_purchase_order(qty=2, rate=100)
|
||||||
|
|
||||||
|
receipts = []
|
||||||
|
for _ in range(2):
|
||||||
|
pr = make_pr_from_po(po.name)
|
||||||
|
pr.items[0].qty = pr.items[0].received_qty = 1
|
||||||
|
pr.submit()
|
||||||
|
receipts.append(pr)
|
||||||
|
|
||||||
|
first_return = make_return_doc("Purchase Receipt", receipts[0].name)
|
||||||
|
first_return.submit()
|
||||||
|
|
||||||
|
po.reload()
|
||||||
|
po.update_status("Closed")
|
||||||
|
|
||||||
|
# a return against a closed Purchase Order should still go through,
|
||||||
|
# the same way a Delivery Note return does against a closed Sales Order
|
||||||
|
second_return = make_return_doc("Purchase Receipt", receipts[1].name)
|
||||||
|
second_return.submit()
|
||||||
|
|
||||||
|
self.assertEqual(second_return.docstatus, 1)
|
||||||
|
self.assertEqual(frappe.db.get_value("Purchase Order", po.name, "status"), "Closed")
|
||||||
|
|
||||||
|
# cancelling the return runs the same check on the closed order
|
||||||
|
second_return.cancel()
|
||||||
|
|
||||||
|
# a regular receipt against the closed order must still be blocked
|
||||||
|
blocked_pr = make_pr_from_po(po.name)
|
||||||
|
self.assertRaisesRegex(frappe.InvalidStatusError, "Closed", blocked_pr.save)
|
||||||
|
|
||||||
def test_pr_billing_status(self):
|
def test_pr_billing_status(self):
|
||||||
"""Flow:
|
"""Flow:
|
||||||
1. PO -> PR1 -> PI
|
1. PO -> PR1 -> PI
|
||||||
|
|||||||
Reference in New Issue
Block a user