Commit d35b95ef authored by Vincent Pelletier's avatar Vincent Pelletier

AccountingTransactionModule_get*AgedBalanceLineList: Rework.

Make it more easily extensible by exposing a callback.
Stop loading documents in the simple version that are only needed by the
detailed version. Also, stop checking for this inside the main loop, and
instead use the callback mechanism.
Use a single query to look for mirror section title instead of one per
mirror section.
Behaviour change: mirror section (ie, the entity) title is not
translated anymore. It used to be because there did not seem to be a
reasion not to. It is not anymore to avoid loading rach entity from ZODB
in the default report. This rework makes customisation easier, including
overwriting this untranslated title with any desired transformation.
parent 53108963
"""
at_date (DateTime)
See getMovementHistoryList.
section_category (str)
section_category_strict (bool)
See Base_getSectionUidListForSectionCategory.
Result passed to getMovementHistoryList.
simulation_state (str or list of str)
See getMovementHistoryList.
period_list (list of numbers)
List of operation age ranges, in days. Used to dispatch operations by age:
period_0: older than earliest period
period_1: younger than earliest period, older than next period
...
period_n: younger than period n-1, older than at_date
period_future: Posterior to at_date
account_type (str)
Must be one of:
'account_type/asset/receivable'
'account_type/liability/payable'
lineCallback ((brain, period_name, line_dict) -> dict)
Called for each line found by getMovementHistoryList.
brain
Current line.
period_name (string)
Name of the period this line belongs to.
line_dict (dict)
Dictionary containing properties of corresponding line in the report.
May be modified by callback.
Returned value is resulting line property dictionary, or None to skip this
row.
reportCallback ((line_list) -> list of dict)
Called once all lines from getMovementHistoryList have been processed.
line_list (list)
Each entry is the dict returned by lineCallback, in order.
May be modified by callback.
Returned value is final line list. Each line must be a dict. If unsure, return
line_list.
"""
from collections import defaultdict
from Products.ZSQLCatalog.SQLCatalog import SimpleQuery, ComplexQuery
from Products.PythonScripts.standard import Object
if reportCallback is None:
reportCallback = lambda x: x
portal = context.getPortalObject()
portal_catalog = portal.portal_catalog
portal_categories = portal.portal_categories
# we set the precision in request, for formatting on editable fields
portal.REQUEST.set(
'precision',
context.account_module.getQuantityPrecisionFromResource(
context.Base_getCurrencyForSection(section_category),
),
)
line_list = []
assert account_type in ('account_type/asset/receivable', 'account_type/liability/payable')
reverse_price_sign = account_type == 'account_type/liability/payable'
by_mirror_section_list_dict = defaultdict(list)
node_uid_list = [
x.uid
for x in portal_catalog(
portal_type='Account',
strict_account_type_uid=portal_categories.restrictedTraverse(account_type).getUid(),
)
]
if not node_uid_list:
return []
extra_kw = {}
ledger_relative_url_list = kw.get('ledger', None)
if ledger_relative_url_list:
if not isinstance(ledger_relative_url_list, list):
ledger_relative_url_list = [ledger_relative_url_list]
traverse = portal.portal_categories.restrictedTraverse
extra_kw['ledger_uid'] = [traverse(x).getUid() for x in ledger_relative_url_list]
period_list = [
('period_%i' % x, y)
for x, y in enumerate(sorted(period_list))
]
last_period_id = 'period_%s' % len(period_list)
for brain in portal.portal_simulation.getMovementHistoryList(
at_date=at_date,
simulation_state=simulation_state,
node_uid=node_uid_list,
portal_type=portal.getPortalAccountingMovementTypeList(),
section_uid=portal.Base_getSectionUidListForSectionCategory(
section_category,
section_category_strict,
),
grouping_query=ComplexQuery(
SimpleQuery(grouping_reference=None),
SimpleQuery(grouping_date=at_date, comparison_operator=">="),
logical_operator="OR",
),
**extra_kw
):
total_price = brain.total_price or 0
if reverse_price_sign:
total_price = -total_price
# Note that we use date_utc because date would load the object and we are just
# interested in the difference of days.
age = int(at_date - brain.date_utc)
if age < 0:
period_name = 'period_future'
else:
for period_name, period in period_list:
if age <= period:
break
else:
period_name = last_period_id
line_dict = lineCallback(
brain=brain,
period_name=period_name,
line_dict={
'mirror_section_uid': brain.mirror_section_uid,
'total_price': total_price,
'age': age,
period_name: total_price,
},
)
if line_dict is not None:
by_mirror_section_list_dict[brain.mirror_section_uid].append(line_dict)
line_list.append(line_dict)
for row in portal_catalog(
select_list=['title'],
uid=by_mirror_section_list_dict.keys(),
  • @vpelletier I think here we can potentially have empty list. Would it be better to have:

    if by_mirror_section_list_dict:
      for row in portal_catalog(
        select_list=['title'],
        ...

    I feel that something like this may break later on report rendering so maybe we can do another fix there to support empty report. Yet this is edge case and the first importan thing it to avoid a case of too long query.

  • the first importan thing it to avoid a case of too long query.

    +1

  • Thanks, done in 406a0181

Please register or sign in to reply
):
title = row.title
for line in by_mirror_section_list_dict[row.uid]:
line['mirror_section_title'] = title
return [
Object(
uid='new_',
**x
)
for x in reportCallback(line_list)
]
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>at_date, section_category, section_category_strict, simulation_state, period_list, account_type, lineCallback, reportCallback=None, **kw</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>AccountingTransactionModule_getAgedBalanceLineList</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
from Products.ZSQLCatalog.SQLCatalog import SimpleQuery, ComplexQuery if lineCallback is None:
from Products.PythonScripts.standard import Object lineCallback = lambda brain, period_name, line_dict: line_dict
portal = context.getPortalObject() if reportCallback is None:
reportCallback = lambda x: x
assert account_type in ('account_type/asset/receivable', 'account_type/liability/payable') traverse = context.getPortalObject().restrictedTraverse
currency = context.Base_getCurrencyForSection(section_category)
precision = context.account_module.getQuantityPrecisionFromResource(currency)
# we set the precision in request, for formatting on editable fields
portal.REQUEST.set('precision', precision)
section_uid = portal.Base_getSectionUidListForSectionCategory(
section_category, section_category_strict)
grouping_query = ComplexQuery(
SimpleQuery(grouping_reference=None),
SimpleQuery(grouping_date=at_date, comparison_operator=">="),
logical_operator="OR")
account_number_memo = {} account_number_memo = {}
def getAccountNumber(account_url): def getAccountNumber(account_url):
try: try:
return account_number_memo[account_url] return account_number_memo[account_url]
except KeyError: except KeyError:
account_number_memo[account_url] =\ account_number_memo[account_url] = traverse(account_url).Account_getGapId()
portal.restrictedTraverse(account_url).Account_getGapId()
return account_number_memo[account_url] return account_number_memo[account_url]
def myLineCallback(brain, period_name, line_dict):
section_title_memo = {None: ''} line_dict = lineCallback(brain=brain, period_name=period_name, line_dict=line_dict)
def getSectionTitle(uid): if line_dict is None:
try: return
return section_title_memo[uid]
except KeyError:
section_title = ''
brain_list = portal.portal_catalog(uid=uid, limit=2)
if brain_list:
brain, = brain_list
section_title = brain.getObject().getTranslatedTitle()
section_title_memo[uid] = section_title
return section_title_memo[uid]
last_period_id = 'period_%s' % len(period_list)
line_list = []
extra_kw = {}
ledger = kw.get('ledger', None)
if ledger:
if not isinstance(ledger, list):
# Allows the generation of reports on different ledgers as the same time
ledger = [ledger]
portal_categories = portal.portal_categories
ledger_value_list = [portal_categories.restrictedTraverse(ledger_category, None)
for ledger_category in ledger]
for ledger_value in ledger_value_list:
extra_kw.setdefault('ledger_uid', []).append(ledger_value.getUid())
for brain in portal.portal_simulation.getMovementHistoryList(
at_date=at_date,
simulation_state=simulation_state,
node_category_strict_membership=account_type,
portal_type=portal.getPortalAccountingMovementTypeList(),
section_uid=section_uid,
grouping_query=grouping_query,
sort_on=(('stock.mirror_section_uid', 'ASC'),
('stock.date', 'ASC'),
('stock.uid', 'ASC')),
**extra_kw):
movement = brain.getObject() movement = brain.getObject()
transaction = movement.getParentValue() transaction = movement.getParentValue()
# Detailed version of the aged balance report needs to get properties from
total_price = brain.total_price or 0 # the movement or transactions, but summary does not. This conditional is
if account_type == 'account_type/liability/payable': # here so that we do not load objects when running in summary mode.
total_price = - total_price line_dict['explanation_title'] = movement.hasTitle() and movement.getTitle() or transaction.getTitle()
line_dict['reference'] = transaction.getReference()
line = Object(uid='new_', line_dict['portal_type'] = transaction.getTranslatedPortalType()
mirror_section_title=getSectionTitle(brain.mirror_section_uid), line_dict['date'] = brain.date
mirror_section_uid=brain.mirror_section_uid, if brain.mirror_section_uid == movement.getSourceSectionUid() and brain.mirror_node_uid == movement.getSourceUid():
total_price=total_price,) line_dict['specific_reference'] = transaction.getDestinationReference()
line_dict['gap_id'] = getAccountNumber(movement.getDestination())
if detail:
# Detailed version of the aged balance report needs to get properties from
# the movement or transactions, but summary does not. This conditional is
# here so that we do not load objects when running in summary mode.
line['explanation_title'] = movement.hasTitle() and movement.getTitle() or transaction.getTitle()
line['reference'] = transaction.getReference()
line['portal_type'] = transaction.getTranslatedPortalType()
line['date'] = brain.date
if brain.mirror_section_uid == movement.getSourceSectionUid() and brain.mirror_node_uid == movement.getSourceUid():
line['specific_reference'] = transaction.getDestinationReference()
line['gap_id'] = getAccountNumber(movement.getDestination())
else:
line['specific_reference'] = transaction.getSourceReference()
line['gap_id'] = getAccountNumber(movement.getSource())
assert brain.mirror_section_uid == movement.getDestinationSectionUid()
# Note that we use date_utc because date would load the object and we are just
# interested in the difference of days.
age = int(at_date - brain.date_utc)
line['age'] = age
if age < 0:
line['period_future'] = total_price
elif age <= period_list[0]:
line['period_0'] = total_price
else: else:
for idx, period in enumerate(period_list): line_dict['specific_reference'] = transaction.getSourceReference()
if age <= period: line_dict['gap_id'] = getAccountNumber(movement.getSource())
line['period_%s' % idx] = total_price assert brain.mirror_section_uid == movement.getDestinationSectionUid()
break return line_dict
else: def myReportCallback(line_list):
line[last_period_id] = total_price return reportCallback(
sorted(
line_list.append(line) line_list,
key=lambda x: (x['mirror_section_title'], x['date'], x['explanation_title']),
return sorted( ),
line_list, )
key=lambda x:(x['mirror_section_title'], return context.AccountingTransactionModule_getAgedBalanceLineList(
x['mirror_section_uid'], # in case we have two mirror section with same title lineCallback=myLineCallback,
# we need lines from same section to be grouped together reportCallback=myReportCallback,
# for summary report. **kw
x.get('date'), )
x.get('explanation_title'),))
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
</item> </item>
<item> <item>
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string>at_date, section_category, section_category_strict, simulation_state, period_list, account_type, detail=True, **kw</string> </value> <value> <string>lineCallback=None, reportCallback=None, **kw</string> </value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
......
from Products.PythonScripts.standard import Object if lineCallback is None:
portal = context.getPortalObject() lineCallback = lambda brain, period_name, line_dict: line_dict
if reportCallback is None:
line_list = [] reportCallback = lambda x: x
detail_line_list = portal\ by_mirror_section_dict = {}
.AccountingTransactionModule_getDetailedAgedBalanceLineList( def myBrainCallback(brain, period_name, line_dict):
at_date, section_category, section_category_strict, try:
simulation_state, period_list, account_type, detail=False, **kw) mirror_section_line_dict = by_mirror_section_dict[line_dict['mirror_section_uid']]
except KeyError:
period_id_list = ['period_future'] line_dict = lineCallback(brain=brain, period_name=period_name, line_dict=line_dict)
for idx, _ in enumerate(period_list): if line_dict is None:
period_id_list.append('period_%s' % idx) return
period_id_list.append('period_%s' % (idx + 1)) by_mirror_section_dict[line_dict['mirror_section_uid']] = line_dict
return line_dict
# Initialize to something that will not be equals to else:
# detail_line.mirror_section_uid below. total_price = line_dict['total_price']
# In case we have used an account with mirror section, mirror_section_line_dict[period_name] = mirror_section_line_dict.get(period_name, 0) + total_price
# then mirror_section_uid will be None mirror_section_line_dict['total_price'] = mirror_section_line_dict['total_price'] + total_price
previous_mirror_section_uid = -1 def myReportCallback(line_list):
return reportCallback(
for detail_line in detail_line_list: sorted(line_list, key=lambda x: x['mirror_section_title']),
if previous_mirror_section_uid != detail_line.mirror_section_uid: )
line = Object(uid='new_', return context.AccountingTransactionModule_getAgedBalanceLineList(
mirror_section_title=detail_line.mirror_section_title, lineCallback=myBrainCallback,
total_price=0) reportCallback=myReportCallback,
line_list.append(line) **kw
previous_mirror_section_uid = detail_line.mirror_section_uid )
line['total_price'] = detail_line.total_price + line['total_price']
for period_id in period_id_list:
previous_value = line.get(period_id, 0)
added_value = detail_line.get(period_id, 0)
new_value = previous_value + added_value
if previous_value or new_value:
line[period_id] = new_value
return line_list
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
</item> </item>
<item> <item>
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string>at_date, section_category, section_category_strict, simulation_state, period_list, account_type, **kw</string> </value> <value> <string>lineCallback=None, reportCallback=None, **kw</string> </value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
......
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