Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[10.0][ADD] Add default analytic account for accounts #152

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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