From 129d7deaf188c85546dcbe472792435c4fa615d8 Mon Sep 17 00:00:00 2001
From: Vincent Pelletier <vincent@nexedi.com>
Date: Wed, 20 Apr 2016 07:09:12 +0200
Subject: [PATCH] AccountModule_getAccountListForTrialBalance: Improve
 performance.

- Correctly ignore parent_portal_type condition when all possible
  portal types are listed.
  This typically saves one stock-catalog join.
- Expand node_category_uid into lists of node_uids.
  This saves one very costly stock-catalog join, as nodes member of given
  category should typically be the minority, inciting MySQL query optimiser
  to use the index materialising this join, preventing use of actual stock
  indexes.
  As there should be few nodes involved in accounting reports, listing
  them all and passing them to queries should not matter much.

Also, add support for src__ parameter.
Also, assorted coding style improvements, changes and code factorisation.
---
 ...untModule_getAccountListForTrialBalance.py | 1085 ++++++++---------
 ...ntModule_getAccountListForTrialBalance.xml |    2 +-
 2 files changed, 523 insertions(+), 564 deletions(-)

diff --git a/bt5/erp5_accounting/SkinTemplateItem/portal_skins/erp5_accounting/AccountModule_getAccountListForTrialBalance.py b/bt5/erp5_accounting/SkinTemplateItem/portal_skins/erp5_accounting/AccountModule_getAccountListForTrialBalance.py
index f2340c9561..9d9c0ac8df 100644
--- a/bt5/erp5_accounting/SkinTemplateItem/portal_skins/erp5_accounting/AccountModule_getAccountListForTrialBalance.py
+++ b/bt5/erp5_accounting/SkinTemplateItem/portal_skins/erp5_accounting/AccountModule_getAccountListForTrialBalance.py
@@ -2,35 +2,68 @@ from Products.ZSQLCatalog.SQLCatalog import Query
 from Products.PythonScripts.standard import Object
 from ZTUtils import LazyFilter
 
-request = container.REQUEST
 portal = context.getPortalObject()
-getInventoryList = portal.portal_simulation.getInventoryList
-traverse = context.getPortalObject().restrictedTraverse
-getObject = context.getPortalObject().portal_catalog.getObject
+request = portal.REQUEST
+getInventoryList_ = portal.portal_simulation.getInventoryList
+traverse = portal.restrictedTraverse
+portal_catalog = portal.portal_catalog
+getObject = portal_catalog.getObject
 Base_translateString = portal.Base_translateString
 selected_gap = gap_root
+traverseCategory = portal.portal_categories.restrictedTraverse
+def getAccountUidListByCategory(category, strict_membership):
+  """
+  Transform node_category* into node_uid list.
+  """
+  if not category:
+    return []
+  kw = {
+    ('strict_' if strict_membership else '') + 'account_type_uid': [
+      traverseCategory(x).getUid() for x in category
+    ],
+  }
+  if node_uid:
+    kw['uid'] = node_uid
+  return [x.uid for x in portal_catalog(portal_type='Account', **kw)]
 
 inventory_movement_type_list = portal.getPortalInventoryMovementTypeList()
 # Balance Movement Type list is all movements that are both inventory movement
 # and accounting movement
-balance_movement_type_list = [ t for t in
-                               portal.getPortalAccountingMovementTypeList()
-                               if t in inventory_movement_type_list ]
-
-accounting_movement_type_list = [ t for t in
-                                  portal.getPortalAccountingMovementTypeList()
-                                  if t not in balance_movement_type_list ]
-
-inventory_params = dict(section_uid=section_uid,
-                        simulation_state=simulation_state,
-                        precision=precision,
-                        group_by_resource=0)
-
+balance_movement_type_list = [
+  t for t in portal.getPortalAccountingMovementTypeList()
+  if t in inventory_movement_type_list
+]
+accounting_movement_type_list = [
+  t for t in portal.getPortalAccountingMovementTypeList()
+  if t not in balance_movement_type_list
+]
+
+src_list = []
+def getInventoryList(node_uid=None, **kw):
+  if not node_uid and node_uid is not None:
+    return []
+  for key, value in inventory_params.iteritems():
+    assert key not in kw, key
+    kw[key] = value
+  result = getInventoryList_(
+    section_uid=section_uid,
+    simulation_state=simulation_state,
+    precision=precision,
+    group_by_resource=0,
+    group_by_node=1,
+    node_uid=node_uid,
+    src__=src__,
+    **kw
+  )
+  if src__:
+    src_list.append(result)
+    return []
+  return result
+inventory_params = {}
 if group_analytic:
   inventory_params['group_by'] = group_analytic
   group_analytic = tuple(group_analytic)
-
-if portal_type and portal_type != portal.getPortalAccountingTransactionTypeList():
+if portal_type and set(portal_type) != set(portal.getPortalAccountingTransactionTypeList()):
   inventory_params['parent_portal_type'] = portal_type
 if function:
   if function == 'None':
@@ -58,33 +91,42 @@ if project:
 if mirror_section_category:
   inventory_params['mirror_section_category'] = mirror_section_category
 
-if node_uid:
-  inventory_params['node_uid'] = node_uid
-
-MARKER = Object()
-
 # a dictionary (node_relative_url, mirror_section_uid, payment_uid + analytic)
-#                        -> dict(debit=, credit=)
+#                        -> {'debit'=, 'credit'=}
 line_per_account = {}
-# a dictionnary node_relative_url -> boolean "do we have transactions for this
-# account ?"
-account_used = {}
+account_used = set()
+def markNodeUsed(node):
+  account_used.add(node['node_relative_url'])
+def getTotalPrice(node):
+  return node['total_price'] or 0
 
 account_type = portal.portal_categories.account_type
