From 68c3752313c52c9796d8006b165c4d217d7c2e1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9rome=20Perrin?= <jerome@nexedi.com> Date: Thu, 9 Jul 2015 16:23:37 +0200 Subject: [PATCH] accounting: prevent user from creating balance transaction while previous one is still beeing reindexed --- ...ountingPeriod_createBalanceTransaction.xml | 386 +++++++++--------- .../scripts/checkTransactionsState.xml | 4 + product/ERP5/tests/testAccounting.py | 34 ++ 3 files changed, 227 insertions(+), 197 deletions(-) diff --git a/bt5/erp5_accounting/SkinTemplateItem/portal_skins/erp5_accounting/AccountingPeriod_createBalanceTransaction.xml b/bt5/erp5_accounting/SkinTemplateItem/portal_skins/erp5_accounting/AccountingPeriod_createBalanceTransaction.xml index 20b166eac5..ac6ced327c 100644 --- a/bt5/erp5_accounting/SkinTemplateItem/portal_skins/erp5_accounting/AccountingPeriod_createBalanceTransaction.xml +++ b/bt5/erp5_accounting/SkinTemplateItem/portal_skins/erp5_accounting/AccountingPeriod_createBalanceTransaction.xml @@ -67,8 +67,8 @@ def roundCurrency(value, resource_relative_url):\n qty_precision = precision_cache[resource_relative_url]\n return round(value, qty_precision)\n \n +# This tag is checked in accounting period workflow\n activity_tag = \'BalanceTransactionCreation\'\n -activate_kw=dict(tag=activity_tag)\n \n at_date = context.getStopDate()\n assert at_date\n @@ -109,7 +109,7 @@ def getDependantSectionList(group, main_section):\n section_list.extend(getDependantSectionList(subgroup, main_section))\n \n return section_list\n - \n +\n group_value = section.getGroupValue()\n section_list = [section]\n if group_value is not None:\n @@ -117,7 +117,6 @@ if group_value is not None:\n \n def createBalanceTransaction(section):\n return portal.accounting_module.newContent(\n - activate_kw=activate_kw,\n portal_type=\'Balance Transaction\',\n start_date=(at_date + 1).earliestTime(),\n title=context.getTitle() or Base_translateString(\'Balance Transaction\'),\n @@ -125,221 +124,214 @@ def createBalanceTransaction(section):\n resource=section_currency,\n causality_value=context)\n \n -for section in section_list:\n - section_uid = section.getUid()\n - balance_transaction = None\n -\n - group_by_node_node_category_list = []\n - group_by_mirror_section_node_category_list = []\n - group_by_payment_node_category_list = []\n - profit_and_loss_node_category_list = []\n -\n - node_category_list = portal.portal_categories\\\n - .account_type.getCategoryChildValueList()\n - for node_category in node_category_list:\n - node_category_url = node_category.getRelativeUrl()\n - if node_category_url in (\n - \'account_type/asset/cash/bank\',):\n - group_by_payment_node_category_list.append(node_category_url)\n - elif node_category_url in (\n - \'account_type/asset/receivable\',\n - \'account_type/liability/payable\'):\n - group_by_mirror_section_node_category_list.append(node_category_url)\n - elif node_category.isMemberOf(\'account_type/income\') or \\\n - node_category.isMemberOf(\'account_type/expense\'):\n - profit_and_loss_node_category_list.append(node_category_url)\n - else:\n - group_by_node_node_category_list.append(node_category_url)\n -\n - getInventoryList = portal.portal_simulation.getInventoryList\n -\n - inventory_param_dict = dict(section_uid=section_uid,\n - simulation_state=(\'delivered\',),\n - precision=section_currency_precision,\n - portal_type=portal.getPortalAccountingMovementTypeList(),\n - at_date=at_date.latestTime(),)\n - \n - # Calculate the sum of profit and loss accounts balances for that period.\n - # This must match the difference between assets, liability and equity accounts.\n - profit_and_loss_accounts_balance = portal.portal_simulation.getInventoryAssetPrice(\n - from_date=context.getStartDate(),\n - node_category_strict_membership=profit_and_loss_node_category_list,\n - **inventory_param_dict)\n - selected_profit_and_loss_account_balance = portal.portal_simulation.getInventoryAssetPrice(\n - node=profit_and_loss_account,\n - resource=section_currency,\n - **inventory_param_dict)\n +with context.defaultActivateParameterDict({\'tag\': activity_tag}, placeless=True):\n + for section in section_list:\n + section_uid = section.getUid()\n + balance_transaction = None\n +\n + group_by_node_node_category_list = []\n + group_by_mirror_section_node_category_list = []\n + group_by_payment_node_category_list = []\n + profit_and_loss_node_category_list = []\n +\n + node_category_list = portal.portal_categories\\\n + .account_type.getCategoryChildValueList()\n + for node_category in node_category_list:\n + node_category_url = node_category.getRelativeUrl()\n + if node_category_url in (\n + \'account_type/asset/cash/bank\',):\n + group_by_payment_node_category_list.append(node_category_url)\n + elif node_category_url in (\n + \'account_type/asset/receivable\',\n + \'account_type/liability/payable\'):\n + group_by_mirror_section_node_category_list.append(node_category_url)\n + elif node_category.isMemberOf(\'account_type/income\') or \\\n + node_category.isMemberOf(\'account_type/expense\'):\n + profit_and_loss_node_category_list.append(node_category_url)\n + else:\n + group_by_node_node_category_list.append(node_category_url)\n +\n + getInventoryList = portal.portal_simulation.getInventoryList\n +\n + inventory_param_dict = dict(section_uid=section_uid,\n + simulation_state=(\'delivered\',),\n + precision=section_currency_precision,\n + portal_type=portal.getPortalAccountingMovementTypeList(),\n + at_date=at_date.latestTime(),)\n \n - section_currency_uid = context.getParentValue().getPriceCurrencyUid()\n -\n - profit_and_loss_quantity = 0\n - line_count = 0\n -\n - for inventory in getInventoryList(\n - node_category_strict_membership=group_by_node_node_category_list,\n - group_by_node=1,\n - group_by_resource=1,\n - **inventory_param_dict):\n - \n - total_price = roundCurrency(inventory.total_price or 0, section_currency)\n - quantity = roundCurrency(inventory.total_quantity or 0,\n - inventory.resource_relative_url)\n - \n - if not total_price and not quantity:\n - continue\n - \n - line_count += 1\n - if inventory.resource_uid != section_currency_uid:\n + # Calculate the sum of profit and loss accounts balances for that period.\n + # This must match the difference between assets, liability and equity accounts.\n + profit_and_loss_accounts_balance = portal.portal_simulation.getInventoryAssetPrice(\n + from_date=context.getStartDate(),\n + node_category_strict_membership=profit_and_loss_node_category_list,\n + **inventory_param_dict)\n + selected_profit_and_loss_account_balance = portal.portal_simulation.getInventoryAssetPrice(\n + node=profit_and_loss_account,\n + resource=section_currency,\n + **inventory_param_dict)\n +\n + section_currency_uid = context.getParentValue().getPriceCurrencyUid()\n +\n + profit_and_loss_quantity = 0\n + line_count = 0\n +\n + for inventory in getInventoryList(\n + node_category_strict_membership=group_by_node_node_category_list,\n + group_by_node=1,\n + group_by_resource=1,\n + **inventory_param_dict):\n +\n + total_price = roundCurrency(inventory.total_price or 0, section_currency)\n + quantity = roundCurrency(inventory.total_quantity or 0,\n + inventory.resource_relative_url)\n +\n + if not total_price and not quantity:\n + continue\n +\n + line_count += 1\n + if inventory.resource_uid != section_currency_uid:\n + profit_and_loss_quantity += total_price\n +\n + if balance_transaction is None:\n + balance_transaction = createBalanceTransaction(section)\n + balance_transaction.newContent(\n + id=\'%03d\' % line_count,\n + portal_type=\'Balance Transaction Line\',\n + destination=inventory.node_relative_url,\n + resource=inventory.resource_relative_url,\n + quantity=quantity,\n + destination_total_asset_price=total_price)\n + else:\n + if total_price != quantity:\n + # If this fail for you, your accounting doesn\'t use currencies with\n + # consistency\n + raise ValueError(\'Different price: %s != %s \' % (\n + total_price, quantity))\n +\n + if inventory.node_relative_url != profit_and_loss_account:\n + profit_and_loss_quantity += total_price\n + if balance_transaction is None:\n + balance_transaction = createBalanceTransaction(section)\n + balance_transaction.newContent(\n + id=\'%03d\' % line_count,\n + portal_type=\'Balance Transaction Line\',\n + destination=inventory.node_relative_url,\n + quantity=total_price)\n +\n +\n + for inventory in getInventoryList(\n + node_category_strict_membership=group_by_mirror_section_node_category_list,\n + group_by_node=1,\n + group_by_mirror_section=1,\n + group_by_resource=1,\n + **inventory_param_dict):\n +\n + total_price = roundCurrency(inventory.total_price or 0, section_currency)\n + quantity = roundCurrency(inventory.total_quantity or 0,\n + inventory.resource_relative_url)\n +\n + if not total_price and not quantity:\n + continue\n profit_and_loss_quantity += total_price\n - \n - if balance_transaction is None:\n - balance_transaction = createBalanceTransaction(section)\n - balance_transaction.newContent(\n + line_count += 1\n +\n + if inventory.resource_uid != section_currency_uid:\n + if balance_transaction is None:\n + balance_transaction = createBalanceTransaction(section)\n + balance_transaction.newContent(\n id=\'%03d\' % line_count,\n portal_type=\'Balance Transaction Line\',\n - activate_kw=activate_kw,\n destination=inventory.node_relative_url,\n + source_section_uid=inventory.mirror_section_uid,\n resource=inventory.resource_relative_url,\n quantity=quantity,\n destination_total_asset_price=total_price)\n - else:\n - if total_price != quantity:\n - # If this fail for you, your accounting doesn\'t use currencies with\n - # consistency\n - raise ValueError(\'Different price: %s != %s \' % (\n - total_price, quantity))\n - \n - if inventory.node_relative_url != profit_and_loss_account:\n - profit_and_loss_quantity += total_price\n + else:\n + if total_price != quantity:\n + raise ValueError(\'Different price: %s != %s \' % (\n + total_price, quantity))\n if balance_transaction is None:\n balance_transaction = createBalanceTransaction(section)\n balance_transaction.newContent(\n id=\'%03d\' % line_count,\n portal_type=\'Balance Transaction Line\',\n - activate_kw=activate_kw,\n destination=inventory.node_relative_url,\n + source_section_uid=inventory.mirror_section_uid,\n quantity=total_price)\n \n - \n - for inventory in getInventoryList(\n - node_category_strict_membership=group_by_mirror_section_node_category_list,\n - group_by_node=1,\n - group_by_mirror_section=1,\n - group_by_resource=1,\n - **inventory_param_dict):\n -\n - total_price = roundCurrency(inventory.total_price or 0, section_currency)\n - quantity = roundCurrency(inventory.total_quantity or 0,\n - inventory.resource_relative_url)\n - \n - if not total_price and not quantity:\n - continue\n - profit_and_loss_quantity += total_price\n - line_count += 1\n \n - if inventory.resource_uid != section_currency_uid:\n - if balance_transaction is None:\n - balance_transaction = createBalanceTransaction(section)\n - balance_transaction.newContent(\n - id=\'%03d\' % line_count,\n - portal_type=\'Balance Transaction Line\',\n - activate_kw=activate_kw,\n - destination=inventory.node_relative_url,\n - source_section_uid=inventory.mirror_section_uid,\n - resource=inventory.resource_relative_url,\n - quantity=quantity,\n - destination_total_asset_price=total_price)\n - else:\n - if total_price != quantity:\n - raise ValueError(\'Different price: %s != %s \' % (\n - total_price, quantity))\n - if balance_transaction is None:\n + for inventory in getInventoryList(\n + node_category_strict_membership=group_by_payment_node_category_list,\n + group_by_node=1,\n + group_by_payment=1,\n + group_by_resource=1,\n + **inventory_param_dict):\n +\n + total_price = roundCurrency(inventory.total_price or 0, section_currency)\n + quantity = roundCurrency(inventory.total_quantity or 0,\n + inventory.resource_relative_url)\n +\n + if not total_price and not quantity:\n + continue\n + profit_and_loss_quantity += total_price\n +\n + line_count += 1\n +\n + if inventory.resource_uid != section_currency_uid:\n + if balance_transaction is None:\n + balance_transaction = createBalanceTransaction(section)\n + balance_transaction.newContent(\n + id=\'%03d\' % line_count,\n + portal_type=\'Balance Transaction Line\',\n + destination=inventory.node_relative_url,\n + resource=inventory.resource_relative_url,\n + quantity=quantity,\n + destination_payment_uid=inventory.payment_uid,\n + destination_total_asset_price=total_price)\n + else:\n + if total_price != quantity:\n + raise ValueError(\'Different price: %s != %s \' % (\n + total_price, quantity))\n + if balance_transaction is None:\n + balance_transaction = createBalanceTransaction(section)\n + balance_transaction.newContent(\n + id=\'%03d\' % line_count,\n + portal_type=\'Balance Transaction Line\',\n + destination=inventory.node_relative_url,\n + destination_payment_uid=inventory.payment_uid,\n + quantity=total_price)\n +\n + if balance_transaction is None:\n + # we did not have any transaction for this section\n +\n + # One possible corner case is that we have only transactions that brings\n + # the balance of all balance sheets accounts to 0. In this case we want to\n + # create a balance transaction that notes that the current balance of profit\n + # and loss account is 0, so that the delta gets indexed. \n + if profit_and_loss_accounts_balance:\n balance_transaction = createBalanceTransaction(section)\n - balance_transaction.newContent(\n - id=\'%03d\' % line_count,\n - portal_type=\'Balance Transaction Line\',\n - activate_kw=activate_kw,\n - destination=inventory.node_relative_url,\n - source_section_uid=inventory.mirror_section_uid,\n - quantity=total_price)\n -\n -\n - for inventory in getInventoryList(\n - node_category_strict_membership=group_by_payment_node_category_list,\n - group_by_node=1,\n - group_by_payment=1,\n - group_by_resource=1,\n - **inventory_param_dict):\n -\n - total_price = roundCurrency(inventory.total_price or 0, section_currency)\n - quantity = roundCurrency(inventory.total_quantity or 0,\n - inventory.resource_relative_url)\n - \n - if not total_price and not quantity:\n + balance_transaction.newContent(\n + portal_type=\'Balance Transaction Line\',\n + destination=profit_and_loss_account,\n + quantity=0)\n + balance_transaction.stop()\n + balance_transaction.deliver()\n continue\n - profit_and_loss_quantity += total_price\n - \n - line_count += 1\n \n - if inventory.resource_uid != section_currency_uid:\n - if balance_transaction is None:\n - balance_transaction = createBalanceTransaction(section)\n - balance_transaction.newContent(\n - id=\'%03d\' % line_count,\n - portal_type=\'Balance Transaction Line\',\n - activate_kw=activate_kw,\n - destination=inventory.node_relative_url,\n - resource=inventory.resource_relative_url,\n - quantity=quantity,\n - destination_payment_uid=inventory.payment_uid,\n - destination_total_asset_price=total_price)\n - else:\n - if total_price != quantity:\n - raise ValueError(\'Different price: %s != %s \' % (\n - total_price, quantity))\n - if balance_transaction is None:\n - balance_transaction = createBalanceTransaction(section)\n - balance_transaction.newContent(\n - id=\'%03d\' % line_count,\n - portal_type=\'Balance Transaction Line\',\n - activate_kw=activate_kw,\n - destination=inventory.node_relative_url,\n - destination_payment_uid=inventory.payment_uid,\n - quantity=total_price)\n -\n - if balance_transaction is None:\n - # we did not have any transaction for this section\n - \n - # One possible corner case is that we have only transactions that brings\n - # the balance of all balance sheets accounts to 0. In this case we want to\n - # create a balance transaction that notes that the current balance of profit\n - # and loss account is 0, so that the delta gets indexed. \n - if profit_and_loss_accounts_balance:\n - balance_transaction = createBalanceTransaction(section)\n - balance_transaction.newContent(\n - activate_kw=activate_kw,\n - portal_type=\'Balance Transaction Line\',\n - destination=profit_and_loss_account,\n - quantity=0)\n - balance_transaction.stop()\n - balance_transaction.deliver()\n - continue\n -\n - assert roundCurrency(profit_and_loss_accounts_balance, section_currency) == roundCurrency(\n - - roundCurrency(selected_profit_and_loss_account_balance, section_currency)\n - - roundCurrency(profit_and_loss_quantity, section_currency), section_currency)\n - \n - # add a final line for p&l\n - balance_transaction.newContent(\n - id=\'%03d\' % (line_count + 1),\n - activate_kw=activate_kw,\n - portal_type=\'Balance Transaction Line\',\n - destination=profit_and_loss_account,\n - quantity=-profit_and_loss_quantity)\n + assert roundCurrency(profit_and_loss_accounts_balance, section_currency) == roundCurrency(\n + - roundCurrency(selected_profit_and_loss_account_balance, section_currency)\n + - roundCurrency(profit_and_loss_quantity, section_currency), section_currency)\n +\n + # add a final line for p&l\n + balance_transaction.newContent(\n + id=\'%03d\' % (line_count + 1),\n + portal_type=\'Balance Transaction Line\',\n + destination=profit_and_loss_account,\n + quantity=-profit_and_loss_quantity)\n \n - # and go to delivered state directly (the user is not supposed to edit this document)\n - balance_transaction.stop()\n - balance_transaction.deliver()\n + # and go to delivered state directly (the user is not supposed to edit this document)\n + balance_transaction.stop()\n + balance_transaction.deliver()\n \n # make sure this Accounting Period has an activity pending during the indexing\n # of the balance transaction.\n diff --git a/bt5/erp5_accounting/WorkflowTemplateItem/portal_workflow/accounting_period_workflow/scripts/checkTransactionsState.xml b/bt5/erp5_accounting/WorkflowTemplateItem/portal_workflow/accounting_period_workflow/scripts/checkTransactionsState.xml index 70a26de875..ff3a3a8f5f 100644 --- a/bt5/erp5_accounting/WorkflowTemplateItem/portal_workflow/accounting_period_workflow/scripts/checkTransactionsState.xml +++ b/bt5/erp5_accounting/WorkflowTemplateItem/portal_workflow/accounting_period_workflow/scripts/checkTransactionsState.xml @@ -60,6 +60,10 @@ portal = period.getPortalObject()\n \n period.Base_checkConsistency()\n \n +# This tag is used in AccountingPeriod_createBalanceTransaction\n +if portal.portal_activities.countMessageWithTag(\'BalanceTransactionCreation\'):\n + raise ValidationFailed(translateString("Balance transaction creation already in progress. Please try again later."))\n +\n valid_simulation_state_list = [\'cancelled\', \'delivered\', \'deleted\', \'rejected\']\n all_state_list = [x[1] for x in\n portal.Base_getTranslatedWorkflowStateItemList(wf_id=\'accounting_workflow\')]\n diff --git a/product/ERP5/tests/testAccounting.py b/product/ERP5/tests/testAccounting.py index 19122c4a67..f94fb3c3f0 100644 --- a/product/ERP5/tests/testAccounting.py +++ b/product/ERP5/tests/testAccounting.py @@ -2499,6 +2499,40 @@ class TestClosingPeriod(AccountingTestCase): section_uid=self.section.getUid(), node_uid=self.account_module.receivable.getUid())) + def test_ParrallelClosingRefused(self): + organisation_module = self.organisation_module + stool = self.portal.portal_simulation + period = self.section.newContent(portal_type='Accounting Period') + period.setStartDate(DateTime(2006, 1, 1)) + period.setStopDate(DateTime(2006, 12, 31)) + period.start() + period2 = self.section.newContent(portal_type='Accounting Period') + period2.setStartDate(DateTime(2007, 1, 1)) + period2.setStopDate(DateTime(2007, 12, 31)) + period2.start() + + pl = self.portal.account_module.newContent( + portal_type='Account', + account_type='equity') + + transaction1 = self._makeOne( + start_date=DateTime(2006, 1, 1), + destination_section_value=organisation_module.client_1, + portal_type='Sale Invoice Transaction', + simulation_state='delivered', + lines=(dict(source_value=self.account_module.goods_sales, + source_credit=100), + dict(source_value=self.account_module.receivable, + source_debit=100))) + + self.portal.portal_workflow.doActionFor( + period, 'stop_action', + profit_and_loss_account=pl.getRelativeUrl()) + + self.assertRaises(ValidationFailed, + self.getPortal().portal_workflow.doActionFor, + period2, 'stop_action' ) + class TestAccountingExport(AccountingTestCase): -- 2.30.9