+
@@ -310,6 +310,7 @@
+
@@ -322,6 +323,7 @@
product.product
form
kanban,tree,form
+
{"default_can_be_expensed": 1, 'default_type': 'service'}
[('can_be_expensed', '=', True)]
diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py
index 758cd1a2953..35b18f29866 100644
--- a/addons/mrp/models/mrp_production.py
+++ b/addons/mrp/models/mrp_production.py
@@ -62,6 +62,7 @@ class MrpProduction(models.Model):
'uom.uom', 'Product Unit of Measure',
oldname='product_uom', readonly=True, required=True,
states={'confirmed': [('readonly', False)]})
+ product_uom_qty = fields.Float(string='Total Quantity', compute='_compute_product_uom_qty', store=True)
picking_type_id = fields.Many2one(
'stock.picking.type', 'Operation Type',
default=_get_default_picking_type, required=True)
@@ -83,7 +84,7 @@ class MrpProduction(models.Model):
states={'confirmed': [('readonly', False)]}, oldname="date_planned")
date_planned_finished = fields.Datetime(
'Deadline End', copy=False, default=fields.Datetime.now,
- index=True,
+ index=True,
states={'confirmed': [('readonly', False)]})
date_start = fields.Datetime('Start Date', copy=False, index=True, readonly=True)
date_finished = fields.Datetime('End Date', copy=False, index=True, readonly=True)
@@ -99,11 +100,11 @@ class MrpProduction(models.Model):
"work centers based on production planning.")
move_raw_ids = fields.One2many(
'stock.move', 'raw_material_production_id', 'Raw Materials', oldname='move_lines',
- copy=False, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]},
+ copy=False, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]},
domain=[('scrapped', '=', False)])
move_finished_ids = fields.One2many(
'stock.move', 'production_id', 'Finished Products',
- copy=False, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]},
+ copy=False, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]},
domain=[('scrapped', '=', False)])
finished_move_line_ids = fields.One2many(
'stock.move.line', compute='_compute_lines', inverse='_inverse_lines', string="Finished Product"
@@ -146,7 +147,7 @@ class MrpProduction(models.Model):
default=lambda self: self.env['res.company']._company_default_get('mrp.production'),
required=True)
- check_to_done = fields.Boolean(compute="_get_produced_qty", string="Check Produced Qty",
+ check_to_done = fields.Boolean(compute="_get_produced_qty", string="Check Produced Qty",
help="Technical Field to see if we can show 'Mark as Done' button")
qty_produced = fields.Float(compute="_get_produced_qty", string="Quantity Produced")
procurement_group_id = fields.Many2one(
@@ -164,6 +165,14 @@ class MrpProduction(models.Model):
show_final_lots = fields.Boolean('Show Final Lots', compute='_compute_show_lots')
production_location_id = fields.Many2one('stock.location', "Production Location", related='product_id.property_stock_production')
+ @api.depends('product_uom_id', 'product_qty', 'product_id.uom_id')
+ def _compute_product_uom_qty(self):
+ for production in self:
+ if production.product_id.uom_id != production.product_uom_id:
+ production.product_uom_qty = production.product_uom_id._compute_quantity(production.product_qty, production.product_id.uom_id)
+ else:
+ production.product_uom_qty = production.product_qty
+
@api.depends('product_id.tracking')
def _compute_show_lots(self):
for production in self:
@@ -648,7 +657,7 @@ class MrpProduction(models.Model):
action = self.env.ref('stock.action_stock_scrap').read()[0]
action['domain'] = [('production_id', '=', self.id)]
return action
-
+
@api.model
def get_empty_list_help(self, help):
self = self.with_context(
diff --git a/addons/mrp/models/product.py b/addons/mrp/models/product.py
index 6d9c7d7ffee..d682efc18dd 100644
--- a/addons/mrp/models/product.py
+++ b/addons/mrp/models/product.py
@@ -1,7 +1,9 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from datetime import timedelta
from odoo import api, fields, models
+from odoo.tools.float_utils import float_round
class ProductTemplate(models.Model):
@@ -11,7 +13,7 @@ class ProductTemplate(models.Model):
bom_ids = fields.One2many('mrp.bom', 'product_tmpl_id', 'Bill of Materials')
bom_count = fields.Integer('# Bill of Material', compute='_compute_bom_count')
used_in_bom_count = fields.Integer('# of BoM Where is Used', compute='_compute_used_in_bom_count')
- mo_count = fields.Integer('# Manufacturing Orders', compute='_compute_mo_count')
+ mrp_product_qty = fields.Float('Manufactured', compute='_compute_mrp_product_qty')
produce_delay = fields.Float(
'Manufacturing Lead Time', default=0.0,
help="Average lead time in days to manufacture this product. In the case of multi-level BOM, the manufacturing lead times of the components will be added.")
@@ -34,16 +36,18 @@ class ProductTemplate(models.Model):
return action
@api.one
- def _compute_mo_count(self):
- # TDE FIXME: directly use a read_group
- self.mo_count = sum(self.mapped('product_variant_ids').mapped('mo_count'))
+ def _compute_mrp_product_qty(self):
+ self.mrp_product_qty = float_round(sum(self.mapped('product_variant_ids').mapped('mrp_product_qty')), precision_rounding=self.uom_id.rounding)
@api.multi
def action_view_mos(self):
- product_ids = self.mapped('product_variant_ids').ids
- action = self.env.ref('mrp.act_product_mrp_production').read()[0]
- action['domain'] = [('product_id', 'in', product_ids)]
- action['context'] = {}
+ action = self.env.ref('mrp.mrp_production_report').read()[0]
+ action['domain'] = [('state', '=', 'done'), '&', ('product_tmpl_id', 'in', self.ids)]
+ action['context'] = {
+ 'search_default_last_year_mo_order': 1,
+ 'search_default_status': 1, 'search_default_scheduled_month': 1,
+ 'graph_measure': 'product_uom_qty',
+ }
return action
@@ -54,7 +58,7 @@ class ProductProduct(models.Model):
bom_line_ids = fields.One2many('mrp.bom.line', 'product_id', 'BoM Components')
bom_count = fields.Integer('# Bill of Material', compute='_compute_bom_count')
used_in_bom_count = fields.Integer('# BoM Where Used', compute='_compute_used_in_bom_count')
- mo_count = fields.Integer('# Manufacturing Orders', compute='_compute_mo_count')
+ mrp_product_qty = fields.Float('Manufactured', compute='_compute_mrp_product_qty')
def _compute_bom_count(self):
for product in self:
@@ -72,11 +76,14 @@ class ProductProduct(models.Model):
action['domain'] = [('bom_line_ids.product_id', '=', self.id)]
return action
- def _compute_mo_count(self):
- read_group_res = self.env['mrp.production'].read_group([('product_id', 'in', self.ids)], ['product_id'], ['product_id'])
- mapped_data = dict([(data['product_id'][0], data['product_id_count']) for data in read_group_res])
+ def _compute_mrp_product_qty(self):
+ date_from = fields.Datetime.to_string(fields.datetime.now() - timedelta(days=365))
+ #TODO: state = done?
+ domain = [('state', '=', 'done'), ('product_id', 'in', self.ids), ('date_planned_start', '>', date_from)]
+ read_group_res = self.env['mrp.production'].read_group(domain, ['product_id', 'product_uom_qty'], ['product_id'])
+ mapped_data = dict([(data['product_id'][0], data['product_uom_qty']) for data in read_group_res])
for product in self:
- product.mo_count = mapped_data.get(product.id, 0)
+ product.mrp_product_qty = float_round(mapped_data.get(product.id, 0), precision_rounding=product.uom_id.rounding)
@api.multi
def action_view_bom(self):
@@ -89,3 +96,14 @@ class ProductProduct(models.Model):
}
action['domain'] = ['|', ('product_id', 'in', self.ids), '&', ('product_id', '=', False), ('product_tmpl_id', 'in', template_ids)]
return action
+
+ @api.multi
+ def action_view_mos(self):
+ action = self.env.ref('mrp.mrp_production_report').read()[0]
+ action['domain'] = [('state', '=', 'done'), '&', ('product_id', 'in', self.ids)]
+ action['context'] = {
+ 'search_default_last_year_mo_order': 1,
+ 'search_default_status': 1, 'search_default_scheduled_month': 1,
+ 'graph_measure': 'product_uom_qty',
+ }
+ return action
diff --git a/addons/mrp/views/mrp_production_views.xml b/addons/mrp/views/mrp_production_views.xml
index 5687f10c605..63036ccb27e 100644
--- a/addons/mrp/views/mrp_production_views.xml
+++ b/addons/mrp/views/mrp_production_views.xml
@@ -260,6 +260,10 @@
+
+
@@ -267,9 +271,6 @@
-
-
@@ -281,8 +282,8 @@
-
-
+
+
diff --git a/addons/mrp/views/product_views.xml b/addons/mrp/views/product_views.xml
index 87de4ef56f9..26ccee7c8ec 100644
--- a/addons/mrp/views/product_views.xml
+++ b/addons/mrp/views/product_views.xml
@@ -15,42 +15,59 @@
-
- product.template.search.bom
- product.template
-
-
+
+
+ mrp.product.template.search
+ product.template
+ primary
+
+
-
+
-
- product.form.mrp.inherited
+
+
+ mrp.product.product.search
product.product
+ primary
-
-
+
+
+
+
+
Products
product.template
form
+
kanban,tree,form
{"search_default_consumable": 1, 'default_type': 'product'}
+
+
+ Product Variants
+ product.product
+ form
+
+ kanban,tree,form
+
+
@@ -70,8 +87,14 @@
@@ -92,9 +115,15 @@
attrs="{'invisible':['|',('type', 'not in', ['product', 'consu']), ('used_in_bom_count', '=', 0)]}" icon="fa-level-up">
-
diff --git a/addons/point_of_sale/models/product.py b/addons/point_of_sale/models/product.py
index 9febe9e985b..609b220dbdd 100644
--- a/addons/point_of_sale/models/product.py
+++ b/addons/point_of_sale/models/product.py
@@ -7,7 +7,7 @@ from odoo.exceptions import UserError
class ProductTemplate(models.Model):
_inherit = 'product.template'
- available_in_pos = fields.Boolean(string='Available in Point of Sale', help='Check if you want this product to appear in the Point of Sale', default=False)
+ available_in_pos = fields.Boolean(string='Available in POS', help='Check if you want this product to appear in the Point of Sale', default=False)
to_weight = fields.Boolean(string='To Weigh With Scale', help="Check if the product should be weighted using the hardware scale integration")
pos_categ_id = fields.Many2one(
'pos.category', string='Point of Sale Category',
diff --git a/addons/point_of_sale/views/product_view.xml b/addons/point_of_sale/views/product_view.xml
index 96f92c5d7be..6e0a4846732 100644
--- a/addons/point_of_sale/views/product_view.xml
+++ b/addons/point_of_sale/views/product_view.xml
@@ -52,13 +52,17 @@
product.template
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/product/models/product_template.py b/addons/product/models/product_template.py
index f307c599971..7638db145b9 100644
--- a/addons/product/models/product_template.py
+++ b/addons/product/models/product_template.py
@@ -103,6 +103,7 @@ class ProductTemplate(models.Model):
'uom.uom', 'Unit of Measure',
default=_get_default_uom_id, required=True,
help="Default Unit of Measure used for all stock operation.")
+ uom_name = fields.Char(string='Unit of Measure Name', related='uom_id.name', readonly=True)
uom_po_id = fields.Many2one(
'uom.uom', 'Purchase Unit of Measure',
default=_get_default_uom_id, required=True,
diff --git a/addons/product/views/product_template_views.xml b/addons/product/views/product_template_views.xml
index abcfc5c8327..8aa451f6f55 100644
--- a/addons/product/views/product_template_views.xml
+++ b/addons/product/views/product_template_views.xml
@@ -107,6 +107,7 @@
kanban,tree,form
form
+
{"search_default_filter_to_sell":1}
diff --git a/addons/product/views/product_views.xml b/addons/product/views/product_views.xml
index 61c7bba55f2..272f979acc4 100644
--- a/addons/product/views/product_views.xml
+++ b/addons/product/views/product_views.xml
@@ -60,7 +60,40 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -72,41 +105,7 @@
m³
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
product.template
-
- Email
-
-
-
+
+ Automatic Email at Invoice
+ 0
+
+
-
+
diff --git a/addons/purchase/data/purchase_demo.xml b/addons/purchase/data/purchase_demo.xml
index 0d03ed90533..b4712166e2c 100644
--- a/addons/purchase/data/purchase_demo.xml
+++ b/addons/purchase/data/purchase_demo.xml
@@ -66,7 +66,7 @@
'name': 'Support Services',
'price_unit': 25.50,
'product_qty': 10.0,
- 'product_uom': ref('uom.product_uom_unit'),
+ 'product_uom': ref('uom.product_uom_hour'),
'date_planned': time.strftime('%Y-%m-%d')}),
]"/>
diff --git a/addons/purchase/models/product.py b/addons/purchase/models/product.py
index fbff07dca89..9f502d2c88a 100644
--- a/addons/purchase/models/product.py
+++ b/addons/purchase/models/product.py
@@ -1,24 +1,23 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from datetime import timedelta
+
from odoo import api, fields, models, _
from odoo.addons.base.models.res_partner import WARNING_MESSAGE, WARNING_HELP
+from odoo.tools.float_utils import float_round
class ProductTemplate(models.Model):
_name = 'product.template'
_inherit = 'product.template'
- @api.multi
- def _purchase_count(self):
- for template in self:
- template.purchase_count = sum([p.purchase_count for p in template.product_variant_ids])
- return True
-
property_account_creditor_price_difference = fields.Many2one(
'account.account', string="Price Difference Account", company_dependent=True,
- help="This account will be used to value price difference between purchase price and cost price.")
- purchase_count = fields.Integer(compute='_purchase_count', string='# Purchases')
+ help="This account is used in automated inventory valuation to "\
+ "record the price difference between a purchase order and its related vendor bill "\
+ "when validating this vendor bill.")
+ purchased_product_qty = fields.Float(compute='_compute_purchased_product_qty', string='Purchased')
purchase_method = fields.Selection([
('purchase', 'On ordered quantities'),
('receive', 'On received quantities'),
@@ -27,6 +26,11 @@ class ProductTemplate(models.Model):
purchase_line_warn = fields.Selection(WARNING_MESSAGE, 'Purchase Order Line', help=WARNING_HELP, required=True, default="no-message")
purchase_line_warn_msg = fields.Text('Message for Purchase Order Line')
+ @api.multi
+ def _compute_purchased_product_qty(self):
+ for template in self:
+ template.purchased_product_qty = float_round(sum([p.purchased_product_qty for p in template.product_variant_ids]), precision_rounding=template.uom_id.rounding)
+
@api.model
def get_import_templates(self):
res = super(ProductTemplate, self).get_import_templates()
@@ -37,22 +41,48 @@ class ProductTemplate(models.Model):
}]
return res
+ @api.multi
+ def action_view_po(self):
+ action = self.env.ref('purchase.action_purchase_order_report_all').read()[0]
+ action['domain'] = [('state', 'in', ['purchase', 'done']), '&', ('product_tmpl_id', 'in', self.ids)]
+ action['context'] = {
+ 'search_default_last_year_purchase': 1,
+ 'search_default_status': 1, 'search_default_order_month': 1,
+ 'graph_measure': 'unit_quantity'
+ }
+ return action
+
class ProductProduct(models.Model):
_name = 'product.product'
_inherit = 'product.product'
+ purchased_product_qty = fields.Float(compute='_compute_purchased_product_qty', string='Purchased')
+
@api.multi
- def _purchase_count(self):
+ def _compute_purchased_product_qty(self):
+ date_from = fields.Datetime.to_string(fields.datetime.now() - timedelta(days=365))
domain = [
('state', 'in', ['purchase', 'done']),
('product_id', 'in', self.mapped('id')),
+ ('date_order', '>', date_from)
]
PurchaseOrderLines = self.env['purchase.order.line'].search(domain)
+ order_lines = self.env['purchase.order.line'].read_group(domain, ['product_id', 'product_uom_qty'], ['product_id'])
+ purchased_data = dict([(data['product_id'][0], data['product_uom_qty']) for data in order_lines])
for product in self:
- product.purchase_count = len(PurchaseOrderLines.filtered(lambda r: r.product_id == product).mapped('order_id'))
+ product.purchased_product_qty = float_round(purchased_data.get(product.id, 0), precision_rounding=product.uom_id.rounding)
- purchase_count = fields.Integer(compute='_purchase_count', string='# Purchases')
+ @api.multi
+ def action_view_po(self):
+ action = self.env.ref('purchase.action_purchase_order_report_all').read()[0]
+ action['domain'] = [('state', 'in', ['purchase', 'done']), '&', ('product_id', 'in', self.ids)]
+ action['context'] = {
+ 'search_default_last_year_purchase': 1,
+ 'search_default_status': 1, 'search_default_order_month': 1,
+ 'graph_measure': 'unit_quantity'
+ }
+ return action
class ProductCategory(models.Model):
diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py
index 30969fa29cd..4ce0b837396 100644
--- a/addons/purchase/models/purchase.py
+++ b/addons/purchase/models/purchase.py
@@ -405,6 +405,7 @@ class PurchaseOrderLine(models.Model):
name = fields.Text(string='Description', required=True)
sequence = fields.Integer(string='Sequence', default=10)
product_qty = fields.Float(string='Quantity', digits=dp.get_precision('Product Unit of Measure'), required=True)
+ product_uom_qty = fields.Float(string='Total Quantity', compute='_compute_product_uom_qty', store=True)
date_planned = fields.Datetime(string='Scheduled Date', required=True, index=True)
taxes_id = fields.Many2many('account.tax', string='Taxes', domain=['|', ('active', '=', False), ('active', '=', True)])
product_uom = fields.Many2one('uom.uom', string='Product Unit of Measure', required=True)
@@ -610,6 +611,15 @@ class PurchaseOrderLine(models.Model):
self.price_unit = price_unit
+ @api.multi
+ @api.depends('product_uom', 'product_qty', 'product_id.uom_id')
+ def _compute_product_uom_qty(self):
+ for line in self:
+ if line.product_id.uom_id != line.product_uom:
+ line.product_uom_qty = line.product_uom._compute_quantity(line.product_qty, line.product_id.uom_id)
+ else:
+ line.product_uom_qty = line.product_qty
+
def _suggest_quantity(self):
'''
Suggest a minimal quantity based on the seller
diff --git a/addons/purchase/report/purchase_report_views.xml b/addons/purchase/report/purchase_report_views.xml
index 5bc8256ee7b..ec9b5005212 100644
--- a/addons/purchase/report/purchase_report_views.xml
+++ b/addons/purchase/report/purchase_report_views.xml
@@ -56,6 +56,8 @@
+
@@ -70,10 +72,10 @@
+
-
diff --git a/addons/purchase/views/product_views.xml b/addons/purchase/views/product_views.xml
index 5fb164080a1..ad0350f7cdb 100644
--- a/addons/purchase/views/product_views.xml
+++ b/addons/purchase/views/product_views.xml
@@ -8,6 +8,9 @@
product.template
+
+ 0
+
@@ -47,15 +50,6 @@
-
- {}
- [('product_id.product_tmpl_id','in',active_ids), ('state', 'in', ['purchase', 'done'])]
- Purchase Order Lines
- purchase.order.line
- tree
-
-
-
product.template.account.purchase.ok.form.inherit
product.template
@@ -78,10 +72,16 @@
-
-
-
+
+
+
+
+
+
+ Purchased
+
+
@@ -97,4 +97,25 @@
+
+ product.product.purchase.order
+ product.product
+
+
+
+
+
+
+
+
+
+
+ Purchased
+
+
+
+
+
+
diff --git a/addons/purchase/views/purchase_views.xml b/addons/purchase/views/purchase_views.xml
index cbe5485d275..425fd472087 100644
--- a/addons/purchase/views/purchase_views.xml
+++ b/addons/purchase/views/purchase_views.xml
@@ -56,7 +56,7 @@
form
kanban,tree,form
{"search_default_filter_to_purchase":1, "purchase_product_template": 1}
-
+
@@ -77,6 +77,7 @@
product.product
tree,kanban,form
form
+
{"search_default_filter_to_purchase": 1}
diff --git a/addons/sale/__manifest__.py b/addons/sale/__manifest__.py
index 915261dfa62..a5ff679d745 100644
--- a/addons/sale/__manifest__.py
+++ b/addons/sale/__manifest__.py
@@ -17,6 +17,7 @@ This module contains all the common features of Sales Management and eCommerce.
'report/sale_report_views.xml',
'report/sale_report_templates.xml',
'report/invoice_report_templates.xml',
+ 'report/report_all_channels_sales_views.xml',
'data/ir_sequence_data.xml',
'data/sale_data.xml',
'data/mail_data.xml',
diff --git a/addons/sale/models/product_product.py b/addons/sale/models/product_product.py
index a3e569e1852..4061ecea987 100644
--- a/addons/sale/models/product_product.py
+++ b/addons/sale/models/product_product.py
@@ -1,28 +1,44 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from datetime import timedelta
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')
+
@api.multi
- def _sales_count(self):
+ def _compute_sales_count(self):
r = {}
if not self.user_has_groups('sales_team.group_sale_salesman'):
return r
+
+ date_from = fields.Datetime.to_string(fields.datetime.now() - timedelta(days=365))
domain = [
('state', 'in', ['sale', 'done']),
('product_id', 'in', self.ids),
+ ('date', '>', date_from)
]
for group in self.env['sale.report'].read_group(domain, ['product_id', 'product_uom_qty'], ['product_id']):
r[group['product_id'][0]] = group['product_uom_qty']
for product in self:
- product.sales_count = r.get(product.id, 0)
+ product.sales_count = float_round(r.get(product.id, 0), precision_rounding=product.uom_id.rounding)
return r
- sales_count = fields.Integer(compute='_sales_count', string='# Sales')
+ @api.multi
+ def action_view_sales(self):
+ action = self.env.ref('sale.report_all_channels_sales_action').read()[0]
+ action['domain'] = [('product_id', 'in', self.ids)]
+ action['context'] = {
+ 'search_default_last_year': 1,
+ 'pivot_measures': ['product_qty'],
+ 'search_default_team_id': 1
+ }
+ return action
def _get_invoice_policy(self):
return self.invoice_policy
diff --git a/addons/sale/models/product_template.py b/addons/sale/models/product_template.py
index 98edf5eb28c..df801fadabe 100644
--- a/addons/sale/models/product_template.py
+++ b/addons/sale/models/product_template.py
@@ -3,6 +3,7 @@
from odoo import api, fields, models, _
from odoo.addons.base.models.res_partner import WARNING_MESSAGE, WARNING_HELP
+from odoo.tools.float_utils import float_round
class ProductTemplate(models.Model):
@@ -18,33 +19,15 @@ class ProductTemplate(models.Model):
expense_policy = fields.Selection(
[('no', 'No'), ('cost', 'At cost'), ('sales_price', 'Sales price')],
string='Re-Invoice Policy',
- default='no')
-
- @api.multi
- @api.depends('product_variant_ids.sales_count')
- def _sales_count(self):
- for product in self:
- product.sales_count = sum([p.sales_count for p in product.with_context(active_test=False).product_variant_ids])
-
- @api.multi
- def action_view_sales(self):
- self.ensure_one()
- action = self.env.ref('sale.action_product_sale_list')
- product_ids = self.with_context(active_test=False).product_variant_ids.ids
-
- return {
- 'name': action.name,
- 'help': action.help,
- 'type': action.type,
- 'view_type': action.view_type,
- 'view_mode': action.view_mode,
- 'target': action.target,
- 'context': "{'default_product_id': " + str(product_ids[0]) + "}",
- 'res_model': action.res_model,
- 'domain': [('state', 'in', ['sale', 'done']), ('product_id.product_tmpl_id', '=', self.id)],
- }
-
- sales_count = fields.Integer(compute='_sales_count', string='# Sales')
+ default='no',
+ help="Expenses registered in Expense app or in a vendor bill (with an analytic account set) "
+ "can be automatically re-invoiced to the customer. This is useful for customer projects. "
+ "With this option activated, the validated expense is added to the sales order linked to the "
+ "analytic account, in order to be invoiced. With Project Management, such an analytic account "
+ "can be automatically generated at the order confirmation, if set on the Service Tracking "
+ "option of one product item.")
+ sales_count = fields.Float(compute='_compute_sales_count', string='Sold')
+ hide_expense_policy = fields.Boolean(compute='_compute_hide_expense_policy')
invoice_policy = fields.Selection(
[('order', 'Ordered quantities'),
('delivery', 'Delivered quantities'),
@@ -53,6 +36,29 @@ class ProductTemplate(models.Model):
'Delivered Quantity: Invoiced based on the quantity the vendor delivered (time or deliveries).',
default='order')
+ @api.multi
+ def _compute_hide_expense_policy(self):
+ hide_expense_policy = self.user_has_groups('!analytic.group_analytic_accounting,!project.group_project_user,!hr_expense.group_hr_expense_user')
+ for template in self:
+ template.hide_expense_policy = hide_expense_policy
+
+ @api.multi
+ @api.depends('product_variant_ids.sales_count')
+ def _compute_sales_count(self):
+ for product in self:
+ product.sales_count = float_round(sum([p.sales_count for p in product.with_context(active_test=False).product_variant_ids]), precision_rounding=product.uom_id.rounding)
+
+ @api.multi
+ def action_view_sales(self):
+ action = self.env.ref('sale.report_all_channels_sales_action').read()[0]
+ action['domain'] = [('product_tmpl_id', 'in', self.ids)]
+ action['context'] = {
+ 'search_default_last_year': 1,
+ 'pivot_measures': ['product_qty'],
+ 'search_default_team_id': 1
+ }
+ return action
+
@api.onchange('type')
def _onchange_type(self):
""" Force values to stay consistent with integrity constraints """
diff --git a/addons/sale/report/__init__.py b/addons/sale/report/__init__.py
index 881c37f9e16..90248f477ca 100644
--- a/addons/sale/report/__init__.py
+++ b/addons/sale/report/__init__.py
@@ -3,3 +3,4 @@
from . import sale_report
from . import invoice_report
+from . import report_all_channels_sales
diff --git a/addons/sale/report/report_all_channels_sales_views.xml b/addons/sale/report/report_all_channels_sales_views.xml
index a29bf412a6c..102a8993390 100644
--- a/addons/sale/report/report_all_channels_sales_views.xml
+++ b/addons/sale/report/report_all_channels_sales_views.xml
@@ -17,7 +17,9 @@
+
+
diff --git a/addons/sale/security/ir.model.access.csv b/addons/sale/security/ir.model.access.csv
index 6fef14c849f..eeb1e148252 100644
--- a/addons/sale/security/ir.model.access.csv
+++ b/addons/sale/security/ir.model.access.csv
@@ -49,3 +49,4 @@ access_account_invoice_tax_sale_manager,account.invoice.tax sale manager,accoun
access_account_tax_group_sale_manager,account.tax.group sale manager,account.model_account_tax_group,sales_team.group_sale_salesman,1,0,0,0
access_account_account_sale_manager,account.account sale manager,account.model_account_account,sales_team.group_sale_manager,1,0,0,0
access_mail_activity_type_sale_manager,mail.activity.type.sale.manager,mail.model_mail_activity_type,sales_team.group_sale_manager,1,1,1,1
+access_report_all_channels_sales,access_report_all_channels_sales,model_report_all_channels_sales,sales_team.group_sale_manager,1,0,0,0
diff --git a/addons/sale/views/sale_views.xml b/addons/sale/views/sale_views.xml
index 761333e3920..69f9eabf864 100644
--- a/addons/sale/views/sale_views.xml
+++ b/addons/sale/views/sale_views.xml
@@ -62,6 +62,7 @@
kanban,tree,form
form
+
{"search_default_filter_to_sell":1, "sale_multi_pricelist_product_template": 1}
@@ -871,14 +872,6 @@
-
- Sales Order Lines
- sale.order.line
- tree
- {'search_default_product_id': [active_id], 'default_product_id': active_id}
- [('state', 'in', ['sale', 'done'])]
-
-
product.product.sale.order
product.product
@@ -886,9 +879,15 @@
-
-
+
+
+
+
+
+
+ Sold
+
@@ -909,9 +908,14 @@
-
+ type="object" icon="fa-signal" groups="sales_team.group_sale_salesman" help="Sold in the last 365 days">
+
+
+
+
+
+ Sold
+
@@ -929,17 +933,14 @@
product.template
-
+
+
+
-
-
-
-
-
-
+
diff --git a/addons/sale_expense/views/product_view.xml b/addons/sale_expense/views/product_view.xml
index 10c91862ac8..9387d655943 100644
--- a/addons/sale_expense/views/product_view.xml
+++ b/addons/sale_expense/views/product_view.xml
@@ -12,6 +12,12 @@
+
+
+
+
+
+
diff --git a/addons/sale_management/views/sale_management_views.xml b/addons/sale_management/views/sale_management_views.xml
index 888ff8376b8..1861502125e 100644
--- a/addons/sale_management/views/sale_management_views.xml
+++ b/addons/sale_management/views/sale_management_views.xml
@@ -100,4 +100,15 @@
+
+ product.template.sales
+ product.template
+
+
+
+ 0
+
+
+
+
diff --git a/addons/sale_stock/__manifest__.py b/addons/sale_stock/__manifest__.py
index f53ee7b3e3e..a1be951ecbc 100644
--- a/addons/sale_stock/__manifest__.py
+++ b/addons/sale_stock/__manifest__.py
@@ -33,7 +33,6 @@ You can choose flexible invoicing methods:
'views/res_config_settings_views.xml',
'views/account_invoice_views.xml',
'views/sale_stock_portal_template.xml',
- 'views/product_template_views.xml',
'views/res_users_views.xml',
'views/stock_production_lot_views.xml',
'report/sale_order_report_templates.xml',
diff --git a/addons/sale_stock/views/product_template_views.xml b/addons/sale_stock/views/product_template_views.xml
deleted file mode 100644
index 9edec3db317..00000000000
--- a/addons/sale_stock/views/product_template_views.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
- product.template.form.expense.policy.inherit
- product.template
-
-
-
- {'invisible': [('type','=', 'product')]}
-
-
-
-
-
diff --git a/addons/sale_stock/views/stock_views.xml b/addons/sale_stock/views/stock_views.xml
index 1a39a3e96e7..80cc91c6069 100644
--- a/addons/sale_stock/views/stock_views.xml
+++ b/addons/sale_stock/views/stock_views.xml
@@ -92,9 +92,15 @@
-
+ type="object" icon="fa-signal" attrs="{'invisible': [('sale_ok', '=', False)]}"
+ groups="sales_team.group_sale_salesman" help="Sold in the last 365 days">
+
+
+
+
+
+ Sold
+
diff --git a/addons/sale_timesheet/models/product.py b/addons/sale_timesheet/models/product.py
index ceb8273549b..10da8cc6127 100644
--- a/addons/sale_timesheet/models/product.py
+++ b/addons/sale_timesheet/models/product.py
@@ -12,7 +12,7 @@ class ProductTemplate(models.Model):
('ordered_timesheet', 'Ordered quantities'),
('delivered_timesheet', 'Timesheets on tasks'),
('delivered_manual', 'Milestones (manually set quantities on order)')
- ], string="Invoice based on", compute='_compute_service_policy', inverse='_inverse_service_policy')
+ ], string="Service Invoicing Policy", compute='_compute_service_policy', inverse='_inverse_service_policy')
service_type = fields.Selection(selection_add=[
('timesheet', 'Timesheets on project (one fare per SO/Project)'),
])
diff --git a/addons/sale_timesheet/views/product_views.xml b/addons/sale_timesheet/views/product_views.xml
index aea6f60a959..827cc9e2e74 100644
--- a/addons/sale_timesheet/views/product_views.xml
+++ b/addons/sale_timesheet/views/product_views.xml
@@ -18,20 +18,6 @@
-
- product.template.search.timesheet
- product.template
-
-
-
-
-
-
-
-
-
-
-
Products
product.template
diff --git a/addons/stock/views/product_views.xml b/addons/stock/views/product_views.xml
index 4abd3836c22..c75a33ff095 100644
--- a/addons/stock/views/product_views.xml
+++ b/addons/stock/views/product_views.xml
@@ -62,17 +62,31 @@
product.template.search.stock.form
product.template
+ primary
-
+
-
-
-
+
+
+
+
+
+
+ product.product.search.stock.form
+ product.product
+ primary
+
+
+
+
+
+
+
@@ -82,23 +96,21 @@
product.template
-
-
-
-
-
-
-
- days
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+ days
+
+
+
+
+
+
+
+
@@ -184,18 +196,25 @@
name="%(product_open_quants)d"
icon="fa-building-o"
type="action" attrs="{'invisible':[('type', '!=', 'product')]}">
-
+
+
+
+
+
+ On Hand
+
-
-
- (-
- +)
+
+
+
+ (-+)
+
Forecasted
@@ -257,17 +276,25 @@
name="action_open_quants"
attrs="{'invisible':[('type', '!=', 'product')]}"
class="oe_stat_button" icon="fa-building-o">
-
+
+
+
+
+
+ On Hand
+
-
+
+
(-+)
+
Forecasted
@@ -327,6 +354,7 @@
product.template
kanban,tree,form
form
+
{"search_default_consumable": 1, 'default_type': 'product'}
@@ -334,9 +362,19 @@
+
+
+ Product Variants
+ ir.actions.act_window
+ product.product
+ tree,form,kanban
+ form
+
+
+
-
diff --git a/addons/web/static/src/js/views/form/form_renderer.js b/addons/web/static/src/js/views/form/form_renderer.js
index 111f5c15031..6e9952c81d7 100644
--- a/addons/web/static/src/js/views/form/form_renderer.js
+++ b/addons/web/static/src/js/views/form/form_renderer.js
@@ -596,6 +596,9 @@ var FormRenderer = BasicRenderer.extend({
extraClass: 'oe_stat_button',
});
$button.append(_.map(node.children, this._renderNode.bind(this)));
+ if (node.attrs.help) {
+ this._addButtonTooltip(node, $button);
+ }
this._addOnClickAction($button, node);
this._handleAttributes($button, node);
this._registerModifiers(node, this.state, $button);
diff --git a/addons/website_sale/views/product_views.xml b/addons/website_sale/views/product_views.xml
index 4b5aef47406..706460b6454 100644
--- a/addons/website_sale/views/product_views.xml
+++ b/addons/website_sale/views/product_views.xml
@@ -17,6 +17,7 @@
product.template
form
kanban,tree,form
+
{'search_default_published': 1, 'default_website_published': True}
@@ -73,58 +74,59 @@
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
diff --git a/addons/website_sale_digital/views/website_sale_digital_view.xml b/addons/website_sale_digital/views/website_sale_digital_view.xml
index 8d3d1f8234b..42b8c8ebd27 100644
--- a/addons/website_sale_digital/views/website_sale_digital_view.xml
+++ b/addons/website_sale_digital/views/website_sale_digital_view.xml
@@ -7,7 +7,7 @@
-
+
@@ -20,7 +20,7 @@
-
+
diff --git a/addons/website_sale_stock/views/product_template_views.xml b/addons/website_sale_stock/views/product_template_views.xml
index 0869099673b..21ffdbf6bf2 100644
--- a/addons/website_sale_stock/views/product_template_views.xml
+++ b/addons/website_sale_stock/views/product_template_views.xml
@@ -5,11 +5,11 @@
product.template
-
-
+
+
-
+