-balance_sheet_account_type_list = [c[0] for c in 
- account_type.asset.getCategoryChildItemList(base=1, is_self_excluded=False, display_none_category=False ) + \
- account_type.equity.getCategoryChildItemList(base=1, is_self_excluded=False, display_none_category=False) + \
- account_type.liability.getCategoryChildItemList(base=1, is_self_excluded=False, display_none_category=False) ]
-
-profit_and_loss_account_type = [
-                  'account_type/expense',
-                  'account_type/income',]
-
-account_type_to_group_by_payment = [ 'account_type/asset/cash/bank' ]
+balance_sheet_account_type_list = [c[0] for c in
+  account_type.asset.getCategoryChildItemList(base=1, is_self_excluded=False, display_none_category=False) +
+  account_type.equity.getCategoryChildItemList(base=1, is_self_excluded=False, display_none_category=False) +
+  account_type.liability.getCategoryChildItemList(base=1, is_self_excluded=False, display_none_category=False)
+]
+
+profit_and_loss_account_uid_list = getAccountUidListByCategory(
+  [
+    'account_type/expense',
+    'account_type/income',
+  ],
+  strict_membership=0,
+)
+
+account_type_to_group_by_payment = [
+  'account_type/asset/cash/bank'
+]
+node_uid_of_strict_account_type_to_group_by_payment = getAccountUidListByCategory(
+  account_type_to_group_by_payment,
+  strict_membership=1,
+)
 
 account_type_payable_receivable = [
-                  'account_type/asset/receivable',
-                  'account_type/liability/payable', ]
+  'account_type/asset/receivable',
+  'account_type/liability/payable',
+]
 
 
 # For initial balance of third party accounts, we want the same bottom line figure for initial
@@ -118,18 +160,23 @@ if expand_accounts:
   account_type_to_group_by_mirror_section_previous_period = account_type_payable_receivable
 else:
   account_type_to_group_by_mirror_section = []
-  account_type_to_group_by_mirror_section_previous_period = []
   if show_detailed_balance_columns:
     account_type_to_group_by_mirror_section_previous_period = account_type_payable_receivable
-
-
-account_type_to_group_by_node = [at for at in balance_sheet_account_type_list
-  if at not in account_type_to_group_by_payment
-  and at not in account_type_to_group_by_mirror_section]
-
-account_type_to_group_by_node_previous_period = [at for at in account_type_to_group_by_node
-  if at not in account_type_to_group_by_mirror_section_previous_period]
-
+  else:
+    account_type_to_group_by_mirror_section_previous_period = []
+node_uid_of_strict_account_type_to_group_by_mirror_section = getAccountUidListByCategory(
+  category=account_type_to_group_by_mirror_section,
+  strict_membership=1,
+)
+
+account_type_to_group_by_node = [
+  x for x in balance_sheet_account_type_list
+  if x not in account_type_to_group_by_payment and x not in account_type_to_group_by_mirror_section
+]
+node_uid_of_strict_account_type_to_group_by_node = getAccountUidListByCategory(
+  category=account_type_to_group_by_node,
+  strict_membership=1,
+)
 
 total_debit = 0
 total_credit = 0
@@ -138,10 +185,9 @@ total_initial_credit_balance = 0
 total_final_balance_if_debit = 0
 total_final_balance_if_credit = 0
 
-def getKey(brain, mirror_section=MARKER, payment=MARKER, all_empty=False):
-  key = (brain['node_relative_url'],
-         mirror_section,
-         payment)
+MARKER = Object()
+def getAccountProps(brain, mirror_section=MARKER, payment=MARKER, all_empty=False):
+  key = (brain['node_relative_url'], mirror_section, payment)
   for analytic in group_analytic:
     if all_empty:
       key += (MARKER, )
@@ -149,194 +195,111 @@ def getKey(brain, mirror_section=MARKER, payment=MARKER, all_empty=False):
       key += (getattr(brain, analytic), )
     else:
       key += (brain.getObject().getProperty(analytic.replace('strict_', '', 1)), )
-  return key
-
-analytic_title_dict = {None: '', }
-def getAnalyticTitleFromUid(uid):
-  if uid is MARKER:
-    return ''
   try:
-    return analytic_title_dict[uid]
+    value = line_per_account[key]
   except KeyError:
-    node = getObject(uid)
-    title = node.getTranslatedTitle()
-    reference = node.getReference()
-    if reference:
-      title = '%s - %s' % (reference, title)
-    return analytic_title_dict.setdefault(uid, title)
+    line_per_account[key] = value = {'debit': 0, 'credit': 0}
+  return value
 
-section_price_currency_dict = {None: ''}
-def getSectionPriceCurrencyFromSectionUid(uid):
-  if uid is MARKER:
-    return ''
-  try:
-    return section_price_currency_dict[uid]
-  except KeyError:
-    section = getObject(uid)
-    price_currency = ''
-    if section is not None:
-      price_currency = section.getProperty('price_currency_reference')
-    return section_price_currency_dict.setdefault(uid, price_currency)
+def addAccountProps(entry_list, *args, **kw):
+  account_props = getAccountProps(*args, **kw)
+  for key, value in entry_list:
+    account_props[key] = account_props.get(key, 0) + value
 
 # standards accounts {{{
-for node in getInventoryList(
-                node_category_strict_membership=account_type_to_group_by_node,
-                group_by_node=1,
-                omit_asset_decrease=1,
-                from_date=from_date,
-                at_date=at_date,
-                portal_type=accounting_movement_type_list,
-                **inventory_params):
-  account_used[node['node_relative_url']] = 1
-  account_props = line_per_account.setdefault(getKey(node), dict(debit=0, credit=0))
-  total_price = node['total_price'] or 0
-  account_props['debit'] = total_price
+local_inventory_params = {
+  'node_uid': node_uid_of_strict_account_type_to_group_by_node,
+  'from_date': from_date,
+  'at_date': at_date,
+  'portal_type': accounting_movement_type_list,
+}
+for node in getInventoryList(omit_asset_decrease=1, **local_inventory_params):
+  markNodeUsed(node)
+  total_price = getTotalPrice(node)
+  getAccountProps(node)['debit'] = total_price
   total_debit += round(total_price, precision)
-
-for node in getInventoryList(
-                node_category_strict_membership=account_type_to_group_by_node,
-                group_by_node=1,
-                omit_asset_increase=1,
-                from_date=from_date,
-                at_date=at_date,
-                portal_type=accounting_movement_type_list,
-                **inventory_params):
-  account_used[node['node_relative_url']] = 1
-  account_props = line_per_account.setdefault(getKey(node), dict(debit=0, credit=0))
-  total_price = node['total_price'] or 0
-  account_props['credit'] = -total_price
+for node in getInventoryList(omit_asset_increase=1, **local_inventory_params):
+  markNodeUsed(node)
+  total_price = getTotalPrice(node)
+  getAccountProps(node)['credit'] = -total_price
   total_credit -= round(total_price, precision)
 # }}}
 
 ### profit & loss accounts {{{
