diff --git a/models/project_overview.py b/models/project_overview.py index f387b516f302daa7be9430f6003859606b73701f..66a0351ad34696a2ab7bcacda72af1e9924fd9d0 100644 --- a/models/project_overview.py +++ b/models/project_overview.py @@ -2,6 +2,8 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from odoo import fields, models, api, _ +import itertools +from odoo.addons.sale_timesheet.models.project_overview import _to_action_data class Project(models.Model): @@ -79,8 +81,6 @@ class Project(models.Model): # ***** Modif Filament ***** rows_sale_line[sale_line_row_key][ -2] = sale_line.product_uom_qty * sale_line.price_unit / sale_line.order_id.taux_horaire if sale_line else 0.0 - # rows_sale_line[sale_line_row_key][-2] = sale_line.product_uom._compute_quantity( - # sale_line.product_uom_qty, uom_hour, raise_if_failure=False) if sale_line else 0.0 rows_sale_line_all_data = {} if not employees: @@ -101,8 +101,6 @@ class Project(models.Model): # ***** Modif Filament ***** rows_sale_line[sale_line_row_key][ -2] = sale_line.product_uom_qty * sale_line.price_unit / sale_line.order_id.taux_horaire if sale_line else 0.0 - # rows_sale_line[sale_line_row_key][-2] = sale_line.product_uom._compute_quantity( - # sale_line.product_uom_qty, uom_hour, raise_if_failure=False) if sale_line else 0.0 if sale_line_row_key not in rows_sale_line_all_data: rows_sale_line_all_data[sale_line_row_key] = [0] * len(row_employee) @@ -155,6 +153,127 @@ class Project(models.Model): 'rows': timesheet_forecast_table_rows } + def _plan_get_stat_button(self): + stat_buttons = [] + num_projects = len(self) + if num_projects == 1: + action_data = _to_action_data('project.project', res_id=self.id, + views=[[self.env.ref('project.edit_project').id, 'form']]) + else: + action_data = _to_action_data(action=self.env.ref('project.open_view_project_all_config').sudo(), + domain=[('id', 'in', self.ids)]) + + stat_buttons.append({ + 'name': _('Project') if num_projects == 1 else _('Projects'), + 'count': num_projects, + 'icon': 'fa fa-puzzle-piece', + 'action': action_data + }) + + # if only one project, add it in the context as default value + tasks_domain = [('project_id', 'in', self.ids)] + tasks_context = self.env.context.copy() + tasks_context.pop('search_default_name', False) + late_tasks_domain = [('project_id', 'in', self.ids), ('date_deadline', '<', fields.Date.to_string(fields.Date.today())), ('date_end', '=', False)] + overtime_tasks_domain = [('project_id', 'in', self.ids), ('overtime', '>', 0), ('planned_hours', '>', 0)] + + if len(self) == 1: + tasks_context = {**tasks_context, 'default_project_id': self.id} + elif len(self): + task_projects_ids = self.env['project.task'].read_group([('project_id', 'in', self.ids)], ['project_id'], ['project_id']) + task_projects_ids = [p['project_id'][0] for p in task_projects_ids] + if len(task_projects_ids) == 1: + tasks_context = {**tasks_context, 'default_project_id': task_projects_ids[0]} + + stat_buttons.append({ + 'name': _('Tasks'), + 'count': sum(self.mapped('task_count')), + 'icon': 'fa fa-tasks', + 'action': _to_action_data( + action=self.env.ref('project.action_view_task').sudo(), + domain=tasks_domain, + context=tasks_context + ) + }) + stat_buttons.append({ + 'name': [_("Tasks"), _("Late")], + 'count': self.env['project.task'].search_count(late_tasks_domain), + 'icon': 'fa fa-tasks', + 'action': _to_action_data( + action=self.env.ref('project.action_view_task').sudo(), + domain=late_tasks_domain, + context=tasks_context, + ), + }) + stat_buttons.append({ + 'name': [_("Tasks"), _("in Overtime")], + 'count': self.env['project.task'].search_count(overtime_tasks_domain), + 'icon': 'fa fa-tasks', + 'action': _to_action_data( + action=self.env.ref('project.action_view_task').sudo(), + domain=overtime_tasks_domain, + context=tasks_context, + ), + }) + + if self.env.user.has_group('sales_team.group_sale_salesman_all_leads'): + # read all the sale orders linked to the projects' tasks + task_so_ids = self.env['project.task'].search_read([ + ('project_id', 'in', self.ids), ('sale_order_id', '!=', False) + ], ['sale_order_id']) + task_so_ids = [o['sale_order_id'][0] for o in task_so_ids] + + sale_ids = self.env['sale.order'].search([('project_id', 'in', self.ids)]) + sale_orders = self.mapped('sale_line_id.order_id') | self.env['sale.order'].browse(task_so_ids) | sale_ids + if sale_orders: + stat_buttons.append({ + 'name': _('Sales Orders'), + 'count': len(sale_orders), + 'icon': 'fa fa-dollar', + 'action': _to_action_data( + action=self.env.ref('sale.action_orders').sudo(), + domain=[('id', 'in', sale_orders.ids)], + context={'create': False, 'edit': False, 'delete': False} + ) + }) + + invoice_ids = self.env['sale.order'].search_read([('id', 'in', sale_orders.ids)], ['invoice_ids']) + invoice_ids = list(itertools.chain(*[i['invoice_ids'] for i in invoice_ids])) + invoice_ids = self.env['account.move'].search_read([('id', 'in', invoice_ids), ('move_type', '=', 'out_invoice')], ['id']) + invoice_ids = list(map(lambda x: x['id'], invoice_ids)) + + if invoice_ids: + stat_buttons.append({ + 'name': _('Invoices'), + 'count': len(invoice_ids), + 'icon': 'fa fa-pencil-square-o', + 'action': _to_action_data( + action=self.env.ref('account.action_move_out_invoice_type').sudo(), + domain=[('id', 'in', invoice_ids), ('move_type', '=', 'out_invoice')], + context={'create': False, 'delete': False} + ) + }) + + ts_tree = self.env.ref('hr_timesheet.hr_timesheet_line_tree') + ts_form = self.env.ref('hr_timesheet.hr_timesheet_line_form') + if self.env.company.timesheet_encode_uom_id == self.env.ref('uom.product_uom_day'): + timesheet_label = [_('Days'), _('Recorded')] + else: + timesheet_label = [_('Hours'), _('Recorded')] + + stat_buttons.append({ + 'name': timesheet_label, + 'count': sum(self.mapped('total_timesheet_time')), + 'icon': 'fa fa-calendar', + 'action': _to_action_data( + 'account.analytic.line', + domain=[('project_id', 'in', self.ids)], + views=[(ts_tree.id, 'list'), (ts_form.id, 'form')], + ) + }) + + return stat_buttons + # ------------------------------------------------------ # Actions # ------------------------------------------------------ diff --git a/models/sale_order.py b/models/sale_order.py index 082944379d287c47fcc215d6408a67b52f66999e..16e001b8482f79195dd517fce0db3b19aae16158 100644 --- a/models/sale_order.py +++ b/models/sale_order.py @@ -8,8 +8,8 @@ from odoo.exceptions import ValidationError class SaleOrder(models.Model): _inherit = "sale.order" - project_id_linked = fields.Many2one('project.project', string='Projet associé', - domain=[('allow_timesheets', '=', True), ('active', '=', True)]) + # project_id_linked = fields.Many2one('project.project', string='Projet associé', + # domain=[('allow_timesheets', '=', True), ('active', '=', True)]) project_name_to_create = fields.Char("Nom du projet") project_tracking = fields.Selection([ @@ -30,7 +30,7 @@ class SaleOrder(models.Model): if not so_line_new_project_with_tasks: self.project_tracking = False elif so_line_new_project_with_tasks and self.partner_id: - if not self.project_id_linked: + if not self.project_id: self.project_tracking = "new" if not self.project_name_to_create: self.project_name_to_create = self.partner_id.name + str(' - ') @@ -42,7 +42,7 @@ class SaleOrder(models.Model): if self.project_tracking == "link": self.project_name_to_create = None elif self.project_tracking == "new": - self.project_id_linked = None + self.project_id = None if not self.project_name_to_create: so_line_new_project_with_tasks = self.mapped('order_line').filtered( lambda sol: sol.is_service and sol.product_id.service_tracking == 'task_in_project') @@ -54,15 +54,15 @@ class SaleOrder(models.Model): # car on laisse le fonctionnement natif pour les articles où on crée le projet sans les tâches so_line_new_project_with_tasks = self.mapped('order_line').filtered( lambda sol: sol.is_service and sol.product_id.service_tracking == 'task_in_project') - if (so_line_new_project_with_tasks and not self.project_id_linked) and ( + if (so_line_new_project_with_tasks and not self.project_id) and ( so_line_new_project_with_tasks and not self.project_name_to_create): raise ValidationError( "Pas de projet associé à ce devis : merci de choisir un projet ou d'en créer un nouveau") - elif so_line_new_project_with_tasks and self.project_id_linked and self.project_name_to_create: + elif so_line_new_project_with_tasks and self.project_id and self.project_name_to_create: raise ValidationError( "Vous ne pouvez pas créer un nouveau projet et associer à un projet existant en même temps : merci de " "vérifier le projet que vous voulez associer à ce devis") # cas normalement géré par le xml - elif not so_line_new_project_with_tasks and (self.project_id_linked or self.project_name_to_create): + elif not so_line_new_project_with_tasks and (self.project_id or self.project_name_to_create): raise ValidationError( "Vous ne pouvez pas associer de projet à ce devis : tous les articles sont déjà liés à des projets " "existants") # cas normalement géré par le xml @@ -158,7 +158,7 @@ class SaleOrderLine(models.Model): project = so_line.project_id # on récupère les variables de classes créées no_create_task = so_line.order_id.no_create_task - project_linked = so_line.order_id.project_id_linked + project_linked = so_line.order_id.project_id new_project_name = so_line.order_id.project_name_to_create if not project and _can_create_project(so_line): if project_linked: # si on a choisi un projet .. diff --git a/views/sale_view.xml b/views/sale_view.xml index 320c68e56ef66098059a77a8aef36e06dd8a6d17..5176ee505ab2723f8ba6a789169ba6fa4ce9d1b1 100644 --- a/views/sale_view.xml +++ b/views/sale_view.xml @@ -17,7 +17,7 @@ <field name="project_ids" string="Projets associés" attrs="{'invisible':[('state','!=','sale')]}"/> <field name="project_tracking" widget="radio" attrs="{'invisible':['|',('state','=','sale'),('project_tracking','=',False)]}"/> - <field name="project_id_linked" attrs="{'invisible':['|',('project_tracking','!=','link'),('state','=','sale')]}" options="{'no_quick_create': True, 'no_create_edit' : True}"/> +<!-- <field name="project_id_linked" attrs="{'invisible':['|',('project_tracking','!=','link'),('state','=','sale')]}" options="{'no_quick_create': True, 'no_create_edit' : True}"/>--> <field name="project_name_to_create" attrs="{'invisible':['|',('project_tracking','!=','new'),('state','=','sale')]}"/> <field name="no_create_task" attrs="{'invisible':['|',('state','=','sale'),('project_tracking','=',False)]}"/>