##############################################################################
#
# Copyright (c) 2005-2009 Nexedi SA and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################

import unittest

from DateTime import DateTime

from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from Products.ZSQLCatalog.SQLCatalog import ComplexQuery, Query
from AccessControl import getSecurityManager
from Products.ERP5Type.tests.utils import updateCellList

class TestBudget(ERP5TypeTestCase):
  
  def afterSetUp(self):
    self.validateRules()
    product_line = self.portal.portal_categories.product_line
    if '1' not in product_line.objectIds():
      category = product_line.newContent(portal_type='Category', id='1')
      category.newContent(portal_type='Category', id='1.1')
      category.newContent(portal_type='Category', id='1.2')
    if '2' not in product_line.objectIds():
      category = product_line.newContent(portal_type='Category', id='2')
      category.newContent(portal_type='Category', id='2.1')
      category.newContent(portal_type='Category', id='2.2')

  def beforeTearDown(self):
    self.abort()
    self.portal.accounting_module.manage_delObjects(
       list(self.portal.accounting_module.objectIds()))
    self.tic()

  def getBusinessTemplateList(self):
    """Return the list of required business templates.
    We'll use erp5_accounting_ui_test to have some content
    """
    return ('erp5_core_proxy_field_legacy',
            'erp5_base', 'erp5_pdm', 'erp5_simulation', 'erp5_trade', 'erp5_accounting',
            'erp5_invoicing', 'erp5_simplified_invoicing',
            'erp5_accounting_ui_test', 'erp5_budget',
            'erp5_configurator_standard_solver',
            'erp5_configurator_standard_trade_template',
            'erp5_configurator_standard_accounting_template',
            'erp5_configurator_standard_invoicing_template',
            'erp5_simulation_test')

  # creation and basic functionalities
  def test_simple_create_budget_model(self):
    budget_model = self.portal.budget_model_module.newContent(
                            portal_type='Budget Model')
    budget_model.newContent(
                    portal_type='Category Budget Variation',
                    int_index=1,
                    budget_variation='budget_cell',
                    inventory_axis='node_category',
                    variation_base_category='site',
                    )
    self.assertEqual([], budget_model.checkConsistency())

  def test_simple_create_budget(self):
    budget = self.portal.budget_module.newContent(
                            portal_type='Budget')
    budget_line = budget.newContent(portal_type='Budget Line')
    budget_cell = budget_line.newContent(portal_type='Budget Cell')
    self.assertEqual([], budget.checkConsistency())

  def test_budget_cell_node_variation_with_aggregate(self):
    budget_model = self.portal.budget_model_module.newContent(
                            portal_type='Budget Model')
    budget_model.newContent(
                    portal_type='Node Budget Variation',
                    int_index=1,
                    budget_variation='budget_cell',
                    inventory_axis='node',
                    variation_base_category='source',
                    aggregate_value_list=(
                      self.portal.account_module.goods_purchase,
                      self.portal.account_module.fixed_assets,
                    ))
    budget = self.portal.budget_module.newContent(
                    portal_type='Budget',
                    specialise_value=budget_model)
    budget_line = budget.newContent(portal_type='Budget Line')
    self.assertEqual(['source'],
                      budget_line.getVariationBaseCategoryList())
    self.assertEqual(
        [('Goods Purchase', 'source/account_module/goods_purchase'),
         ('Fixed Assets', 'source/account_module/fixed_assets')],
        budget_line.BudgetLine_getVariationRangeCategoryList())

    budget_line.setVariationCategoryList(
         ('source/account_module/goods_purchase',))
    self.assertEqual(
        ['source/account_module/goods_purchase'],
        budget_line.getVariationCategoryList())
  
    # This was a budget cell variation, so no criterion is set on budget line
    self.assertEqual(budget_line.getMembershipCriterionCategoryList(), [])
    self.assertEqual(
        budget_line.getMembershipCriterionBaseCategoryList(), [])
    

    # simuate a request and call Base_edit, which does all the work of creating
    # cell and setting cell properties.
    form = budget_line.BudgetLine_view
    self.portal.REQUEST.other.update(
        dict(AUTHENTICATED_USER=getSecurityManager().getUser(),

             field_membership_criterion_base_category_list=
        form.membership_criterion_base_category_list.get_value('default'),
             field_mapped_value_property_list=
        form.mapped_value_property_list.get_value('default'),

             field_matrixbox_quantity_cell_0_0_0="5",
             field_matrixbox_membership_criterion_category_list_cell_0_0_0=[
               'source/account_module/goods_purchase'],
        ))
    budget_line.Base_edit(form_id=form.getId())

    self.assertEqual(1, len(budget_line.contentValues()))
    budget_cell = budget_line.getCell('source/account_module/goods_purchase')
    self.assertNotEquals(None, budget_cell)

    self.assertEqual(['source/account_module/goods_purchase'],
        budget_cell.getMembershipCriterionCategoryList())
    self.assertEqual(5, budget_cell.getQuantity())

    # there is no budget consumption
    self.assertEqual(0, budget_cell.getConsumedBudget())
    self.assertEqual(0, budget_cell.getEngagedBudget())
    self.assertEqual(5, budget_cell.getAvailableBudget())
    # there is no budget transfer
    self.assertEqual(5, budget_cell.getCurrentBalance())


  def test_category_budget_cell_variation(self):
    budget_model = self.portal.budget_model_module.newContent(
                            portal_type='Budget Model')
    budget_model.newContent(
                    portal_type='Category Budget Variation',
                    int_index=1,
                    budget_variation='budget_cell',
                    inventory_axis='node_category',
                    variation_base_category='account_type',)
    budget = self.portal.budget_module.newContent(
                    portal_type='Budget',
                    specialise_value=budget_model)
    budget_line = budget.newContent(portal_type='Budget Line')
    self.assertEqual(['account_type'],
                      budget_line.getVariationBaseCategoryList())

    variation_range_category_list = \
       budget_line.BudgetLine_getVariationRangeCategoryList()
    self.assertTrue(['', ''] in variation_range_category_list)
    self.assertTrue(['Expense', 'account_type/expense'] in variation_range_category_list)

  def test_category_budget_line_variation(self):
    # test that using a variation on budget line level sets membership
    # criterion on budget line
    budget_model = self.portal.budget_model_module.newContent(
                            portal_type='Budget Model')
    budget_model.newContent(
                    portal_type='Category Budget Variation',
                    int_index=1,
                    budget_variation='budget_line',
                    inventory_axis='section_category',
                    variation_base_category='group',)
    budget = self.portal.budget_module.newContent(
                    portal_type='Budget',
                    specialise_value=budget_model)
    budget_line = budget.newContent(portal_type='Budget Line')

    self.assertEqual(['group'],
                      budget_line.getVariationBaseCategoryList())

    variation_range_category_list = \
       budget_line.BudgetLine_getVariationRangeCategoryList()

    self.assertTrue(['', ''] in variation_range_category_list)
    self.assertTrue(['Demo Group', 'group/demo_group'] in variation_range_category_list)
    
    budget_line.edit(variation_category_list=['group/demo_group'])
    self.assertEqual(['group'],
        budget_line.getMembershipCriterionBaseCategoryList())
    self.assertEqual(['group/demo_group'],
        budget_line.getMembershipCriterionCategoryList())

  def test_category_budget_line_and_budget_cell_variation(self):
    # test that using a variation on budget line level sets membership
    # criterion on budget line, but not on budget cell
    budget_model = self.portal.budget_model_module.newContent(
                            portal_type='Budget Model')
    budget_model.newContent(
                    portal_type='Category Budget Variation',
                    int_index=1,
                    budget_variation='budget_line',
                    inventory_axis='section_category',
                    variation_base_category='group',)
    budget_model.newContent(
                    portal_type='Category Budget Variation',
                    int_index=2,
                    budget_variation='budget_cell',
                    inventory_axis='node_category',
                    variation_base_category='account_type',)
    budget = self.portal.budget_module.newContent(
                    portal_type='Budget',
                    specialise_value=budget_model)
    budget_line = budget.newContent(portal_type='Budget Line')

    self.assertEqual(['group', 'account_type'],
                      budget_line.getVariationBaseCategoryList())
    
    budget_line.edit(variation_category_list=['group/demo_group',
                                              'account_type/expense'])
    self.assertEqual(['group'],
        budget_line.getMembershipCriterionBaseCategoryList())
    self.assertEqual(['group/demo_group'],
        budget_line.getMembershipCriterionCategoryList())

    form = budget_line.BudgetLine_view
    self.portal.REQUEST.other.update(
        dict(AUTHENTICATED_USER=getSecurityManager().getUser(),

             field_membership_criterion_base_category_list=
        form.membership_criterion_base_category_list.get_value('default'),
             field_mapped_value_property_list=
        form.mapped_value_property_list.get_value('default'),

             field_matrixbox_quantity_cell_0_0_0="1",
             field_matrixbox_membership_criterion_category_list_cell_0_0_0=[
               'account_type/expense'],
        ))
    budget_line.Base_edit(form_id=form.getId())

    self.assertEqual(1, len(budget_line.contentValues()))
    budget_cell = budget_line.getCell('account_type/expense')
    self.assertEqual(['account_type'],
                       budget_cell.getMembershipCriterionBaseCategoryList())
    self.assertEqual(['account_type/expense'],
                       budget_cell.getMembershipCriterionCategoryList())


  def test_category_budget_variation(self):
    budget_model = self.portal.budget_model_module.newContent(
                            portal_type='Budget Model')
    budget_model.newContent(
                    portal_type='Category Budget Variation',
                    int_index=1,
                    budget_variation='budget',
                    inventory_axis='section_category',
                    variation_base_category='group',)
    budget = self.portal.budget_module.newContent(
                    portal_type='Budget',
                    specialise_value=budget_model)

    self.assertEqual(['group'],
                      budget.getVariationBaseCategoryList())

    variation_range_category_list = \
       budget.Budget_getVariationRangeCategoryList()

    self.assertTrue(['', ''] in variation_range_category_list)
    self.assertTrue(['Demo Group', 'group/demo_group'] in variation_range_category_list)

    # setting this variation on the budget also sets membership
    budget.edit(variation_category_list=['group/demo_group'])
    self.assertEqual('demo_group', budget.getGroup())
    self.assertEqual('Demo Group', budget.getGroupTitle())

  # consumptions
  def test_simple_consumption(self):
    budget_model = self.portal.budget_model_module.newContent(
                            portal_type='Budget Model')
    budget_model.newContent(
                    portal_type='Category Budget Variation',
                    int_index=1,
                    budget_variation='budget',
                    inventory_axis='section_category',
                    variation_base_category='group',)
    budget_model.newContent(
                    portal_type='Node Budget Variation',
                    int_index=2,
                    budget_variation='budget_cell',
                    inventory_axis='node',
                    variation_base_category='source',
                    aggregate_value_list=(
                      self.portal.account_module.goods_purchase,
                      self.portal.account_module.fixed_assets,
                    ))
    budget_model.newContent(
                    portal_type='Category Budget Variation',
                    int_index=3,
                    budget_variation='budget_cell',
                    inventory_axis='node_category',
                    variation_base_category='account_type',)

    budget = self.portal.budget_module.newContent(
                    portal_type='Budget',
                    start_date_range_min=DateTime(2000, 1, 1),
                    start_date_range_max=DateTime(2000, 12, 31),
                    specialise_value=budget_model)

    budget.edit(variation_category_list=['group/demo_group'])
    budget_line = budget.newContent(portal_type='Budget Line')

    # set the range, this will adjust the matrix
    budget_line.edit(
        variation_category_list=(
          'source/account_module/goods_purchase',
          'source/account_module/fixed_assets',
          'account_type/expense',
          'account_type/asset', ))

    # simuate a request and call Base_edit, which does all the work of creating
    # cell and setting cell properties.
    form = budget_line.BudgetLine_view
    self.portal.REQUEST.other.update(
        dict(AUTHENTICATED_USER=getSecurityManager().getUser(),

             field_membership_criterion_base_category_list=
        form.membership_criterion_base_category_list.get_value('default'),
             field_mapped_value_property_list=
        form.mapped_value_property_list.get_value('default'),

             field_matrixbox_quantity_cell_0_0_0="",
             field_matrixbox_membership_criterion_category_list_cell_0_0_0=[],
             field_matrixbox_quantity_cell_1_0_0="2",
             field_matrixbox_membership_criterion_category_list_cell_1_0_0=[
               'source/account_module/fixed_assets',
               'account_type/asset'],
             field_matrixbox_quantity_cell_0_1_0="1",
             field_matrixbox_membership_criterion_category_list_cell_0_1_0=[
               'source/account_module/goods_purchase',
               'account_type/expense'],
             field_matrixbox_quantity_cell_1_1_0="",
             field_matrixbox_membership_criterion_category_list_cell_1_1_0=[],
        ))
    budget_line.Base_edit(form_id=form.getId())

    self.assertEqual(2, len(budget_line.contentValues()))
    budget_cell = budget_line.getCell('source/account_module/goods_purchase',
                                      'account_type/expense')
    self.assertNotEquals(None, budget_cell)
    self.assertEqual(
        dict(from_date=DateTime(2000, 1, 1),
             at_date=DateTime(2000, 12, 31).latestTime(),
             node_category=['account_type/expense'],
             node_uid=[self.portal.account_module.goods_purchase.getUid()],
             section_category=['group/demo_group'],),
        budget_model.getInventoryQueryDict(budget_cell))

    budget_cell = budget_line.getCell('source/account_module/fixed_assets',
                                      'account_type/asset')
    self.assertNotEquals(None, budget_cell)
    self.assertEqual(
        dict(from_date=DateTime(2000, 1, 1),
             at_date=DateTime(2000, 12, 31).latestTime(),
             node_category=['account_type/asset'],
             node_uid=[self.portal.account_module.fixed_assets.getUid()],
             section_category=['group/demo_group'],),
        budget_model.getInventoryQueryDict(budget_cell))

    self.assertEqual(
        dict(from_date=DateTime(2000, 1, 1),
             at_date=DateTime(2000, 12, 31).latestTime(),
             node_category=['account_type/expense', 'account_type/asset'],
             node_uid=[self.portal.account_module.goods_purchase.getUid(),
                       self.portal.account_module.fixed_assets.getUid()],
             section_category=['group/demo_group'],
             group_by_node_category=True,
             group_by_node=True,
             group_by_section_category=True,
             ),
        budget_model.getInventoryListQueryDict(budget_line))


    atransaction = self.portal.accounting_module.newContent(
                  portal_type='Accounting Transaction',
                  resource_value=self.portal.currency_module.euro,
                  source_section_value=self.portal.organisation_module.my_organisation,
                  start_date=DateTime(2000, 1, 2))
    atransaction.newContent(
                  portal_type='Accounting Transaction Line',
                  source_value=self.portal.account_module.goods_purchase,
                  source_debit=100)
    atransaction.newContent(
                  portal_type='Accounting Transaction Line',
                  source_value=self.portal.account_module.fixed_assets,
                  source_credit=100)
    atransaction.confirm()

    # a confirmed transaction engages budget
    self.tic()

    self.assertEqual(dict(), budget_line.getConsumedBudgetDict())

    self.assertEqual(
      {('source/account_module/fixed_assets', 'account_type/asset'): -100.0,
       ('source/account_module/goods_purchase', 'account_type/expense'): 100.0},
        budget_line.getEngagedBudgetDict())

    self.assertEqual(
      {('source/account_module/fixed_assets', 'account_type/asset'): 102.0,
       ('source/account_module/goods_purchase', 'account_type/expense'): -99.0},
        budget_line.getAvailableBudgetDict())

    atransaction.stop()
    # a stopped transaction consumes budget
    self.tic()

    self.assertEqual(
      {('source/account_module/fixed_assets', 'account_type/asset'): -100.0,
       ('source/account_module/goods_purchase', 'account_type/expense'): 100.0},
        budget_line.getConsumedBudgetDict())

    self.assertEqual(
      {('source/account_module/fixed_assets', 'account_type/asset'): -100.0,
       ('source/account_module/goods_purchase', 'account_type/expense'): 100.0},
        budget_line.getEngagedBudgetDict())

    self.assertEqual(
      {('source/account_module/fixed_assets', 'account_type/asset'): 102.0,
       ('source/account_module/goods_purchase', 'account_type/expense'): -99.0},
        budget_line.getAvailableBudgetDict())
      
    # we can view the forms without error
    budget_line.BudgetLine_viewEngagedBudget()
    budget_line.BudgetLine_viewConsumedBudget()
    budget_line.BudgetLine_viewAvailableBudget()

  def test_all_other_and_strict_consumption(self):
    # tests consumptions, by using "all other" virtual node on a node budget
    # variation, and strict membership on category budget variation
    budget_model = self.portal.budget_model_module.newContent(
                            portal_type='Budget Model')
    budget_model.newContent(
                    portal_type='Category Budget Variation',
                    int_index=1,
                    budget_variation='budget',
                    inventory_axis='section_category_strict_membership',
                    variation_base_category='group',)
    budget_model.newContent(
                    portal_type='Node Budget Variation',
                    int_index=2,
                    budget_variation='budget_cell',
                    inventory_axis='node',
                    variation_base_category='source',
                    aggregate_value_list=(
                      self.portal.account_module.goods_purchase,),
                    include_virtual_other_node=True)
    budget_model.newContent(
                    portal_type='Category Budget Variation',
                    int_index=3,
                    budget_variation='budget_cell',
                    inventory_axis='node_category_strict_membership',
                    variation_base_category='account_type',)

    budget = self.portal.budget_module.newContent(
                    portal_type='Budget',
                    start_date_range_min=DateTime(2000, 1, 1),
                    start_date_range_max=DateTime(2000, 12, 31),
                    specialise_value=budget_model)

    budget.edit(variation_category_list=['group/demo_group/sub1'])
    budget_line = budget.newContent(portal_type='Budget Line')

    # set the range, this will adjust the matrix
    budget_line.edit(
        variation_category_list=(
          'source/account_module/goods_purchase',
          'source/budget_special_node/all_other', # this is 'all others'
          'account_type/expense',
          'account_type/asset', ))

    # simuate a request and call Base_edit, which does all the work of creating
    # cell and setting cell properties.
    form = budget_line.BudgetLine_view
    self.portal.REQUEST.other.update(
        dict(AUTHENTICATED_USER=getSecurityManager().getUser(),

             field_membership_criterion_base_category_list=
        form.membership_criterion_base_category_list.get_value('default'),
             field_mapped_value_property_list=
        form.mapped_value_property_list.get_value('default'),

             field_matrixbox_quantity_cell_0_0_0="",
             field_matrixbox_membership_criterion_category_list_cell_0_0_0=[],
             field_matrixbox_quantity_cell_1_0_0="2",
             field_matrixbox_membership_criterion_category_list_cell_1_0_0=[
               'source/budget_special_node/all_other',
               'account_type/asset'],
             field_matrixbox_quantity_cell_0_1_0="1",
             field_matrixbox_membership_criterion_category_list_cell_0_1_0=[
               'source/account_module/goods_purchase',
               'account_type/expense'],
             field_matrixbox_quantity_cell_1_1_0="",
             field_matrixbox_membership_criterion_category_list_cell_1_1_0=[],
        ))
    budget_line.Base_edit(form_id=form.getId())

    self.assertEqual(2, len(budget_line.contentValues()))

    self.assertEqual(
        dict(from_date=DateTime(2000, 1, 1),
             at_date=DateTime(2000, 12, 31).latestTime(),
             node_category_strict_membership=['account_type/expense',
                                              'account_type/asset'],
             section_category_strict_membership=['group/demo_group/sub1'],
             group_by_node_category_strict_membership=True,
             group_by_node=True,
             group_by_section_category_strict_membership=True,
             ),
        budget_model.getInventoryListQueryDict(budget_line))


    atransaction = self.portal.accounting_module.newContent(
                  portal_type='Accounting Transaction',
                  resource_value=self.portal.currency_module.euro,
                  source_section_value=self.portal.organisation_module.my_organisation,
                  start_date=DateTime(2000, 1, 2))
    atransaction.newContent(
                  portal_type='Accounting Transaction Line',
                  source_value=self.portal.account_module.goods_purchase,
                  source_debit=100)
    atransaction.newContent(
                  portal_type='Accounting Transaction Line',
                  source_value=self.portal.account_module.fixed_assets,
                  source_credit=100)
    atransaction.stop()

    self.tic()

    self.assertEqual(
      {('source/budget_special_node/all_other', 'account_type/asset'): -100.0,
       ('source/account_module/goods_purchase', 'account_type/expense'): 100.0},
        budget_line.getConsumedBudgetDict())

    self.assertEqual(
      {('source/budget_special_node/all_other', 'account_type/asset'): -100.0,
       ('source/account_module/goods_purchase', 'account_type/expense'): 100.0},
        budget_line.getEngagedBudgetDict())

    self.assertEqual(
      {('source/budget_special_node/all_other', 'account_type/asset'): 102.0,
       ('source/account_module/goods_purchase', 'account_type/expense'): -99.0},
        budget_line.getAvailableBudgetDict())
   
  def test_none_virtual_node(self):
    # tests consumptions, by using "none" virtual node on a node budget
    # variation
    budget_model = self.portal.budget_model_module.newContent(
                            portal_type='Budget Model')
    budget_model.newContent(
                    portal_type='Node Budget Variation',
                    int_index=1,
                    budget_variation='budget_cell',
                    # this does not work for movement, node and section
                    # categories ...
                    inventory_axis='project',
                    variation_base_category='source_project',
                    aggregate_value_list=(
                      self.portal.organisation_module.my_organisation,),
                    include_virtual_none_node=True)
    budget_model.newContent(
                    portal_type='Category Budget Variation',
                    int_index=2,
                    budget_variation='budget_line',
                    inventory_axis='node_category_strict_membership',
                    variation_base_category='account_type',)

    budget = self.portal.budget_module.newContent(
                    portal_type='Budget',
                    start_date_range_min=DateTime(2000, 1, 1),
                    start_date_range_max=DateTime(2000, 12, 31),
                    specialise_value=budget_model)

    budget_line = budget.newContent(portal_type='Budget Line')

    budget_line.edit(
        variation_category_list=(
          'source_project/organisation_module/my_organisation',
          'source_project/budget_special_node/none', # this is 'none'
          'account_type/expense',))

    form = budget_line.BudgetLine_view
    self.portal.REQUEST.other.update(
        dict(AUTHENTICATED_USER=getSecurityManager().getUser(),

             field_membership_criterion_base_category_list=
        form.membership_criterion_base_category_list.get_value('default'),
             field_mapped_value_property_list=
        form.mapped_value_property_list.get_value('default'),

             field_matrixbox_quantity_cell_0_0_0="100",
             field_matrixbox_membership_criterion_category_list_cell_0_0_0=[
               'source_project/organisation_module/my_organisation',],
             field_matrixbox_quantity_cell_1_0_0="200",
             field_matrixbox_membership_criterion_category_list_cell_1_0_0=[
               'source_project/budget_special_node/none',],
        ))
    budget_line.Base_edit(form_id=form.getId())

    self.assertEqual(2, len(budget_line.contentValues()))

    class ReferenceQuery:
      """Helper class to compare queries
      """
      def __eq__(me, query):
        self.assertTrue(isinstance(query, ComplexQuery))
        self.assertEqual(query.logical_operator, 'or')
        self.assertEqual(2, len(query.query_list))
        self.assertEqual(query.query_list[0].kw, {'project_uid': None})
        self.assertEqual(query.query_list[1].kw,
          {'project_uid':
            [self.portal.organisation_module.my_organisation.getUid()]})
        return True

    self.assertEqual(
        dict(from_date=DateTime(2000, 1, 1),
             at_date=DateTime(2000, 12, 31).latestTime(),
             node_category_strict_membership=['account_type/expense',],
             project_uid=ReferenceQuery(),
             group_by_node_category_strict_membership=True,
             group_by_project=True,
             ),
        budget_model.getInventoryListQueryDict(budget_line))

    atransaction = self.portal.accounting_module.newContent(
                  portal_type='Accounting Transaction',
                  source_section_value=self.portal.organisation_module.my_organisation,
                  resource_value=self.portal.currency_module.euro,
                  start_date=DateTime(2000, 1, 2))
    atransaction.newContent(
                  portal_type='Accounting Transaction Line',
                  source_value=self.portal.account_module.goods_purchase,
                  source_project_value=self.portal.organisation_module.my_organisation,
                  source_debit=200)
    atransaction.newContent(
                  portal_type='Accounting Transaction Line',
                  source_value=self.portal.account_module.goods_purchase,
                  source_credit=300)
    atransaction.stop()
    
    self.tic()

    self.assertEqual(
      {('source_project/organisation_module/my_organisation',): 200.0,
       ('source_project/budget_special_node/none',): -300.0
       }, budget_line.getConsumedBudgetDict())

    self.assertEqual(
      {('source_project/organisation_module/my_organisation',): 200.0,
       ('source_project/budget_special_node/none',): -300.0
       }, budget_line.getEngagedBudgetDict())
   
  def test_only_none_virtual_node(self):
    # tests consumptions, by using only "none" virtual node on a node budget
    # variation
    budget_model = self.portal.budget_model_module.newContent(
                            portal_type='Budget Model')
    budget_model.newContent(
                    portal_type='Node Budget Variation',
                    int_index=1,
                    budget_variation='budget_cell',
                    # this does not work for movement, node and section
                    # categories ...
                    inventory_axis='project',
                    variation_base_category='source_project',
                    aggregate_value_list=(
                      self.portal.organisation_module.my_organisation,),
                    include_virtual_none_node=True)
    budget_model.newContent(
                    portal_type='Category Budget Variation',
                    int_index=2,
                    budget_variation='budget_line',
                    inventory_axis='node_category_strict_membership',
                    variation_base_category='account_type',)

    budget = self.portal.budget_module.newContent(
                    portal_type='Budget',
                    start_date_range_min=DateTime(2000, 1, 1),
                    start_date_range_max=DateTime(2000, 12, 31),
                    specialise_value=budget_model)

    budget_line = budget.newContent(portal_type='Budget Line')

    budget_line.edit(
        variation_category_list=(
          'source_project/budget_special_node/none', # this is 'none'
          'account_type/expense',))

    form = budget_line.BudgetLine_view
    self.portal.REQUEST.other.update(
        dict(AUTHENTICATED_USER=getSecurityManager().getUser(),

             field_membership_criterion_base_category_list=
        form.membership_criterion_base_category_list.get_value('default'),
             field_mapped_value_property_list=
        form.mapped_value_property_list.get_value('default'),

             field_matrixbox_quantity_cell_0_0_0="200",
             field_matrixbox_membership_criterion_category_list_cell_0_0_0=[
               'source_project/budget_special_node/none',],
        ))
    budget_line.Base_edit(form_id=form.getId())

    self.assertEqual(1, len(budget_line.contentValues()))

    class ReferenceQuery:
      """Helper class to compare queries
      """
      def __eq__(me, query):
        self.assertTrue(isinstance(query, Query))
        self.assertEqual(query.kw, {'project_uid': None})
        return True

    self.assertEqual(
        dict(from_date=DateTime(2000, 1, 1),
             at_date=DateTime(2000, 12, 31).latestTime(),
             node_category_strict_membership=['account_type/expense',],
             project_uid=ReferenceQuery(),
             group_by_node_category_strict_membership=True,
             group_by_project=True,
             ),
        budget_model.getInventoryListQueryDict(budget_line))

    budget_cell = budget_line.contentValues()[0]
    self.assertEqual(
        dict(from_date=DateTime(2000, 1, 1),
             at_date=DateTime(2000, 12, 31).latestTime(),
             node_category_strict_membership=['account_type/expense',],
             project_uid=ReferenceQuery(),
             ),
        budget_model.getInventoryQueryDict(budget_cell))

    atransaction = self.portal.accounting_module.newContent(
                  portal_type='Accounting Transaction',
                  source_section_value=self.portal.organisation_module.my_organisation,
                  resource_value=self.portal.currency_module.euro,
                  start_date=DateTime(2000, 1, 2))
    atransaction.newContent(
                  portal_type='Accounting Transaction Line',
                  source_value=self.portal.account_module.goods_purchase,
                  source_project_value=self.portal.organisation_module.my_organisation,
                  source_debit=200)
    atransaction.newContent(
                  portal_type='Accounting Transaction Line',
                  source_value=self.portal.account_module.goods_purchase,
                  source_credit=300)
    atransaction.stop()
    
    self.tic()

    self.assertEqual(
      {('source_project/budget_special_node/none',): -300.0
       }, budget_line.getConsumedBudgetDict())

    self.assertEqual(
      {('source_project/budget_special_node/none',): -300.0
       }, budget_line.getEngagedBudgetDict())

    self.assertEqual(-300, budget_cell.getConsumedBudget())

  def test_none_and_all_others_virtual_nodes_together(self):
    # tests consumptions, by using "none" and "all other" virtual nodes
    # together on a node budget variation
    budget_model = self.portal.budget_model_module.newContent(
                            portal_type='Budget Model')
    budget_model.newContent(
                    portal_type='Node Budget Variation',
                    int_index=1,
                    budget_variation='budget_cell',
                    inventory_axis='project',
                    variation_base_category='source_project',
                    aggregate_value_list=(
                      self.portal.organisation_module.my_organisation,),
                    include_virtual_other_node=True,
                    include_virtual_none_node=True)
    budget_model.newContent(
                    portal_type='Category Budget Variation',
                    int_index=2,
                    budget_variation='budget_line',
                    inventory_axis='node_category_strict_membership',
                    variation_base_category='account_type',)

    budget = self.portal.budget_module.newContent(
                    portal_type='Budget',
                    start_date_range_min=DateTime(2000, 1, 1),
                    start_date_range_max=DateTime(2000, 12, 31),
                    specialise_value=budget_model)

    budget_line = budget.newContent(portal_type='Budget Line')

    budget_line.edit(
        variation_category_list=(
          'source_project/organisation_module/my_organisation',
          'source_project/budget_special_node/none', # this is 'none'
          'source_project/budget_special_node/all_other', # this is 'all_other'
          'account_type/expense',))

    form = budget_line.BudgetLine_view
    self.portal.REQUEST.other.update(
        dict(AUTHENTICATED_USER=getSecurityManager().getUser(),

             field_membership_criterion_base_category_list=
        form.membership_criterion_base_category_list.get_value('default'),
             field_mapped_value_property_list=
        form.mapped_value_property_list.get_value('default'),

             field_matrixbox_quantity_cell_0_0_0="100",
             field_matrixbox_membership_criterion_category_list_cell_0_0_0=[
               'source_project/organisation_module/my_organisation',],
             field_matrixbox_quantity_cell_1_0_0="200",
             field_matrixbox_membership_criterion_category_list_cell_1_0_0=[
               'source_project/budget_special_node/none',],
             field_matrixbox_quantity_cell_2_0_0="300",
             field_matrixbox_membership_criterion_category_list_cell_2_0_0=[
               'source_project/budget_special_node/all_other',],
        ))
    budget_line.Base_edit(form_id=form.getId())

    self.assertEqual(3, len(budget_line.contentValues()))

    self.assertEqual(
        dict(from_date=DateTime(2000, 1, 1),
             at_date=DateTime(2000, 12, 31).latestTime(),
             node_category_strict_membership=['account_type/expense',],
             group_by_node_category_strict_membership=True,
             group_by_project=True,
             ),
        budget_model.getInventoryListQueryDict(budget_line))

    atransaction = self.portal.accounting_module.newContent(
                  portal_type='Accounting Transaction',
                  source_section_value=self.portal.organisation_module.my_organisation,
                  resource_value=self.portal.currency_module.euro,
                  start_date=DateTime(2000, 1, 2))
    atransaction.newContent(
                  portal_type='Accounting Transaction Line',
                  source_value=self.portal.account_module.goods_purchase,
                  source_project_value=self.portal.organisation_module.my_organisation,
                  source_debit=200)
    atransaction.newContent(
                  portal_type='Accounting Transaction Line',
                  source_value=self.portal.account_module.goods_purchase,
                  # this will count for all other
                  source_project_value=self.portal.organisation_module.client_1,
                  source_credit=80)
    atransaction.newContent(
                  portal_type='Accounting Transaction Line',
                  source_value=self.portal.account_module.goods_purchase,
                  # this will count for none
                  source_credit=120)
    atransaction.stop()
    
    self.tic()

    self.assertEqual(
      {('source_project/organisation_module/my_organisation',): 200.0,
       ('source_project/budget_special_node/all_other',): -80.0,
       ('source_project/budget_special_node/none',): -120.0
       }, budget_line.getConsumedBudgetDict())

    self.assertEqual(
      {('source_project/organisation_module/my_organisation',): 200.0,
       ('source_project/budget_special_node/all_other',): -80.0,
       ('source_project/budget_special_node/none',): -120.0
       }, budget_line.getEngagedBudgetDict())

  def test_full_consumption_detail_node_variation(self):
    # tests consumptions, by using "full consumption detail" on a node budget
    # variation
    budget_model = self.portal.budget_model_module.newContent(
                            portal_type='Budget Model')
    budget_model.newContent(
                    portal_type='Category Budget Variation',
                    int_index=1,
                    budget_variation='budget',
                    inventory_axis='section_category_strict_membership',
                    variation_base_category='group',)
    budget_model.newContent(
                    portal_type='Node Budget Variation',
                    int_index=2,
                    budget_variation='budget_cell',
                    inventory_axis='node',
                    variation_base_category='source',
                    aggregate_value_list=(
                      self.portal.account_module.goods_purchase,
                      self.portal.account_module.fixed_assets,
                    ),
                    full_consumption_detail=True)
    budget_model.newContent(
                    portal_type='Category Budget Variation',
                    int_index=3,
                    budget_variation='budget_cell',
                    inventory_axis='node_category_strict_membership',
                    variation_base_category='account_type',)

    budget = self.portal.budget_module.newContent(
                    portal_type='Budget',
                    start_date_range_min=DateTime(2000, 1, 1),
                    start_date_range_max=DateTime(2000, 12, 31),
                    specialise_value=budget_model)

    budget.edit(variation_category_list=['group/demo_group/sub1'])
    budget_line = budget.newContent(portal_type='Budget Line')

    # set the range, this will adjust the matrix
    budget_line.edit(
        variation_category_list=(
          'source/account_module/goods_purchase',
            # Fixed assets is in the cell range, but is not selected
          'account_type/expense',
          'account_type/asset', ))

    # simuate a request and call Base_edit, which does all the work of creating
    # cell and setting cell properties.
    form = budget_line.BudgetLine_view
    self.portal.REQUEST.other.update(
        dict(AUTHENTICATED_USER=getSecurityManager().getUser(),

             field_membership_criterion_base_category_list=
        form.membership_criterion_base_category_list.get_value('default'),
             field_mapped_value_property_list=
        form.mapped_value_property_list.get_value('default'),

             field_matrixbox_quantity_cell_0_0_0="",
             field_matrixbox_membership_criterion_category_list_cell_0_0_0=[],
             field_matrixbox_quantity_cell_0_1_0="1",
             field_matrixbox_membership_criterion_category_list_cell_0_1_0=[
               'source/account_module/goods_purchase',
               'account_type/expense'],
        ))
    budget_line.Base_edit(form_id=form.getId())

    self.assertEqual(1, len(budget_line.contentValues()))

    # At this time there are no consumption, so consumption and definition cell
    # ranges are all the same.
    default_cell_range = [['source/account_module/goods_purchase'],
                           ['account_type/asset', 'account_type/expense']]

    self.assertEqual(default_cell_range,
        budget_line.BudgetLine_asCellRange('cell'))
    self.assertEqual(default_cell_range,
        budget_line.BudgetLine_asCellRange('consumed'))
    self.assertEqual(default_cell_range,
        budget_line.BudgetLine_asCellRange('engaged'))
    self.assertEqual(default_cell_range,
        budget_line.BudgetLine_asCellRange('available'))

    self.assertEqual(
        dict(from_date=DateTime(2000, 1, 1),
             at_date=DateTime(2000, 12, 31).latestTime(),
             node_category_strict_membership=['account_type/expense',
                                              'account_type/asset'],
             section_category_strict_membership=['group/demo_group/sub1'],
             group_by_node_category_strict_membership=True,
             group_by_node=True,
             node_uid=[self.portal.account_module.goods_purchase.getUid(),
                       self.portal.account_module.fixed_assets.getUid()],
             group_by_section_category_strict_membership=True,
             ),
        budget_model.getInventoryListQueryDict(budget_line))


    atransaction = self.portal.accounting_module.newContent(
                  portal_type='Accounting Transaction',
                  resource_value=self.portal.currency_module.euro,
                  source_section_value=self.portal.organisation_module.my_organisation,
                  start_date=DateTime(2000, 1, 2))
    atransaction.newContent(
                  portal_type='Accounting Transaction Line',
                  source_value=self.portal.account_module.goods_purchase,
                  source_debit=100)
    atransaction.newContent(
                  portal_type='Accounting Transaction Line',
                  source_value=self.portal.account_module.fixed_assets,
                  source_credit=100)
    atransaction.stop()

    self.tic()

    # Now that we have consumptions, consumption cell ranges are updated
    consumption_cell_range = [['source/account_module/goods_purchase',
                               'source/account_module/fixed_assets'],
                           ['account_type/asset', 'account_type/expense']]
    self.assertEqual(default_cell_range,
        budget_line.BudgetLine_asCellRange('cell'))
    self.assertEqual(consumption_cell_range,
        budget_line.BudgetLine_asCellRange('consumed'))
    self.assertEqual(consumption_cell_range,
        budget_line.BudgetLine_asCellRange('engaged'))
    self.assertEqual(consumption_cell_range,
        budget_line.BudgetLine_asCellRange('available'))

    self.assertEqual(
      {('source/account_module/fixed_assets', 'account_type/asset'): -100.0,
       ('source/account_module/goods_purchase', 'account_type/expense'): 100.0},
        budget_line.getConsumedBudgetDict())

    self.assertEqual(
      {('source/account_module/fixed_assets', 'account_type/asset'): -100.0,
       ('source/account_module/goods_purchase', 'account_type/expense'): 100.0},
        budget_line.getEngagedBudgetDict())

    self.assertEqual(
      {('source/account_module/fixed_assets', 'account_type/asset'): 100.0,
       ('source/account_module/goods_purchase', 'account_type/expense'): -99.0},
        budget_line.getAvailableBudgetDict())

    cell = budget_line.getCell('source/account_module/goods_purchase',
        'account_type/expense')
    self.assertEqual(100, cell.getConsumedBudget())
    self.assertEqual(100, cell.getEngagedBudget())

  def test_full_consumption_detail_category_variation(self):
    # tests consumptions, by using "full consumption detail" on a category
    # budget variation
    budget_model = self.portal.budget_model_module.newContent(
                            portal_type='Budget Model')
    budget_model.newContent(
                    portal_type='Category Budget Variation',
                    int_index=1,
                    budget_variation='budget',
                    inventory_axis='section_category_strict_membership',
                    variation_base_category='group',)
    budget_model.newContent(
                    portal_type='Node Budget Variation',
                    int_index=2,
                    budget_variation='budget_cell',
                    inventory_axis='node',
                    variation_base_category='source',
                    aggregate_value_list=(
                      self.portal.account_module.goods_purchase,
                      self.portal.account_module.fixed_assets,
                    ))
    budget_model.newContent(
                    portal_type='Category Budget Variation',
                    int_index=3,
                    budget_variation='budget_cell',
                    inventory_axis='node_category_strict_membership',
                    variation_base_category='account_type',
                    full_consumption_detail=True)

    budget = self.portal.budget_module.newContent(
                    portal_type='Budget',
                    start_date_range_min=DateTime(2000, 1, 1),
                    start_date_range_max=DateTime(2000, 12, 31),
                    specialise_value=budget_model)

    budget.edit(variation_category_list=['group/demo_group/sub1'])
    budget_line = budget.newContent(portal_type='Budget Line')

    # set the range, this will adjust the matrix
    budget_line.edit(
        variation_category_list=(
          'source/account_module/goods_purchase',
          'source/account_module/fixed_assets',
          'account_type/expense',
            # account type asset is in the cell range, but is not selected
          ))

    # simuate a request and call Base_edit, which does all the work of creating
    # cell and setting cell properties.
    form = budget_line.BudgetLine_view
    self.portal.REQUEST.other.update(
        dict(AUTHENTICATED_USER=getSecurityManager().getUser(),

             field_membership_criterion_base_category_list=
        form.membership_criterion_base_category_list.get_value('default'),
             field_mapped_value_property_list=
        form.mapped_value_property_list.get_value('default'),

             field_matrixbox_quantity_cell_0_0_0="1",
             field_matrixbox_membership_criterion_category_list_cell_0_0_0=[
               'source/account_module/goods_purchase',
               'account_type/expense'],
             field_matrixbox_quantity_cell_1_0_0="",
             field_matrixbox_membership_criterion_category_list_cell_1_0_0=[],
        ))
    budget_line.Base_edit(form_id=form.getId())

    self.assertEqual(1, len(budget_line.contentValues()))

    # At this time there are no consumption, so consumption and definition cell
    # ranges are all the same.
    default_cell_range = [['source/account_module/goods_purchase',
                            'source/account_module/fixed_assets'],
                           ['account_type/expense']]

    self.assertEqual(default_cell_range,
        budget_line.BudgetLine_asCellRange('cell'))
    self.assertEqual(default_cell_range,
        budget_line.BudgetLine_asCellRange('consumed'))
    self.assertEqual(default_cell_range,
        budget_line.BudgetLine_asCellRange('engaged'))
    self.assertEqual(default_cell_range,
        budget_line.BudgetLine_asCellRange('available'))

    if 0 :self.assertEqual(
        dict(from_date=DateTime(2000, 1, 1),
             at_date=DateTime(2000, 12, 31).latestTime(),
             #node_category_strict_membership=['account_type/expense',
             #                                 'account_type/asset'],
             node_category_strict_membership='account_type',

             section_category_strict_membership=['group/demo_group/sub1'],
             group_by_node_category_strict_membership=True,
             group_by_node=True,
             node_uid=[self.portal.account_module.goods_purchase.getUid(),
                       self.portal.account_module.fixed_assets.getUid()],
             group_by_section_category_strict_membership=True,
             ),
        budget_model.getInventoryListQueryDict(budget_line))


    atransaction = self.portal.accounting_module.newContent(
                  portal_type='Accounting Transaction',
                  resource_value=self.portal.currency_module.euro,
                  source_section_value=self.portal.organisation_module.my_organisation,
                  start_date=DateTime(2000, 1, 2))
    atransaction.newContent(
                  portal_type='Accounting Transaction Line',
                  source_value=self.portal.account_module.goods_purchase,
                  source_debit=100)
    atransaction.newContent(
                  portal_type='Accounting Transaction Line',
                  source_value=self.portal.account_module.fixed_assets,
                  source_credit=100)
    atransaction.stop()

    self.tic()

    # Now that we have consumptions, consumption cell ranges are updated
    consumption_cell_range = [['source/account_module/goods_purchase',
                               'source/account_module/fixed_assets'],
                           ['account_type/asset', 'account_type/expense']]
    self.assertEqual(default_cell_range,
        budget_line.BudgetLine_asCellRange('cell'))
    self.assertEqual(consumption_cell_range,
        budget_line.BudgetLine_asCellRange('consumed'))
    self.assertEqual(consumption_cell_range,
        budget_line.BudgetLine_asCellRange('engaged'))
    self.assertEqual(consumption_cell_range,
        budget_line.BudgetLine_asCellRange('available'))


    self.assertEqual(
      {('source/account_module/fixed_assets', 'account_type/asset'): -100.0,
       ('source/account_module/goods_purchase', 'account_type/expense'): 100.0},
        budget_line.getConsumedBudgetDict())

    self.assertEqual(
      {('source/account_module/fixed_assets', 'account_type/asset'): -100.0,
       ('source/account_module/goods_purchase', 'account_type/expense'): 100.0},
        budget_line.getEngagedBudgetDict())

    self.assertEqual(
      {('source/account_module/fixed_assets', 'account_type/asset'): 100.0,
       ('source/account_module/goods_purchase', 'account_type/expense'): -99.0},
        budget_line.getAvailableBudgetDict())

    cell = budget_line.getCell('source/account_module/goods_purchase',
        'account_type/expense')
    self.assertEqual(100, cell.getConsumedBudget())
    self.assertEqual(100, cell.getEngagedBudget())

  def test_consumption_movement_category(self):
    # test for budget consumption using movement category
    budget_model = self.portal.budget_model_module.newContent(
                            portal_type='Budget Model')
    budget_model.newContent(
                    portal_type='Category Budget Variation',
                    int_index=1,
                    budget_variation='budget',
                    inventory_axis='section_category',
                    variation_base_category='group',)
    budget_model.newContent(
                    portal_type='Node Budget Variation',
                    int_index=2,
                    budget_variation='budget_cell',
                    inventory_axis='node',
                    variation_base_category='source',
                    aggregate_value_list=(
                      self.portal.account_module.goods_purchase,
                      self.portal.account_module.fixed_assets,
                    ))
    budget_model.newContent(
                    portal_type='Category Budget Variation',
                    int_index=3,
                    budget_variation='budget_cell',
                    inventory_axis='movement',
                    variation_base_category='product_line',)

    budget = self.portal.budget_module.newContent(
                    portal_type='Budget',
                    start_date_range_min=DateTime(2000, 1, 1),
                    start_date_range_max=DateTime(2000, 12, 31),
                    specialise_value=budget_model)

    budget.edit(variation_category_list=['group/demo_group'])
    budget_line = budget.newContent(portal_type='Budget Line')

    # set the range, this will adjust the matrix
    budget_line.edit(
        variation_category_list=(
          'source/account_module/goods_purchase',
          'source/account_module/fixed_assets',
          'product_line/1',
          'product_line/1/1.1',
          'product_line/1/1.2', ))

    # simuate a request and call Base_edit, which does all the work of creating
    # cell and setting cell properties.
    form = budget_line.BudgetLine_view
    self.portal.REQUEST.other.update(
        dict(AUTHENTICATED_USER=getSecurityManager().getUser(),

             field_membership_criterion_base_category_list=
        form.membership_criterion_base_category_list.get_value('default'),
             field_mapped_value_property_list=
        form.mapped_value_property_list.get_value('default'),

            # this cell will be a summary cell
             field_matrixbox_quantity_cell_0_0_0="2",
             field_matrixbox_membership_criterion_category_list_cell_0_0_0=[
               'source/account_module/goods_purchase',
               'product_line/1'],
             field_matrixbox_quantity_cell_1_0_0="",
             field_matrixbox_membership_criterion_category_list_cell_1_0_0=[],
             field_matrixbox_quantity_cell_0_1_0="2",
             field_matrixbox_membership_criterion_category_list_cell_0_1_0=[
               'source/account_module/goods_purchase',
               'product_line/1/1.1'],
             field_matrixbox_quantity_cell_1_1_0="",
             field_matrixbox_membership_criterion_category_list_cell_1_1_0=[],
             field_matrixbox_quantity_cell_0_2_0="",
             field_matrixbox_membership_criterion_category_list_cell_0_2_0=[],
             field_matrixbox_quantity_cell_1_2_0="",
             field_matrixbox_membership_criterion_category_list_cell_1_2_0=[],
        ))
    budget_line.Base_edit(form_id=form.getId())

    self.assertEqual(2, len(budget_line.contentValues()))

    product_line_1 = self.portal.portal_categories.product_line['1']
    product_line_1_11 = product_line_1['1.1']
    product_line_1_12 = product_line_1['1.2']

    self.assertEqual(
        dict(from_date=DateTime(2000, 1, 1),
             at_date=DateTime(2000, 12, 31).latestTime(),
             node_uid=[self.portal.account_module.goods_purchase.getUid(),
                       self.portal.account_module.fixed_assets.getUid(),],
             default_product_line_uid=[product_line_1.getUid(),
                                       product_line_1_11.getUid(),
                                       product_line_1_12.getUid(),],
             section_category=['group/demo_group'],
             group_by=['default_product_line_uid'],
             # select list is passed, because getInventoryList does not add
             # group by related keys to select
             select_list=['default_product_line_uid'],
             group_by_node=True,
             group_by_section_category=True,
             ),
        budget_model.getInventoryListQueryDict(budget_line))


    atransaction = self.portal.accounting_module.newContent(
                  portal_type='Accounting Transaction',
                  resource_value=self.portal.currency_module.euro,
                  source_section_value=self.portal.organisation_module.my_organisation,
                  start_date=DateTime(2000, 1, 2))
    atransaction.newContent(
                  portal_type='Accounting Transaction Line',
                  source_value=self.portal.account_module.goods_purchase,
                  product_line_value=product_line_1_11,
                  source_debit=100)
    atransaction.newContent(
                  portal_type='Accounting Transaction Line',
                  source_value=self.portal.account_module.fixed_assets,
                  product_line_value=product_line_1_12,
                  source_credit=100)
    atransaction.stop()

    self.tic()

    self.assertEqual(
      {('source/account_module/fixed_assets', 'product_line/1/1.2'): -100.0,
       ('source/account_module/goods_purchase', 'product_line/1/1.1'): 100.0,
       # summary lines are automatically added
       ('source/account_module/fixed_assets', 'product_line/1'): -100.0,
       ('source/account_module/goods_purchase', 'product_line/1'): 100.0
       },
        budget_line.getConsumedBudgetDict())

    self.assertEqual(
      {('source/account_module/fixed_assets', 'product_line/1/1.2'): -100.0,
       ('source/account_module/goods_purchase', 'product_line/1/1.1'): 100.0,
       ('source/account_module/fixed_assets', 'product_line/1'): -100.0,
       ('source/account_module/goods_purchase', 'product_line/1'): 100.0
       },
        budget_line.getEngagedBudgetDict())

    self.assertEqual(
      {('source/account_module/fixed_assets', 'product_line/1/1.2'): 100.0,
       ('source/account_module/goods_purchase', 'product_line/1/1.1'): -98.0,
       ('source/account_module/fixed_assets', 'product_line/1'): 100.0,
       ('source/account_module/goods_purchase', 'product_line/1'): -98,
       },
        budget_line.getAvailableBudgetDict())

  def test_consumption_category_variation_summary(self):
    budget_model = self.portal.budget_model_module.newContent(
                            portal_type='Budget Model')
    budget_model.newContent(
                    portal_type='Category Budget Variation',
                    int_index=1,
                    budget_variation='budget_cell',
                    inventory_axis='section_category',
                    variation_base_category='group',)
    budget_model.newContent(
                    portal_type='Node Budget Variation',
                    int_index=2,
                    budget_variation='budget_cell',
                    inventory_axis='node',
                    variation_base_category='source',
                    aggregate_value_list=(
                      self.portal.account_module.goods_purchase,
                      self.portal.account_module.fixed_assets,
                    ))
    budget = self.portal.budget_module.newContent(
                    portal_type='Budget',
                    start_date_range_min=DateTime(2000, 1, 1),
                    start_date_range_max=DateTime(2000, 12, 31),
                    specialise_value=budget_model)

    budget_line = budget.newContent(portal_type='Budget Line',)

    # set the range, this will adjust the matrix
    budget_line.edit(
        variation_category_list=(
          'source/account_module/goods_purchase',
          'group/demo_group',
          'group/demo_group/sub1',
          ))
    
    form = budget_line.BudgetLine_view
    self.portal.REQUEST.other.update(
        dict(AUTHENTICATED_USER=getSecurityManager().getUser(),

             field_membership_criterion_base_category_list=
        form.membership_criterion_base_category_list.get_value('default'),
             field_mapped_value_property_list=
        form.mapped_value_property_list.get_value('default'),

             field_matrixbox_quantity_cell_0_0_0="",
             field_matrixbox_membership_criterion_category_list_cell_0_0_0=[],
             field_matrixbox_quantity_cell_1_0_0="500",
             field_matrixbox_membership_criterion_category_list_cell_1_0_0=[
               'group/demo_group/sub1',
               'source/account_module/goods_purchase', ],
        ))
    budget_line.Base_edit(form_id=form.getId())

    self.assertEqual(1, len(budget_line.contentValues()))

    self.assertEqual(
        dict(from_date=DateTime(2000, 1, 1),
             at_date=DateTime(2000, 12, 31).latestTime(),
             node_uid=[self.portal.account_module.goods_purchase.getUid(),],
             section_category=['group/demo_group',
                               'group/demo_group/sub1'],
             group_by_node=True,
             group_by_section_category=True,
             ),
        budget_model.getInventoryListQueryDict(budget_line))


    atransaction = self.portal.accounting_module.newContent(
                  portal_type='Accounting Transaction',
                  resource_value=self.portal.currency_module.euro,
                  source_section_value=self.portal.organisation_module.my_organisation,
                  start_date=DateTime(2000, 1, 2))
    atransaction.newContent(
                  portal_type='Accounting Transaction Line',
                  source_value=self.portal.account_module.goods_purchase,
                  source_debit=100)
    atransaction.newContent(
                  portal_type='Accounting Transaction Line',
                  source_value=self.portal.account_module.fixed_assets,
                  source_credit=100)
    atransaction.stop()

    self.tic()

    self.assertEqual(
      {('group/demo_group/sub1', 'source/account_module/goods_purchase'): 100.0,
       ('group/demo_group', 'source/account_module/goods_purchase'): 100.0,},
       budget_line.getConsumedBudgetDict())

    self.assertEqual(
      {('group/demo_group/sub1', 'source/account_module/goods_purchase'): 100.0,
       ('group/demo_group', 'source/account_module/goods_purchase'): 100.0,},
       budget_line.getEngagedBudgetDict())

  def test_consumption_node_budget_variation_not_set(self):
    # test consumption calculation when a node budget variation is used, but
    # this variation category is not set
    budget_model = self.portal.budget_model_module.newContent(
                            portal_type='Budget Model')
    budget_model.newContent(
                    portal_type='Category Budget Variation',
                    int_index=1,
                    budget_variation='budget_cell',
                    inventory_axis='section_category',
                    variation_base_category='group',)
    budget_model.newContent(
                    portal_type='Node Budget Variation',
                    int_index=2,
                    budget_variation='budget_cell',
                    inventory_axis='node',
                    variation_base_category='source',
                    aggregate_value_list=(
                      self.portal.account_module.goods_purchase,
                      self.portal.account_module.fixed_assets,
                    ))
    budget = self.portal.budget_module.newContent(
                    portal_type='Budget',
                    start_date_range_min=DateTime(2000, 1, 1),
                    start_date_range_max=DateTime(2000, 12, 31),
                    specialise_value=budget_model)

    budget_line = budget.newContent(portal_type='Budget Line',)

    # we don't set 
    budget_line.edit(
        variation_category_list=(
          'group/demo_group/sub1',
          ))
    
    form = budget_line.BudgetLine_view
    self.portal.REQUEST.other.update(
        dict(AUTHENTICATED_USER=getSecurityManager().getUser(),

             field_membership_criterion_base_category_list=
        form.membership_criterion_base_category_list.get_value('default'),
             field_mapped_value_property_list=
        form.mapped_value_property_list.get_value('default'),

             field_matrixbox_quantity_cell_0_0_0="500",
             field_matrixbox_membership_criterion_category_list_cell_0_0_0=[
               'group/demo_group/sub1', ],
        ))
    budget_line.Base_edit(form_id=form.getId())

    self.assertEqual(1, len(budget_line.contentValues()))

    self.assertEqual(
        dict(from_date=DateTime(2000, 1, 1),
             at_date=DateTime(2000, 12, 31).latestTime(),
             section_category=['group/demo_group/sub1',],
             group_by_section_category=True,
             ),
        budget_model.getInventoryListQueryDict(budget_line))


    atransaction = self.portal.accounting_module.newContent(
                  portal_type='Accounting Transaction',
                  resource_value=self.portal.currency_module.euro,
                  source_section_value=self.portal.organisation_module.my_organisation,
                  start_date=DateTime(2000, 1, 2))
    atransaction.newContent(
                  portal_type='Accounting Transaction Line',
                  source_value=self.portal.account_module.goods_purchase,
                  source_debit=100)
    atransaction.newContent(
                  portal_type='Accounting Transaction Line',
                  source_value=self.portal.account_module.fixed_assets,
                  source_credit=100)
    atransaction.stop()

    self.tic()

    self.assertEqual(
      {('group/demo_group/sub1', ): 0.0, },
       budget_line.getConsumedBudgetDict())

    self.assertEqual(
      {('group/demo_group/sub1', ): 0.0, },
       budget_line.getEngagedBudgetDict())

  def test_consumption_category_budget_variation_not_set(self):
    # test consumption calculation when a category budget variation is used, but
    # this variation category is not set
    budget_model = self.portal.budget_model_module.newContent(
                            portal_type='Budget Model')
    budget_model.newContent(
                    portal_type='Category Budget Variation',
                    int_index=1,
                    budget_variation='budget_cell',
                    inventory_axis='section_category',
                    variation_base_category='group',)
    budget_model.newContent(
                    portal_type='Node Budget Variation',
                    int_index=2,
                    budget_variation='budget_cell',
                    inventory_axis='node',
                    variation_base_category='source',
                    aggregate_value_list=(
                      self.portal.account_module.goods_purchase,
                      self.portal.account_module.fixed_assets,
                    ))
    budget = self.portal.budget_module.newContent(
                    portal_type='Budget',
                    start_date_range_min=DateTime(2000, 1, 1),
                    start_date_range_max=DateTime(2000, 12, 31),
                    specialise_value=budget_model)

    budget_line = budget.newContent(portal_type='Budget Line',)

    # we don't set 
    budget_line.edit(
        variation_category_list=(
          'source/account_module/goods_purchase',
          ))
    
    form = budget_line.BudgetLine_view
    self.portal.REQUEST.other.update(
        dict(AUTHENTICATED_USER=getSecurityManager().getUser(),

             field_membership_criterion_base_category_list=
        form.membership_criterion_base_category_list.get_value('default'),
             field_mapped_value_property_list=
        form.mapped_value_property_list.get_value('default'),

             field_matrixbox_quantity_cell_0_0_0="500",
             field_matrixbox_membership_criterion_category_list_cell_0_0_0=[
               'source/account_module/goods_purchase', ],
        ))
    budget_line.Base_edit(form_id=form.getId())

    self.assertEqual(1, len(budget_line.contentValues()))

    self.assertEqual(
        dict(from_date=DateTime(2000, 1, 1),
             at_date=DateTime(2000, 12, 31).latestTime(),
             node_uid=[self.portal.account_module.goods_purchase.getUid(),],
             group_by_node=True,
             ),
        budget_model.getInventoryListQueryDict(budget_line))


    atransaction = self.portal.accounting_module.newContent(
                  portal_type='Accounting Transaction',
                  resource_value=self.portal.currency_module.euro,
                  source_section_value=self.portal.organisation_module.my_organisation,
                  start_date=DateTime(2000, 1, 2))
    atransaction.newContent(
                  portal_type='Accounting Transaction Line',
                  source_value=self.portal.account_module.goods_purchase,
                  source_debit=100)
    atransaction.newContent(
                  portal_type='Accounting Transaction Line',
                  source_value=self.portal.account_module.fixed_assets,
                  source_credit=100)
    atransaction.stop()

    self.tic()

    self.assertEqual(
      {('source/account_module/goods_purchase', ): 100.0, },
       budget_line.getConsumedBudgetDict())

    self.assertEqual(
      {('source/account_module/goods_purchase', ): 100.0, },
       budget_line.getEngagedBudgetDict())

  def test_multiple_variation_line_level(self):
    # tests the behaviour of getInventoryListQueryDict and
    # getInventoryQueryDict when we are using budget line level variation with
    # multiple variation set. It should be a 'OR' between all the selected
    # variations.
    budget_model = self.portal.budget_model_module.newContent(
                            portal_type='Budget Model')
    budget_model.newContent(
                    portal_type='Node Budget Variation',
                    int_index=1,
                    budget_variation='budget_line',
                    inventory_axis='node',
                    variation_base_category='source',
                    aggregate_value_list=(
                      self.portal.account_module.goods_purchase,
                      self.portal.account_module.fixed_assets,
                    ))
    budget_model.newContent(
                    portal_type='Category Budget Variation',
                    int_index=2,
                    budget_variation='budget_line',
                    inventory_axis='section_category',
                    variation_base_category='group',)
    # this variation will be needed to create cells
    budget_model.newContent(
                    portal_type='Category Budget Variation',
                    int_index=3,
                    budget_variation='budget_cell',
                    inventory_axis='node_category_strict_membership',
                    variation_base_category='account_type',)

    budget = self.portal.budget_module.newContent(
                    portal_type='Budget',
                    specialise_value=budget_model)
    budget_line = budget.newContent(portal_type='Budget Line')

    budget_line.edit(
        variation_category_list=['group/demo_group/sub1',
                                 'group/demo_group/sub2',
                                 'source/account_module/goods_purchase',
                                 'source/account_module/fixed_assets',
                                 ])
    self.assertEqual({
      'from_date': None,
      'group_by_node': True,
      'group_by_section_category': True,
      'section_category': ['group/demo_group/sub1',
                           'group/demo_group/sub2'],
      'node_uid': [self.portal.account_module.goods_purchase.getUid(),
                   self.portal.account_module.fixed_assets.getUid()], },
      budget_model.getInventoryListQueryDict(budget_line))

    self.assertEqual({
      'from_date': None,
      'simulation_state': ('delivered', 'stopped', 'started'),
      'transit_simulation_state': ('started', ),
      'omit_transit': False,
      # XXX order is reversed for some reason ...
      'section_category': ['group/demo_group/sub2',
                           'group/demo_group/sub1'],
      'node_uid': [self.portal.account_module.fixed_assets.getUid(),
                   self.portal.account_module.goods_purchase.getUid()],
      'node_category_strict_membership': ['account_type/expense']},

      # BudgetLine_getInventoryQueryDictForCellIndex uses getInventoryQueryDict
      # but does not require the cell to be physically present
      budget_line.BudgetLine_getInventoryQueryDictForCellIndex(
        cell_index=('account_type/expense')))

  
  # Report
  def test_budget_consumption_report(self):
    budget_model = self.portal.budget_model_module.newContent(
                            portal_type='Budget Model')
    budget_model.newContent(
                    portal_type='Category Budget Variation',
                    int_index=1,
                    budget_variation='budget',
                    inventory_axis='section_category',
                    variation_base_category='group',)
    budget_model.newContent(
                    portal_type='Node Budget Variation',
                    int_index=2,
                    budget_variation='budget_cell',
                    inventory_axis='node',
                    variation_base_category='source',
                    aggregate_value_list=(
                      self.portal.account_module.goods_purchase,
                      self.portal.account_module.fixed_assets,
                    ))
    budget_model.newContent(
                    portal_type='Category Budget Variation',
                    int_index=3,
                    budget_variation='budget_cell',
                    inventory_axis='node_category',
                    variation_base_category='account_type',)

    budget = self.portal.budget_module.newContent(
                    portal_type='Budget',
                    title='Budget Title',
                    start_date_range_min=DateTime(2000, 1, 1),
                    start_date_range_max=DateTime(2000, 12, 31),
                    specialise_value=budget_model)

    budget.edit(variation_category_list=['group/demo_group'])
    budget_line = budget.newContent(portal_type='Budget Line',
                                    title='Budget Line Title',)

    # set the range, this will adjust the matrix
    budget_line.edit(
        variation_category_list=(
          'source/account_module/goods_purchase',
          'source/account_module/fixed_assets',
          'account_type/asset', ))

    # simuate a request and call Base_edit, which does all the work of creating
    # cell and setting cell properties.
    form = budget_line.BudgetLine_view
    self.portal.REQUEST.other.update(
        dict(AUTHENTICATED_USER=getSecurityManager().getUser(),

             field_membership_criterion_base_category_list=
        form.membership_criterion_base_category_list.get_value('default'),
             field_mapped_value_property_list=
        form.mapped_value_property_list.get_value('default'),

             field_matrixbox_quantity_cell_0_0_0="",
             field_matrixbox_membership_criterion_category_list_cell_0_0_0=[],
             field_matrixbox_quantity_cell_1_0_0="200",
             field_matrixbox_membership_criterion_category_list_cell_1_0_0=[
               'source/account_module/fixed_assets',
               'account_type/asset'],
        ))
    budget_line.Base_edit(form_id=form.getId())

    atransaction = self.portal.accounting_module.newContent(
                  portal_type='Accounting Transaction',
                  resource_value=self.portal.currency_module.euro,
                  source_section_value=self.portal.organisation_module.my_organisation,
                  start_date=DateTime(2000, 1, 2))
    atransaction.newContent(
                  portal_type='Accounting Transaction Line',
                  source_value=self.portal.account_module.goods_purchase,
                  source_credit=100)
    atransaction.newContent(
                  portal_type='Accounting Transaction Line',
                  source_value=self.portal.account_module.fixed_assets,
                  source_debit=100)
    atransaction.stop()

    self.tic()
   
    # Budget_getBudgetConsumptionReportData returns all the data for the report
    line_list, line_count = budget.Budget_getBudgetConsumptionReportData()
    # the number of lines, which will be used in the report to set the print
    # range
    self.assertEqual(6, line_count)
    # number of line can be different from the length of the line list, because
    # line list is a recursive structure.
    self.assertEqual(4, len(line_list))
    
    # first line is for the title of the budget
    self.assertEqual('Budget Title', line_list[0]['title'])
    self.assertTrue(line_list[0]['is_budget'])
    
    # then we have a first level for budget lines
    self.assertEqual('Budget Line Title', line_list[1]['title'])
    self.assertTrue(line_list[1]['is_level_1'])
    # we can see global consumptions for the budget
    self.assertEqual(200, line_list[2]['initial_budget'])
    self.assertEqual(200, line_list[2]['current_budget'])
    self.assertEqual(100, line_list[2]['consumed_budget'])
    self.assertEqual(100, line_list[2]['engaged_budget'])
    self.assertEqual(.5, line_list[2]['consumed_ratio'])
    
    # the dimensions are reversed in the budget report, so on level 2 we have
    # the last dimension from cell range, here "account type"
    self.assertEqual('Asset', line_list[2]['title'])
    # we can see global consumptions for that summary line
    self.assertEqual(200, line_list[2]['initial_budget'])
    self.assertEqual(200, line_list[2]['current_budget'])
    self.assertEqual(100, line_list[2]['consumed_budget'])
    self.assertEqual(100, line_list[2]['engaged_budget'])
    self.assertEqual(.5, line_list[2]['consumed_ratio'])

    # no we have a recursive list, for the next dimension: node.
    self.assertTrue(isinstance(line_list[3], list))
    self.assertEqual(3, len(line_list[3]))

    # first is again a title XXX why ??
    self.assertEqual('Asset', line_list[3][0]['title'])
    # then we have two level 3 cells
    self.assertTrue(line_list[3][1]['is_level_3'])
    self.assertEqual('Goods Purchase', line_list[3][1]['title'])
    self.assertEqual(0, line_list[3][1]['initial_budget'])
    self.assertEqual(0, line_list[3][1]['current_budget'])
    self.assertEqual(0, line_list[3][1]['consumed_budget'])
    self.assertEqual(0, line_list[3][1]['engaged_budget'])
    self.assertEqual(0, line_list[3][1]['consumed_ratio'])

    self.assertEqual('Fixed Assets', line_list[3][2]['title'])
    self.assertEqual(200, line_list[3][2]['initial_budget'])
    self.assertEqual(200, line_list[3][2]['current_budget'])
    self.assertEqual(100, line_list[3][2]['consumed_budget'])
    self.assertEqual(100, line_list[3][2]['engaged_budget'])
    self.assertEqual(.5, line_list[3][2]['consumed_ratio'])

    # validate report ODF
    from Products.ERP5OOo.tests.utils import Validator
    odf_validator = Validator()
    odf = budget.Budget_viewBudgetConsumptionReport()
    err_list = odf_validator.validate(odf)
    if err_list:
      self.fail(''.join(err_list))

  # "update summary cells" feature
  def test_update_summary_cell_simple(self):
    # test the action to create or update quantity on summary cells
    budget_model = self.portal.budget_model_module.newContent(
                            portal_type='Budget Model')
    budget_model.newContent(
                    portal_type='Category Budget Variation',
                    int_index=1,
                    budget_variation='budget_cell',
                    inventory_axis='movement',
                    variation_base_category='product_line',)
    budget_model.newContent(
                    portal_type='Node Budget Variation',
                    int_index=2,
                    budget_variation='budget_cell',
                    inventory_axis='node',
                    variation_base_category='source',
                    aggregate_value_list=(
                      self.portal.account_module.goods_purchase,
                      self.portal.account_module.fixed_assets,
                    ))
    budget_model.newContent(
                    portal_type='Category Budget Variation',
                    int_index=3,
                    budget_variation='budget_cell',
                    inventory_axis='section_category',
                    variation_base_category='group',)

    budget = self.portal.budget_module.newContent(
                    portal_type='Budget',
                    start_date_range_min=DateTime(2000, 1, 1),
                    start_date_range_max=DateTime(2000, 12, 31),
                    specialise_value=budget_model)

    budget_line = budget.newContent(portal_type='Budget Line')

    # set the range, this will adjust the matrix
    budget_line.edit(
        variation_category_list=(
          'group/demo_group',
          'group/demo_group/sub1',
          'group/demo_group/sub2',
          'source/account_module/goods_purchase',
          'source/account_module/fixed_assets',
          'product_line/1',
          'product_line/1/1.1',
          'product_line/1/1.2', ))

    form = budget_line.BudgetLine_view
    self.portal.REQUEST.other.update(
        dict(AUTHENTICATED_USER=getSecurityManager().getUser(),

             field_membership_criterion_base_category_list=
        form.membership_criterion_base_category_list.get_value('default'),
             field_mapped_value_property_list=
        form.mapped_value_property_list.get_value('default'),
            
             # group/demo_group
             field_matrixbox_quantity_cell_0_0_0="",
             field_matrixbox_membership_criterion_category_list_cell_0_0_0=[],
             field_matrixbox_quantity_cell_1_0_0="",
             field_matrixbox_membership_criterion_category_list_cell_1_0_0=[],
             field_matrixbox_quantity_cell_2_0_0="",
             field_matrixbox_membership_criterion_category_list_cell_2_0_0=[],
             field_matrixbox_quantity_cell_0_1_0="",
             field_matrixbox_membership_criterion_category_list_cell_0_1_0=[],
             field_matrixbox_quantity_cell_1_1_0="",
             field_matrixbox_membership_criterion_category_list_cell_1_1_0=[],
             # This is a summary cell, but we set a manual value.
             field_matrixbox_quantity_cell_2_1_0="100",
             field_matrixbox_membership_criterion_category_list_cell_2_1_0=[
                'product_line/1/1.2',
                'source/account_module/fixed_assets',
                'group/demo_group',
                ],

             # group/demo_group/sub1
             field_matrixbox_quantity_cell_0_0_1="",
             field_matrixbox_membership_criterion_category_list_cell_0_0_1=[],
             field_matrixbox_quantity_cell_1_0_1="1",
             field_matrixbox_membership_criterion_category_list_cell_1_0_1=[
                'product_line/1/1.1',
                'source/account_module/goods_purchase',
                'group/demo_group/sub1',
                ],
             field_matrixbox_quantity_cell_2_0_1="2",
             field_matrixbox_membership_criterion_category_list_cell_2_0_1=[
                'product_line/1/1.2',
                'source/account_module/goods_purchase',
                'group/demo_group/sub1',
                ],
             field_matrixbox_quantity_cell_0_1_1="",
             field_matrixbox_membership_criterion_category_list_cell_0_1_1=[],
             field_matrixbox_quantity_cell_1_1_1="3",
             field_matrixbox_membership_criterion_category_list_cell_1_1_1=[
                'product_line/1/1.1',
                'source/account_module/fixed_assets',
                'group/demo_group/sub1',
               ],
             field_matrixbox_quantity_cell_2_1_1="4",
             field_matrixbox_membership_criterion_category_list_cell_2_1_1=[
                'product_line/1/1.2',
                'source/account_module/fixed_assets',
                'group/demo_group/sub1',
               ],

             # group/demo_group/sub2
             field_matrixbox_quantity_cell_0_0_2="",
             field_matrixbox_membership_criterion_category_list_cell_0_0_2=[],
             # we only have 1 cell here
             field_matrixbox_quantity_cell_1_0_2="5",
             field_matrixbox_membership_criterion_category_list_cell_1_0_2=[
                  'product_line/1/1.1',
                  'source/account_module/goods_purchase',
                  'group/demo_group/sub2',
                 ],
             field_matrixbox_quantity_cell_2_0_2="",
             field_matrixbox_membership_criterion_category_list_cell_2_0_2=[],
             # we have no cells here
             field_matrixbox_quantity_cell_0_1_2="",
             field_matrixbox_membership_criterion_category_list_cell_0_1_2=[],
             field_matrixbox_quantity_cell_1_1_2="",
             field_matrixbox_membership_criterion_category_list_cell_1_1_2=[],
             field_matrixbox_quantity_cell_2_1_2="",
             field_matrixbox_membership_criterion_category_list_cell_2_1_2=[],
        ))

    budget_line.Base_edit(form_id=form.getId())

    self.assertEqual(6, len(budget_line.contentValues()))

    budget_line.BudgetLine_setQuantityOnSummaryCellList()

    # summary cells have been created:
    self.assertEqual(14, len(budget_line.contentValues()))

    # those cells are aggregating
    self.assertEqual(1+2, budget_line.getCell(
                              'product_line/1',
                              'source/account_module/goods_purchase',
                              'group/demo_group/sub1',).getQuantity())
    self.assertEqual(4+3, budget_line.getCell(
                              'product_line/1',
                              'source/account_module/fixed_assets',
                              'group/demo_group/sub1',).getQuantity())
    self.assertEqual(1+5, budget_line.getCell(
                              'product_line/1/1.1',
                              'source/account_module/goods_purchase',
                              'group/demo_group',).getQuantity())
    self.assertEqual(1+2+5, budget_line.getCell(
                              'product_line/1',
                              'source/account_module/goods_purchase',
                              'group/demo_group',).getQuantity())

    # the cell that we have modified is erased
    self.assertEqual(4, budget_line.getCell(
                              'product_line/1/1.2',
                              'source/account_module/fixed_assets',
                              'group/demo_group',).getQuantity())

    # test all cells for complete coverage
    self.assertEqual(6, budget_line.getCell(
                              'product_line/1/1.1',
                              'source/account_module/goods_purchase',
                              'group/demo_group',).getQuantity())
    self.assertEqual(2, budget_line.getCell(
                              'product_line/1/1.2',
                              'source/account_module/goods_purchase',
                              'group/demo_group',).getQuantity())
    self.assertEqual(3+4, budget_line.getCell(
                              'product_line/1',
                              'source/account_module/fixed_assets',
                              'group/demo_group',).getQuantity())
    self.assertEqual(3, budget_line.getCell(
                              'product_line/1/1.1',
                              'source/account_module/fixed_assets',
                              'group/demo_group',).getQuantity())
    self.assertEqual(4, budget_line.getCell(
                              'product_line/1/1.2',
                              'source/account_module/fixed_assets',
                              'group/demo_group',).getQuantity())
    self.assertEqual(5, budget_line.getCell(
                              'product_line/1',
                              'source/account_module/goods_purchase',
                              'group/demo_group/sub2',).getQuantity())
    
    # change a cell quantity and call again
    budget_cell = budget_line.getCell(
        'product_line/1/1.2',
        'source/account_module/goods_purchase',
        'group/demo_group/sub1')
    self.assertNotEquals(None, budget_cell)
    self.assertEqual(2, budget_cell.getQuantity())
    budget_cell.setQuantity(6)

    budget_line.BudgetLine_setQuantityOnSummaryCellList()
    self.assertEqual(14, len(budget_line.contentValues()))

    self.assertEqual(1+6, budget_line.getCell(
                              'product_line/1',
                              'source/account_module/goods_purchase',
                              'group/demo_group/sub1',).getQuantity())
    self.assertEqual(4+3, budget_line.getCell(
                              'product_line/1',
                              'source/account_module/fixed_assets',
                              'group/demo_group/sub1',).getQuantity())
    self.assertEqual(1+5, budget_line.getCell(
                              'product_line/1/1.1',
                              'source/account_module/goods_purchase',
                              'group/demo_group',).getQuantity())
    self.assertEqual(1+6+5, budget_line.getCell(
                              'product_line/1',
                              'source/account_module/goods_purchase',
                              'group/demo_group',).getQuantity())

    
  def test_update_summary_cell_non_strict_and_second_summary(self):
    # test the action to create or update quantity on summary cells, variation
    # which are strict are not updated, and multiple level summary does not
    # aggregate again intermediate summaries
    budget_model = self.portal.budget_model_module.newContent(
                            portal_type='Budget Model')
    budget_model.newContent(
                    portal_type='Category Budget Variation',
                    int_index=1,
                    budget_variation='budget_cell',
                    inventory_axis='movement_strict_membership',
                    variation_base_category='product_line',)
    budget_model.newContent(
                    portal_type='Node Budget Variation',
                    int_index=2,
                    budget_variation='budget_cell',
                    inventory_axis='node',
                    variation_base_category='source',
                    aggregate_value_list=(
                      self.portal.account_module.goods_purchase,
                      self.portal.account_module.fixed_assets,
                    ))
    budget_model.newContent(
                    portal_type='Category Budget Variation',
                    int_index=3,
                    budget_variation='budget_cell',
                    inventory_axis='node_category',
                    variation_base_category='account_type',)

    budget = self.portal.budget_module.newContent(
                    portal_type='Budget',
                    start_date_range_min=DateTime(2000, 1, 1),
                    start_date_range_max=DateTime(2000, 12, 31),
                    specialise_value=budget_model)

    budget_line = budget.newContent(portal_type='Budget Line')

    # set the range, this will adjust the matrix
    budget_line.edit(
        variation_category_list=(
          'account_type/asset',
          'account_type/asset/cash',
          'account_type/asset/cash/bank',
          'source/account_module/goods_purchase',
          'product_line/1',
          'product_line/1/1.1', ))
    
    form = budget_line.BudgetLine_view
    self.portal.REQUEST.other.update(
        dict(AUTHENTICATED_USER=getSecurityManager().getUser(),

             field_membership_criterion_base_category_list=
        form.membership_criterion_base_category_list.get_value('default'),
             field_mapped_value_property_list=
        form.mapped_value_property_list.get_value('default'),
             field_matrixbox_quantity_cell_0_0_0="",
             field_matrixbox_membership_criterion_category_list_cell_0_0_0=[],
             field_matrixbox_quantity_cell_1_0_0="",
             field_matrixbox_membership_criterion_category_list_cell_1_0_0=[],
             field_matrixbox_quantity_cell_0_0_1="",
             field_matrixbox_membership_criterion_category_list_cell_0_0_1=[],
             field_matrixbox_quantity_cell_1_0_1="",
             field_matrixbox_membership_criterion_category_list_cell_1_0_1=[],
             field_matrixbox_quantity_cell_0_0_2="",
             field_matrixbox_membership_criterion_category_list_cell_0_0_2=[],
             field_matrixbox_quantity_cell_1_0_2="1",
             field_matrixbox_membership_criterion_category_list_cell_1_0_2=[
                 'product_line/1/1.1',
                 'source/account_module/goods_purchase',
                 'account_type/asset/cash/bank',
               ],
          ))
    budget_line.Base_edit(form_id=form.getId())

    self.assertEqual(1, len(budget_line.contentValues()))

    budget_line.BudgetLine_setQuantityOnSummaryCellList()
    self.assertEqual(3, len(budget_line.contentValues()))

    budget_cell = budget_line.getCell(
        'product_line/1/1.1',
        'source/account_module/goods_purchase',
        'account_type/asset/cash')
    self.assertNotEquals(None, budget_cell)
    self.assertEqual(1, budget_cell.getQuantity())

    budget_cell = budget_line.getCell(
        'product_line/1/1.1',
        'source/account_module/goods_purchase',
        'account_type/asset',)
    self.assertNotEquals(None, budget_cell)
    self.assertEqual(1, budget_cell.getQuantity())

  def updateBudgetCellList(self, budget_line, table_list):
    updateCellList(self.portal,
                   budget_line,
                   'Budget Cell',
                   'BudgetLine_asCellRange',
                   table_list)

  def makeTableList(self, base_id, cell_range_kw,
                    mapped_value_argument_list, table):
    return [{'base_id':base_id,
            'cell_range_kw':cell_range_kw,
            'mapped_value_argument_list':mapped_value_argument_list,
            'table':table
            }]

  def makeQuantityTable(self, table):
    #two_dimension = (
    #  (        column,         column,),
    #  (line,   mapped_value,   mapped_value,),
    #  (line,   mapped_value,   mapped_value,),
    #  )
    return self.makeTableList(
              base_id='cell',
              cell_range_kw={},
              mapped_value_argument_list=('quantity',),
              table=table)

  def testNodeVariationWithMovemetAxisPackingList(self):
    """
     Budgets are normally used with accounting transactions, however it can be
     used with packing lists and other Movement. This is an experimental usage.
    """
    self.portal.product_module.newContent(portal_type='Product',
                                          id='test_product',
                                          title='Test Product')
    self.portal.product_module.newContent(portal_type='Product',
                                          id='demo_product',
                                          title='Demo Prduct')
    self.commit()
    budget_model = self.portal.budget_model_module.newContent(
                            portal_type='Budget Model')
    budget_model.newContent(
                    portal_type='Node Budget Variation',
                    int_index=1,
                    budget_variation='budget_cell',
                    inventory_axis='movement',
                    variation_base_category='resource',
                    aggregate_value_list=(
                      self.portal.product_module.test_product,
                      self.portal.product_module.demo_product,
                    ))
    budget_model.newContent(
                    portal_type='Node Budget Variation',
                    int_index=2,
                    budget_variation='budget_cell',
                    inventory_axis='mirror_section',
                    variation_base_category='source_section',
                    aggregate_value_list=(
                      self.portal.organisation_module.my_organisation,
                      self.portal.organisation_module.main_organisation
                    ))
    budget = self.portal.budget_module.newContent(
                    portal_type='Budget',
                    start_date_range_min=DateTime(2011, 1, 1),
                    start_date_range_max=DateTime(2011, 12, 31),
                    specialise_value=budget_model)
    budget_line = budget.newContent(portal_type='Budget Line')
    budget_line.edit(
        variation_category_list=(
          'resource/product_module/test_product',
          'resource/product_module/demo_product',
          'source_section/organisation_module/my_organisation',
          'source_section/organisation_module/main_organisation', ))
    self.updateBudgetCellList(
      budget_line,
      self.makeQuantityTable(
        table=[
           ('source_section/organisation_module/my_organisation',
            'source_section/organisation_module/main_organisation'),
           ('resource/product_module/test_product', 12000, 11000),
           ('resource/product_module/demo_product', 17000, 15000),
        ])
    )

    def createPackingList(organisation_id, product_id, quantity, price):
      spl = self.portal.sale_packing_list_module.newContent(
              portal_type='Sale Packing List')
      spl.setSpecialise(
        'business_process_module/erp5_default_business_process')
      spl.setStartDate('2011/08/01')
      spl.setStopDate('2011/08/05')
      spl.setDestinationSection('organisation_module/client_1')
      spl.setDestination('organisation_module/client_1')
      organisation = 'organisation_module/%s' % organisation_id
      spl.setSourceSection(organisation)
      spl.setSource(organisation)
      spll = spl.newContent(portal_type='Sale Packing List Line')
      spll.setQuantity(quantity)
      spll.setPrice(price)
      spll.setResource('product_module/%s' % product_id)
      self.commit()
      spl.confirm()
      self.tic()
      spl.start()
      self.tic()
      spl.stop()
      self.tic()
      return spl

    createPackingList('my_organisation', 'test_product', 100, 5)
    createPackingList('main_organisation', 'demo_product', 200, 6)
    self.tic()
    # Budget Line only support total price. It is considerable to support
    # total_quantity.
    total_price = budget_line.getConsumedBudgetDict().get(
      ('resource/product_module/test_product',
       'source_section/organisation_module/my_organisation'),
      None)
    self.assertNotEquals(None, total_price)
    self.assertEqual(500.0, total_price)
    total_price = budget_line.getEngagedBudgetDict().get(
      ('resource/product_module/demo_product',
       'source_section/organisation_module/main_organisation'),
      None)
    self.assertNotEquals(None, total_price)
    self.assertEqual(1200.0, total_price)


  # Other TODOs:

  # budget level variation and budget cell level variation for same inventory
  # axis

  # resource/price currency on budget ?

  # test virtual all others when cloning an existing budget

  # predicates 


def test_suite():
  suite = unittest.TestSuite()
  suite.addTest(unittest.makeSuite(TestBudget))
  return suite