-for node in getInventoryList(
-                node_category=profit_and_loss_account_type,
-                from_date=max(period_start_date, from_date),
-                group_by_node=1,
-                omit_asset_decrease=1,
-                at_date=at_date,
-                portal_type=accounting_movement_type_list,
-                **inventory_params):
-  account_used[node['node_relative_url']] = 1
-  account_props = line_per_account.setdefault(getKey(node), dict(debit=0, credit=0))
-  total_price = node['total_price'] or 0
-  account_props['debit'] = total_price
+local_inventory_params = {
+  'node_uid': profit_and_loss_account_uid_list,
+  'from_date': max(period_start_date, from_date),
+  'at_date': at_date,
+  'portal_type': accounting_movement_type_list,
+}
+for node in getInventoryList(omit_asset_decrease=1, **local_inventory_params):
+  markNodeUsed(node)
+  total_price = getTotalPrice(node)
+  getAccountProps(node)['debit'] = total_price
   total_debit += round(total_price, precision)
-
-for node in getInventoryList(
-                node_category=profit_and_loss_account_type,
-                from_date=max(period_start_date, from_date),
-                group_by_node=1,
-                omit_asset_increase=1,
-                at_date=at_date,
-                portal_type=accounting_movement_type_list,
-                **inventory_params):
-  account_used[node['node_relative_url']] = 1
-  account_props = line_per_account.setdefault(getKey(node), dict(debit=0, credit=0))
-  total_price = node['total_price'] or 0
-  account_props['credit'] = -total_price
+for node in getInventoryList(omit_asset_increase=1, **local_inventory_params):
+  markNodeUsed(node)
+  total_price = getTotalPrice(node)
+  getAccountProps(node)['credit'] = -total_price
   total_credit -= round(total_price, precision)
 # }}}
 
 # payable / receivable accounts {{{
-if account_type_to_group_by_mirror_section:
-  for node in getInventoryList(
-                  node_category_strict_membership=
-                          account_type_to_group_by_mirror_section,
-                  group_by_mirror_section=1,
-                  group_by_node=1,
-                  omit_asset_decrease=1,
-                  from_date=from_date,
-                  at_date=at_date,
-                  portal_type=accounting_movement_type_list,
-                  **inventory_params):
-    account_used[node['node_relative_url']] = 1
-    account_props = line_per_account.setdefault(
-          getKey(node, mirror_section=node['mirror_section_uid']),
-          dict(debit=0, credit=0))
-    total_price = node['total_price'] or 0
-    account_props['debit'] = total_price
+if node_uid_of_strict_account_type_to_group_by_mirror_section:
+  if src__:
+    src_list.append('-- payable / receivable accounts')
+  local_inventory_params = {
+    'node_uid': node_uid_of_strict_account_type_to_group_by_mirror_section,
+    'group_by_mirror_section': 1,
+    'from_date': from_date,
+    'at_date': at_date,
+    'portal_type': accounting_movement_type_list,
+  }
+  for node in getInventoryList(omit_asset_decrease=1, **local_inventory_params):
+    markNodeUsed(node)
+    total_price = getTotalPrice(node)
+    getAccountProps(node, mirror_section=node['mirror_section_uid'])['debit'] = total_price
     total_debit += round(total_price, precision)
-
-  for node in getInventoryList(
-                  node_category_strict_membership=
-                          account_type_to_group_by_mirror_section,
-                  group_by_mirror_section=1,
-                  group_by_node=1,
-                  omit_asset_increase=1,
-                  from_date=from_date,
-                  at_date=at_date,
-                  portal_type=accounting_movement_type_list,
-                  **inventory_params):
-    account_used[node['node_relative_url']] = 1
-    account_props = line_per_account.setdefault(
-          getKey(node, mirror_section=node['mirror_section_uid']),
-          dict(debit=0, credit=0))
-    total_price = node['total_price'] or 0
-    account_props['credit'] = - total_price
+  for node in getInventoryList(omit_asset_increase=1, **local_inventory_params):
+    markNodeUsed(node)
+    total_price = getTotalPrice(node)
+    getAccountProps(node, mirror_section=node['mirror_section_uid'])['credit'] = -total_price
     total_credit -= round(total_price, precision)
 # }}}
 
 # bank accounts {{{
-if account_type_to_group_by_payment:
-  for node in getInventoryList(
-                  node_category_strict_membership=
-                          account_type_to_group_by_payment,
-                  group_by_payment=1,
-                  group_by_node=1,
-                  omit_asset_decrease=1,
-                  from_date=from_date,
-                  at_date=at_date,
-                  portal_type=accounting_movement_type_list,
-                  **inventory_params):
-    account_used[node['node_relative_url']] = 1
-    account_props = line_per_account.setdefault(
-          getKey(node, payment=node['payment_uid']),
-          dict(debit=0, credit=0))
-    total_price = node['total_price'] or 0
-    account_props['debit'] = total_price
+if node_uid_of_strict_account_type_to_group_by_payment:
+  if src__:
+    src_list.append('-- bank accounts')
+  local_inventory_params = {
+    'node_uid': node_uid_of_strict_account_type_to_group_by_payment,
+    'group_by_payment': 1,
+    'from_date': from_date,
+    'at_date': at_date,
+    'portal_type': accounting_movement_type_list,
+  }
+  for node in getInventoryList(omit_asset_decrease=1, **local_inventory_params):
+    markNodeUsed(node)
+    total_price = getTotalPrice(node)
+    getAccountProps(node, payment=node['payment_uid'])['debit'] = total_price
     total_debit += round(total_price, precision)
-
-  for node in getInventoryList(
-                  node_category_strict_membership=
-                          account_type_to_group_by_payment,
-                  group_by_payment=1,
-                  group_by_node=1,
-                  omit_asset_increase=1,
-                  from_date=from_date,
-                  at_date=at_date,
-                  portal_type=accounting_movement_type_list,
-                  **inventory_params):
-    account_used[node['node_relative_url']] = 1
-    account_props = line_per_account.setdefault(
-          getKey(node, payment=node['payment_uid']),
-          dict(debit=0, credit=0))
-    total_price = node['total_price'] or 0
-    account_props['credit'] = - total_price
+  for node in getInventoryList(omit_asset_increase=1, **local_inventory_params):
+    markNodeUsed(node)
+    total_price = getTotalPrice(node)
+    getAccountProps(node, payment=node['payment_uid'])['credit'] = - total_price
     total_credit -= round(total_price, precision)
