I am really trying to understand how Assignment Rules are supposed to work, but they don’t make any sense to me. I’ve gone over the manual several times, but all it does is walk you through the fields - it doesn’t explain how any of it is designed to work.
I have a super basic scenario where I want a) automatic assignment of new documents, where b) they can mark their task complete and move on. Note that I want to allow the user to decide when they have completed their task(s).
The assignment flow looks like this:
1. New -> assign document
2. Assignee unassigns the document
3. Profit
The first issue is that I don’t know how to program an assign condition for new documents. I attempted to figure that out through experimentation (e.g. is_new() == True), and quickly ran into confusion.
First I set up the assign condition True, so it would always assign. Testing this seemed to work as expected: when any change happened to a document, it was assigned.
Next, I set the assign condition to False, thinking it wouldn’t run. I immediately ran into confusion:
Saving the document I had just unassigned resulted in erasing my efforts to unassign the document.
Thinking there must be something wrong with the system, I tried creating a new document, but that remained unassigned.
I realize there is a field called unassign condition, but I have even less of an idea how to code “use unassigned themselves” into that field that detecting a new document in the assign condition field.
Just what is going on? Why is the assign condition condition ignored once it has been assigned, resulting in perpetual assignment? Is there any way to meet my assignment requirements?
I don’t use Assignment Rules much these days, but I used to use them a bit more. Everything I’m saying here is from memory, but I can check next time I’m at my desk.
If I remember correctly, the rule is processed on document save. If the rule in Assign Condition is True, the assignment is added; if it is False, nothing happens. In contrast, it sounds like you might be expecting the assignment to be removed if the Assign Condition evaluates to False?
The punchline is that people can’t just remove their own assignment. If you want the assignment to be removed, you’d have to tie it into a form field or a workflow status of some sort.
That doesn’t seem like what is happening though. As you can see in steps 2&3 of my tests, when I set the condition to simply False and unassigned the document it still re-assigned it, but when I created a new document it remained unassigned. The whole thing doesn’t make sense to me.
Ah, I understand what you’re saying now. I’m not able to reproduce it, though. On my installation, if the condition is set to False, I can unassign the document and it is not re-assigned. Are you seeing this behavior consistently?
I’m not sure what was going on, but I can’t repeat my initial observation. The system has been updated since then, so maybe there was a bug that was fixed, or maybe I was hallucinating.
I am looking for a way to assign NEW (not existing) documents on save though. I haven’t figured that out yet.
I don’t know your specific use case, but I suspect that workflows might be a more idiomatic way of doing what you’re trying to do. I don’t think is_new() will work because the assignment rule triggers after the save event is finished. Assignment rules won’t ever see a “new” document.
If you need to distinguish which documents have just been created, you could create/alter a field value with before_insert or after_insert hooks.
I actually came up with a solution that looks like it will work for my use case: compare creation == modified. In doctypes without a lot of weird things going on in the background I expect it to work just fine.
It should be possible to test __islocal, because that would be more reliable and intuitive, but that variable isn’t allowed because it starts with an underscore.
I don’t know much about `__islocal, but I always assumed it was primarily a client-side variable. Again, though, I think you wouldn’t be able to get that to work; as the Assignment Rule triggers happen after the doc has been persisted in the database.
Comparing created and modified is smart, but I suspect it will break in cases where the document has an after save hook that manipulates data. If I were in your shoes, a more semantic approach would definitely feel like less of a hack, but glad it’s working for your needs.
Ah, good point about __islocal. I was thinking about notifications. They triggers while __islocal is still available, and I guess I assumed (wrongly) that assignment happened the same way.
I agree 100% that it is a hack, although I think it is a bit more resilient than you suggest. After save hooks don’t bother me, since modified should already be updated. The only problem case I can think of is if assignment hooks are run without changing modified, but I think would happen pretty rarely, and probably? not at all in vanilla ERPNext.
For me, the reason it feels hacky is less about risks of breaking and more about working against the design. The Assignment Rule doctype is declarative, but it seems like you’re trying to bend it to be imperative/transactional. Nothing wrong with how you want to use it, but it’s a square-peg-round-hole kind of thing.
The risk would be that minor changes to how Assignment Rules get triggered could start giving you unexpected results. You’re running into this a bit already with is_new and __islocal. More broadly, Assignment Rules get applied on_update, and I would expect that there’s a real chance of race conditions with other data events riding on the same hook.
Ultimately, if your use case is about lifecycle events, why not use the hooks Frappe provides for that purpose? You might have a specific reason for wanting to use Assignment Rules, but it definitely feels to me like the wrong tool for the job.
Of course, all that said, if you’re happy with the results you’re getting, don’t let me tell you to do it a different way!
I understand what you are saying. I would counter-argue that auto-assignment functionality should not leave common cases that require programming regardless of the ideology. A “new” document should not require a hack or coding to assign, simply because of how useful and common that particular condition is. Besides, it isn’t clear to me that the state of “newness” shouldn’t be considered declarative.
I don’t see how this is a square-peg-round-hole situation. I am trying to assign new documents, and you’re telling me the assignment system isn’t designed to do that. You have to admit that sounds like a gaping hole in functionality.
In our world whether a document is new is the first condition for assigning a document. Does that mean in our world we are supposed to have a team of programmers handle assignment? That just doesn’t make any sense. Every ticketing (something ERPNext is built for via Issues) and group email system I have worked with have that capability built-in.
Sorry, I guess I’m frustrated because I see your point, and it blows my mind to see this use-case ignored. This isn’t an edge or corner case as I see it, and a practical system should be capable of addressing common use-cases regardless of the ideology that was chosen while designing it.
I don’t like the idea of “newness” being a part of the base document model, simply because it feels arbitrary. Document creation is an event. After that event, how long does the document remain “new”? Until it is saved again?
That’s what a creation == modified test would be looking at. But, what’s so special about a document that has been saved once vs a document that has been saved twice? It will probably work as an approximation for your use case at least most of the time, but I definitely wouldn’t be surprised if you run into unexpected behaviors.
In other words, I do think the event/state distinction is valid and important here. Right now, Assignment Rules monitor state. I’m not saying that’s how it should be; I’m just saying that’s what it does. It could certainly be extended to monitor insert events, too. At quick glance, I think it would need just a few minor modifications to work. But, so far, it looks like nobody has needed it enough to make it happen.
Yeah, sorry - one can’t always avoid voicing frustration
I’d just like to point out a couple observations of my own
Document creation is an event. After that event, how long does the document remain “new”? Until it is saved again?
It remains new just until the initial save operation is complete.
Only [save] events lead to assignment, regardless of how a document is assigned; state change doesn’t triggers assignment. During the save event the document has a state that is evaluated.
This is an important observation, because I consider the newness of a document to be part of its state. In fact ERPNext seems to agree with me: __islocal is added during the insert process before any action is taken (it also exists in the browser), and it is removed immediately before adding document followers.
But, so far, it looks like nobody has needed it enough to make it happen.
This actually has me curious; I wonder how much assignment is even used. The assignment system seems to have issues. Aside from being unable to handle this common use case, in the few days after implementing my rule random documents are ending assigned that don’t match the rule. One such document ended up assigned to the same person three times. I don’t even know how that can happen looking at the code, since it removes all assignments before adding new ones.
I think this is worth filing a bug report over to draw attention to it. I’ll mention this thread in the report. In any event, thanks for engaging with me @peterg, this has been an interesting conversation, well appreciated!
Right, exactly. That’s the problem. You can’t assign a document that hasn’t been saved. By the time you’re ready to make assignments, the document isn’t new any more. This is why you need event hooks and not document state.
That isn’t a problem though, because saving the document to the database is only part of the insert process. During virtually the entire insert process, the state of __islocal is set to True. An entire run_post_save_methods method runs after adding the file to the database and before __islocal is deleted. There is plenty of time to run new document operations during that time, and assignment is probably already one of them.
My very rough translation of the insert method looks like this:
def insert():
set self.flags from args
__islocal = True
housekeeping
document hooks
set name
more document hooks
validate
self.flags.in_insert = True
**insert into the database**
**insert children into the database**
more document hooks
self.flags.in_insert = False
delete __islocal
follow_document
I can’t verify it, but I suspect that __islocal may actually be available while assigning takes place anyway. I just can’t access it from the Assignment Rule.
The only real issue I can see is if the assignment took place on a different thread, in which case it would have to read the document from the database. In that case __islocal would not be available.
If it’s your preference to use an undocumented and known-buggy private variable over the documented lifecycle hooks designed for this purpose, I certainly won’t try to talk you out of it. Good luck!