From 2e8f7656a9376b9f8c7f018db98e05d7b285af6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9rome=20Perrin?= <jerome@nexedi.com> Date: Tue, 24 Jan 2006 17:44:37 +0000 Subject: [PATCH] initial checkin; contains basic tests for multi currency accounting. git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@5283 20353a03-c40f-0410-a6d1-a30d3c3de9de --- product/ERP5/tests/testAccounting.py | 370 +++++++++++++++++++++++++++ 1 file changed, 370 insertions(+) create mode 100755 product/ERP5/tests/testAccounting.py diff --git a/product/ERP5/tests/testAccounting.py b/product/ERP5/tests/testAccounting.py new file mode 100755 index 0000000000..c900c0b6de --- /dev/null +++ b/product/ERP5/tests/testAccounting.py @@ -0,0 +1,370 @@ +############################################################################# +# +# Copyright (c) 2004 Nexedi SARL and Contributors. All Rights Reserved. +# Jerome Perrin <jerome@nexedi.com> +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contract a Free Software +# Service Company +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +""" + Tests some functionnalities of accounting. + +""" +import os, sys +if __name__ == '__main__': + execfile(os.path.join(sys.path[0], 'framework.py')) + +# Needed in order to have a log file inside the current folder +os.environ['EVENT_LOG_FILE'] = os.path.join(os.getcwd(), 'zLOG.log') +os.environ['EVENT_LOG_SEVERITY'] = '-300' + +from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase +from AccessControl.SecurityManagement import newSecurityManager +from zLOG import LOG +from testPackingList import TestPackingListMixin +from Products.ERP5Type.tests.Sequence import Sequence, SequenceList + +class TestAccounting(ERP5TypeTestCase): + """Test Accounting. """ + + def getAccountingModule(self): + return getattr(self.getPortal(), 'accounting_module', + getattr(self.getPortal(), 'accounting', None)) + + def getAccountModule(self) : + return getattr(self.getPortal(), 'account_module', + getattr(self.getPortal(), 'account', None)) + + # XXX + def playSequence(self, sequence_string) : + sequence_list = SequenceList() + sequence_list.addSequenceString(sequence_string) + sequence_list.play(self) + + RUN_ALL_TESTS = 1 + + account_portal_type = 'Account' + currency_portal_type = 'Currency' + organisation_portal_type = 'Organisation' + sale_invoice_portal_type = 'Sale Invoice Transaction' + sale_invoice_line_portal_type = 'Sale Invoice Line' + sale_invoice_transaction_line_portal_type = 'Sale Invoice Transaction Line' + sale_invoice_cell_portal_type = 'Invoice Cell' + purchase_invoice_portal_type = 'Purchase Invoice Transaction' + purchase_invoice_line_portal_type = 'Purchase Invoice Line' + purchase_invoice_transaction_line_portal_type = \ + 'Purchase Invoice Transaction Line' + purchase_invoice_cell_portal_type = 'Invoice Cell' + + def getTitle(self): + return "Accounting" + + def login(self) : + """sets the security manager""" + uf = self.getPortal().acl_users + uf._doAddUser('alex', '', ['Member', 'Assignee', 'Assignor', + 'Auditor', 'Author', 'Manager'], []) + user = uf.getUserById('alex').__of__(uf) + newSecurityManager(None, user) + + def createCategories(self): + """Create the categories for our test. """ + TestPackingListMixin.createCategories(self) + # create categories + for cat_string in self.getNeededCategoryList() : + base_cat = cat_string.split("/")[0] + path = self.getPortal().portal_categories[base_cat] + for cat in cat_string.split("/")[1:] : + if not cat in path.objectIds() : + path = path.newContent( + portal_type = 'Category', + id = cat, + immediate_reindex = 1 ) + # check categories have been created + for cat_string in self.getNeededCategoryList() : + self.assertNotEquals(None, + self.getCategoryTool().restrictedTraverse(cat_string), + cat_string) + + def getNeededCategoryList(self): + """return a list of categories that should be created.""" + return ('group/client', 'group/vendor' ) + + def getBusinessTemplateList(self): + """ """ + return ('erp5_pdm', 'erp5_trade', 'erp5_accounting',) + + def stepTic(self, **kw): + self.tic() + + def stepCreateEntities(self, sequence, **kw) : + """Create a vendor and a client. """ + client = self.getOrganisationModule().newContent( + portal_type = self.organisation_portal_type, + group = "group/client", + price_currency = "currency_module/USD") + vendor = self.getOrganisationModule().newContent( + portal_type = self.organisation_portal_type, + group = "group/vendor", + price_currency = "currency_module/EUR") + sequence.edit( client = client, vendor = vendor) + + def stepCreateCurrencies(self, sequence, **kw) : + """Create a some currencies. """ + EUR = self.getCurrencyModule().newContent( + portal_type = self.currency_portal_type, + reference = "EUR", + id = "EUR" ) + USD = self.getCurrencyModule().newContent( + portal_type = self.currency_portal_type, + reference = "USD", + id = "USD" ) + YEN = self.getCurrencyModule().newContent( + portal_type = self.currency_portal_type, + reference = "YEN", + id = "YEN" ) + sequence.edit( EUR = EUR, USD = USD, YEN = YEN ) + + def stepCreateAccounts(self, sequence, **kw) : + """Create necessary accounts. """ + receivable = self.getAccountModule().newContent( + title = 'receivable', + portal_type = self.account_portal_type, + account_type = 'asset/receivable' ) + payable = self.getAccountModule().newContent( + title = 'payable', + portal_type = self.account_portal_type, + account_type = 'liability/payable' ) + expense = self.getAccountModule().newContent( + title = 'expense', + portal_type = self.account_portal_type, + account_type = 'expense' ) + income = self.getAccountModule().newContent( + title = 'income', + portal_type = self.account_portal_type, + account_type = 'income' ) + collected_vat = self.getAccountModule().newContent( + title = 'collected_vat', + portal_type = self.account_portal_type, + account_type = 'liability/payable/collected_vat' ) + refundable_vat = self.getAccountModule().newContent( + title = 'refundable_vat', + portal_type = self.account_portal_type, + account_type = 'asset/receivable/refundable_vat' ) + bank = self.getAccountModule().newContent( + title = 'bank', + portal_type = self.account_portal_type, + account_type = 'asset/cash/bank') + + # set mirror accounts. + receivable.setDestinationValue(payable) + payable.setDestinationValue(receivable) + expense.setDestinationValue(income) + income.setDestinationValue(expense) + collected_vat.setDestinationValue(refundable_vat) + refundable_vat.setDestinationValue(collected_vat) + bank.setDestinationValue(bank) + + sequence.edit( receivable_account = receivable, + payable_account = payable, + expense_account = expense, + income_account = income, + collected_vat_account = collected_vat, + refundable_vat_account = refundable_vat, + bank = bank, ) + + def getInvoicePropertyList(self): + """Returns the list of properties for invoices, stored as + a list of dictionnaries. """ + # source currency is EUR + # destination currency is USD + return [ + # in currency of destination, converted for source + { 'income' : -200, 'source_converted_income' : -180, + 'collected_vat' : -40, 'source_converted_collected_vat' : -36, + 'receivable' : 240, 'source_converted_receivable' : 216, + 'currency' : 'currency_module/USD' }, + + # in currency of source, converted for destination + { 'income' : -100, 'destination_converted_expense' : -200, + 'collected_vat' : 10, 'destination_converted_refundable_vat' : -100, + 'receivable' : 90, 'destination_converted_payable' : 100, + 'currency' : 'currency_module/EUR' }, + + { 'income' : -100, 'destination_converted_expense' : -200, + 'collected_vat' : 10, 'destination_converted_refundable_vat' : -100, + 'receivable' : 90, 'destination_converted_payable' : 100, + 'currency' : 'currency_module/EUR' }, + + # in an external currency, converted for both source and dest. + { 'income' : -300, + 'source_converted_income' : -200, + 'destination_converted_expense' : -400, + 'collected_vat' : 40, + 'source_converted_collected_vat' : 36, + 'destination_converted_refundable_vat' : 50, + 'receivable' : 260, + 'source_converted_receivable' : 164, + 'destination_converted_payable': 350, + 'currency' : 'currency_module/YEN' }, + + # currency of source, not converted for destination -> 0 + # FIXME: validation should be refused by accounting workflow ? + { 'income' : -100, + 'collected_vat' : -20, + 'receivable' : 120, + 'currency' : 'currency_module/EUR' }, + + ] + + def stepCreateInvoices(self, sequence, **kw) : + """Create invoices with properties from getInvoicePropertyList. """ + invoice_prop_list = self.getInvoicePropertyList() + invoice_list = [] + for invoice_prop in invoice_prop_list : + invoice = self.getAccountingModule().newContent( + portal_type = self.sale_invoice_portal_type, + source_section_value = sequence.get('vendor'), + source_value = sequence.get('vendor'), + destination_section_value = sequence.get('client'), + destination_value = sequence.get('client'), + resource = invoice_prop['currency'], + bypass_init_script = 0, + ) + + for line_type in ['income', 'receivable', 'collected_vat'] : + source_account = sequence.get('%s_account' % line_type) + line = invoice.newContent( + portal_type = self.sale_invoice_transaction_line_portal_type, + quantity = invoice_prop[line_type], + source_value = source_account + ) + source_converted = invoice_prop.get( + 'source_converted_%s' % line_type, None) + if source_converted is not None : + line.setSourceTotalAssetPrice(source_converted) + + destination_account = source_account.getDestinationValue( + portal_type = 'Account' ) + destination_converted = invoice_prop.get( + 'destination_converted_%s' % + destination_account.getAccountTypeId(), None) + if destination_converted is not None : + line.setDestinationTotalAssetPrice(destination_converted) + + invoice_list.append(invoice) + sequence.edit( invoice_list = invoice_list ) + + def checkAccountBalanceInCurrency(self, section, currency, + sequence, **kw) : + """ Checks accounts balances in a given currency.""" + invoice_list = sequence.get('invoice_list') + for account_type in [ 'income', 'receivable', 'collected_vat', + 'expense', 'payable', 'refundable_vat' ] : + account = sequence.get('%s_account' % account_type) + calculated_balance = 0 + for invoice in invoice_list : + for line in invoice.getMovementList(): + # source + if line.getSourceValue() == account and\ + line.getResourceValue() == currency and\ + section == line.getSourceSectionValue() : + calculated_balance += ( + line.getSourceDebit() - line.getSourceCredit()) + # dest. + elif line.getDestinationValue() == account and\ + line.getResourceValue() == currency and\ + section == line.getDestinationSectionValue() : + calculated_balance += ( + line.getDestinationDebit() - line.getDestinationCredit()) + + self.assertEquals(calculated_balance, + self.getPortal().portal_simulation.getInventory( + node_uid = account.getUid(), + section_uid = section.getUid(), + # resource_uid = currency.getUid() # FIXME: raises a KeyError in SQLCatalog.buildSQLQuery + resource = currency.getRelativeUrl() + )) + + def stepCheckAccountBalanceLocalCurrency(self, sequence, **kw) : + """ Checks accounts balances in the organisation default currency.""" + for section in (sequence.get('vendor'), sequence.get('client')) : + currency = section.getPriceCurrencyValue() + self.checkAccountBalanceInCurrency(section, currency, sequence) + + def stepCheckAccountBalanceExternalCurrency(self, sequence, **kw) : + """ Checks accounts balances in external currencies .""" + for section in (sequence.get('vendor'), sequence.get('client')) : + for currency in (sequence.get('USD'), sequence.get('YEN')) : + self.checkAccountBalanceInCurrency(section, currency, sequence) + + def checkAccountBalanceInConvertedCurrency(self, section, sequence, **kw) : + """ Checks accounts balances converted in section default currency.""" + invoice_list = sequence.get('invoice_list') + for account_type in [ 'income', 'receivable', 'collected_vat', + 'expense', 'payable', 'refundable_vat' ] : + account = sequence.get('%s_account' % account_type) + calculated_balance = 0 + for invoice in invoice_list : + for line in invoice.getMovementList() : + if line.getSourceValue() == account and \ + section == line.getSourceSectionValue() : + calculated_balance += line.getSourceInventoriatedTotalAssetPrice() + elif line.getDestinationValue() == account and\ + section == line.getDestinationSectionValue() : + calculated_balance += \ + line.getDestinationInventoriatedTotalAssetPrice() + self.assertEquals(calculated_balance, + self.getPortal().portal_simulation.getInventoryAssetPrice( + node_uid = account.getUid(), + section_uid = section.getUid(), + )) + + def stepCheckAccountBalanceConvertedCurrency(self, sequence, **kw): + """Checks accounts balances converted in the organisation default + currency.""" + for section in (sequence.get('vendor'), sequence.get('client')) : + self.checkAccountBalanceInConvertedCurrency(section, sequence) + + def test_MultiCurrencyInvoice(self, quiet=0, run=RUN_ALL_TESTS): + """Basic test for multi currency accounting""" + self.playSequence(""" + stepCreateCurrencies + stepCreateEntities + stepCreateAccounts + stepCreateInvoices + stepTic + stepCheckAccountBalanceLocalCurrency + stepCheckAccountBalanceExternalCurrency + stepCheckAccountBalanceConvertedCurrency + """) + +if __name__ == '__main__': + framework() +else: + import unittest + def test_suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(TestAccounting)) + return suite + -- 2.30.9