##############################################################################
#
# 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

import transaction
from DateTime import DateTime

from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from AccessControl import getSecurityManager

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):
    transaction.abort()
    self.portal.accounting_module.manage_delObjects(
       list(self.portal.accounting_module.objectIds()))
    transaction.commit()
    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_base', 'erp5_pdm', 'erp5_trade', 'erp5_accounting',
            'erp5_invoicing', 'erp5_simplified_invoicing',
            'erp5_accounting_ui_test', 'erp5_budget')

  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.assertEquals([], 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.assertEquals([], 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.assertEquals(['source'],
                      budget_line.getVariationBaseCategoryList())
    self.assertEquals(
        [('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.assertEquals(
        ['source/account_module/goods_purchase'],
        budget_line.getVariationCategoryList())
  
    # This was a budget cell variation, so no criterion is set on budget line
    self.assertEquals(budget_line.getMembershipCriterionCategoryList(), [])
    self.assertEquals(
        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.assertEquals(1, len(budget_line.contentValues()))
    budget_cell = budget_line.getCell('source/account_module/goods_purchase')
    self.assertNotEquals(None, budget_cell)

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

    # there is no budget consumption
    self.assertEquals(0, budget_cell.getConsumedBudget())
    self.assertEquals(0, budget_cell.getEngagedBudget())
    self.assertEquals(5, budget_cell.getAvailableBudget())
    # there is no budget transfer
    self.assertEquals(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.assertEquals(['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.assertEquals(['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.assertEquals(['group'],
        budget_line.getMembershipCriterionBaseCategoryList())
    self.assertEquals(['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.assertEquals(['group', 'account_type'],
                      budget_line.getVariationBaseCategoryList())
    
    budget_line.edit(variation_category_list=['group/demo_group',
                                              'account_type/expense'])
    self.assertEquals(['group'],
        budget_line.getMembershipCriterionBaseCategoryList())
    self.assertEquals(['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.assertEquals(1, len(budget_line.contentValues()))
    budget_cell = budget_line.getCell('account_type/expense')
    self.assertEquals(['account_type'],
                       budget_cell.getMembershipCriterionBaseCategoryList())
    self.assertEquals(['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.assertEquals(['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.assertEquals('demo_group', budget.getGroup())
    self.assertEquals('Demo Group', budget.getGroupTitle())

  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.assertEquals(2, len(budget_line.contentValues()))
    budget_cell = budget_line.getCell('source/account_module/goods_purchase',
                                      'account_type/expense')
    self.assertNotEquals(None, budget_cell)
    self.assertEquals(
        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.assertEquals(
        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.assertEquals(
        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.stop()

    transaction.commit()
    self.tic()

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

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

    self.assertEquals(
      {('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/%s' % budget_line.getRelativeUrl(), # 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/%s' % budget_line.getRelativeUrl(),
               '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.assertEquals(2, len(budget_line.contentValues()))

    self.assertEquals(
        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()

    transaction.commit()
    self.tic()

    self.assertEquals(
      {('source/%s' % budget_line.getRelativeUrl(), 'account_type/asset'): -100.0,
       ('source/account_module/goods_purchase', 'account_type/expense'): 100.0},
        budget_line.getConsumedBudgetDict())

    self.assertEquals(
      {('source/%s' % budget_line.getRelativeUrl(), 'account_type/asset'): -100.0,
       ('source/account_module/goods_purchase', 'account_type/expense'): 100.0},
        budget_line.getEngagedBudgetDict())

    self.assertEquals(
      {('source/%s' % budget_line.getRelativeUrl(), 'account_type/asset'): 102.0,
       ('source/account_module/goods_purchase', 'account_type/expense'): -99.0},
        budget_line.getAvailableBudgetDict())
      

  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.assertEquals(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.assertEquals(
        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()

    transaction.commit()
    self.tic()

    self.assertEquals(
      {('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.assertEquals(
      {('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.assertEquals(
      {('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.assertEquals(1, len(budget_line.contentValues()))

    self.assertEquals(
        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()

    transaction.commit()
    self.tic()

    self.assertEquals(
      {('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.assertEquals(
      {('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_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()

    transaction.commit()
    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.assertEquals(6, line_count)
    # number of line can be different from the length of the line list, because
    # line list is a recursive structure.
    self.assertEquals(4, len(line_list))
    
    # first line is for the title of the budget
    self.assertEquals('Budget Title', line_list[0]['title'])
    self.assertTrue(line_list[0]['is_budget'])
    
    # then we have a first level for budget lines
    self.assertEquals('Budget Line Title', line_list[1]['title'])
    self.assertTrue(line_list[1]['is_level_1'])
    # we can see global consumptions for the budget
    self.assertEquals(200, line_list[2]['initial_budget'])
    self.assertEquals(200, line_list[2]['current_budget'])
    self.assertEquals(100, line_list[2]['consumed_budget'])
    self.assertEquals(100, line_list[2]['engaged_budget'])
    self.assertEquals(.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.assertEquals('Asset', line_list[2]['title'])
    # we can see global consumptions for that summary line
    self.assertEquals(200, line_list[2]['initial_budget'])
    self.assertEquals(200, line_list[2]['current_budget'])
    self.assertEquals(100, line_list[2]['consumed_budget'])
    self.assertEquals(100, line_list[2]['engaged_budget'])
    self.assertEquals(.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.assertEquals(3, len(line_list[3]))

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

    self.assertEquals('Fixed Assets', line_list[3][2]['title'])
    self.assertEquals(200, line_list[3][2]['initial_budget'])
    self.assertEquals(200, line_list[3][2]['current_budget'])
    self.assertEquals(100, line_list[3][2]['consumed_budget'])
    self.assertEquals(100, line_list[3][2]['engaged_budget'])
    self.assertEquals(.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))

    
  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.assertEquals(6, len(budget_line.contentValues()))

    budget_line.BudgetLine_setQuantityOnSummaryCellList()

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

    # those cells are aggregating
    self.assertEquals(1+2, budget_line.getCell(
                              'product_line/1',
                              'source/account_module/goods_purchase',
                              'group/demo_group/sub1',).getQuantity())
    self.assertEquals(4+3, budget_line.getCell(
                              'product_line/1',
                              'source/account_module/fixed_assets',
                              'group/demo_group/sub1',).getQuantity())
    self.assertEquals(1+5, budget_line.getCell(
                              'product_line/1/1.1',
                              'source/account_module/goods_purchase',
                              'group/demo_group',).getQuantity())
    self.assertEquals(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.assertEquals(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.assertEquals(6, budget_line.getCell(
                              'product_line/1/1.1',
                              'source/account_module/goods_purchase',
                              'group/demo_group',).getQuantity())
    self.assertEquals(2, budget_line.getCell(
                              'product_line/1/1.2',
                              'source/account_module/goods_purchase',
                              'group/demo_group',).getQuantity())
    self.assertEquals(3+4, budget_line.getCell(
                              'product_line/1',
                              'source/account_module/fixed_assets',
                              'group/demo_group',).getQuantity())
    self.assertEquals(3, budget_line.getCell(
                              'product_line/1/1.1',
                              'source/account_module/fixed_assets',
                              'group/demo_group',).getQuantity())
    self.assertEquals(4, budget_line.getCell(
                              'product_line/1/1.2',
                              'source/account_module/fixed_assets',
                              'group/demo_group',).getQuantity())
    self.assertEquals(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.assertEquals(2, budget_cell.getQuantity())
    budget_cell.setQuantity(6)

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

    self.assertEquals(1+6, budget_line.getCell(
                              'product_line/1',
                              'source/account_module/goods_purchase',
                              'group/demo_group/sub1',).getQuantity())
    self.assertEquals(4+3, budget_line.getCell(
                              'product_line/1',
                              'source/account_module/fixed_assets',
                              'group/demo_group/sub1',).getQuantity())
    self.assertEquals(1+5, budget_line.getCell(
                              'product_line/1/1.1',
                              'source/account_module/goods_purchase',
                              'group/demo_group',).getQuantity())
    self.assertEquals(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.assertEquals(1, len(budget_line.contentValues()))

    budget_line.BudgetLine_setQuantityOnSummaryCellList()
    self.assertEquals(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.assertEquals(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.assertEquals(1, budget_cell.getQuantity())


  # 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