mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-22 18:59:58 +00:00
fix(buying): allow purchase returns against a closed purchase order (#58126)
(cherry picked from commit fd728dacca)
# Conflicts:
# erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
# erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
This commit is contained in:
@@ -279,7 +279,11 @@ class PurchaseInvoice(BuyingController):
|
||||
self.check_conversion_rate()
|
||||
self.validate_credit_to_acc()
|
||||
self.clear_unallocated_advances("Purchase Invoice Advance", "advances")
|
||||
<<<<<<< HEAD
|
||||
self.check_on_hold_or_closed_status()
|
||||
=======
|
||||
self.check_purchase_order_on_hold_or_close("purchase_order", exclude_if_field="purchase_receipt")
|
||||
>>>>>>> fd728dacca (fix(buying): allow purchase returns against a closed purchase order (#58126))
|
||||
self.validate_with_previous_doc()
|
||||
self.validate_uom_is_integer("uom", "qty")
|
||||
self.validate_uom_is_integer("stock_uom", "stock_qty")
|
||||
@@ -1707,7 +1711,11 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
super().on_cancel()
|
||||
|
||||
<<<<<<< HEAD
|
||||
self.check_on_hold_or_closed_status()
|
||||
=======
|
||||
self.check_purchase_order_on_hold_or_close("purchase_order", exclude_if_field="purchase_receipt")
|
||||
>>>>>>> fd728dacca (fix(buying): allow purchase returns against a closed purchase order (#58126))
|
||||
|
||||
if self.is_return and not self.update_billed_amount_in_purchase_order:
|
||||
# NOTE status updating bypassed for is_return
|
||||
|
||||
@@ -742,6 +742,13 @@ class BuyingController(SubcontractingController):
|
||||
item.serial_and_batch_bundle, warehouse, type_of_transaction=type_of_transaction
|
||||
)
|
||||
|
||||
def check_purchase_order_on_hold_or_close(self, ref_fieldname, exclude_if_field=None):
|
||||
if self.get("is_return"):
|
||||
return
|
||||
self.check_for_on_hold_or_closed_status(
|
||||
"Purchase Order", ref_fieldname, exclude_if_field=exclude_if_field
|
||||
)
|
||||
|
||||
def update_ordered_and_reserved_qty(self):
|
||||
po_map = {}
|
||||
for d in self.get("items"):
|
||||
@@ -755,7 +762,7 @@ class BuyingController(SubcontractingController):
|
||||
if po and po_item_rows:
|
||||
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(
|
||||
_("{0} {1} is cancelled or closed").format(_("Purchase Order"), po),
|
||||
frappe.InvalidStatusError,
|
||||
|
||||
@@ -262,7 +262,11 @@ class PurchaseReceipt(BuyingController):
|
||||
self.validate_cwip_accounts()
|
||||
self.validate_provisional_expense_account()
|
||||
|
||||
<<<<<<< HEAD
|
||||
self.check_on_hold_or_closed_status()
|
||||
=======
|
||||
self.check_purchase_order_on_hold_or_close("purchase_order")
|
||||
>>>>>>> fd728dacca (fix(buying): allow purchase returns against a closed purchase order (#58126))
|
||||
|
||||
if getdate(self.posting_date) > getdate(nowdate()):
|
||||
throw(_("Posting Date cannot be future date"))
|
||||
@@ -407,7 +411,11 @@ class PurchaseReceipt(BuyingController):
|
||||
def on_cancel(self):
|
||||
super().on_cancel()
|
||||
|
||||
<<<<<<< HEAD
|
||||
self.check_on_hold_or_closed_status()
|
||||
=======
|
||||
self.check_purchase_order_on_hold_or_close("purchase_order")
|
||||
>>>>>>> fd728dacca (fix(buying): allow purchase returns against a closed purchase order (#58126))
|
||||
|
||||
self.update_prevdoc_status()
|
||||
self.update_billing_status()
|
||||
|
||||
@@ -708,6 +708,41 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
update_purchase_receipt_status(pr.name, "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.mapper 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):
|
||||
"""Flow:
|
||||
1. PO -> PR1 -> PI
|
||||
|
||||
Reference in New Issue
Block a user