-  # }}}
-
-
-node_title_and_id_cache = {}
-def getNodeTitleAndId(node_relative_url):
-  try:
-    return node_title_and_id_cache[node_relative_url]
-  except KeyError:
-    node = traverse(node_relative_url)
-    return node_title_and_id_cache.setdefault(node_relative_url,
-                  ( node.getUid(),
-                    node.getTranslatedTitle(),
-                    node.Account_getGapId(gap_root=selected_gap),
-                    node.getProperty('string_index'),
-                    node))
+# }}}
 
 # include all accounts, even those not selected before (no movements in the
 # period)
-for node in LazyFilter(context.account_module.contentValues(), skip=''):
+for node in LazyFilter(portal.account_module.contentValues(), skip=''): # XXX: context should already be account_module
   if node.getRelativeUrl() not in account_used:
-    line_per_account.setdefault(
-          getKey(dict(node_relative_url=node.getRelativeUrl()), all_empty=True),
-          dict(debit=0, credit=0))
+    getAccountProps(
+      {
+        'node_relative_url': node.getRelativeUrl(),
+      },
+      all_empty=True,
+    )
 
 initial_balance_date = (from_date - 1).latestTime()
 
@@ -344,305 +307,306 @@ initial_balance_date = (from_date - 1).latestTime()
 
 # standards accounts {{{
 # balance at period start date
+local_inventory_params = {
+  'node_uid': getAccountUidListByCategory(
+    [
+      x for x in account_type_to_group_by_node
+      if x not in account_type_to_group_by_mirror_section_previous_period
+    ],
+    strict_membership=1,
+  ),
+}
 for node in getInventoryList(
-                node_category_strict_membership=
-                   account_type_to_group_by_node_previous_period,
-                group_by_node=1,
-                to_date=period_start_date,
-                portal_type=accounting_movement_type_list +
-                              balance_movement_type_list,
-                **inventory_params):
-  account_props = line_per_account.setdefault(getKey(node), dict(debit=0, credit=0))
-  total_price = node['total_price'] or 0
-  account_props['initial_balance'] = account_props.get(
-              'initial_balance', 0) + total_price
-  account_props['initial_debit_balance'] = account_props.get(
-              'initial_debit_balance', 0) + max(total_price, 0)
-  account_props['initial_credit_balance'] = account_props.get(
-              'initial_credit_balance', 0) + max(- total_price, 0)
-
+      to_date=period_start_date,
+      portal_type=accounting_movement_type_list + balance_movement_type_list,
+      **local_inventory_params
+    ):
+  total_price = getTotalPrice(node)
+  addAccountProps(
+    (
+      ('initial_balance', total_price),
+      ('initial_debit_balance', max(total_price, 0)),
+      ('initial_credit_balance', max(-total_price, 0)),
+    ),
+    node,
+  )
 found_balance = False
 # Balance Transaction
 for node in getInventoryList(
-                node_category_strict_membership=
-                   account_type_to_group_by_node_previous_period,
-                group_by_node=1,
-                from_date=from_date,
-                at_date=from_date + 1,
-                portal_type=balance_movement_type_list,
-                **inventory_params):
-  account_props = line_per_account.setdefault(getKey(node), dict(debit=0, credit=0))
-  total_price = node['total_price'] or 0
-  account_props['initial_balance'] = account_props.get(
-              'initial_balance', 0) + total_price
-  account_props['initial_debit_balance'] = account_props.get(
-              'initial_debit_balance', 0) + max(total_price, 0)
-  account_props['initial_credit_balance'] = account_props.get(
-              'initial_credit_balance', 0) + max(- total_price, 0)
+      from_date=from_date,
+      at_date=from_date + 1,
+      portal_type=balance_movement_type_list,
+      **local_inventory_params
+    ):
+  total_price = getTotalPrice(node)
+  addAccountProps(
+    (
+      ('initial_balance', total_price),
+      ('initial_debit_balance', max(total_price, 0)),
+      ('initial_credit_balance', max(-total_price, 0)),
+    ),
+    node,
+  )
   found_balance = True
-
 period_movement_type_list = accounting_movement_type_list
 if not found_balance:
-  period_movement_type_list = accounting_movement_type_list +\
-      balance_movement_type_list
-
-for node in getInventoryList(
-                node_category_strict_membership=
-                          account_type_to_group_by_node,
-                group_by_node=1,
-                omit_asset_decrease=1,
-                from_date=period_start_date,
-                to_date=from_date,
-                portal_type=period_movement_type_list,
-                **inventory_params):
-  account_props = line_per_account.setdefault(getKey(node), dict(debit=0, credit=0))
-  total_price = node['total_price'] or 0
-  account_props['initial_debit_balance'] = account_props.get(
-                    'initial_debit_balance', 0) + total_price
-
-for node in getInventoryList(
-                node_category_strict_membership=
-                          account_type_to_group_by_node,
-                group_by_node=1,
-                omit_asset_increase=1,
-                from_date=period_start_date,
-                to_date=from_date,
-                portal_type=period_movement_type_list,
-                **inventory_params):
-  account_props = line_per_account.setdefault(getKey(node), dict(debit=0, credit=0))
-  total_price = node['total_price'] or 0
-  account_props['initial_credit_balance'] = account_props.get(
-                    'initial_credit_balance', 0) - total_price
+  period_movement_type_list += balance_movement_type_list
+local_inventory_params = {
+  'node_uid': node_uid_of_strict_account_type_to_group_by_node,
+  'from_date': period_start_date,
+  'to_date': from_date,
+  'portal_type': period_movement_type_list,
+}
+for node in getInventoryList(omit_asset_decrease=1, **local_inventory_params):
+  addAccountProps(
+    (
+      ('initial_debit_balance', getTotalPrice(node)),
+    ),
+    node,
+  )
+for node in getInventoryList(omit_asset_increase=1, **local_inventory_params):
+  addAccountProps(
+    (
+      ('initial_credit_balance', -(getTotalPrice(node))),
+    ),
+    node,
+  )
 # }}}
 
 ### profit & loss accounts {{{
-for node in getInventoryList(
-                node_category=profit_and_loss_account_type,
-                omit_asset_decrease=1,
-                from_date=min(period_start_date,
-                              initial_balance_date),
-                at_date=initial_balance_date,
-                group_by_node=1,
-                portal_type=accounting_movement_type_list,
-                **inventory_params):
-  account_props = line_per_account.setdefault(getKey(node), dict(debit=0, credit=0))
-  total_price = node['total_price'] or 0
-  account_props['initial_debit_balance'] = account_props.get(
-                    'initial_debit_balance', 0) + total_price
-
-for node in getInventoryList(
-                node_category=profit_and_loss_account_type,
-                omit_asset_increase=1,
-                from_date=min(period_start_date,
-                              initial_balance_date),
-                at_date=initial_balance_date,
-                group_by_node=1,
-                portal_type=accounting_movement_type_list,
-                **inventory_params):
-  account_props = line_per_account.setdefault(getKey(node), dict(debit=0, credit=0))
-  total_price = node['total_price'] or 0
-  account_props['initial_credit_balance'] = account_props.get(
-                    'initial_credit_balance', 0) - total_price
+local_inventory_params = {
+  'node_uid': profit_and_loss_account_uid_list,
+  'from_date': min(period_start_date, initial_balance_date),
+  'at_date': initial_balance_date,
+  'portal_type': accounting_movement_type_list,
+}
+for node in getInventoryList(omit_asset_decrease=1, **local_inventory_params):
+  addAccountProps(
+    (
+      ('initial_debit_balance', getTotalPrice(node)),
+    ),
+    node,
+  )
+for node in getInventoryList(omit_asset_increase=1, **local_inventory_params):
+  addAccountProps(
+    (
+      ('initial_credit_balance', -(getTotalPrice(node))),
+    ),
+    node,
+  )
 # }}}
 
 # payable / receivable accounts {{{
-# initial balance
-if account_type_to_group_by_mirror_section_previous_period:
-  for node in getInventoryList(
-                  node_category_strict_membership=account_type_to_group_by_mirror_section_previous_period,
-                  group_by_mirror_section=1,
-                  group_by_node=1,
-                  to_date=period_start_date,
-                  portal_type=accounting_movement_type_list +
-                                balance_movement_type_list,
-                  **inventory_params):
-    mirror_section_key = MARKER
-    if expand_accounts:
-      mirror_section_key = node['mirror_section_uid']
-
-    account_props = line_per_account.setdefault(
-                      getKey(node, mirror_section=mirror_section_key),
-                             dict(debit=0, credit=0))
-    total_price = node['total_price'] or 0
-    account_props['initial_debit_balance'] = account_props.get(
-                    'initial_debit_balance', 0) + max(total_price, 0)
-    account_props['initial_credit_balance'] = account_props.get(
-                   'initial_credit_balance', 0) + max(-total_price, 0)
-
 found_balance=False
-# Balance Transactions
 if account_type_to_group_by_mirror_section_previous_period:
+  if src__:
+    src_list.append('-- payable / receivable accounts')
+  # initial balance
+  local_inventory_params = {
+    'node_uid': getAccountUidListByCategory(
+      category=account_type_to_group_by_mirror_section_previous_period,
+      strict_membership=1,
+    ),
+    'group_by_mirror_section': 1,
+  }
   for node in getInventoryList(
-                  node_category_strict_membership=account_type_to_group_by_mirror_section_previous_period,
-                  group_by_mirror_section=1,
-                  group_by_node=1,
-                  from_date=from_date,
-                  at_date=from_date + 1,
-                  portal_type=balance_movement_type_list,
-                  **inventory_params):
-    mirror_section_key = MARKER
-    if expand_accounts:
-      mirror_section_key = node['mirror_section_uid']
-    account_props = line_per_account.setdefault(
-            getKey(node, mirror_section=mirror_section_key),
-                   dict(debit=0, credit=0))
-    total_price = node['total_price'] or 0
-    account_props['initial_debit_balance'] = account_props.get(
-                'initial_debit_balance', 0) + max(total_price, 0)
-    account_props['initial_credit_balance'] = account_props.get(
-                'initial_credit_balance', 0) + max(- total_price, 0)
+        to_date=period_start_date,
+        portal_type=accounting_movement_type_list + balance_movement_type_list,
+        **local_inventory_params
+      ):
+    total_price = getTotalPrice(node)
+    addAccountProps(
+      (
+        ('initial_debit_balance', max(total_price, 0)),
+        ('initial_credit_balance', max(-total_price, 0)),
+      ),
+      node,
+      mirror_section=node['mirror_section_uid'] if expand_accounts else MARKER,
+    )
+  # Balance Transactions
+  for node in getInventoryList(
+        from_date=from_date,
+        at_date=from_date + 1,
+        portal_type=balance_movement_type_list,
+        **local_inventory_params
+      ):
+    total_price = getTotalPrice(node)
+    addAccountProps(
+      (
+        ('initial_debit_balance', max(total_price, 0)),
+        ('initial_credit_balance', max(-total_price, 0)),
+      ),
+      node,
+      mirror_section=node['mirror_section_uid'] if expand_accounts else MARKER,
+    )
     found_balance=True
-
-
 period_movement_type_list = accounting_movement_type_list
 if not found_balance:
-  period_movement_type_list = accounting_movement_type_list +\
-      balance_movement_type_list
-
-
+  period_movement_type_list += balance_movement_type_list
 if expand_accounts:
-  for node in getInventoryList(
-                  node_category_strict_membership=
-                          account_type_to_group_by_mirror_section,
-                  group_by_mirror_section=1,
-                  group_by_node=1,
-                  omit_asset_decrease=1,
-                  from_date=period_start_date,
-                  to_date=from_date,
-                  portal_type=period_movement_type_list,
-                  **inventory_params):
-    account_props = line_per_account.setdefault(
-          getKey(node, mirror_section=node['mirror_section_uid']),
-                 dict(debit=0, credit=0))
-    total_price = node['total_price'] or 0
-    account_props['initial_debit_balance'] = account_props.get(
-                      'initial_debit_balance', 0) + total_price
-
-  for node in getInventoryList(
-                  node_category_strict_membership=
-                          account_type_to_group_by_mirror_section,
-                  group_by_mirror_section=1,
-                  group_by_node=1,
-                  omit_asset_increase=1,
-                  from_date=period_start_date,
-                  to_date=from_date,
-                  portal_type=period_movement_type_list,
-                  **inventory_params):
-    account_props = line_per_account.setdefault(
-          getKey(node, mirror_section=node['mirror_section_uid']),
-                 dict(debit=0, credit=0))
-    total_price = node['total_price'] or 0
-    account_props['initial_credit_balance'] = account_props.get(
-                      'initial_credit_balance', 0) - total_price
+  if src__:
+    src_list.append('-- expand_accounts')
+  local_inventory_params = {
+    'node_uid': node_uid_of_strict_account_type_to_group_by_mirror_section,
+    'group_by_mirror_section': 1,
+    'from_date': period_start_date,
+    'to_date': from_date,
+    'portal_type': period_movement_type_list,
+  }
+  for node in getInventoryList(omit_asset_decrease=1, **local_inventory_params):
+    addAccountProps(
+      (
+        ('initial_debit_balance', getTotalPrice(node)),
+      ),
+      node,
+      mirror_section=node['mirror_section_uid'],
+    )
+  for node in getInventoryList(omit_asset_increase=1, **local_inventory_params):
+    addAccountProps(
+      (
+        ('initial_credit_balance', -(getTotalPrice(node))),
+      ),
+      node,
+      mirror_section=node['mirror_section_uid'],
+    )
 # }}}
 
 # bank accounts {{{
