Currently, Fields Sales are struggling to create quotations or sale orders directly from the customer place. One of the issues is that it is not easy to quickly add products to the SO. Products must be added one by one and, in mobile, it requires completing a form with quantities, ... for each new line. This commit introduces a catalog view that allows users to select products in a faster and easier way than before, both in desktop and mobile view. Task - 3062080 closes odoo/odoo#106382 Related: odoo/enterprise#37915 Signed-off-by: Victor Feyens (vfe) <vfe@odoo.com> Co-authored-by: chevalierv <vcr@odoo.com>
118 lines
4.8 KiB
Python
118 lines
4.8 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
from datetime import timedelta, time
|
|
from odoo.exceptions import UserError
|
|
from odoo import api, fields, models, _
|
|
from odoo.tools.float_utils import float_round
|
|
|
|
|
|
class ProductProduct(models.Model):
|
|
_inherit = 'product.product'
|
|
|
|
sales_count = fields.Float(compute='_compute_sales_count', string='Sold', digits='Product Unit of Measure')
|
|
|
|
# Catalog related fields
|
|
product_catalog_product_is_in_sale_order = fields.Boolean(
|
|
compute='_compute_product_is_in_sale_order',
|
|
search='_search_product_is_in_sale_order',
|
|
)
|
|
|
|
def _compute_sales_count(self):
|
|
r = {}
|
|
self.sales_count = 0
|
|
if not self.user_has_groups('sales_team.group_sale_salesman'):
|
|
return r
|
|
date_from = fields.Datetime.to_string(fields.datetime.combine(fields.datetime.now() - timedelta(days=365),
|
|
time.min))
|
|
|
|
done_states = self.env['sale.report']._get_done_states()
|
|
|
|
domain = [
|
|
('state', 'in', done_states),
|
|
('product_id', 'in', self.ids),
|
|
('date', '>=', date_from),
|
|
]
|
|
for product, product_uom_qty in self.env['sale.report']._read_group(domain, ['product_id'], ['product_uom_qty:sum']):
|
|
r[product.id] = product_uom_qty
|
|
for product in self:
|
|
if not product.id:
|
|
product.sales_count = 0.0
|
|
continue
|
|
product.sales_count = float_round(r.get(product.id, 0), precision_rounding=product.uom_id.rounding)
|
|
return r
|
|
|
|
@api.onchange('type')
|
|
def _onchange_type(self):
|
|
if self._origin and self.sales_count > 0:
|
|
return {'warning': {
|
|
'title': _("Warning"),
|
|
'message': _("You cannot change the product's type because it is already used in sales orders.")
|
|
}}
|
|
|
|
@api.depends_context('order_id')
|
|
def _compute_product_is_in_sale_order(self):
|
|
order_id = self.env.context.get('order_id')
|
|
if not order_id:
|
|
self.product_catalog_product_is_in_sale_order = False
|
|
return
|
|
|
|
read_group_data = self.env['sale.order.line']._read_group(
|
|
domain=[('order_id', '=', order_id)],
|
|
groupby=['product_id'],
|
|
aggregates=['__count'],
|
|
)
|
|
data = {product.id: count for product, count in read_group_data}
|
|
for product in self:
|
|
product.product_catalog_product_is_in_sale_order = bool(data.get(product.id, 0))
|
|
|
|
def _search_product_is_in_sale_order(self, operator, value):
|
|
if operator not in ['=', '!='] or not isinstance(value, bool):
|
|
raise UserError(_("Operation not supported"))
|
|
product_ids = self.env['sale.order.line'].search([
|
|
('order_id', 'in', [self.env.context.get('order_id', '')]),
|
|
]).product_id.ids
|
|
return [('id', 'in', product_ids)]
|
|
|
|
def action_view_sales(self):
|
|
action = self.env["ir.actions.actions"]._for_xml_id("sale.report_all_channels_sales_action")
|
|
action['domain'] = [('product_id', 'in', self.ids)]
|
|
action['context'] = {
|
|
'pivot_measures': ['product_uom_qty'],
|
|
'active_id': self._context.get('active_id'),
|
|
'search_default_Sales': 1,
|
|
'active_model': 'sale.report',
|
|
'search_default_filter_order_date': 1,
|
|
}
|
|
return action
|
|
|
|
def _get_invoice_policy(self):
|
|
return self.invoice_policy
|
|
|
|
def _get_combination_info_variant(self, add_qty=1, pricelist=False, parent_combination=False):
|
|
"""Return the variant info based on its combination.
|
|
See `_get_combination_info` for more information.
|
|
"""
|
|
self.ensure_one()
|
|
return self.product_tmpl_id._get_combination_info(self.product_template_attribute_value_ids, self.id, add_qty, pricelist, parent_combination)
|
|
|
|
def _filter_to_unlink(self):
|
|
domain = [('product_id', 'in', self.ids)]
|
|
lines = self.env['sale.order.line']._read_group(domain, ['product_id'])
|
|
linked_product_ids = [product.id for [product] in lines]
|
|
return super(ProductProduct, self - self.browse(linked_product_ids))._filter_to_unlink()
|
|
|
|
|
|
class ProductAttributeCustomValue(models.Model):
|
|
_inherit = "product.attribute.custom.value"
|
|
|
|
sale_order_line_id = fields.Many2one('sale.order.line', string="Sales Order Line", required=True, ondelete='cascade')
|
|
|
|
_sql_constraints = [
|
|
('sol_custom_value_unique', 'unique(custom_product_template_attribute_value_id, sale_order_line_id)', "Only one Custom Value is allowed per Attribute Value per Sales Order Line.")
|
|
]
|
|
|
|
class ProductPackaging(models.Model):
|
|
_inherit = 'product.packaging'
|
|
|
|
sales = fields.Boolean("Sales", default=True, help="If true, the packaging can be used for sales orders")
|