Commit 12d62298 authored by Jérome Perrin's avatar Jérome Perrin

make it possible to budget for "unset" value. Due to technical limitations,

this is not working for section, node and movement axis.


git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@39011 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent f0bd3fdb
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="BaseCategory" module="Products.ERP5Type.Document.BaseCategory"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_folders_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Copy_or_Move_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Delete_objects_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>budget_special_node</string>
</tuple>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>budget_special_node</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Base Category</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Special Nodes for Budget</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Category" module="Products.ERP5Type.Document.Category"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_folders_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Copy_or_Move_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Delete_objects_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>budget_special_node/all_other</string>
</tuple>
</value>
</item>
<item>
<key> <string>codification</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>all_other</string> </value>
</item>
<item>
<key> <string>int_index</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Category</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>All Others</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Category" module="Products.ERP5Type.Document.Category"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_folders_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Copy_or_Move_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Delete_objects_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>budget_special_node/none</string>
</tuple>
</value>
</item>
<item>
<key> <string>codification</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>none</string> </value>
</item>
<item>
<key> <string>int_index</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Category</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>None</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -109,6 +109,7 @@
<value>
<list>
<string>my_aggregate_title_list</string>
<string>my_include_virtual_none_node</string>
<string>my_include_virtual_other_node</string>
</list>
</value>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="CheckBoxField" module="Products.Formulator.StandardFields"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>my_include_virtual_none_node</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Include a Virtual Node For "None"</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
budget_line_type
budget_section
budget_special_node
budget_type
budget_variation
inventory_axis
\ No newline at end of file
portal_categories/budget_special_node/**
portal_categories/budget_variation/**
portal_categories/inventory_axis/**
\ No newline at end of file
......@@ -34,28 +34,6 @@ from Products.ZSQLCatalog.SQLCatalog import Query, NegatedQuery, ComplexQuery
from Products.ERP5Type.Message import translateString
class VirtualNode(object):
"""A Virtual Node for all Other Nodes.
This virtual document can be used in budget variations.
"""
__allow_access_to_unprotected_subobjects__ = 1
def __init__(self, relative_url):
"""The Virtual Node will use the relative URL of the budget line for
memberships.
"""
self.relative_url = relative_url
def getTitle(self):
return str(translateString('All Others'))
def getRelativeUrl(self):
return self.relative_url
def getUid(self):
return -1L
class NodeBudgetVariation(BudgetVariation):
""" A budget variation for node
......@@ -94,11 +72,15 @@ class NodeBudgetVariation(BudgetVariation):
node_select_method_id = self.getProperty('node_select_method_id')
if node_select_method_id:
return guarded_getattr(context, node_select_method_id)()
# no script defined, used the explicitly selected values
node_list = self.getAggregateValueList()
portal_categories = self.getPortalObject().portal_categories
if self.getProperty('include_virtual_none_node'):
node_list.append(portal_categories.budget_special_node.none)
if self.getProperty('include_virtual_other_node'):
return self.getAggregateValueList() + [
VirtualNode(context.getRelativeUrl()), ]
return self.getAggregateValueList()
node_list.append(portal_categories.budget_special_node.all_other)
return node_list
def _getNodeTitle(self, node):
"""Returns the title of a node
......@@ -155,18 +137,31 @@ class NodeBudgetVariation(BudgetVariation):
continue
criterion_base_category, node_url = criterion_category.split('/', 1)
if criterion_base_category == base_category:
if node_url == budget_line.getRelativeUrl():
if node_url == 'budget_special_node/none':
# This is the "Nothing" virtual node
query_dict.setdefault(axis, []).append(Query(**{axis: None}))
if node_url == 'budget_special_node/all_other':
# This is the "All Other" virtual node
other_uid_list = []
none_node_selected = False
for node in self._getNodeList(budget_line):
if '%s/%s' % (base_category, node.getRelativeUrl()) in\
budget_line.getVariationCategoryList():
other_uid_list.append(node.getUid())
query_dict.setdefault(axis, []).append(
ComplexQuery(
NegatedQuery(Query(**{axis: other_uid_list})),
Query(**{axis: None}),
operator="OR"))
if node.getRelativeUrl() == 'budget_special_node/none':
none_node_selected = True
else:
other_uid_list.append(node.getUid())
if none_node_selected:
# in this case we don't want to include NULL in All others
query_dict.setdefault(axis, []).append(
NegatedQuery(Query(**{axis: other_uid_list})))
else:
query_dict.setdefault(axis, []).append(
ComplexQuery(
NegatedQuery(Query(**{axis: other_uid_list})),
Query(**{axis: None}),
operator="OR"))
query_dict.setdefault(axis, []).append(
portal_categories.getCategoryValue(node_url,
base_category=criterion_base_category).getUid())
......@@ -204,29 +199,38 @@ class NodeBudgetVariation(BudgetVariation):
# if we have a virtual "all others" node, we don't set a criterion here.
if self.getProperty('include_virtual_other_node'):
return query_dict
found = False
for node_url in context.getVariationCategoryList(
base_category_list=(base_category,)):
query_dict.setdefault(axis, []).append(
if node_url != '%s/budget_special_node/none' % base_category:
query_dict.setdefault(axis, []).append(
portal_categories.getCategoryValue(node_url,
base_category=base_category).getUid())
found = True
if found:
if self.getProperty('include_virtual_none_node'):
query_dict[axis] = ComplexQuery(
Query(**{axis: None}),
Query(**{axis: query_dict[axis]}),
operator="OR")
return query_dict
return dict()
def _getCellKeyFromInventoryListBrain(self, brain, budget_line,
cell_key_cache=None):
"""Compute key from inventory brain, with support for "all others" virtual
node.
"""Compute key from inventory brain, with support for virtual nodes.
"""
cell_key_cache[None] = '%s/budget_special_node/none'\
% self.getProperty('variation_base_category')
key = BudgetVariation._getCellKeyFromInventoryListBrain(
self, brain, budget_line, cell_key_cache=cell_key_cache)
if self.getProperty('include_virtual_other_node'):
if key not in [x[1] for x in
self.getBudgetVariationRangeCategoryList(budget_line)]:
key = '%s/%s' % ( self.getProperty('variation_base_category'),
budget_line.getRelativeUrl() )
key = '%s/budget_special_node/all_other' % (
self.getProperty('variation_base_category'),)
return key
def getBudgetLineVariationRangeCategoryList(self, budget_line):
......
......@@ -31,6 +31,7 @@ import transaction
from DateTime import DateTime
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from Products.ZSQLCatalog.SQLCatalog import ComplexQuery
from AccessControl import getSecurityManager
class TestBudget(ERP5TypeTestCase):
......@@ -470,7 +471,7 @@ class TestBudget(ERP5TypeTestCase):
budget_line.edit(
variation_category_list=(
'source/account_module/goods_purchase',
'source/%s' % budget_line.getRelativeUrl(), # this is 'all others'
'source/budget_special_node/all_other', # this is 'all others'
'account_type/expense',
'account_type/asset', ))
......@@ -489,7 +490,7 @@ class TestBudget(ERP5TypeTestCase):
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(),
'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=[
......@@ -534,20 +535,237 @@ class TestBudget(ERP5TypeTestCase):
self.tic()
self.assertEquals(
{('source/%s' % budget_line.getRelativeUrl(), 'account_type/asset'): -100.0,
{('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.assertEquals(
{('source/%s' % budget_line.getRelativeUrl(), 'account_type/asset'): -100.0,
{('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.assertEquals(
{('source/%s' % budget_line.getRelativeUrl(), 'account_type/asset'): 102.0,
{('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.assertEquals(2, len(budget_line.contentValues()))
class ReferenceQuery:
"""Helper class to compare queries
"""
def __eq__(me, query):
self.assertTrue(isinstance(query, ComplexQuery))
self.assertEquals(query.logical_operator, 'or')
self.assertEquals(2, len(query.query_list))
self.assertEquals(query.query_list[0].kw, {'project_uid': None})
self.assertEquals(query.query_list[1].kw,
{'project_uid':
[self.portal.organisation_module.my_organisation.getUid()]})
return True
self.assertEquals(
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()
transaction.commit()
self.tic()
self.assertEquals(
{('source_project/organisation_module/my_organisation',): 200.0,
('source_project/budget_special_node/none',): -300.0
}, budget_line.getConsumedBudgetDict())
self.assertEquals(
{('source_project/organisation_module/my_organisation',): 200.0,
('source_project/budget_special_node/none',): -300.0
}, budget_line.getEngagedBudgetDict())
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.assertEquals(3, 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',],
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()
transaction.commit()
self.tic()
self.assertEquals(
{('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.assertEquals(
{('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_consumption_movement_category(self):
# test for budget consumption using movement category
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment