From c4477405a976daa0ed60ddd91d5c97d25d42e282 Mon Sep 17 00:00:00 2001 From: Bernat Brunet Torruella Date: Tue, 21 Jul 2015 16:29:24 +0200 Subject: [PATCH] When create a product with or without kit, you need to inform first the main the aprent analytic account. When product is a kit an analytic structure will be create and this structure will be different if the checkbox of analytic by reference are checket or not --- __init__.py | 2 + product.py | 82 +++++++- product.xml | 13 ++ tests/scenario_analytic_product_account.rst | 50 ++++- tests/scenario_analytic_product_work.rst | 205 ++++++++++++++++++++ tests/test_analytic_product_account.py | 10 +- tryton.cfg | 1 + view/template_form.xml | 12 ++ 8 files changed, 364 insertions(+), 11 deletions(-) create mode 100644 product.xml create mode 100644 tests/scenario_analytic_product_work.rst create mode 100644 view/template_form.xml diff --git a/__init__.py b/__init__.py index c67265a..3ff7afa 100644 --- a/__init__.py +++ b/__init__.py @@ -8,4 +8,6 @@ def register(): Pool.register( Account, ProductKitLine, + Template, + Product, module='analytic_product_account', type_='model') diff --git a/product.py b/product.py index 04dcbf6..a1419d9 100644 --- a/product.py +++ b/product.py @@ -1,16 +1,18 @@ from trytond.model import fields from trytond.pool import Pool, PoolMeta +from trytond.pyson import Eval, Bool from trytond.transaction import Transaction __metaclass__ = PoolMeta -__all__ = ['Account', 'ProductKitLine'] +__all__ = ['Account', 'ProductKitLine', 'Template', 'Product'] class Account: __name__ = 'analytic_account.account' kit_line = fields.Many2One('product.kit.line', 'Kit Line') + parent_kit_line = fields.Many2One('product.product', 'Parent Kit Line') class ProductKitLine: @@ -41,15 +43,30 @@ class ProductKitLine: return product_kit_lines def get_missing_analytic_accounts(self): - existing = {a.parent for a in self.analytic_accounts} - accounts = {a for a in self.parent.template.analytic_accounts.accounts} - return [self.get_analytic_account(p) for p in accounts - existing] + res = [] + template = self.parent.template + if not template.parent_analytic_account: + return res + parents = [template.parent_analytic_account] + if template.create_analytic_by_reference: + if not self.parent.parent_analytic_accounts: + parent = self.get_analytic_account( + template.parent_analytic_account, + name=self.parent.template.name) + parent.kit_line = None + parent.parent_kit_line = self.parent + parent.save() + parents = self.parent.parent_analytic_accounts + return [self.get_analytic_account(p) for p in parents] - def get_analytic_account(self, parent): + def get_analytic_account(self, parent, name=None): pool = Pool() AnalyticAccount = pool.get('analytic_account.account') account = AnalyticAccount() - account.name = self.product.template.name + if name is None: + account.name = self.product.template.name + else: + account.name = name account.parent = parent account.root = parent.root account.type = 'normal' @@ -108,3 +125,56 @@ class ProductKitLine: } values.append(value) return values + + +class Template: + __name__ = 'product.template' + + parent_analytic_account = fields.Many2One('analytic_account.account', + 'Parent Analytic Account') + create_analytic_by_reference = fields.Boolean( + 'Create Analytic By Reference', help=('If marked an analytic account' + ' will be created for the parent product of the kit')) + + @staticmethod + def default_create_analytic_by_reference(): + return True + + @classmethod + def __setup__(cls): + super(Template, cls).__setup__() + if 'parent_analytic_account' not in cls.analytic_accounts.depends: + states = cls.analytic_accounts.states + readonly = states.get('readonly', False) + cls.analytic_accounts.states.update({ + 'readonly': (Bool(readonly) | + ~Bool(Eval('parent_analytic_account'))), + }) + cls.analytic_accounts.depends.append('parent_analytic_account') + + +class Product: + __name__ = 'product.product' + + parent_analytic_accounts = fields.One2Many('analytic_account.account', + 'parent_kit_line', 'Analytic Accounts') + analytic_configured = fields.Function(fields.Boolean( + 'Analytic Configured'), + 'on_change_with_analytic_configured') + + @classmethod + def __setup__(cls): + super(Product, cls).__setup__() + if (hasattr(cls, 'kit_lines') and + 'analytic_configured' not in cls.kit_lines.depends): + states = cls.kit_lines.states + invisible = states.get('invisible') + cls.kit_lines.states.update({ + 'invisible': invisible | ~Eval('analytic_configured'), + }) + cls.kit_lines.depends.append('analytic_configured') + + def on_change_with_analytic_configured(self, name=None): + if self.template and self.template.parent_analytic_account: + return True + return False diff --git a/product.xml b/product.xml new file mode 100644 index 0000000..3a5c6e5 --- /dev/null +++ b/product.xml @@ -0,0 +1,13 @@ + + + + + + product.template + + form + template_form + + + diff --git a/tests/scenario_analytic_product_account.rst b/tests/scenario_analytic_product_account.rst index f15b0c7..77da9b9 100644 --- a/tests/scenario_analytic_product_account.rst +++ b/tests/scenario_analytic_product_account.rst @@ -114,6 +114,9 @@ Create analytic accounts:: >>> analytic_account = AnalyticAccount(root=root, parent=root, ... name='Analytic') >>> analytic_account.save() + >>> reference_analytic_account = AnalyticAccount(root=root, parent=root, + ... name='Reference Analytic') + >>> reference_analytic_account.save() Create parent product:: @@ -134,10 +137,8 @@ Create parent product:: >>> template.cost_price_method = 'fixed' >>> template.account_expense = expense >>> template.account_revenue = revenue - >>> analytic_selection = AnalyticSelection() - >>> analytic_selection.accounts.append(analytic_account) - >>> analytic_selection.save() - >>> template.analytic_accounts = analytic_selection + >>> template.parent_analytic_account = analytic_account + >>> template.create_analytic_by_reference = False >>> template.save() >>> product.template = template >>> product.kit = True @@ -194,6 +195,10 @@ Make a kit from parent product and check that analytic accounts are created:: u'Component A' >>> account_b.name u'Component B' + >>> account_a.parent.name + u'Analytic' + >>> account_b.parent.name + u'Analytic' If we delete some component analytic account is also deleted:: @@ -245,3 +250,40 @@ Components can't be deleted if exists entries on their analytic accounts:: ... UserError: ('UserError', (u'You cannot delete component "Component A" because it has associated costs.', '')) + +Create analytic accounts with reference:: + + >>> product = Product() + >>> template = ProductTemplate() + >>> template.name = 'Parent with reference' + >>> template.default_uom = unit + >>> template.type = 'service' + >>> template.purchasable = True + >>> template.salable = True + >>> template.list_price = Decimal('10') + >>> template.cost_price = Decimal('5') + >>> template.cost_price_method = 'fixed' + >>> template.account_expense = expense + >>> template.account_revenue = revenue + >>> template.parent_analytic_account = reference_analytic_account + >>> template.create_analytic_by_reference = True + >>> template.save() + >>> product.template = template + >>> product.kit = True + >>> product.save() + >>> kit_line = product.kit_lines.new() + >>> kit_line.product = component_a + >>> kit_line.quantity = 1 + >>> kit_line = product.kit_lines.new() + >>> kit_line.product = component_b + >>> kit_line.quantity = 2 + >>> product.save() + >>> reference_analytic_account.reload() + >>> intermediate_account, = reference_analytic_account.childs + >>> intermediate_account.name + u'Parent with reference' + >>> account_a, account_b = intermediate_account.childs + >>> account_a.name + u'Component A' + >>> account_b.name + u'Component B' diff --git a/tests/scenario_analytic_product_work.rst b/tests/scenario_analytic_product_work.rst new file mode 100644 index 0000000..5f93257 --- /dev/null +++ b/tests/scenario_analytic_product_work.rst @@ -0,0 +1,205 @@ +============================== +Analytic Product Work Scenario +============================== + +============= +General Setup +============= + +Imports:: + + >>> import datetime + >>> from dateutil.relativedelta import relativedelta + >>> from decimal import Decimal + >>> from proteus import config, Model, Wizard + >>> today = datetime.date.today() + +Create database:: + + >>> config = config.set_trytond() + >>> config.pool.test = True + +Install modules:: + + >>> Module = Model.get('ir.module.module') + >>> modules = Module.find([ + ... ('name', 'in', ['analytic_product_account', + ... 'analytic_product_work'])]) + >>> Module.install([x.id for x in modules], config.context) + >>> Wizard('ir.module.module.install_upgrade').execute('upgrade') + +Create company:: + + >>> Currency = Model.get('currency.currency') + >>> CurrencyRate = Model.get('currency.currency.rate') + >>> Company = Model.get('company.company') + >>> Party = Model.get('party.party') + >>> company_config = Wizard('company.company.config') + >>> company_config.execute('company') + >>> company = company_config.form + >>> party = Party(name='Dunder Mifflin') + >>> party.save() + >>> company.party = party + >>> currencies = Currency.find([('code', '=', 'USD')]) + >>> if not currencies: + ... currency = Currency(name='US Dollar', symbol='$', code='USD', + ... rounding=Decimal('0.01'), mon_grouping='[3, 3, 0]', + ... mon_decimal_point='.') + ... currency.save() + ... CurrencyRate(date=today + relativedelta(month=1, day=1), + ... rate=Decimal('1.0'), currency=currency).save() + ... else: + ... currency, = currencies + >>> company.currency = currency + >>> company_config.execute('add') + >>> company, = Company.find() + +Reload the context:: + + >>> User = Model.get('res.user') + >>> config._context = User.get_preferences(True, config.context) + +Create chart of accounts:: + + >>> AccountTemplate = Model.get('account.account.template') + >>> Account = Model.get('account.account') + >>> account_template, = AccountTemplate.find([('parent', '=', None)]) + >>> create_chart = Wizard('account.create_chart') + >>> create_chart.execute('account') + >>> create_chart.form.account_template = account_template + >>> create_chart.form.company = company + >>> create_chart.execute('create_account') + >>> receivable, = Account.find([ + ... ('kind', '=', 'receivable'), + ... ('company', '=', company.id), + ... ]) + >>> payable, = Account.find([ + ... ('kind', '=', 'payable'), + ... ('company', '=', company.id), + ... ]) + >>> revenue, = Account.find([ + ... ('kind', '=', 'revenue'), + ... ('company', '=', company.id), + ... ]) + >>> expense, = Account.find([ + ... ('kind', '=', 'expense'), + ... ('company', '=', company.id), + ... ]) + >>> create_chart.form.account_receivable = receivable + >>> create_chart.form.account_payable = payable + >>> create_chart.execute('create_properties') + +Create fiscal year:: + + >>> FiscalYear = Model.get('account.fiscalyear') + >>> Sequence = Model.get('ir.sequence') + >>> SequenceStrict = Model.get('ir.sequence.strict') + >>> fiscalyear = FiscalYear(name='%s' % today.year) + >>> fiscalyear.start_date = today + relativedelta(month=1, day=1) + >>> fiscalyear.end_date = today + relativedelta(month=12, day=31) + >>> fiscalyear.company = company + >>> post_move_sequence = Sequence(name='%s' % today.year, + ... code='account.move', company=company) + >>> post_move_sequence.save() + >>> fiscalyear.post_move_sequence = post_move_sequence + >>> fiscalyear.save() + >>> FiscalYear.create_period([fiscalyear.id], config.context) + >>> period = fiscalyear.periods[0] + + +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() + >>> reference_analytic_account = AnalyticAccount(root=root, parent=root, + ... name='Reference Analytic') + >>> reference_analytic_account.save() + +Create parent product:: + + >>> ProductUom = Model.get('product.uom') + >>> ProductTemplate = Model.get('product.template') + >>> Product = Model.get('product.product') + >>> AnalyticSelection = Model.get('analytic_account.account.selection') + >>> unit, = ProductUom.find([('name', '=', 'Unit')]) + >>> product = Product() + >>> template = ProductTemplate() + >>> template.name = 'Parent' + >>> template.default_uom = unit + >>> template.type = 'service' + >>> template.purchasable = True + >>> template.salable = True + >>> template.list_price = Decimal('10') + >>> template.cost_price = Decimal('5') + >>> template.cost_price_method = 'fixed' + >>> template.account_expense = expense + >>> template.account_revenue = revenue + >>> template.parent_analytic_account = analytic_account + >>> template.create_analytic_by_reference = False + >>> template.save() + >>> product.template = template + >>> product.kit = True + >>> product.save() + +Create component A (service) and component B (good):: + + >>> component_a = Product() + >>> template = ProductTemplate() + >>> template.name = 'Component A' + >>> template.default_uom = unit + >>> template.type = 'service' + >>> template.purchasable = True + >>> template.salable = True + >>> template.list_price = Decimal('10') + >>> template.cost_price = Decimal('5') + >>> template.cost_price_method = 'fixed' + >>> template.account_expense = expense + >>> template.account_revenue = revenue + >>> template.save() + >>> component_a.template = template + >>> component_a.save() + + >>> component_b = Product() + >>> template = ProductTemplate() + >>> template.name = 'Component B' + >>> template.default_uom = unit + >>> template.type = 'goods' + >>> template.purchasable = True + >>> template.salable = True + >>> template.list_price = Decimal('10') + >>> template.cost_price = Decimal('5') + >>> template.cost_price_method = 'fixed' + >>> template.account_expense = expense + >>> template.account_revenue = revenue + >>> template.save() + >>> component_b.template = template + >>> component_b.save() + +Make a kit from parent product and check that analytic accounts are created:: + + >>> len(analytic_account.childs) + 0 + >>> kit_line = product.kit_lines.new() + >>> kit_line.product = component_a + >>> kit_line.quantity = 1 + >>> kit_line = product.kit_lines.new() + >>> kit_line.product = component_b + >>> kit_line.quantity = 2 + >>> product.save() + >>> analytic_account.reload() + >>> account_a, account_b = analytic_account.childs + >>> account_a.name + u'Component A' + >>> account_b.name + u'Component B' + >>> kit_component_a, kit_component_b = product.kit_lines + >>> component_a_work, = kit_component_a.product.works + >>> component_b_work, = kit_component_b.product.works + >>> component_a_work.rec_name + u'Root\\Analytic\\Component A\\Component A' + >>> component_b_work.rec_name + u'Root\\Analytic\\Component B\\Component B' diff --git a/tests/test_analytic_product_account.py b/tests/test_analytic_product_account.py index 4ff55bd..35fda98 100644 --- a/tests/test_analytic_product_account.py +++ b/tests/test_analytic_product_account.py @@ -3,7 +3,7 @@ import unittest import doctest import trytond.tests.test_tryton -from trytond.tests.test_tryton import test_depends +from trytond.tests.test_tryton import test_view, test_depends from trytond.tests.test_tryton import doctest_setup, doctest_teardown @@ -13,6 +13,10 @@ class TestCase(unittest.TestCase): def setUp(self): trytond.tests.test_tryton.install_module('analytic_product_account') + def test0005views(self): + 'Test views' + test_view('analytic_product_account') + def test0006depends(self): 'Test depends' test_depends() @@ -25,4 +29,8 @@ def suite(): 'scenario_analytic_product_account.rst', setUp=doctest_setup, tearDown=doctest_teardown, encoding='utf-8', optionflags=doctest.REPORT_ONLY_FIRST_FAILURE)) + suite.addTests(doctest.DocFileSuite( + 'scenario_analytic_product_work.rst', + setUp=doctest_setup, tearDown=doctest_teardown, encoding='utf-8', + optionflags=doctest.REPORT_ONLY_FIRST_FAILURE)) return suite diff --git a/tryton.cfg b/tryton.cfg index e08658c..fd6d19b 100644 --- a/tryton.cfg +++ b/tryton.cfg @@ -6,3 +6,4 @@ depends: extras_depend: analytic_product_work xml: + product.xml diff --git a/view/template_form.xml b/view/template_form.xml new file mode 100644 index 0000000..b28d9ae --- /dev/null +++ b/view/template_form.xml @@ -0,0 +1,12 @@ + + + + + +