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):
self.assertRaises(ValidationFailed,
self.portal.portal_workflow.doActionFor,
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.validate()
self.tic()
accounting_transaction.setSourcePaymentValue(bank_account)
self.portal.portal_workflow.doActionFor(accounting_transaction, 'stop_action')
......@@ -704,6 +706,7 @@ class TestTransactionValidation(AccountingTestCase):
# bank account
bank_account = self.section.newContent(portal_type='Bank Account',
price_currency_value=self.currency_module.euro)
bank_account.validate()
accounting_transaction = self._makeOne(
portal_type='Payment Transaction',
start_date=DateTime('2007/01/02'),
......@@ -725,6 +728,112 @@ class TestTransactionValidation(AccountingTestCase):
accounting_transaction.setResourceValue(self.currency_module.euro)
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):
# Accounting Transactions have to be balanced to be validated
accounting_transaction = self._makeOne(
......@@ -3490,6 +3599,8 @@ class TestTransactions(AccountingTestCase):
def test_Invoice_createRelatedPaymentTransactionSimple(self):
# Simple case of creating a related payment transaction.
payment_node = self.section.newContent(portal_type='Bank Account')
payment_node.validate()
self.tic()
invoice = self._makeOne(
destination_section_value=self.organisation_module.client_1,
lines=(dict(source_value=self.account_module.goods_purchase,
......@@ -3507,6 +3618,8 @@ class TestTransactions(AccountingTestCase):
# Simple creating a related payment transaction when grouping reference of
# some lines is already set.
payment_node = self.section.newContent(portal_type='Bank Account')
payment_node.validate()
self.tic()
invoice = self._makeOne(
destination_section_value=self.organisation_module.client_1,
lines=(dict(source_value=self.account_module.goods_purchase,
......@@ -3528,6 +3641,8 @@ class TestTransactions(AccountingTestCase):
# Simple creating a related payment transaction when we have two line for
# 2 different destination sections.
payment_node = self.section.newContent(portal_type='Bank Account')
payment_node.validate()
self.tic()
invoice = self._makeOne(
destination_section_value=self.organisation_module.client_1,
lines=(dict(source_value=self.account_module.goods_purchase,
......@@ -3549,6 +3664,8 @@ class TestTransactions(AccountingTestCase):
# Simple creating a related payment transaction when we have related
# transactions.
payment_node = self.section.newContent(portal_type='Bank Account')
payment_node.validate()
self.tic()
invoice = self._makeOne(
destination_section_value=self.organisation_module.client_1,
lines=(dict(source_value=self.account_module.goods_purchase,
......@@ -3580,6 +3697,8 @@ class TestTransactions(AccountingTestCase):
# Simple creating a related payment transaction when we have related
# transactions with different side
payment_node = self.section.newContent(portal_type='Bank Account')
payment_node.validate()
self.tic()
invoice = self._makeOne(
destination_section_value=self.organisation_module.client_1,
lines=(dict(source_value=self.account_module.goods_purchase,
......@@ -3610,6 +3729,8 @@ class TestTransactions(AccountingTestCase):
# Simple creating a related payment transaction when we have related
# transactions in draft/cancelled state (they are ignored)
payment_node = self.section.newContent(portal_type='Bank Account')
payment_node.validate()
self.tic()
invoice = self._makeOne(
destination_section_value=self.organisation_module.client_1,
lines=(dict(source_value=self.account_module.goods_purchase,
......@@ -3644,6 +3765,8 @@ class TestTransactions(AccountingTestCase):
def test_Invoice_createRelatedPaymentTransactionDifferentCurrency(self):
payment_node = self.section.newContent(portal_type='Bank Account')
payment_node.validate()
self.tic()
invoice = self._makeOne(
destination_section_value=self.organisation_module.client_1,
resource_value=self.portal.currency_module.usd,
......@@ -3678,6 +3801,8 @@ class TestTransactions(AccountingTestCase):
"""Checks in case of deleted Payments related to invoice"""
# Simple case of creating a related payment transaction.
payment_node = self.section.newContent(portal_type='Bank Account')
payment_node.validate()
self.tic()
invoice = self._makeOne(
destination_section_value=self.organisation_module.client_1,
lines=(dict(source_value=self.account_module.goods_purchase,
......@@ -5776,68 +5901,6 @@ class TestAccountingWithSequences(ERP5TypeTestCase):
except ValidationFailed as err:
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):
"""Check validating a transaction remove empty lines. """
accounting_transaction = sequence.get('transaction')
......@@ -6014,18 +6077,6 @@ class TestAccountingWithSequences(ERP5TypeTestCase):
stepCreateValidAccountingTransaction
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,
run=RUN_ALL_TESTS):
"""Transaction validation removes empty lines"""
......
......@@ -20,17 +20,24 @@ container.script_validateTransaction(state_change)
# parties or bank accounts
transaction_lines = transaction.objectValues(portal_type=transaction.getPortalAccountingMovementTypeList())
id_to_delete_list = []
getBankAccountItemList = portal.AccountModule_getBankAccountItemList
for line in transaction_lines:
for account, third_party, bank_account in (
( line.getSourceValue(portal_type='Account'),
for account, third_party, bank_account, bank_account_relative_url_list_getter in (
(
line.getSourceValue(portal_type='Account'),
line.getDestinationSectionValue(portal_type=section_portal_type_list),
line.getSourcePaymentValue(portal_type=bank_account_portal_type),),
( line.getDestinationValue(portal_type='Account'),
line.getSourcePaymentValue(portal_type=bank_account_portal_type),
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.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':
raise ValidationFailed(translateString(
"Account ${account_title} is not validated.",
......@@ -43,7 +50,7 @@ for line in transaction_lines:
mapping=dict(third_party_name=third_party.getTitle())))
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(
"Bank Account ${bank_account_reference} is invalid.",
mapping=dict(bank_account_reference=bank_account.getReference())))
......
......@@ -22,12 +22,15 @@ organisation = portal.organisation_module.newContent(
title=organisation_id,
group_value=portal.portal_categories.group.demo_group,
)
organisation.validate()
bank_account_id = "erp5_payment_mean_bank"
bank_account = organisation.newContent(
portal_type='Bank Account',
id=bank_account_id,
title=bank_account_id
)
bank_account.validate()
payment_mean_id = "erp5_payment_mean_ui_test_payment_transaction_group"
payment_mean = portal.payment_transaction_group_module.newContent(
......
......@@ -314,7 +314,7 @@ class TestOrderMixin(SubcontentReindexingWrapper):
portal_type=organisation_portal_type)
organisation.newContent(id='bank',
portal_type='Bank Account',
title='bank%s' % organisation.getId())
title='bank%s' % organisation.getId()).validate()
organisation.setDefaultAddressStreetAddress('rue xv')
organisation.setDefaultAddressZipCode('12345')
if title is None:
......
......@@ -68,6 +68,7 @@ my_organisation.validate()
bank_account = my_organisation.newContent(portal_type="Bank Account",
title=howto_dict["sale_howto_bank_account_title"],
reference=howto_dict["sale_howto_bank_account_reference"],)
bank_account.validate()
organisation = portal.organisation_module.newContent(portal_type='Organisation',
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