diff --git a/extra_time_application/README.rst b/extra_time_application/README.rst new file mode 100644 index 00000000..fd7aaa6c --- /dev/null +++ b/extra_time_application/README.rst @@ -0,0 +1,60 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +====================== +Extra Time Application +====================== +This module adds functionality to the application time and +will not allow to add new items if the task time is less than 0 + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smash it by providing detailed and welcomed feedback. + +Credits +======= + +Images +------ + +* Elico Corp: `Icon `_. + +Contributors +------------ + +* Miya Xing +* Eric Caudal +* Sébastien Maillard +* Hulk Liu + + +Maintainer +---------- + +.. image:: https://www.elico-corp.com/logo.png + :alt: Elico Corp + :target: https://www.elico-corp.com + +This module is maintained by Elico Corporation. + +Elico Corp is an innovative actor in China, Hong-Kong and Singapore servicing +well known international companies and as well as local mid-sized businesses. +Since 2010, our seasoned Sino-European consultants have been providing full +range Odoo services: + +* Business consultancy for Gap analysis, BPM, operational work-flows review. +* Ready-to-use ERP packages aimed at starting businesses. +* Odoo implementation for manufacturing, international trading, service industry + and e-commerce. +* Connectors and integration with 3rd party software (Magento, Taobao, Coswin, + Joomla, Prestashop, Tradevine etc...). +* Odoo Support services such as developments, training, maintenance and hosting. + +Our headquarters are located in Shanghai with branch in Singapore servicing +customers from all over Asia Pacific. + +Contact information: `Sales `__ diff --git a/extra_time_application/__init__.py b/extra_time_application/__init__.py new file mode 100644 index 00000000..30ac26c2 --- /dev/null +++ b/extra_time_application/__init__.py @@ -0,0 +1,5 @@ +# -*-coding: utf-8 -*- + +from . import models +from . import wizard +from . import tests diff --git a/extra_time_application/__manifest__.py b/extra_time_application/__manifest__.py new file mode 100644 index 00000000..13c9031a --- /dev/null +++ b/extra_time_application/__manifest__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# © 2017 Elico Corp (https://www.elico-corp.com) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + 'name': 'Extra Time Application', + 'version': '10.0.1.0.0', + 'author': "Elico Corp", + 'website': 'https://www.elico-corp.com', + 'license': 'AGPL-3', + 'support': 'https://support@elico-corp.com', + 'depends': [ + 'project', + ], + 'data': [ + 'wizard/time_prompt_view.xml', + 'views/extra_time_approve_view.xml', + 'views/project_task_form_inherit.xml', + 'security/extra_time_application_security.xml', + 'security/ir.model.access.csv', + ], + 'installable': True +} diff --git a/extra_time_application/models/__init__.py b/extra_time_application/models/__init__.py new file mode 100644 index 00000000..9af443d6 --- /dev/null +++ b/extra_time_application/models/__init__.py @@ -0,0 +1,5 @@ +# -*- coding:utf-8 -*- + +from . import task_timesheet +from . import project_task_inherit +from . import project_project_inherit diff --git a/extra_time_application/models/project_project_inherit.py b/extra_time_application/models/project_project_inherit.py new file mode 100644 index 00000000..6cbbaabd --- /dev/null +++ b/extra_time_application/models/project_project_inherit.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +# © 2017 Elico Corp (www.elico-corp.com). +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from openerp import fields, models + + +class ProjectProjectInherit(models.Model): + _inherit = 'project.project' + + is_modified = fields.Boolean( + string='Is modified', + help='If this field is true,everyone can create the corresponding ' + 'task,and if it is false, only the person in the manager group ' + 'can create the corresponding task') diff --git a/extra_time_application/models/project_task_inherit.py b/extra_time_application/models/project_task_inherit.py new file mode 100644 index 00000000..94321a01 --- /dev/null +++ b/extra_time_application/models/project_task_inherit.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- +# © 2017 Elico Corp (www.elico-corp.com). +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from openerp import api, fields, models, _ +from odoo.exceptions import UserError +from lxml import etree + + +class ProjectTaskInherit(models.Model): + _inherit = 'project.task' + + sub_extra_time = fields.Float( + 'Extra Time', help='the sum of the extra time', + default='0', + ) + + @api.multi + def write(self, vals): + for log in self: + current_user = log.env.user + remaining = log.remaining_hours + spending_hours = 0 + if not log.env.context.get('flag') and \ + not log.env.context.get('flag_remaine'): + for record in vals.get('timesheet_ids', []): + if record[0] == 0: + remaining -= record[2]['unit_amount'] + spending_hours += record[2]['unit_amount'] + elif record[0] == 2: + item = log.timesheet_ids.search([ + ('id', '=', record[1]) + ]) + remaining += item['unit_amount'] + spending_hours -= item['unit_amount'] + if vals.get('remaining_hours') and \ + (vals.get('remaining_hours') != remaining): + log.env['extra.time.application'].create({ + 'submit_user_id': current_user.id, + 'task_id': log.id, + 'reason': 'Automatically create From PM or Reviewer', + 'apply_hours': + vals.get('remaining_hours') - + log.remaining_hours + spending_hours, + 'state': 'approve', + }) + log.sub_extra_time += \ + (vals.get('remaining_hours') - + log.remaining_hours + spending_hours) + if remaining < 0: + raise UserError( + _( + 'The task has no enough time left, ' + 'please apply for more extra time.' + )) + res = super(ProjectTaskInherit, self).write(vals) + return res + + @api.model + def create(self, vals): + current_user = self.env.user + is_exist = current_user.has_group( + 'extra_time_application.group_project_task_manager') + if not vals.get('project_id.is_modified'): + if not is_exist and self.project_id.user_id != current_user: + raise UserError( + _('You do not have permission to ' + 'create task belong to this project.')) + return super(ProjectTaskInherit, self).create(vals) + + @api.multi + def open_extra_time_line(self): + for record in self: + domain = [('task_id', '=', record.name)] + return { + 'name': _('Extra Time Approval'), + 'type': 'ir.actions.act_window', + 'view_type': 'form', + 'view_mode': 'tree,form', + 'res_model': 'extra.time.application', + 'target': 'current', + 'domain': domain, + } + + @api.model + def fields_view_get(self, view_id=None, view_type='form', + toolbar=False, submenu=False): + res = super(ProjectTaskInherit, self).fields_view_get( + view_id=view_id, view_type=view_type, toolbar=toolbar, + submenu=submenu) + dom = etree.XML(res['arch']) + if view_type == 'form': + current_user = self.env.user + is_exist = current_user.has_group( + 'extra_time_application.group_project_task_manager') + if is_exist or self.project_id.is_modified: + for node in dom.xpath("//field[@name='remaining_hours']"): + node.set("modifiers", '{"readonly": false}') + res['arch'] = etree.tostring(dom) + return res diff --git a/extra_time_application/models/task_timesheet.py b/extra_time_application/models/task_timesheet.py new file mode 100644 index 00000000..de6329d2 --- /dev/null +++ b/extra_time_application/models/task_timesheet.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- +# © 2017 Elico Corp (www.elico-corp.com). +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from openerp import api, fields, models, _ +from odoo.exceptions import UserError + + +class TaskTimeSheet(models.Model): + _name = 'extra.time.application' + _inherit = ['mail.thread', 'ir.needaction_mixin'] + + submit_user_id = fields.Many2one( + 'res.users', 'Applicant', help='Applicant', + ) + task_id = fields.Many2one('project.task', 'Task No', help='Task No') + reason = fields.Text('Reason', help='the reason of apply') + apply_hours = fields.Float('Apply Hours', help='the length of apply time') + state = fields.Selection([ + ('to_approve', 'To Approve'), + ('approve', 'Approved'), + ('refused', 'Refused'), + ], track_visibility='onchange', defaule='to_approve') + + @api.one + def approve_function(self): + task_id = self.task_id + task_manager = task_id.project_id.user_id + if (self.env.user in self.env.ref( + 'extra_time_application.group_extra_time_manager').users) or ( + self.env.user == task_manager.user_id) or \ + task_id.project_id.is_modified: + self.with_context(flag='True'). \ + task_id.remaining_hours += self.apply_hours + self.task_id.sub_extra_time += self.apply_hours + self.state = 'approve' + self.message_post( + body=_(" %s Approved.") % (self.env.user.name)) + else: + raise UserError( + _( + 'You do not have permission to approve it.' + )) + + @api.one + def refuse_function(self): + task_id = self.task_id + task_manager = task_id.project_id.user_id + if (self.env.user in self.env.ref( + 'extra_time_application.group_extra_time_manager').users) or ( + self.env.user == task_manager.user_id) or \ + task_id.project_id.is_modified: + self.state = 'refused' + self.message_post( + body=_(" %s Refused.") % (self.env.user.name)) + else: + raise UserError( + _( + 'You do not have permission to refuse it.' + )) + + @api.model + def create(self, vals): + msg_followers = [] + for user in self.env.ref( + 'extra_time_application.group_extra_time_manager').users: + if user != self.env['project.task'].browse( + vals.get('task_id')).user_id: + msg_vals = { + 'partner_id': user.partner_id.id, + 'res_model': self._name, + } + msg_followers.append((0, 0, msg_vals)) + task_id = self.env['project.task']. \ + browse(vals.get('task_id')) + task_manager = task_id.project_id.user_id + if (task_manager not in self.env.ref( + 'extra_time_application.group_extra_time_manager').users) and ( + task_manager != task_id.user_id): + msg_vals = { + 'partner_id': task_manager.partner_id.id, + 'res_model': self._name, + } + msg_followers.append((0, 0, msg_vals)) + if msg_followers: + vals['message_follower_ids'] = msg_followers + return super(TaskTimeSheet, self).create(vals) diff --git a/extra_time_application/security/extra_time_application_security.xml b/extra_time_application/security/extra_time_application_security.xml new file mode 100644 index 00000000..954b8f33 --- /dev/null +++ b/extra_time_application/security/extra_time_application_security.xml @@ -0,0 +1,19 @@ + + + + Time Application Manager + + + + Time Application User + + + Extra Time Manager + + + + extra time rule + + [('submit_user_id','=',user.id)] + + diff --git a/extra_time_application/security/ir.model.access.csv b/extra_time_application/security/ir.model.access.csv new file mode 100644 index 00000000..527e51a2 --- /dev/null +++ b/extra_time_application/security/ir.model.access.csv @@ -0,0 +1,5 @@ +"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" +access_project_task_manager,project_task_manager,project.model_project_task,extra_time_application.group_project_task_manager,1,1,1,1 +access_project_task_user,project_task_user,project.model_project_task,extra_time_application.group_project_task_user,1,1,0,0 +access_extra_time_manager,extra_time_manager,extra_time_application.model_extra_time_application,extra_time_application.group_extra_time_manager,1,1,1,1 +access_extra_time_user,extra_time_user,extra_time_application.model_extra_time_application,base.group_user,1,1,1,1 diff --git a/extra_time_application/tests/__init__.py b/extra_time_application/tests/__init__.py new file mode 100644 index 00000000..7d4880eb --- /dev/null +++ b/extra_time_application/tests/__init__.py @@ -0,0 +1,5 @@ +# -*- coding:utf-8 -*- + +from . import test_extra_time +from . import test_project_task_save +from . import test_extra_time_wizard diff --git a/extra_time_application/tests/test_extra_time.py b/extra_time_application/tests/test_extra_time.py new file mode 100644 index 00000000..934376dd --- /dev/null +++ b/extra_time_application/tests/test_extra_time.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# © 2017 Elico Corp (www.elico-corp.com) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from odoo.tests import common +from odoo.exceptions import UserError + + +class TestExtraTime(common.TransactionCase): + + def setUp(self): + super(TestExtraTime, self).setUp() + self.submit_user_id = self.env.ref("base.user_demo") + self.task_id = self.env.ref('project.project_task_9') + + self.extra_time_record = self.env['extra.time.application'].create({ + 'submit_user_id': self.submit_user_id.id, + 'task_id': self.task_id.id, + 'reason': 'Automatically created From PM or Reviewer', + 'apply_hours': 2, + 'state': 'to_approve' + }) + + def test_approve_function(self): + try: + self.extra_time_record.approve_function() + except UserError: + pass + + def test_refuse_function(self): + try: + self.extra_time_record.refuse_function() + except UserError: + pass diff --git a/extra_time_application/tests/test_extra_time_wizard.py b/extra_time_application/tests/test_extra_time_wizard.py new file mode 100644 index 00000000..b567cb0c --- /dev/null +++ b/extra_time_application/tests/test_extra_time_wizard.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# © 2017 Elico Corp (www.elico-corp.com) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from odoo.tests import common + + +class TestExtraTimeWizard(common.TransactionCase): + + def setUp(self): + super(TestExtraTimeWizard, self).setUp() + self.task_id = self.env.ref('project.project_task_9') + self.submit_user_id = self.env.ref('base.user_demo') + self.extra_time_1 = self.env['extra.time.application.wizard'].create({ + 'submit_user_id': self.submit_user_id.id, + 'task_id': self.task_id.id, + 'reason': 'system test', + 'apply_hours': 3 + }) + self.extra_time_2 = self.env['extra.time.application.wizard'].create({ + 'submit_user_id': self.submit_user_id.id, + 'task_id': self.task_id.id, + 'reason': 'system test', + 'apply_hours': 3 + }) + + def test_subscribe(self): + self.extra_time_1.subscribe() + self.extra_time_2.subscribe() diff --git a/extra_time_application/tests/test_project_task_save.py b/extra_time_application/tests/test_project_task_save.py new file mode 100644 index 00000000..bc7f49e4 --- /dev/null +++ b/extra_time_application/tests/test_project_task_save.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +# © 2017 Elico Corp (www.elico-corp.com) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from odoo.tests import common +from datetime import datetime +from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT +from odoo.exceptions import UserError + + +class TestProjectTaskSave(common.TransactionCase): + + def setUp(self): + super(TestProjectTaskSave, self).setUp() + self.name_1 = self.env.ref('project.project_task_9') + self.name_2 = self.env.ref('project.project_task_8') + self.project_id = self.env.ref('project.project_project_4') + self.user_id = self.env.ref('base.user_root') + self.sub_extra_time = 0 + self.timesheet_ids = self.env['account.analytic.line'].write([[ + 0, False, { + 'date_time': datetime.today().strftime( + DEFAULT_SERVER_DATETIME_FORMAT), + 'user_id': self.user_id.id, + 'name': 'test', + 'unit_amount': 1, + 'date': '2017-12-15', + 'project_id': self.project_id.id + } + ]]) + self.task_1 = self.env['project.task'].create({ + 'name': self.name_1.id, + 'project_id': self.project_id.id, + 'user_id': self.user_id.id, + 'planned_hours': 18, + 'remaining_hours': 18, + 'timesheet_ids': self.timesheet_ids + }) + self.task_2 = self.env['project.task'].create({ + 'name': self.name_2.id, + 'project_id': self.project_id.id, + 'user_id': self.user_id.id, + 'planned_hours': 18, + 'remaining_hours': 1, + 'timesheet_ids': self.timesheet_ids + }) + self.view_id = self.env.ref( + 'extra_time_application.project_task_form_inherit_view').id + + def test_save(self): + vals_1 = {'remaining_hours': 23} + self.task_1.write(vals_1) + vals_2 = {'timesheet_ids': [[4, 20, False], [0, False, { + 'date_time': datetime.today().strftime( + DEFAULT_SERVER_DATETIME_FORMAT), + 'user_id': 1, + 'name': self.name_2, + 'unit_amount': 2, + 'date': '2017-12-13', + 'project_id': self.project_id.id + }]]} + self.task_1.write(vals_2) + try: + self.task_2.write(vals_2) + except UserError: + pass + + def test_open_extra_time_line(self): + self.task_1.open_extra_time_line() + + def test_fields_view_get(self): + self.task_1.fields_view_get(view_id=self.view_id, view_type='form') diff --git a/extra_time_application/views/extra_time_approve_view.xml b/extra_time_application/views/extra_time_approve_view.xml new file mode 100644 index 00000000..e63ac045 --- /dev/null +++ b/extra_time_application/views/extra_time_approve_view.xml @@ -0,0 +1,64 @@ + + + + extra.time.approve.form + extra.time.application + form + +
+
+
+ + + + + + + + + + + + + + + + +
+
+ + +
+
+
+ + extra.time.approve.tree + extra.time.application + + + + + + + + + + + + Extra Time Approve + ir.actions.act_window + extra.time.application + form + form,tree + + + +
diff --git a/extra_time_application/views/project_task_form_inherit.xml b/extra_time_application/views/project_task_form_inherit.xml new file mode 100644 index 00000000..8f2b9a93 --- /dev/null +++ b/extra_time_application/views/project_task_form_inherit.xml @@ -0,0 +1,34 @@ + + + + project.task.form + project.task + + + + + + + + + project.project.form + project.project + + + +
+ +
+
+
+
+
diff --git a/extra_time_application/wizard/__init__.py b/extra_time_application/wizard/__init__.py new file mode 100644 index 00000000..f05e90a1 --- /dev/null +++ b/extra_time_application/wizard/__init__.py @@ -0,0 +1,3 @@ +# -*- coding:utf-8 -*- + +from . import extra_time_application_wizard diff --git a/extra_time_application/wizard/extra_time_application_wizard.py b/extra_time_application/wizard/extra_time_application_wizard.py new file mode 100644 index 00000000..28b2ab98 --- /dev/null +++ b/extra_time_application/wizard/extra_time_application_wizard.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +# © 2017 Elico Corp (www.elico-corp.com). +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from openerp import fields, models, api + + +class TimePromptBox(models.TransientModel): + _name = 'extra.time.application.wizard' + + @api.model + def _get_task_id(self): + return self.env['project.task'].browse( + self._context.get('active_id') + ) + + submit_user_id = fields.Many2one( + 'res.users', 'Applicant', help='Applicant', + default=lambda self: self.env.user, readonly="True" + ) + task_id = fields.Many2one('project.task', 'Task No', help='Task No', + default=_get_task_id, readonly="True" + ) + reason = fields.Text('Reason', help='the reason of apply') + apply_hours = fields.Float('Apply Hours', help='the length of apply time') + + @api.multi + def subscribe(self): + current_user = self.env.user + is_exist = current_user.has_group( + 'extra_time_application.group_project_task_manager') + if is_exist: + self.env['extra.time.application'].create({ + 'submit_user_id': self.submit_user_id.id, + 'task_id': self.task_id.id, + 'reason': 'Automatically created From PM or Reviewer', + 'apply_hours': self.apply_hours, + 'state': 'approve', + }) + self.with_context(flag_remaine='True'). \ + task_id.remaining_hours += self.apply_hours + self.task_id.sub_extra_time += self.apply_hours + + else: + mail = self.env['mail.mail'].create({ + 'body_html': 'apply time:' + str( + self.apply_hours) + '
reason:' + + str(self.reason) + '
Task:' + + str(self.task_id.code), + 'email_to': self.task_id.project_id.user_id.login, + 'subject': 'Apply Time', + }) + mail.send() + self.env['extra.time.application'].create({ + 'submit_user_id': self.submit_user_id.id, + 'task_id': self.task_id.id, + 'reason': self.reason, + 'apply_hours': self.apply_hours, + 'state': 'to_approve', + }) diff --git a/extra_time_application/wizard/time_prompt_view.xml b/extra_time_application/wizard/time_prompt_view.xml new file mode 100644 index 00000000..6c8096b7 --- /dev/null +++ b/extra_time_application/wizard/time_prompt_view.xml @@ -0,0 +1,42 @@ + + + + view.time.prompt.form + extra.time.application.wizard + form + +
+ + + + + + + + + + + + + + + + +
+
+
+
+
+ + Time Application + ir.actions.act_window + extra.time.application.wizard + form + form + new + + +