############################################################################## # # Copyright (c) 2010 Nexedi SA and Contributors. All Rights Reserved. # Sebastien Robin <seb@nexedi.com> # 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # ############################################################################## """ Tests accounting simulation rules and delivery builder. This tests also do basic checks for XMLMatrix and Predicate matching the way it is used in the invoice related simulation. """ # TODO : # * test with a Person as destination_section # * test cancelling / deleting an invoice # * test payment rule & payment builder # * test simulation purge when Payment delivered or top level Order cancelled # * test removing cells for a line # import unittest import random from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase from Products.ERP5Type.tests.Sequence import SequenceList from Products.ERP5.tests.utils import newSimulationExpectedFailure from Products.ERP5.Document.MirrorMovementGroup import _isMirrored from Testing import ZopeTestCase from zLOG import LOG, INFO from DateTime import DateTime class PurchaseInvoiceTest: invoice_portal_type = 'Purchase Invoice Transaction' invoice_transaction_line_portal_type \ = "Purchase Invoice Transaction Line" invoice_line_portal_type = "Invoice Line" invoice_cell_portal_type = "Invoice Cell" payment_transaction_line_portal_type = "Accounting Transaction Line" class SaleInvoiceTest: invoice_portal_type = 'Sale Invoice Transaction' invoice_transaction_line_portal_type \ = "Sale Invoice Transaction Line" invoice_line_portal_type = "Invoice Line" invoice_cell_portal_type = "Invoice Cell" payment_transaction_line_portal_type = "Accounting Transaction Line" class TestAccountingRulesMixin: # define portal_types account_module_portal_type = "Account Module" accounting_module_portal_type = "Accounting Module" product_module_portal_type = "Product Module" currency_module_portal_type = "Currency Module" organisation_portal_type = "Organisation" account_portal_type = "Account" product_portal_type = "Product" currency_portal_type = "Currency" predicate_portal_type = "Predicate" applied_rule_portal_type = "Applied Rule" simulation_movement_portal_type = "Simulation Movement" accounting_rule_cell_portal_type = "Accounting Rule Cell" payment_transaction_portal_type = "Payment Transaction" payment_transaction_line_definition_list = ( ('bank', 'account_module/bank', 'account_module/bank'), ) def getBusinessTemplateList(self): """ Return the list of business templates. """ return ('erp5_base','erp5_pdm', 'erp5_simulation', 'erp5_trade', 'erp5_accounting', 'erp5_invoicing', 'erp5_simplified_invoicing', 'erp5_simulation_test') def getAccountModule(self): return getattr(self.getPortal(), 'account', getattr(self.getPortal(), 'account_module')) def getAccountingModule(self): return getattr(self.getPortal(), 'accounting', getattr(self.getPortal(), 'accounting_module')) def getProductModule(self): return getattr(self.getPortal(), 'product', getattr(self.getPortal(), 'product_module')) ## XXX move this to "Sequence class" def playSequence(self, sequence_string, quiet=0) : sequence_list = SequenceList() sequence_list.addSequenceString(sequence_string) sequence_list.play(self, quiet=quiet) class TestAccountingRules(TestAccountingRulesMixin, ERP5TypeTestCase): """ This should test the simulation tree and builds starting from the invoice. """ RUN_ALL_TESTS = 1 QUIET = 0 def getTitle(self): return "Accounting Rules" def afterSetUp(self) : self.login() self.createCategories() self.validateRules() def createCategories(self) : """ create all categories that are needed for this test. It uses getCategoriesToCreate, so you should overload this method. """ # create base categories for base_cat in self.getBaseCategoriesToCreate() : if not base_cat in self.getCategoryTool().objectIds() : self.getCategoryTool().newContent( portal_type = 'Base Category', id = base_cat) # create categories for cat_string in self.getCategoriesToCreate() : 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) else: path = getattr(path, cat) # check categories have been created for cat_string in self.getCategoriesToCreate() : self.assertNotEquals(None, self.getCategoryTool().restrictedTraverse(cat_string), cat_string) def getBaseCategoriesToCreate(self) : return ("hd_size", "cpu_freq") def getCategoriesToCreate(self): return ( # regions for our organisations "region/europe/west/france", "region/africa", # those are mandatory for account, and accounting rules depends on # the account_type category. (ie payable, will create a Payment # Transaction accordingly) "account_type/asset/cash", "account_type/asset/receivable/refundable_vat", "account_type/equity", "account_type/expense", "account_type/income", "account_type/liability/payable/collected_vat", # some products lines for our products "product_line/storever/notebook", "product_line/storever/barebone", "product_line/storever/openbrick", "product_line/not_used/not_matched", # some categories for variating our products "cpu_freq/1Ghz", "cpu_freq/2Ghz", "hd_size/60Go", "hd_size/120Go", ) def stepCreateInvoiceTransactionRule(self, sequence, **kw) : """ Create some predicates in the Invoice Transaction Rule """ invoice_transaction_rule = getattr(self.getRuleTool(), 'default_invoice_transaction_simulation_rule') if invoice_transaction_rule.getValidationState() == 'validated': invoice_transaction_rule.invalidate() self.commit() # delete anything inside the rule first # clear the message queue, so that it does not contains unexistant paths self.tic() invoice_transaction_rule.deleteContent( [x for x in invoice_transaction_rule.objectIds()]) self.assertEquals(len(invoice_transaction_rule.objectValues()), 0) self.commit() # and add new content, predicate product_line predicate_product_notebook = invoice_transaction_rule.newContent( id = 'product_notebook', title = 'Product Notebook', portal_type = self.predicate_portal_type, string_index = 'product', int_index = '1', membership_criterion_base_category_list = ['product_line',], membership_criterion_category_list = ['product_line/storever/notebook'], ) predicate_product_barebone = invoice_transaction_rule.newContent( id = 'product_barebone', title = 'Product Barebone', portal_type = self.predicate_portal_type, string_index = 'product', int_index = '2', membership_criterion_base_category_list = ['product_line',], membership_criterion_category_list = ['product_line/storever/barebone'], ) # ... and destination_region predicate_region_france = invoice_transaction_rule.newContent( id = 'region_france', title = 'Region France', portal_type = self.predicate_portal_type, string_index = 'region', int_index ='1', membership_criterion_base_category_list = ['destination_region',], membership_criterion_category_list = ['destination_region/region/europe/west/france'], ) predicate_region_africa = invoice_transaction_rule.newContent( id = 'region_africa', title = 'region_africa', portal_type = self.predicate_portal_type, string_index = 'region', int_index = '2', membership_criterion_base_category_list = ['destination_region',], membership_criterion_category_list = ['destination_region/region/africa'], ) # sanity checks self.failUnless(predicate_product_notebook != None) self.failUnless(predicate_product_barebone != None) self.failUnless(predicate_region_france != None) self.failUnless(predicate_region_africa != None) predicate_list = invoice_transaction_rule.contentValues( filter = {'portal_type': self.predicate_portal_type}) self.assertEqual(len(predicate_list), 4) sequence.edit( invoice_transaction_rule = invoice_transaction_rule, predicate_product_notebook = predicate_product_notebook, predicate_product_barebone = predicate_product_barebone, predicate_region_france = predicate_region_france, predicate_region_africa = predicate_region_africa, ) def stepUpdateInvoiceTransactionRuleMatrix(self, sequence, **kw) : """Creates/updates the matrix of the sale invoice transaction rule """ invoice_transaction_rule = sequence.get('invoice_transaction_rule') # update the matrix, generates the accounting rule cells invoice_transaction_rule.edit() invoice_transaction_rule.updateMatrix() self.tic() # check the accounting rule cells inside the matrix cell_list = invoice_transaction_rule.contentValues( portal_type=self.accounting_rule_cell_portal_type) kw = dict( product_notebook_region_france_cell=invoice_transaction_rule.getCell( 'product_notebook', 'region_france', base_id='movement'), product_notebook_region_africa_cell=invoice_transaction_rule.getCell( 'product_notebook', 'region_africa', base_id='movement'), product_barebone_region_france_cell=invoice_transaction_rule.getCell( 'product_barebone', 'region_france', base_id='movement'), product_barebone_region_africa_cell=invoice_transaction_rule.getCell( 'product_barebone', 'region_africa', base_id='movement'), ) self.assertSameSet(cell_list, kw.values()) sequence.edit(**kw) def stepValidateInvoiceTransaction(self, sequence, **kw) : """validates the sale invoice transaction rule""" sequence.get('invoice_transaction_rule').validate() def stepCreateNotebookFranceCell(self, sequence, **kw): """ creates the content of product_notebook_region_france_cell """ # create content in the notebook / france cell product_notebook_region_france_cell = sequence.get( 'product_notebook_region_france_cell') product_notebook_region_france_cell_income = \ product_notebook_region_france_cell.newContent( id = 'income', source = sequence.get('income').getRelativeUrl(), quantity = 1) product_notebook_region_france_cell_receivable = \ product_notebook_region_france_cell.newContent( id = 'receivable', source = sequence.get('receivable').getRelativeUrl(), quantity = -1.196) product_notebook_region_france_cell_vat = \ product_notebook_region_france_cell.newContent( id = 'collected_vat', source = sequence.get('collected_vat').getRelativeUrl(), quantity = 0.196) sequence.edit( invoice_transaction_rule_cell = product_notebook_region_france_cell, product_notebook_region_france_cell_income = product_notebook_region_france_cell_income, product_notebook_region_france_cell_receivable = product_notebook_region_france_cell_receivable, product_notebook_region_france_cell_vat = product_notebook_region_france_cell_vat, ) def stepCreateBareboneFranceCell(self, sequence, **kw): """ creates the content of product_barebone_region_france_cell, the same as product_notebook_region_france_cell, but the income account is differrent """ # create content in the notebook / france cell product_barebone_region_france_cell = sequence.get( 'product_barebone_region_france_cell') product_barebone_region_france_cell_income = \ product_barebone_region_france_cell.newContent( id = 'income', source = sequence.get('income_barebone').getRelativeUrl(), quantity = 1) product_barebone_region_france_cell_receivable = \ product_barebone_region_france_cell.newContent( id = 'receivable', source = sequence.get('receivable').getRelativeUrl(), quantity = -1.196) product_barebone_region_france_cell_vat = \ product_barebone_region_france_cell.newContent( id = 'collected_vat', source = sequence.get('collected_vat').getRelativeUrl(), quantity = 0.196) sequence.edit( product_barebone_region_france_cell = product_barebone_region_france_cell, product_barebone_region_france_cell_income =\ product_barebone_region_france_cell_income, product_barebone_region_france_cell_vat =\ product_barebone_region_france_cell_vat, product_barebone_region_france_cell_receivable =\ product_barebone_region_france_cell_receivable ) def stepCreateAccounts(self, sequence, **kw): """ Create an income, an payable and a collected_vat account """ portal = self.getPortal() account_module = self.getAccountModule() if not hasattr(account_module, 'income') : income = account_module.newContent( id = "income", portal_type = self.account_portal_type, title = "Income Notebook", account_type = "income", ) income_barebone = account_module.newContent( id = "income_barebone", portal_type = self.account_portal_type, title = "Income Barebone", account_type = "income", ) receivable = account_module.newContent( id = "receivable", portal_type=self.account_portal_type, title = "Receivable", account_type = "asset/receivable", ) bank = account_module.newContent( id = "bank", portal_type=self.account_portal_type, title = "Bank", account_type = "asset/cash/bank", ) collected_vat = account_module.newContent( id = "collected_vat", portal_type=self.account_portal_type, title = "Collected VAT", account_type = "liability/payable/collected_vat", ) # store accounts in sequence object sequence.edit( income = account_module.income, income_barebone = account_module.income_barebone, receivable = account_module.receivable, bank = account_module.bank, collected_vat = account_module.collected_vat, ) def stepCreateEntities(self, sequence, **kw) : """ Create a vendor and a client organisation. The region of the client is the same as the region defined in the rule. """ organisation_module = self.getOrganisationModule() if not hasattr(organisation_module, 'vendor') : vendor = organisation_module.newContent( portal_type = self.organisation_portal_type, id = "vendor", title = "Vendor", region = "europe/west/france", ) self.assertNotEquals(vendor.getDefaultRegionValue(), None) client_fr = organisation_module.newContent( portal_type = self.organisation_portal_type, id = "client_fr", title = "French Client", region = "europe/west/france", ) self.assertNotEquals(client_fr.getDefaultRegionValue(), None) sequence.edit( vendor = organisation_module.vendor, client_fr = organisation_module.client_fr, client = organisation_module.client_fr, ) def stepCreateProducts(self, sequence, **kw) : """ Create 2 kind of products, a notebook (Varianted) and a barebone not varianted. """ product_module = self.getProductModule() if not hasattr(product_module, 'notebook') : # Create some products notebook = product_module.newContent( id = 'notebook', title = 'Notebook', portal_type = self.product_portal_type, product_line = 'storever/notebook', base_price = 3.0, ) # sets some variation categories on the notebook product notebook.setVariationBaseCategoryList(["hd_size", "cpu_freq"]) notebook.setVariationCategoryList([ "cpu_freq/1Ghz", "cpu_freq/2Ghz", "hd_size/60Go", "hd_size/120Go",]) barebone = product_module.newContent( id = 'barebone', title = 'Barebone', portal_type = self.product_portal_type, product_line = 'storever/barebone', base_price = 5.0, ) sequence.edit( notebook = product_module.notebook, barebone = product_module.barebone, product_notebook = product_module.notebook, product_barebone = product_module.barebone, ) def stepCreateCurrencies(self, sequence, **kw) : """ Create EUR currency """ currency_module = self.getCurrencyModule() if not hasattr(currency_module, 'EUR') : currency_module.newContent( id = 'EUR', title = 'Euro', portal_type = self.currency_portal_type, base_unit_quantity = .01, ) sequence.edit(euro=currency_module.EUR, currency=currency_module.EUR) def stepCreatePaymentRule(self, sequence, **kw) : """ create a rule payment transaction generation """ payment_rule = getattr(self.getRuleTool(), 'default_payment_simulation_rule') if payment_rule.getValidationState() == 'validated': payment_rule.invalidate() self.commit() # delete anything inside the rule first # clear the message queue, so that it does not contains unexistant paths self.tic() payment_rule.deleteContent( [x.getId() for x in payment_rule.objectValues( portal_type=['Predicate', self.accounting_rule_cell_portal_type])]) self.commit() # and add a new predicate payment_rule.newContent( id = 'all', title = 'all', portal_type = self.predicate_portal_type, string_index = 'all', int_index = '1', ) sequence.edit(payment_rule=payment_rule) def stepUpdatePaymentRuleMatrix(self, sequence, **kw) : """Creates/updates the matrix of the sale invoice transaction rule """ payment_rule = sequence.get('payment_rule') # update the matrix, generates the accounting rule cells payment_rule.edit() payment_rule.updateMatrix() self.tic() # check the accounting rule cells inside the matrix cell_list = payment_rule.contentValues( filter = {'portal_type':self.accounting_rule_cell_portal_type}) self.assertEqual(len(cell_list), 1) cell = cell_list[0] for line_id, line_source_id, line_destination_id in \ self.payment_transaction_line_definition_list: line = cell.newContent(id=line_id, portal_type='Accounting Transaction Line', source=line_source_id, destination=line_destination_id) payment_rule.validate() self.tic() def stepCreateEmptyInvoice(self, sequence, **kw) : """ Create an empty invoice that will be modified later """ vendor = sequence.get('vendor') client = sequence.get('client') currency = sequence.get('currency') empty_invoice = self.getAccountingModule().newContent( id = 'empty_invoice', portal_type = self.invoice_portal_type, resource = currency.getRelativeUrl(), stop_date = DateTime(2004, 01, 01), start_date = DateTime(2004, 01, 01), source_section = vendor.getRelativeUrl(), destination_section = client.getRelativeUrl(), created_by_builder = 1, ) empty_invoice.setPaymentConditionEfficiency(1.0) sequence.edit( simple_invoice = empty_invoice, invoice = empty_invoice, ) def stepCreateSimpleInvoice(self, sequence, **kw) : """ creates a simple sale invoice for non varianted notebook product. The invoice is from `vendor` to `client_fr`, so the cell defined in stepUpdateInvoiceTransactionRuleMatrix should match. This invoice containts one line, 10 notebook * 10 EUR, so total price is 100 """ vendor = sequence.get('vendor') client = sequence.get('client') product_notebook = sequence.get('product_notebook') currency = sequence.get('currency') simple_invoice = self.getAccountingModule().newContent( id = 'simple_invoice', portal_type = self.invoice_portal_type, resource = currency.getRelativeUrl(), price_currency = currency.getRelativeUrl(), stop_date = DateTime(2004, 01, 01), start_date = DateTime(2004, 01, 01), source_section = vendor.getRelativeUrl(), destination_section = client.getRelativeUrl(), created_by_builder = 1, ) invoice_line = simple_invoice.newContent( id = 'invoice_line', resource = product_notebook.getRelativeUrl(), quantity = 10, price = 10, portal_type = self.invoice_line_portal_type) simple_invoice.setPaymentConditionEfficiency(1.0) self.assertEqual(invoice_line.getTotalPrice(), 100) sequence.edit( simple_invoice = simple_invoice, invoice = simple_invoice, invoice_line = invoice_line, invoice_lines = [invoice_line] ) def stepCreateOtherSimpleInvoice(self, sequence, **kw) : """ creates a simple sale invoice for non varianted notebook product. It will contain one line that will later be changed. """ vendor = sequence.get('vendor') client = sequence.get('client') product_notebook = sequence.get('product_notebook') currency = sequence.get('currency') simple_invoice = self.getAccountingModule().newContent( id = 'other_simple_invoice', portal_type = self.invoice_portal_type, resource = currency.getRelativeUrl(), stop_date = DateTime(2004, 01, 01), start_date = DateTime(2004, 01, 01), source_section = vendor.getRelativeUrl(), destination_section = client.getRelativeUrl(), created_by_builder = 1, ) invoice_line = simple_invoice.newContent( id = 'invoice_line', resource = product_notebook.getRelativeUrl(), quantity = 123, price = 456, portal_type = self.invoice_line_portal_type) simple_invoice.setPaymentConditionEfficiency(1.0) sequence.edit( simple_invoice = simple_invoice, invoice = simple_invoice, invoice_line = invoice_line, invoice_lines = [invoice_line] ) def stepAddInvoiceLine(self, sequence, **kw) : """ add an invoice line in the current invoice : 10 notebook * 10 EUR, so total price is 100 """ product_notebook = sequence.get('product_notebook') invoice = sequence.get('invoice') invoice_line = invoice.newContent( id = 'invoice_line_%s'%(int(random.random()*1000)), portal_type = self.invoice_line_portal_type) invoice_line.edit( resource = product_notebook.getRelativeUrl(), quantity = 10, price = 10 ) self.assertEqual(invoice_line.getTotalPrice(), 100) sequence.edit( invoice_line = invoice_line, invoice_lines = [invoice_line] ) def stepEditInvoiceLine(self, sequence, **kw) : """ edit the invoice line : 10 notebook * 10 EUR, so total price is 100 """ invoice = sequence.get('invoice') invoice_line = sequence.get('invoice_line') invoice_line.edit( quantity = 10, price = 10) self.assertEqual(invoice_line.getTotalPrice(), 100) def stepDeleteInvoiceLine(self, sequence, **kw) : """ remove an invoice line from the invoice """ invoice = sequence.get('invoice') invoice_line = sequence.get('invoice_line') invoice._delObject(invoice_line.getId()) invoice.recursiveReindexObject() def stepCreateSimpleInvoiceTwoLines(self, sequence, **kw) : """ similar to stepCreateSimpleInvoice, but replace "10 notebook * 10 EUR, so total price is 100" by : "5 notebook * 10 EUR + 5 notebook * 10 EUR , so total price is 100" """ vendor = sequence.get('vendor') client = sequence.get('client') product_notebook = sequence.get('product_notebook') currency = sequence.get('currency') simple_invoice = self.getAccountingModule().newContent( id = 'simple_invoice_two_lines', portal_type = self.invoice_portal_type, resource = currency.getRelativeUrl(), stop_date = DateTime(2004, 01, 01), start_date = DateTime(2004, 01, 01), source_section = vendor.getRelativeUrl(), destination_section = client.getRelativeUrl(), created_by_builder = 1, ) invoice_line1 = simple_invoice.newContent( id = 'invoice_line1', resource = product_notebook.getRelativeUrl(), quantity = 5, price = 10, portal_type = self.invoice_line_portal_type) invoice_line2 = simple_invoice.newContent( id = 'invoice_line2', REsource = product_notebook.getRelativeUrl(), quantity = 5, price = 10, portal_type = self.invoice_line_portal_type) simple_invoice.setPaymentConditionEfficiency(1.0) self.assertEqual(invoice_line1.getTotalPrice() + invoice_line2.getTotalPrice(), 100) sequence.edit( simple_invoice = simple_invoice, invoice = simple_invoice, invoice_lines = [invoice_line1, invoice_line2] ) def stepCreateSimpleInvoiceTwoCells(self, sequence, **kw) : """ similar to stepCreateSimpleInvoiceTwoLines, but use two differents cells on the same line instead of two differents lines. """ vendor = sequence.get('vendor') client = sequence.get('client') product_notebook = sequence.get('product_notebook') currency = sequence.get('currency') simple_invoice = self.getAccountingModule().newContent( id = 'simple_invoice_two_cells', portal_type = self.invoice_portal_type, resource = currency.getRelativeUrl(), stop_date = DateTime(2004, 01, 01), start_date = DateTime(2004, 01, 01), source_section = vendor.getRelativeUrl(), destination_section = client.getRelativeUrl(), created_by_builder = 1, ) invoice_line = simple_invoice.newContent( id = 'invoice_line', resource = product_notebook.getRelativeUrl(), portal_type = self.invoice_line_portal_type) simple_invoice.setPaymentConditionEfficiency(1.0) sequence.edit( simple_invoice = simple_invoice, invoice = simple_invoice, invoice_line = invoice_line, invoice_lines = [invoice_line] ) self.stepAddCellsInInvoiceLine(sequence) def stepAddCellsInInvoiceLine(self, sequence, **kw): """ add 2 cells in the invoice line, same quantity as simple invoice """ invoice_line = sequence.get('invoice_line') # initialy, the line must not contain cells self.assertEqual(len(invoice_line.objectIds()), 0) invoice_line._setVariationBaseCategoryList(['hd_size', 'cpu_freq']) invoice_line._setVariationCategoryList( ['hd_size/60Go', 'hd_size/120Go', 'cpu_freq/1Ghz' ]) base_id = 'movement' invoice_line.updateCellRange(base_id) cell_key_list = list(invoice_line.getCellKeyList(base_id = base_id)) # this is probably not the easiest way to create cells ... price = 10 quantity = 5 for cell_key in cell_key_list: cell = invoice_line.newCell(base_id = base_id, portal_type = self.invoice_cell_portal_type, *cell_key) cell.edit(mapped_value_property_list = ['price','quantity'], price = price, quantity = quantity, predicate_category_list = cell_key, variation_category_list = cell_key) # getTotalPrice uses mysql, so we must make sure the invoice is cataloged # to have correct results invoice_line.getParentValue().recursiveImmediateReindexObject() self.assertEqual(invoice_line.getTotalPrice(), 100) self.assertEqual(invoice_line.getTotalQuantity(), 10) # then we must have 2 cells inside our line self.assertEqual(len(invoice_line.objectIds()), 2) def stepCreateMultiLineInvoice(self, sequence, **kw) : """ create an invoice with varianted products The invoice is from `vendor` to `client_fr`, so the cell defined in This invoice containts two lines : 10 notebook * 10 EUR, so total price is 100 (matched by product_notebook_region_france_cell) 10 barebone * 100 EUR, so total price is 1000 (matched by product_notebook_region_france_cell) total price for the invoice is 100 + 1000 """ vendor = sequence.get('vendor') client = sequence.get('client') product_notebook = sequence.get('product_notebook') product_barebone = sequence.get('product_barebone') currency = sequence.get('currency') multi_line_invoice = self.getAccountingModule().newContent( id = 'multi_line_invoice', portal_type = self.invoice_portal_type, resource = currency.getRelativeUrl(), price_currency = currency.getRelativeUrl(), stop_date = DateTime(2004, 01, 01), start_date = DateTime(2004, 01, 01), source_section = vendor.getRelativeUrl(), destination_section = client.getRelativeUrl(), created_by_builder = 1, ) notebook_line = multi_line_invoice.newContent( portal_type = self.invoice_line_portal_type, id = 'notebook_line', resource = product_notebook.getRelativeUrl(), quantity = 10, price = 10) barebone_line = multi_line_invoice.newContent( portal_type = self.invoice_line_portal_type, id = 'barebone_line', resource = product_barebone.getRelativeUrl(), quantity = 10, price = 100) multi_line_invoice.setPaymentConditionEfficiency(1.0) self.assertEqual( 10*10 + 10*100, notebook_line.getTotalPrice() + barebone_line.getTotalPrice()) sequence.edit( multi_line_invoice = multi_line_invoice, invoice = multi_line_invoice, invoice_lines = [notebook_line, barebone_line], ) def stepCheckAddPredicate(self, sequence, **kw) : invoice_transaction_rule = sequence.get('invoice_transaction_rule') # next, we add a predicate to see if it is still okay (3x2 cells) predicate_product3 = invoice_transaction_rule.newContent( id = 'product_3', title = 'product_3', portal_type = self.predicate_portal_type, string_index = 'product', int_index = '3', membership_criterion_base_category_list = ['product_line',], membership_criterion_category_list = ['product_line/storever/openbrick'], ) invoice_transaction_rule.updateMatrix() self.tic() cell_list = invoice_transaction_rule.contentValues( filter = {'portal_type': self.accounting_rule_cell_portal_type}) self.assertEqual(len(cell_list), 6) def stepCheckRemovePredicate(self, sequence, **kw) : invoice_transaction_rule = sequence.get('invoice_transaction_rule') self.tic() # make sure message queue is empty # then, we remove a predicate and check again (3x3 cells) invoice_transaction_rule.deleteContent(id = 'region_africa') invoice_transaction_rule.updateMatrix() cell_list = invoice_transaction_rule.contentValues( filter = {'portal_type':self.accounting_rule_cell_portal_type}) self.assertEqual(len(cell_list), 3) def stepCheckRestoreOriginalPredicates(self, sequence, **kw) : """ we put back the matrix in the original format (2x2 cells) """ invoice_transaction_rule = sequence.get("invoice_transaction_rule") self.tic() # make sure message queue is empty invoice_transaction_rule.deleteContent(id='product_3') predicate_region_africa = invoice_transaction_rule.newContent( id = 'region_africa', title = 'Region Africa', portal_type = self.predicate_portal_type, string_index = 'region', int_index = '2', membership_criterion_base_category_list = ['destination_region',], membership_criterion_category_list = ['destination_region/region/africa'], ) invoice_transaction_rule.updateMatrix() cell_list = invoice_transaction_rule.contentValues( filter = {'portal_type':self.accounting_rule_cell_portal_type}) self.assertEqual(len(cell_list), 4) def stepCreateDummyInvoice(self, sequence, **kw) : """ Create a dummy invoice for temp movements """ invoice = self.getAccountingModule().newContent( id = "dummy_invoice", ) sequence.edit(invoice = invoice) def stepCreateMatchableInvoiceMovements(self, sequence, **kw) : """ Create a temp movement that will be matched by the default_invoice_transaction_rule """ from Products.ERP5Type.Document import newTempMovement product_notebook_region_france_movement = newTempMovement( sequence.get('invoice'), 'test1', resource = sequence.get('notebook').getRelativeUrl(), destination = sequence.get('client_fr').getRelativeUrl(), ) product_barebone_region_france_movement = newTempMovement( sequence.get('invoice'), 'test2', resource = sequence.get('barebone').getRelativeUrl(), destination = sequence.get('client_fr').getRelativeUrl(), ) sequence.edit( product_notebook_region_france_movement = product_notebook_region_france_movement , product_barebone_region_france_movement = product_barebone_region_france_movement , ) def stepCheckMatchableInvoiceMovements(self, sequence, **kw) : """ Check that we have a matching cell for the movement """ invoice_transaction_rule = sequence.get("invoice_transaction_rule") product_barebone_region_france_movement = sequence.get( 'product_barebone_region_france_movement') product_notebook_region_france_movement = sequence.get( 'product_notebook_region_france_movement') # Make sure acquisition is working for destination_region self.assertEqual( product_barebone_region_france_movement.getDestinationRegion(), 'region/europe/west/france') self.assertEqual( product_notebook_region_france_movement.getDestinationRegion(), 'region/europe/west/france') # Make sure category is working for resource self.assertEqual(product_barebone_region_france_movement.getProductLine(), 'storever/barebone') self.assertEqual(product_notebook_region_france_movement.getProductLine(), 'storever/notebook') # check the predicates predicate_product_notebook = sequence.get("predicate_product_notebook") predicate_product_barebone = sequence.get("predicate_product_barebone") predicate_region_france = sequence.get("predicate_region_france") predicate_region_africa = sequence.get("predicate_region_africa") self.assert_(not predicate_region_africa.test( product_barebone_region_france_movement )) self.assert_( predicate_region_france.test( product_barebone_region_france_movement )) self.assert_(not predicate_product_notebook.test( product_barebone_region_france_movement )) self.assert_( predicate_product_barebone.test( product_barebone_region_france_movement )) self.assert_(not predicate_region_africa.test( product_notebook_region_france_movement )) self.assert_( predicate_region_france.test( product_notebook_region_france_movement )) self.assert_(not predicate_product_barebone.test( product_notebook_region_france_movement )) self.assert_( predicate_product_notebook.test( product_notebook_region_france_movement )) # check the cells product_notebook_region_france_cell = sequence.get( 'product_notebook_region_france_cell') product_barebone_region_france_cell = sequence.get( 'product_barebone_region_france_cell') product_notebook_region_africa_cell = sequence.get( 'product_notebook_region_africa_cell') product_barebone_region_africa_cell = sequence.get( 'product_barebone_region_africa_cell') self.assert_(not product_notebook_region_france_cell.test( product_barebone_region_france_movement )) self.assert_( product_barebone_region_france_cell.test( product_barebone_region_france_movement )) self.assert_(not product_notebook_region_africa_cell.test( product_barebone_region_france_movement )) self.assert_(not product_barebone_region_africa_cell.test( product_barebone_region_france_movement )) self.assert_( product_notebook_region_france_cell.test( product_notebook_region_france_movement )) self.assert_(not product_barebone_region_france_cell.test( product_notebook_region_france_movement )) self.assert_(not product_notebook_region_africa_cell.test( product_notebook_region_france_movement )) self.assert_(not product_barebone_region_africa_cell.test( product_notebook_region_france_movement )) # finally check the matching cell is the good one self.assertEquals(product_notebook_region_france_cell, invoice_transaction_rule._getMatchingCell( product_notebook_region_france_movement )) self.assertEqual(product_barebone_region_france_cell, invoice_transaction_rule._getMatchingCell( product_barebone_region_france_movement )) def stepCreateNotMatchableInvoiceMovements(self, sequence, **kw) : """ create a temp movement that not any cell could match. """ from Products.ERP5Type.Document import newTempMovement bad_movement1 = newTempMovement( sequence.get("invoice"), 'test3', product = None, destination = sequence.get('client').getRelativeUrl(), ) bad_movement2 = newTempMovement( sequence.get("invoice"), 'test4', gap = 'gap/1', destination = sequence.get('client').getRelativeUrl(), ) sequence.edit( bad_movement1 = bad_movement1, bad_movement2 = bad_movement2, ) def stepCheckNotMatchableInvoiceMovements(self, sequence, **kw) : """ check that temp movement that cannot be matched is not matched. """ invoice_transaction_rule = sequence.get('invoice_transaction_rule') self.assertEqual(None, invoice_transaction_rule._getMatchingCell( sequence.get('bad_movement1'))) self.assertEqual(None, invoice_transaction_rule._getMatchingCell( sequence.get('bad_movement2'))) def stepClearSimulation(self, sequence, **kw) : """ clear the content of portal_simulation """ self.tic() # make sure message queue is empty self.getSimulationTool().deleteContent( list(self.getSimulationTool().objectIds())) def stepClearAccountingModule(self, sequence, **kw) : """ clear the content of accounting module """ self.tic() # make sure message queue is empty # delete self.getAccountingModule().deleteContent( list(self.getAccountingModule().objectIds())) def stepCheckFirstRuleIsApplied(self, sequence, **kw) : """ check every level of the simulation """ invoice = sequence.get('invoice') invoice_line = sequence.get('invoice_line') invoice_transaction_rule = sequence.get('invoice_transaction_rule') vendor = sequence.get('vendor') client = sequence.get('client') currency = sequence.get('currency') invoice_transaction_rule_cell = sequence.get( 'invoice_transaction_rule_cell') # content of the simulation tool is a list of invoice rules applied_rule_list = self.getSimulationTool().contentValues() self.assertEqual(len(applied_rule_list), 1) applied_rule = applied_rule_list[0] self.assertEqual( applied_rule.getPortalType(), self.applied_rule_portal_type) self.assertEqual( applied_rule.getSpecialiseReference(), 'default_invoice_rule') self.assertEqual( applied_rule.getCausality(), invoice.getRelativeUrl()) # inside the rule there are simulation movements simulation_movement_list = applied_rule.contentValues() # the first one is a mirror of the movement in the invoice line # this applied rule can also contain movement related to those # created in the init script, so we only take into account sim. # movement linked to an invoice line invoice_line_simulation_movement_list = [] for simulation_movement in simulation_movement_list : self.assertNotEquals(simulation_movement.getDeliveryValue(), None) if simulation_movement.getDeliveryValue().getPortalType() == \ self.invoice_line_portal_type : invoice_line_simulation_movement_list.append(simulation_movement) self.assertEqual( len(invoice_line_simulation_movement_list), 1) simulation_movement = invoice_line_simulation_movement_list[0] self.assertEqual( simulation_movement.getPortalType(), self.simulation_movement_portal_type) self.assertEqual( invoice_line.getResource(), simulation_movement.getResource()) self.assertEqual( invoice_line.getQuantity(), simulation_movement.getQuantity()) self.assertEqual( invoice_line.getStopDate(), simulation_movement.getStopDate()) self.assertEqual( invoice_line.getStartDate(), simulation_movement.getStartDate()) self.assertEqual( invoice_line.getSourceSection(), simulation_movement.getSourceSection()) self.assertEqual( invoice_line.getDestinationSection(), simulation_movement.getDestinationSection()) self.assertEqual( invoice_line.getSource(), simulation_movement.getSource()) self.assertEqual( invoice_line.getDestination(), simulation_movement.getDestination()) # inside this movement there are applied rules which specialize # invoice_transaction_rule and trade_model_rule... applied_rule_list = simulation_movement.contentValues() self.assertEquals( len(applied_rule_list), 2) # ...but only invoice_transaction_rule is interesting applied_rule = [applied_rule for applied_rule in applied_rule_list if applied_rule.getSpecialiseValue().getPortalType() == 'Invoice Transaction Simulation Rule'][0] self.assertEquals( applied_rule.getPortalType(), self.applied_rule_portal_type) self.assertEquals( applied_rule.getSpecialise(), invoice_transaction_rule.getRelativeUrl()) # and in this applied rule, we got simulation movements, # based on those inside product_notebook_region_france_cell simulation_movement_list = applied_rule.contentValues() self.assertEqual( len(simulation_movement_list), 3) rule_movement_found = {} for simulation_movement in simulation_movement_list : self.assertEquals( simulation_movement.getSourceSection(), vendor.getRelativeUrl()) self.assertEquals( simulation_movement.getDestinationSection(), client.getRelativeUrl()) self.assertEquals( simulation_movement.getResource(), currency.getRelativeUrl()) self.assertEquals( simulation_movement.getCausalityState(), 'expanded') for rule_movement in invoice_transaction_rule_cell.contentValues() : if simulation_movement.getSource() == rule_movement.getSource() : rule_movement_found[rule_movement.getSource()] = 1 self.assertEquals(simulation_movement.getQuantity(), rule_movement.getQuantity() * invoice_line.getTotalPrice()) self.assertEquals(simulation_movement.getSourceCredit(), rule_movement.getSourceCredit() * invoice_line.getTotalPrice()) self.assertEquals(simulation_movement.getSourceDebit(), rule_movement.getSourceDebit() * invoice_line.getTotalPrice()) self.assert_(len(rule_movement_found.keys()), 3) sequence.edit( simulation_movement_list = simulation_movement_list ) def stepCollectSimulationMovements(self, sequence, **kw) : """ put some simulation movements in sequence for later checkings """ invoice = sequence.get('invoice') invoice_line = sequence.get('invoice_line') applied_rule_list = self.getSimulationTool().contentValues() self.assertEquals(len(applied_rule_list), 1) simulation_movement_list = [] simulation_movement_quantities = {} simulation_movement_resources = {} simulation_movement_paths = {} simulation_movement_section_paths = {} applied_rule = applied_rule_list[0] for invoice_simulation_movement in applied_rule.objectValues() : for invoice_transaction_applied_rule in \ invoice_simulation_movement.objectValues() : for simulation_movement in \ invoice_transaction_applied_rule.objectValues() : path = simulation_movement.getPath() simulation_movement_list.append(simulation_movement) simulation_movement_quantities[path] = \ simulation_movement.getQuantity() simulation_movement_resources[path] = \ simulation_movement.getResource() simulation_movement_paths[path] = ( simulation_movement.getSource(), simulation_movement.getDestination()) simulation_movement_section_paths[path] = ( simulation_movement.getSourceSection(), simulation_movement.getDestinationSection()) sequence.edit( simulation_movement_list = simulation_movement_list , simulation_movement_quantities = simulation_movement_quantities , simulation_movement_resources = simulation_movement_resources , simulation_movement_paths = simulation_movement_paths , simulation_movement_section_paths = simulation_movement_section_paths ) def stepCheckSimulationMovements(self, sequence, **kw) : """ checks simulation movements from the sequence object """ simulation_movement_list = sequence.get( 'simulation_movement_list') simulation_movement_quantities = sequence.get( 'simulation_movement_quantities') simulation_movement_resources = sequence.get( 'simulation_movement_resources') simulation_movement_paths = sequence.get( 'simulation_movement_paths') simulation_movement_section_paths = sequence.get( 'simulation_movement_section_paths') for simulation_movement in simulation_movement_list : path = simulation_movement.getPath() self.assertEquals( simulation_movement.getQuantity(), simulation_movement_quantities[path] ) self.assertEquals( simulation_movement.getResource(), simulation_movement_resources[path] ) self.assertEquals( (simulation_movement.getSource(), simulation_movement.getDestination()), simulation_movement_paths[path] ) self.assertEquals( ( simulation_movement.getSourceSection(), simulation_movement.getDestinationSection()), simulation_movement_section_paths[path] ) def stepCheckPaymentRuleIsApplied(self, sequence, **kw) : """ checks that a payment rule is applied for the total amount of receivable """ invoice_line = sequence.get('invoice_line') simulation_movement = invoice_line.getDeliveryRelatedValue() applied_rule_list = simulation_movement.objectValues() # Invoice Transaction Rule and Trade Model Rule self.assertEquals(2, len(applied_rule_list)) applied_rule = [x for x in applied_rule_list \ if x.getSpecialiseReference() == \ 'default_invoice_transaction_rule'][0] simulation_movement_list = [x for x in applied_rule.objectValues() \ if x.getSourceTitle() in ('Receivable', 'Payable')] self.assertEquals(1, len(simulation_movement_list)) simulation_movement = simulation_movement_list[0] applied_rule_list = simulation_movement.objectValues() self.assertEquals(1, len(applied_rule_list)) payment_applied_rule = applied_rule_list[0] def stepPlanInvoice(self, sequence, **kw) : """ put the invoice in the `planned` state, which will start the simulation process. """ invoice = sequence.get('invoice') self.getPortal().portal_workflow.doActionFor( invoice, 'plan_action', skip_period_validation=1 ) self.assertEquals(invoice.getSimulationState(), 'planned') def stepConfirmInvoice(self, sequence, **kw) : """ put the invoice in the `confirmed` state, which does nothing specific, the delivery builder is invoked when starting the invoice. """ invoice = sequence.get('invoice') self.getPortal().portal_workflow.doActionFor( invoice, 'confirm_action', wf_id = 'accounting_workflow', skip_period_validation = 1 ) self.assertEquals(invoice.getSimulationState(), 'confirmed') def stepCheckNoAccountingLinesBuiltYet(self, sequence, **kw) : invoice = sequence.get('invoice') self.assertEquals(0, len(invoice.getMovementList( portal_type=invoice.getPortalAccountingMovementTypeList()))) def stepStartInvoice(self, sequence, **kw) : """ put the invoice in the `started` state, which starts the delivery builder. """ invoice = sequence.get('invoice') self.getPortal().portal_workflow.doActionFor( invoice, 'start_action', wf_id = 'accounting_workflow', skip_period_validation = 1 ) self.assertEquals(invoice.getSimulationState(), 'started') def stepCheckAccountingLinesCoherantWithSimulation(self, sequence, **kw) : """ checks that accounting lines are created on the sale invoice transaction """ invoice = sequence.get('invoice') vendor = sequence.get('vendor') client = sequence.get('client') currency = sequence.get('currency') invoice_line = sequence.get('invoice_line') simulation_movement_list = sequence.get('simulation_movement_list') invoice_transaction_rule_cell = sequence.get( 'invoice_transaction_rule_cell') invoice_transaction_line_list = invoice.contentValues( filter = {'portal_type': self.invoice_transaction_line_portal_type}) self.assertEquals( len(invoice_transaction_line_list), len(simulation_movement_list)) simulation_movement_found = {} for invoice_transaction_line in invoice_transaction_line_list : self.assertEquals( invoice_transaction_line.getSourceSection(), vendor.getRelativeUrl()) self.assertEquals( invoice_transaction_line.getDestinationSection(), client.getRelativeUrl()) self.assertEquals( invoice_transaction_line.getResource(), currency.getRelativeUrl()) for simulation_movement in simulation_movement_list : if simulation_movement.getSource() == \ invoice_transaction_line.getSource() : simulation_movement_found[simulation_movement.getSource()] = 1 self.assertEquals(simulation_movement.getQuantity(), invoice_transaction_line.getQuantity()) self.assertEquals(simulation_movement.getSourceCredit(), invoice_transaction_line.getSourceCredit()) self.assertEquals(simulation_movement.getSourceDebit(), invoice_transaction_line.getSourceDebit()) self.assertEquals(simulation_movement.getDelivery(), invoice_transaction_line.getRelativeUrl()) self.assert_(len(simulation_movement_found.keys()), 3) def stepCheckAccountingLinesCreatedForSimpleInvoice( self, sequence, **kw) : """ Checks that accounting lines are created on the sale invoice transaction and that all movement are correctly aggregated. The price of the invoice was 100, it should result in the following accounting layout : =============== ======= ======= account Debit Credit =============== ======= ======= income 100 collected_vat 19.60 receivable 119.60 =============== ======= ======= """ invoice = sequence.get('invoice') vendor = sequence.get('vendor') client = sequence.get('client') currency = sequence.get('currency') invoice_lines = sequence.get('invoice_lines') invoice_transaction_line_list = invoice.contentValues( filter = {'portal_type': self.invoice_transaction_line_portal_type}) self.assertEquals(len(invoice_transaction_line_list), 3) accounting_lines_layout = { 'income' : (0, 100), 'collected_vat' : (0, 19.60), 'receivable' : (119.60, 0), } for invoice_transaction_line in invoice_transaction_line_list : self.assert_( invoice_transaction_line.getSourceId() in accounting_lines_layout.keys(), 'unexepected source_id %s' % invoice_transaction_line.getSourceId()) debit, credit = accounting_lines_layout[ invoice_transaction_line.getSourceId()] self.assertEquals(debit, invoice_transaction_line.getSourceDebit()) self.assertEquals(credit, invoice_transaction_line.getSourceCredit()) self.assertNotEquals( len(invoice_transaction_line.getDeliveryRelatedValueList( portal_type='Simulation Movement')), 0) def stepCheckPaymentLinesCreatedForSimpleInvoice( self, sequence, **kw) : """ Checks that payment transaction lines are created on the payment transaction and that all movement are correctly aggregated. The price of the invoice was 100, it should result in the following accounting layout : =============== ======= ======= account Debit Credit =============== ======= ======= receivable 119.60 bank 119.60 =============== ======= ======= """ payment = sequence.get('invoice').getCausalityRelatedValue( portal_type='Payment Transaction') payment_transaction_line_list = payment.contentValues( filter = {'portal_type': self.payment_transaction_line_portal_type}) self.assertEquals(len(payment_transaction_line_list), 2) accounting_lines_layout = { 'receivable' : (0, 119.60), 'bank' : (119.60, 0), } for payment_transaction_line in payment_transaction_line_list : if _isMirrored(payment_transaction_line): self.assert_( payment_transaction_line.getDestinationId() in accounting_lines_layout.keys(), 'unexepected destination_id %s' % payment_transaction_line.getDestinationId()) debit, credit = accounting_lines_layout[ payment_transaction_line.getDestinationId()] self.assertEquals(debit, payment_transaction_line.getDestinationDebit()) self.assertEquals(credit, payment_transaction_line.getDestinationCredit()) self.assertNotEquals( len(payment_transaction_line.getDeliveryRelatedValueList( portal_type='Simulation Movement')), 0) else: self.assert_( payment_transaction_line.getSourceId() in accounting_lines_layout.keys(), 'unexepected source_id %s' % payment_transaction_line.getSourceId()) debit, credit = accounting_lines_layout[ payment_transaction_line.getSourceId()] self.assertEquals(debit, payment_transaction_line.getSourceDebit()) self.assertEquals(credit, payment_transaction_line.getSourceCredit()) self.assertNotEquals( len(payment_transaction_line.getDeliveryRelatedValueList( portal_type='Simulation Movement')), 0) def stepCheckAccountingLinesCreatedForMultiLineInvoice( self, sequence, **kw) : """ Checks that accounting lines are created on the sale invoice transaction and that all movement are correctly aggregated. The price of the invoice was 1100, it should result in the following accounting layout : =============== ======= ======= account Debit Credit =============== ======= ======= income 100 income_barebone 1000 collected_vat 215.60 receivable 1315.60 =============== ======= ======= """ invoice = sequence.get('invoice') vendor = sequence.get('vendor') client = sequence.get('client') currency = sequence.get('currency') invoice_lines = sequence.get('invoice_lines') invoice_transaction_line_list = invoice.contentValues( filter = {'portal_type': self.invoice_transaction_line_portal_type}) self.assertEquals(len(invoice_transaction_line_list), 4) accounting_lines_layout = { 'income' : (0, 100), 'income_barebone' : (0, 1000), 'collected_vat' : (0, 215.60), 'receivable' : (1315.60, 0), } for invoice_transaction_line in invoice_transaction_line_list : debit, credit = accounting_lines_layout[ invoice_transaction_line.getSourceId()] self.assertEquals(debit, invoice_transaction_line.getSourceDebit()) self.assertEquals(credit, invoice_transaction_line.getSourceCredit()) self.assertNotEquals( len(invoice_transaction_line.getDeliveryRelatedValueList( portal_type='Simulation Movement')), 0) def stepCheckPaymentLinesCreatedForMultiLineInvoice( self, sequence, **kw) : """ Checks that payment transaction lines are created on the payment transaction and that all movement are correctly aggregated. The price of the invoice was 100, it should result in the following accounting layout : =============== ======= ======= account Debit Credit =============== ======= ======= receivable 1315.60 bank 1315.60 =============== ======= ======= """ payment = sequence.get('invoice').getCausalityRelatedValue( portal_type='Payment Transaction') payment_transaction_line_list = payment.contentValues( filter = {'portal_type': self.payment_transaction_line_portal_type}) self.assertEquals(len(payment_transaction_line_list), 2) accounting_lines_layout = { 'receivable' : (0, 1315.60), 'bank' : (1315.60, 0), } for payment_transaction_line in payment_transaction_line_list : if _isMirrored(payment_transaction_line): self.assert_( payment_transaction_line.getDestinationId() in accounting_lines_layout.keys(), 'unexepected source_id %s' % payment_transaction_line.getDestinationId()) debit, credit = accounting_lines_layout[ payment_transaction_line.getDestinationId()] self.assertEquals(debit, payment_transaction_line.getDestinationDebit()) self.assertEquals(credit, payment_transaction_line.getDestinationCredit()) self.assertNotEquals( len(payment_transaction_line.getDeliveryRelatedValueList( portal_type='Simulation Movement')), 0) else: self.assert_( payment_transaction_line.getSourceId() in accounting_lines_layout.keys(), 'unexepected source_id %s' % payment_transaction_line.getSourceId()) debit, credit = accounting_lines_layout[ payment_transaction_line.getSourceId()] self.assertEquals(debit, payment_transaction_line.getSourceDebit()) self.assertEquals(credit, payment_transaction_line.getSourceCredit()) self.assertNotEquals( len(payment_transaction_line.getDeliveryRelatedValueList( portal_type='Simulation Movement')), 0) def stepRebuildAndCheckNothingIsCreated(self, sequence, **kw) : """ Calls the DeliveryBuilder again and checks that the accounting module remains unchanged. """ accounting_transaction_count = len(self.getAccountingModule().objectIds()) accounting_lines_dict = {} for transaction in self.getAccountingModule().objectValues(): transaction_dict = {} for accounting_line in transaction.objectValues() : if accounting_line.getPortalType() != \ self.invoice_line_portal_type : transaction_dict[accounting_line.getId()] = \ accounting_line.getTotalQuantity() accounting_lines_dict[transaction.getId()] = transaction_dict # reindex the simulation for testing purposes self.getSimulationTool().recursiveReindexObject() self.tic() delivery_tool = self.getPortal().portal_deliveries # and build again ... delivery_tool.sale_invoice_transaction_builder.build() delivery_tool.purchase_invoice_transaction_builder.build() delivery_tool.payment_transaction_builder.build() if hasattr(delivery_tool, 'pay_sheet_transaction_builder') : # TODO: conflict with pay_sheet_transaction_builder must be tested too delivery_tool.pay_sheet_transaction_builder.build() self.tic() # nothing should have changed self.assertEquals(accounting_transaction_count, len(self.getAccountingModule().objectIds())) for transaction in self.getAccountingModule().objectValues() : transaction_dict = accounting_lines_dict[transaction.getId()] for accounting_line in transaction.objectValues() : if accounting_line.getPortalType() != \ self.invoice_line_portal_type : self.assertEquals( transaction_dict[accounting_line.getId()], accounting_line.getTotalQuantity()) def stepCheckInvoiceSimulation(self, sequence=None, \ sequence_list=None, **kw): invoice = sequence.get('invoice') applied_rule_list = invoice.getCausalityRelatedValueList( portal_type = self.applied_rule_portal_type, ) self.assertEquals( 1, len(applied_rule_list) ) applied_rule = applied_rule_list[0] self.assertEquals( 'default_invoice_rule', applied_rule.getSpecialiseReference() ) self.assertEquals( # reduntant? invoice.getRelativeUrl(), applied_rule.getCausality() ) simulation_movement_list = applied_rule.objectValues() self.assertEquals( 1, len(simulation_movement_list) ) simulation_movement = simulation_movement_list[0] invoice_line = sequence.get('invoice_line') resource = sequence.get('product_notebook') vendor = sequence.get('vendor') client = sequence.get('client') currency = sequence.get('currency') self.assertEquals( resource.getRelativeUrl(), simulation_movement.getResource() ) self.assertEquals( currency.getRelativeUrl(), simulation_movement.getPriceCurrency() ) self.assertEquals( vendor.getRelativeUrl(), simulation_movement.getSourceSection() ) self.assertEquals( client.getRelativeUrl(), simulation_movement.getDestinationSection() ) self.assertEquals( invoice_line.getRelativeUrl(), simulation_movement.getDelivery() ) self.assertEquals( 10, simulation_movement.getQuantity() ) self.assertEquals( 10, simulation_movement.getPrice() ) @newSimulationExpectedFailure def test_04_SimpleInvoice(self, quiet=QUIET, run=RUN_ALL_TESTS): """ Simple Invoice. Try to expand an invoice containing only one simple Invoice Line. Check that the build is correct. """ if not run: return if not quiet: message = 'Test Simple Invoice Rule' ZopeTestCase._print('\n%s ' % message) LOG('Testing... ', INFO, message) self.playSequence(""" stepCreateAccounts stepCreateEntities stepCreateCurrencies stepCreateProducts stepCreateInvoiceTransactionRule stepUpdateInvoiceTransactionRuleMatrix stepCreatePaymentRule stepUpdatePaymentRuleMatrix stepValidateInvoiceTransaction stepCreateNotebookFranceCell stepTic stepClearSimulation stepClearAccountingModule stepCreateSimpleInvoice stepPlanInvoice stepTic stepCheckFirstRuleIsApplied stepCheckPaymentRuleIsApplied stepConfirmInvoice stepTic stepCheckNoAccountingLinesBuiltYet stepStartInvoice stepTic stepCheckAccountingLinesCoherantWithSimulation stepCheckAccountingLinesCreatedForSimpleInvoice stepCheckPaymentLinesCreatedForSimpleInvoice """, quiet=quiet ) @newSimulationExpectedFailure def test_04b_SimpleInvoiceConfirm(self, quiet=QUIET, run=RUN_ALL_TESTS): """ Same test as SimpleInvoice but directly confirm the invoice without planning it """ if not run: return if not quiet: message = 'Test Simple Invoice Rule (without plan)' ZopeTestCase._print('\n%s ' % message) LOG('Testing... ', INFO, message) self.playSequence(""" stepCreateAccounts stepCreateEntities stepCreateCurrencies stepCreateProducts stepCreateInvoiceTransactionRule stepUpdateInvoiceTransactionRuleMatrix stepCreatePaymentRule stepUpdatePaymentRuleMatrix stepValidateInvoiceTransaction stepCreateNotebookFranceCell stepTic stepClearSimulation stepClearAccountingModule stepCreateSimpleInvoice stepConfirmInvoice stepTic stepCheckNoAccountingLinesBuiltYet stepStartInvoice stepTic stepCheckAccountingLinesCreatedForSimpleInvoice stepCheckPaymentLinesCreatedForSimpleInvoice stepRebuildAndCheckNothingIsCreated """, quiet=quiet ) @newSimulationExpectedFailure def test_04c_SimpleInvoiceTwoLines(self, quiet=QUIET, run=RUN_ALL_TESTS): """ Simple Invoice, 2 lines. Same test as SimpleInvoice but use 2 lines of quantity 5 instead of 1 line of quantity 10. """ if not run: return if not quiet: message = 'Test Simple Invoice Rule (with 2 lines)' ZopeTestCase._print('\n%s ' % message) LOG('Testing... ', INFO, message) self.playSequence(""" stepCreateAccounts stepCreateEntities stepCreateCurrencies stepCreateProducts stepCreateInvoiceTransactionRule stepUpdateInvoiceTransactionRuleMatrix stepCreatePaymentRule stepUpdatePaymentRuleMatrix stepValidateInvoiceTransaction stepCreateNotebookFranceCell stepTic stepClearSimulation stepClearAccountingModule stepCreateSimpleInvoiceTwoLines stepConfirmInvoice stepTic stepCheckNoAccountingLinesBuiltYet stepStartInvoice stepTic stepCheckAccountingLinesCreatedForSimpleInvoice stepCheckPaymentLinesCreatedForSimpleInvoice stepRebuildAndCheckNothingIsCreated """, quiet=quiet ) @newSimulationExpectedFailure def test_04d_SimpleInvoiceTwoCells(self, quiet=QUIET, run=RUN_ALL_TESTS): """ Simple Invoice, 2 cells. Same test as SimpleInvoice but use 2 cells of quantity 5 instead of 1 line of quantity 10. """ if not run: return if not quiet: message = 'Test Simple Invoice Rule (with 2 cells)' ZopeTestCase._print('\n%s ' % message) LOG('Testing... ', INFO, message) self.playSequence(""" stepCreateAccounts stepCreateEntities stepCreateCurrencies stepCreateProducts stepCreateInvoiceTransactionRule stepUpdateInvoiceTransactionRuleMatrix stepCreatePaymentRule stepUpdatePaymentRuleMatrix stepValidateInvoiceTransaction stepCreateNotebookFranceCell stepTic stepClearSimulation stepClearAccountingModule stepCreateSimpleInvoiceTwoCells stepConfirmInvoice stepTic stepCheckNoAccountingLinesBuiltYet stepStartInvoice stepTic stepCheckAccountingLinesCreatedForSimpleInvoice stepCheckPaymentLinesCreatedForSimpleInvoice stepRebuildAndCheckNothingIsCreated """, quiet=quiet ) # next 5 tests will check update of applied rules. @newSimulationExpectedFailure def test_05a_SimpleInvoiceReExpandAddLine(self, quiet=QUIET, run=RUN_ALL_TESTS): """ Add a new line then updateSimulation. Create an empty invoice, plan, add a line so that this invoice is the same as `SimpleInvoice`, confirm it then check accounting lines """ if not run: return if not quiet: message = 'Test Simple Invoice Rule (add invoice line and reexpand)' ZopeTestCase._print('\n%s ' % message) LOG('Testing... ', INFO, message) self.playSequence(""" stepCreateAccounts stepCreateEntities stepCreateCurrencies stepCreateProducts stepCreateInvoiceTransactionRule stepUpdateInvoiceTransactionRuleMatrix stepCreatePaymentRule stepUpdatePaymentRuleMatrix stepValidateInvoiceTransaction stepCreateNotebookFranceCell stepTic stepClearSimulation stepClearAccountingModule stepCreateEmptyInvoice stepPlanInvoice stepTic stepAddInvoiceLine stepConfirmInvoice stepTic stepCheckNoAccountingLinesBuiltYet stepStartInvoice stepTic stepCheckAccountingLinesCreatedForSimpleInvoice stepCheckPaymentLinesCreatedForSimpleInvoice stepRebuildAndCheckNothingIsCreated """, quiet=quiet ) @newSimulationExpectedFailure def test_05b_SimpleInvoiceReExpandEditLine(self, quiet=QUIET, run = RUN_ALL_TESTS): """ Tests that editing a line updates simulation correctly """ if not run: return if not quiet: message = 'Test Simple Invoice Rule (edit line and reexpand)' ZopeTestCase._print('\n%s ' % message) LOG('Testing... ', INFO, message) self.playSequence(""" stepCreateAccounts stepCreateEntities stepCreateCurrencies stepCreateProducts stepCreateInvoiceTransactionRule stepUpdateInvoiceTransactionRuleMatrix stepCreatePaymentRule stepUpdatePaymentRuleMatrix stepValidateInvoiceTransaction stepCreateNotebookFranceCell stepTic stepClearSimulation stepClearAccountingModule stepCreateOtherSimpleInvoice stepPlanInvoice stepTic stepEditInvoiceLine stepConfirmInvoice stepTic stepCheckNoAccountingLinesBuiltYet stepStartInvoice stepTic stepCheckAccountingLinesCreatedForSimpleInvoice stepCheckPaymentLinesCreatedForSimpleInvoice stepRebuildAndCheckNothingIsCreated """, quiet=quiet ) @newSimulationExpectedFailure def test_05c_SimpleInvoiceReExpandDeleteLine( self, quiet=QUIET, run=RUN_ALL_TESTS): """ Tests that removing a line updates simulation correctly """ if not run: return if not quiet: message = 'Test Simple Invoice Rule (delete line and reexpand)' ZopeTestCase._print('\n%s ' % message) LOG('Testing... ', INFO, message) self.playSequence(""" stepCreateAccounts stepCreateEntities stepCreateCurrencies stepCreateProducts stepCreateInvoiceTransactionRule stepUpdateInvoiceTransactionRuleMatrix stepCreatePaymentRule stepUpdatePaymentRuleMatrix stepValidateInvoiceTransaction stepCreateNotebookFranceCell stepTic stepClearSimulation stepClearAccountingModule stepCreateSimpleInvoice stepAddInvoiceLine stepPlanInvoice stepTic stepDeleteInvoiceLine stepTic stepConfirmInvoice stepTic stepCheckNoAccountingLinesBuiltYet stepStartInvoice stepTic stepCheckAccountingLinesCreatedForSimpleInvoice stepCheckPaymentLinesCreatedForSimpleInvoice stepRebuildAndCheckNothingIsCreated """, quiet=quiet ) @newSimulationExpectedFailure def test_05d_SimpleInvoiceReExpandCreateCell(self, quiet=QUIET, run=RUN_ALL_TESTS): """ Tests that replacing a line by cells updates simulation correctly """ if not run: return if not quiet: message = 'Test Simple Invoice Rule (add cells in a line and reexpand)' ZopeTestCase._print('\n%s ' % message) LOG('Testing... ', INFO, message) self.playSequence(""" stepCreateAccounts stepCreateEntities stepCreateCurrencies stepCreateProducts stepCreateInvoiceTransactionRule stepUpdateInvoiceTransactionRuleMatrix stepCreatePaymentRule stepUpdatePaymentRuleMatrix stepValidateInvoiceTransaction stepCreateNotebookFranceCell stepTic stepClearSimulation stepClearAccountingModule stepCreateOtherSimpleInvoice stepPlanInvoice stepTic stepAddCellsInInvoiceLine stepConfirmInvoice stepTic stepCheckNoAccountingLinesBuiltYet stepStartInvoice stepTic stepCheckAccountingLinesCreatedForSimpleInvoice stepCheckPaymentLinesCreatedForSimpleInvoice stepRebuildAndCheckNothingIsCreated """, quiet=quiet) @newSimulationExpectedFailure def test_05e_SimpleInvoiceExpandManyTimes( self, quiet=QUIET, run=RUN_ALL_TESTS): """ Tests that updating an applied rule many times doesn't break the build """ if not run: return if not quiet: message = 'Test Simple Invoice Rule (many updateSimulation)' ZopeTestCase._print('\n%s ' % message) LOG('Testing... ', INFO, message) self.playSequence(""" stepCreateAccounts stepCreateEntities stepCreateCurrencies stepCreateProducts stepCreateInvoiceTransactionRule stepUpdateInvoiceTransactionRuleMatrix stepCreatePaymentRule stepUpdatePaymentRuleMatrix stepValidateInvoiceTransaction stepCreateNotebookFranceCell stepTic stepClearSimulation stepClearAccountingModule stepCreateSimpleInvoice stepPlanInvoice stepTic """ + (""" stepEditInvoiceLine stepTic""" * 4) + """ stepConfirmInvoice stepTic stepCheckNoAccountingLinesBuiltYet stepStartInvoice stepTic stepCheckAccountingLinesCreatedForSimpleInvoice stepCheckPaymentLinesCreatedForSimpleInvoice stepRebuildAndCheckNothingIsCreated """, quiet=quiet ) @newSimulationExpectedFailure def test_06_MultiLineInvoice(self, quiet=QUIET, run=RUN_ALL_TESTS): """ Multiple lines invoice. Try to expand an invoice containing multiples Invoice Line. Check that the build is correct, ie similar movements are aggregated. """ if not run: return if not quiet: message = 'Test Multi Line Invoice Rule' ZopeTestCase._print('\n%s ' % message) LOG('Testing... ', INFO, message) self.playSequence(""" stepCreateAccounts stepCreateEntities stepCreateCurrencies stepCreateProducts stepCreateInvoiceTransactionRule stepUpdateInvoiceTransactionRuleMatrix stepCreatePaymentRule stepUpdatePaymentRuleMatrix stepValidateInvoiceTransaction stepTic stepCreateNotebookFranceCell stepCreateBareboneFranceCell stepTic stepClearSimulation stepClearAccountingModule stepCreateMultiLineInvoice stepPlanInvoice stepConfirmInvoice stepTic stepCheckNoAccountingLinesBuiltYet stepStartInvoice stepTic stepCheckAccountingLinesCreatedForMultiLineInvoice stepCheckPaymentLinesCreatedForMultiLineInvoice stepRebuildAndCheckNothingIsCreated """, quiet=quiet ) @newSimulationExpectedFailure def test_planning_invoice_creates_simulation(self, quiet=QUIET): # http://mail.nexedi.com/pipermail/erp5-dev/2008-June/001969.html self.playSequence(""" stepCreateAccounts stepCreateEntities stepCreateCurrencies stepCreateProducts stepCreateInvoiceTransactionRule stepUpdateInvoiceTransactionRuleMatrix stepCreatePaymentRule stepUpdatePaymentRuleMatrix stepValidateInvoiceTransaction stepTic stepCreateNotebookFranceCell stepCreateBareboneFranceCell stepTic stepClearSimulation stepClearAccountingModule stepCreateSimpleInvoice stepTic stepPlanInvoice stepTic stepCheckInvoiceSimulation """, quiet=quiet) class TestSaleAccountingRules(SaleInvoiceTest, TestAccountingRules): pass class TestPurchaseAccountingRules(PurchaseInvoiceTest, TestAccountingRules): # XXX this test is not really complete, originally we were testing Sale only, # so the test steps just test that source is correct, and not the # destination. For now it just tests that workflows for Purchase invokes # building correctly ... pass def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestSaleAccountingRules)) suite.addTest(unittest.makeSuite(TestPurchaseAccountingRules)) return suite