fix: validate doc_status for non-submittable doctypes in workflow
This commit is contained in:
parent
f12b5c081b
commit
675b23c47c
5 changed files with 118 additions and 12 deletions
|
|
@ -4,6 +4,11 @@ import { useStore } from "../store";
|
|||
|
||||
let store = useStore();
|
||||
|
||||
const is_doc_status_readonly = computed(() => {
|
||||
if (!store.workflow.selected || !("state" in store.workflow.selected.data)) return false;
|
||||
return !store.is_submittable();
|
||||
});
|
||||
|
||||
let title = ref("Workflow Details");
|
||||
|
||||
let doc = computed(() => {
|
||||
|
|
@ -30,6 +35,13 @@ let properties = computed(() => {
|
|||
);
|
||||
store.statefields.splice(2, 0, allow_edit);
|
||||
|
||||
const submittable = store.is_submittable();
|
||||
|
||||
// Auto-reset doc_status to "Draft" for non-submittable doctypes
|
||||
if (!submittable && store.workflow.selected.data.doc_status !== "Draft") {
|
||||
store.workflow.selected.data.doc_status = "Draft";
|
||||
}
|
||||
|
||||
return store.statefields.filter((df) => {
|
||||
if (df.fieldname == "doc_status") {
|
||||
df.options = ["Draft", "Submitted", "Cancelled"];
|
||||
|
|
@ -61,6 +73,7 @@ let properties = computed(() => {
|
|||
v-model="doc[df.fieldname]"
|
||||
:data-fieldname="df.fieldname"
|
||||
:data-fieldtype="df.fieldtype"
|
||||
:read_only="df.fieldname === 'doc_status' ? is_doc_status_readonly : false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -135,13 +135,18 @@ export const useStore = defineStore("workflow-builder-store", () => {
|
|||
frappe.breadcrumbs.$breadcrumbs.append(breadcrumbs);
|
||||
}
|
||||
|
||||
function is_submittable() {
|
||||
if (!workflow_doc.value?.document_type) return true;
|
||||
return frappe.get_meta(workflow_doc.value.document_type)?.is_submittable;
|
||||
}
|
||||
|
||||
function get_state_df(data) {
|
||||
let doc_status_map = {
|
||||
Draft: 0,
|
||||
Submitted: 1,
|
||||
Cancelled: 2,
|
||||
};
|
||||
data.doc_status = doc_status_map[data.doc_status];
|
||||
data.doc_status = is_submittable() ? doc_status_map[data.doc_status] : 0;
|
||||
return data;
|
||||
}
|
||||
|
||||
|
|
@ -234,5 +239,6 @@ export const useStore = defineStore("workflow-builder-store", () => {
|
|||
reset_changes,
|
||||
save_changes,
|
||||
setup_undo_redo,
|
||||
is_submittable,
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -108,14 +108,15 @@ class TestWorkflow(IntegrationTestCase):
|
|||
self.assertEqual(workflow_actions[0].status, "Completed")
|
||||
|
||||
def test_if_workflow_set_on_action(self):
|
||||
self.workflow, doc = create_new_submittable_doctype_with_workflow()
|
||||
self.workflow._update_state_docstatus = True
|
||||
self.workflow.states[1].doc_status = 1
|
||||
self.workflow.save()
|
||||
todo = create_new_todo()
|
||||
self.assertEqual(todo.docstatus, 0)
|
||||
todo.submit()
|
||||
self.assertEqual(todo.docstatus, 1)
|
||||
self.assertEqual(todo.workflow_state, "Approved")
|
||||
|
||||
self.assertEqual(doc.docstatus, 0)
|
||||
doc.submit()
|
||||
self.assertEqual(doc.docstatus, 1)
|
||||
self.assertEqual(doc.workflow_state, "Approved")
|
||||
|
||||
self.workflow.states[1].doc_status = 0
|
||||
self.workflow.save()
|
||||
|
|
@ -350,6 +351,57 @@ def create_new_todo():
|
|||
return frappe.get_doc(doctype="ToDo", description="workflow " + random_string(10)).insert()
|
||||
|
||||
|
||||
def create_new_submittable_doctype_with_workflow():
|
||||
submittable_dt = frappe.get_doc(
|
||||
{
|
||||
"doctype": "DocType",
|
||||
"module": "Core",
|
||||
"name": "Test Submittable Doc",
|
||||
"custom": 1,
|
||||
"is_submittable": 1,
|
||||
"fields": [
|
||||
{"label": "Field", "fieldname": "test_field", "fieldtype": "Data"},
|
||||
{
|
||||
"label": "Workflow State",
|
||||
"fieldname": "workflow_state",
|
||||
"fieldtype": "Link",
|
||||
"options": "Workflow State",
|
||||
},
|
||||
],
|
||||
"permissions": [{"role": "System Manager", "read": 1, "write": 1, "submit": 1, "cancel": 1}],
|
||||
}
|
||||
).insert(ignore_if_duplicate=True)
|
||||
|
||||
workflow = None
|
||||
if not frappe.db.exists("Workflow", "Submittable Workflow"):
|
||||
workflow = frappe.new_doc("Workflow")
|
||||
workflow.workflow_name = "Submittable Workflow"
|
||||
workflow.document_type = submittable_dt.name
|
||||
workflow.workflow_state_field = "workflow_state"
|
||||
workflow.is_active = 1
|
||||
workflow.append("states", dict(state="Pending", allow_edit="All"))
|
||||
workflow.append(
|
||||
"states",
|
||||
dict(state="Approved", allow_edit="System Manager", doc_status=0),
|
||||
)
|
||||
workflow.append(
|
||||
"transitions",
|
||||
dict(
|
||||
state="Pending",
|
||||
action="Approve",
|
||||
next_state="Approved",
|
||||
allowed="System Manager",
|
||||
allow_self_approval=1,
|
||||
),
|
||||
)
|
||||
workflow.insert(ignore_permissions=True)
|
||||
else:
|
||||
workflow = frappe.get_doc("Workflow", "Submittable Workflow")
|
||||
|
||||
doc = frappe.get_doc({"doctype": submittable_dt.name, "test_field": "test"}).insert()
|
||||
return workflow, doc
|
||||
|
||||
|
||||
def create_new_note(doc):
|
||||
note = frappe.new_doc("Note")
|
||||
note.title = "workflow - " + doc.name
|
||||
|
|
|
|||
|
|
@ -109,9 +109,10 @@ frappe.ui.form.on("Workflow", {
|
|||
return;
|
||||
}
|
||||
frappe.model.with_doctype(doc.document_type, () => {
|
||||
const fieldnames = frappe
|
||||
.get_meta(doc.document_type)
|
||||
.fields.filter((field) => !frappe.model.no_value_type.includes(field.fieldtype))
|
||||
const meta = frappe.get_meta(doc.document_type);
|
||||
const is_submittable = meta.is_submittable;
|
||||
const fieldnames = meta.fields
|
||||
.filter((field) => !frappe.model.no_value_type.includes(field.fieldtype))
|
||||
.map((field) => field.fieldname);
|
||||
|
||||
frm.fields_dict.states.grid.update_docfield_property(
|
||||
|
|
@ -119,6 +120,23 @@ frappe.ui.form.on("Workflow", {
|
|||
"options",
|
||||
[""].concat(fieldnames)
|
||||
);
|
||||
|
||||
frm.fields_dict.states.grid.update_docfield_property(
|
||||
"doc_status",
|
||||
"read_only",
|
||||
!is_submittable
|
||||
);
|
||||
|
||||
if (!is_submittable) {
|
||||
let changed = false;
|
||||
frm.doc.states.forEach((row) => {
|
||||
if (parseInt(row.doc_status || 0) !== 0) {
|
||||
row.doc_status = "0";
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
if (changed) frm.refresh_field("states");
|
||||
}
|
||||
});
|
||||
},
|
||||
create_warning_dialog: function (frm) {
|
||||
|
|
|
|||
|
|
@ -90,23 +90,40 @@ class Workflow(Document):
|
|||
|
||||
frappe.throw(frappe._("{0} not a valid State").format(state))
|
||||
|
||||
# Check if doctype is submittable
|
||||
meta = frappe.get_meta(self.document_type)
|
||||
is_submittable = meta.is_submittable
|
||||
|
||||
# Validate that non-submittable doctypes only have doc_status 0
|
||||
if not is_submittable:
|
||||
for state in self.states:
|
||||
if cint(state.doc_status or 0) != 0:
|
||||
frappe.throw(
|
||||
frappe._(
|
||||
"Workflow State '{0}' has Document Status {1}, but DocType '{2}' is not submittable. "
|
||||
"Only Document Status 0 (Draft) is allowed for non-submittable DocTypes."
|
||||
).format(state.state, state.doc_status, self.document_type)
|
||||
)
|
||||
|
||||
for t in self.transitions:
|
||||
state = get_state(t.state)
|
||||
next_state = get_state(t.next_state)
|
||||
state_docstatus = cint(state.doc_status or 0)
|
||||
next_state_docstatus = cint(next_state.doc_status or 0)
|
||||
|
||||
if state.doc_status == "2":
|
||||
if state_docstatus == 2:
|
||||
frappe.throw(
|
||||
frappe._("Cannot change state of Cancelled Document. Transition row {0}").format(t.idx)
|
||||
)
|
||||
|
||||
if state.doc_status == "1" and next_state.doc_status == "0":
|
||||
if state_docstatus == 1 and next_state_docstatus == 0:
|
||||
frappe.throw(
|
||||
frappe._(
|
||||
"Submitted Document cannot be converted back to draft. Transition row {0}"
|
||||
).format(t.idx)
|
||||
)
|
||||
|
||||
if state.doc_status == "0" and next_state.doc_status == "2":
|
||||
if state_docstatus == 0 and next_state_docstatus == 2:
|
||||
frappe.throw(frappe._("Cannot cancel before submitting. See Transition {0}").format(t.idx))
|
||||
|
||||
def set_active(self):
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue