Commit 5e85b655 authored by Jérome Perrin's avatar Jérome Perrin

accounting: only allow validated bank accounts belonging to sections

The only check with accounting transactions regarding bank account
was that the bank account is not invalidated. This makes the
constraint more strict by checking that the bank account belongs to
the corresponding entity and also that the bank account is validated.

A few tests needed to be updated to validate the bank accounts. Also
a legacy sequence test has been removed because it is now covered by
normal tests.
parent ae15e7e1
Pipeline #25868 failed with stage
in 0 seconds
...@@ -694,8 +694,10 @@ class TestTransactionValidation(AccountingTestCase): ...@@ -694,8 +694,10 @@ class TestTransactionValidation(AccountingTestCase):
self.assertRaises(ValidationFailed, self.assertRaises(ValidationFailed,
self.portal.portal_workflow.doActionFor, self.portal.portal_workflow.doActionFor,
accounting_transaction, 'stop_action') accounting_transaction, 'stop_action')
# with bank account, it's OK # with validated bank account, it's OK
bank_account = self.section.newContent(portal_type='Bank Account') bank_account = self.section.newContent(portal_type='Bank Account')
bank_account.validate()
self.tic()
accounting_transaction.setSourcePaymentValue(bank_account) accounting_transaction.setSourcePaymentValue(bank_account)
self.portal.portal_workflow.doActionFor(accounting_transaction, 'stop_action') self.portal.portal_workflow.doActionFor(accounting_transaction, 'stop_action')
...@@ -704,6 +706,7 @@ class TestTransactionValidation(AccountingTestCase): ...@@ -704,6 +706,7 @@ class TestTransactionValidation(AccountingTestCase):
# bank account # bank account
bank_account = self.section.newContent(portal_type='Bank Account', bank_account = self.section.newContent(portal_type='Bank Account',
price_currency_value=self.currency_module.euro) price_currency_value=self.currency_module.euro)
bank_account.validate()
accounting_transaction = self._makeOne( accounting_transaction = self._makeOne(
portal_type='Payment Transaction', portal_type='Payment Transaction',
start_date=DateTime('2007/01/02'), start_date=DateTime('2007/01/02'),
...@@ -725,6 +728,112 @@ class TestTransactionValidation(AccountingTestCase): ...@@ -725,6 +728,112 @@ class TestTransactionValidation(AccountingTestCase):
accounting_transaction.setResourceValue(self.currency_module.euro) accounting_transaction.setResourceValue(self.currency_module.euro)
self.portal.portal_workflow.doActionFor(accounting_transaction, 'stop_action') self.portal.portal_workflow.doActionFor(accounting_transaction, 'stop_action')
def test_PaymentTransactionValidationCheckBankAccountValidationState(self):
for section, mirror_section in (
(self.main_section, self.portal.organisation_module.client_1),
(self.main_section, self.portal.person_module.newContent()),
# with person member of section's group
(self.main_section, self.portal.person_module.john_smith),
(self.section, self.portal.organisation_module.client_1),
(self.main_section, self.portal.person_module.newContent()),
(self.main_section, self.section),
):
section_bank_account = section.newContent(
title='section bank account',
portal_type='Bank Account',
price_currency_value=self.currency_module.euro)
mirror_section_bank_account = mirror_section.newContent(
title='mirror section bank account',
portal_type='Bank Account',
price_currency_value=self.currency_module.euro)
accounting_transaction = self._makeOne(
portal_type='Payment Transaction',
start_date=DateTime('2007/01/02'),
source_section_value=section,
source_payment_value=section_bank_account,
destination_section_value=mirror_section,
destination_payment_value=mirror_section_bank_account,
payment_mode='default',
resource_value=self.currency_module.euro,
lines=(
dict(source_value=self.account_module.bank, source_debit=500),
dict(source_value=self.account_module.receivable, source_credit=500)))
self.assertRaisesRegex(
ValidationFailed,
"Bank Account section bank account is invalid.",
self.portal.portal_workflow.doActionFor,
accounting_transaction,
'stop_action',
)
section_bank_account.validate()
self.tic()
self.assertRaisesRegex(
ValidationFailed,
"Bank Account mirror section bank account is invalid.",
self.portal.portal_workflow.doActionFor,
accounting_transaction,
'stop_action',
)
mirror_section_bank_account.validate()
self.tic()
self.portal.portal_workflow.doActionFor(
accounting_transaction, 'stop_action')
def test_PaymentTransactionValidationCheckBankAccountOwner(self):
main_section_bank_account = self.main_section.newContent(
title='main section bank account',
portal_type='Bank Account',
price_currency_value=self.currency_module.euro)
main_section_bank_account.validate()
client_1_bank_account = self.portal.organisation_module.client_1.newContent(
title='client_1 bank account',
portal_type='Bank Account',
price_currency_value=self.currency_module.euro)
client_1_bank_account.validate()
# main_section_bank_account can be used by both main_section and section.
for section in self.main_section, self.section:
accounting_transaction = self._makeOne(
portal_type='Payment Transaction',
start_date=DateTime('2007/01/02'),
source_section_value=section,
source_payment_value=main_section_bank_account,
destination_section_value=self.portal.organisation_module.client_1,
destination_payment_value=client_1_bank_account,
payment_mode='default',
resource_value=self.currency_module.euro,
lines=(
dict(source_value=self.account_module.bank, source_debit=500),
dict(source_value=self.account_module.receivable,
source_credit=500)))
self.portal.portal_workflow.doActionFor(
accounting_transaction, 'stop_action')
# client_1's bank account can only be used with client 1, not with client 2
accounting_transaction = self._makeOne(
portal_type='Payment Transaction',
start_date=DateTime('2007/01/02'),
source_section_value=self.main_section,
source_payment_value=main_section_bank_account,
destination_section_value=self.portal.organisation_module.client_2,
destination_payment_value=client_1_bank_account,
payment_mode='default',
resource_value=self.currency_module.euro,
lines=(
dict(source_value=self.account_module.bank, source_debit=500),
dict(source_value=self.account_module.receivable, source_credit=500)))
self.assertRaisesRegex(
ValidationFailed,
"Bank Account client_1 bank account is invalid.",
self.portal.portal_workflow.doActionFor,
accounting_transaction,
'stop_action',
)
def test_NonBalancedAccountingTransaction(self): def test_NonBalancedAccountingTransaction(self):
# Accounting Transactions have to be balanced to be validated # Accounting Transactions have to be balanced to be validated
accounting_transaction = self._makeOne( accounting_transaction = self._makeOne(
...@@ -3490,6 +3599,8 @@ class TestTransactions(AccountingTestCase): ...@@ -3490,6 +3599,8 @@ class TestTransactions(AccountingTestCase):
def test_Invoice_createRelatedPaymentTransactionSimple(self): def test_Invoice_createRelatedPaymentTransactionSimple(self):
# Simple case of creating a related payment transaction. # Simple case of creating a related payment transaction.
payment_node = self.section.newContent(portal_type='Bank Account') payment_node = self.section.newContent(portal_type='Bank Account')
payment_node.validate()
self.tic()
invoice = self._makeOne( invoice = self._makeOne(
destination_section_value=self.organisation_module.client_1, destination_section_value=self.organisation_module.client_1,
lines=(dict(source_value=self.account_module.goods_purchase, lines=(dict(source_value=self.account_module.goods_purchase,
...@@ -3507,6 +3618,8 @@ class TestTransactions(AccountingTestCase): ...@@ -3507,6 +3618,8 @@ class TestTransactions(AccountingTestCase):
# Simple creating a related payment transaction when grouping reference of # Simple creating a related payment transaction when grouping reference of
# some lines is already set. # some lines is already set.
payment_node = self.section.newContent(portal_type='Bank Account') payment_node = self.section.newContent(portal_type='Bank Account')
payment_node.validate()
self.tic()
invoice = self._makeOne( invoice = self._makeOne(
destination_section_value=self.organisation_module.client_1, destination_section_value=self.organisation_module.client_1,
lines=(dict(source_value=self.account_module.goods_purchase, lines=(dict(source_value=self.account_module.goods_purchase,
...@@ -3528,6 +3641,8 @@ class TestTransactions(AccountingTestCase): ...@@ -3528,6 +3641,8 @@ class TestTransactions(AccountingTestCase):
# Simple creating a related payment transaction when we have two line for # Simple creating a related payment transaction when we have two line for
# 2 different destination sections. # 2 different destination sections.
payment_node = self.section.newContent(portal_type='Bank Account') payment_node = self.section.newContent(portal_type='Bank Account')
payment_node.validate()
self.tic()
invoice = self._makeOne( invoice = self._makeOne(
destination_section_value=self.organisation_module.client_1, destination_section_value=self.organisation_module.client_1,
lines=(dict(source_value=self.account_module.goods_purchase, lines=(dict(source_value=self.account_module.goods_purchase,
...@@ -3549,6 +3664,8 @@ class TestTransactions(AccountingTestCase): ...@@ -3549,6 +3664,8 @@ class TestTransactions(AccountingTestCase):
# Simple creating a related payment transaction when we have related # Simple creating a related payment transaction when we have related
# transactions. # transactions.
payment_node = self.section.newContent(portal_type='Bank Account') payment_node = self.section.newContent(portal_type='Bank Account')
payment_node.validate()
self.tic()
invoice = self._makeOne( invoice = self._makeOne(
destination_section_value=self.organisation_module.client_1, destination_section_value=self.organisation_module.client_1,
lines=(dict(source_value=self.account_module.goods_purchase, lines=(dict(source_value=self.account_module.goods_purchase,
...@@ -3580,6 +3697,8 @@ class TestTransactions(AccountingTestCase): ...@@ -3580,6 +3697,8 @@ class TestTransactions(AccountingTestCase):
# Simple creating a related payment transaction when we have related # Simple creating a related payment transaction when we have related
# transactions with different side # transactions with different side
payment_node = self.section.newContent(portal_type='Bank Account') payment_node = self.section.newContent(portal_type='Bank Account')
payment_node.validate()
self.tic()
invoice = self._makeOne( invoice = self._makeOne(
destination_section_value=self.organisation_module.client_1, destination_section_value=self.organisation_module.client_1,
lines=(dict(source_value=self.account_module.goods_purchase, lines=(dict(source_value=self.account_module.goods_purchase,
...@@ -3610,6 +3729,8 @@ class TestTransactions(AccountingTestCase): ...@@ -3610,6 +3729,8 @@ class TestTransactions(AccountingTestCase):
# Simple creating a related payment transaction when we have related # Simple creating a related payment transaction when we have related
# transactions in draft/cancelled state (they are ignored) # transactions in draft/cancelled state (they are ignored)
payment_node = self.section.newContent(portal_type='Bank Account') payment_node = self.section.newContent(portal_type='Bank Account')
payment_node.validate()
self.tic()
invoice = self._makeOne( invoice = self._makeOne(
destination_section_value=self.organisation_module.client_1, destination_section_value=self.organisation_module.client_1,
lines=(dict(source_value=self.account_module.goods_purchase, lines=(dict(source_value=self.account_module.goods_purchase,
...@@ -3644,6 +3765,8 @@ class TestTransactions(AccountingTestCase): ...@@ -3644,6 +3765,8 @@ class TestTransactions(AccountingTestCase):
def test_Invoice_createRelatedPaymentTransactionDifferentCurrency(self): def test_Invoice_createRelatedPaymentTransactionDifferentCurrency(self):
payment_node = self.section.newContent(portal_type='Bank Account') payment_node = self.section.newContent(portal_type='Bank Account')
payment_node.validate()
self.tic()
invoice = self._makeOne( invoice = self._makeOne(
destination_section_value=self.organisation_module.client_1, destination_section_value=self.organisation_module.client_1,
resource_value=self.portal.currency_module.usd, resource_value=self.portal.currency_module.usd,
...@@ -3678,6 +3801,8 @@ class TestTransactions(AccountingTestCase): ...@@ -3678,6 +3801,8 @@ class TestTransactions(AccountingTestCase):
"""Checks in case of deleted Payments related to invoice""" """Checks in case of deleted Payments related to invoice"""
# Simple case of creating a related payment transaction. # Simple case of creating a related payment transaction.
payment_node = self.section.newContent(portal_type='Bank Account') payment_node = self.section.newContent(portal_type='Bank Account')
payment_node.validate()
self.tic()
invoice = self._makeOne( invoice = self._makeOne(
destination_section_value=self.organisation_module.client_1, destination_section_value=self.organisation_module.client_1,
lines=(dict(source_value=self.account_module.goods_purchase, lines=(dict(source_value=self.account_module.goods_purchase,
...@@ -5776,68 +5901,6 @@ class TestAccountingWithSequences(ERP5TypeTestCase): ...@@ -5776,68 +5901,6 @@ class TestAccountingWithSequences(ERP5TypeTestCase):
except ValidationFailed as err: except ValidationFailed as err:
raise AssertionError("Validation failed : %s" % err.msg) raise AssertionError("Validation failed : %s" % err.msg)
def stepValidateNoPayment(self, sequence, sequence_list=None, **kw) :
"""Check validation behaviour related to payment & mirror_payment.
If we use an account of type asset/cash/bank, we must use set a Bank
Account as source_payment or destination_payment.
This this source/destination payment must be a portal type from the
`payment node` portal type group. It can be defined on transaction
or line.
"""
def useBankAccount(accounting_transaction):
"""Modify the transaction, so that a line will use an account member of
account_type/cash/bank , which requires to use a payment category.
"""
# get the default and replace income account by bank
income_account_found = 0
for line in accounting_transaction.getMovementList() :
source_account = line.getSourceValue()
if source_account.isMemberOf('account_type/income') :
income_account_found = 1
line.edit( source_value = sequence.get('bank_account'),
destination_value = sequence.get('bank_account') )
self.assertTrue(income_account_found)
# XXX
accounting_transaction = sequence.get('transaction')
useBankAccount(accounting_transaction)
self.assertRaises(ValidationFailed,
self.getWorkflowTool().doActionFor,
accounting_transaction,
'stop_action')
source_section_value = accounting_transaction.getSourceSectionValue()
destination_section_value = accounting_transaction.getDestinationSectionValue()
for ptype in self.getPortal().getPortalPaymentNodeTypeList() :
source_payment_value = source_section_value.newContent(
portal_type = ptype, )
destination_payment_value = destination_section_value.newContent(
portal_type = ptype, )
accounting_transaction = self.createAccountingTransaction(
destination_section_value=self.other_vendor)
useBankAccount(accounting_transaction)
# payment node have to be set on both sides if both sides have accounting
# lines
accounting_transaction.setSourcePaymentValue(source_payment_value)
accounting_transaction.setDestinationPaymentValue(None)
self.assertRaises(ValidationFailed,
self.getWorkflowTool().doActionFor,
accounting_transaction,
'stop_action')
accounting_transaction.setSourcePaymentValue(None)
accounting_transaction.setDestinationPaymentValue(destination_payment_value)
self.assertRaises(ValidationFailed,
self.getWorkflowTool().doActionFor,
accounting_transaction,
'stop_action')
accounting_transaction.setSourcePaymentValue(source_payment_value)
accounting_transaction.setDestinationPaymentValue(destination_payment_value)
try:
self.getWorkflowTool().doActionFor(accounting_transaction, 'stop_action')
self.assertEqual(accounting_transaction.getSimulationState(), 'stopped')
except ValidationFailed as err:
self.fail("Validation failed : %s" % err.msg)
def stepValidateRemoveEmptyLines(self, sequence, sequence_list=None, **kw): def stepValidateRemoveEmptyLines(self, sequence, sequence_list=None, **kw):
"""Check validating a transaction remove empty lines. """ """Check validating a transaction remove empty lines. """
accounting_transaction = sequence.get('transaction') accounting_transaction = sequence.get('transaction')
...@@ -6014,18 +6077,6 @@ class TestAccountingWithSequences(ERP5TypeTestCase): ...@@ -6014,18 +6077,6 @@ class TestAccountingWithSequences(ERP5TypeTestCase):
stepCreateValidAccountingTransaction stepCreateValidAccountingTransaction
stepValidateNotBalanced""", quiet=quiet) stepValidateNotBalanced""", quiet=quiet)
def test_AccountingTransactionValidationPayment(self, quiet=QUIET,
run=RUN_ALL_TESTS):
"""Transaction validation and payment"""
if not run : return
self.playSequence("""
stepCreateEntities
stepCreateCurrencies
stepCreateAccounts
stepCreateValidAccountingTransaction
stepValidateNoPayment
""", quiet=quiet)
def test_AccountingTransactionValidationRemoveEmptyLines(self, quiet=QUIET, def test_AccountingTransactionValidationRemoveEmptyLines(self, quiet=QUIET,
run=RUN_ALL_TESTS): run=RUN_ALL_TESTS):
"""Transaction validation removes empty lines""" """Transaction validation removes empty lines"""
......
...@@ -20,17 +20,24 @@ container.script_validateTransaction(state_change) ...@@ -20,17 +20,24 @@ container.script_validateTransaction(state_change)
# parties or bank accounts # parties or bank accounts
transaction_lines = transaction.objectValues(portal_type=transaction.getPortalAccountingMovementTypeList()) transaction_lines = transaction.objectValues(portal_type=transaction.getPortalAccountingMovementTypeList())
id_to_delete_list = [] id_to_delete_list = []
getBankAccountItemList = portal.AccountModule_getBankAccountItemList
for line in transaction_lines: for line in transaction_lines:
for account, third_party, bank_account, bank_account_relative_url_list_getter in (
for account, third_party, bank_account in ( (
( line.getSourceValue(portal_type='Account'), line.getSourceValue(portal_type='Account'),
line.getDestinationSectionValue(portal_type=section_portal_type_list), line.getDestinationSectionValue(portal_type=section_portal_type_list),
line.getSourcePaymentValue(portal_type=bank_account_portal_type),), line.getSourcePaymentValue(portal_type=bank_account_portal_type),
( line.getDestinationValue(portal_type='Account'), lambda: (x[1] for x in getBankAccountItemList(
organisation=line.getSourceSection(portal_type=section_portal_type_list))),
),
(
line.getDestinationValue(portal_type='Account'),
line.getSourceSectionValue(portal_type=section_portal_type_list), line.getSourceSectionValue(portal_type=section_portal_type_list),
line.getDestinationPaymentValue(portal_type=bank_account_portal_type),), line.getDestinationPaymentValue(portal_type=bank_account_portal_type),
): lambda: (x[1] for x in getBankAccountItemList(
organisation=line.getDestinationSection(portal_type=section_portal_type_list))),
),
):
if account is not None and account.getValidationState() != 'validated': if account is not None and account.getValidationState() != 'validated':
raise ValidationFailed(translateString( raise ValidationFailed(translateString(
"Account ${account_title} is not validated.", "Account ${account_title} is not validated.",
...@@ -43,7 +50,7 @@ for line in transaction_lines: ...@@ -43,7 +50,7 @@ for line in transaction_lines:
mapping=dict(third_party_name=third_party.getTitle()))) mapping=dict(third_party_name=third_party.getTitle())))
if bank_account is not None: if bank_account is not None:
if bank_account.getValidationState() in invalid_state_list: if bank_account.getRelativeUrl() not in bank_account_relative_url_list_getter():
raise ValidationFailed(translateString( raise ValidationFailed(translateString(
"Bank Account ${bank_account_reference} is invalid.", "Bank Account ${bank_account_reference} is invalid.",
mapping=dict(bank_account_reference=bank_account.getReference()))) mapping=dict(bank_account_reference=bank_account.getReference())))
......
...@@ -22,12 +22,15 @@ organisation = portal.organisation_module.newContent( ...@@ -22,12 +22,15 @@ organisation = portal.organisation_module.newContent(
title=organisation_id, title=organisation_id,
group_value=portal.portal_categories.group.demo_group, group_value=portal.portal_categories.group.demo_group,
) )
organisation.validate()
bank_account_id = "erp5_payment_mean_bank" bank_account_id = "erp5_payment_mean_bank"
bank_account = organisation.newContent( bank_account = organisation.newContent(
portal_type='Bank Account', portal_type='Bank Account',
id=bank_account_id, id=bank_account_id,
title=bank_account_id title=bank_account_id
) )
bank_account.validate()
payment_mean_id = "erp5_payment_mean_ui_test_payment_transaction_group" payment_mean_id = "erp5_payment_mean_ui_test_payment_transaction_group"
payment_mean = portal.payment_transaction_group_module.newContent( payment_mean = portal.payment_transaction_group_module.newContent(
......
...@@ -314,7 +314,7 @@ class TestOrderMixin(SubcontentReindexingWrapper): ...@@ -314,7 +314,7 @@ class TestOrderMixin(SubcontentReindexingWrapper):
portal_type=organisation_portal_type) portal_type=organisation_portal_type)
organisation.newContent(id='bank', organisation.newContent(id='bank',
portal_type='Bank Account', portal_type='Bank Account',
title='bank%s' % organisation.getId()) title='bank%s' % organisation.getId()).validate()
organisation.setDefaultAddressStreetAddress('rue xv') organisation.setDefaultAddressStreetAddress('rue xv')
organisation.setDefaultAddressZipCode('12345') organisation.setDefaultAddressZipCode('12345')
if title is None: if title is None:
......
...@@ -68,6 +68,7 @@ my_organisation.validate() ...@@ -68,6 +68,7 @@ my_organisation.validate()
bank_account = my_organisation.newContent(portal_type="Bank Account", bank_account = my_organisation.newContent(portal_type="Bank Account",
title=howto_dict["sale_howto_bank_account_title"], title=howto_dict["sale_howto_bank_account_title"],
reference=howto_dict["sale_howto_bank_account_reference"],) reference=howto_dict["sale_howto_bank_account_reference"],)
bank_account.validate()
organisation = portal.organisation_module.newContent(portal_type='Organisation', organisation = portal.organisation_module.newContent(portal_type='Organisation',
title=howto_dict['sale_howto_organisation2_title'], title=howto_dict['sale_howto_organisation2_title'],
......
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