From 1bf2bfd2a1b72d8f3954ba79a0f116ee579d4e14 Mon Sep 17 00:00:00 2001 From: Yannick Tivisse Date: Thu, 19 Dec 2019 14:55:39 +0100 Subject: [PATCH] [IMP] hr_timesheet: Small cleaning and linting --- addons/account/security/account_security.xml | 4 -- .../hr_timesheet/data/hr_timesheet_data.xml | 43 +++++++++---------- addons/hr_timesheet/models/hr_timesheet.py | 35 ++------------- addons/hr_timesheet/models/project.py | 26 +++-------- addons/hr_timesheet/models/timer_mixin.py | 35 ++++++--------- .../security/hr_timesheet_security.xml | 19 ++------ addons/hr_timesheet/static/src/js/Timer.js | 4 +- addons/hr_timesheet/static/src/js/timer.js | 3 +- .../static/src/js/timesheet_uom.js | 2 +- .../static/src/scss/timer_button.scss | 2 +- .../hr_timesheet/views/hr_timesheet_views.xml | 2 +- addons/sale/models/sale.py | 2 +- addons/sale_timesheet/models/account_move.py | 4 +- .../sale_timesheet/models/project_overview.py | 2 +- addons/sale_timesheet/models/sale_order.py | 14 +++--- .../wizard/sale_make_invoice_advance.py | 18 +++----- 16 files changed, 70 insertions(+), 145 deletions(-) diff --git a/addons/account/security/account_security.xml b/addons/account/security/account_security.xml index 7b67561fc6d..986c3563b21 100644 --- a/addons/account/security/account_security.xml +++ b/addons/account/security/account_security.xml @@ -65,10 +65,6 @@ [(1, '=', 1)] - - - - diff --git a/addons/hr_timesheet/data/hr_timesheet_data.xml b/addons/hr_timesheet/data/hr_timesheet_data.xml index ed572593da6..8fb4eb76cf6 100644 --- a/addons/hr_timesheet/data/hr_timesheet_data.xml +++ b/addons/hr_timesheet/data/hr_timesheet_data.xml @@ -1,29 +1,26 @@ - - - - project_tour - - + + + float_toggle + - - - float_toggle - + + float_time + - - float_time - + + - - - - - - + + hr_timesheet.timesheet_rounding + 15 + + + hr_timesheet.timesheet_min_duration + 15 + diff --git a/addons/hr_timesheet/models/hr_timesheet.py b/addons/hr_timesheet/models/hr_timesheet.py index f258d49c5de..4e4fa8e20a1 100644 --- a/addons/hr_timesheet/models/hr_timesheet.py +++ b/addons/hr_timesheet/models/hr_timesheet.py @@ -48,12 +48,9 @@ class AccountAnalyticLine(models.Model): employee_id = fields.Many2one('hr.employee', "Employee", check_company=True, domain=_domain_employee_id) department_id = fields.Many2one('hr.department', "Department", compute='_compute_department_id', store=True, compute_sudo=True) encoding_uom_id = fields.Many2one('uom.uom', compute='_compute_encoding_uom_id') - - # Timer Fields display_timer = fields.Boolean( compute='_compute_display_timer', - help="used to display the timer if the encoding unit is 'Hours'" - ) + help="Technical field used to display the timer if the encoding unit is 'Hours'.") def _compute_encoding_uom_id(self): for analytic_line in self: @@ -82,10 +79,6 @@ class AccountAnalyticLine(models.Model): for line in self: line.department_id = line.employee_id.department_id - # ---------------------------------------------------- - # ORM overrides - # ---------------------------------------------------- - @api.model_create_multi def create(self, vals_list): default_user_id = self._default_user() @@ -134,10 +127,6 @@ class AccountAnalyticLine(models.Model): node.set('string', _('Duration (%s)') % (re.sub(r'[\(\)]', '', encoding_uom.name or ''))) return etree.tostring(doc, encoding='unicode') - # ---------------------------------------------------- - # Business Methods - # ---------------------------------------------------- - def _timesheet_get_portal_domain(self): return ['|', '&', ('task_id.project_id.privacy_visibility', '=', 'portal'), @@ -207,51 +196,37 @@ class AccountAnalyticLine(models.Model): }) return result - # ---------------------------------------------------- - # Timer Methods - # ---------------------------------------------------- def _compute_display_timer(self): - """ Check if the encoding unit is 'Hours', - - if yes then the timer button is displayed - """ + uom_hour = self.env.ref('uom.product_uom_hour') for analytic_line in self: - analytic_line.display_timer = analytic_line.encoding_uom_id.id == self.env.ref('uom.product_uom_hour').id + analytic_line.display_timer = analytic_line.encoding_uom_id == uom_hour def action_timer_start(self): - """ Action start the timer of the current timesheet. - - Start timer and search if another timer hasn't been launched. + """ Start timer and search if another timer hasn't been launched. If yes, then stop the timer before launch this timer. """ - self.ensure_one() if not self.timer_start and self.display_timer: self._stop_running_timers() super().action_timer_start() def _stop_running_timers(self): """ Search if a timesheet has a timer activated and stop the timer. - Check if a timer is activated for another timesheet if yes, then update unit_amount field and stop timer, otherwise, do nothing. """ analytic_line = self.search([('timer_start', '!=', False), ('user_id', '=', self.env.uid)]) - if analytic_line: analytic_line.action_timer_stop() def action_timer_stop(self): """ Action stop the timer of the current timesheet. - When the timer must be stopped, we must calculate the new unit_amount based on the timer and the previous value of unit_amount for the current timesheet. """ - self.ensure_one() if self.timer_start and self.display_timer: minutes_spent = self._get_minutes_spent() - if self.unit_amount == 0 and minutes_spent < 1: # Check if unit_amount equals 0 and minutes_spent is less than 1 minute, # if yes, then remove the timesheet @@ -261,7 +236,5 @@ class AccountAnalyticLine(models.Model): amount = self.unit_amount else: amount = self.unit_amount + minutes_spent * 60 / 3600 - self.write({'unit_amount': amount}) - super().action_timer_stop() diff --git a/addons/hr_timesheet/models/project.py b/addons/hr_timesheet/models/project.py index d50a9571882..a475536aae7 100644 --- a/addons/hr_timesheet/models/project.py +++ b/addons/hr_timesheet/models/project.py @@ -20,15 +20,12 @@ class Project(models.Model): allow_timesheet_timer = fields.Boolean('Timesheet Timer', default=False, help="Use a timer to record timesheets on tasks") _sql_constraints = [ - ('timer_only_when_timesheet', "CHECK((allow_timesheets = 'f' AND allow_timesheet_timer = 'f') OR (allow_timesheets = 't'))", 'The timesheet timer can only be activated on project allowing timesheet.'), + ('timer_only_when_timesheet', "CHECK((allow_timesheets = 'f' AND allow_timesheet_timer = 'f') OR (allow_timesheets = 't'))", 'The timesheet timer can only be activated on project allowing timesheets.'), ] @api.onchange('partner_id') def _onchange_partner_id(self): - domain = [] - if self.partner_id: - domain = [('partner_id', '=', self.partner_id.id)] - return {'domain': {'analytic_account_id': domain}} + return {'domain': {'analytic_account_id': [('partner_id', '=', self.partner_id.id)] if self.partner_id else []}} @api.onchange('analytic_account_id') def _onchange_analytic_account(self): @@ -74,16 +71,12 @@ class Project(models.Model): project._create_analytic_account() result = super(Project, self).write(values) if 'allow_timesheet_timer' in values and not values.get('allow_timesheet_timer'): - self.env['project.task'].with_context(active_test=False).search([('project_id', 'in', self.ids)]).write({ + self.with_context(active_test=False).mapped('task_ids').write({ 'timer_start': False, 'timer_pause': False, }) return result - # --------------------------------------------------- - # Business Methods - # --------------------------------------------------- - @api.model def _init_data_analytic_account(self): self.search([('analytic_account_id', '=', False), ('allow_timesheets', '=', True)])._create_analytic_account() @@ -102,8 +95,9 @@ class Task(models.Model): subtask_effective_hours = fields.Float("Sub-tasks Hours Spent", compute='_compute_subtask_effective_hours', store=True, help="Sum of actually spent hours on the subtask(s)") timesheet_ids = fields.One2many('account.analytic.line', 'task_id', 'Timesheets') - timer_start = fields.Datetime("Timesheet Timer Start", default=None) + timer_start = fields.Datetime("Timesheet Timer Start") timer_pause = fields.Datetime("Timesheet Timer Last Pause") + # YTI FIXME: Those field seems quite useless timesheet_timer_first_start = fields.Datetime("Timesheet Timer First Use", readonly=True) timesheet_timer_last_stop = fields.Datetime("Timesheet Timer Last Use", readonly=True) display_timesheet_timer = fields.Boolean("Display Timesheet Time", compute='_compute_display_timesheet_timer') @@ -145,10 +139,6 @@ class Task(models.Model): for task in self: task.display_timesheet_timer = task.allow_timesheets and task.project_id.allow_timesheet_timer and task.analytic_account_active - # --------------------------------------------------------- - # ORM - # --------------------------------------------------------- - def write(self, values): # a timesheet must have an analytic account (and a project) if 'project_id' in values and self and not values.get('project_id'): @@ -173,10 +163,6 @@ class Task(models.Model): result['arch'] = self.env['account.analytic.line']._apply_timesheet_label(result['arch']) return result - # --------------------------------------------------------- - # Timer Methods - # --------------------------------------------------------- - def action_timer_start(self): self.ensure_one() if not self.timesheet_timer_first_start: @@ -201,7 +187,7 @@ class Task(models.Model): def _action_create_timesheet(self, time_spent): return { - "name": _("Confirm Time Spent"), + "name": _("Validate Spent Time"), "type": 'ir.actions.act_window', "res_model": 'project.task.create.timesheet', "views": [[False, "form"]], diff --git a/addons/hr_timesheet/models/timer_mixin.py b/addons/hr_timesheet/models/timer_mixin.py index 319d020a66e..e4070d9b0a3 100644 --- a/addons/hr_timesheet/models/timer_mixin.py +++ b/addons/hr_timesheet/models/timer_mixin.py @@ -7,25 +7,20 @@ from odoo import models, fields, api class TimerMixin(models.AbstractModel): _name = 'timer.mixin' _description = 'Timer Mixin' + # YTI Note: This mixin is supposed to be extended to + # any models timer_start = fields.Datetime("Timer Start") timer_pause = fields.Datetime("Timer Last Pause") - - # is_timer_running field is used with timer_toggle_button widget - # to create a timer button in the view. - # If the timer field is set on False, - # then it displays a button with fa-icon-play icon. - # Otherwise, it displays a button with fa-icon-stop icon is_timer_running = fields.Boolean(compute="_compute_timer") @api.depends('timer_start') - def _compute_timer(self) -> None: + def _compute_timer(self): for record in self: record.is_timer_running = bool(record.timer_start) - def action_timer_start(self) -> None: + def action_timer_start(self): """ Action start the timer. - Start timer and search if another timer hasn't been launched. If yes, then stop the timer before launch this timer. """ @@ -45,32 +40,26 @@ class TimerMixin(models.AbstractModel): self.write({'timer_start': False, 'timer_pause': False}) return minutes_spent - def _get_minutes_spent(self) -> float: - """ Compute the minutes spent with the timer - - :return minutes spent - """ + def _get_minutes_spent(self): start_time = self.timer_start stop_time = fields.Datetime.now() - # timer was either running or paused if self.timer_pause: start_time += (stop_time - self.timer_pause) - return (stop_time - start_time).total_seconds() / 60 - def action_timer_pause(self) -> None: + def action_timer_pause(self): self.write({'timer_pause': fields.Datetime.now()}) - def action_timer_resume(self) -> None: + def action_timer_resume(self): new_start = self.timer_start + (fields.Datetime.now() - self.timer_pause) self.write({'timer_start': new_start, 'timer_pause': False}) + @api.model def get_server_time(self): - """ Get the time of the server - - The problem with the timer, it's the time can be different between server side and client side. - We need to have the time of the server and don't use the local time. Then, we have a timer beginning at 0:00 - and not 23:59 or something else. + """ Returns the server time. + The timer widget needs the server time instead of the client time + to avoid time desynchronization issues like the timer beginning at 0:00 + and not 23:59 and so on. """ return fields.Datetime.now() diff --git a/addons/hr_timesheet/security/hr_timesheet_security.xml b/addons/hr_timesheet/security/hr_timesheet_security.xml index 43bea381021..0b7970610f7 100644 --- a/addons/hr_timesheet/security/hr_timesheet_security.xml +++ b/addons/hr_timesheet/security/hr_timesheet_security.xml @@ -36,17 +36,12 @@ [ ('user_id', '=', user.id), ('project_id', '!=', False), - '|', + '|', '|', ('project_id.privacy_visibility', '!=', 'followers'), - '|', - ('project_id.message_partner_ids', 'in', [user.partner_id.id]), - ('task_id.message_partner_ids', 'in', [user.partner_id.id]) + ('project_id.message_partner_ids', 'in', [user.partner_id.id]), + ('task_id.message_partner_ids', 'in', [user.partner_id.id]) ] - - - - @@ -59,10 +54,6 @@ ('project_id.message_partner_ids', 'in', [user.partner_id.id]) ] - - - - @@ -70,10 +61,6 @@ [('project_id', '!=', False)] - - - - diff --git a/addons/hr_timesheet/static/src/js/Timer.js b/addons/hr_timesheet/static/src/js/Timer.js index c2cbabc5834..2a2c993dd91 100644 --- a/addons/hr_timesheet/static/src/js/Timer.js +++ b/addons/hr_timesheet/static/src/js/Timer.js @@ -2,8 +2,8 @@ odoo.define('hr_timesheet.Timer', function (require) { "use strict"; /** - * This class allow to facilitate to display the timer - * in unit_amount field of account.analytic.line. + * This class improves the timer display using the + * unit_amount field on account.analytic.line. */ class Timer { constructor(hours, minutes, seconds) { diff --git a/addons/hr_timesheet/static/src/js/timer.js b/addons/hr_timesheet/static/src/js/timer.js index f1ef8edd53e..5e7e4a1b2fb 100644 --- a/addons/hr_timesheet/static/src/js/timer.js +++ b/addons/hr_timesheet/static/src/js/timer.js @@ -19,6 +19,7 @@ var TimerFieldWidget = AbstractField.extend({ * @private */ _render: function () { + this._super.apply(this, arguments); this._startTimeCounter(); }, /** @@ -52,7 +53,7 @@ var TimerFieldWidget = AbstractField.extend({ return this._rpc({ model: 'account.analytic.line', method: 'get_server_time', - args: [false] + args: [] }); } }); diff --git a/addons/hr_timesheet/static/src/js/timesheet_uom.js b/addons/hr_timesheet/static/src/js/timesheet_uom.js index 9b74f686c4e..a462b1d463b 100644 --- a/addons/hr_timesheet/static/src/js/timesheet_uom.js +++ b/addons/hr_timesheet/static/src/js/timesheet_uom.js @@ -96,7 +96,7 @@ const FieldTimesheetTime = basicFields.FieldFloatTime.extend({ const time = await this._rpc({ model: this.model, method: 'get_server_time', - args: [false] + args: [] }); this.time = Timer.createTimer(this.recordData.unit_amount, this.recordData.timer_start, time); return this._startTimeCounter(); diff --git a/addons/hr_timesheet/static/src/scss/timer_button.scss b/addons/hr_timesheet/static/src/scss/timer_button.scss index 8d68d4eb9ea..8b269fa8b20 100644 --- a/addons/hr_timesheet/static/src/scss/timer_button.scss +++ b/addons/hr_timesheet/static/src/scss/timer_button.scss @@ -23,4 +23,4 @@ &:hover > i.o-timer-stop-button { color: darken($red, 15%) !important; } -} \ No newline at end of file +} diff --git a/addons/hr_timesheet/views/hr_timesheet_views.xml b/addons/hr_timesheet/views/hr_timesheet_views.xml index 06b400d845e..d81fee4d186 100644 --- a/addons/hr_timesheet/views/hr_timesheet_views.xml +++ b/addons/hr_timesheet/views/hr_timesheet_views.xml @@ -167,7 +167,7 @@
- +
diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index 889bf98dc44..5fce56f0fa1 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -572,7 +572,7 @@ class SaleOrder(models.Model): action['context'] = context return action - def _create_invoices(self, grouped=False, final=False): + def _create_invoices(self, grouped=False, final=False, date=None): """ Create the invoice associated to the SO. :param grouped: if True, invoices are grouped by SO id. If False, invoices are grouped by diff --git a/addons/sale_timesheet/models/account_move.py b/addons/sale_timesheet/models/account_move.py index 69d8ee4277f..e98e479576b 100644 --- a/addons/sale_timesheet/models/account_move.py +++ b/addons/sale_timesheet/models/account_move.py @@ -59,7 +59,7 @@ class AccountMove(models.Model): domain = line._timesheet_domain_get_invoiced_lines(sale_line_delivery) if date: domain = expression.AND([domain, [('date', '<=', date)]]) - timesheets = self.env['account.analytic.line'].search(domain).sudo() + timesheets = self.env['account.analytic.line'].sudo().search(domain) timesheets.write({'timesheet_invoice_id': line.move_id.id}) @@ -73,9 +73,7 @@ class AccountMoveLine(models.Model): :return a normalized domain """ return [ - '&', ('so_line', 'in', sale_line_delivery.ids), - '&', ('timesheet_invoice_id', '=', False), ('project_id', '!=', False) ] diff --git a/addons/sale_timesheet/models/project_overview.py b/addons/sale_timesheet/models/project_overview.py index 7dbb0f192ec..c5953a556f4 100644 --- a/addons/sale_timesheet/models/project_overview.py +++ b/addons/sale_timesheet/models/project_overview.py @@ -62,7 +62,7 @@ class Project(models.Model): # hours (from timesheet) and rates (by billable type) dashboard_domain = [('project_id', 'in', self.ids), ('timesheet_invoice_type', '!=', False), '|', ('so_line', '=', False), ('so_line.state', '!=', 'cancel')] # force billable type - dashboard_data = self.env['account.analytic.line'].sudo().read_group(dashboard_domain, ['unit_amount', 'timesheet_invoice_type'], ['timesheet_invoice_type']) + dashboard_data = self.env['account.analytic.line'].read_group(dashboard_domain, ['unit_amount', 'timesheet_invoice_type'], ['timesheet_invoice_type']) dashboard_total_hours = sum([data['unit_amount'] for data in dashboard_data]) + total_canceled_hours for data in dashboard_data: billable_type = data['timesheet_invoice_type'] diff --git a/addons/sale_timesheet/models/sale_order.py b/addons/sale_timesheet/models/sale_order.py index cd4a056e719..bf3c405cbd2 100644 --- a/addons/sale_timesheet/models/sale_order.py +++ b/addons/sale_timesheet/models/sale_order.py @@ -65,8 +65,16 @@ class SaleOrder(models.Model): @api.depends('timesheet_ids', 'company_id.timesheet_encode_uom_id') def _compute_timesheet_total_duration(self): for sale_order in self: + duration_list = [] timesheets = sale_order.timesheet_ids if self.user_has_groups('hr_timesheet.group_hr_timesheet_approver') else sale_order.timesheet_ids.filtered(lambda t: t.user_id.id == self.env.uid) - sale_order.timesheet_total_duration = sum([timesheet.unit_amount for timesheet in timesheets]) + for timesheet in timesheets: + timesheet_uom = timesheet.product_uom_id or timesheet.company_id.project_time_mode_id + if timesheet_uom != sale_order.timesheet_encode_uom_id and timesheet_uom.category_id == sale_order.timesheet_encode_uom_id.category_id: + duration_list.append(timesheet_uom._compute_quantity(timesheet.unit_amount, sale_order.timesheet_encode_uom_id)) + else: + duration_list.append(timesheet.unit_amount) + sale_order.timesheet_total_duration = sum(duration_list) + @api.onchange('project_id') def _onchange_project_id(self): @@ -144,18 +152,14 @@ class SaleOrder(models.Model): def _create_invoices(self, grouped=False, final=False, date=None): """ Override the _create_invoice method in sale.order model in sale module - Add new parameter in this method, to invoice sale.order with a date. This date is used in sale_make_invoice_advance_inv into this module. - :param date - :return {account.move}: the invoices created """ moves = super(SaleOrder, self)._create_invoices(grouped, final) moves._link_timesheets_to_invoice(date) return moves - class SaleOrderLine(models.Model): _inherit = "sale.order.line" diff --git a/addons/sale_timesheet/wizard/sale_make_invoice_advance.py b/addons/sale_timesheet/wizard/sale_make_invoice_advance.py index ab4e866dd21..4c74fa9c0a5 100644 --- a/addons/sale_timesheet/wizard/sale_make_invoice_advance.py +++ b/addons/sale_timesheet/wizard/sale_make_invoice_advance.py @@ -6,18 +6,12 @@ class SaleAdvancePaymentInv(models.TransientModel): @api.model def _default_invoicing_timesheet_enabled(self): - product_ids = None - if self._context.get('active_id', False): - sale_order = self.env['sale.order'].browse(self._context.get('active_id')) - product_ids = sale_order.order_line.filtered(lambda sol: sol.invoice_status == 'to invoice').mapped('product_id') - elif self._context.get('active_ids', []): - sale_orders = self.env['sale.order'].browse(self._context.get('active_ids')) - order_lines = sale_orders.mapped('order_line').filtered(lambda sol: sol.invoice_status == 'to invoice') - product_ids = order_lines.mapped('product_id') - - if product_ids: - return any(product._is_delivered_timesheet() for product in product_ids) - return False + if 'active_id' not in self._context and 'active_ids' not in self._context: + return False + sale_orders = self.env['sale.order'].browse(self._context.get('active_id') or self._context.get('active_ids')) + order_lines = sale_orders.mapped('order_line').filtered(lambda sol: sol.invoice_status == 'to invoice') + product_ids = order_lines.mapped('product_id').filtered(lambda p: p._is_delivered_timesheet()) + return bool(product_ids) date_invoice_timesheet = fields.Date( string='Invoice Timesheets Up To This Date',