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 @@ + + + + + + + + +