After workflow action

Is this the correct use of frm.selected_workflow_action? It is not working for me, it is not throwing the error. Here is my code.

frappe.ui.form.on('Expense Claim', {
before_workflow_action: (frm) => {
if(frm.doc.workflow_state === 'Advance Validated By Finance'){
 if (frm.doc.checkbox_m == 1){
    if(frm.selected_workflow_action == "Approve Advance Request"){
        frappe.throw("Please check the 'approval' checkbox before approving");
        frappe.validated = false;    
        }
}}}});

(Friendly request: your code will be a lot easier to read if you use code fences (```) in the line above and the line below your code to preserve formatting)

Can you this with console.log(frm.selected_workflow_action)? What value do you see in output?

Is this what you mean?

Sort of. Are you unfamiliar with using console.log to debug? You need to put this into your actual before_workflow_action event trigger, where the variable frm is defined. That will allow you to see what the value of selected_workflow_action is when it runs and, consequently, why your error message isn’t triggering.

Unfortunately, I am not. If you could show me a resource to understand this I would be happy to execute it.

Also, my apologies, I am very new to programming (erpnext being my first experience). I am still learning the basics. Is this what you mean?

frappe.ui.form.on('Expense Claim', {
before_workflow_action: (frm) => {
if(frm.doc.workflow_state === 'Advance Validated By Finance'){
    if(frm.selected_workflow_action == "Approve Advance Request"){
        if (frm.doc.checkbox_m == 1)
        {
        frappe.throw("Please check the 'approval' checkbox before approving");
        frappe.validated = false;    
        }
}}}});
1 Like

The console.log function writes whatever argument you send it to the dev console.

So, try something like this:

frappe.ui.form.on("Expense Claim", {
    before_workflow_action: (frm) => {
        console.log(frm.selected_workflow_action);
        if (
            frm.doc.workflow_state === "Advance Validated By Finance" &&
            frm.selected_workflow_action === "Approve Advance Request" &&
            frm.doc.checkbox_m == 1
        ) {
            frappe.throw("Please check the 'approval' checkbox before approving");
            frappe.validated = false;
        }
    },
});

You should see the value of frm.selected_workflow_action output to the console before the workflow action triggers. (I’ve also tidied your code up a bit, removing the nested if statements and the unindended bracket closures.)

5 Likes

It WORKED!!!
Thank You!!!

1 Like

One last thing,
I would like to write a server-side script to automatically delete all the forms of a custom doctype every 1 hour.

How do I go about that? How do I execute the cron job?

If you have your own app, you can define scheduler jobs in your hooks.py definition.

https://frappeframework.com/docs/user/en/python-api/hooks#scheduler-events

Otherwise, if your site is configured for server scripts, you can just do it there.

https://frappeframework.com/docs/user/en/desk/scripting/server-script

Thank you so much!!

Hey @Syd and @peterg , Did this issue “The form changes to “Not Saved” so that once the I refresh, I lose the contents of the fields.” get solved.
Cause I am facing the same situation. The fields get populated with username and time but the form changes to ‘Not Saved’ and hence the data is not saved in the database.

Can you please help we with this?

I tried the following variations:

frappe.ui.form.on('Purchase Order', {
	after_workflow_action: (frm) => {
    if(frm.doc.workflow_state === 'Approval Pending by Manager'){
      frm.doc.custom_created_by = frappe.session.user,
      frm.doc.custom_created_time = frappe.datetime.now_datetime()
    } 
      else if(frm.doc.workflow_state === 'Submitted'){
      frm.doc.custom_approved_by = frappe.session.user,
      frm.doc.custom_approved_time = frappe.datetime.now_datetime()
    }
    frm.save();
	}
});

Comment: User and time being fetched perfectly. BUT, as workflow is executed, the normal user doesn’t have permission to edit document anymore, hence the values are not getting saved. Also not getting saved by the Manager in the final submit state.

Trial 2:

frappe.ui.form.on('Purchase Order', {
	before_workflow_action: (frm) => {
    if(frm.selected_workflow_action === 'Submit'){
        frm.set_value({
                custom_created_by: frappe.session.user,
                custom_created_time: frappe.datetime.now_datetime()
            });
    }
    else if(frm.selected_workflow_action === 'Approve'){
        frm.set_value({
                custom_approved_by: frappe.session.user,
                custom_approved_time: frappe.datetime.now_datetime()
            });
    }
    frm.save();
	}
});

Comment: Same error as above

Trial 3:

frappe.ui.form.on('Purchase Order', {
	after_workflow_action: (frm) => {
    if(frm.doc.workflow_state === 'Approval Pending by Manager'){
        frm.set_value({
                custom_created_by: frappe.session.user,
                custom_created_time: frappe.datetime.now_datetime()
            });
    }
    else if(frm.doc.workflow_state === 'Submitted'){
    frm.set_value({
                custom_approved_by: frappe.session.user,
                custom_approved_time: frappe.datetime.now_datetime()
            });
    }
    frm.save();
	}
});

Comment: It just shows values on the screen, but they are not saved in database. The document is showing as ‘Not Saved’

Please help solve this…

Hi there,

I’m not fully understanding the differences between these three bits of code, but if the user doesn’t have permission to edit the document after the workflow change, you’re not going to be able to set those values after the workflow action has been completed. You might try using the before_workflow_action hook, or you might have to use a general save hook handler of some sort.

Hi @peterg,

I tried this code with the before_workflow_action hook, BUT the document status is showing as ‘Not Saved’ after execution of the workflow. The fields are populated, but as the document is not saved, when I refresh the page, the values go away.

Here even for a system user (with full access), the document is not saved after workflow action and values go away as soon as page is reloaded.

frappe.ui.form.on('Purchase Order', {
	before_workflow_action: (frm) => {
    if(frm.selected_workflow_action === 'Submit'){
        frm.set_value({
                custom_created_by: frappe.session.user,
                custom_created_time: frappe.datetime.now_datetime()
            });
    }
    else if(frm.selected_workflow_action === 'Approve'){
        frm.set_value({
                custom_approved_by: frappe.session.user,
                custom_approved_time: frappe.datetime.now_datetime()
            });
    }
    frm.save();
	}
});

Can you point me towards the general save hook handler that you have suggested to me?

Thanks

I don’t have access to a frappe instance at the moment to test, but it sounds like you’re just running into a race condition. I’m pretty sure that the set_value function is asynchronous, but in your code it doesn’t look like you’re waiting for it to return.

What about something like this?

frappe.ui.form.on('Purchase Order', {
  before_workflow_action: (frm) => {
    const my_actions = ['Submit', 'Approve']
    if (my_actions.includes(frm.selected_workflow_action)) {
      frm.set_value({
        custom_created_by: frappe.session.user,
        custom_created_time: frappe.datetime.now_datetime()
      }).then(r => frm.save())
    }
  }
});
1 Like

I tried this code; this is also not saving any values…

frappe.ui.form.on('Purchase Order', {
  before_workflow_action: (frm) => {
    if (frm.selected_workflow_action === 'Submit for Approval') {
      frm.set_value({
        custom_created_by: frappe.session.user,
        custom_created_time: frappe.datetime.now_datetime()
      }).then(r => frm.save())
    }
    else if (frm.selected_workflow_action === 'Approve') {
      frm.set_value({
        custom_approved_by: frappe.session.user,
        custom_approved_time: frappe.datetime.now_datetime()
      }).then(r => frm.save())
    } 
  }
});


Workflow Transition:

You’ll have to do some debugging with console.log statements. I still think it’s a race condition. It may not be possible to edit the document on the client side while it’s already in a save cycle. That makes sense, actually. The server is the source of truth, and client interaction is all via remote procedure calls. It would be challenging to allow client-side changes to data while a workflow transition is in process.

FWIW, this kind of logic probably should be executed on the server side, anyway. As you’re trying to do it, it would be pretty simple for any authorized user to set these values arbitrarily.

Thank you for your explanation.
I’ll try it server side or in a different way.

If I get a concrete solution, I’ll share it here :+1:

Hi @peterg,

I have found a solution to my problem which I have posted here:

I want to throw an error if the checkbox is not ticked, and the verifier tries to move to the next stage.

frappe.ui.form.on('Purchase Order', {
    validate: (frm) => {
        // Doc Creation:
        if (!frm.doc.custom_created_by && frm.doc.workflow_state === 'Draft') {
            frm.doc.custom_created_by = frappe.session.user,
            frm.doc.custom_created_time = frappe.datetime.now_datetime();
        }
        // Doc Verification:
        else if ((frm.doc.custom_read_and_verified == 1) && (frm.doc.workflow_state === 'Verification Pending')){
            frm.doc.custom_verified_by = frappe.session.user,
            frm.doc.custom_verified_time = frappe.datetime.now_datetime();
        }
        else if ((frm.doc.custom_read_and_verified != 1) && (frm.doc.workflow_state === 'Verification Pending')){
            frm.doc.custom_verified_by = null,
            frm.doc.custom_verified_time = null;
        }
        // Doc Approval:
        else if ((frm.doc.custom_read_and_approved == 1) && (frm.doc.workflow_state === 'Approval Pending by Manager')){
            frm.doc.custom_approved_by = frappe.session.user,
            frm.doc.custom_approved_time = frappe.datetime.now_datetime();
        }
        else if ((frm.doc.custom_read_and_approved != 1) && (frm.doc.workflow_state === 'Approval Pending by Manager')){
            frm.doc.custom_approved_by = null,
            frm.doc.custom_approved_time = null;
        }
    },

    before_workflow_action: (frm) => {
            //console.log(frm.selected_workflow_action);
            if ((frm.doc.workflow_state === 'Verification Pending') && (frm.selected_workflow_action === 'Verify') && (frm.custom_read_and_verified != 1))
            {
                frappe.throw("Please tick the Read by Verifier checkbox");
            }
    }
});

It is working, thanks.

Great, glad it’s working. If you want to make it a separate step, that’s fine. Because this is a client-side operation, it will be possible for users with a small amount of javascript knowledge to falsify the user id, but that may not be an issue at your organization.

1 Like

Hi @rps49 ,
Facing same problem
before_workflow_action(frm){
console.log(“Action”,frm.selected_workflow_action);
action = frm.selected_workflow_action
if(action===“Submit” && frm.doc.workflow_state==“Draft”){
let kra_kpi = frm.doc.kra_kpi_mapping
kra_kpi.forEach((ele)=>{
if(ele.self_ratings===0){
frappe.throw(“Please enter Self Ratings for KRA KPI”)
}
})

    }
    else if(action=="Approve" && frm.doc.workflow_state=="Pending with RO for Ratings"){
        let kra_kpi = frm.doc.kra_kpi_mapping
                kra_kpi.forEach((ele)=>{
                        if(ele.self_ratings===0){
                            frappe.throw("Please enter Self Ratings for KRA KPI")
                        }
                    })
        let goal_kra = frm.doc.goal_and_kra
        goal_kra.forEach((ele)=>{
            if(ele.ro_ratings===0){
                frappe.throw("Please enter RO Ratings for Goal and KRA")
            }
        })
    }
    else if(action=="Approve" && self.dro_required && frm.doc.workflow_state=="Pending with DRO for Ratings"){
        let kra_kpi = frm.doc.kra_kpi_mapping
                kra_kpi.forEach((ele)=>{
                        if(ele.dro_ratings===0){
                            frappe.throw("Please enter RO Ratings for KRA KPI")
                        }
                    })
        let goal_kra = frm.doc.goal_and_kra
        goal_kra.forEach((ele)=>{
            if(ele.dro_ratings===0){
                frappe.throw("Please enter DLO Ratings for Goal And KRA")
            }
        })
    }
},

Working code but document gets freezed and need to refresh it.