-if account_type_to_group_by_payment:
+if node_uid_of_strict_account_type_to_group_by_payment:
+  if src__:
+    src_list.append('-- bank accounts')
   # Initial balance
+  local_inventory_params = {
+    'node_uid': node_uid_of_strict_account_type_to_group_by_payment,
+    'group_by_payment': 1,
+  }
   for node in getInventoryList(
-                  node_category_strict_membership=
-                          account_type_to_group_by_payment,
-                  group_by_payment=1,
-                  group_by_node=1,
-                  to_date=period_start_date,
-                  portal_type=accounting_movement_type_list +
-                                balance_movement_type_list,
-                  **inventory_params):
-    account_props = line_per_account.setdefault(
-          getKey(node, payment=node['payment_uid']),
-                 dict(debit=0, credit=0))
-    total_price = node['total_price'] or 0
-    account_props['initial_debit_balance'] = account_props.get(
-                    'initial_debit_balance', 0) + max(total_price, 0)
-    account_props['initial_credit_balance'] = account_props.get(
-                   'initial_credit_balance', 0) + max(- total_price, 0)
-
+        to_date=period_start_date,
+        portal_type=accounting_movement_type_list + balance_movement_type_list,
+        **local_inventory_params
+      ):
+    total_price = getTotalPrice(node)
+    addAccountProps(
+      (
+        ('initial_debit_balance', max(total_price, 0)),
+        ('initial_credit_balance', max(-total_price, 0)),
+      ),
+      node,
+      payment=node['payment_uid'],
+    )
   found_balance = False
   # Balance Transaction
   for node in getInventoryList(
-                  node_category_strict_membership=
-                          account_type_to_group_by_payment,
-                  group_by_payment=1,
-                  group_by_node=1,
-                  from_date=from_date,
-                  at_date=from_date + 1,
-                  portal_type=balance_movement_type_list,
-                  **inventory_params):
-    account_used[node['node_relative_url']] = 1
-    account_props = line_per_account.setdefault(
-          getKey(node, payment=node['payment_uid']),
-                 dict(debit=0, credit=0))
-    total_price = node['total_price'] or 0
-    total_price += account_props.get('initial_debit_balance', 0)
-    total_price -= account_props.get('initial_credit_balance', 0)
+        from_date=from_date,
+        at_date=from_date + 1,
+        portal_type=balance_movement_type_list,
+        **local_inventory_params
+      ):
+    account_props = getAccountProps(node, payment=node['payment_uid'])
+    total_price = (getTotalPrice(node)) + account_props.get('initial_debit_balance', 0) - account_props.get('initial_credit_balance', 0)
     account_props['initial_debit_balance'] = max(total_price, 0)
     account_props['initial_credit_balance'] = max(- total_price, 0)
     found_balance = True
-
   period_movement_type_list = accounting_movement_type_list
   if not found_balance:
-    period_movement_type_list = accounting_movement_type_list +\
-        balance_movement_type_list
-  for node in getInventoryList(
-                  node_category_strict_membership=
-                          account_type_to_group_by_payment,
-                  group_by_payment=1,
-                  group_by_node=1,
-                  omit_asset_decrease=1,
-                  from_date=period_start_date,
-                  to_date=from_date,
-                  portal_type=period_movement_type_list,
-                  **inventory_params):
-    account_used[node['node_relative_url']] = 1
-    account_props = line_per_account.setdefault(
-          getKey(node, payment=node['payment_uid']),
-                 dict(debit=0, credit=0))
-    total_price = node['total_price'] or 0
-    account_props['initial_debit_balance'] = account_props.get(
-                      'initial_debit_balance', 0) + total_price
+    period_movement_type_list += balance_movement_type_list
+  local_inventory_params = {
+    'node_uid': node_uid_of_strict_account_type_to_group_by_payment,
+    'group_by_payment': 1,
+    'from_date': period_start_date,
+    'to_date': from_date,
+    'portal_type': period_movement_type_list,
+  }
+  for node in getInventoryList(omit_asset_decrease=1, **local_inventory_params):
+    addAccountProps(
+      (
+        ('initial_debit_balance', getTotalPrice(node)),
+      ),
+      node,
+      payment=node['payment_uid'],
+    )
+  for node in getInventoryList(omit_asset_increase=1, **local_inventory_params):
+    addAccountProps(
+      (
+        ('initial_credit_balance', -(getTotalPrice(node))),
+      ),
+      node,
+      payment=node['payment_uid'],
+    )
+# }}}
 
-  for node in getInventoryList(
-                  node_category_strict_membership=
-                          account_type_to_group_by_payment,
-                  group_by_payment=1,
-                  group_by_node=1,
-                  omit_asset_increase=1,
-                  from_date=period_start_date,
-                  to_date=from_date,
-                  portal_type=period_movement_type_list,
-                  **inventory_params):
-    account_used[node['node_relative_url']] = 1
-    account_props = line_per_account.setdefault(
-          getKey(node, payment=node['payment_uid']),
-                 dict(debit=0, credit=0))
-    total_price = node['total_price'] or 0
-    account_props['initial_credit_balance'] = account_props.get(
-                      'initial_credit_balance', 0) - total_price
-  # }}}
+if src__:
+  return src_list
 
+node_title_and_id_cache = {}
+def getNodeTitleAndId(node_relative_url):
+  try:
+    return node_title_and_id_cache[node_relative_url]
+  except KeyError:
+    node = traverse(node_relative_url)
+    return node_title_and_id_cache.setdefault(
+      node_relative_url,
+      (
+        node.getUid(),
+        node.getTranslatedTitle(),
+        node.Account_getGapId(gap_root=selected_gap),
+        node.getProperty('string_index'),
+        node,
+      )
+    )
+
+section_price_currency_dict = {None: ''}
+def getSectionPriceCurrencyFromSectionUid(uid):
+  if uid is MARKER:
+    return ''
+  try:
+    return section_price_currency_dict[uid]
+  except KeyError:
+    section = getObject(uid)
+    if section is None:
+      price_currency = ''
+    else:
+      price_currency = section.getProperty('price_currency_reference')
+    section_price_currency_dict[uid] = price_currency
+    return price_currency
+
+analytic_title_dict = {None: ''}
+def getAnalyticTitleFromUid(uid):
+  if uid is MARKER:
+    return ''
+  try:
+    return analytic_title_dict[uid]
+  except KeyError:
+    node = getObject(uid)
+    title = node.getTranslatedTitle()
+    reference = node.getReference()
+    if reference:
+      title = '%s - %s' % (reference, title)
+    analytic_title_dict[uid] = title
+    return title
+
+TRANSLATED_NONE = Base_translateString('None')
 line_list = []
-for key, data in line_per_account.items():
+for key, data in line_per_account.iteritems():
   node_relative_url = key[0]
   mirror_section_uid = key[1]
   payment_uid = key[2]
   analytic_key_list = key[3:]
 
-  mirror_section_title = None
   if expand_accounts and mirror_section_uid is not MARKER:
     mirror_section_title = getObject(mirror_section_uid).getTitle()
+  else:
+     mirror_section_title = None
 
-  node_uid, node_title, node_id, node_string_index, node =\
-          getNodeTitleAndId(node_relative_url)
+  node_uid, node_title, node_id, node_string_index, node = getNodeTitleAndId(node_relative_url)
 
   if selected_gap and not node.isMemberOf(selected_gap):
     continue
 
   if payment_uid is not MARKER:
-    if payment_uid is None:
-      node_title = '%s (%s)' % ( node_title, Base_translateString('None'))
-    else:
-      payment = getObject(payment_uid)
-      node_title = "%s (%s)" % ( node_title, payment.getTitle() )
+    node_title += " (%s)" % (
+      TRANSLATED_NONE if payment_uid is None else getObject(payment_uid).getTitle(),
+    )
 
   if not node_string_index:
     node_string_index = '%-10s' % node_id
@@ -652,29 +616,29 @@ for key, data in line_per_account.items():
 
   total_initial_debit_balance += round(initial_debit_balance, precision)
   total_initial_credit_balance += round(initial_credit_balance, precision)
-  final_debit_balance = round(initial_debit_balance + data['debit'],
-                              precision)
-  final_credit_balance = round(initial_credit_balance + data['credit'],
-                               precision)
+  final_debit_balance = round(initial_debit_balance + data['debit'], precision)
+  final_credit_balance = round(initial_credit_balance + data['credit'], precision)
   closing_balance = final_debit_balance - final_credit_balance
   total_final_balance_if_debit += round(max(closing_balance, 0), precision)
   total_final_balance_if_credit += round(max(-closing_balance, 0) or 0, precision)
 
-  line = Object(uid='new_',
-                node_id=node_id,
-                node_title=node_title,
-                mirror_section_title=mirror_section_title,
-                node_relative_url=node_relative_url,
-                initial_balance=initial_debit_balance - initial_credit_balance,
-                initial_debit_balance=initial_debit_balance,
-                initial_credit_balance=initial_credit_balance,
-                debit=data['debit'],
-                credit=data['credit'],
-                final_balance=final_debit_balance - final_credit_balance,
-                final_debit_balance=final_debit_balance,
-                final_credit_balance=final_credit_balance,
-                final_balance_if_debit=max(closing_balance, 0),
-                final_balance_if_credit=max(-closing_balance, 0) or 0,)
+  line = Object(
+    uid='new_',
+    node_id=node_id,
+    node_title=node_title,
+    mirror_section_title=mirror_section_title,
+    node_relative_url=node_relative_url,
+    initial_balance=initial_debit_balance - initial_credit_balance,
+    initial_debit_balance=initial_debit_balance,
+    initial_credit_balance=initial_credit_balance,
+    debit=data['debit'],
+    credit=data['credit'],
+    final_balance=final_debit_balance - final_credit_balance,
+    final_debit_balance=final_debit_balance,
+    final_credit_balance=final_credit_balance,
+    final_balance_if_debit=max(closing_balance, 0),
+    final_balance_if_credit=max(-closing_balance, 0) or 0,
+  )
 
   sort_key = (node_string_index, node_title, mirror_section_title)
   analytic_dict = {}
@@ -686,94 +650,89 @@ for key, data in line_per_account.items():
       # We sort on section title first
       sort_key = (title, ) + sort_key
     sort_key += (title, )
-    
+
   analytic_dict['sort_key'] = sort_key
   line.update(analytic_dict)
   line_list.append(line)
-  
 
 if not show_empty_accounts:
-  line_list = [ line for line in line_list
-                if line['debit'] or
-                   line['credit'] or
-                   line['initial_credit_balance'] or
-                   line['initial_debit_balance'] ]
-
-line_list.sort(key=lambda obj:obj['sort_key'])
+  line_list = [
+    line for line in line_list
+    if line['debit'] or
+       line['credit'] or
+       line['initial_credit_balance'] or
+       line['initial_debit_balance']
+  ]
+line_list.sort(key=lambda obj: obj['sort_key'])
 
 # cache values for stat
-request.set('TrialBalance.total_initial_debit_balance',
-            total_initial_debit_balance)
-request.set('TrialBalance.total_initial_credit_balance',
-            total_initial_credit_balance)
+request.set('TrialBalance.total_initial_debit_balance', total_initial_debit_balance)
+request.set('TrialBalance.total_initial_credit_balance', total_initial_credit_balance)
 request.set('TrialBalance.debit', total_debit)
 request.set('TrialBalance.credit', total_credit)
 request.set('TrialBalance.final_balance_if_debit', total_final_balance_if_debit)
 request.set('TrialBalance.final_balance_if_credit', total_final_balance_if_credit)
 
