diff --git a/addons/account/models/account_analytic_line.py b/addons/account/models/account_analytic_line.py index 80e35b8080b..92f19595a42 100644 --- a/addons/account/models/account_analytic_line.py +++ b/addons/account/models/account_analytic_line.py @@ -7,6 +7,9 @@ from odoo.exceptions import UserError class AccountAnalyticAccount(models.Model): _inherit = 'account.analytic.account' + invoice_count = fields.Integer("Invoice Count", compute='_compute_invoice_count') + vendor_bill_count = fields.Integer("Vendor Bill Count", compute='_compute_vendor_bill_count') + @api.constrains('company_id') def _check_company_consistency(self): analytic_accounts = self.filtered('company_id') @@ -26,6 +29,55 @@ class AccountAnalyticAccount(models.Model): if self._cr.fetchone(): raise UserError(_("You can't set a different company on your analytic account since there are some journal items linked to it.")) + @api.depends('line_ids') + def _compute_invoice_count(self): + sale_types = self.env['account.move'].get_sale_types() + domain = [ + ('move_id.state', '=', 'posted'), + ('move_id.move_type', 'in', sale_types), + ('analytic_account_id', 'in', self.ids) + ] + groups = self.env['account.move.line'].read_group(domain, ['move_id:count_distinct'], ['analytic_account_id']) + moves_count_mapping = dict((g['analytic_account_id'][0], g['analytic_account_id_count']) for g in groups) + for account in self: + account.invoice_count = moves_count_mapping.get(account.id, 0) + + @api.depends('line_ids') + def _compute_vendor_bill_count(self): + purchase_types = self.env['account.move'].get_purchase_types() + domain = [ + ('move_id.state', '=', 'posted'), + ('move_id.move_type', 'in', purchase_types), + ('analytic_account_id', 'in', self.ids) + ] + groups = self.env['account.move.line'].read_group(domain, ['move_id:count_distinct'], ['analytic_account_id']) + moves_count_mapping = dict((g['analytic_account_id'][0], g['analytic_account_id_count']) for g in groups) + for account in self: + account.vendor_bill_count = moves_count_mapping.get(account.id, 0) + + def action_view_invoice(self): + self.ensure_one() + result = { + "type": "ir.actions.act_window", + "res_model": "account.move", + "domain": [('id', 'in', self.line_ids.move_id.move_id.ids), ('move_type', 'in', self.env['account.move'].get_sale_types())], + "context": {"create": False}, + "name": "Customer Invoices", + 'view_mode': 'tree,form', + } + return result + + def action_view_vendor_bill(self): + self.ensure_one() + result = { + "type": "ir.actions.act_window", + "res_model": "account.move", + "domain": [('id', 'in', self.line_ids.move_id.move_id.ids), ('move_type', 'in', self.env['account.move'].get_purchase_types())], + "context": {"create": False}, + "name": "Vendor Bills", + 'view_mode': 'tree,form', + } + return result class AccountAnalyticTag(models.Model): _inherit = 'account.analytic.tag' @@ -62,6 +114,7 @@ class AccountAnalyticLine(models.Model): move_id = fields.Many2one('account.move.line', string='Journal Item', ondelete='cascade', index=True, check_company=True) code = fields.Char(size=8) ref = fields.Char(string='Ref.') + category = fields.Selection(selection_add=[('invoice', 'Customer Invoice'), ('vendor_bill', 'Vendor Bill')]) @api.onchange('product_id', 'product_uom_id', 'unit_amount', 'currency_id') def on_change_unit_amount(self): diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index 2ebdc23cef3..b8a6d5f6c8a 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -5007,6 +5007,11 @@ class AccountMoveLine(models.Model): for move_line in self: amount = (move_line.credit or 0.0) - (move_line.debit or 0.0) default_name = move_line.name or (move_line.ref or '/' + ' -- ' + (move_line.partner_id and move_line.partner_id.name or '/')) + category = 'other' + if move_line.move_id.is_sale_document(): + category = 'invoice' + elif move_line.move_id.is_purchase_document(): + category = 'vendor_bill' result.append({ 'name': default_name, 'date': move_line.date, @@ -5023,6 +5028,7 @@ class AccountMoveLine(models.Model): 'user_id': move_line.move_id.invoice_user_id.id or self._uid, 'partner_id': move_line.partner_id.id, 'company_id': move_line.analytic_account_id.company_id.id or move_line.move_id.company_id.id, + 'category': category, }) return result diff --git a/addons/account/views/account_analytic_view.xml b/addons/account/views/account_analytic_view.xml index 0488534d2c8..34307b3fcb4 100644 --- a/addons/account/views/account_analytic_view.xml +++ b/addons/account/views/account_analytic_view.xml @@ -69,5 +69,25 @@ + + account.analytic.account.form.inherit + account.analytic.account + + + +
+ + +
+
+
+ + diff --git a/addons/analytic/models/analytic_account.py b/addons/analytic/models/analytic_account.py index d7424b69a59..88cba7bf583 100644 --- a/addons/analytic/models/analytic_account.py +++ b/addons/analytic/models/analytic_account.py @@ -184,7 +184,7 @@ class AccountAnalyticLine(models.Model): amount = fields.Monetary('Amount', required=True, default=0.0) unit_amount = fields.Float('Quantity', default=0.0) product_uom_id = fields.Many2one('uom.uom', string='Unit of Measure', domain="[('category_id', '=', product_uom_category_id)]") - product_uom_category_id = fields.Many2one(related='product_uom_id.category_id', readonly=True) + product_uom_category_id = fields.Many2one(related='product_uom_id.category_id', string='UoM Category', readonly=True) account_id = fields.Many2one('account.analytic.account', 'Analytic Account', required=True, ondelete='restrict', index=True, check_company=True) partner_id = fields.Many2one('res.partner', string='Partner', check_company=True) user_id = fields.Many2one('res.users', string='User', default=_default_user) @@ -192,6 +192,7 @@ class AccountAnalyticLine(models.Model): company_id = fields.Many2one('res.company', string='Company', required=True, readonly=True, default=lambda self: self.env.company) currency_id = fields.Many2one(related="company_id.currency_id", string="Currency", readonly=True, store=True, compute_sudo=True) group_id = fields.Many2one('account.analytic.group', related='account_id.group_id', store=True, readonly=True, compute_sudo=True) + category = fields.Selection([('other', 'Other')], default='other') @api.constrains('company_id', 'account_id') def _check_company_id(self): diff --git a/addons/analytic/views/analytic_account_views.xml b/addons/analytic/views/analytic_account_views.xml index 22e98f95aa2..43ddf175eeb 100644 --- a/addons/analytic/views/analytic_account_views.xml +++ b/addons/analytic/views/analytic_account_views.xml @@ -127,7 +127,7 @@ {'search_default_group_date': 1, 'default_account_id': active_id} [('account_id','=', active_id)] - Costs & Revenues + Gross Margin account.analytic.line tree,form,graph,pivot @@ -157,8 +157,14 @@
-
@@ -171,8 +177,8 @@ - + @@ -321,6 +327,7 @@ + diff --git a/addons/mrp_account/__manifest__.py b/addons/mrp_account/__manifest__.py index 961bae09460..e1531fbe988 100644 --- a/addons/mrp_account/__manifest__.py +++ b/addons/mrp_account/__manifest__.py @@ -24,6 +24,7 @@ If the automated inventory valuation is active, the necessary accounting entries "views/product_views.xml", "views/mrp_bom_views.xml", "views/mrp_production_views.xml", + "views/analytic_account_views.xml", ], 'demo': [ 'data/mrp_account_demo.xml', diff --git a/addons/mrp_account/models/__init__.py b/addons/mrp_account/models/__init__.py index 63fd1d47605..9835d6ccffa 100644 --- a/addons/mrp_account/models/__init__.py +++ b/addons/mrp_account/models/__init__.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. - from . import analytic_account from . import mrp_bom from . import mrp_workcenter diff --git a/addons/mrp_account/models/analytic_account.py b/addons/mrp_account/models/analytic_account.py new file mode 100644 index 00000000000..75098f21f42 --- /dev/null +++ b/addons/mrp_account/models/analytic_account.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models + + +class AccountAnalyticAccount(models.Model): + _inherit = 'account.analytic.account' + _description = 'Analytic Account' + + production_ids = fields.One2many('mrp.production', 'analytic_account_id', string='Manufacturing Orders') + production_count = fields.Integer("Manufacturing Orders Count", compute='_compute_production_count') + bom_ids = fields.One2many('mrp.bom', 'analytic_account_id', string='Bills of Materials') + bom_count = fields.Integer("BoM Count", compute='_compute_bom_count') + workcenter_ids = fields.One2many('mrp.workcenter', 'costs_hour_account_id', string='Workcenters') + workorder_count = fields.Integer("Work Order Count", compute='_compute_workorder_count') + + @api.depends('production_ids') + def _compute_production_count(self): + for account in self: + account.production_count = len(account.production_ids) + + @api.depends('bom_ids') + def _compute_bom_count(self): + for account in self: + account.bom_count = len(account.bom_ids) + + @api.depends('workcenter_ids.order_ids', 'production_ids.workorder_ids') + def _compute_workorder_count(self): + for account in self: + account.workorder_count = len(account.workcenter_ids.order_ids | account.production_ids.workorder_ids) + + def action_view_mrp_production(self): + self.ensure_one() + result = { + "type": "ir.actions.act_window", + "res_model": "mrp.production", + "domain": [['id', 'in', self.production_ids.ids]], + "name": "Manufacturing Orders", + 'view_mode': 'tree,form', + "context": {'default_analytic_account_id': self.id}, + } + if len(self.production_ids) == 1: + result['view_mode'] = 'form' + result['res_id'] = self.production_ids.id + return result + + def action_view_mrp_bom(self): + self.ensure_one() + result = { + "type": "ir.actions.act_window", + "res_model": "mrp.bom", + "domain": [['id', 'in', self.bom_ids.ids]], + "name": "Bills of Materials", + 'view_mode': 'tree,form', + "context": {'default_analytic_account_id': self.id}, + } + if self.bom_count == 1: + result['view_mode'] = 'form' + result['res_id'] = self.bom_ids.id + return result + + def action_view_workorder(self): + self.ensure_one() + result = { + "type": "ir.actions.act_window", + "res_model": "mrp.workorder", + "domain": [['id', 'in', (self.workcenter_ids.order_ids | self.production_ids.workorder_ids).ids]], + "context": {"create": False}, + "name": "Work Orders", + 'view_mode': 'tree', + } + return result + + +class AccountAnalyticLine(models.Model): + _inherit = 'account.analytic.line' + + category = fields.Selection(selection_add=[('manufacturing_order', 'Manufacturing Order')]) diff --git a/addons/mrp_account/models/mrp_workorder.py b/addons/mrp_account/models/mrp_workorder.py index 1039dde9bbc..dfdfe85558d 100644 --- a/addons/mrp_account/models/mrp_workorder.py +++ b/addons/mrp_account/models/mrp_workorder.py @@ -36,6 +36,7 @@ class MrpWorkorder(models.Model): 'product_uom_id': self.product_id.uom_id.id, 'company_id': self.company_id.id, 'ref': self.production_id.name, + 'category': 'manufacturing_order', } def _create_or_update_analytic_entry(self): diff --git a/addons/mrp_account/tests/test_analytic_account.py b/addons/mrp_account/tests/test_analytic_account.py index 24abb4ae470..982ffdab425 100644 --- a/addons/mrp_account/tests/test_analytic_account.py +++ b/addons/mrp_account/tests/test_analytic_account.py @@ -62,12 +62,13 @@ class TestAnalyticAccount(TransactionCase): self.assertEqual(mo.state, 'progress') self.assertEqual(mo.move_raw_ids.analytic_account_line_id.amount, -50.0) - # decrease qty_producing to 10.0 + # increase qty_producing to 10.0 mo_form = Form(mo) mo_form.qty_producing = 10.0 mo_form.save() # Hack to bypass test doing strange things mo._set_qty_producing() + mo.workorder_ids.button_finish() self.assertEqual(mo.state, 'to_close') self.assertEqual(mo.move_raw_ids.analytic_account_line_id.amount, -100.0) diff --git a/addons/mrp_account/views/analytic_account_views.xml b/addons/mrp_account/views/analytic_account_views.xml new file mode 100644 index 00000000000..14d2da069bc --- /dev/null +++ b/addons/mrp_account/views/analytic_account_views.xml @@ -0,0 +1,22 @@ + + + + account.analytic.account.form.mrp + account.analytic.account + + + +
+ + +
+
+
+ +
diff --git a/addons/project/models/project.py b/addons/project/models/project.py index 8f4a3b7c2d6..8cccd0a6a89 100644 --- a/addons/project/models/project.py +++ b/addons/project/models/project.py @@ -140,7 +140,7 @@ class ProjectTaskType(models.Model): class Project(models.Model): _name = "project.project" _description = "Project" - _inherit = ['portal.mixin', 'mail.alias.mixin', 'mail.thread', 'rating.parent.mixin'] + _inherit = ['portal.mixin', 'mail.alias.mixin', 'mail.thread', 'mail.activity.mixin', 'rating.parent.mixin'] _order = "sequence, name, id" _rating_satisfaction_days = False # takes all existing ratings _check_company_auto = True @@ -234,6 +234,7 @@ class Project(models.Model): domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", check_company=True, help="Analytic account to which this project is linked for financial management. " "Use an analytic account to record cost and revenue on your project.") + analytic_account_balance = fields.Monetary(related="analytic_account_id.balance") favorite_user_ids = fields.Many2many( 'res.users', 'project_favorite_user_rel', 'project_id', 'user_id', @@ -604,6 +605,33 @@ class Project(models.Model): 'project.open_view_project_all_group_stage') return action + def action_view_analytic_account_entries(self): + self.ensure_one() + return { + 'res_model': 'account.analytic.line', + 'type': 'ir.actions.act_window', + 'name': _("Gross Margin"), + 'domain': [('account_id', '=', self.analytic_account_id.id)], + 'views': [(self.env.ref('analytic.view_account_analytic_line_tree').id, 'list'), + (self.env.ref('analytic.view_account_analytic_line_form').id, 'form'), + (self.env.ref('analytic.view_account_analytic_line_graph').id, 'graph'), + (self.env.ref('analytic.view_account_analytic_line_pivot').id, 'pivot')], + 'view_mode': 'tree,form,graph,pivot', + 'context': {'search_default_group_date': 1, 'default_account_id': self.analytic_account_id.id} + } + + def action_view_kanban_project(self): + self.ensure_one() + return { + 'res_model': 'project.project', + 'type': 'ir.actions.act_window', + 'name': _("Project"), + 'res_id': self.id, + 'views': [(self.env.ref('project.edit_project').id, 'form')], + 'view_mode': 'form', + 'context': {'form_view_initial_mode': 'readonly'} + } + # --------------------------------------------- # PROJECT UPDATES # --------------------------------------------- diff --git a/addons/project/views/analytic_views.xml b/addons/project/views/analytic_views.xml index 48f968d708d..68ebb85d2da 100644 --- a/addons/project/views/analytic_views.xml +++ b/addons/project/views/analytic_views.xml @@ -7,14 +7,14 @@ account.analytic.account.form.inherit account.analytic.account - + -
+ -
+
diff --git a/addons/project/views/project_views.xml b/addons/project/views/project_views.xml index cf46269bf37..7d74d523fc6 100644 --- a/addons/project/views/project_views.xml +++ b/addons/project/views/project_views.xml @@ -272,6 +272,15 @@
+ +
+
+
diff --git a/addons/purchase/__manifest__.py b/addons/purchase/__manifest__.py index 861fda3ab65..ce6107fddeb 100644 --- a/addons/purchase/__manifest__.py +++ b/addons/purchase/__manifest__.py @@ -30,6 +30,7 @@ 'report/purchase_order_templates.xml', 'report/purchase_quotation_templates.xml', 'views/product_packaging_views.xml', + 'views/analytic_account_views.xml', ], 'demo': [ 'data/purchase_demo.xml', diff --git a/addons/purchase/models/__init__.py b/addons/purchase/models/__init__.py index f2f1fa553dc..f738a5995c7 100644 --- a/addons/purchase/models/__init__.py +++ b/addons/purchase/models/__init__.py @@ -2,6 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from . import account_invoice +from . import analytic_account from . import purchase from . import product from . import res_company diff --git a/addons/purchase/models/analytic_account.py b/addons/purchase/models/analytic_account.py new file mode 100644 index 00000000000..73516d903b4 --- /dev/null +++ b/addons/purchase/models/analytic_account.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models + + +class AccountAnalyticAccount(models.Model): + _inherit = 'account.analytic.account' + + purchase_order_count = fields.Integer("Purchase Order Count", compute='_compute_purchase_order_count') + + @api.depends('line_ids') + def _compute_purchase_order_count(self): + for account in self: + account.purchase_order_count = len(account.line_ids.move_id.purchase_order_id) + + def action_view_purchase_orders(self): + self.ensure_one() + purchase_orders = self.line_ids.move_id.purchase_order_id + result = { + "type": "ir.actions.act_window", + "res_model": "purchase.order", + "domain": [['id', 'in', purchase_orders.ids]], + "name": "Purchase Orders", + 'view_mode': 'tree,form', + } + if len(purchase_orders) == 1: + result['view_mode'] = 'form' + result['res_id'] = purchase_orders.id + return result diff --git a/addons/purchase/views/analytic_account_views.xml b/addons/purchase/views/analytic_account_views.xml new file mode 100644 index 00000000000..06c2255d291 --- /dev/null +++ b/addons/purchase/views/analytic_account_views.xml @@ -0,0 +1,18 @@ + + + + account.analytic.account.form.purchase + account.analytic.account + + + +
+ +
+
+
+ +