From c1f7987f493ca655390eb40756f744342f4a31e8 Mon Sep 17 00:00:00 2001 From: Kirti Choudhary Date: Tue, 7 Aug 2018 10:19:50 +0200 Subject: [PATCH 1/6] [IMP] various: improve product form usability --- addons/account/views/product_view.xml | 3 + .../delivery/views/product_template_view.xml | 8 +- addons/hr_expense/views/hr_expense_views.xml | 3 +- addons/point_of_sale/models/product.py | 2 +- addons/point_of_sale/views/product_view.xml | 18 ++-- addons/product/views/product_views.xml | 69 +++++++------- .../views/product_views.xml | 12 +-- addons/purchase/views/product_views.xml | 3 + addons/sale/models/product_template.py | 32 +++++-- addons/sale/views/sale_views.xml | 11 +-- addons/sale_expense/views/product_view.xml | 6 ++ .../views/sale_management_views.xml | 11 +++ addons/sale_stock/__manifest__.py | 1 - .../views/product_template_views.xml | 15 --- addons/sale_timesheet/models/product.py | 2 +- addons/sale_timesheet/views/product_views.xml | 14 --- addons/stock/views/product_views.xml | 40 ++++---- addons/website_sale/views/product_views.xml | 91 ++++++++++--------- .../views/website_sale_digital_view.xml | 4 +- .../views/product_template_views.xml | 6 +- 20 files changed, 179 insertions(+), 172 deletions(-) delete mode 100644 addons/sale_stock/views/product_template_views.xml diff --git a/addons/account/views/product_view.xml b/addons/account/views/product_view.xml index 5ea5ccc00ff..45ab7996679 100644 --- a/addons/account/views/product_view.xml +++ b/addons/account/views/product_view.xml @@ -7,6 +7,9 @@ 5 + + 0 + diff --git a/addons/delivery/views/product_template_view.xml b/addons/delivery/views/product_template_view.xml index 9073377f826..a37c76aed0d 100644 --- a/addons/delivery/views/product_template_view.xml +++ b/addons/delivery/views/product_template_view.xml @@ -6,9 +6,11 @@ product.template - - - + + + + + diff --git a/addons/hr_expense/views/hr_expense_views.xml b/addons/hr_expense/views/hr_expense_views.xml index ad96e756d94..b09d265fa5b 100644 --- a/addons/hr_expense/views/hr_expense_views.xml +++ b/addons/hr_expense/views/hr_expense_views.xml @@ -260,7 +260,7 @@
-
+
@@ -310,6 +310,7 @@ + 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/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 @@ - + + + + + + + +
+ + + + + + + + + + + +
+ + + + + +
+ + + + + + + + - - - - - - - -
- - - - - - - - - - - -
- - - - - - -
- - - - - - product.template
- - Email - - - + + Automatic Email at Invoice + 0 + + - + diff --git a/addons/purchase/views/product_views.xml b/addons/purchase/views/product_views.xml index 5fb164080a1..cf8dbffe806 100644 --- a/addons/purchase/views/product_views.xml +++ b/addons/purchase/views/product_views.xml @@ -8,6 +8,9 @@ product.template + + 0 + diff --git a/addons/sale/models/product_template.py b/addons/sale/models/product_template.py index 98edf5eb28c..e8b0390037c 100644 --- a/addons/sale/models/product_template.py +++ b/addons/sale/models/product_template.py @@ -18,7 +18,28 @@ class ProductTemplate(models.Model): expense_policy = fields.Selection( [('no', 'No'), ('cost', 'At cost'), ('sales_price', 'Sales price')], string='Re-Invoice Policy', - default='no') + 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'), + ], string='Invoicing Policy', + help='Ordered Quantity: Invoice based on the quantity the customer ordered.\n' + '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') @@ -44,15 +65,6 @@ class ProductTemplate(models.Model): 'domain': [('state', 'in', ['sale', 'done']), ('product_id.product_tmpl_id', '=', self.id)], } - sales_count = fields.Integer(compute='_sales_count', string='# Sales') - invoice_policy = fields.Selection( - [('order', 'Ordered quantities'), - ('delivery', 'Delivered quantities'), - ], string='Invoicing Policy', - help='Ordered Quantity: Invoice based on the quantity the customer ordered.\n' - 'Delivered Quantity: Invoiced based on the quantity the vendor delivered (time or deliveries).', - default='order') - @api.onchange('type') def _onchange_type(self): """ Force values to stay consistent with integrity constraints """ diff --git a/addons/sale/views/sale_views.xml b/addons/sale/views/sale_views.xml index 761333e3920..f2692de0397 100644 --- a/addons/sale/views/sale_views.xml +++ b/addons/sale/views/sale_views.xml @@ -929,17 +929,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_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..59e7289ea75 100644 --- a/addons/stock/views/product_views.xml +++ b/addons/stock/views/product_views.xml @@ -62,17 +62,17 @@ product.template.search.stock.form product.template + primary - + - - - + + @@ -82,23 +82,21 @@ product.template - - - - - - - - - - - - - + + + + + + + + + + + diff --git a/addons/website_sale/views/product_views.xml b/addons/website_sale/views/product_views.xml index 4b5aef47406..c3edd829154 100644 --- a/addons/website_sale/views/product_views.xml +++ b/addons/website_sale/views/product_views.xml @@ -73,58 +73,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 - - + + - + From 5118d248ccf16fe4d2703c2a64e0267f783a56d1 Mon Sep 17 00:00:00 2001 From: Kirti Choudhary Date: Tue, 7 Aug 2018 10:26:47 +0200 Subject: [PATCH 2/6] [IMP] various: use uom in stat buttons In purchase we add a stat button that counts the number of purchase orders linked to the product. In this commit we change this count to count the quantity of products linked to a purchase order. We basically do the same thing with the stat button of sale (sale orders) and mrp (manufacturing orders). --- addons/mrp/models/mrp_production.py | 19 ++++++--- addons/mrp/models/product.py | 44 +++++++++++++------ addons/mrp/views/product_views.xml | 23 +++++++--- addons/product/models/product_template.py | 1 + addons/purchase/models/product.py | 52 ++++++++++++++++++----- addons/purchase/models/purchase.py | 10 +++++ addons/purchase/views/product_views.xml | 44 +++++++++++++------ addons/sale/__manifest__.py | 1 + addons/sale/models/product_product.py | 22 ++++++++-- addons/sale/models/product_template.py | 26 +++++------- addons/sale/report/__init__.py | 1 + addons/sale/security/ir.model.access.csv | 1 + addons/sale/views/sale_views.xml | 31 ++++++++------ addons/sale_stock/views/stock_views.xml | 12 ++++-- addons/stock/views/product_views.xml | 29 ++++++++++--- 15 files changed, 225 insertions(+), 91 deletions(-) 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/product_views.xml b/addons/mrp/views/product_views.xml index 87de4ef56f9..6439863312b 100644 --- a/addons/mrp/views/product_views.xml +++ b/addons/mrp/views/product_views.xml @@ -48,7 +48,6 @@ name="Products" action="product_template_action" parent="menu_mrp_bom" sequence="1"/> - @@ -70,8 +69,14 @@ @@ -92,9 +97,15 @@ attrs="{'invisible':['|',('type', 'not in', ['product', 'consu']), ('used_in_bom_count', '=', 0)]}" icon="fa-level-up"> - 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/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/views/product_views.xml b/addons/purchase/views/product_views.xml index cf8dbffe806..ad0350f7cdb 100644 --- a/addons/purchase/views/product_views.xml +++ b/addons/purchase/views/product_views.xml @@ -50,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 @@ -81,10 +72,16 @@ + @@ -100,4 +97,25 @@ + + product.product.purchase.order + product.product + + + + + + + + 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 e8b0390037c..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): @@ -43,27 +44,20 @@ class ProductTemplate(models.Model): @api.multi @api.depends('product_variant_ids.sales_count') - def _sales_count(self): + def _compute_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]) + 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): - 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)], + 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): 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/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 f2692de0397..e3688b3fe70 100644 --- a/addons/sale/views/sale_views.xml +++ b/addons/sale/views/sale_views.xml @@ -871,14 +871,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 +878,15 @@ @@ -909,9 +907,14 @@ 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 @@ diff --git a/addons/stock/views/product_views.xml b/addons/stock/views/product_views.xml index 59e7289ea75..04bf63cac3e 100644 --- a/addons/stock/views/product_views.xml +++ b/addons/stock/views/product_views.xml @@ -182,18 +182,25 @@ name="%(product_open_quants)d" icon="fa-building-o" type="action" attrs="{'invisible':[('type', '!=', 'product')]}"> - +
+ + + + + On Hand +