diff options
Diffstat (limited to 'addons/project')
| -rw-r--r-- | addons/project/__openerp__.py | 4 | ||||
| -rw-r--r-- | addons/project/project.py | 18 | ||||
| -rw-r--r-- | addons/project/project_view.xml | 115 | ||||
| -rw-r--r-- | addons/project/static/src/css/project.css | 220 | ||||
| -rw-r--r-- | addons/project/static/src/img/tasks_icon.png | bin | 0 -> 2545 bytes | |||
| -rw-r--r-- | addons/project/static/src/js/dropdown.js | 92 | ||||
| -rw-r--r-- | addons/project/static/src/js/project.js | 48 |
7 files changed, 479 insertions, 18 deletions
diff --git a/addons/project/__openerp__.py b/addons/project/__openerp__.py index 35b920828b2..68c85ffb836 100644 --- a/addons/project/__openerp__.py +++ b/addons/project/__openerp__.py @@ -29,7 +29,7 @@ "sequence": 8, 'complexity': "easy", "images": ["images/gantt.png", "images/project_dashboard.jpeg","images/project_task_tree.jpeg","images/project_task.jpeg","images/project.jpeg","images/task_analysis.jpeg"], - "depends": ["base_setup", "product", "analytic", "board", "mail", "resource"], + "depends": ["base_setup", "product", "analytic", "board", "mail", "resource","web_kanban"], "description": """ Project management module tracks multi-level projects, tasks, work done on tasks, eso. ====================================================================================== @@ -70,6 +70,8 @@ Dashboard for project members that includes: 'installable': True, 'auto_install': False, 'application': True, + 'css': ['static/src/css/project.css'], + 'js': ['static/src/js/dropdown.js','static/src/js/project.js'], 'certificate': '0075116868317', } # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/project/project.py b/addons/project/project.py index 0335b7cb540..a25e55d12e5 100644 --- a/addons/project/project.py +++ b/addons/project/project.py @@ -155,6 +155,13 @@ class project(osv.osv): raise osv.except_osv(_('Operation Not Permitted !'), _('You cannot delete a project containing tasks. I suggest you to desactivate it.')) return super(project, self).unlink(cr, uid, ids, *args, **kwargs) + def _task_count(self, cr, uid, ids, field_name, arg, context=None): + res = dict.fromkeys(ids, 0) + task_ids = self.pool.get('project.task').search(cr, uid, [('project_id', 'in', ids)]) + for task in self.pool.get('project.task').browse(cr, uid, task_ids, context): + res[task.project_id.id] += 1 + return res + _columns = { 'complete_name': fields.function(_complete_name, string="Project Name", type='char', size=250), 'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the project without removing it."), @@ -191,7 +198,15 @@ class project(osv.osv): 'warn_header': fields.text('Mail Header', help="Header added at the beginning of the email for the warning message sent to the customer when a task is closed.", states={'close':[('readonly',True)], 'cancelled':[('readonly',True)]}), 'warn_footer': fields.text('Mail Footer', help="Footer added at the beginning of the email for the warning message sent to the customer when a task is closed.", states={'close':[('readonly',True)], 'cancelled':[('readonly',True)]}), 'type_ids': fields.many2many('project.task.type', 'project_task_type_rel', 'project_id', 'type_id', 'Tasks Stages', states={'close':[('readonly',True)], 'cancelled':[('readonly',True)]}), + 'use_tasks': fields.boolean('Use Tasks', help="Check this field if this project is aimed at managing tasks"), + 'task_count': fields.function(_task_count, type='integer', string="Open Tasks"), + 'color': fields.integer('Color Index'), + 'company_uom_id': fields.related('company_id', 'project_time_mode_id', type='many2one', relation='product.uom'), } + + def dummy(self, cr, uid, ids, context): + return True + def _get_type_common(self, cr, uid, context): ids = self.pool.get('project.task.type').search(cr, uid, [('project_default','=',1)], context=context) return ids @@ -201,7 +216,8 @@ class project(osv.osv): 'active': True, 'priority': 1, 'sequence': 10, - 'type_ids': _get_type_common + 'type_ids': _get_type_common, + 'use_tasks': True, } # TODO: Why not using a SQL contraints ? diff --git a/addons/project/project_view.xml b/addons/project/project_view.xml index 956b159a0bb..19a1d0d4454 100644 --- a/addons/project/project_view.xml +++ b/addons/project/project_view.xml @@ -43,12 +43,17 @@ </group> <notebook colspan="4"> <page string="Administration"> + <group col="2" colspan="2" name="kanban"> + <separator colspan="2" string="Project Management"/> + <field name="use_tasks"/> + </group> <group col="2" colspan="2"> - <separator colspan="2" string="Performance"/> + <separator colspan="4" string="Performance"/> <field name="planned_hours" widget="float_time"/> <field name="effective_hours" widget="float_time" /> <field name="resource_calendar_id"/> </group> + <newline/> <group col="2" colspan="2" name="misc"> <separator colspan="4" string="Miscelleanous"/> <field name="company_id" select="1" groups="base.group_multi_company" widget="selection" required="1"/> @@ -155,6 +160,94 @@ </tree> </field> </record> + + <act_window + context="{'search_default_project_id': [active_id], 'default_project_id': active_id}" + id="act_project_project_2_project_task_all" + name="Tasks" + res_model="project.task" + src_model="project.project" + view_mode="kanban,tree,form,calendar,graph" + view_type="form"/> + + + <record model="ir.ui.view" id="view_project_kanban"> + <field name="name">project.project.kanban</field> + <field name="model">project.project</field> + <field name="type">kanban</field> + <field name="arch" type="xml"> + <kanban> + <field name="effective_hours"/> + <field name="planned_hours"/> + <field name="name"/> + <field name="members"/> + <field name="use_tasks"/> + <field name="user_id"/> + <field name="date"/> + <field name="color"/> + <field name="task_count"/> + <templates> + <t t-name="kanban-box"> + <div class="project_vignettes"> + <li t-attf-class="#{kanban_color(record.color.raw_value)} oe_project_kanban_vignette" id="oe_project_kanban_vignette"> + <a href="#" class="oe_project_kanban_action dropdown-toggle"><span class="oe_i">B</span></a> + <ul class="dropdown-menu"> + <li ><a type="edit" >Edit...</a></li> + <li ><a type="delete">Delete</a></li> + <li> + <ul class="color-chooser"> + <li><a t-att-id="record.id.value" class="bgcolor"><span class="oe_kanban_color_0 square"></span></a></li> + <li><a t-att-id="record.id.value" class="bgcolor"><span class="oe_kanban_color_1 square"></span></a></li> + <li><a t-att-id="record.id.value" class="bgcolor"><span class="oe_kanban_color_2 square"></span></a></li> + <li><a t-att-id="record.id.value" class="bgcolor"><span class="oe_kanban_color_3 square"></span></a></li> + <li><a t-att-id="record.id.value" class="bgcolor"><span class="oe_kanban_color_4 square"></span></a></li> + <li><a t-att-id="record.id.value" class="bgcolor"><span class="oe_kanban_color_5 square"></span></a></li> + </ul> + </li> + </ul> + <h4><t t-esc="record.name.value.substr(0,33)"/><t t-if="record.name.value.length > 33">...</t></h4> + <div id="list"> + <a t-if="record.use_tasks.raw_value" class="oe_project_buttons" + id="1" name="%(act_project_project_2_project_task_all)d" type="action"> + Tasks(<field name="task_count"/>)</a> + </div> + <br/> + <button class="click_button" name="dummy" type="object"> + <table class="project_fields"> + <tr id="deadline" t-if="record.date.raw_value"> + <th align="left">Deadline</th> + <td align="left"><field name="date"/></td> + </tr> + <tr> + <th align="left" width="70px">Progress</th> + <td align="left"> + <t t-esc="Math.round(record.effective_hours.raw_value)"/> / <t t-esc="Math.round(record.planned_hours.raw_value)"/> <field name="company_uom_id"/> + </td> + </tr> + </table> + <br/> + <t t-foreach="record.members.raw_value" t-as="member"> + <img t-att-src="kanban_image('res.users', 'avatar', member)" t-att-id="member" class="project_avatar"/> + </t> + </button> + </li> + </div> + <script type="text/javascript"> + //open dropdwon when click on the icon. + $('.dropdown-toggle').dropdown(); + + //show and hide the dropdown icon when mouseover and mouseour. + $('.oe_project_kanban_vignette').mouseover(function() { + return $(this).find('.oe_project_kanban_action').show(); + }).mouseout(function() { + return $(this).find('.oe_project_kanban_action').hide(); + }); + </script> + </t> + </templates> + </kanban> + </field> + </record> <record id="view_project_project_gantt" model="ir.ui.view"> <field name="name">project.project.gantt</field> @@ -171,8 +264,8 @@ <field name="res_model">project.project</field> <field name="view_type">form</field> <field name="domain">[]</field> - <field name="view_mode">tree,form,gantt</field> - <field name="view_id" ref="view_project"/> + <field name="view_mode">kanban,tree,form,gantt</field> + <field name="view_id" ref="view_project_kanban"/> <field name="search_view_id" ref="view_project_project_filter"/> <field name="context">{}</field> <field name="help">A project contains a set of tasks or issues that will be performed by your resources assigned to it. A project can be hierarchically structured, as a child of a Parent Project. This allows you to design large project structures with different phases spread over the project duration cycle. Each user can set his default project in his own preferences to automatically filter the tasks or issues he usually works on. If you choose to invoice the time spent on a project task, you can find project tasks to be invoiced in the billing section.</field> @@ -314,8 +407,7 @@ </form> </field> </record> - - + <!-- Project Task Kanban View --> <record model="ir.ui.view" id="view_task_kanban"> <field name="name">project.task.kanban</field> @@ -627,22 +719,13 @@ <menuitem id="menu_tasks_config" name="GTD" parent="project.menu_definitions" sequence="1"/> - <menuitem id="menu_project_config_project" name="Projects and Stages" parent="menu_definitions" sequence="1"/> + <menuitem id="menu_project_config_project" name="Stages" parent="menu_definitions" sequence="1"/> <menuitem action="open_task_type_form" id="menu_task_types_view" parent="menu_project_config_project" sequence="2" groups="base.group_no_one"/> - <menuitem action="open_view_project_all" id="menu_open_view_project_all" parent="menu_project_config_project" sequence="1" groups="base.group_no_one"/> + <menuitem action="open_view_project_all" id="menu_projects" name="Projects" parent="menu_project_management" sequence="1"/> <act_window context="{'search_default_user_id': [active_id], 'default_user_id': active_id}" id="act_res_users_2_project_project" name="User's projects" res_model="project.project" src_model="res.users" view_mode="tree,form" view_type="form"/> - <act_window - context="{'search_default_project_id': [active_id], 'default_project_id': active_id}" - id="act_project_project_2_project_task_all" - name="Tasks" - res_model="project.task" - src_model="project.project" - view_mode="tree,form,calendar,graph" - view_type="form"/> - <record id="task_company" model="ir.ui.view"> <field name="name">res.company.task.config</field> <field name="model">res.company</field> diff --git a/addons/project/static/src/css/project.css b/addons/project/static/src/css/project.css new file mode 100644 index 00000000000..93437c878ba --- /dev/null +++ b/addons/project/static/src/css/project.css @@ -0,0 +1,220 @@ +.project_fields { + margin-top: 1px; + margin-bottom: 1px; + font-size: 11px; + padding-left: 0px; + } +.project_fields td { + border: none; + padding: 2px 0 2px 8px; } +.project_fields th { + padding: 0; + border-right: 1px solid #dddddd; + vertical-align: top; + margin-right: 8px; } +.project_vignettes{ + padding: 4px !important } + .project_vignettes li { + float: left; + } + .project_vignettes .project_avatar { + width: 30px; + height: 30px; + padding-left: 0px; } + .project_vignettes .project_fields { + width: 100%; } + .project_vignettes .project_fields th { + width: 120px; + font-weight: normal; } + .project_vignettes .project_fields td { + color: #888888; } + .project_vignettes h4 a { + color: #4c4c4c; } + .project_vignettes > li h4 { + margin-bottom: 2px; + padding-left: 2px; } + +.oe_project_buttons { + padding: 2px 2px !important; + background: none !important; + background-color: transparent !important; + border: hidden !important; + color: #8A89BA !important; +} +.oe_project_buttons:hover { + cursor: pointer; + color: #0000FF; +} +.project_avatar { + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; + -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); + -box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); + } +.click_button { + background: none !important; + background-color: transparent !important; + border: hidden !important; + min-height: 155px; + min-width: 265px; + border: hidden; + /*margin-left: 6px !important;*/ + margin-top: 3px !important; + text-align: left; + vertical-align: super; + font-size: 11px; + -webkit-box-align: baseline; +} + + +.click_button:hover { + cursor: default !important; +} +.dropdown-menu { + display: none; + position: absolute; } +.dropdown { + position: relative; } +.dropdown-toggle:after { + width: 0; + height: 0; + display: inline-block; + content: "&darr"; + text-indent: -99999px; + vertical-align: top; + margin-top: 8px; + margin-left: 4px; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 4px solid white; + filter: alpha(opacity=50); + -khtml-opacity: 0.5; + -moz-opacity: 0.5; + opacity: 0.5; } + .oe_project_kanban_vignette .dropdown-menu .color-chooser { + padding: 0 3px; } + .oe_project_kanban_vignette .dropdown-menu .color-chooser li { + float: left; } + .oe_project_kanban_vignette .dropdown-menu .color-chooser li a { + padding: 2px; } +a.oe_project_kanban_action { + position: absolute; + right: 0; + display: none; + } + a.oe_project_kanban_action:hover { + text-decoration: none; } + a.oe_project_kanban_action .oe_i { + color: #4c4c4c; } + +.square { + display: inline-block; + width: 18px; + height: 18px; + border:1px solid grey; + } + +.oe_kanban_color_0 { + background: white; +} +.oe_kanban_color_1 { + background: #B1DCFE; +} +.oe_kanban_color_2 { + background: #FF8E8E; +} +.oe_kanban_color_3 { + background: khaki; +} +.oe_kanban_color_4 { + background: thistle; +} +.oe_kanban_color_5 { + background: orange; +} + +.open { + display: block; + } + .open .dropdown-menu { + display: block; } + +a.oe_project_kanban_action { + position: absolute; + right: 0; + } + a.oe_project_kanban_action:hover { + text-decoration: none; + } + a.oe_project_kanban_action .eo_i { + color: #4c4c4c; } + +.oe_project_kanban_vignette { + position: relative; + min-height: 50px; + /*background: white;*/ + border: 1px solid #d8d8d8; + border-bottom-color: #b9b9b9; + padding: 6px; + margin: 6px 0; + display: inline-block; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; + +} + .oe_project_kanban_vignette:last-child { + margin-bottom: 0; } + .oe_project_kanban_vignette:hover { + -moz-box-shadow: 0 0 3px rgba(0, 0, 0, 0.6); + -webkit-box-shadow: 0 0 3px rgba(0, 0, 0, 0.6); + -box-shadow: 0 0 3px rgba(0, 0, 0, 0.6); } + .oe_project_kanban_vignette h4 { + margin: 0 0 2px; } + +.oe_project_kanban_vignette .dropdown-menu { + top: 30px; + right: -140px; + padding: 4px; + border: 1px solid #afafb6; + width: 160px; + overflow-x: hidden; + z-index: 900; + background: white; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); + -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); + -box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); } + .oe_project_kanban_vignette .dropdown-menu p { + margin-left: 12px; } + .oe_project_kanban_vignette .dropdown-menu li { + float: none; + display: block; + background-color: none; } + .oe_project_kanban_vignette .dropdown-menu li a { + display: block; + padding: 3px 6px; + clear: both; + font-weight: normal; + line-height: 14px; + color: #4c4c4c; + text-decoration: none; } + .oe_project_kanban_vignette .dropdown-menu li a:hover { + background: #f0f0fa; + background: -moz-linear-gradient(#f0f0fa, #eeeef6); + background: -webkit-gradient(linear, left top, left bottom, from(#f0f0fa), to(#eeeef6)); + background: -webkit-linear-gradient(#f0f0fa, #eeeef6); + -moz-box-shadow: none; + -webkit-box-shadow: none; + -box-shadow: none; } + +.oe_project_kanban_vignette .dropdown-menu .color-chooser { + padding: 0 3px; } + .oe_project_kanban_vignette .dropdown-menu .color-chooser li { + float: left; } + .oe_project_kanban_vignette .dropdown-menu .color-chooser li a { + padding: 2px; } diff --git a/addons/project/static/src/img/tasks_icon.png b/addons/project/static/src/img/tasks_icon.png Binary files differnew file mode 100644 index 00000000000..5c5073b1633 --- /dev/null +++ b/addons/project/static/src/img/tasks_icon.png diff --git a/addons/project/static/src/js/dropdown.js b/addons/project/static/src/js/dropdown.js new file mode 100644 index 00000000000..54b61c5e9d0 --- /dev/null +++ b/addons/project/static/src/js/dropdown.js @@ -0,0 +1,92 @@ +/* ============================================================ + * bootstrap-dropdown.js v2.0.2 + * http://twitter.github.com/bootstrap/javascript.html#dropdowns + * ============================================================ + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============================================================ */ + + +!function( $ ){ + + "use strict" + + /* DROPDOWN CLASS DEFINITION + * ========================= */ + + var toggle = '[data-toggle="dropdown"]' + , Dropdown = function ( element ) { + var $el = $(element).on('click.dropdown.data-api', this.toggle) + $('html').on('click.dropdown.data-api', function () { + $el.parent().removeClass('open') + }) + } + + Dropdown.prototype = { + + constructor: Dropdown + + , toggle: function ( e ) { + var $this = $(this) + , selector = $this.attr('data-target') + , $parent + , isActive + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 + } + + $parent = $(selector) + $parent.length || ($parent = $this.parent()) + + isActive = $parent.hasClass('open') + + clearMenus() + !isActive && $parent.toggleClass('open') + + return false + } + + } + + function clearMenus() { + $(toggle).parent().removeClass('open') + } + + + /* DROPDOWN PLUGIN DEFINITION + * ========================== */ + + $.fn.dropdown = function ( option ) { + return this.each(function () { + var $this = $(this) + , data = $this.data('dropdown') + if (!data) $this.data('dropdown', (data = new Dropdown(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + $.fn.dropdown.Constructor = Dropdown + + + /* APPLY TO STANDARD DROPDOWN ELEMENTS + * =================================== */ + + $(function () { + $('html').on('click.dropdown.data-api', clearMenus) + $('body').on('click.dropdown.data-api', toggle, Dropdown.prototype.toggle) + }) + +}( window.jQuery );
\ No newline at end of file diff --git a/addons/project/static/src/js/project.js b/addons/project/static/src/js/project.js new file mode 100644 index 00000000000..09a7c9eb256 --- /dev/null +++ b/addons/project/static/src/js/project.js @@ -0,0 +1,48 @@ +openerp.project = function(openerp) { + openerp.web_kanban.ProjectKanban = openerp.web_kanban.KanbanRecord.include({ + bind_events: function() { + var self = this; + self._super(); + if(this.view.dataset.model == 'project.project') { + + /*set avatar title for members. + In many2many fields, returns only list of ids. + we can implement return value of m2m fields like [(1,"Adminstration"),...]. + */ + _.each($(this.$element).find('.project_avatar'),function(avatar){ + var dataset = new openerp.web.DataSetSearch(this, 'res.users', self.session.context, [['id','=',avatar.id]]); + dataset.read_slice(['name']).then(function(result){ + avatar.setAttribute("title",result[0].name) + }); + }); + + // set sequence like Tasks,Issues,Timesheets and Phases + var my_list = $("#list a") + my_list.sort(function (a, b) { + var aValue = parseInt(a.id); + var bValue = parseInt(b.id); + return aValue == bValue ? 0 : aValue < bValue ? -1 : 1; + }); + $('#list').replaceWith(my_list); + + // when vignette is clicked, it opens the first action in sequence + if (my_list.length != 0) { + var click_button = $(this.$element).find('.click_button') + click_button.attr('data-name', my_list[0].getAttribute('data-name')); + click_button.attr('data-type', "action"); + } + + /* set background color. + we can do other way to implement new widget. + because we need to rpc call for that. + */ + this.$element.find('.bgcolor').click(function(){ + var color = parseInt($(this).find('span').attr('class').split(' ')[0].substring(16)) + var color_class = $(this).find('span').attr('class').split(' ')[0] + $(this).closest('#oe_project_kanban_vignette').removeClass().addClass(color_class + ' oe_project_kanban_vignette'); + self.view.dataset.write(parseInt(this.id), {color:color}); + }); + } + } + }); +} |
