From 1d208b8a4dc4d2036d08f027c5007a39fdece98c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=A9rome=20Perrin?= <jerome@nexedi.com>
Date: Tue, 23 Sep 2008 10:51:07 +0000
Subject: [PATCH] AccountingTransactionBalance was not handling correctly
 transactions where parties are defined at line level

git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@23755 20353a03-c40f-0410-a6d1-a30d3c3de9de
---
 .../AccountingTransactionBalance.py           | 57 +++++++++-------
 product/ERP5/tests/testAccounting.py          | 68 +++++++++++++++++++
 2 files changed, 99 insertions(+), 26 deletions(-)

diff --git a/product/ERP5/Constraint/AccountingTransactionBalance.py b/product/ERP5/Constraint/AccountingTransactionBalance.py
index 1dba7639ba..b268c38fa5 100644
--- a/product/ERP5/Constraint/AccountingTransactionBalance.py
+++ b/product/ERP5/Constraint/AccountingTransactionBalance.py
@@ -46,36 +46,41 @@ class AccountingTransactionBalance(Constraint):
     """Implement here the consistency checker
     """
     error_list = []
-    source_sum = 0
-    destination_sum = 0
+    source_sum = dict()
+    destination_sum = dict()
     for line in obj.getMovementList(
           portal_type=obj.getPortalAccountingMovementTypeList()):
       if line.getSourceValue() is not None:
-        source_sum += line.getSourceInventoriatedTotalAssetPrice() or 0
+        section = line.getSourceSectionValue()
+        source_sum[section] = source_sum.get(section, 0) + \
+            (line.getSourceInventoriatedTotalAssetPrice() or 0)
       if line.getDestinationValue() is not None:
-        destination_sum += \
-          line.getDestinationInventoriatedTotalAssetPrice() or 0
+        section = line.getDestinationSectionValue()
+        destination_sum[section] = destination_sum.get(section, 0) + \
+          (line.getDestinationInventoriatedTotalAssetPrice() or 0)
     
-    source_section = obj.getSourceSectionValue()
-    destination_section = obj.getDestinationSectionValue()
-    source_precision = destination_precision = 2
-
-    if source_section is not None and\
-                 source_section.getPortalType() == 'Organisation':
-      source_currency = source_section.getPriceCurrencyValue()
-      if source_currency is not None:
-        source_precision = source_currency.getQuantityPrecision()
-    if round(source_sum, source_precision) != 0:
-      error_list.append(self._generateError(obj, self._getMessage(
-            'message_transaction_not_balanced_for_source')))
-
-    if destination_section is not None and\
-                 destination_section.getPortalType() == 'Organisation':
-      destination_currency = destination_section.getPriceCurrencyValue()
-      if destination_currency is not None:
-        destination_precision = destination_currency.getQuantityPrecision()
-    if round(destination_sum, destination_precision) != 0:
-      error_list.append(self._generateError(obj, self._getMessage(
-              'message_transaction_not_balanced_for_destination')))
+    for section, total in source_sum.items():
+      precision = 2
+      if section is not None and\
+          section.getPortalType() == 'Organisation':
+        section_currency = section.getPriceCurrencyValue()
+        if section_currency is not None:
+          precision = section_currency.getQuantityPrecision()
+        if round(total, precision) != 0:
+          error_list.append(self._generateError(obj, self._getMessage(
+                'message_transaction_not_balanced_for_source')))
+          break
     
+    for section, total in destination_sum.items():
+      precision = 2
+      if section is not None and\
+          section.getPortalType() == 'Organisation':
+        section_currency = section.getPriceCurrencyValue()
+        if section_currency is not None:
+          precision = section_currency.getQuantityPrecision()
+        if round(total, precision) != 0:
+          error_list.append(self._generateError(obj, self._getMessage(
+                'message_transaction_not_balanced_for_source')))
+          break
+
     return error_list
diff --git a/product/ERP5/tests/testAccounting.py b/product/ERP5/tests/testAccounting.py
index 6f17682cc6..78f89e229d 100644
--- a/product/ERP5/tests/testAccounting.py
+++ b/product/ERP5/tests/testAccounting.py
@@ -514,6 +514,74 @@ class TestTransactionValidation(AccountingTestCase):
     # but if there are no accounts defined it's not a problem
     self.portal.portal_workflow.doActionFor(transaction, 'stop_action')
 
+  def test_NonBalancedAccountingTransactionSectionOnLines(self):
+    transaction = self._makeOne(
+               portal_type='Accounting Transaction',
+               start_date=DateTime('2007/01/02'),
+               resource='currency_module/yen',
+               lines=(dict(source_value=self.account_module.goods_sales,
+                           destination_value=self.account_module.goods_purchase,
+                           destination_section_value=self.organisation_module.client_1,
+                           source_debit=500),
+                      dict(source_value=self.account_module.goods_purchase,
+                           source_credit=500)))
+
+    # This is not balanced for client 1
+    self.assertRaises(ValidationFailed,
+        self.portal.portal_workflow.doActionFor,
+        transaction, 'stop_action')
+
+    for line in transaction.getMovementList():
+      line.setDestinationSection(None)
+    self.assertEquals([], transaction.checkConsistency())
+    self.portal.portal_workflow.doActionFor(transaction, 'stop_action')
+
+  def test_NonBalancedAccountingTransactionDifferentSectionOnLines(self):
+    transaction = self._makeOne(
+               portal_type='Accounting Transaction',
+               start_date=DateTime('2007/01/02'),
+               resource='currency_module/yen',
+               lines=(dict(source_value=self.account_module.goods_sales,
+                           destination_value=self.account_module.goods_purchase,
+                           destination_section_value=self.organisation_module.client_1,
+                           source_debit=500),
+                      dict(source_value=self.account_module.goods_purchase,
+                           destination_value=self.account_module.goods_sales,
+                           destination_section_value=self.organisation_module.client_2,
+                           source_credit=500)))
+
+    # This is not balanced for client 1 and client 2, but if you look globally,
+    # it looks balanced.
+    self.assertRaises(ValidationFailed,
+        self.portal.portal_workflow.doActionFor,
+        transaction, 'stop_action')
+    self.assertEquals(1, len(transaction.checkConsistency()),
+                         transaction.checkConsistency())
+
+    for line in transaction.getMovementList():
+      line.setDestinationSectionValue(
+          self.organisation_module.client_2)
+
+    self.assertEquals([], transaction.checkConsistency())
+    self.portal.portal_workflow.doActionFor(transaction, 'stop_action')
+
+  def test_NonBalancedAccountingTransactionSectionPersonOnLines(self):
+    transaction = self._makeOne(
+               portal_type='Accounting Transaction',
+               start_date=DateTime('2007/01/02'),
+               resource='currency_module/yen',
+               lines=(dict(source_value=self.account_module.goods_purchase,
+                           destination_value=self.account_module.goods_purchase,
+                           destination_section_value=self.person_module.john_smith,
+                           source_debit=500),
+                      dict(source_value=self.account_module.goods_purchase,
+                           source_credit=500)))
+
+    # This is not balanced for john smith, but as he is a person, it's not a
+    # problem
+    self.assertEquals([], transaction.checkConsistency())
+    self.portal.portal_workflow.doActionFor(transaction, 'stop_action')
+
   def test_AccountingTransactionValidationRefusedWithCategoriesAsSections(self):
     # Validating a transaction with categories as sections is refused.
     # See http://wiki.erp5.org/Discussion/AccountingProblems
-- 
2.30.9