From 43a7b9799fda03012c1c016510ef4caf5fcb6d34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Wed, 12 Oct 2016 22:36:54 +0200 Subject: [PATCH] Add support for depreciable assets The analytic entries should not be created when posting the invoice for depreciable assets. Instead entries must be created when the assets are depreciated. issue5909 review26731002 --- CHANGELOG | 2 + __init__.py | 2 + asset.py | 52 +++++++++ asset.xml | 12 ++ invoice.py | 36 +++++- tests/scenario_analytic_invoice_asset.rst | 127 ++++++++++++++++++++++ tests/test_analytic_invoice.py | 4 + tryton.cfg | 2 + view/asset_form.xml | 10 ++ 9 files changed, 243 insertions(+), 4 deletions(-) create mode 100644 asset.py create mode 100644 asset.xml create mode 100644 tests/scenario_analytic_invoice_asset.rst create mode 100644 view/asset_form.xml diff --git a/CHANGELOG b/CHANGELOG index 98668f856e..1710c8bd1b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Add support for depreciable assets + Version 4.0.0 - 2016-05-02 * Bug fixes (see mercurial logs for details) * Add Python3 support diff --git a/__init__.py b/__init__.py index 9487c80123..644128c681 100644 --- a/__init__.py +++ b/__init__.py @@ -3,10 +3,12 @@ from trytond.pool import Pool from .invoice import * +from . import asset def register(): Pool.register( InvoiceLine, AnalyticAccountEntry, + asset.Asset, module='analytic_invoice', type_='model') diff --git a/asset.py b/asset.py new file mode 100644 index 0000000000..25a5d47526 --- /dev/null +++ b/asset.py @@ -0,0 +1,52 @@ +# This file is part of Tryton. The COPYRIGHT file at the top level of +# this repository contains the full copyright notices and license terms. +from trytond.pool import Pool, PoolMeta +from trytond.model import fields + +from trytond.modules.analytic_account import AnalyticMixin + +__all__ = ['Asset'] + + +class Asset(AnalyticMixin): + __metaclass__ = PoolMeta + __name__ = 'account.asset' + + @fields.depends('supplier_invoice_line', 'analytic_accounts') + def on_change_supplier_invoice_line(self): + pool = Pool() + Entry = pool.get('analytic.account.entry') + + super(Asset, self).on_change_supplier_invoice_line() + if self.supplier_invoice_line: + entries = [] + for entry in self.supplier_invoice_line.analytic_accounts: + new_entry = Entry() + for field in Entry._fields: + if field in {'origin', 'id'}: + continue + setattr(new_entry, field, getattr(entry, field)) + entries.append(new_entry) + self.analytic_accounts = entries + + def get_move(self, line): + move = super(Asset, self).get_move(line) + self.set_analytic_lines(move) + return move + + def get_closing_move(self, account): + move = super(Asset, self).get_closing_move(account) + self.set_analytic_lines(move) + return move + + def set_analytic_lines(self, move): + "Fill analytic lines on lines with expense account" + if self.analytic_accounts: + for line in move.lines: + if line.account != self.product.account_expense_used: + continue + analytic_lines = [] + for entry in self.analytic_accounts: + analytic_lines.extend( + entry.get_analytic_lines(line, move.date)) + line.analytic_lines = analytic_lines diff --git a/asset.xml b/asset.xml new file mode 100644 index 0000000000..bdbceb6e33 --- /dev/null +++ b/asset.xml @@ -0,0 +1,12 @@ + + + + + + account.asset + + asset_form + + + diff --git a/invoice.py b/invoice.py index 1b4eae120a..f75d95315c 100644 --- a/invoice.py +++ b/invoice.py @@ -10,6 +10,7 @@ __all__ = ['InvoiceLine', 'AnalyticAccountEntry'] class InvoiceLine(AnalyticMixin): + __metaclass__ = PoolMeta __name__ = 'account.invoice.line' @classmethod @@ -37,7 +38,14 @@ class InvoiceLine(AnalyticMixin): def get_move_lines(self): lines = super(InvoiceLine, self).get_move_lines() - if self.analytic_accounts: + if self.invoice and self.invoice.type: + type_ = self.invoice.type + else: + type_ = self.invoice_type + asset_depreciable = (self.product and type_ == 'in' + and self.product.type == 'assets' + and getattr(self.product, 'depreciable', False)) + if self.analytic_accounts and not asset_depreciable: date = self.invoice.accounting_date or self.invoice.invoice_date for line in lines: analytic_lines = [] @@ -54,24 +62,44 @@ class AnalyticAccountEntry: @classmethod def _get_origin(cls): + pool = Pool() origins = super(AnalyticAccountEntry, cls)._get_origin() - return origins + ['account.invoice.line'] + origins.append('account.invoice.line') + try: + pool.get('account.asset') + origins.append('account.asset') + except KeyError: + pass + return origins @fields.depends('origin') def on_change_with_company(self, name=None): pool = Pool() InvoiceLine = pool.get('account.invoice.line') + try: + Asset = pool.get('account.asset') + except KeyError: + Asset = None company = super(AnalyticAccountEntry, self).on_change_with_company( name) - if isinstance(self.origin, InvoiceLine): + if (isinstance(self.origin, InvoiceLine) + or (Asset and isinstance(self.origin, Asset))): company = self.origin.company.id return company @classmethod def search_company(cls, name, clause): + pool = Pool() domain = super(AnalyticAccountEntry, cls).search_company(name, clause), - return ['OR', + domain = ['OR', domain, (('origin.company',) + tuple(clause[1:]) + ('account.invoice.line',)), ] + try: + pool.get('account.asset') + domain.append( + (('origin.company',) + tuple(clause[1:]) + ('account.asset',))) + except KeyError: + pass + return domain diff --git a/tests/scenario_analytic_invoice_asset.rst b/tests/scenario_analytic_invoice_asset.rst new file mode 100644 index 0000000000..bd4657d775 --- /dev/null +++ b/tests/scenario_analytic_invoice_asset.rst @@ -0,0 +1,127 @@ +===================================== +Analytic Invoice with Assets Scenario +===================================== + +Imports:: + + >>> import datetime + >>> from dateutil.relativedelta import relativedelta + >>> from decimal import Decimal + >>> from operator import attrgetter + >>> from proteus import Model, Wizard + >>> from trytond.tests.tools import install_modules + >>> from trytond.modules.company.tests.tools import create_company, \ + ... get_company + >>> from trytond.modules.account.tests.tools import create_fiscalyear, \ + ... create_chart, get_accounts + >>> from trytond.modules.account_invoice.tests.tools import \ + ... set_fiscalyear_invoice_sequences, create_payment_term + >>> from trytond.modules.account_asset.tests.tools \ + ... import add_asset_accounts + >>> today = datetime.date.today() + +Install analytic_invoice and account_asset:: + + >>> config = install_modules(['analytic_invoice', 'account_asset']) + +Create company:: + + >>> _ = create_company() + >>> company = get_company() + +Create fiscal year:: + + >>> fiscalyear = set_fiscalyear_invoice_sequences( + ... create_fiscalyear(company)) + >>> fiscalyear.click('create_period') + +Create chart of accounts:: + + >>> _ = create_chart(company) + >>> accounts = add_asset_accounts(get_accounts(company), company) + >>> revenue = accounts['revenue'] + >>> asset_account = accounts['asset'] + >>> expense = accounts['expense'] + >>> depreciation_account = accounts['depreciation'] + +Create analytic accounts:: + + >>> AnalyticAccount = Model.get('analytic_account.account') + >>> root = AnalyticAccount(type='root', name='Root') + >>> root.save() + >>> analytic_account = AnalyticAccount(root=root, parent=root, + ... name='Analytic') + >>> analytic_account.save() + +Create supplier:: + + >>> Party = Model.get('party.party') + >>> supplier = Party(name='Supplier') + >>> supplier.save() + +Create an asset:: + + >>> ProductUom = Model.get('product.uom') + >>> unit, = ProductUom.find([('name', '=', 'Unit')]) + >>> ProductTemplate = Model.get('product.template') + >>> Product = Model.get('product.product') + >>> asset_product = Product() + >>> asset_template = ProductTemplate() + >>> asset_template.name = 'Asset' + >>> asset_template.type = 'assets' + >>> asset_template.default_uom = unit + >>> asset_template.list_price = Decimal('1000') + >>> asset_template.cost_price = Decimal('1000') + >>> asset_template.depreciable = True + >>> asset_template.account_expense = expense + >>> asset_template.account_revenue = revenue + >>> asset_template.account_asset = asset_account + >>> asset_template.account_depreciation = depreciation_account + >>> asset_template.depreciation_duration = Decimal(10) + >>> asset_template.save() + >>> asset_product.template = asset_template + >>> asset_product.save() + +Buy an asset:: + + >>> Invoice = Model.get('account.invoice') + >>> InvoiceLine = Model.get('account.invoice.line') + >>> supplier_invoice = Invoice(type='in') + >>> supplier_invoice.party = supplier + >>> invoice_line = supplier_invoice.lines.new() + >>> invoice_line.product = asset_product + >>> invoice_line.quantity = 1 + >>> invoice_line.unit_price = Decimal('1000') + >>> entry, = invoice_line.analytic_accounts + >>> entry.account = analytic_account + >>> supplier_invoice.invoice_date = today + relativedelta(day=1, month=1) + >>> supplier_invoice.click('post') + >>> supplier_invoice.state + u'posted' + >>> invoice_line, = supplier_invoice.lines + >>> analytic_account.debit + Decimal('0.00') + >>> analytic_account.credit + Decimal('0.00') + +Depreciate the asset:: + + >>> Asset = Model.get('account.asset') + >>> asset = Asset() + >>> asset.product = asset_product + >>> asset.supplier_invoice_line = invoice_line + >>> asset.residual_value = Decimal(0) + >>> asset.click('create_lines') + >>> asset.click('run') + +Create Moves for 1 month:: + + >>> create_moves = Wizard('account.asset.create_moves') + >>> create_moves.form.date = (supplier_invoice.invoice_date + ... + relativedelta(months=1)) + >>> create_moves.execute('create_moves') + >>> analytic_account.reload() + >>> analytic_account.debit + Decimal('100.00') + >>> analytic_account.credit + Decimal('0.00') diff --git a/tests/test_analytic_invoice.py b/tests/test_analytic_invoice.py index 94d7dfc1a4..0cc8f0ecef 100644 --- a/tests/test_analytic_invoice.py +++ b/tests/test_analytic_invoice.py @@ -21,4 +21,8 @@ def suite(): tearDown=doctest_teardown, encoding='utf-8', checker=doctest_checker, optionflags=doctest.REPORT_ONLY_FIRST_FAILURE)) + suite.addTests(doctest.DocFileSuite('scenario_analytic_invoice_asset.rst', + tearDown=doctest_teardown, encoding='utf-8', + checker=doctest_checker, + optionflags=doctest.REPORT_ONLY_FIRST_FAILURE)) return suite diff --git a/tryton.cfg b/tryton.cfg index 0f4693a17e..01d02a466b 100644 --- a/tryton.cfg +++ b/tryton.cfg @@ -3,5 +3,7 @@ version=4.1.0 depends: account_invoice analytic_account +extras_depend: + account_asset xml: invoice.xml diff --git a/view/asset_form.xml b/view/asset_form.xml new file mode 100644 index 0000000000..c19a11b42c --- /dev/null +++ b/view/asset_form.xml @@ -0,0 +1,10 @@ + + + + + + + + +