Permalink
Browse files

⚡️ status bar for kanban tasks, automatic sorting of subtasks

  • Loading branch information...
KolushovAlexandr committed Dec 5, 2018
1 parent 168f657 commit 296b346f28464efd14c7dc92443c9b2c214bc8ba
@@ -3,7 +3,7 @@
"summary": """Use checklist to be ensure that all your tasks are performed and to make easy control over them""",
"category": """Project Management""",
"images": ['images/checklist_main.png'],
"version": "11.0.1.0.3",
"version": "11.0.1.1.0",
"application": False,

"author": "IT-Projects LLC, Manaev Rafael",
@@ -18,6 +18,7 @@
"data": [
'security/ir.model.access.csv',
'views/project_task_subtask.xml',
'views/assets.xml',
'data/subscription_template.xml',
'security/project_security.xml'
],
@@ -1,3 +1,8 @@
`1.1.0`
-------

**Improvement:** Status bar on tasks for project.task kanban view
**Improvement:** Automatically sorting subtasks

`1.0.2`
-------
@@ -7,7 +7,7 @@

SUBTASK_STATES = {'done': 'Done',
'todo': 'TODO',
'waiting': 'Waiting',
'waiting': 'In Progress',
'cancelled': 'Cancelled'}


@@ -100,6 +100,8 @@ class Task(models.Model):
subtask_ids = fields.One2many('project.task.subtask', 'task_id', 'Subtask')
kanban_subtasks = fields.Text(compute='_compute_kanban_subtasks')
default_user = fields.Many2one('res.users', compute='_compute_default_user')
completion = fields.Integer('Completion', compute='_compute_completion')
completion_xml = fields.Text(compute='_compute_completion_xml')

@api.multi
def _compute_default_user(self):
@@ -117,28 +119,62 @@ def _compute_default_user(self):
@api.multi
def _compute_kanban_subtasks(self):
for record in self:
result_string1 = ''
result_string2 = ''
result_string3 = ''
for subtask in record.subtask_ids:
bounding_length = 25
tmp_list = (subtask.name).split()
for index in range(len(tmp_list)):
if len(tmp_list[index]) > bounding_length:
tmp_list[index] = tmp_list[index][:bounding_length] + '...'
tmp_subtask_name = " ".join(tmp_list)
if subtask.state == 'todo' and record.env.user == subtask.user_id and record.env.user == subtask.reviewer_id:
tmp_string3 = escape(': {0}'.format(tmp_subtask_name))
result_string3 += '<li><b>TODO</b>{}</li>'.format(tmp_string3)
elif subtask.state == 'todo' and record.env.user == subtask.user_id:
tmp_string1_1 = escape('{0}'.format(subtask.reviewer_id.name))
tmp_string1_2 = escape('{0}'.format(tmp_subtask_name))
result_string1 += '<li><b>TODO</b> from <em>{0}</em>: {1}</li>'.format(tmp_string1_1, tmp_string1_2)
elif subtask.state == 'todo' and record.env.user == subtask.reviewer_id:
tmp_string2_1 = escape('{0}'.format(subtask.user_id.name))
tmp_string2_2 = escape('{0}'.format(tmp_subtask_name))
result_string2 += '<li>TODO for <em>{0}</em>: {1}</li>'.format(tmp_string2_1, tmp_string2_2)
record.kanban_subtasks = '<ul>' + result_string1 + result_string3 + result_string2 + '</ul>'
result_string_td = ''
result_string_wt = ''
if record.subtask_ids:
task_todo_ids = record.subtask_ids.filtered(lambda x: x.state == 'todo' and (
x.user_id.id == record.env.user.id))
task_waiting_ids = record.subtask_ids.filtered(lambda x: x.state == 'waiting' and (
x.user_id.id == record.env.user.id))
if task_todo_ids:
tmp_string_td = escape(': {0}'.format(len(task_todo_ids)))
result_string_td += '<li><b>TODO</b>{}</li>'.format(tmp_string_td)
if task_waiting_ids:
tmp_string_wt = escape(': {0}'.format(len(task_waiting_ids)))
result_string_wt += '<li><b>WAITING</b>{}</li>'.format(tmp_string_wt)
record.kanban_subtasks = '<div class="kanban_subtasks"><ul>' + \
result_string_td + result_string_wt + '</ul></div>'

@api.multi
def _compute_completion(self):
for record in self:
record.completion = record.task_completion()

@api.multi
def _compute_completion_xml(self):
for record in self:
completion = record.task_completion()
color = 'bg-success-full'
if completion < 70:
color = 'bg-danger-full'
elif completion < 100:
color = 'bg-warning-full'
record.completion_xml = """
<div class="task_progress">
<div class="progress_info">
Checklist status:
</div>
<div class ="o_kanban_counter_progress progress task_progress_bar">
<div data-filter="done"
class ="progress-bar {1} o_bar_has_records task_progress_bar_done"
data-original-title="1 done"
style="width: {0}%;">
</div>
<div data-filter="blocked"
class ="progress-bar bg-danger-full"
data-original-title="0 blocked">
</div>
</div>
<div class="task_completion"> {0}% </div>
</div>
""".format(int(completion), color)

def task_completion(self):
user_task_ids = self.subtask_ids.filtered(lambda x: x.user_id.id == self.env.user.id and x.state != 'cancelled')
if not user_task_ids:
return 100
user_done_task_ids = user_task_ids.filtered(lambda x: x.state == 'done')
return (len(user_done_task_ids) / len(user_task_ids)) * 100

