[IMP] project: implement new project task state - #107593
Conversation
cbf7388 to
5469fc4
Compare
32152ac to
9afd4f4
Compare
edddeaa to
943ef5d
Compare
| 'state', | ||
| 'state_approval_mode' |
There was a problem hiding this comment.
if those fields are writable in project sharing then they are also readable. 🙂
That is, no need to add those fields in the readable fields. 🙂
odoo/addons/project/models/project.py
Lines 1347 to 1349 in b9f0963
943ef5d to
37b6542
Compare
2455620 to
30c6f6e
Compare
|
Feel free to ping me when this PR is ready. |
30c6f6e to
5486664
Compare
d5acc1c to
b003140
Compare
MissingNoShiny
left a comment
There was a problem hiding this comment.
Hello, congratulations for your work! 🤩
I left some comments 🙂
There was a problem hiding this comment.
| task.is_closed = True if task.state in CLOSED_STATES else False | |
| task.is_closed = task.state in CLOSED_STATES |
😇
There was a problem hiding this comment.
If these are not used anymore, don't forget to remove them
There was a problem hiding this comment.
This is a bit too many if else, how about something like this?
# We build a dictionary that maps every state value to its key
state_dict = dict(map(reversed, self.env['project.task']._fields['state']._description_selection(request.env)))
search_domain.append([('state', 'ilike', state_dict.get(search, search))])There was a problem hiding this comment.
Don't forget to remove this
There was a problem hiding this comment.
If I understand correctly, a state can only be either blocking or closed? Could we not just use CLOSED_STATES then? That way, if we add a blocking state but forget to add it in BLOCKING_STATES, it won't be a problem.
To avoid repeating information, you could even make CLOSED_STATES a dictionary, and use its items in the state field, like this:
CLOSED_STATES = {
'done': 'Done',
'canceled': 'Canceled',
} state = fields.Selection([
('in_progress', 'In Progress'),
('changes_requested', 'Changes Requested'),
('approved', 'Approved'),
*CLOSED_STATES.items(),
('waiting_normal', 'Waiting'),
], string='Status', copy=False, default='in_progress', required=True, compute='_compute_state', readonly=False, store=True, recursive=True, task_dependency_tracking=True, tracking=True)The in operator on dictionaries checks the keys, so all your task.state in CLOSED_STATES would keep the same behavior
There was a problem hiding this comment.
Don't forget to remove this
There was a problem hiding this comment.
Don't forget to remove this
There was a problem hiding this comment.
Don't forget to remove this if it's not used anymore
There was a problem hiding this comment.
This means that the task will have the technical name of the state on hover (for example, "changes_requested"). I don't think that's what we want
4832124 to
2282668
Compare
|
Thanks a lot for the review :) very insightful
Here are some improvements that should be done in more long term
What would you say is the more important? And i have a small question to finish, haven't you seen any problem with the access rights/groups? I did not implement this feature with the access rights in mind at all (it does not seem that important for that small of a feature but still.. may be important) |
7001444 to
d494ac0
Compare
I would say tests/tours can wait a bit if necessary, eliminating visible problems is more important (and other bugs that might arise in testing).
I didn't notice anything problematic besides the portal rights. Hopefully if there are big issues, they will come up during testing 🙂 |
d6c8ae4 to
6f44962
Compare
xavierbol
left a comment
There was a problem hiding this comment.
Great job! Thanks for your work, I left some comments. 🙂
There was a problem hiding this comment.
| return | |
| break |
? 🤔
Otherwise you will exit the method and not the for loop
There was a problem hiding this comment.
If i break there i'll just get out of the last loop and end up in the if task.state == '04_waiting_normal' condition.
There was a problem hiding this comment.
I am wondering if we could not do something like this:
| for task in self: | |
| if task.allow_task_dependencies: | |
| for dependent_task in task.depend_on_ids: | |
| # if one of the blocking task is in a blocking state | |
| if dependent_task.state not in CLOSED_STATES: | |
| # here we check that the blocked task is not alread in a closed state (if the task is already done we don't put it in waiting state) | |
| if task.state not in CLOSED_STATES: | |
| task.state = '04_waiting_normal' | |
| return | |
| # if the task as no blocking dependencies and is in waiting_normal, the task goes back to in progress | |
| if task.state == '04_waiting_normal': | |
| task.state = '01_in_progress' | |
| tasks_with_dependencies_feature = self.filtered("allow_dependencies") | |
| dependent_tasks = self.env["project.task"].search([ | |
| ("dependent_ids", 'in', tasks_with_dependencies.ids), | |
| ("state", "not in", CLOSED_STATES) | |
| ]) | |
| for task in self: | |
| # if one of the blocking task is in a blocking state | |
| if dependent_task.state not in CLOSED_STATES: | |
| # here we check that the blocked task is not already | |
| # in a closed state (if the task is already done we | |
| # don't put it in waiting state) | |
| if task.state not in CLOSED_STATES and task in dependent_tasks: | |
| task.state = '04_waiting_normal' | |
| elif task.state == '04_waiting_normal': | |
| # if the task as no blocking dependencies and is in | |
| # waiting_normal, the task goes back to in progress | |
| task.state = '01_in_progress' |
I didn't test the code
There was a problem hiding this comment.
Should not it be in the _compute_state? Since the allow_task_dependencies is a setting in the project model
There was a problem hiding this comment.
Mmmh i don't think so,
Here we need to catch the transition of allow_task_dependencies from True to False
-> in this case every task that was in waiting_normal is just resets to in_progress, How can you make this distinction in the compute_state? isn't the function always going to reset the task to in_progress when the allow_task_dependencies is False?
There was a problem hiding this comment.
I don't think we have to know that we can just check if the allow_task_dependencies=False then we do the change accordingly, for instance, if I change a little bit your compute method:
@api.depends('depend_on_ids.state', 'allow_task_dependencies')
def _compute_state(self):
for task in self:
dependent_open_tasks = []
if task.allow_task_dependencies:
dependent_open_tasks = [dependent_task for dependent_task in task.depend_on_ids if dependent_task.state not in CLOSED_STATES]
# if one of the blocking task is in a blocking state
if dependent_open_tasks:
# here we check that the blocked task is not already in a closed state (if the task is already done we don't put it in waiting state)
if task.state not in CLOSED_STATES:
task.state = '04_waiting_normal'
# if the task as no blocking dependencies and is in waiting_normal, the task goes back to in progress
elif task.state == '04_waiting_normal':
task.state = '01_in_progress'when allow_task_dependencies is False we have to do the same things than when we don't have any dependencies.
(FYI, break stops the for loop, I think here we want to go to the next task in self and so you have to use continue instead)
There was a problem hiding this comment.
Ok does work indeed, i can remove the onchange allow_task_dependencies
There was a problem hiding this comment.
I am wondering if it should not be in the _compute_state too.
There was a problem hiding this comment.
Same thing as for allow task dependencies, here we are just trying to handle a change of stage,
how can you handle that in the _compute function?
There was a problem hiding this comment.
you can let this onchange, you are right. 🙂
There was a problem hiding this comment.
Same, it could be in the _compute_state, IMO
What do you think? 🤔
There was a problem hiding this comment.
Same thing as for allow_task_dependencies and onchange_stage_id :p
There was a problem hiding this comment.
you are right, however, I think the compute is triggered when the project changed because we have allow_task_dependencies (project_id.allow_task_dependencies) in the dependencies, so maybe this onchange is useless. 🤔
There was a problem hiding this comment.
the compute is triggered but it won't reset the state unless it was in waiting_normal, we want it to reset under every circumstance (except if it is still blocked by task and the project allow_task_dependencies)
There was a problem hiding this comment.
| 'state': '03_approved' | |
| 'state': '03_approved', |
😇
There was a problem hiding this comment.
| 'state': '1_done' | |
| 'state': '1_done', |
😇
There was a problem hiding this comment.
| 'state': '1_done' | |
| 'state': '1_done', |
😇
There was a problem hiding this comment.
I guess it is needed. 🤷🏼♂️
There was a problem hiding this comment.
Then we can remove the div? 🤔
There was a problem hiding this comment.
we are keeping the date_deadline finally (couldn't remove it because another view was referring to this div)
There was a problem hiding this comment.
Why not doing this:
| <xpath expr="//span[@t-esc='formattedValue']" position="replace"> | |
| <s t-if="['1_done', '1_canceled'].includes(this.props.record.data.state) and ['kanban', 'list'].includes(this.props.record.activeFields[this.props.name].viewType)" t-esc="formattedValue"/> | |
| <span t-else="" t-esc="formattedValue"/> | |
| </xpath> | |
| <xpath expr="//span[@t-esc='formattedValue']" position="before"> | |
| <s t-if="['1_done', '1_canceled'].includes(this.props.record.data.state) and ['kanban', 'list'].includes(this.props.record.activeFields[this.props.name].viewType)" t-esc="formattedValue"/> | |
| </xpath> | |
| <xpath expr="//span[@t-esc='formattedValue']" position="attributes"> | |
| <attribute name="t-else">""</attribute> | |
| </xpath> |
? 🤔
What is the s element? 🤔
You didn't want to put span instead?
There was a problem hiding this comment.
s is for striked value (when the task is done/canceled)
There was a problem hiding this comment.
Why not doing this:
? 🤔
What is the
selement? 🤔 You didn't want to putspaninstead?
Don't forget to check that when you have time, I think we can avoid doing a replace here. 🙂
Thanks in advance!
There was a problem hiding this comment.
Yes sorry :) it's done
There was a problem hiding this comment.
| } | |
| else { | |
| } else { |
xavierbol
left a comment
There was a problem hiding this comment.
Thanks again for your work, I left one comment and I will push a commit if you agree with the changes then you can squash it in yours. 🙂
There was a problem hiding this comment.
I guess it is needed. 🤷🏼♂️
xavierbol
left a comment
There was a problem hiding this comment.
LGTM, thanks again for your work. 🤩 🥳
May I ask you to edit the PR message to avoid having a big DEPRECATED? Thanks in advance! 🙂
robodoo r+
Before this PR the task state was fixed by the kanban_state field which was useful when you use the stage of the project as parts of a pipeline, but not relevant when users are using stages as bucket lists. (specific examples at the end of the specs) The goal of this PR is to provide users a way to mark their tasks as done with a simple button press, while keeping the option to label a task as Approved, Canceled or Requesting changes like in the old kanban_state field. The kanban_state of a task had no impact whatsoever on other tasks of the pipe, we would like to change that and make the task state have an influence on its dependent tasks. The state will also have influence over the 'recurrent' tasks (to be implemented in Task #3084945) If you want a better description of those changes with screenshot and colors check specs of: Task-3084930 PRs: See odoo/enterprise#35359 See odoo/upgrade#4367 ----------------------------------------- Interaction with blocking tasks: the closed values which mark the task as closed or finished: - Done - Canceled The Open values when the task isn't finished yet: - In progress - Changes Requested - Approved - Waiting (which is not selectable) Where to change the state of a task: - For kanban and form views: same place as kanban_state (bottom right of kanban card, top right of form view) - For list view: left of list (after task priority) more details about the state widget in state field widgets part Interaction with existing fields - is_closed: which was determined by the task.stage_id.fold, now a task is closed when in one of the following stages - Done - Canceled a closed task is considered as finished, the time of the closing will be stored in the date_last_stage_update field - is_blocked: a task is considered blocked if ANY of its blocking task is in one of the blocking states (more details about this in the following part Interaction with blocking tasks): - in Progress - Changes Requested - Approved - Waiting !! important !! is_closed and is_blocked are not mutually exclusive, you can have a task that blocked and is closed at the same time, the reason why will be explained late date_last_stage_update: this field is updated everytime the task goes into a closing state OR when the task changes stage. We need to check that the value is updated in each case (using the already available filter) Interaction with blocking tasks the state of a task can now be changed by its blocking tasks following the logic: if ANY of the blocking tasks is NOT closed (so its state is in one of the open values) the task is considered as blocked - if a task is blocked and NOT closed its state will switch to Waiting - the Waiting state will display an unclickable hourglass icon on the task kanban/list views, once in the waiting state you can't change the state of the taskfrom the kanban/list views - a blocked task state can be changed through the form view, so you can override the 'block' by choosing a closed state (only done or canceled) - once overriden, the task will change to the closed state the user wants, but the task is still blocked so in case where the user comes back to an open state, the task will automatically switch back to the waiting state (according to the state before the block) - if the blocking task switches to a non-blocking state, the task will not be considered as blocked anymore and its state will switch back to In Progress Default values the default value is always in progress Special cases when a task is moved from a stage to another one - if the state was in one of the open states (approved, changes requested, in progress ) the state goes back to In Progress - if not, the state stays the same when a task is moved from a project to another one - the state goes back to In Progress when a task is duplicated - if the state was in one of the open states (approved, changes requested, in progress ) the state goes back to In Progress - if not, the state stays the same
|
robodoo r+ |

Specs Mark as Done
Overview
Before this PR the task state was fixed by the kanban_state field which was useful when you use the stage of the project as parts of a pipeline,
but not relevant when users are using stages as bucket lists. (specific examples at the end of the specs)
The goal of this PR is to provide users a way to mark their tasks as done with a simple button press,
while keeping the option to label a task as Approved, Canceled or Requesting changes like in the old kanban_state field.
The kanban_state of a task had no impact whatsoever on other tasks of the pipe, we would like to change that and make the task state have an influence on its dependent tasks.
The state will also have influence over the 'recurrent' tasks (to be implemented in Task #3084945)
If you want a better description of those changes with screenshot and colors check specs of:
Task-3084930
PRs:
See https://github.com/odoo/enterprise/pull/35359
See https://github.com/odoo/upgrade/pull/4367
Interaction with blocking tasks:
the closed values which mark the task as closed or finished:
The Open values when the task isn't finished yet:
Where to change the state of a task:
more details about the state widget in state field widgets part
Interaction with existing fields
is_closed: which was determined by the task.stage_id.fold, now a task is closed when in one of the following stages
a closed task is considered as finished, the time of the closing will be stored in the date_last_stage_update field
is_blocked: a task is considered blocked if ANY of its blocking task is in one of the blocking states (more details about this in the following part Interaction with blocking tasks):
!! important !! is_closed and is_blocked are not mutually exclusive, you can have a task that blocked and is closed at the same time, the reason why will be explained late
date_last_stage_update: this field is updated everytime the task goes into a closing state OR when the task changes stage.
We need to check that the value is updated in each case (using the already available filter)
Interaction with blocking tasks
the state of a task can now be changed by its blocking tasks following the logic:
if ANY of the blocking tasks is NOT closed (so its state is in one of the open values) the task is considered as blocked
Default values
the default value is always in progress
Special cases
when a task is moved from a stage to another one
when a task is moved from a project to another one
when a task is duplicated