-if not per_account_class_summary:
-  return line_list
-
-current_gap = selected_gap or \
-                 portal.portal_preferences.getPreferredAccountingTransactionGap() or ''
-if current_gap.startswith('gap/'):
-  current_gap = current_gap[4:]
-def getAccountClass(account_relative_url):
-  account = traverse(account_relative_url)
-  for gap in account.getGapList():
-    if gap.startswith(current_gap):
-      gap_part_list = gap.split('/')
-      # country / accounting principle / ${class}
-      if len(gap_part_list) > 2:
-        return gap_part_list[2]
-  return None # this account has no class on the current GAP  
-
-new_line_list = []
-account_per_class = {}
-for brain in line_list:
-  account_per_class.setdefault(
-      getAccountClass(brain.node_relative_url), []).append(brain)
-
-account_class_list = account_per_class.keys()
-account_class_list.sort()
-
-add_line = new_line_list.append
-for account_class in account_class_list:
-  initial_debit_balance = 0
-  debit = 0
-  final_debit_balance = 0
-  initial_credit_balance = 0
-  credit = 0
-  final_credit_balance = 0
-  final_balance_if_debit = 0
-  final_balance_if_credit = 0
-
-  for account in account_per_class[account_class]:
-    initial_debit_balance += account.initial_debit_balance
-    debit += account.debit
-    final_debit_balance += account.final_debit_balance
-    initial_credit_balance += account.initial_credit_balance
-    credit += account.credit
-    final_credit_balance += account.final_credit_balance
-    final_balance_if_debit += account.final_balance_if_debit
-    final_balance_if_credit += account.final_balance_if_credit
-    add_line(account)
-
-  # summary
-  add_line(Object(node_title=Base_translateString('Total for class ${account_class}',
-                   mapping=dict(account_class=account_class or '???')),
-          initial_balance=round(initial_debit_balance - initial_credit_balance, precision),
-          initial_debit_balance=round(initial_debit_balance, precision),
-          debit=round(debit, precision),
-          final_debit_balance=round(final_debit_balance, precision),
-          initial_credit_balance=round(initial_credit_balance, precision),
-          credit=round(credit, precision),
-          final_credit_balance=round(final_credit_balance, precision),
-          final_balance_if_debit=round(final_balance_if_debit, precision),
-          final_balance_if_credit=round(final_balance_if_credit, precision),
-          final_balance=round(final_debit_balance - final_credit_balance, precision),))
-
-  add_line(Object(node_title=' '))
-
-return new_line_list
+if per_account_class_summary:
+  current_gap = selected_gap or portal.portal_preferences.getPreferredAccountingTransactionGap() or ''
+  if current_gap.startswith('gap/'):
+    current_gap = current_gap[4:]
+  def getAccountClass(account_relative_url):
+    account = traverse(account_relative_url)
+    for gap in account.getGapList():
+      if gap.startswith(current_gap):
+        gap_part_list = gap.split('/')
+        # country / accounting principle / ${class}
+        if len(gap_part_list) > 2:
+          return gap_part_list[2]
+    return None # this account has no class on the current GAP
+
+  account_per_class = {}
+  for brain in line_list:
+    account_per_class.setdefault(getAccountClass(brain.node_relative_url), []).append(brain)
+
+  line_list = []
+  add_line = line_list.append
+  for account_class in sorted(account_per_class):
+    initial_debit_balance = 0
+    debit = 0
+    final_debit_balance = 0
+    initial_credit_balance = 0
+    credit = 0
+    final_credit_balance = 0
+    final_balance_if_debit = 0
+    final_balance_if_credit = 0
+
+    for account in account_per_class[account_class]:
+      initial_debit_balance += account.initial_debit_balance
+      debit += account.debit
+      final_debit_balance += account.final_debit_balance
+      initial_credit_balance += account.initial_credit_balance
+      credit += account.credit
+      final_credit_balance += account.final_credit_balance
+      final_balance_if_debit += account.final_balance_if_debit
+      final_balance_if_credit += account.final_balance_if_credit
+      add_line(account)
+
+    # summary
+    add_line(Object(
+      node_title=Base_translateString(
+        'Total for class ${account_class}',
+        mapping={'account_class': account_class or '???'},
+      ),
+      initial_balance=round(initial_debit_balance - initial_credit_balance, precision),
+      initial_debit_balance=round(initial_debit_balance, precision),
+      debit=round(debit, precision),
+      final_debit_balance=round(final_debit_balance, precision),
+      initial_credit_balance=round(initial_credit_balance, precision),
+      credit=round(credit, precision),
+      final_credit_balance=round(final_credit_balance, precision),
+      final_balance_if_debit=round(final_balance_if_debit, precision),
+      final_balance_if_credit=round(final_balance_if_credit, precision),
+      final_balance=round(final_debit_balance - final_credit_balance, precision),
+    ))
+
+    add_line(Object(node_title=' '))
+
+return line_list
 # vim: foldmethod=marker
diff --git a/bt5/erp5_accounting/SkinTemplateItem/portal_skins/erp5_accounting/AccountModule_getAccountListForTrialBalance.xml b/bt5/erp5_accounting/SkinTemplateItem/portal_skins/erp5_accounting/AccountModule_getAccountListForTrialBalance.xml
index e2288f5d56..d905bedf3f 100644
--- a/bt5/erp5_accounting/SkinTemplateItem/portal_skins/erp5_accounting/AccountModule_getAccountListForTrialBalance.xml
+++ b/bt5/erp5_accounting/SkinTemplateItem/portal_skins/erp5_accounting/AccountModule_getAccountListForTrialBalance.xml
@@ -50,7 +50,7 @@
         </item>
         <item>
             <key> <string>_params</string> </key>
-            <value> <string>show_empty_accounts, expand_accounts, at_date, from_date, period_start_date, section_uid, simulation_state, precision, node_uid, gap_root=None, per_account_class_summary=0, portal_type=None, function=None, funding=None, project=None, group_analytic=[], mirror_section_category=None, show_detailed_balance_columns=False, **kw</string> </value>
+            <value> <string>show_empty_accounts, expand_accounts, at_date, from_date, period_start_date, section_uid, simulation_state, precision, node_uid, gap_root=None, per_account_class_summary=0, portal_type=None, function=None, funding=None, project=None, group_analytic=[], mirror_section_category=None, show_detailed_balance_columns=False, src__=False, **kw</string> </value>
         </item>
         <item>
             <key> <string>id</string> </key>
-- 
2.30.9