@api.multi
def send_subtask_email(self, subtask_name, subtask_state, subtask_reviewer_id, subtask_user_id, old_name=None):
@@ -0,0 +1,32 @@
.task_progress {
width:40%;
display:inline-block;
font-size: 10px;
}

.task_progress {
width:40%;
display:inline-block;
font-size: 10px;
}
.task_progress .task_progress_bar{
width:85%;
display:inline-block;
margin-bottom:0px;
height: 10px;
}
.task_progress .task_progress_bar_done{
max-width: 100%;
min-width: 3%;
}
.task_progress .task_completion{
width:10%;
display:inline-block;
height: 13px;
}

.kanban_subtasks {
width: 50%;
display:inline-block;
font-size: 10px;
}
@@ -0,0 +1,162 @@
odoo.define('project_task_subtask.one2many_renderer', function(require){

var FieldOne2Many = require('web.relational_fields').FieldOne2Many;
var BasicModel = require('web.BasicModel');

FieldOne2Many.include({

check_task_tree_mode: function(){
if (this.view &&
this.view.arch.tag === 'tree' &&
this.record && this.record.model === "project.task"
) {
return true;
}
return false;
},

sort_data: function() {
var user_id = this.record.context.uid;

var new_rows = _.filter(this.value.data, function(d){
return !d.res_id;
});
var data = _.difference(this.value.data, new_rows);

_.each(data, function(d){
d.u_name = d.data.user_id.data.display_name;
});

var name_index = _.sortBy(_.uniq(_.map(data, function(d){
return d.data.user_id.data.display_name;
})));


data = _.sortBy(data, 'u_name');
_.each(data, function(d){
d.deadline = d.data.deadline;
if (d.data.user_id.data.id === user_id) {
d.index = 0;
} else {
d.index = (_.indexOf(name_index, d.data.user_id.data.display_name) + 1) * 1000000;
}
});

data = _.sortBy(data, 'deadline');
_.each(data, function(d){
d.index += _.indexOf(data, d);
if (!d.deadline) {
d.index += 90000;
}
if (d.data.state === 'todo') {
// continue
} else if (d.data.state === 'waiting') {
d.index += 100000;
} else if (d.data.state === 'done') {
d.index += 400000;
} else {
d.index += 700000;
}
});
data = _.sortBy(data, 'index');
_.each(new_rows, function(r){
data.push(r);
});
this.value.data = data;
},

_render: function () {
if (this.check_task_tree_mode()) {
this.sort_data();
}
return this._super(arguments);
},

reset: function (record, ev, fieldChanged) {
var self = this;
return this._super.apply(this, arguments).then(function(res){
if (self.check_task_tree_mode()) {
self._render();
}
});
},

});

BasicModel.include({

_sortList: function(list){
// taken from odoo
if (!list.static) {
// only sort x2many lists
return;
}
var self = this;
// -----

if (list.model === "project.task.subtask" && list.orderedResIDs) {
var rows = [];
var new_rows = [];
_.each(list.data, function(d){
var r = self.localData[d];
if (Number(r.res_id) === r.res_id) {
rows.push(r);
} else {
new_rows.push(r);
}
});
rows = this.sort_data(rows, list.context.uid, this);
_.each(new_rows, function(r){
rows.push(r);
});
list.orderedResIDs = _.pluck(rows, 'res_id');
return this._setDataInRange(list);
}

return this._super(list);

},

sort_data: function(data, user_id, parent) {
user_id = user_id || 1;

_.each(data, function(d){
d.u_name = parent.localData[d.data.user_id].data.display_name;
});

var name_index = _.sortBy(_.uniq(_.map(data, function(d){
return parent.localData[d.data.user_id].data.display_name;
})));


data = _.sortBy(data, 'u_name');
_.each(data, function(d){
d.deadline = d.data.deadline;
if (parent.localData[d.data.user_id].data.id === user_id) {
d.index = 0;
} else {
d.index = (_.indexOf(name_index, parent.localData[d.data.user_id].data.display_name) + 1) * 1000000;
}
});

data = _.sortBy(data, 'deadline');
_.each(data, function(d){
d.index += _.indexOf(data, d);
if (!d.deadline) {
d.index += 90000;
}
if (d.data.state === 'todo') {
// continue
} else if (d.data.state === 'waiting') {
d.index += 100000;
} else if (d.data.state === 'done') {
d.index += 400000;
} else {
d.index += 700000;
}
});
return _.sortBy(data, 'index');
},

});
});
@@ -0,0 +1,8 @@
<odoo>
<template id="assets_backend" name="Project timelog assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link rel="stylesheet" type="text/less" href="/project_task_subtask/static/src/css/kanban_styles.css"/>
<script type="text/javascript" src="/project_task_subtask/static/src/js/one2many_renderer.js"></script>
</xpath>
</template>
</odoo>
@@ -34,11 +34,14 @@
<field name="inherit_id" ref="project.view_task_kanban"/>
<field name="arch" type="xml">
<xpath expr="//field[@name ='tag_ids']" position="after">
<field name="subtask_ids"/>
<field name="kanban_subtasks" invisible="1"/>
<field name="completion" invisible="1"/>
<field name="subtask_ids" invisible="1"/>
<field name="kanban_subtasks" invisible="1"/>
<field name="completion_xml" invisible="1"/>
</xpath>
<xpath expr="//div[hasclass('o_kanban_record_bottom')]" position="before">
<div>
<t t-raw="record.completion_xml.raw_value"/>
<t t-raw="record.kanban_subtasks.raw_value"/>
</div>
</xpath>

0 comments on commit 296b346

Please sign in to comment.