Skip to content

Commit

Permalink
[10.0][ADD] Add default analytic account for accounts (OCA#152)
Browse files Browse the repository at this point in the history
Add the ability to set default analytic account on accounts.
This is useful when, e.g. you use the account_analytic_required
and have multi-currencies, thus need to create an automatic
currency exchange difference entry.
  • Loading branch information
p-tombez authored and Zar21 committed May 17, 2021
1 parent 0b0ff72 commit f9f6779
Show file tree
Hide file tree
Showing 8 changed files with 411 additions and 0 deletions.
58 changes: 58 additions & 0 deletions account_analytic_default_account/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License

================================
Account Analytic Default Account
================================

This module adds the ability to set a default analytic account on
accounts.

Configuration
=============

Go to *Accounting -> Configuration -> Analytic Defaults*
and set the corresponding settings.

Usage
=====

.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/87/10.0

Bug Tracker
===========

Bugs are tracked on `GitHub Issues
<https://github.com/OCA/account-analytic/issues>`_. In case of trouble, please
check there if your issue has already been reported. If you spotted it first,
help us smashing it by providing a detailed and welcomed feedback.

Credits
=======

Images
------

* Odoo Community Association: `Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_.

Contributors
------------

* Patrick Tombez <[email protected]>

Maintainer
----------
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

This module is maintained by the OCA.

OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.

To contribute to this module, please visit https://odoo-community.org.
4 changes: 4 additions & 0 deletions account_analytic_default_account/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from . import models
20 changes: 20 additions & 0 deletions account_analytic_default_account/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Copyright 2018 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
{
'name': 'Account Analytic Default Account',
'version': '10.0.1.0.0',
'category': 'Analytic Accounting',
'license': 'AGPL-3',
'author': "Odoo Community Association (OCA)",
'website': 'https://github.com/OCA/account-analytic',
'depends': [
'account',
'analytic',
'account_analytic_default'
],
'data': [
'views/account_analytic_default_account_view.xml'
],
'installable': True,
}
4 changes: 4 additions & 0 deletions account_analytic_default_account/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from . import account_analytic_default_account
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# -*- coding: utf-8 -*-
# Copyright 2018 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)

from odoo import api, fields, models


class AccountAnalyticDefaultAccount(models.Model):
_inherit = "account.analytic.default"

account_id = fields.Many2one(
'account.account', string='Account',
ondelete='cascade',
help="Select the account corresponding to an analytic account "
"which will be used on the lines of invoices or account moves"
)

def _account_get_domain(self, **kw):
"""Build account.analytic.default domain.
:param kw: filter keys.
Available filter keys are defined into `account_get_domain_keys`.
:return: a search domain with all required keys' leaves.
Each key will be searched for both False and specified value.
For instance: [
('product_id'), '=', False), '|', [('product_id'), '=', 100)
]
"""
domain = []
# date will be handled differently
date_val = kw.pop('date')

for key, value in kw.iteritems():
if value:
domain += ['|', (key, '=', kw.get(key))]
domain += [(key, '=', False)]

if date_val:
domain += ['|', ('date_start', '<=', date_val),
('date_start', '=', False)]
domain += ['|', ('date_stop', '>=', date_val),
('date_stop', '=', False)]
return domain

@api.model
def account_get(self, product_id=None, partner_id=None, user_id=None,
date=None, company_id=None, account_id=None):
"""Search the records matching the built domain,
compute an index score based on the actual record values then
return the record with the highest index score"""
filters = {
'product_id': product_id,
'partner_id': partner_id,
'user_id': user_id,
'date': date,
'company_id': company_id,
'account_id': account_id,
}
domain = self._account_get_domain(**filters)
keys = filters.keys()
if 'date' in keys:
keys.remove('date')
keys += ['date_start', 'date_stop']
best_index = -1
res = self.env['account.analytic.default']
for rec in self.search(domain):
index = 0
for k in keys:
if rec[k]:
index += 1
if index > best_index:
res = rec
best_index = index
return res


class AccountInvoiceLine(models.Model):
_inherit = "account.invoice.line"

@api.onchange('product_id', 'account_id')
def _onchange_product_id(self):
res = super(AccountInvoiceLine, self)._onchange_product_id()
rec = self.env['account.analytic.default'].account_get(
product_id=self.product_id.id,
partner_id=self.invoice_id.partner_id.id,
user_id=self.env.uid, date=fields.Date.today(),
company_id=self.company_id.id, account_id=self.account_id.id
)
self.account_analytic_id = rec.analytic_id.id
return res

def _set_additional_fields(self, invoice):
if not self.account_analytic_id:
rec = self.env['account.analytic.default'].account_get(
product_id=self.product_id.id,
partner_id=self.invoice_id.partner_id.id,
user_id=self.env.uid, date=fields.Date.today(),
company_id=self.company_id.id, account_id=self.account_id.id
)
if rec:
self.account_analytic_id = rec.analytic_id.id
super(AccountInvoiceLine, self)._set_additional_fields(invoice)


class AccountMoveLine(models.Model):
_inherit = "account.move.line"

@api.onchange('account_id')
def _onchange_account_id(self):
for line in self:
if line.account_id and not line.analytic_account_id:
rec = self.env['account.analytic.default'].account_get(
account_id=line.account_id.id)
if rec:
line.analytic_account_id = rec.analytic_id.id

@api.multi
def _set_default_analytic_account(self):
for line in self:
if not line.analytic_account_id:
rec = self.env['account.analytic.default'].account_get(
account_id=line.account_id.id)
if rec:
line.analytic_account_id = rec.analytic_id.id

@api.model
def create(self, vals):
if 'analytic_account_id' not in vals:
rec = self.env['account.analytic.default'].account_get(
account_id=vals.get('account_id'))
if rec:
vals['analytic_account_id'] = rec.analytic_id.id
return super(AccountMoveLine, self).create(vals)


class AccountMove(models.Model):
_inherit = "account.move"

@api.multi
def post(self):
self.mapped('line_ids')._set_default_analytic_account()
return super(AccountMove, self).post()
4 changes: 4 additions & 0 deletions account_analytic_default_account/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)

from . import test_analytic_default_account
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# -*- coding: utf-8 -*-
# Copyright 2018 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)

from odoo.tests import common
import time


class TestAnalyticDefaultAccount(common.SavepointCase):

@classmethod
def setUpClass(cls):
super(TestAnalyticDefaultAccount, cls).setUpClass()

cls.account_analytic_default_model = \
cls.env['account.analytic.default']
cls.analytic_account_model = cls.env['account.analytic.account']
cls.invoice_model = cls.env['account.invoice']
cls.invoice_line_model = cls.env['account.invoice.line']
cls.move_obj = cls.env['account.move']
cls.move_line_obj = cls.env['account.move.line']

cls.partner_agrolait = cls.env.ref("base.res_partner_2")
cls.product = cls.env.ref("product.product_product_4")
cls.account_receivable = cls.env['account.account'].search(
[('user_type_id', '=',
cls.env.ref('account.data_account_type_receivable').id)],
limit=1
)
cls.account_sales = cls.env['account.account'].create({
'code': "X1020",
'name': "Product Sales - (test)",
'user_type_id': cls.env.ref('account.data_account_type_revenue').id
})

cls.sales_journal = cls.env['account.journal'].create({
'name': "Sales Journal - (test)",
'code': "TSAJ",
'type': "sale",
'refund_sequence': True,
'default_debit_account_id': cls.account_sales.id,
'default_credit_account_id': cls.account_sales.id,
})

cls.analytic_account_1 = cls.analytic_account_model.create(
{'name': 'test 1'})
cls.analytic_account_2 = cls.analytic_account_model.create(
{'name': 'test 2'})
cls.analytic_account_3 = cls.analytic_account_model.create(
{'name': 'test 3'})
cls.analytic_account_4 = cls.analytic_account_model.create(
{'name': 'test 4'})

cls.account_analytic_default_model.create({
'product_id': cls.product.id,
'analytic_id': cls.analytic_account_1.id
})
cls.account_analytic_default_model.create({
'partner_id': cls.partner_agrolait.id,
'analytic_id': cls.analytic_account_2.id
})
cls.account_analytic_default_model.create({
'product_id': cls.product.id,
'account_id': cls.account_sales.id,
'analytic_id': cls.analytic_account_3.id
})
cls.account_analytic_default_model.create({
'account_id': cls.account_sales.id,
'analytic_id': cls.analytic_account_4.id
})

def create_invoice(self, amount=100, type='out_invoice'):
""" Returns an open invoice """
invoice = self.invoice_model.create({
'partner_id': self.partner_agrolait.id,
'reference_type': 'none',
'name': (type == 'out_invoice' and 'invoice to client' or
'invoice to supplier'),
'account_id': self.account_receivable.id,
'type': type,
'date_invoice': time.strftime('%Y') + '-06-26',
})
self.invoice_line_model.create({
'product_id': self.product.id,
'quantity': 1,
'price_unit': amount,
'invoice_id': invoice.id,
'name': 'something',
'account_id': self.account_sales.id
})
invoice.action_invoice_open()
return invoice

def create_move(self, amount=100):
ml_obj = self.move_line_obj.with_context(check_move_validity=False)
move_vals = {
'name': '/',
'journal_id': self.sales_journal.id,
'date': time.strftime('%Y') + '-07-25',
}
move = self.move_obj.create(move_vals)
move_line_1 = ml_obj.create(
{'move_id': move.id,
'name': '/',
'debit': 0,
'credit': amount,
'account_id': self.account_sales.id})
move_line_2 = ml_obj.create(
{'move_id': move.id,
'name': '/',
'debit': amount,
'credit': 0,
'account_id': self.account_receivable.id,
})
return move, move_line_1, move_line_2

def test_account_analytic_default_get_account(self):
rec = self.account_analytic_default_model.account_get(
account_id=self.account_sales.id
)
self.assertEqual(self.analytic_account_4.id, rec.analytic_id.id)

rec = self.account_analytic_default_model.account_get(
account_id=self.account_receivable.id
)
self.assertFalse(rec.id)

def test_account_analytic_default_invoice(self):
invoice = self.create_invoice()
self.assertFalse(invoice.invoice_line_ids[0].account_analytic_id.id)
invoice.invoice_line_ids[0]._set_additional_fields(invoice)
self.assertEqual(invoice.invoice_line_ids[0].account_analytic_id,
self.analytic_account_3)

def test_account_analytic_default_account_move(self):
move, move_line_1, move_line_2 = self.create_move()
self.assertEqual(move_line_1.analytic_account_id,
self.analytic_account_4)
self.assertFalse(move_line_2.analytic_account_id.id)
Loading

0 comments on commit f9f6779

Please sign in to comment.