diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index ee56ff2567b..caf6358fcca 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -1222,7 +1222,6 @@ class AccountInvoiceLine(models.Model): def _onchange_uom_id(self): warning = {} result = {} - self._onchange_product_id() if not self.uom_id: self.price_unit = 0.0 if self.product_id and self.uom_id: diff --git a/addons/account_asset/account_asset.py b/addons/account_asset/account_asset.py index 373b44922b3..4c96d4aee67 100644 --- a/addons/account_asset/account_asset.py +++ b/addons/account_asset/account_asset.py @@ -15,9 +15,9 @@ class AccountAssetCategory(models.Model): active = fields.Boolean(default=True) name = fields.Char(required=True, index=True, string="Asset Type") account_analytic_id = fields.Many2one('account.analytic.account', string='Analytic Account') - account_asset_id = fields.Many2one('account.account', string='Expense Account', required=True, domain=[('internal_type','=','other'), ('deprecated', '=', False)]) - account_income_recognition_id = fields.Many2one('account.account', string='Recognition Income Account', domain=[('internal_type','=','other'), ('deprecated', '=', False)], oldname='account_expense_depreciation_id') - account_depreciation_id = fields.Many2one('account.account', string='Depreciation Account', required=True, domain=[('internal_type','=','other'), ('deprecated', '=', False)]) + account_asset_id = fields.Many2one('account.account', string='Asset Account', required=True, domain=[('internal_type','=','other'), ('deprecated', '=', False)], help="Account used to record the purchase of the asset at its original price.") + account_depreciation_id = fields.Many2one('account.account', string='Depreciation Entries: Asset Account', required=True, domain=[('internal_type','=','other'), ('deprecated', '=', False)], help="Account used in the depreciation entries, to decrease the asset value.") + account_depreciation_expense_id = fields.Many2one('account.account', string='Depreciation Entries: Expense Account', required=True, domain=[('internal_type','=','other'), ('deprecated', '=', False)], oldname='account_income_recognition_id', help="Account used in the periodical entries, to record a part of the asset as expense.") journal_id = fields.Many2one('account.journal', string='Journal', required=True) company_id = fields.Many2one('res.company', string='Company', required=True, default=lambda self: self.env['res.company']._company_default_get('account.asset.category')) method = fields.Selection([('linear', 'Linear'), ('degressive', 'Degressive')], string='Computation Method', required=True, default='linear', @@ -33,9 +33,14 @@ class AccountAssetCategory(models.Model): " * Ending Date: Choose the time between 2 depreciations and the date the depreciations won't go beyond.") method_end = fields.Date('Ending date') prorata = fields.Boolean(string='Prorata Temporis', help='Indicates that the first depreciation entry for this asset have to be done from the purchase date instead of the first of January') - open_asset = fields.Boolean(string='Post Journal Entries', help="Check this if you want to automatically confirm the assets of this category when created by invoices.") + open_asset = fields.Boolean(string='Auto-confirm Assets', help="Check this if you want to automatically confirm the assets of this category when created by invoices.") + group_entries = fields.Boolean(string='Group Journal Entries', help="Check this if you want to group the generated entries by categories.") type = fields.Selection([('sale', 'Sale: Revenue Recognition'), ('purchase', 'Purchase: Asset')], required=True, index=True, default='purchase') + @api.onchange('account_asset_id') + def onchange_account_asset(self): + self.account_depreciation_id = self.account_asset_id + @api.onchange('type') def onchange_type(self): if self.type == 'sale': @@ -50,7 +55,6 @@ class AccountAssetAsset(models.Model): _description = 'Asset/Revenue Recognition' _inherit = ['mail.thread', 'ir.needaction_mixin'] - account_move_ids = fields.One2many('account.move', 'asset_id', string='Entries', readonly=True, states={'draft': [('readonly', False)]}) entry_count = fields.Integer(compute='_entry_count', string='# Asset Entries') name = fields.Char(string='Asset Name', required=True, readonly=True, states={'draft': [('readonly', False)]}) code = fields.Char(string='Reference', size=32, readonly=True, states={'draft': [('readonly', False)]}) @@ -94,8 +98,9 @@ class AccountAssetAsset(models.Model): for asset in self: if asset.state in ['open', 'close']: raise UserError(_('You cannot delete a document is in %s state.') % (asset.state,)) - if asset.account_move_ids: - raise UserError(_('You cannot delete a document that contains posted entries.')) + for depreciation_line in asset.depreciation_line_ids: + if depreciation_line.move_id: + raise UserError(_('You cannot delete a document that contains posted entries.')) return super(AccountAssetAsset, self).unlink() @api.multi @@ -107,16 +112,32 @@ class AccountAssetAsset(models.Model): self.env.cr.execute(""" SELECT a.id as id, COALESCE(MAX(m.date),a.date) AS date FROM account_asset_asset a - LEFT JOIN account_move m ON (m.asset_id = a.id) + LEFT JOIN account_asset_depreciation_line rel ON (rel.asset_id = a.id) + LEFT JOIN account_move m ON (rel.move_id = m.id) WHERE a.id IN %s - GROUP BY a.id, a.date """, (tuple(self.ids),)) + GROUP BY a.id, m.date """, (tuple(self.ids),)) result = dict(self.env.cr.fetchall()) return result @api.model def _cron_generate_entries(self): - assets = self.env['account.asset.asset'].search([('state', '=', 'open')]) - assets._compute_entries(datetime.today()) + self.compute_generated_entries(datetime.today()) + + @api.model + def compute_generated_entries(self, date, asset_type=None): + # Entries generated : one by grouped category and one by asset from ungrouped category + created_move_ids = [] + type_domain = [] + if asset_type: + type_domain = [('type', '=', asset_type)] + + ungrouped_assets = self.env['account.asset.asset'].search(type_domain + [('state', '=', 'open'), ('category_id.group_entries', '=', False)]) + created_move_ids += ungrouped_assets._compute_entries(date, group_entries=False) + + for grouped_category in self.env['account.asset.category'].search(type_domain + [('group_entries', '=', True)]): + assets = self.env['account.asset.asset'].search([('state', '=', 'open'), ('category_id', '=', grouped_category.id)]) + created_move_ids += assets._compute_entries(date, group_entries=True) + return created_move_ids def _compute_board_amount(self, sequence, residual_amount, amount_to_depr, undone_dotation_number, posted_depreciation_line_ids, total_days, depreciation_date): amount = 0 @@ -300,10 +321,11 @@ class AccountAssetAsset(models.Model): self.currency_id = self.company_id.currency_id.id @api.multi - @api.depends('account_move_ids') + @api.depends('depreciation_line_ids.move_id') def _entry_count(self): for asset in self: - asset.entry_count = self.env['account.move'].search_count([('asset_id', '=', asset.id)]) + res = self.env['account.asset.depreciation.line'].search_count([('asset_id', '=', asset.id), ('move_id', '!=', False)]) + asset.entry_count = res or 0 @api.one @api.constrains('prorata', 'method_time') @@ -347,10 +369,12 @@ class AccountAssetAsset(models.Model): return super(AccountAssetAsset, self).copy_data(default)[0] @api.multi - def _compute_entries(self, date): - depreciation_ids = self.env['account.asset.depreciation.line'].search([ + def _compute_entries(self, date, group_entries=False): + depreciation_ids = self.env['account.asset.depreciation.line'].with_context(depreciation_date=date).search([ ('asset_id', 'in', self.ids), ('depreciation_date', '<=', date), ('move_check', '=', False)]) + if group_entries: + return depreciation_ids.create_grouped_move() return depreciation_ids.create_move() @api.model @@ -368,6 +392,11 @@ class AccountAssetAsset(models.Model): @api.multi def open_entries(self): + move_ids = [] + for asset in self: + for depreciation_line in asset.depreciation_line_ids: + if depreciation_line.move_id: + move_ids.append(depreciation_line.move_id.id) return { 'name': _('Journal Entries'), 'view_type': 'form', @@ -375,7 +404,7 @@ class AccountAssetAsset(models.Model): 'res_model': 'account.move', 'view_id': False, 'type': 'ir.actions.act_window', - 'context': dict(self.env.context or {}, search_default_asset_id=self.id, default_asset_id=self.id), + 'domain': [('id', 'in', move_ids)] } @@ -403,49 +432,41 @@ class AccountAssetDepreciationLine(models.Model): def create_move(self, post_move=True): created_moves = self.env['account.move'] for line in self: + category_id = line.asset_id.category_id depreciation_date = self.env.context.get('depreciation_date') or line.depreciation_date or fields.Date.context_today(self) company_currency = line.asset_id.company_id.currency_id current_currency = line.asset_id.currency_id amount = current_currency.compute(line.amount, company_currency) - sign = (line.asset_id.category_id.journal_id.type == 'purchase' or line.asset_id.category_id.journal_id.type == 'sale' and 1) or -1 + sign = (category_id.journal_id.type == 'purchase' or category_id.journal_id.type == 'sale' and 1) or -1 asset_name = line.asset_id.name + ' (%s/%s)' % (line.sequence, line.asset_id.method_number) - reference = line.asset_id.code - journal_id = line.asset_id.category_id.journal_id.id - partner_id = line.asset_id.partner_id.id - categ_type = line.asset_id.category_id.type - debit_account = line.asset_id.category_id.account_asset_id.id - credit_account = line.asset_id.category_id.account_depreciation_id.id move_line_1 = { 'name': asset_name, - 'account_id': credit_account, + 'account_id': category_id.account_depreciation_id.id, 'debit': 0.0, 'credit': amount, - 'journal_id': journal_id, - 'partner_id': partner_id, + 'journal_id': category_id.journal_id.id, + 'partner_id': line.asset_id.partner_id.id, + 'analytic_account_id': category_id.account_analytic_id.id if category_id.type == 'sale' else False, 'currency_id': company_currency != current_currency and current_currency.id or False, 'amount_currency': company_currency != current_currency and - sign * line.amount or 0.0, - 'analytic_account_id': line.asset_id.category_id.account_analytic_id.id if categ_type == 'sale' else False, - 'date': depreciation_date, } move_line_2 = { 'name': asset_name, - 'account_id': debit_account, + 'account_id': category_id.account_depreciation_expense_id.id, 'credit': 0.0, 'debit': amount, - 'journal_id': journal_id, - 'partner_id': partner_id, + 'journal_id': category_id.journal_id.id, + 'partner_id': line.asset_id.partner_id.id, + 'analytic_account_id': category_id.account_analytic_id.id if category_id.type == 'purchase' else False, 'currency_id': company_currency != current_currency and current_currency.id or False, 'amount_currency': company_currency != current_currency and sign * line.amount or 0.0, - 'analytic_account_id': line.asset_id.category_id.account_analytic_id.id if categ_type == 'purchase' else False, - 'date': depreciation_date, } move_vals = { - 'ref': reference, + 'ref': line.asset_id.code, 'date': depreciation_date or False, - 'journal_id': line.asset_id.category_id.journal_id.id, + 'journal_id': category_id.journal_id.id, 'line_ids': [(0, 0, move_line_1), (0, 0, move_line_2)], - 'asset_id': line.asset_id.id, - } + } move = self.env['account.move'].create(move_vals) line.write({'move_id': move.id, 'move_check': True}) created_moves |= move @@ -455,6 +476,53 @@ class AccountAssetDepreciationLine(models.Model): return [x.id for x in created_moves] @api.multi + def create_grouped_move(self, post_move=True): + if not self.exists(): + return [] + + created_moves = self.env['account.move'] + category_id = self[0].asset_id.category_id # we can suppose that all lines have the same category + depreciation_date = self.env.context.get('depreciation_date') or fields.Date.context_today(self) + amount = 0.0 + for line in self: + # Sum amount of all depreciation lines + company_currency = line.asset_id.company_id.currency_id + current_currency = line.asset_id.currency_id + amount += current_currency.compute(line.amount, company_currency) + + name = category_id.name + _(' (grouped)') + move_line_1 = { + 'name': name, + 'account_id': category_id.account_depreciation_id.id, + 'debit': 0.0, + 'credit': amount, + 'journal_id': category_id.journal_id.id, + 'analytic_account_id': category_id.account_analytic_id.id if category_id.type == 'sale' else False, + } + move_line_2 = { + 'name': name, + 'account_id': category_id.account_depreciation_expense_id.id, + 'credit': 0.0, + 'debit': amount, + 'journal_id': category_id.journal_id.id, + 'analytic_account_id': category_id.account_analytic_id.id if category_id.type == 'purchase' else False, + } + move_vals = { + 'ref': category_id.name, + 'date': depreciation_date or False, + 'journal_id': category_id.journal_id.id, + 'line_ids': [(0, 0, move_line_1), (0, 0, move_line_2)], + } + move = self.env['account.move'].create(move_vals) + self.write({'move_id': move.id, 'move_check': True}) + created_moves |= move + + if post_move and created_moves: + self.post_lines_and_close_asset() + created_moves.post() + return [x.id for x in created_moves] + + api.multi def post_lines_and_close_asset(self): # we re-evaluate the assets to determine whether we can close them for line in self: @@ -500,11 +568,11 @@ class AccountAssetDepreciationLine(models.Model): class AccountMove(models.Model): _inherit = 'account.move' - asset_id = fields.Many2one('account.asset.asset', string='Asset', ondelete="restrict") + asset_depreciation_ids = fields.One2many('account.asset.depreciation.line', 'move_id', string='Assets Depreciation Lines', ondelete="restrict") @api.multi def post(self): for move in self: - if move.asset_id: - move.asset_id.depreciation_line_ids.post_lines_and_close_asset() + for depreciation_line in move.asset_depreciation_ids: + depreciation_line.post_lines_and_close_asset() return super(AccountMove, self).post() diff --git a/addons/account_asset/account_asset_demo.yml b/addons/account_asset/account_asset_demo.yml index 5fa541ca2bb..22cfc0cff35 100644 --- a/addons/account_asset/account_asset_demo.yml +++ b/addons/account_asset/account_asset_demo.yml @@ -15,18 +15,18 @@ 'journal_id': journal_id[0], 'name': 'Hardware - 3 Years', 'method_number': 3, - 'account_asset_id': expense_account_id[0], + 'account_asset_id': xfa_account_id[0], 'account_depreciation_id': xfa_account_id[0], - 'account_income_recognition_id': expense_account_id[0], + 'account_depreciation_expense_id': expense_account_id[0], } self._update(cr, uid, 'account.asset.category', 'account_asset', vals, 'account_asset_category_fixedassets0') vals = { 'journal_id': journal_id[0], 'name': 'Cars - 5 Years', 'method_number': 5, - 'account_asset_id': expense_account_id[0], + 'account_asset_id': xfa_account_id[0], 'account_depreciation_id': xfa_account_id[0], - 'account_income_recognition_id': expense_account_id[0], + 'account_depreciation_expense_id': expense_account_id[0], } self._update(cr, uid, 'account.asset.category', 'account_asset', vals, 'account_asset_category_fixedassets1') vals = { diff --git a/addons/account_asset/account_asset_invoice.py b/addons/account_asset/account_asset_invoice.py index 0312063c907..26c35dd7903 100644 --- a/addons/account_asset/account_asset_invoice.py +++ b/addons/account_asset/account_asset_invoice.py @@ -70,12 +70,19 @@ class AccountInvoiceLine(models.Model): return True @api.onchange('product_id') - def onchange_product_id(self): + def _onchange_product_id(self): + vals = super(AccountInvoiceLine, self)._onchange_product_id() if self.product_id: if self.invoice_id.type == 'out_invoice': self.asset_category_id = self.product_id.product_tmpl_id.deferred_revenue_category_id elif self.invoice_id.type == 'in_invoice': self.asset_category_id = self.product_id.product_tmpl_id.asset_category_id + return vals + + @api.onchange('asset_category_id') + def onchange_asset_category_id(self): + if self.asset_category_id: + self.account_id = self.asset_category_id.account_asset_id def _set_additional_fields(self, invoice): if not self.asset_category_id: @@ -90,13 +97,3 @@ class ProductTemplate(models.Model): _inherit = 'product.template' asset_category_id = fields.Many2one('account.asset.category', string='Asset Type', ondelete="restrict") deferred_revenue_category_id = fields.Many2one('account.asset.category', string='Deferred Revenue Type', ondelete="restrict") - - @api.onchange('deferred_revenue_category_id') - def onchange_revenue_category_id(self): - if self.deferred_revenue_category_id: - self.property_account_income_id = self.deferred_revenue_category_id.account_depreciation_id.id - - @api.onchange('asset_category_id') - def onchange_asset(self): - if self.asset_category_id: - self.property_account_expense_id = self.asset_category_id.account_asset_id diff --git a/addons/account_asset/test/account_asset_demo_test.xml b/addons/account_asset/test/account_asset_demo_test.xml index 55951f7d817..bba5a04a20a 100644 --- a/addons/account_asset/test/account_asset_demo_test.xml +++ b/addons/account_asset/test/account_asset_demo_test.xml @@ -6,7 +6,8 @@ --> - + + Hardware - 3 Years @@ -15,7 +16,8 @@ - + + Cars - 5 Years @@ -59,45 +61,5 @@ - - diff --git a/addons/account_asset/tests/test_account_asset.py b/addons/account_asset/tests/test_account_asset.py index dce2cda9d19..0027c6db815 100644 --- a/addons/account_asset/tests/test_account_asset.py +++ b/addons/account_asset/tests/test_account_asset.py @@ -16,12 +16,6 @@ class TestAccountAsset(common.TransactionCase): self._load('account', 'test', 'account_minimal_test.xml') self._load('account_asset', 'test', 'account_asset_demo_test.xml') - # self.browse_ref("account_asset.data_fiscalyear_plus1").create_period() - # self.browse_ref("account_asset.data_fiscalyear_plus2").create_period() - # self.browse_ref("account_asset.data_fiscalyear_plus3").create_period() - # self.browse_ref("account_asset.data_fiscalyear_plus4").create_period() - # self.browse_ref("account_asset.data_fiscalyear_plus5").create_period() - # In order to test the process of Account Asset, I perform a action to confirm Account Asset. self.browse_ref("account_asset.account_asset_asset_vehicles_test0").validate() @@ -42,7 +36,7 @@ class TestAccountAsset(common.TransactionCase): # I check the move line is created. asset = self.env['account.asset.asset'].browse([self.ref("account_asset.account_asset_asset_vehicles_test0")])[0] - self.assertEqual(len(asset.depreciation_line_ids), len(asset.account_move_ids), + self.assertEqual(len(asset.depreciation_line_ids), asset.entry_count, 'Move lines not created correctly') # I Check that After creating all the moves of depreciation lines the state "Close". @@ -51,7 +45,6 @@ class TestAccountAsset(common.TransactionCase): # WIZARD # I create a record to change the duration of asset for calculating depreciation. - account_asset_asset_office0 = self.browse_ref('account_asset.account_asset_asset_office_test0') asset_modify_number_0 = self.env['asset.modify'].create({ 'name': 'Test reason', @@ -63,7 +56,6 @@ class TestAccountAsset(common.TransactionCase): # I check the proper depreciation lines created. self.assertEqual(account_asset_asset_office0.method_number, len(account_asset_asset_office0.depreciation_line_ids) - 1) # I compute a asset on period. - context = { "active_ids": [self.ref("account_asset.menu_asset_depreciation_confirmation_wizard")], "active_id": self.ref('account_asset.menu_asset_depreciation_confirmation_wizard'), diff --git a/addons/account_asset/views/account_asset_invoice_view.xml b/addons/account_asset/views/account_asset_invoice_view.xml index 1bb0432be72..aebd076638d 100644 --- a/addons/account_asset/views/account_asset_invoice_view.xml +++ b/addons/account_asset/views/account_asset_invoice_view.xml @@ -7,7 +7,7 @@ account.invoice - + diff --git a/addons/account_asset/views/account_asset_view.xml b/addons/account_asset/views/account_asset_view.xml index 0cb1f6feaa6..b7e2135c11f 100644 --- a/addons/account_asset/views/account_asset_view.xml +++ b/addons/account_asset/views/account_asset_view.xml @@ -30,9 +30,14 @@
+
+
+ @@ -44,7 +49,10 @@ months + + + @@ -240,28 +248,6 @@ - - Journal Entries (form) - account.move - - - - - - - - - - Journal Entries (Search) - account.move - - - - - - - - Assets account.asset.asset diff --git a/addons/account_asset/wizard/wizard_asset_compute.py b/addons/account_asset/wizard/wizard_asset_compute.py index 0a3711a7f76..a7925dc4475 100644 --- a/addons/account_asset/wizard/wizard_asset_compute.py +++ b/addons/account_asset/wizard/wizard_asset_compute.py @@ -12,18 +12,14 @@ class AssetDepreciationConfirmationWizard(models.TransientModel): def asset_compute(self): self.ensure_one() context = self._context - assets = self.env['account.asset.asset'].search([('state', '=', 'open'), ('category_id.type', '=', context.get('asset_type'))]) - created_move_ids = assets._compute_entries(self.date) - if context.get('asset_type') == 'purchase': - title = _('Created Asset Moves') - else: - title = _('Created Revenue Moves') + created_move_ids = self.env['account.asset.asset'].compute_generated_entries(self.date, asset_type=context.get('asset_type')) + return { - 'name': title, + 'name': _('Created Asset Moves') if context.get('asset_type') == 'purchase' else _('Created Revenue Moves'), 'view_type': 'form', 'view_mode': 'tree,form', 'res_model': 'account.move', 'view_id': False, - 'domain': "[('id','in',["+','.join(map(str, created_move_ids))+"])]", + 'domain': "[('id','in',[" + ','.join(map(str, created_move_ids)) + "])]", 'type': 'ir.actions.act_window', }