Fix #4587: Status does not display "Pending" in report filter (#9104)
* adds Material Request to `status_map`
* updates eval condition for Partially Ordered in Material Request map
* changes material_request doctype to include "pending", "ordered", "partially ordered", "issued", "transferred" as options
* adds more options to `validate_status`
* adds `set_status` just before saving
* adds `check_for_closed_status` in `before_cancel`
* adds patch to convert status to material request specific status
* adds stricter status update conditions
* changes `update_status` to me `set_status`
* adds checker such that draft status can only change to pending
* renames `check_draft_status` to `status_can_change`
* adds Cancelled to Material Request map
* makes `status_can_change` block any attempt to change a cancelled document
* adds more test cases
* updates what `set_status` checks for before adding comment
* adds patch to rename the present material request status
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index 42327b9..2f54fc0 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -85,6 +85,16 @@
["Completed", "eval:self.per_billed == 100 and self.docstatus == 1"],
["Cancelled", "eval:self.docstatus==2"],
["Closed", "eval:self.status=='Closed'"],
+ ],
+ "Material Request": [
+ ["Draft", None],
+ ["Stopped", "eval:self.status == 'Stopped'"],
+ ["Cancelled", "eval:self.docstatus == 2"],
+ ["Pending", "eval:self.status != 'Stopped' and self.per_ordered == 0 and self.docstatus == 1"],
+ ["Partially Ordered", "eval:self.status != 'Stopped' and self.per_ordered < 100 and self.per_ordered > 0 and self.docstatus == 1"],
+ ["Ordered", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"],
+ ["Transferred", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Transfer'"],
+ ["Issued", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Issue'"]
]
}
@@ -127,7 +137,8 @@
self.status = s[0]
break
- if self.status != _status and self.status not in ("Submitted", "Cancelled"):
+ if self.status != _status and self.status not in ("Cancelled", "Partially Ordered",
+ "Ordered", "Issued", "Transferred"):
self.add_comment("Label", _(self.status))
if update:
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 853efa1..0777ab7 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -397,3 +397,4 @@
erpnext.patches.v8_0.fix_status_for_invoices_with_negative_outstanding
erpnext.patches.v8_0.make_payments_table_blank_for_non_pos_invoice
erpnext.patches.v8_0.delete_schools_depricated_doctypes
+erpnext.patches.v8_0.rename_items_in_status_field_of_material_request
diff --git a/erpnext/patches/v8_0/rename_items_in_status_field_of_material_request.py b/erpnext/patches/v8_0/rename_items_in_status_field_of_material_request.py
new file mode 100644
index 0000000..5ad862a
--- /dev/null
+++ b/erpnext/patches/v8_0/rename_items_in_status_field_of_material_request.py
@@ -0,0 +1,25 @@
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ frappe.db.sql(
+ """
+ UPDATE `tabMaterial Request`
+ SET status = CASE
+ WHEN docstatus = 2 THEN 'Cancelled'
+ WHEN docstatus = 0 THEN 'Draft'
+ ELSE CASE
+ WHEN status = 'Stopped' THEN 'Stopped'
+ WHEN status != 'Stopped' AND per_ordered = 0 THEN 'Pending'
+ WHEN per_ordered < 100 AND per_ordered > 0 AND status != 'Stopped'
+ THEN 'Partially Ordered'
+ WHEN per_ordered = 100 AND material_request_type = 'Purchase'
+ AND status != 'Stopped' THEN 'Ordered'
+ WHEN per_ordered = 100 AND material_request_type = 'Material Transfer'
+ AND status != 'Stopped' THEN 'Transferred'
+ WHEN per_ordered = 100 AND material_request_type = 'Material Issue'
+ AND status != 'Stopped' THEN 'Issued'
+ END
+ END
+ """
+ )
\ No newline at end of file
diff --git a/erpnext/stock/doctype/material_request/material_request.json b/erpnext/stock/doctype/material_request/material_request.json
index 2ab0907..fc174a4 100644
--- a/erpnext/stock/doctype/material_request/material_request.json
+++ b/erpnext/stock/doctype/material_request/material_request.json
@@ -1,5 +1,6 @@
{
"allow_copy": 0,
+ "allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 0,
"autoname": "naming_series:",
@@ -12,6 +13,7 @@
"editable_grid": 0,
"fields": [
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -41,6 +43,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
@@ -71,6 +74,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -100,6 +104,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -127,6 +132,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -158,6 +164,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -191,6 +198,7 @@
"width": "150px"
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -225,6 +233,7 @@
"width": "150px"
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -255,6 +264,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -286,6 +296,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
@@ -316,6 +327,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -344,6 +356,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -377,6 +390,7 @@
"width": "100px"
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -407,6 +421,7 @@
"width": "50%"
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -425,7 +440,7 @@
"no_copy": 1,
"oldfieldname": "status",
"oldfieldtype": "Select",
- "options": "\nDraft\nSubmitted\nStopped\nCancelled",
+ "options": "\nDraft\nSubmitted\nStopped\nCancelled\nPending\nPartially Ordered\nOrdered\nIssued\nTransferred",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
@@ -440,6 +455,7 @@
"width": "100px"
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -471,6 +487,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
@@ -500,6 +517,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
@@ -531,6 +549,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
@@ -560,6 +579,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
@@ -591,6 +611,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -622,6 +643,7 @@
"unique": 0
},
{
+ "allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -652,19 +674,19 @@
"unique": 0
}
],
+ "has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-ticket",
"idx": 70,
"image_view": 0,
"in_create": 0,
- "in_dialog": 0,
"is_submittable": 1,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
- "modified": "2017-02-20 13:29:56.743544",
+ "modified": "2017-05-31 15:06:44.611826",
"modified_by": "Administrator",
"module": "Stock",
"name": "Material Request",
diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py
index 82c4c19..65263a0 100644
--- a/erpnext/stock/doctype/material_request/material_request.py
+++ b/erpnext/stock/doctype/material_request/material_request.py
@@ -70,7 +70,9 @@
self.status = "Draft"
from erpnext.controllers.status_updater import validate_status
- validate_status(self.status, ["Draft", "Submitted", "Stopped", "Cancelled"])
+ validate_status(self.status, ["Draft", "Submitted", "Stopped", "Cancelled", "Pending",
+ "Partially Ordered", "Ordered", "Issued", "Transferred"]
+ )
validate_for_items(self)
@@ -91,9 +93,20 @@
self.title = ', '.join(items)
def on_submit(self):
- frappe.db.set(self, 'status', 'Submitted')
+ # frappe.db.set(self, 'status', 'Submitted')
self.update_requested_qty()
+ def before_save(self):
+ self.set_status(update=True)
+
+ def before_submit(self):
+ self.set_status(update=True)
+
+ def before_cancel(self):
+ # if MRQ is already closed, no point saving the document
+ check_for_closed_status(self.doctype, self.name)
+ self.set_status(update=True, status='Cancelled')
+
def check_modified_date(self):
mod_db = frappe.db.sql("""select modified from `tabMaterial Request` where name = %s""",
self.name)
@@ -105,16 +118,36 @@
def update_status(self, status):
self.check_modified_date()
- frappe.db.set(self, 'status', cstr(status))
+ self.status_can_change(status)
+ self.set_status(update=True, status=status)
self.update_requested_qty()
+ def status_can_change(self, status):
+ """
+ validates that `status` is acceptable for the present controller status
+ and throws an Exception if otherwise.
+ """
+ if self.status and self.status == 'Cancelled':
+ # cancelled documents cannot change
+ if status != self.status:
+ frappe.throw(
+ _("{0} {1} is cancelled so the action cannot be completed").
+ format(_(self.doctype), self.name),
+ frappe.InvalidStatusError
+ )
+
+ elif self.status and self.status == 'Draft':
+ # draft document to pending only
+ if status != 'Pending':
+ frappe.throw(
+ _("{0} {1} has not been submitted so the action cannot be completed").
+ format(_(self.doctype), self.name),
+ frappe.InvalidStatusError
+ )
+
def on_cancel(self):
- check_for_closed_status(self.doctype, self.name)
-
self.update_requested_qty()
- frappe.db.set(self,'status','Cancelled')
-
def update_completed_qty(self, mr_items=None, update_modified=True):
if self.material_request_type == "Purchase":
return
diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py
index 8f43acd..c3a2137 100644
--- a/erpnext/stock/doctype/material_request/test_material_request.py
+++ b/erpnext/stock/doctype/material_request/test_material_request.py
@@ -98,6 +98,94 @@
se.insert()
se.submit()
+ def test_cannot_stop_cancelled_material_request(self):
+ mr = frappe.copy_doc(test_records[0])
+ mr.insert()
+ mr.submit()
+
+ mr.load_from_db()
+ mr.cancel()
+ self.assertRaises(frappe.ValidationError, mr.update_status, 'Stopped')
+
+ def test_mr_changes_from_stopped_to_pending_after_reopen(self):
+ mr = frappe.copy_doc(test_records[0])
+ mr.insert()
+ mr.submit()
+ self.assertEqual('Pending', mr.status)
+
+ mr.update_status('Stopped')
+ self.assertEqual('Stopped', mr.status)
+
+ mr.update_status('Submitted')
+ self.assertEqual('Pending', mr.status)
+
+ def test_cannot_submit_cancelled_mr(self):
+ mr = frappe.copy_doc(test_records[0])
+ mr.insert()
+ mr.submit()
+ mr.load_from_db()
+ mr.cancel()
+ self.assertRaises(frappe.ValidationError, mr.submit)
+
+ def test_mr_changes_from_pending_to_cancelled_after_cancel(self):
+ mr = frappe.copy_doc(test_records[0])
+ mr.insert()
+ mr.submit()
+ mr.cancel()
+ self.assertEqual('Cancelled', mr.status)
+
+ def test_cannot_change_cancelled_mr(self):
+ mr = frappe.copy_doc(test_records[0])
+ mr.insert()
+ mr.submit()
+ mr.load_from_db()
+ mr.cancel()
+
+ self.assertRaises(frappe.InvalidStatusError, mr.update_status, 'Draft')
+ self.assertRaises(frappe.InvalidStatusError, mr.update_status, 'Stopped')
+ self.assertRaises(frappe.InvalidStatusError, mr.update_status, 'Ordered')
+ self.assertRaises(frappe.InvalidStatusError, mr.update_status, 'Issued')
+ self.assertRaises(frappe.InvalidStatusError, mr.update_status, 'Transferred')
+ self.assertRaises(frappe.InvalidStatusError, mr.update_status, 'Pending')
+
+ def test_cannot_submit_deleted_material_request(self):
+ mr = frappe.copy_doc(test_records[0])
+ mr.insert()
+ mr.delete()
+
+ self.assertRaises(frappe.ValidationError, mr.submit)
+
+ def test_cannot_delete_submitted_mr(self):
+ mr = frappe.copy_doc(test_records[0])
+ mr.insert()
+ mr.submit()
+
+ self.assertRaises(frappe.ValidationError, mr.delete)
+
+ def test_stopped_mr_changes_to_pending_after_reopen(self):
+ mr = frappe.copy_doc(test_records[0])
+ mr.insert()
+ mr.submit()
+ mr.load_from_db()
+
+ mr.update_status('Stopped')
+ mr.update_status('Submitted')
+ self.assertEqual(mr.status, 'Pending')
+
+ def test_pending_mr_changes_to_stopped_after_stop(self):
+ mr = frappe.copy_doc(test_records[0])
+ mr.insert()
+ mr.submit()
+ mr.load_from_db()
+
+ mr.update_status('Stopped')
+ self.assertEqual(mr.status, 'Stopped')
+
+ def test_cannot_stop_unsubmitted_mr(self):
+ mr = frappe.copy_doc(test_records[0])
+ mr.insert()
+ self.assertRaises(frappe.InvalidStatusError, mr.update_status, 'Stopped')
+
def test_completed_qty_for_purchase(self):
existing_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
existing_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")