From e41a8b924c368f4d73d663567a0ace1fefdcbb38 Mon Sep 17 00:00:00 2001 From: "Xavier BOL (xbo)" Date: Thu, 7 Apr 2022 09:41:51 +0000 Subject: [PATCH] [IMP] sale_{project,timesheet}: display price_unit when 2 SOLs for the same service Before this commit, the `name_get` method is override in `sale_timesheet` module to add the price_unit when more than one SOL is shown for the same product in the same Sale Order. This override adds the price unit only when the `with_price_unit` key is in the context, so it is only used in the `sale_line_id` field defined in `project.project` and `project.task` models and also `so_line` field in `analytic.account.line` model when the user wants to set a SOL for those models. However, the Sales section displayed in project right side panel is added in `sale_project` and so we cannot use the override in that module since `sale_timesheet` is not installed when `sale_project`. This commit moves the part concerning the `with_price_unit` of the `name_get` override defined in `sale_timesheet` module to `sale_project` one. And then, we will use the context key in the Sales section to display the price unit when there are more than one SOL for a same product in a same Sales Order. task-2814466 Part-of: odoo/odoo#88219 --- addons/sale_project/models/project.py | 2 +- addons/sale_project/models/sale_order_line.py | 25 ++++++ addons/sale_timesheet/models/sale_order.py | 83 +++++++------------ 3 files changed, 58 insertions(+), 52 deletions(-) diff --git a/addons/sale_project/models/project.py b/addons/sale_project/models/project.py index 1d5f4d694fb..1bd4b91ec7b 100644 --- a/addons/sale_project/models/project.py +++ b/addons/sale_project/models/project.py @@ -272,7 +272,7 @@ class Project(models.Model): return [{ **sol_read, **get_action(sol_read['id']), - } for sol_read in sols.read(['display_name', 'product_uom_qty', 'qty_delivered', 'qty_invoiced', 'product_uom'])] + } for sol_read in sols.with_context(with_price_unit=True).read(['display_name', 'product_uom_qty', 'qty_delivered', 'qty_invoiced', 'product_uom'])] def _get_sale_items_domain(self, additional_domain=None): sale_orders = self._get_sale_order_items().sudo().order_id diff --git a/addons/sale_project/models/sale_order_line.py b/addons/sale_project/models/sale_order_line.py index 8a569491a32..d0607e0aa49 100644 --- a/addons/sale_project/models/sale_order_line.py +++ b/addons/sale_project/models/sale_order_line.py @@ -1,7 +1,10 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from collections import defaultdict + from odoo import api, Command, fields, models, _ +from odoo.tools import format_amount from odoo.tools.sql import column_exists, create_column @@ -18,6 +21,28 @@ class SaleOrderLine(models.Model): is_service = fields.Boolean("Is a Service", compute='_compute_is_service', store=True, compute_sudo=True, help="Sales Order item should generate a task and/or a project, depending on the product settings.") reached_milestones_ids = fields.One2many('project.milestone', 'sale_line_id', string='Reached Milestones', domain=[('is_reached', '=', True)]) + def name_get(self): + res = super().name_get() + with_price_unit = self.env.context.get('with_price_unit') + if with_price_unit: + names = dict(res) + result = [] + sols_by_so_dict = defaultdict(lambda: self.env[self._name]) # key: (sale_order_id, product_id), value: sale order line + for line in self: + sols_by_so_dict[line.order_id.id, line.product_id.id] += line + + for sols in sols_by_so_dict.values(): + if len(sols) > 1 and all(sols.mapped('is_service')): + result += [( + line.id, + '%s - %s' % ( + names.get(line.id), format_amount(self.env, line.price_unit, line.currency_id)) + ) for line in sols] + else: + result += [(line.id, names.get(line.id)) for line in sols] + return result + return res + @api.depends('product_id.type') def _compute_is_service(self): for so_line in self: diff --git a/addons/sale_timesheet/models/sale_order.py b/addons/sale_timesheet/models/sale_order.py index 4eb2d0f747e..eaa07c20fff 100644 --- a/addons/sale_timesheet/models/sale_order.py +++ b/addons/sale_timesheet/models/sale_order.py @@ -6,7 +6,7 @@ from collections import defaultdict from odoo import api, fields, models, _ from odoo.osv import expression -from odoo.tools import float_compare, format_amount +from odoo.tools import float_compare class SaleOrder(models.Model): @@ -125,60 +125,41 @@ class SaleOrderLine(models.Model): def name_get(self): res = super(SaleOrderLine, self).name_get() with_remaining_hours = self.env.context.get('with_remaining_hours') - with_price_unit = self.env.context.get('with_price_unit') - if with_remaining_hours or with_price_unit: + if with_remaining_hours: names = dict(res) result = [] - uom_hour = with_remaining_hours and self.env.ref('uom.product_uom_hour') - uom_day = with_remaining_hours and self.env.ref('uom.product_uom_day') - sols_by_so_dict = with_price_unit and defaultdict(lambda: self.env[self._name]) # key: (sale_order_id, product_id), value: sale order line + uom_hour = self.env.ref('uom.product_uom_hour') + uom_day = self.env.ref('uom.product_uom_day') for line in self: - if with_remaining_hours: - name = names.get(line.id) - if line.remaining_hours_available: - company = self.env.company - encoding_uom = company.timesheet_encode_uom_id - remaining_time = '' - if encoding_uom == uom_hour: - hours, minutes = divmod(abs(line.remaining_hours) * 60, 60) - round_minutes = minutes / 30 - minutes = math.ceil(round_minutes) if line.remaining_hours >= 0 else math.floor(round_minutes) - if minutes > 1: - minutes = 0 - hours += 1 - else: - minutes = minutes * 30 - remaining_time = ' ({sign}{hours:02.0f}:{minutes:02.0f})'.format( - sign='-' if line.remaining_hours < 0 else '', - hours=hours, - minutes=minutes) - elif encoding_uom == uom_day: - remaining_days = company.project_time_mode_id._compute_quantity(line.remaining_hours, encoding_uom, round=False) - remaining_time = ' ({qty:.02f} {unit})'.format( - qty=remaining_days, - unit=_('days') if abs(remaining_days) > 1 else _('day') - ) - name = '{name}{remaining_time}'.format( - name=name, - remaining_time=remaining_time + name = names.get(line.id) + if line.remaining_hours_available: + company = self.env.company + encoding_uom = company.timesheet_encode_uom_id + remaining_time = '' + if encoding_uom == uom_hour: + hours, minutes = divmod(abs(line.remaining_hours) * 60, 60) + round_minutes = minutes / 30 + minutes = math.ceil(round_minutes) if line.remaining_hours >= 0 else math.floor(round_minutes) + if minutes > 1: + minutes = 0 + hours += 1 + else: + minutes = minutes * 30 + remaining_time = ' ({sign}{hours:02.0f}:{minutes:02.0f})'.format( + sign='-' if line.remaining_hours < 0 else '', + hours=hours, + minutes=minutes) + elif encoding_uom == uom_day: + remaining_days = company.project_time_mode_id._compute_quantity(line.remaining_hours, encoding_uom, round=False) + remaining_time = ' ({qty:.02f} {unit})'.format( + qty=remaining_days, + unit=_('days') if abs(remaining_days) > 1 else _('day') ) - if with_price_unit: - names[line.id] = name - if not with_price_unit: - result.append((line.id, name)) - if with_price_unit: - sols_by_so_dict[line.order_id.id, line.product_id.id] += line - - if with_price_unit: - for sols in sols_by_so_dict.values(): - if len(sols) > 1: - result += [( - line.id, - '%s - %s' % ( - names.get(line.id), format_amount(self.env, line.price_unit, line.currency_id)) - ) for line in sols] - else: - result.append((sols.id, names.get(sols.id))) + name = '{name}{remaining_time}'.format( + name=name, + remaining_time=remaining_time + ) + result.append((line.id, name)) return result return res