diff --git a/product/ERP5/tests/testLegacyInvoice.py b/product/ERP5/tests/testLegacyInvoice.py new file mode 100644 index 0000000000000000000000000000000000000000..856d8d72c7f6b64be8bae197d5c07ca07f78da8a --- /dev/null +++ b/product/ERP5/tests/testLegacyInvoice.py @@ -0,0 +1,3497 @@ +############################################################################## +# +# Copyright (c) 2004-2008 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 responsibility 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 +# guarantees 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 invoice creation from simulation. + +""" + +import transaction +from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase +from Products.ERP5Type.tests.utils import FileUpload, DummyMailHost +from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod +from Products.ERP5OOo.OOoUtils import OOoParser +from AccessControl.SecurityManagement import newSecurityManager +from DateTime import DateTime +from Acquisition import aq_parent +from zLOG import LOG +from Products.ERP5Type.tests.Sequence import SequenceList +from testLegacyPackingList import TestPackingListMixin +from testAccountingRules import TestAccountingRulesMixin + +class TestInvoiceMixin(TestPackingListMixin, + TestAccountingRulesMixin,): + """Test methods for invoices + """ + default_region = "europe/west/france" + vat_gap = 'fr/pcg/4/44/445/4457/44571' + vat_rate = 0.196 + sale_gap = 'fr/pcg/7/70/707/7071/70712' + customer_gap = 'fr/pcg/4/41/411' + bank_gap = 'fr/pcg/5/51/512' + mail_delivery_mode = 'by_mail' + cpt_incoterm = 'cpt' + unit_piece_quantity_unit = 'unit/piece' + mass_quantity_unit = 'mass/kg' + oldMailhost = None + + # (account_id, account_gap, account_type) + account_definition_list = ( + ('receivable_vat', vat_gap, 'liability/payable/collected_vat',), + ('sale', sale_gap, 'income'), + ('customer', customer_gap, 'asset/receivable'), + ('refundable_vat', vat_gap, 'asset/receivable/refundable_vat'), + ('purchase', sale_gap, 'expense'), + ('supplier', customer_gap, 'liability/payable'), + ('bank', bank_gap, 'asset/cash/bank'), + ) + # (line_id, source_account_id, destination_account_id, line_quantity) + transaction_line_definition_list = ( + ('income', 'sale', 'purchase', 1.0), + ('receivable', 'customer', 'supplier', -1.0 - vat_rate), + ('collected_vat', 'receivable_vat', 'refundable_vat', vat_rate), + ) + + + def getTitle(self): + return "Invoices" + + def getBusinessTemplateList(self): + return ('erp5_base', 'erp5_pdm', 'erp5_trade', 'erp5_accounting', + 'erp5_invoicing', 'erp5_simplified_invoicing', 'erp5_apparel', + 'erp5_project', 'erp5_administration') + + @UnrestrictedMethod + def createCategories(self): + """Create the categories for our test. """ + 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,) + else: + path = path[cat] + # 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 ('region/%s' % self.default_region, + 'gap/%s' % self.vat_gap, + 'gap/%s' % self.sale_gap, + 'gap/%s' % self.customer_gap, + 'gap/%s' % self.bank_gap, + 'delivery_mode/%s' % self.mail_delivery_mode, + 'incoterm/%s' % self.cpt_incoterm, + 'quantity_unit/%s' % self.unit_piece_quantity_unit, + 'quantity_unit/%s' % self.mass_quantity_unit, + ) + + + def afterSetUp(self): + self.createCategories() + self.validateRules() + self.login() + self.oldMailHost = getattr(self.portal, 'MailHost', None) + if self.oldMailHost is not None: + self.portal.manage_delObjects(['MailHost']) + self.portal._setObject('MailHost', DummyMailHost('MailHost')) + + def beforeTearDown(self): + transaction.abort() + self.tic() + # restore the original MailHost + if self.oldMailHost is not None: + self.portal.manage_delObjects(['MailHost']) + self.portal._setObject('MailHost', DummyMailHost('MailHost')) + for folder in (self.portal.accounting_module, + self.portal.organisation_module, + self.portal.sale_order_module, + self.portal.purchase_order_module, + self.portal.sale_packing_list_module, + self.portal.purchase_packing_list_module, + self.portal.portal_simulation,): + + folder.manage_delObjects([x for x in folder.objectIds() if x not in ('organisation_1','organisation_2','ppl_1','ppl_2')]) + + transaction.commit() + self.tic() + + def login(self): + """login, without manager role""" + uf = self.getPortal().acl_users + uf._doAddUser('test_invoice_user', '', ['Assignee', 'Assignor', 'Member', + 'Associate', 'Auditor', 'Author'], []) + user = uf.getUserById('test_invoice_user').__of__(uf) + newSecurityManager(None, user) + + def stepCreateSaleInvoiceTransactionRule(self, sequence, **kw) : + """Create the rule for accounting. """ + self.createInvoiceTransactionRule(resource=sequence.get('resource')) + + @UnrestrictedMethod + def createInvoiceTransactionRule(self, resource=None): + """Create a sale invoice transaction rule with only one cell for + product_line/apparel and default_region + The accounting rule cell will have the provided resource, but this his more + or less optional (as long as price currency is set correctly on order) + """ + portal = self.portal + account_module = portal.account_module + for account_id, account_gap, account_type \ + in self.account_definition_list: + if not account_id in account_module.objectIds(): + account = account_module.newContent(id=account_id) + account.setGap(account_gap) + account.setAccountType(account_type) + portal.portal_workflow.doActionFor(account, 'validate_action') + + invoice_rule = portal.portal_rules.default_invoice_transaction_rule + if invoice_rule.getValidationState() == 'validated': + invoice_rule.invalidate() + invoice_rule.deleteContent(list(invoice_rule.objectIds())) + transaction.commit() + self.tic() + region_predicate = invoice_rule.newContent(portal_type = 'Predicate') + product_line_predicate = invoice_rule.newContent(portal_type = 'Predicate') + region_predicate.edit( + membership_criterion_base_category_list = ['destination_region'], + membership_criterion_category_list = + ['destination_region/region/%s' % self.default_region ], + int_index = 1, + string_index = 'region' + ) + product_line_predicate.edit( + membership_criterion_base_category_list = ['product_line'], + membership_criterion_category_list = + ['product_line/apparel'], + int_index = 1, + string_index = 'product' + ) + product_line_predicate.immediateReindexObject() + region_predicate.immediateReindexObject() + + invoice_rule.updateMatrix() + cell_list = invoice_rule.getCellValueList(base_id='movement') + self.assertEquals(len(cell_list),1) + cell = cell_list[0] + + for line_id, line_source_id, line_destination_id, line_ratio in \ + self.transaction_line_definition_list: + line = cell.newContent(id=line_id, + portal_type='Accounting Transaction Line', quantity=line_ratio, + resource_value=resource, + source_value=account_module[line_source_id], + destination_value=account_module[line_destination_id]) + + invoice_rule.validate() + transaction.commit() + self.tic() + + def stepCreateEntities(self, sequence, **kw) : + """Create a vendor and two clients. """ + self.stepCreateOrganisation1(sequence, **kw) + self.stepCreateOrganisation2(sequence, **kw) + self.stepCreateOrganisation3(sequence, **kw) + self.stepCreateProject1(sequence, **kw) + self.stepCreateProject2(sequence, **kw) + vendor = sequence.get('organisation1') + vendor.setRegion(self.default_region) + vendor.validate() + sequence.edit(vendor=vendor) + client1 = sequence.get('organisation2') + client1.setRegion(self.default_region) + self.assertNotEquals(client1.getRegionValue(), None) + client1.validate() + sequence.edit(client1=client1) + client2 = sequence.get('organisation3') + self.assertEquals(client2.getRegionValue(), None) + client2.validate() + sequence.edit(client2=client2) + + def stepCheckOrderRule(self, sequence=None, sequence_list=None, **kw): + """Check we have a related Order Rule""" + order = sequence.get('order') + simulation_tool = self.getSimulationTool() + # Check that there is an applied rule for our packing list + rule_list = [x for x in simulation_tool.objectValues() + if x.getCausalityValue()==order] + self.assertNotEquals(len(rule_list), 0) + sequence.edit(order_rule_list = rule_list) + + self.assertEquals(len(order.getMovementList()), + sum([len(rule.objectIds()) for rule in rule_list])) + + def stepCheckInvoicingRule(self, sequence=None, sequence_list=None, **kw): + """ + Checks that the invoicing rule is applied and its values are correct. + """ + order_rule_list = sequence.get('order_rule_list') + invoicing_rule_list = [] + invoice_transaction_rule_list = [] + for order_rule in order_rule_list : + for order_simulation_movement in order_rule.objectValues() : + temp_invoicing_rule_list = [ar for ar in order_simulation_movement.objectValues() + if ar.getSpecialiseValue().getPortalType() == 'Invoicing Rule'] + self.assertEquals(len(temp_invoicing_rule_list), 1) + invoicing_rule_list.extend(temp_invoicing_rule_list) + sequence.edit(invoicing_rule_list=invoicing_rule_list) + invoicing_rule = invoicing_rule_list[0] + sequence.edit(invoicing_rule = invoicing_rule) + for invoicing_rule in invoicing_rule_list: + self.assertEquals(invoicing_rule.getSpecialiseReference(), + 'default_invoicing_rule') + self.assertEquals(invoicing_rule.getPortalType(), + 'Applied Rule') + simulation_movement_list = invoicing_rule.objectValues() + self.assertNotEquals(len(simulation_movement_list), 0) + for simulation_movement in simulation_movement_list : + invoice_transaction_rule_list.extend([applied_rule for applied_rule + in simulation_movement.objectValues() if applied_rule \ + .getSpecialiseValue().getPortalType() + == 'Invoice Transaction Rule']) + resource_list = sequence.get('resource_list') + self.assertEquals(simulation_movement.getPortalType(), + 'Simulation Movement') + self.assertTrue(simulation_movement.getResourceValue() in + resource_list) + self.assertTrue(simulation_movement.isConvergent()) + # TODO: What is the invoice dates supposed to be ? + # is this done through profiles ? + #self.assertEquals(simulation_movement.getStartDate(), + # sequence.get('order').getStartDate()) + #self.assertEquals(simulation_movement.getStopDate(), + # sequence.get('order').getStopDate()) + sequence.edit(invoice_transaction_rule_list=invoice_transaction_rule_list) + + def stepCheckInvoiceTransactionRule(self, sequence=None, sequence_list=None, + **kw): + """ + Checks that the applied invoice_transaction_rule is expanded and its movements are + consistent with its parent movement + """ + invoice_transaction_rule_list = \ + sequence.get('invoice_transaction_rule_list') + for applied_invoice_transaction_rule in invoice_transaction_rule_list: + parent_movement = aq_parent(applied_invoice_transaction_rule) + invoice_transaction_rule = \ + applied_invoice_transaction_rule.getSpecialiseValue() + self.assertEquals(3, len(applied_invoice_transaction_rule.objectValues())) + for line_id, line_source_id, line_destination_id, line_ratio in \ + self.transaction_line_definition_list: + movement = None + for simulation_movement in \ + applied_invoice_transaction_rule.objectValues(): + if simulation_movement.getSourceId() == line_source_id and\ + simulation_movement.getDestinationId() == line_destination_id: + movement = simulation_movement + break + + self.assertTrue(movement is not None) + self.assertEquals(movement.getCorrectedQuantity(), parent_movement.getPrice() * + parent_movement.getCorrectedQuantity() * line_ratio) + self.assertEquals(movement.getStartDate(), + parent_movement.getStartDate()) + self.assertEquals(movement.getStopDate(), + parent_movement.getStopDate()) + + + +class TestInvoice(TestInvoiceMixin): + """Test methods for sale and purchase invoice. + Subclasses must defines portal types to use. + """ + quiet = 1 + def test_invoice_transaction_line_resource(self): + """ + tests that simulation movements corresponding to accounting line have a + good resource in the simulation + """ + resource = self.portal.getDefaultModule( + self.resource_portal_type).newContent( + portal_type=self.resource_portal_type, + title='Resource', + product_line='apparel') + currency = self.portal.currency_module.newContent( + portal_type='Currency', + title='Currency', + base_unit_quantity=0.01) + self.createInvoiceTransactionRule(currency) + + client = self.portal.organisation_module.newContent( + portal_type='Organisation', + title='Client', + price_currency= currency.getRelativeUrl(), + default_address_region=self.default_region) + vendor = self.portal.organisation_module.newContent( + portal_type='Organisation', + title='Vendor', + price_currency= currency.getRelativeUrl(), + default_address_region=self.default_region) + order = self.portal.getDefaultModule(self.order_portal_type).newContent( + portal_type=self.order_portal_type, + source_value=vendor, + source_section_value=vendor, + destination_value=client, + destination_section_value=client, + start_date=DateTime(2008, 1, 1), + price_currency_value=currency, + title='Order') + order_line = order.newContent(portal_type=self.order_line_portal_type, + resource_value=resource, + quantity=1, + price=2) + + order.confirm() + transaction.commit() + self.tic() + + related_applied_rule = order.getCausalityRelatedValue( + portal_type='Applied Rule') + delivery_movement = related_applied_rule.contentValues()[0] + invoice_applied_rule = delivery_movement.contentValues()[0] + invoice_movement = invoice_applied_rule.contentValues()[0] + invoice_transaction_applied_rule = invoice_movement.contentValues()[0] + invoice_transaction_movement =\ + invoice_transaction_applied_rule.contentValues()[0] + self.assertEquals(currency, + invoice_transaction_movement.getResourceValue()) + self.assertEquals(currency, + delivery_movement.getPriceCurrencyValue()) + + + def test_modify_planned_order_invoicing_rule(self): + """ + tests that modifying a planned order affects movements from invoicing + rule + """ + resource = self.portal.getDefaultModule( + self.resource_portal_type).newContent( + portal_type=self.resource_portal_type, + title='Resource', + product_line='apparel') + currency = self.portal.currency_module.newContent( + portal_type='Currency', + title='Currency', + base_unit_quantity=0.01) + + client = self.portal.organisation_module.newContent( + portal_type='Organisation', + title='Client', + price_currency= currency.getRelativeUrl()) + vendor = self.portal.organisation_module.newContent( + portal_type='Organisation', + title='Vendor', + price_currency= currency.getRelativeUrl()) + order = self.portal.getDefaultModule(self.order_portal_type).newContent( + portal_type=self.order_portal_type, + source_value=vendor, + source_section_value=vendor, + destination_value=client, + destination_section_value=client, + start_date=DateTime(2008, 1, 1), + price_currency_value=currency, + title='Order') + order_line = order.newContent(portal_type=self.order_line_portal_type, + resource_value=resource, + quantity=1, + price=2) + + other_entity = self.portal.organisation_module.newContent( + portal_type='Organisation', + title='Other Entity', + price_currency=currency.getRelativeUrl()) + other_project = self.portal.project_module.newContent( + portal_type='Project', + title='Other Project') + order.plan() + transaction.commit() + self.tic() + self.assertEquals('planned', order.getSimulationState()) + + related_applied_rule = order.getCausalityRelatedValue( + portal_type='Applied Rule') + delivery_movement = related_applied_rule.contentValues()[0] + invoice_applied_rule = delivery_movement.contentValues()[0] + invoice_movement = invoice_applied_rule.contentValues()[0] + + order_line.setSourceValue(other_entity) + transaction.commit() + self.tic() + invoice_movement = invoice_applied_rule.contentValues()[0] + self.assertEquals(other_entity, + invoice_movement.getSourceValue()) + + order_line.setDestinationValue(other_entity) + transaction.commit() + self.tic() + invoice_movement = invoice_applied_rule.contentValues()[0] + self.assertEquals(other_entity, + invoice_movement.getDestinationValue()) + + order_line.setSourceSectionValue(other_entity) + transaction.commit() + self.tic() + invoice_movement = invoice_applied_rule.contentValues()[0] + self.assertEquals(other_entity, + invoice_movement.getSourceSectionValue()) + + # make sure destination_section != source_section, this might be needed by + # some rules + order_line.setSourceSectionValue(order_line.getDestinationSectionValue()) + + order_line.setDestinationSectionValue(other_entity) + transaction.commit() + self.tic() + invoice_movement = invoice_applied_rule.contentValues()[0] + self.assertEquals(other_entity, + invoice_movement.getDestinationSectionValue()) + + order_line.setSourceAdministrationValue(other_entity) + transaction.commit() + self.tic() + invoice_movement = invoice_applied_rule.contentValues()[0] + self.assertEquals(other_entity, + invoice_movement.getSourceAdministrationValue()) + + order_line.setDestinationAdministrationValue(other_entity) + transaction.commit() + self.tic() + invoice_movement = invoice_applied_rule.contentValues()[0] + self.assertEquals(other_entity, + invoice_movement.getDestinationAdministrationValue()) + + order_line.setSourceDecisionValue(other_entity) + transaction.commit() + self.tic() + invoice_movement = invoice_applied_rule.contentValues()[0] + self.assertEquals(other_entity, + invoice_movement.getSourceDecisionValue()) + + order_line.setDestinationDecisionValue(other_entity) + transaction.commit() + self.tic() + invoice_movement = invoice_applied_rule.contentValues()[0] + self.assertEquals(other_entity, + invoice_movement.getDestinationDecisionValue()) + + order_line.setSourceProjectValue(other_project) + transaction.commit() + self.tic() + invoice_movement = invoice_applied_rule.contentValues()[0] + self.assertEquals(other_project, + invoice_movement.getSourceProjectValue()) + + order_line.setDestinationProjectValue(other_project) + transaction.commit() + self.tic() + invoice_movement = invoice_applied_rule.contentValues()[0] + self.assertEquals(other_project, + invoice_movement.getDestinationProjectValue()) + + order_line.setSourcePaymentValue(other_entity) + transaction.commit() + self.tic() + invoice_movement = invoice_applied_rule.contentValues()[0] + self.assertEquals(other_entity, + invoice_movement.getSourcePaymentValue()) + + order_line.setDestinationPaymentValue(other_entity) + transaction.commit() + self.tic() + invoice_movement = invoice_applied_rule.contentValues()[0] + self.assertEquals(other_entity, + invoice_movement.getDestinationPaymentValue()) + + order_line.setSourceFunctionValue(other_entity) + transaction.commit() + self.tic() + invoice_movement = invoice_applied_rule.contentValues()[0] + self.assertEquals(other_entity, + invoice_movement.getSourceFunctionValue()) + + order_line.setDestinationFunctionValue(other_entity) + transaction.commit() + self.tic() + invoice_movement = invoice_applied_rule.contentValues()[0] + self.assertEquals(other_entity, + invoice_movement.getDestinationFunctionValue()) + + self.assertNotEquals(123, order_line.getPrice()) + order_line.setPrice(123) + transaction.commit() + self.tic() + invoice_movement = invoice_applied_rule.contentValues()[0] + self.assertEquals(123, + invoice_movement.getPrice()) + + self.assertNotEquals(456, order_line.getQuantity()) + order_line.setQuantity(456) + transaction.commit() + self.tic() + invoice_movement = invoice_applied_rule.contentValues()[0] + self.assertEquals(456, + invoice_movement.getQuantity()) + + other_resource = self.portal.product_module.newContent( + portal_type='Product', + title='Other Resource') + order_line.setResourceValue(other_resource) + transaction.commit() + self.tic() + # after changing 'resource', related simulation movement will be + # replaced with another id, and we need to find the appropriate one + # here. + delivery_movement = related_applied_rule.contentValues()[0] + invoice_applied_rule = delivery_movement.contentValues()[0] + invoice_movement = invoice_applied_rule.contentValues()[0] + self.assertEquals(other_resource, + invoice_movement.getResourceValue()) + + order_line.setStartDate(DateTime(2001, 02, 03)) + transaction.commit() + self.tic() + invoice_movement = invoice_applied_rule.contentValues()[0] + self.assertEquals(DateTime(2001, 02, 03), + invoice_movement.getStartDate()) + + order_line.setStopDate(DateTime(2002, 03, 04)) + transaction.commit() + self.tic() + invoice_movement = invoice_applied_rule.contentValues()[0] + self.assertEquals(DateTime(2002, 03, 04), + invoice_movement.getStopDate()) + + def test_modify_planned_order_invoice_transaction_rule(self): + """ + tests that modifying a planned order affects movements from invoice + transaction rule + """ + resource = self.portal.getDefaultModule( + self.resource_portal_type).newContent( + portal_type=self.resource_portal_type, + title='Resource', + product_line='apparel') + currency = self.portal.currency_module.newContent( + portal_type='Currency', + title='Currency', + base_unit_quantity=0.01) + self.createInvoiceTransactionRule(currency) + + client = self.portal.organisation_module.newContent( + portal_type='Organisation', + title='Client', + default_address_region=self.default_region) + vendor = self.portal.organisation_module.newContent( + portal_type='Organisation', + title='Vendor', + default_address_region=self.default_region) + order = self.portal.getDefaultModule(self.order_portal_type).newContent( + portal_type=self.order_portal_type, + source_value=vendor, + source_section_value=vendor, + destination_value=client, + destination_section_value=client, + start_date=DateTime(2008, 1, 1), + price_currency_value=currency, + title='Order') + order_line = order.newContent(portal_type=self.order_line_portal_type, + resource_value=resource, + quantity=1, + price=2) + other_entity = self.portal.organisation_module.newContent( + portal_type='Organisation', + title='Other Entity', + default_address_region=self.default_region) + other_project = self.portal.project_module.newContent( + portal_type='Project', + title='Other Project') + order.plan() + transaction.commit() + self.tic() + self.assertEquals('planned', order.getSimulationState()) + + related_applied_rule = order.getCausalityRelatedValue( + portal_type='Applied Rule') + delivery_movement = related_applied_rule.contentValues()[0] + invoice_applied_rule = delivery_movement.contentValues()[0] + invoice_movement = invoice_applied_rule.contentValues()[0] + invoice_transaction_applied_rule = invoice_movement.contentValues()[0] + + # utility function to return the simulation movement that should be used + # for "income" line + def getIncomeSimulationMovement(applied_rule): + for movement in applied_rule.contentValues(): + if movement.getDestination() == 'account_module/purchase'\ + and movement.getSource() == 'account_module/sale': + return movement + self.fail('Income movement not found') + + self.assertEquals(3, len(invoice_transaction_applied_rule)) + invoice_transaction_movement = getIncomeSimulationMovement( + invoice_transaction_applied_rule) + + order_line.setSourceSectionValue(other_entity) + transaction.commit() + self.tic() + self.assertEquals(other_entity, + invoice_transaction_movement.getSourceSectionValue()) + + # make sure destination_section != source_section, this might be needed by + # some rules + order_line.setSourceSectionValue(order_line.getDestinationSectionValue()) + + order_line.setDestinationSectionValue(other_entity) + transaction.commit() + self.tic() + self.assertEquals(3, len(invoice_transaction_applied_rule)) + invoice_transaction_movement = getIncomeSimulationMovement( + invoice_transaction_applied_rule) + self.assertEquals(other_entity, + invoice_transaction_movement.getDestinationSectionValue()) + + order_line.setSourceAdministrationValue(other_entity) + transaction.commit() + self.tic() + self.assertEquals(3, len(invoice_transaction_applied_rule)) + invoice_transaction_movement = getIncomeSimulationMovement( + invoice_transaction_applied_rule) + self.assertEquals(other_entity, + invoice_transaction_movement.getSourceAdministrationValue()) + + order_line.setDestinationAdministrationValue(other_entity) + transaction.commit() + self.tic() + self.assertEquals(3, len(invoice_transaction_applied_rule)) + invoice_transaction_movement = getIncomeSimulationMovement( + invoice_transaction_applied_rule) + self.assertEquals(other_entity, + invoice_transaction_movement.getDestinationAdministrationValue()) + + order_line.setSourceDecisionValue(other_entity) + transaction.commit() + self.tic() + self.assertEquals(3, len(invoice_transaction_applied_rule)) + invoice_transaction_movement = getIncomeSimulationMovement( + invoice_transaction_applied_rule) + self.assertEquals(other_entity, + invoice_transaction_movement.getSourceDecisionValue()) + + order_line.setDestinationDecisionValue(other_entity) + transaction.commit() + self.tic() + self.assertEquals(3, len(invoice_transaction_applied_rule)) + invoice_transaction_movement = getIncomeSimulationMovement( + invoice_transaction_applied_rule) + self.assertEquals(other_entity, + invoice_transaction_movement.getDestinationDecisionValue()) + + order_line.setSourceProjectValue(other_project) + transaction.commit() + self.tic() + self.assertEquals(3, len(invoice_transaction_applied_rule)) + invoice_transaction_movement = getIncomeSimulationMovement( + invoice_transaction_applied_rule) + self.assertEquals(other_project, + invoice_transaction_movement.getSourceProjectValue()) + + order_line.setDestinationProjectValue(other_project) + transaction.commit() + self.tic() + self.assertEquals(3, len(invoice_transaction_applied_rule)) + invoice_transaction_movement = getIncomeSimulationMovement( + invoice_transaction_applied_rule) + self.assertEquals(other_project, + invoice_transaction_movement.getDestinationProjectValue()) + + order_line.setSourceFunctionValue(other_entity) + transaction.commit() + self.tic() + self.assertEquals(3, len(invoice_transaction_applied_rule)) + invoice_transaction_movement = getIncomeSimulationMovement( + invoice_transaction_applied_rule) + self.assertEquals(other_entity, + invoice_transaction_movement.getSourceFunctionValue()) + + order_line.setDestinationFunctionValue(other_entity) + transaction.commit() + self.tic() + self.assertEquals(3, len(invoice_transaction_applied_rule)) + invoice_transaction_movement = getIncomeSimulationMovement( + invoice_transaction_applied_rule) + self.assertEquals(other_entity, + invoice_transaction_movement.getDestinationFunctionValue()) + + order_line.setSourcePaymentValue(other_entity) + transaction.commit() + self.tic() + self.assertEquals(3, len(invoice_transaction_applied_rule)) + invoice_transaction_movement = getIncomeSimulationMovement( + invoice_transaction_applied_rule) + self.assertEquals(other_entity, + invoice_transaction_movement.getSourcePaymentValue()) + + order_line.setDestinationPaymentValue(other_entity) + transaction.commit() + self.tic() + self.assertEquals(3, len(invoice_transaction_applied_rule)) + invoice_transaction_movement = getIncomeSimulationMovement( + invoice_transaction_applied_rule) + self.assertEquals(other_entity, + invoice_transaction_movement.getDestinationPaymentValue()) + + order_line.setQuantity(1) + order_line.setPrice(123) + transaction.commit() + self.tic() + self.assertEquals(3, len(invoice_transaction_applied_rule)) + invoice_transaction_movement = getIncomeSimulationMovement( + invoice_transaction_applied_rule) + self.assertEquals(123, + invoice_transaction_movement.getQuantity()) + + order_line.setQuantity(456) + order_line.setPrice(1) + transaction.commit() + self.tic() + self.assertEquals(3, len(invoice_transaction_applied_rule)) + invoice_transaction_movement = getIncomeSimulationMovement( + invoice_transaction_applied_rule) + self.assertEquals(456, + invoice_transaction_movement.getQuantity()) + + order_line.setStartDate(DateTime(2001, 02, 03)) + transaction.commit() + self.tic() + self.assertEquals(3, len(invoice_transaction_applied_rule)) + invoice_transaction_movement = getIncomeSimulationMovement( + invoice_transaction_applied_rule) + self.assertEquals(DateTime(2001, 02, 03), + invoice_transaction_movement.getStartDate()) + + order_line.setStopDate(DateTime(2002, 03, 04)) + transaction.commit() + self.tic() + self.assertEquals(3, len(invoice_transaction_applied_rule)) + invoice_transaction_movement = getIncomeSimulationMovement( + invoice_transaction_applied_rule) + self.assertEquals(DateTime(2002, 03, 04), + invoice_transaction_movement.getStopDate()) + + + def test_Invoice_viewAsODT(self): + resource = self.portal.getDefaultModule( + self.resource_portal_type).newContent( + portal_type=self.resource_portal_type, + title='Resource',) + client = self.portal.organisation_module.newContent( + portal_type='Organisation', title='Client') + vendor = self.portal.organisation_module.newContent( + portal_type='Organisation', title='Vendor') + invoice = self.portal.getDefaultModule(self.invoice_portal_type).newContent( + portal_type=self.invoice_portal_type, + start_date=DateTime(2008, 12, 31), + title='Invoice', + source_value=vendor, + source_section_value=vendor, + destination_value=client, + destination_section_value=client) + line = invoice.newContent(portal_type=self.invoice_line_portal_type, + resource_value=resource, + quantity=10, + price=3) + invoice.confirm() + transaction.commit() + self.tic() + + odt = invoice.Invoice_viewAsODT() + from Products.ERP5OOo.tests.utils import Validator + odf_validator = Validator() + err_list = odf_validator.validate(odt) + if err_list: + self.fail(''.join(err_list)) + + def test_Invoice_viewAsODT_empty_image(self): + resource = self.portal.getDefaultModule( + self.resource_portal_type).newContent( + portal_type=self.resource_portal_type, + title='Resource',) + client = self.portal.organisation_module.newContent( + portal_type='Organisation', title='Client') + client_logo = client.newContent(portal_type='Image', + id='default_image') + vendor = self.portal.organisation_module.newContent( + portal_type='Organisation', title='Vendor') + vendor_logo = vendor.newContent(portal_type='Image', + id='default_image') + self.assertEquals(0, vendor_logo.getSize()) + self.assertEquals(0, vendor.getDefaultImageWidth()) + self.assertEquals(0, vendor.getDefaultImageHeight()) + invoice = self.portal.getDefaultModule(self.invoice_portal_type).newContent( + portal_type=self.invoice_portal_type, + start_date=DateTime(2008, 12, 31), + title='Invoice', + source_value=vendor, + source_section_value=vendor, + destination_value=client, + destination_section_value=client) + line = invoice.newContent(portal_type=self.invoice_line_portal_type, + resource_value=resource, + quantity=10, + price=3) + invoice.confirm() + transaction.commit() + self.tic() + + odt = invoice.Invoice_viewAsODT() + from Products.ERP5OOo.tests.utils import Validator + odf_validator = Validator() + err_list = odf_validator.validate(odt) + if err_list: + self.fail(''.join(err_list)) + + # the <draw:image> should not be present, because there's no logo + parser = OOoParser() + parser.openFromString(odt) + style_xml = parser.oo_files['styles.xml'] + self.assert_('<draw:image' not in style_xml) + + def test_Invoice_viewAsODT_invalid_image(self): + resource = self.portal.getDefaultModule( + self.resource_portal_type).newContent( + portal_type=self.resource_portal_type, + title='Resource',) + file_data = FileUpload(__file__, 'rb') + client = self.portal.organisation_module.newContent( + portal_type='Organisation', title='Client') + client_logo = client.newContent(portal_type='Image', + id='default_image', + file=file_data) + vendor = self.portal.organisation_module.newContent( + portal_type='Organisation', title='Vendor') + vendor_logo = vendor.newContent(portal_type='Image', + id='default_image', + file=file_data) + + # width and height of an invalid image are -1 according to + # OFS.Image.getImageInfo maybe this is not what we want here ? + self.assertEquals(-1, vendor.getDefaultImageWidth()) + self.assertEquals(-1, vendor.getDefaultImageHeight()) + + invoice = self.portal.getDefaultModule(self.invoice_portal_type).newContent( + portal_type=self.invoice_portal_type, + start_date=DateTime(2008, 12, 31), + title='Invoice', + source_value=vendor, + source_section_value=vendor, + destination_value=client, + destination_section_value=client) + line = invoice.newContent(portal_type=self.invoice_line_portal_type, + resource_value=resource, + quantity=10, + price=3) + invoice.confirm() + transaction.commit() + self.tic() + + odt = invoice.Invoice_viewAsODT() + from Products.ERP5OOo.tests.utils import Validator + odf_validator = Validator() + err_list = odf_validator.validate(odt) + if err_list: + self.fail(''.join(err_list)) + + def test_invoice_building_with_cells(self): + # if the order has cells, the invoice built from that order must have + # cells too + resource = self.portal.getDefaultModule( + self.resource_portal_type).newContent( + portal_type=self.resource_portal_type, + title='Resource', + variation_base_category_list=['size']) + currency = self.portal.currency_module.newContent( + portal_type='Currency', + title='Currency') + + client = self.portal.organisation_module.newContent( + portal_type='Organisation', + title='Client') + vendor = self.portal.organisation_module.newContent( + portal_type='Organisation', + title='Vendor') + order = self.portal.getDefaultModule(self.order_portal_type).newContent( + portal_type=self.order_portal_type, + source_value=vendor, + source_section_value=vendor, + destination_value=client, + destination_section_value=client, + start_date=DateTime(2008, 1, 1), + price_currency_value=currency, + title='Order') + + order_line = order.newContent(portal_type=self.order_line_portal_type, + resource_value=resource,) + order_line.setVariationBaseCategoryList(('size', )) + order_line.setVariationCategoryList(['size/Baby', 'size/Child/32']) + order_line.updateCellRange() + + cell_baby = order_line.newCell('size/Baby', base_id='movement', + portal_type=self.order_cell_portal_type) + cell_baby.edit(quantity=10, + price=4, + variation_category_list=['size/Baby'], + mapped_value_property_list=['quantity', 'price'],) + + cell_child_32 = order_line.newCell('size/Child/32', base_id='movement', + portal_type=self.order_cell_portal_type) + cell_child_32.edit(quantity=20, + price=5, + variation_category_list=['size/Child/32'], + mapped_value_property_list=['quantity', 'price'],) + order.confirm() + transaction.commit() + self.tic() + + related_packing_list = order.getCausalityRelatedValue( + portal_type=self.packing_list_portal_type) + self.assertNotEquals(related_packing_list, None) + + related_packing_list.start() + related_packing_list.stop() + transaction.commit() + self.tic() + + related_invoice = related_packing_list.getCausalityRelatedValue( + portal_type=self.invoice_portal_type) + self.assertNotEquals(related_invoice, None) + + line_list = related_invoice.contentValues( + portal_type=self.invoice_line_portal_type) + self.assertEquals(1, len(line_list)) + invoice_line = line_list[0] + + self.assertEquals(resource, invoice_line.getResourceValue()) + self.assertEquals(['size'], invoice_line.getVariationBaseCategoryList()) + self.assertEquals(2, + len(invoice_line.getCellValueList(base_id='movement'))) + + cell_baby = invoice_line.getCell('size/Baby', base_id='movement') + self.assertNotEquals(cell_baby, None) + self.assertEquals(resource, cell_baby.getResourceValue()) + self.assertEquals(10, cell_baby.getQuantity()) + self.assertEquals(4, cell_baby.getPrice()) + self.assertTrue('size/Baby' in + cell_baby.getVariationCategoryList()) + self.assertTrue(cell_baby.isMemberOf('size/Baby')) + + cell_child_32 = invoice_line.getCell('size/Child/32', base_id='movement') + self.assertNotEquals(cell_child_32, None) + self.assertEquals(resource, cell_child_32.getResourceValue()) + self.assertEquals(20, cell_child_32.getQuantity()) + self.assertEquals(5, cell_child_32.getPrice()) + self.assertTrue('size/Child/32' in + cell_child_32.getVariationCategoryList()) + self.assertTrue(cell_child_32.isMemberOf('size/Child/32')) + + + + def test_invoice_created_from_packing_list_with_no_order(self): + # if the order has cells and an aggregate, the invoice built + #from that order must have + # cells too + resource = self.portal.getDefaultModule( + self.resource_portal_type).newContent( + portal_type=self.resource_portal_type, + title='Resource', + variation_base_category_list=['size']) + currency = self.portal.currency_module.newContent( + portal_type='Currency', + title='Currency') + + client = self.portal.organisation_module.newContent( + portal_type='Organisation', + title='Client') + vendor = self.portal.organisation_module.newContent( + portal_type='Organisation', + title='Vendor') + no_order_packing_list = \ +self.portal.getDefaultModule(self.packing_list_portal_type).newContent( + portal_type=self.packing_list_portal_type, + source_value=vendor, + source_section_value=vendor, + destination_value=client, + destination_section_value=client, + start_date=DateTime(2008, 1, 1), + price_currency_value=currency, + title='Order') + + packing_list_line = no_order_packing_list.newContent( + portal_type=self.packing_list_line_portal_type, + resource_value=resource,) + packing_list_line.setVariationBaseCategoryList(('size', )) + packing_list_line.setVariationCategoryList(['size/Baby', 'size/Child/32']) + packing_list_line.updateCellRange() + + cell_baby = packing_list_line.newCell('size/Baby', base_id='movement', + portal_type=self.packing_list_cell_portal_type) + cell_baby.edit(quantity=10, + price=4, + variation_category_list=['size/Baby'], + mapped_value_property_list=['quantity', 'price'],) + + cell_child_32 = packing_list_line.newCell( + 'size/Child/32',base_id='movement', + portal_type=self.packing_list_cell_portal_type) + cell_child_32.edit(quantity=20, + price=5, + variation_category_list=['size/Child/32'], + mapped_value_property_list=['quantity', 'price'],) + no_order_packing_list.confirm() + transaction.commit() + self.tic() + self.assertNotEquals(no_order_packing_list, None) + + no_order_packing_list.start() + no_order_packing_list.stop() + transaction.commit() + self.tic() + + related_invoice = no_order_packing_list.getCausalityRelatedValue( + portal_type=self.invoice_portal_type) + self.assertNotEquals(related_invoice, None) + + line_list = related_invoice.contentValues( + portal_type=self.invoice_line_portal_type) + self.assertEquals(1, len(line_list)) + invoice_line = line_list[0] + + self.assertEquals(resource, invoice_line.getResourceValue()) + self.assertEquals(['size'], invoice_line.getVariationBaseCategoryList()) + self.assertEquals(2, + len(invoice_line.getCellValueList(base_id='movement'))) + + cell_baby = invoice_line.getCell('size/Baby', base_id='movement') + self.assertNotEquals(cell_baby, None) + self.assertEquals(resource, cell_baby.getResourceValue()) + self.assertEquals(10, cell_baby.getQuantity()) + self.assertEquals(4, cell_baby.getPrice()) + self.assertTrue('size/Baby' in + cell_baby.getVariationCategoryList()) + self.assertTrue(cell_baby.isMemberOf('size/Baby')) + + cell_child_32 = invoice_line.getCell('size/Child/32', base_id='movement') + self.assertNotEquals(cell_child_32, None) + self.assertEquals(resource, cell_child_32.getResourceValue()) + self.assertEquals(20, cell_child_32.getQuantity()) + self.assertEquals(5, cell_child_32.getPrice()) + self.assertTrue('size/Child/32' in + cell_child_32.getVariationCategoryList()) + self.assertTrue(cell_child_32.isMemberOf('size/Child/32')) + + def test_invoice_building_with_cells_and_aggregate(self): + # if the order has cells and an aggregate, the invoice built + #from that order must have + # cells too + resource = self.portal.getDefaultModule( + self.resource_portal_type).newContent( + portal_type=self.resource_portal_type, + title='Resource', + variation_base_category_list=['size']) + currency = self.portal.currency_module.newContent( + portal_type='Currency', + title='Currency') + + client = self.portal.organisation_module.newContent( + portal_type='Organisation', + title='Client') + vendor = self.portal.organisation_module.newContent( + portal_type='Organisation', + title='Vendor') + order = self.portal.getDefaultModule(self.order_portal_type).newContent( + portal_type=self.order_portal_type, + source_value=vendor, + source_section_value=vendor, + destination_value=client, + destination_section_value=client, + start_date=DateTime(2008, 1, 1), + price_currency_value=currency, + title='Order') + + order_line = order.newContent(portal_type=self.order_line_portal_type, + resource_value=resource,) + order_line.setVariationBaseCategoryList(('size', )) + order_line.setVariationCategoryList(['size/Baby', 'size/Child/32']) + order_line.updateCellRange() + + cell_baby = order_line.newCell('size/Baby', base_id='movement', + portal_type=self.order_cell_portal_type) + cell_baby.edit(quantity=10, + price=4, + variation_category_list=['size/Baby'], + mapped_value_property_list=['quantity', 'price'],) + + cell_child_32 = order_line.newCell('size/Child/32', base_id='movement', + portal_type=self.order_cell_portal_type) + cell_child_32.edit(quantity=20, + price=5, + variation_category_list=['size/Child/32'], + mapped_value_property_list=['quantity', 'price'],) + order.confirm() + transaction.commit() + self.tic() + + related_packing_list = order.getCausalityRelatedValue( + portal_type=self.packing_list_portal_type) + self.assertNotEquals(related_packing_list, None) + + related_packing_list.start() + related_packing_list.stop() + transaction.commit() + self.tic() + + related_invoice = related_packing_list.getCausalityRelatedValue( + portal_type=self.invoice_portal_type) + self.assertNotEquals(related_invoice, None) + + line_list = related_invoice.contentValues( + portal_type=self.invoice_line_portal_type) + self.assertEquals(1, len(line_list)) + invoice_line = line_list[0] + + self.assertEquals(resource, invoice_line.getResourceValue()) + self.assertEquals(['size'], invoice_line.getVariationBaseCategoryList()) + self.assertEquals(2, + len(invoice_line.getCellValueList(base_id='movement'))) + + cell_baby = invoice_line.getCell('size/Baby', base_id='movement') + self.assertNotEquals(cell_baby, None) + self.assertEquals(resource, cell_baby.getResourceValue()) + self.assertEquals(10, cell_baby.getQuantity()) + self.assertEquals(4, cell_baby.getPrice()) + self.assertTrue('size/Baby' in + cell_baby.getVariationCategoryList()) + self.assertTrue(cell_baby.isMemberOf('size/Baby')) + + cell_child_32 = invoice_line.getCell('size/Child/32', base_id='movement') + self.assertNotEquals(cell_child_32, None) + self.assertEquals(resource, cell_child_32.getResourceValue()) + self.assertEquals(20, cell_child_32.getQuantity()) + self.assertEquals(5, cell_child_32.getPrice()) + self.assertTrue('size/Child/32' in + cell_child_32.getVariationCategoryList()) + self.assertTrue(cell_child_32.isMemberOf('size/Child/32')) + + + def test_description_copied_on_lines(self): + # if the order lines have different descriptions, description must be + # copied in the simulation and on created movements + resource = self.portal.getDefaultModule( + self.resource_portal_type).newContent( + portal_type=self.resource_portal_type, + title='Resource',) + resource2 = self.portal.getDefaultModule( + self.resource_portal_type).newContent( + portal_type=self.resource_portal_type, + title='Resource2',) + currency = self.portal.currency_module.newContent( + portal_type='Currency', + title='Currency') + + client = self.portal.organisation_module.newContent( + portal_type='Organisation', + title='Client') + vendor = self.portal.organisation_module.newContent( + portal_type='Organisation', + title='Vendor') + order = self.portal.getDefaultModule(self.order_portal_type).newContent( + portal_type=self.order_portal_type, + source_value=vendor, + source_section_value=vendor, + destination_value=client, + destination_section_value=client, + start_date=DateTime(2008, 1, 1), + price_currency_value=currency, + title='Order') + + order.newContent(portal_type=self.order_line_portal_type, + quantity=3, + price=10, + description='The first line', + resource_value=resource,) + order.newContent(portal_type=self.order_line_portal_type, + quantity=5, + price=10, + description='The second line', + resource_value=resource2,) + + order.confirm() + transaction.commit() + self.tic() + + related_packing_list = order.getCausalityRelatedValue( + portal_type=self.packing_list_portal_type) + self.assertNotEquals(related_packing_list, None) + + movement_list = related_packing_list.getMovementList() + self.assertEquals(2, len(movement_list)) + self.assertEquals(['The first line'], + [m.getDescription() for m in movement_list if m.getQuantity() == 3]) + self.assertEquals(['The second line'], + [m.getDescription() for m in movement_list if m.getQuantity() == 5]) + + related_packing_list.start() + related_packing_list.stop() + transaction.commit() + self.tic() + + related_invoice = related_packing_list.getCausalityRelatedValue( + portal_type=self.invoice_portal_type) + self.assertNotEquals(related_invoice, None) + + movement_list = related_invoice.getMovementList( + portal_type=self.invoice_line_portal_type) + self.assertEquals(2, len(movement_list)) + self.assertEquals(['The first line'], + [m.getDescription() for m in movement_list if m.getQuantity() == 3]) + self.assertEquals(['The second line'], + [m.getDescription() for m in movement_list if m.getQuantity() == 5]) + + + def test_CopyAndPaste(self): + """Test copy on paste on Invoice. + When an invoice is copy/pasted, references should be resetted. + """ + accounting_module = self.portal.accounting_module + invoice = accounting_module.newContent( + portal_type=self.invoice_portal_type) + invoice.edit(reference='reference', + source_reference='source_reference', + destination_reference='destination_reference',) + cb_data = accounting_module.manage_copyObjects([invoice.getId()]) + copied, = accounting_module.manage_pasteObjects(cb_data) + new_invoice = accounting_module[copied['new_id']] + self.assertNotEquals(invoice.getReference(), + new_invoice.getReference()) + self.assertNotEquals(invoice.getSourceReference(), + new_invoice.getSourceReference()) + self.assertNotEquals(invoice.getDestinationReference(), + new_invoice.getDestinationReference()) + + def test_delivery_mode_and_incoterm_on_invoice(self): + """ + test that categories delivery_mode and incoterm are copied on + the invoice by the delivery builder + """ + resource = self.portal.product_module.newContent( + portal_type='Product', + title='Resource', + product_line='apparel') + currency = self.portal.currency_module.newContent( + portal_type='Currency', + title='euro') + currency.setBaseUnitQuantity(0.01) + self.createInvoiceTransactionRule(currency) + transaction.commit() + self.tic()#execute transaction + client = self.portal.organisation_module.newContent( + portal_type='Organisation', + title='Client', + default_address_region=self.default_region) + vendor = self.portal.organisation_module.newContent( + portal_type='Organisation', + title='Vendor', + default_address_region=self.default_region) + order = self.portal.getDefaultModule(self.order_portal_type).newContent( + portal_type=self.order_portal_type, + source_value=vendor, + source_section_value=vendor, + destination_value=client, + destination_section_value=client, + start_date=DateTime(2008,10, 21), + price_currency_value=currency, + delivery_mode=self.mail_delivery_mode, + incoterm=self.cpt_incoterm, + title='Order') + order_line = order.newContent(portal_type=self.order_line_portal_type, + resource_value=resource, + quantity=5, + price=2) + order.confirm() + transaction.commit() + self.tic() + related_packing_list = order.getCausalityRelatedValue( + portal_type=self.packing_list_portal_type) + self.assertNotEquals(related_packing_list, None) + self.assertEquals(related_packing_list.getDeliveryMode(), + order.getDeliveryMode()) + self.assertEquals(related_packing_list.getIncoterm(), + order.getIncoterm()) + related_packing_list.start() + related_packing_list.stop() + transaction.commit() + self.tic() + related_invoice = related_packing_list.getCausalityRelatedValue( + portal_type=self.invoice_portal_type) + self.assertNotEquals(related_invoice, None) + self.assertEquals(related_invoice.getDeliveryMode(), + order.getDeliveryMode()) + self.assertEquals(related_invoice.getIncoterm(), + order.getIncoterm()) + + + def test_01_quantity_unit_copied(self): + """ + tests that when a resource uses different quantity unit that the + quantity units are copied on the packing list line and then the invoice + line using the delivery builers + """ + resource = self.portal.product_module.newContent( + portal_type='Product', + title='Resource', + product_line='apparel') + resource.setQuantityUnitList([self.unit_piece_quantity_unit, + self.mass_quantity_unit]) + currency = self.portal.currency_module.newContent( + portal_type='Currency', + title='euro') + currency.setBaseUnitQuantity(0.01) + transaction.commit() + self.tic()#execute transaction + client = self.portal.organisation_module.newContent( + portal_type='Organisation', + title='Client', + default_address_region=self.default_region) + vendor = self.portal.organisation_module.newContent( + portal_type='Organisation', + title='Vendor', + default_address_region=self.default_region) + order = self.portal.getDefaultModule(self.order_portal_type).newContent( + portal_type=self.order_portal_type, + source_value=vendor, + source_section_value=vendor, + destination_value=client, + destination_section_value=client, + start_date=DateTime(2008,10, 21), + price_currency_value=currency, + delivery_mode=self.mail_delivery_mode, + incoterm=self.cpt_incoterm, + title='Order') + first_order_line = order.newContent( + portal_type=self.order_line_portal_type, + resource_value=resource, + quantity_unit = self.unit_piece_quantity_unit, + quantity=5, + price=3) + second_order_line = order.newContent( + portal_type=self.order_line_portal_type, + resource_value=resource, + quantity_unit=self.mass_quantity_unit, + quantity=1.5, + price=2) + self.assertEquals(first_order_line.getQuantityUnit(), + self.unit_piece_quantity_unit) + self.assertEquals(second_order_line.getQuantityUnit(), + self.mass_quantity_unit) + + order.confirm() + transaction.commit() + self.tic() + related_packing_list = order.getCausalityRelatedValue( + portal_type=self.packing_list_portal_type) + self.assertNotEquals(related_packing_list, None) + movement_list = related_packing_list.getMovementList() + self.assertEquals(len(movement_list),2) + movement_list = sorted(movement_list, key=lambda x: x.getQuantity()) + self.assertEquals(movement_list[0].getQuantityUnit(), + self.mass_quantity_unit) + self.assertEquals(movement_list[0].getQuantity(), 1.5) + self.assertEquals(movement_list[1].getQuantityUnit(), + self.unit_piece_quantity_unit) + self.assertEquals(movement_list[1].getQuantity(), 5) + + related_packing_list.start() + related_packing_list.stop() + related_packing_list.deliver() + transaction.commit() + self.tic() + related_invoice = related_packing_list.getCausalityRelatedValue( + portal_type=self.invoice_portal_type) + self.assertNotEquals(related_invoice, None) + movement_list = related_invoice.getMovementList() + self.assertEquals(len(movement_list),2) + movement_list = sorted(movement_list, key=lambda x: x.getQuantity()) + self.assertEquals(movement_list[0].getQuantityUnit(), + self.mass_quantity_unit) + self.assertEquals(movement_list[0].getQuantity(), 1.5) + self.assertEquals(movement_list[1].getQuantityUnit(), + self.unit_piece_quantity_unit) + self.assertEquals(movement_list[1].getQuantity(), 5) + + + + def _acceptDivergenceOnInvoice(self, invoice, divergence_list): + builder_list = invoice.getBuilderList() + self.assertEquals(2, len(builder_list)) + for builder in builder_list: + builder.solveDivergence(invoice.getRelativeUrl(), + divergence_to_accept_list=divergence_list) + + def test_accept_quantity_divergence_on_invoice_with_stopped_packing_list( + self, quiet=quiet): + sequence_list = SequenceList() + sequence = sequence_list.addSequenceString(self.PACKING_LIST_DEFAULT_SEQUENCE) + sequence_list.play(self, quiet=quiet) + + packing_list = sequence.get('packing_list') + packing_list_line = packing_list.getMovementList()[0] + previous_quantity = packing_list_line.getQuantity() + + packing_list.setReady() + packing_list.start() + packing_list.stop() + self.assertEquals('stopped', packing_list.getSimulationState()) + transaction.commit() + self.tic() + + invoice = packing_list.getCausalityRelatedValue( + portal_type=self.invoice_portal_type) + self.assertNotEquals(invoice, None) + invoice_line_list = invoice.getMovementList() + self.assertEquals(1, len(invoice_line_list)) + invoice_line = invoice_line_list[0] + + new_quantity = invoice_line.getQuantity() * 2 + invoice_line.setQuantity(new_quantity) + + transaction.commit() + self.tic() + + self.assertTrue(invoice.isDivergent()) + divergence_list = invoice.getDivergenceList() + self.assertEquals(1, len(divergence_list)) + + divergence = divergence_list[0] + self.assertEquals('quantity', divergence.tested_property) + + # accept decision + self._acceptDivergenceOnInvoice(invoice, divergence_list) + + transaction.commit() + self.tic() + self.assertEquals('solved', invoice.getCausalityState()) + + self.assertEquals([], invoice.getDivergenceList()) + self.assertEquals(new_quantity, invoice_line.getQuantity()) + self.assertEquals(new_quantity, + invoice_line.getDeliveryRelatedValue(portal_type='Simulation Movement' + ).getQuantity()) + + self.assertEquals([], packing_list.getDivergenceList()) + self.assertEquals('solved', packing_list.getCausalityState()) + + def _adoptDivergenceOnInvoice(self, invoice, divergence_list): + builder_list = invoice.getBuilderList() + self.assertEquals(2, len(builder_list)) + for builder in builder_list: + builder.solveDivergence(invoice.getRelativeUrl(), + divergence_to_adopt_list=divergence_list) + + def test_adopt_quantity_divergence_on_invoice_line_with_stopped_packing_list( + self, quiet=quiet): + # #1053 + sequence_list = SequenceList() + sequence = sequence_list.addSequenceString(self.PACKING_LIST_DEFAULT_SEQUENCE) + sequence_list.play(self, quiet=quiet) + + packing_list = sequence.get('packing_list') + packing_list_line = packing_list.getMovementList()[0] + previous_quantity = packing_list_line.getQuantity() + previous_resource = packing_list_line.getResource() + previous_price = packing_list_line.getPrice() + + packing_list.setReady() + packing_list.start() + packing_list.stop() + self.assertEquals('stopped', packing_list.getSimulationState()) + transaction.commit() + self.tic() + + invoice = packing_list.getCausalityRelatedValue( + portal_type=self.invoice_portal_type) + self.assertNotEquals(invoice, None) + invoice_line_list = invoice.getMovementList() + self.assertEquals(1, len(invoice_line_list)) + invoice_line = invoice_line_list[0] + + new_quantity = invoice_line.getQuantity() * 2 + invoice_line.setQuantity(new_quantity) + + transaction.commit() + self.tic() + + self.assertTrue(invoice.isDivergent()) + divergence_list = invoice.getDivergenceList() + self.assertEquals(1, len(divergence_list)) + + divergence = divergence_list[0] + self.assertEquals('quantity', divergence.tested_property) + + # adopt prevision + self._adoptDivergenceOnInvoice(invoice, divergence_list) + + transaction.commit() + self.tic() + self.assertEquals([], invoice.getDivergenceList()) + self.assertEquals('solved', invoice.getCausalityState()) + + self.assertEquals(1, + len(invoice.getMovementList(portal_type=self.invoice_line_portal_type))) + self.assertEquals(0, + len(invoice.getMovementList(portal_type=self.invoice_transaction_line_portal_type))) + + self.assertEquals(previous_resource, invoice_line.getResource()) + self.assertEquals(previous_quantity, invoice_line.getQuantity()) + self.assertEquals(previous_price, invoice_line.getPrice()) + self.assertEquals(previous_quantity, + invoice_line.getDeliveryRelatedValue(portal_type='Simulation Movement' + ).getQuantity()) + + self.assertEquals([], packing_list.getDivergenceList()) + self.assertEquals('solved', packing_list.getCausalityState()) + + + +class TestSaleInvoiceMixin(TestInvoiceMixin, + ERP5TypeTestCase): + """Test sale invoice are created from orders then packing lists. + + Those tests methods only work for sale, because sale and purchase invoice + are not built at the same time on packing list workflow. + """ + quiet = 0 + invoice_portal_type = 'Sale Invoice Transaction' + invoice_line_portal_type = 'Invoice Line' + invoice_cell_portal_type = 'Invoice Cell' + invoice_transaction_line_portal_type = 'Sale Invoice Transaction Line' + + # default sequence for one line of not varianted resource. + PACKING_LIST_DEFAULT_SEQUENCE = """ + stepCreateEntities + stepCreateCurrency + stepCreateSaleInvoiceTransactionRule + stepCreateOrder + stepSetOrderProfile + stepSetOrderPriceCurrency + stepCreateNotVariatedResource + stepTic + stepCreateOrderLine + stepSetOrderLineResource + stepSetOrderLineDefaultValues + stepOrderOrder + stepTic + stepCheckDeliveryBuilding + stepConfirmOrder + stepTic + stepCheckOrderRule + stepCheckOrderSimulation + stepCheckDeliveryBuilding + stepAddPackingListContainer + stepAddPackingListContainerLine + stepSetContainerLineFullQuantity + stepTic + stepCheckPackingListIsPacked + """ + + # default sequence for two lines of not varianted resource. + PACKING_LIST_TWO_LINES_DEFAULT_SEQUENCE = """ + stepCreateEntities + stepCreateCurrency + stepCreateSaleInvoiceTransactionRule + stepCreateOrder + stepSetOrderProfile + stepSetOrderPriceCurrency + stepCreateNotVariatedResource + stepTic + stepCreateOrderLine + stepSetOrderLineResource + stepSetOrderLineDefaultValues + stepCreateNotVariatedResource + stepTic + stepCreateOrderLine + stepSetOrderLineResource + stepSetOrderLineDefaultValues + stepOrderOrder + stepTic + stepCheckDeliveryBuilding + stepConfirmOrder + stepTic + stepCheckOrderRule + stepCheckOrderSimulation + stepCheckDeliveryBuilding + stepAddPackingListContainer + stepAddPackingListContainerLine + stepTic + stepSetContainerFullQuantity + stepTic + stepCheckPackingListIsPacked + """ + + # default sequence for one line of not varianted resource. + TWO_PACKING_LIST_DEFAULT_SEQUENCE = """ + stepCreateEntities + stepCreateCurrency + stepCreateSaleInvoiceTransactionRule + stepCreateOrder + stepSetOrderProfile + stepSetOrderPriceCurrency + stepCreateNotVariatedResource + stepTic + stepCreateOrderLine + stepSetOrderLineResource + stepSetOrderLineDefaultValues + stepOrderOrder + stepTic + stepCheckDeliveryBuilding + stepConfirmOrder + stepTic + stepCheckOrderRule + stepCheckOrderSimulation + stepCheckDeliveryBuilding + stepDecreasePackingListLineQuantity + stepCheckPackingListIsCalculating + stepTic + stepCheckPackingListIsDiverged + stepSplitAndDeferPackingList + stepTic + stepCheckPackingListIsSolved + stepCheckPackingListSplitted + stepAddPackingListContainer + stepAddPackingListContainerLine + stepSetContainerLineFullQuantity + stepTic + stepCheckPackingListIsPacked + stepDefineNewPackingListContainer + stepTic + stepCheckNewPackingListIsPacked + """ + + def modifyPackingListState(self, transition_name, + sequence,packing_list=None): + """ calls the workflow for the packing list """ + if packing_list is None: + packing_list = sequence.get('packing_list') + packing_list.portal_workflow.doActionFor(packing_list, transition_name) + + def stepSetReadyPackingList(self, sequence=None, sequence_list=None, **kw): + """ set the Packing List as Ready. This must build the invoice. """ + self.modifyPackingListState('set_ready_action', sequence=sequence) + packing_list = sequence.get('packing_list') + self.assertEquals(packing_list.getSimulationState(), 'ready') + + def stepSetReadyNewPackingList(self, sequence=None, + sequence_list=None, **kw): + """ set the Packing List as Ready. This must build the invoice. """ + packing_list = sequence.get('new_packing_list') + self.modifyPackingListState('set_ready_action', sequence=sequence, + packing_list=packing_list) + self.assertEquals(packing_list.getSimulationState(), 'ready') + + def stepStartPackingList(self, sequence=None, sequence_list=None, **kw): + self.modifyPackingListState('start_action', sequence=sequence) + packing_list = sequence.get('packing_list') + self.assertEquals(packing_list.getSimulationState(), 'started') + + def stepStartNewPackingList(self, sequence=None, sequence_list=None, **kw): + packing_list = sequence.get('new_packing_list') + self.modifyPackingListState('start_action', sequence=sequence, + packing_list=packing_list) + self.assertEquals(packing_list.getSimulationState(), 'started') + + def stepStopPackingList(self, sequence=None, sequence_list=None, **kw): + self.modifyPackingListState('stop_action', sequence=sequence) + packing_list = sequence.get('packing_list') + self.assertEquals(packing_list.getSimulationState(), 'stopped') + + def stepDeliverPackingList(self, sequence=None, sequence_list=None, **kw): + self.modifyPackingListState('deliver_action', sequence=sequence) + packing_list = sequence.get('packing_list') + self.assertEquals(packing_list.getSimulationState(), 'delivered') + + def stepCancelPackingList(self, sequence=None, sequence_list=None, **kw): + self.modifyPackingListState('cancel_action', sequence=sequence) + packing_list = sequence.get('packing_list') + self.assertEquals(packing_list.getSimulationState(), 'cancelled') + + def modifyInvoiceState(self, transition_name, + sequence,invoice=None): + """ calls the workflow for the invoice """ + if invoice is None: + invoice = sequence.get('invoice') + invoice.portal_workflow.doActionFor(invoice, transition_name) + + def stepStartInvoice(self, sequence=None, sequence_list=None, **kw): + self.modifyInvoiceState('start_action', sequence=sequence) + invoice = sequence.get('invoice') + self.assertEquals(invoice.getSimulationState(), 'started') + + def stepStartNewInvoice(self, sequence=None, sequence_list=None, **kw): + invoice = sequence.get('new_invoice') + self.modifyInvoiceState('start_action', sequence=sequence, + invoice=invoice) + self.assertEquals(invoice.getSimulationState(), 'started') + + def stepStopInvoice(self, sequence=None, sequence_list=None, **kw): + self.modifyInvoiceState('stop_action', sequence=sequence) + invoice = sequence.get('invoice') + self.assertEquals(invoice.getSimulationState(), 'stopped') + + def stepDeliverInvoice(self, sequence=None, sequence_list=None, **kw): + self.modifyInvoiceState('deliver_action', sequence=sequence) + invoice = sequence.get('invoice') + self.assertEquals(invoice.getSimulationState(), 'delivered') + + def stepCancelInvoice(self, sequence=None, sequence_list=None, **kw): + self.modifyInvoiceState('cancel_action', sequence=sequence) + invoice = sequence.get('invoice') + self.assertEquals(invoice.getSimulationState(), 'cancelled') + + + def stepSwitchPackingLists(self, sequence=None, sequence_list=None, **kw): + packing_list = sequence.get('packing_list') + new_packing_list = sequence.get('new_packing_list') + #invoice = new_packing_list.getDefaultCausalityRelatedValue( + #portal_type=self.invoice_portal_type) + sequence.edit(packing_list=new_packing_list, + new_packing_list=packing_list)#, invoice=invoice) + + def stepSwitchInvoices(self, sequence=None, sequence_list=None, **kw): + invoice = sequence.get('invoice') + new_invoice = sequence.get('new_invoice') + sequence.edit(invoice=new_invoice, new_invoice=invoice) + + def stepCheckPackingListSimulation(self, sequence=None, sequence_list=None, **kw): + """ checks that simulation movements related to the packing list are OK """ + packing_list = sequence.get('packing_list') + order = sequence.get('order') + order_root_applied_rule = order.getCausalityRelatedValueList( + portal_type = 'Applied Rule')[0] + # check simulation movements from this packing list + for movement in packing_list.getMovementList() : + simulation_movement_list = movement.getOrderRelatedValueList() + self.assertNotEquals(len(simulation_movement_list), 0) + total_quantity = 0 + for simulation_movement in simulation_movement_list : + total_quantity += simulation_movement.getQuantity() + # check that those movements come from the same root applied + # rule than the order. + self.assertEquals( simulation_movement.getRootAppliedRule(), + order_root_applied_rule) + self.assertEquals(total_quantity, movement.getQuantity()) + + def stepCheckInvoiceBuilding(self, sequence=None, sequence_list=None, **kw): + """ + checks that the invoice is built with the default_invoice_builder + """ + packing_list = sequence.get('packing_list') + related_invoice_list = packing_list.getCausalityRelatedValueList( + portal_type=self.invoice_portal_type) + + packing_list_building_state = 'started' + packing_list_state = packing_list.getSimulationState() + if packing_list_state != packing_list_building_state : + self.assertEquals(0, len(related_invoice_list)) + else: + self.assertEquals(1, len(related_invoice_list)) + + invoice = related_invoice_list[0].getObject() + self.failUnless(invoice is not None) + # Invoices created by Delivery Builder are in confirmed state + self.assertEquals(invoice.getSimulationState(), 'confirmed') + + # Get the list of simulation movements of packing list ... + packing_list_simulation_movement_list = [] + for packing_list_movement in packing_list.getMovementList(): + packing_list_simulation_movement_list.extend( + packing_list_movement.getDeliveryRelatedValueList()) + # ... invoice simulation movement are their childrens. + simulation_movement_list = [] + for p_l_simulation_movement in packing_list_simulation_movement_list : + for applied_rule in p_l_simulation_movement.objectValues() : + simulation_movement_list.extend(applied_rule.objectValues()) + + # First, test if each Simulation Movement is related to an + # Invoice Movement + invoice_relative_url = invoice.getRelativeUrl() + for simulation_movement in simulation_movement_list: + invoice_movement_list = simulation_movement.getDeliveryValueList() + self.assertEquals(len(invoice_movement_list), 1) + invoice_movement = invoice_movement_list[0] + self.failUnless(invoice_movement is not None) + self.assert_(invoice_movement.getRelativeUrl().\ + startswith(invoice_relative_url)) + + # Then, test if each Invoice movement is equals to the sum of somes + # Simulation Movements + for invoice_movement in invoice.getMovementList(portal_type = [ + self.invoice_cell_portal_type, + self.invoice_line_portal_type]) : + related_simulation_movement_list = invoice_movement.\ + getDeliveryRelatedValueList(portal_type='Simulation Movement') + quantity = 0 + total_price = 0 + invoice_movement_quantity = invoice_movement.getQuantity() + for related_simulation_movement in related_simulation_movement_list: + quantity += related_simulation_movement.getQuantity() + total_price += related_simulation_movement.getPrice() *\ + related_simulation_movement.getQuantity() + # Test resource + self.assertEquals(invoice_movement.getResource(), \ + related_simulation_movement.getResource()) + # Test resource variation + self.assertEquals(invoice_movement.getVariationText(), \ + related_simulation_movement.getVariationText()) + self.assertEquals(invoice_movement.getVariationCategoryList(), \ + related_simulation_movement.getVariationCategoryList()) + # Test acquisition + self.checkAcquisition(invoice_movement, + related_simulation_movement) + # Test delivery ratio + self.assertEquals(related_simulation_movement.getQuantity() /\ + invoice_movement_quantity, \ + related_simulation_movement.getDeliveryRatio()) + + self.assertEquals(quantity, invoice_movement.getQuantity()) + # Test price + self.assertEquals(total_price / quantity, invoice_movement.getPrice()) + + sequence.edit(invoice = invoice) + + # Test causality + self.assertEquals(len(invoice.getCausalityValueList( + portal_type = self.packing_list_portal_type)), 1) + self.assertEquals(invoice.getCausalityValue(), packing_list) + + # Finally, test getTotalQuantity and getTotalPrice on Invoice + self.assertEquals(packing_list.getTotalQuantity(), + invoice.getTotalQuantity()) + self.assertEquals(packing_list.getTotalPrice(), + invoice.getTotalPrice()) + + + def stepCheckInvoicesConsistency(self, sequence=None, sequence_list=None, + **kw): + """ + Checks that all invoices are consistent: + - transaction lines match invoice lines + - no movement is divergent + """ + invoice_list = self.getPortal()['accounting_module'].objectValues() + for invoice in invoice_list: + accounting_state_list = \ + list(self.getPortal().getPortalCurrentInventoryStateList()) + accounting_state_list.append('cancelled') + if invoice.getSimulationState() in accounting_state_list: + invoice_line_list = invoice.contentValues( + portal_type=self.invoice_line_portal_type) + invoice_transaction_line_list = invoice.contentValues( + portal_type=self.invoice_transaction_line_portal_type) + self.assertEquals(3, len(invoice_transaction_line_list)) + expected_price = 0.0 + for line in invoice_line_list: + expected_price += line.getTotalPrice() + for line_id, line_source, line_dest, line_ratio in \ + self.transaction_line_definition_list: + for line in invoice.contentValues( + portal_type=self.invoice_transaction_line_portal_type): + if line.getSource() == 'account_module/%s' % line_source and \ + line.getDestination() == 'account_module/%s' % line_dest: + break + else: + self.fail('No line found that matches %s' % line_id) + resource_precision = line.getResourceValue().getQuantityPrecision() + self.assertEquals(round(line.getQuantity(), resource_precision), + round(expected_price * line_ratio, resource_precision)) + + def stepCheckInvoiceLineHasReferenceAndIntIndex(self, sequence=None, **kw): + """Check that the unique invoice line in the invoice has reference and int + index. + """ + invoice = sequence.get('invoice') + invoice_line_list = invoice.contentValues( + portal_type=self.invoice_line_portal_type) + self.assertEquals(1, len(invoice_line_list)) + invoice_line = invoice_line_list[0] + self.assertEquals(1, invoice_line.getIntIndex()) + self.assertEquals('1', invoice_line.getReference()) + + def stepCheckPackingListInvoice( + self, sequence=None, sequence_list=None, **kw): + """ Checks if the delivery builder is working as expected, + coping the atributes from packing list to invoice.""" + packing_list = sequence.get('packing_list') + related_invoice_list = packing_list.getCausalityRelatedValueList( + portal_type=self.invoice_portal_type) + self.assertEquals(len(related_invoice_list), 1) + invoice = related_invoice_list[0] + self.assertEquals(packing_list.getSource(), invoice.getSource()) + self.assertEquals(packing_list.getDestination(), invoice.getDestination()) + self.assertEquals(packing_list.getDestinationSection(), \ + invoice.getDestinationSection()) + self.assertEquals(packing_list.getSourceSection(), \ + invoice.getSourceSection()) + self.assertEquals(packing_list.getDestinationDecision(), \ + invoice.getDestinationDecision()) + self.assertEquals(packing_list.getSourceDecision(), \ + invoice.getSourceDecision()) + self.assertEquals(packing_list.getDestinationAdministration(), \ + invoice.getDestinationAdministration()) + self.assertEquals(packing_list.getSourceAdministration(), \ + invoice.getSourceAdministration()) + self.assertEquals(packing_list.getDestinationProject(), \ + invoice.getDestinationProject()) + self.assertEquals(packing_list.getSourceProject(), \ + invoice.getSourceProject()) + self.assertEquals(packing_list.getPriceCurrency(), \ + invoice.getPriceCurrency()) + + + + def stepCheckDeliveryRuleForDeferred( + self, sequence=None, sequence_list=None, **kw): + """ Checks that a delivery rule has been created when we took 'split + and defer' decision on the divergeant Packing List. """ + # TODO + + def stepCheckDeliveryRuleIsEmpty( + self, sequence=None, sequence_list=None, **kw): + """ Checks that an empty delivery rule is created for the + convergeant Packing List""" + packing_list = sequence.get('packing_list') + self.failUnless(packing_list is not None) + simulation_tool = self.getSimulationTool() + # Check that there is an applied rule for our packing list + rule_list = [x for x in simulation_tool.objectValues() + if x.getCausalityValue()==packing_list] + self.assertEquals(len(rule_list),1) + packing_list_rule = rule_list[0] + sequence.edit(packing_list_rule=packing_list_rule) + rule_line_list = packing_list_rule.objectValues() + packing_list_line_list = packing_list.objectValues() + self.assertEquals(len(packing_list_line_list), + len(rule_line_list)) + self.assertEquals(1, len(rule_line_list)) + rule_line = rule_line_list[0] + packing_list_line = packing_list_line_list[0] + self.assertEquals(rule_line.getQuantity(), 10) + self.assertEquals(rule_line.getPrice(), 100) + self.assertEquals(rule_line.getDeliveryValue(), + packing_list_line) + self.assertEquals(rule_line.getStartDate(), + packing_list_line.getStartDate()) + self.assertEquals(rule_line.getStopDate(), + packing_list_line.getStopDate()) + self.assertEquals(rule_line.getPortalType(), + 'Simulation Movement') + + + def stepCheckPackingList(self,sequence=None, sequence_list=None,**kw): + """ """ + packing_list_module = self.getSalePackingListModule() + order_rule = sequence.get('order_rule') + order = sequence.get('order') + sale_packing_list_list = [] + for o in packing_list_module.objectValues(): + if o.getCausalityValue() == order: + sale_packing_list_list.append(o) + self.assertEquals(len(sale_packing_list_list), 1) + sale_packing_list = sale_packing_list_list[0] + sale_packing_list_line_list = sale_packing_list.objectValues() + self.assertEquals(len(sale_packing_list_line_list),1) + sale_packing_list_line = sale_packing_list_line_list[0] + product = sequence.get('resource') + self.assertEquals(sale_packing_list_line.getResourceValue(), + product) + self.assertEquals(sale_packing_list_line.getPrice(), + self.price1) + LOG('sale_packing_list_line.showDict()',0, + sale_packing_list_line.showDict()) + self.assertEquals(sale_packing_list_line.getQuantity(), + self.quantity1) + self.assertEquals(sale_packing_list_line.getTotalPrice(), + self.total_price1) + sequence.edit(packing_list = sale_packing_list) + + def stepCheckTwoInvoices(self,sequence=None, sequence_list=None, **kw): + """ checks invoice properties are well set. """ + # Now we will check that we have two invoices created + packing_list = sequence.get('packing_list') + invoice_list = packing_list.getCausalityRelatedValueList( + portal_type=self.invoice_portal_type) + self.assertEquals(len(invoice_list),1) + invoice = invoice_list[0] + self.assertEquals(invoice.getSimulationState(), 'confirmed') + sequence.edit(invoice=invoice) + new_packing_list = sequence.get('new_packing_list') + new_invoice_list = new_packing_list.getCausalityRelatedValueList( + portal_type=self.invoice_portal_type) + self.assertEquals(len(new_invoice_list),1) + new_invoice = new_invoice_list[0] + self.assertEquals(new_invoice.getSimulationState(), 'confirmed') + sequence.edit(new_invoice=new_invoice) + + def stepStartTwoInvoices(self,sequence=None, sequence_list=None, **kw): + """ start both invoices. """ + portal = self.getPortal() + invoice = sequence.get('invoice') + new_invoice = sequence.get('new_invoice') + portal.portal_workflow.doActionFor(invoice, 'start_action') + portal.portal_workflow.doActionFor(new_invoice, 'start_action') + + def stepCheckTwoInvoicesTransactionLines(self,sequence=None, + sequence_list=None, **kw): + """ checks invoice properties are well set. """ + invoice = sequence.get('invoice') + new_invoice = sequence.get('new_invoice') + self.assertEquals(3,len(invoice.objectValues( + portal_type=self.invoice_transaction_line_portal_type))) + self.assertEquals(3,len(new_invoice.objectValues( + portal_type=self.invoice_transaction_line_portal_type))) + account_module = self.getAccountModule() + found_dict = {} + for line in invoice.objectValues( + portal_type=self.invoice_transaction_line_portal_type): + source_id = line.getSourceId() + found_dict[source_id] = line.getQuantity() + total_price = (self.default_quantity-1) * self.default_price + expected_dict = { + 'sale' : total_price, + 'receivable_vat' : total_price * self.vat_rate, + 'customer' : - (total_price + total_price * self.vat_rate) + } + self.failIfDifferentSet(expected_dict.keys(),found_dict.keys()) + for key in found_dict.keys(): + self.assertAlmostEquals(expected_dict[key],found_dict[key],places=2) + found_dict = {} + for line in new_invoice.objectValues( + portal_type=self.invoice_transaction_line_portal_type): + source_id = line.getSourceId() + found_dict[source_id] = line.getQuantity() + total_price = 1 * self.default_price + expected_dict = { + 'sale' : total_price, + 'receivable_vat' : total_price * self.vat_rate, + 'customer' : - (total_price + total_price * self.vat_rate) + } + self.failIfDifferentSet(expected_dict.keys(), found_dict.keys()) + for key in found_dict.keys(): + self.assertAlmostEquals(expected_dict[key], found_dict[key], places=2) + + def stepRebuildAndCheckNothingIsCreated(self, sequence=None, + sequence_list=None, **kw): + """Rebuilds with sale_invoice_builder and checks nothing more is + created. """ + accounting_module = self.getAccountingModule() + portal_type_list = ('Sale Invoice Transaction', 'Purchase Invoice Transaction') + sale_invoice_transaction_count = len(accounting_module.objectValues( + portal_type=portal_type_list)) + for builder in self.getPortal().portal_deliveries.objectValues(): + builder.build() + self.assertEquals(sale_invoice_transaction_count, + len(accounting_module.objectValues( + portal_type=portal_type_list))) + + def stepModifyInvoicesDate(self, sequence=None, + sequence_list=None, **kw): + """Change invoice date""" + invoice = sequence.get('invoice') + new_invoice = sequence.get('new_invoice') + invoice.edit(start_date=self.datetime, + stop_date=self.datetime+1) + new_invoice.edit(start_date=self.datetime, + stop_date=self.datetime+1) + + def stepRemoveDateMovementGroupForTransactionBuilder(self, sequence=None, + sequence_list=None, **kw): + """ + Remove DateMovementGroup + """ + portal = self.getPortal() + builder = portal.portal_deliveries.sale_invoice_transaction_builder + delivery_movement_group_list = builder.getDeliveryMovementGroupList() + uf = self.getPortal().acl_users + uf._doAddUser('admin', '', ['Manager'], []) + user = uf.getUserById('admin').__of__(uf) + newSecurityManager(None, user) + for movement_group in delivery_movement_group_list: + if movement_group.getPortalType() == 'Property Movement Group': + # it contains 'start_date' and 'stop_date' only, so we remove + # movement group itself. + builder.deleteContent(movement_group.getId()) + builder.newContent( + portal_type = 'Parent Explanation Movement Group', + collect_order_group='delivery', + int_index=len(delivery_movement_group_list)+1 + ) + user = uf.getUserById('test_invoice_user').__of__(uf) + newSecurityManager(None, user) + + def stepEditInvoice(self, sequence=None, sequence_list=None, **kw): + """Edit the current invoice, to trigger updateAppliedRule.""" + invoice = sequence.get('invoice') + invoice.edit() + + # call updateAppliedRule directly, don't rely on edit interactions + rule_reference = 'default_invoice_rule' + self.assertNotEquals(0, + len(self.portal.portal_rules.searchFolder(reference=rule_reference))) + invoice.updateAppliedRule(rule_reference=rule_reference) + + def stepCheckInvoiceRuleNotAppliedOnInvoiceEdit(self, + sequence=None, sequence_list=None, **kw): + """If we call edit on the invoice, invoice rule should not be + applied on lines created by delivery builder.""" + invoice = sequence.get('invoice') + # FIXME: empty applied rule should not be created + #self.assertEquals(len(invoice.getCausalityRelatedValueList( + # portal_type=self.applied_rule_portal_type)), 0) + for invoice_mvt in invoice.getMovementList(): + self.assertEquals(len(invoice_mvt.getOrderRelatedValueList( + portal_type=self.simulation_movement_portal_type)), 0) + + def stepEditPackingList(self, sequence=None, sequence_list=None, **kw): + """Edit the current packing list, to trigger updateAppliedRule.""" + packing_list = sequence.get('packing_list') + packing_list.edit() + + # call updateAppliedRule directly, don't rely on edit interactions + rule_reference = 'default_delivery_rule' + self.assertNotEquals(0, + len(self.portal.portal_rules.searchFolder(reference=rule_reference))) + packing_list.updateAppliedRule(rule_reference=rule_reference) + + def stepCheckDeliveryRuleNotAppliedOnPackingListEdit(self, + sequence=None, sequence_list=None, **kw): + """If we call edit on the packing list, delivery rule should not be + applied on lines created by delivery builder.""" + packing_list = sequence.get('packing_list') + # FIXME: empty applied rule should not be created + #self.assertEquals(len(packing_list.getCausalityRelatedValueList( + # portal_type=self.applied_rule_portal_type)), 0) + for delivery_mvt in packing_list.getMovementList(): + self.assertEquals(len(delivery_mvt.getOrderRelatedValueList( + portal_type=self.simulation_movement_portal_type)), 0) + + def stepDecreaseInvoiceLineQuantity(self, sequence=None, sequence_list=None, + **kw): + """ + Set a decreased quantity on invoice lines + """ + invoice = sequence.get('invoice') + quantity = sequence.get('line_quantity',default=self.default_quantity) + quantity = quantity - 1 + sequence.edit(line_quantity=quantity) + for invoice_line in invoice.objectValues( + portal_type=self.invoice_line_portal_type): + invoice_line.edit(quantity=quantity) + sequence.edit(last_delta = sequence.get('last_delta', 0.0) - 1.0) + + def stepIncreaseInvoiceLineQuantity(self, sequence=None, sequence_list=None, + **kw): + """ + Set a Increased quantity on invoice lines + """ + invoice = sequence.get('invoice') + quantity = sequence.get('line_quantity',default=self.default_quantity) + quantity = quantity + 1 + sequence.edit(line_quantity=quantity) + for invoice_line in invoice.objectValues( + portal_type=self.invoice_line_portal_type): + invoice_line.edit(quantity=quantity) + sequence.edit(last_delta = sequence.get('last_delta', 0.0) + 1.0) + + def stepSetInvoiceLineQuantityToZero(self, sequence=None, sequence_list=None, + **kw): + """ + Set the quantity on invoice lines to zero + """ + invoice = sequence.get('invoice') + #default_quantity = sequence.get('line_quantity',default_quantity) + quantity = 0.0 + sequence.edit(line_quantity=quantity) + for invoice_line in invoice.objectValues( + portal_type=self.invoice_line_portal_type): + invoice_line.edit(quantity=quantity) + sequence.edit(last_delta = - self.default_quantity) + + def stepChangeInvoiceStartDate(self, sequence=None, sequence_list=None, **kw): + """ + Change the start_date of the invoice. + """ + invoice = sequence.get('invoice') + invoice.edit(start_date=self.datetime + 15) + + def stepCheckInvoiceIsCalculating(self, sequence=None, sequence_list=None, + **kw): + """ + Test if invoice is calculating + """ + invoice = sequence.get('invoice') + self.assertEquals('calculating',invoice.getCausalityState()) + + def stepCheckInvoiceIsDiverged(self, sequence=None, sequence_list=None, + **kw): + """ + Test if invoice is diverged + """ + invoice = sequence.get('invoice') + self.assertEquals('diverged',invoice.getCausalityState()) + + def stepCheckInvoiceIsSolved(self, sequence=None, sequence_list=None, + **kw): + """ + Test if invoice is solved + """ + invoice = sequence.get('invoice') + self.assertEquals('solved', invoice.getCausalityState(), + invoice.getDivergenceList()) + + def stepCheckInvoiceIsDivergent(self, sequence=None, sequence_list=None, + **kw): + """ + Test if invoice is divergent + """ + invoice = sequence.get('invoice') + self.assertTrue(invoice.isDivergent()) + + def stepCheckInvoiceIsNotDivergent(self, sequence=None, sequence_list=None, + **kw): + """ + Test if invoice is not divergent + """ + invoice = sequence.get('invoice') + if invoice.isDivergent(): + self.fail(invoice.getDivergenceList()) + + def stepSplitAndDeferInvoice(self, sequence=None, sequence_list=None, + **kw): + """ + split and defer at the invoice level + """ + invoice = sequence.get('invoice') + kw = {'listbox':[ + {'listbox_key':line.getRelativeUrl(), + 'choice':'SplitAndDefer'} for line in invoice.getMovementList()]} + self.portal.portal_workflow.doActionFor( + invoice, + 'split_and_defer_action', + start_date=self.datetime + 15, + stop_date=self.datetime + 25, + **kw) + pass + + def stepUnifyStartDateWithDecisionInvoice(self, sequence=None, + sequence_list=None): + invoice = sequence.get('invoice') + self._solveDeliveryGroupDivergence(invoice, 'start_date', + invoice.getRelativeUrl()) + + def stepAcceptDecisionQuantityInvoice(self,sequence=None, sequence_list=None): + invoice = sequence.get('invoice') + self._solveDivergence(invoice, 'quantity', 'accept') + + def stepAcceptDecisionInvoice(self, sequence=None, sequence_list=None, + **kw): + """ + accept decision at the invoice level + """ + invoice = sequence.get('invoice') + invoice.portal_workflow.doActionFor(invoice,'accept_decision_action') + + def stepCheckInvoiceSplitted(self, sequence=None, sequence_list=None, **kw): + """ + Test if invoice was splitted + """ + packing_list = sequence.get('packing_list') + invoice_list = packing_list.getCausalityRelatedValueList( + portal_type=self.invoice_portal_type) + self.assertEquals(2,len(invoice_list)) + invoice1 = None + invoice2 = None + for invoice in invoice_list: + if invoice.getUid() == sequence.get('invoice').getUid(): + invoice1 = invoice + else: + invoice2 = invoice + sequence.edit(new_invoice=invoice2) + for line in invoice1.objectValues( + portal_type=self.invoice_line_portal_type): + self.assertEquals(self.default_quantity-1,line.getQuantity()) + for line in invoice2.objectValues( + portal_type=self.invoice_line_portal_type): + self.assertEquals(1,line.getQuantity()) + + def stepCheckInvoiceNotSplitted(self, sequence=None, sequence_list=None, **kw): + """ + Test if invoice was not splitted + """ + packing_list = sequence.get('packing_list') + invoice_list = packing_list.getCausalityRelatedValueList( + portal_type=self.invoice_portal_type) + self.assertEquals(1,len(invoice_list)) + invoice1 = None + for invoice in invoice_list: + if invoice.getUid() == sequence.get('invoice').getUid(): + invoice1 = invoice + last_delta = sequence.get('last_delta', 0.0) + for line in invoice1.objectValues( + portal_type=self.invoice_line_portal_type): + self.assertEquals(self.default_quantity + last_delta, + line.getQuantity()) + + def stepAddInvoiceLines(self, sequence=None, sequence_list=[]): + """ + add some invoice and accounting lines to the invoice + """ + invoice = sequence.get('invoice') + invoice.newContent(portal_type='Invoice Line', + resource_value=sequence.get('resource'), quantity=3, price=555) + invoice.newContent(portal_type='Sale Invoice Transaction Line', + id ='receivable', source='account_module/customer', + destination='account_module/supplier', quantity=-1665) + invoice.newContent(portal_type='Sale Invoice Transaction Line', + id='income', source='account_module/sale', + destination='account_module/purchase', quantity=1665) + + def stepAddWrongInvoiceLines(self, sequence=None, sequence_list=[]): + """ + add some wrong invoice and accounting lines to the invoice + """ + invoice = sequence.get('invoice') + invoice.newContent(portal_type='Sale Invoice Transaction Line', + id='bad_movement', source='account_module/sale', + destination='account_module/purchase', quantity=2, price=4) + invoice.newContent(portal_type='Sale Invoice Transaction Line', + id='counter_bad_movement', source='account_module/sale', + destination='account_module/purchase', quantity=-2, price=4) + for movement in invoice.getMovementList(): + movement.edit(resource_value=sequence.get('resource')) + + def stepCheckStartInvoiceFail(self, sequence=None, sequence_list=[]): + """ + checks that it's not possible to start an invoice with really wrong + lines + """ + try: + self.tic() + except RuntimeError, exc: + invoice = sequence.get('invoice') + it_builder = self.portal.portal_deliveries.sale_invoice_transaction_builder + # check which activities are failing + self.assertTrue(str(exc).startswith('tic is looping forever.'), + '%s does not start with "tic is looping forever."' % str(exc)) + msg_list = ['/'.join(x.object_path) for x in + self.getActivityTool().getMessageList()] + self.assertTrue(it_builder.getPath() in msg_list, '%s in %s' % + (it_builder.getPath(), msg_list)) + # flush failing activities + activity_tool = self.getActivityTool() + activity_tool.manageClearActivities(keep=0) + else: + self.fail("Error: stepStartInvoice didn't fail, the builder script" + + " InvoiceTransaction_postTransactionLineGeneration should have" + + " complained that accounting movements use multiple resources") + + def stepCheckSimulationTrees(self, sequence=None, sequence_list=[]): + """ + check that rules are created in the order we expect them + """ + applied_rule_set = set() + invoice = sequence.get('invoice') + for movement in invoice.getMovementList(): + for sm in movement.getDeliveryRelatedValueList(): + applied_rule_set.add(sm.getRootAppliedRule()) + + rule_dict = { + 'Order Rule': { + 'movement_type_list': ['Sale Packing List Line', 'Sale Packing List Cell'], + 'next_rule_list': ['Invoicing Rule', ], + }, + 'Invoicing Rule': { + 'movement_type_list': invoice.getPortalInvoiceMovementTypeList(), + 'next_rule_list': ['Invoice Transaction Rule', 'Trade Model Rule'], + }, + 'Trade Model Rule': { + 'next_rule_list': ['Invoice Transaction Rule'], + }, + 'Invoice Rule': { + 'movement_type_list': invoice.getPortalInvoiceMovementTypeList() \ + + invoice.getPortalAccountingMovementTypeList(), + 'next_rule_list': ['Invoice Transaction Rule', 'Payment Rule', + 'Trade Model Rule'], + }, + 'Invoice Transaction Rule': { + 'parent_movement_type_list': invoice.getPortalInvoiceMovementTypeList(), + 'movement_type_list': invoice.getPortalAccountingMovementTypeList(), + 'next_rule_list': ['Payment Rule'], + }, + 'Payment Rule': { + 'parent_movement_type_list': invoice.getPortalAccountingMovementTypeList(), + 'next_rule_list': [], + }, + } + + def checkTree(rule): + """ + checks the tree recursively + """ + rule_type = rule.getSpecialiseValue().getPortalType() + rule_def = rule_dict.get(rule_type, {}) + for k, v in rule_def.iteritems(): + if k == 'movement_type_list': + for movement in rule.objectValues(): + if movement.getDeliveryValue() is not None: + self.assertTrue(movement.getDeliveryValue().getPortalType() in v, + 'looking for %s in %s on %s' % ( + movement.getDeliveryValue().getPortalType(), v, + movement.getPath())) + elif k == 'next_rule_list': + for movement in rule.objectValues(): + found_rule_dict = {} + for next_rule in movement.objectValues(): + next_rule_type = next_rule.getSpecialiseValue().getPortalType() + self.assertTrue(next_rule_type in v, + 'looking for %s in %s on %s' % ( + next_rule_type, v, next_rule.getPath())) + n = found_rule_dict.get(next_rule_type, 0) + found_rule_dict[next_rule_type] = n + 1 + # for each movement, we want to make sure that each rule is not + # instanciated more than once + if len(found_rule_dict): + self.assertEquals(set(found_rule_dict.itervalues()), set([1])) + elif k == 'parent_movement_type_list': + if rule.getParentValue().getDeliveryValue() is not None: + parent_type = rule.getParentValue().getDeliveryValue().getPortalType() + self.assertTrue(parent_type in v, 'looking for %s in %s on %s' % ( + parent_type, v, rule.getParentValue().getPath())) + elif k == 'parent_id_list': + self.assertTrue(rule.getParentId() in v, 'looking for %s in %s on %s' + % (rule.getParentId(), v, rule.getPath())) + for movement in rule.objectValues(): + for next_rule in movement.objectValues(): + checkTree(next_rule) + + for applied_rule in applied_rule_set: + checkTree(applied_rule) + + +class TestSaleInvoice(TestSaleInvoiceMixin, TestInvoice, ERP5TypeTestCase): + """Tests for sale invoice. + """ + quiet = 0 + + # fix inheritance + login = TestInvoiceMixin.login + + @UnrestrictedMethod + def createCategories(self): + TestPackingListMixin.createCategories(self) + TestInvoiceMixin.createCategories(self) + + getNeededCategoryList = TestInvoiceMixin.getNeededCategoryList + + def test_01_SimpleInvoice(self, quiet=quiet): + """ + Checks that a Simple Invoice is created from a Packing List + """ + if not quiet: + self.logMessage('Simple Invoice') + sequence_list = SequenceList() + for base_sequence in (self.PACKING_LIST_DEFAULT_SEQUENCE, ) : + sequence_list.addSequenceString( + base_sequence + + """ + stepSetReadyPackingList + stepTic + stepStartPackingList + stepCheckInvoicingRule + stepTic + stepCheckInvoiceBuilding + stepRebuildAndCheckNothingIsCreated + stepCheckInvoicesConsistency + stepCheckInvoiceLineHasReferenceAndIntIndex + """) + sequence_list.play(self, quiet=quiet) + + def test_02_TwoInvoicesFromTwoPackingList(self, quiet=quiet): + """ + This test was created for the following bug: + - an order is created and confirmed + - the packing list is split + - the 2 packing list are delivered (at different date) + - 2 invoices are built, then we set the same date on both of them + - the accounting rules are generated and put in only one invoice !!, + so we have an invoice with twice the number of accounting rules + and an invoice with no accounting rules. both invoices are wrong + """ + if not quiet: self.logMessage('Two Invoices from Two Packing List') + sequence_list = SequenceList() + for base_sequence in (self.TWO_PACKING_LIST_DEFAULT_SEQUENCE, ) : + sequence_list.addSequenceString( + base_sequence + + """ + stepSetReadyPackingList + stepSetReadyNewPackingList + stepTic + stepStartPackingList + stepStartNewPackingList + stepTic + stepCheckTwoInvoices + stepRemoveDateMovementGroupForTransactionBuilder + stepStartTwoInvoices + stepTic + stepCheckTwoInvoicesTransactionLines + stepCheckInvoicesConsistency + """) + sequence_list.play(self, quiet=quiet) + + def test_03_InvoiceEditAndInvoiceRule(self, quiet=quiet): + """ + Invoice Rule should not be applied on invoice lines created from\ + Packing List. + + We want to prevent this from happening: + - Create a packing list + - An invoice is created from packing list + - Invoice is edited, updateAppliedRule is called + - A new Invoice Rule is created for this invoice, and accounting + movements for this invoice are present twice in the simulation. + """ + if not quiet: + self.logMessage('Invoice Edit') + sequence_list = SequenceList() + for base_sequence in (self.PACKING_LIST_DEFAULT_SEQUENCE, ) : + sequence_list.addSequenceString( + base_sequence + + """ + stepSetReadyPackingList + stepTic + stepStartPackingList + stepCheckInvoicingRule + stepTic + stepCheckInvoiceBuilding + stepEditInvoice + stepCheckInvoiceRuleNotAppliedOnInvoiceEdit + stepCheckInvoicesConsistency + """) + sequence_list.play(self, quiet=quiet) + + def test_04_PackingListEditAndInvoiceRule(self, quiet=quiet): + """ + Delivery Rule should not be applied on packing list lines created\ + from Order. + """ + if not quiet: + self.logMessage('Packing List Edit') + sequence_list = SequenceList() + for base_sequence in (self.PACKING_LIST_DEFAULT_SEQUENCE, ) : + sequence_list.addSequenceString( + base_sequence + + """ + stepEditPackingList + stepCheckDeliveryRuleNotAppliedOnPackingListEdit + stepCheckInvoicesConsistency + """) + sequence_list.play(self, quiet=quiet) + + def test_05_InvoiceEditPackingListLine(self, quiet=quiet): + """ + Checks that editing a Packing List Line still creates a correct + Invoice + """ + if not quiet: + self.logMessage('Packing List Line Edit') + sequence_list = SequenceList() + for base_sequence in (self.PACKING_LIST_DEFAULT_SEQUENCE, ) : + sequence_list.addSequenceString( + base_sequence + + """ + stepEditPackingListLine + stepSetReadyPackingList + stepTic + stepStartPackingList + stepCheckInvoicingRule + stepTic + stepCheckInvoiceBuilding + stepRebuildAndCheckNothingIsCreated + stepCheckInvoicesConsistency + """) + sequence_list.play(self, quiet=quiet) + + def test_06_InvoiceDeletePackingListLine(self, quiet=quiet): + """ + Checks that deleting a Packing List Line still creates a correct + Invoice + """ + if not quiet: + self.logMessage('Packing List Line Delete') + sequence_list = SequenceList() + for base_sequence in (self.PACKING_LIST_TWO_LINES_DEFAULT_SEQUENCE, ) : + sequence_list.addSequenceString( + base_sequence + + """ + stepDeletePackingListLine + stepSetReadyPackingList + stepTic + stepStartPackingList + stepCheckInvoicingRule + stepTic + stepCheckInvoiceBuilding + stepRebuildAndCheckNothingIsCreated + stepCheckInvoicesConsistency + """) + sequence_list.play(self, quiet=quiet) + + def test_07_InvoiceAddPackingListLine(self, quiet=quiet): + """ + Checks that adding a Packing List Line still creates a correct + Invoice + """ + if not quiet: + self.logMessage('Packing List Line Add') + sequence_list = SequenceList() + for base_sequence in (self.PACKING_LIST_DEFAULT_SEQUENCE, + self.PACKING_LIST_TWO_LINES_DEFAULT_SEQUENCE) : + sequence_list.addSequenceString( + base_sequence + + """ + stepAddPackingListLine + stepSetContainerFullQuantity + stepTic + stepSetReadyPackingList + stepTic + stepStartPackingList + stepCheckInvoicingRule + stepTic + stepCheckInvoiceBuilding + stepRebuildAndCheckNothingIsCreated + stepCheckInvoicesConsistency + """) + sequence_list.play(self, quiet=quiet) + + def test_08_InvoiceDecreaseQuantity(self, quiet=quiet): + """ + Change the quantity of a Invoice Line, + check that the invoice is divergent, + then split and defer, and check everything is solved + """ + if not quiet: + self.logMessage('Invoice Decrease Quantity') + sequence = self.PACKING_LIST_DEFAULT_SEQUENCE + \ + """ + stepSetReadyPackingList + stepTic + stepStartPackingList + stepCheckInvoicingRule + stepCheckInvoiceTransactionRule + stepTic + stepCheckInvoiceBuilding + + stepDecreaseInvoiceLineQuantity + stepCheckInvoiceIsDivergent + stepCheckInvoiceIsCalculating + stepTic + stepCheckInvoiceIsDiverged + stepSplitAndDeferInvoice + stepTic + + stepCheckInvoiceIsNotDivergent + stepCheckInvoiceIsSolved + stepCheckInvoiceSplitted + + stepCheckPackingListIsNotDivergent + stepCheckPackingListIsSolved + stepCheckInvoiceTransactionRule + + stepRebuildAndCheckNothingIsCreated + stepCheckInvoicesConsistency + """ + self.playSequence(sequence, quiet=quiet) + + def test_09_InvoiceChangeStartDateFail(self, quiet=quiet): + """ + Change the start_date of a Invoice Line, + check that the invoice is divergent, + then accept decision, and check Packing list is divergent + """ + if not quiet: + self.logMessage('Invoice Change Sart Date') + sequence = self.PACKING_LIST_DEFAULT_SEQUENCE + \ + """ + stepSetReadyPackingList + stepTic + stepStartPackingList + stepCheckInvoicingRule + stepCheckInvoiceTransactionRule + stepTic + stepCheckInvoiceBuilding + + stepChangeInvoiceStartDate + stepCheckInvoiceIsDivergent + stepCheckInvoiceIsCalculating + stepTic + stepCheckInvoiceIsDiverged + stepUnifyStartDateWithDecisionInvoice + stepTic + + stepCheckInvoiceNotSplitted + stepCheckInvoiceIsNotDivergent + stepCheckInvoiceIsSolved + + stepCheckPackingListIsDivergent + """ + self.playSequence(sequence, quiet=quiet) + + def test_09b_InvoiceChangeStartDateSucceed(self, quiet=quiet): + """ + Change the start_date of a Invoice Line, + check that the invoice is divergent, + deliver the Packing List to make sure it's frozen, + then accept decision, and check everything is solved + """ + if not quiet: + self.logMessage('Invoice Change Sart Date') + sequence = self.PACKING_LIST_DEFAULT_SEQUENCE + \ + """ + stepSetReadyPackingList + stepTic + stepStartPackingList + stepCheckInvoicingRule + stepCheckInvoiceTransactionRule + stepTic + stepCheckInvoiceBuilding + stepStopPackingList + stepTic + stepDeliverPackingList + stepTic + + stepChangeInvoiceStartDate + stepCheckInvoiceIsDivergent + stepCheckInvoiceIsCalculating + stepTic + stepCheckInvoiceIsDiverged + stepUnifyStartDateWithDecisionInvoice + stepTic + + stepCheckInvoiceNotSplitted + stepCheckInvoiceIsNotDivergent + stepCheckInvoiceIsSolved + + stepCheckPackingListIsNotDivergent + stepCheckPackingListIsSolved + stepCheckInvoiceTransactionRule + + stepRebuildAndCheckNothingIsCreated + stepCheckInvoicesConsistency + """ + self.playSequence(sequence, quiet=quiet) + + def test_10_AcceptDecisionOnPackingList(self, quiet=quiet): + """ + - Increase or Decrease the quantity of a Packing List line + - Accept Decision on Packing List + - Packing List must not be divergent and use new quantity + - Invoice must not be divergent and use new quantity + """ + if not quiet: + self.logMessage('InvoiceAcceptDecisionOnPackingList') + end_sequence = \ + """ + stepSetContainerFullQuantity + stepCheckPackingListIsCalculating + stepTic + stepCheckPackingListIsDiverged + stepAcceptDecisionQuantity + stepTic + stepCheckPackingListIsSolved + stepCheckPackingListNotSplitted + + stepSetReadyPackingList + stepTic + stepStartPackingList + stepCheckInvoicingRule + stepCheckInvoiceTransactionRule + stepTic + stepCheckInvoiceBuilding + + stepStopPackingList + stepTic + stepDeliverPackingList + stepTic + stepCheckPackingListIsNotDivergent + stepCheckPackingListIsSolved + stepCheckInvoiceTransactionRule + + stepStartInvoice + stepTic + stepStopInvoice + stepTic + stepDeliverInvoice + stepTic + stepCheckInvoiceNotSplitted + stepCheckInvoiceIsNotDivergent + stepCheckInvoiceIsSolved + + stepRebuildAndCheckNothingIsCreated + stepCheckInvoicesConsistency + """ + + mid_sequence_list = [""" + stepCheckInvoicingRule + stepDecreasePackingListLineQuantity + """, """ + stepCheckInvoicingRule + stepIncreasePackingListLineQuantity + """] + + sequence_list = SequenceList() + for seq in mid_sequence_list: + sequence = self.PACKING_LIST_DEFAULT_SEQUENCE + \ + seq + end_sequence + sequence_list.addSequenceString(sequence) + sequence_list.play(self, quiet=quiet) + + def stepAddInvoiceLinesManyTransactions(self, sequence=None, sequence_list=[]): + """ + add some invoice and accounting lines to the invoice + """ + invoice = sequence.get('invoice') + invoice_line = invoice.newContent(portal_type='Invoice Line') + transaction_line_1 = invoice.newContent(portal_type='Sale Invoice Transaction Line') + transaction_line_2 = invoice.newContent(portal_type='Sale Invoice Transaction Line') + transaction.commit() + self.tic() + invoice_line.edit(resource_value=sequence.get('resource'), quantity=3, + price=555) + transaction_line_1.edit(id ='receivable', source='account_module/customer', + destination='account_module/supplier', quantity=-1665) + transaction_line_2.edit( + id='income', source='account_module/sale', + destination='account_module/purchase', quantity=1665) + + + def test_16a_ManuallyAddedMovementsManyTransactions(self, quiet=quiet): + """ + Checks that adding invoice lines and accounting lines to one invoice + generates correct simulation + + In this case checks what is happening, where movements are added in + one transaction and edited in another + """ + if not quiet: + self.logMessage('Invoice with Manually Added Movements in separate transactions') + sequence_list = SequenceList() + for base_sequence in (self.PACKING_LIST_DEFAULT_SEQUENCE, ) : + sequence_list.addSequenceString( + base_sequence + + """ + stepSetReadyPackingList + stepTic + stepStartPackingList + stepCheckInvoicingRule + stepTic + stepCheckInvoiceBuilding + stepRebuildAndCheckNothingIsCreated + stepCheckInvoicesConsistency + stepAddInvoiceLinesManyTransactions + stepTic + stepCheckInvoiceIsSolved + stepStartInvoice + stepTic + stepCheckSimulationTrees + """) + sequence_list.play(self, quiet=quiet) + + + def test_11_AcceptDecisionOnPackingListAndInvoice(self, quiet=quiet): + """ + - Increase or Decrease the quantity of a Packing List line + - Accept Decision on Packing List + - Packing List must not be divergent and use new quantity + - Put old quantity on Invoice + - Accept Decision on Invoice + - Packing List must not be divergent and use new quantity + - Invoice must not be divergent and use old quantity + """ + if not quiet: + self.logMessage('InvoiceAcceptDecisionOnPackingListAndInvoice') + mid_sequence = \ + """ + stepSetContainerFullQuantity + stepCheckPackingListIsCalculating + stepTic + stepCheckPackingListIsDiverged + stepAcceptDecisionQuantity + stepTic + stepCheckPackingListIsSolved + stepCheckPackingListNotSplitted + + stepSetReadyPackingList + stepTic + stepStartPackingList + stepCheckInvoicingRule + stepCheckInvoiceTransactionRule + stepTic + stepCheckInvoiceBuilding + + stepStopPackingList + stepTic + stepDeliverPackingList + stepTic + stepCheckPackingListIsNotDivergent + stepCheckPackingListIsSolved + stepCheckInvoiceTransactionRule + """ + end_sequence = \ + """ + stepCheckInvoiceIsDiverged + stepAcceptDecisionQuantityInvoice + stepTic + stepCheckInvoiceIsNotDivergent + stepCheckInvoiceIsSolved + stepStartInvoice + stepTic + stepStopInvoice + stepTic + stepDeliverInvoice + stepTic + stepCheckPackingListIsNotDivergent + stepCheckPackingListIsSolved + stepCheckInvoiceTransactionRule + stepCheckInvoiceNotSplitted + stepCheckInvoiceIsNotDivergent + stepCheckInvoiceIsSolved + + stepRebuildAndCheckNothingIsCreated + stepCheckInvoicesConsistency + """ + + mid_sequence_list = [(""" + stepCheckInvoicingRule + stepDecreasePackingListLineQuantity + """, """ + stepIncreaseInvoiceLineQuantity + stepTic + """), (""" + stepCheckInvoicingRule + stepIncreasePackingListLineQuantity + """, """ + stepDecreaseInvoiceLineQuantity + stepTic + """)] + + sequence_list = SequenceList() + for seq1, seq2 in mid_sequence_list: + sequence = self.PACKING_LIST_DEFAULT_SEQUENCE + \ + seq1 + mid_sequence + seq2 + end_sequence + sequence_list.addSequenceString(sequence) + sequence_list.play(self, quiet=quiet) + + def test_12_SplitPackingListAndAcceptInvoice(self, quiet=quiet): + """ + - Decrease the quantity of a Packing List line + - Split and Defer on Packing List + - Packing List must not be divergent and use new quantity + - splitted Packing List must not be divergent and use old - new quantity + + - Put old quantity on Invoice1 + - Accept Decision on Invoice1 + - Packing List must not be divergent and use new quantity + - splitted Packing List must not be divergent and use old - new quantity + - Invoice1 must not be divergent and use old quantity + + - set Invoice2 quantity to 0 + - Accept Decision on Invoice2 + - Packing List must not be divergent and use new quantity + - splitted Packing List must not be divergent and use old - new quantity + - Invoice1 must not be divergent and use old quantity + - Invoice2 must not be divergent and use 0 as quantity + """ + if not quiet: + self.logMessage('InvoiceSplitPackingListAndAcceptInvoice') + sequence = self.PACKING_LIST_DEFAULT_SEQUENCE + \ + """ + stepCheckInvoicingRule + stepDecreasePackingListLineQuantity + stepSetContainerFullQuantity + stepCheckPackingListIsCalculating + stepTic + stepCheckPackingListIsDiverged + stepSplitAndDeferPackingList + stepTic + stepCheckPackingListIsSolved + stepCheckPackingListSplitted + + stepSetReadyPackingList + stepTic + stepStartPackingList + stepCheckInvoicingRule + stepCheckInvoiceTransactionRule + stepTic + stepCheckInvoiceBuilding + stepStopPackingList + stepTic + stepDeliverPackingList + stepTic + stepCheckPackingListIsSolved + stepCheckPackingListSplitted + + stepIncreaseInvoiceLineQuantity + stepCheckInvoiceIsCalculating + stepTic + stepCheckInvoiceIsDiverged + stepAcceptDecisionQuantityInvoice + stepTic + stepStartInvoice + stepTic + stepStopInvoice + stepTic + stepDeliverInvoice + stepTic + stepCheckInvoiceIsSolved + stepCheckInvoiceNotSplitted + stepCheckPackingListIsNotDivergent + stepCheckPackingListIsSolved + stepCheckInvoiceTransactionRule + + stepRebuildAndCheckNothingIsCreated + stepCheckInvoicesConsistency + + stepSwitchPackingLists + + stepAddPackingListContainer + stepSetContainerFullQuantity + stepTic + stepCheckPackingListIsSolved + stepSetReadyPackingList + stepTic + stepStartPackingList + stepCheckInvoicingRule + stepCheckInvoiceTransactionRule + stepTic + stepCheckInvoiceBuilding + stepStopPackingList + stepTic + stepDeliverPackingList + stepTic + stepCheckPackingListIsSolved + + stepSetInvoiceLineQuantityToZero + stepCheckInvoiceIsCalculating + stepTic + stepCheckInvoiceIsDiverged + stepAcceptDecisionQuantityInvoice + stepTic + stepStartInvoice + stepTic + stepStopInvoice + stepTic + stepDeliverInvoice + stepTic + stepCheckInvoiceIsSolved + stepCheckInvoiceNotSplitted + stepCheckPackingListIsNotDivergent + stepCheckPackingListIsSolved + stepCheckInvoiceTransactionRule + + stepRebuildAndCheckNothingIsCreated + stepCheckInvoicesConsistency + """ + self.playSequence(sequence, quiet=quiet) + + def test_13_SplitAndDeferInvoice(self, quiet=quiet): + """ + - Accept Order, Accept Packing List + - Decrease quantity on Invoice + - Split and defer Invoice + - Accept Invoice + - Accept splitted Invoice + - Packing List must not be divergent and use old quantity + - Invoice must not be divergent and use new quantity + - splitted Invoice must not be divergent and use old - new quantity + """ + if not quiet: + self.logMessage('InvoiceSplitAndDeferInvoice') + sequence = self.PACKING_LIST_DEFAULT_SEQUENCE + \ + """ + stepSetReadyPackingList + stepTic + stepStartPackingList + stepCheckInvoicingRule + stepCheckInvoiceTransactionRule + stepTic + stepCheckInvoiceBuilding + stepStopPackingList + stepTic + stepDeliverPackingList + stepTic + stepCheckPackingListIsSolved + stepCheckPackingListNotSplitted + + stepDecreaseInvoiceLineQuantity + stepCheckInvoiceIsDivergent + stepCheckInvoiceIsCalculating + stepTic + stepCheckInvoiceIsDiverged + stepSplitAndDeferInvoice + stepTic + stepStartInvoice + stepTic + stepStopInvoice + stepTic + stepDeliverInvoice + stepTic + stepCheckInvoiceIsNotDivergent + stepCheckInvoiceIsSolved + stepCheckInvoiceSplitted + + stepRebuildAndCheckNothingIsCreated + stepCheckInvoicesConsistency + + stepCheckPackingListIsNotDivergent + stepCheckPackingListIsSolved + stepCheckInvoiceTransactionRule + + stepSwitchInvoices + + stepStartInvoice + stepTic + stepStopInvoice + stepTic + stepDeliverInvoice + stepTic + stepCheckInvoiceIsNotDivergent + stepCheckInvoiceIsSolved + + stepRebuildAndCheckNothingIsCreated + stepCheckInvoicesConsistency + """ + self.playSequence(sequence, quiet=quiet) + + def test_14_AcceptDecisionOnInvoice(self, quiet=quiet): + """ + - Accept Order, Accept Packing List + - Increase or Decrease quantity on Invoice + - Accept Decision on Invoice + - Accept Invoice + - Packing List must not be divergent and use old quantity + - Invoice must not be divergent and use new quantity + """ + if not quiet: + self.logMessage('InvoiceAcceptDecisionOnInvoice') + mid_sequence = \ + """ + stepSetReadyPackingList + stepTic + stepStartPackingList + stepCheckInvoicingRule + stepCheckInvoiceTransactionRule + stepTic + stepCheckInvoiceBuilding + stepStopPackingList + stepTic + stepDeliverPackingList + stepTic + stepCheckPackingListIsSolved + stepCheckPackingListNotSplitted + """ + end_sequence = \ + """ + stepCheckInvoiceIsDivergent + stepCheckInvoiceIsCalculating + stepTic + stepCheckInvoiceIsDiverged + stepAcceptDecisionQuantityInvoice + stepTic + stepStartInvoice + stepTic + stepStopInvoice + stepTic + stepDeliverInvoice + stepTic + + stepCheckPackingListIsNotDivergent + stepCheckPackingListIsSolved + stepCheckInvoiceTransactionRule + + stepCheckInvoiceNotSplitted + stepCheckInvoiceIsNotDivergent + stepCheckInvoiceIsSolved + + stepRebuildAndCheckNothingIsCreated + stepCheckInvoicesConsistency + """ + + mid_sequence_list = [""" + stepDecreaseInvoiceLineQuantity + """, """ + stepIncreaseInvoiceLineQuantity + """] + + sequence_list = SequenceList() + for seq in mid_sequence_list: + sequence = self.PACKING_LIST_DEFAULT_SEQUENCE + \ + mid_sequence + seq + end_sequence + sequence_list.addSequenceString(sequence) + sequence_list.play(self, quiet=quiet) + + + def test_Reference(self): + # A reference is set automatically on Sale Invoice Transaction + supplier = self.portal.organisation_module.newContent( + portal_type='Organisation', + title='Supplier') + client = self.portal.organisation_module.newContent( + portal_type='Organisation', + title='Client') + currency = self.portal.currency_module.newContent( + portal_type='Currency') + invoice = self.portal.accounting_module.newContent( + portal_type='Sale Invoice Transaction', + start_date=DateTime(), + price_currency_value=currency, + resource_value=currency, + source_section_value=supplier, + destination_section_value=client) + self.portal.portal_workflow.doActionFor(invoice, 'confirm_action') + + # We could generate a better reference here. + self.assertEquals('1', invoice.getReference()) + + def test_16_ManuallyAddedMovements(self, quiet=quiet): + """ + Checks that adding invoice lines and accounting lines to one invoice + generates correct simulation + """ + if not quiet: + self.logMessage('Invoice with Manually Added Movements') + sequence_list = SequenceList() + for base_sequence in (self.PACKING_LIST_DEFAULT_SEQUENCE, ) : + sequence_list.addSequenceString( + base_sequence + + """ + stepSetReadyPackingList + stepTic + stepStartPackingList + stepCheckInvoicingRule + stepTic + stepCheckInvoiceBuilding + stepRebuildAndCheckNothingIsCreated + stepCheckInvoicesConsistency + stepAddInvoiceLines + stepTic + stepStartInvoice + stepTic + stepCheckSimulationTrees + """) + sequence_list.play(self, quiet=quiet) + + def test_17_ManuallyAddedWrongMovements(self, quiet=quiet): + """ + Checks that adding invoice lines and accounting lines to one invoice + generates correct simulation, even when adding very wrong movements + """ + if not quiet: + self.logMessage('Invoice with Manually Added Movements') + sequence_list = SequenceList() + for base_sequence in (self.PACKING_LIST_DEFAULT_SEQUENCE, ) : + sequence_list.addSequenceString( + base_sequence + + """ + stepSetReadyPackingList + stepTic + stepStartPackingList + stepCheckInvoicingRule + stepTic + stepCheckInvoiceBuilding + stepAddWrongInvoiceLines + stepTic + stepStartInvoice + stepCheckStartInvoiceFail + stepCheckSimulationTrees + """) + sequence_list.play(self, quiet=quiet) + + def test_18_compareInvoiceAndPackingList(self, quiet=quiet): + """ + Checks that a Simple Invoice is created from a Packing List + """ + if not quiet: + self.logMessage('Simple Invoice') + sequence_list = SequenceList() + for base_sequence in (self.PACKING_LIST_DEFAULT_SEQUENCE, ) : + sequence_list.addSequenceString( + base_sequence + + """ + stepSetReadyPackingList + stepTic + stepStartPackingList + stepCheckInvoicingRule + stepTic + stepCheckInvoiceBuilding + stepCheckInvoicesConsistency + stepCheckPackingListInvoice + """) + sequence_list.play(self, quiet=quiet) + + def _adoptDivergenceOnPackingList(self, packing_list, divergence_list): + builder_list = packing_list.getBuilderList() + for builder in builder_list: + builder.solveDivergence(packing_list.getRelativeUrl(), + divergence_to_adopt_list=divergence_list) + + def test_accept_quantity_divergence_on_invoice_with_started_packing_list( + self, quiet=quiet): + # only applies to sale invoice, because purchase invoices are not built yet + # when the packing list is in started state + sequence_list = SequenceList() + sequence = sequence_list.addSequenceString(self.PACKING_LIST_DEFAULT_SEQUENCE) + sequence_list.play(self, quiet=quiet) + + packing_list = sequence.get('packing_list') + packing_list_line = packing_list.getMovementList()[0] + previous_quantity = packing_list_line.getQuantity() + + packing_list.setReady() + packing_list.start() + self.assertEquals('started', packing_list.getSimulationState()) + transaction.commit() + self.tic() + + invoice = packing_list.getCausalityRelatedValue( + portal_type=self.invoice_portal_type) + self.assertNotEquals(invoice, None) + invoice_line_list = invoice.getMovementList() + self.assertEquals(1, len(invoice_line_list)) + invoice_line = invoice_line_list[0] + + new_quantity = invoice_line.getQuantity() * 2 + invoice_line.setQuantity(new_quantity) + + transaction.commit() + self.tic() + + self.assertTrue(invoice.isDivergent()) + divergence_list = invoice.getDivergenceList() + self.assertEquals(1, len(divergence_list)) + + divergence = divergence_list[0] + self.assertEquals('quantity', divergence.tested_property) + + # accept decision + self._acceptDivergenceOnInvoice(invoice, divergence_list) + + transaction.commit() + self.tic() + self.assertEquals('solved', invoice.getCausalityState()) + + self.assertEquals([], invoice.getDivergenceList()) + self.assertEquals(new_quantity, invoice_line.getQuantity()) + self.assertEquals(new_quantity, + invoice_line.getDeliveryRelatedValue(portal_type='Simulation Movement' + ).getQuantity()) + + # the packing list is divergent, because it is not frozen + self.assertEquals('diverged', packing_list.getCausalityState()) + + divergence_list = packing_list.getDivergenceList() + self.assertEquals(1, len(divergence_list)) + + divergence = divergence_list[0] + self.assertEquals('quantity', divergence.tested_property) + + # if we adopt prevision on this packing list, both invoice and packing list + # will be solved + self._adoptDivergenceOnPackingList(packing_list, divergence_list) + + transaction.commit() + self.tic() + self.assertEquals('solved', packing_list.getCausalityState()) + self.assertEquals('solved', invoice.getCausalityState()) + + +class TestPurchaseInvoice(TestInvoice, ERP5TypeTestCase): + """Tests for purchase invoice. + """ + resource_portal_type = 'Product' + order_portal_type = 'Purchase Order' + order_line_portal_type = 'Purchase Order Line' + order_cell_portal_type = 'Purchase Order Cell' + packing_list_portal_type = 'Purchase Packing List' + packing_list_line_portal_type = 'Purchase Packing List Line' + packing_list_cell_portal_type = 'Purchase Packing List Cell' + 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' + + # default sequence for one line of not varianted resource. + PACKING_LIST_DEFAULT_SEQUENCE = """ + stepCreateEntities + stepCreateCurrency + stepCreateSaleInvoiceTransactionRule + stepCreateOrder + stepSetOrderProfile + stepSetOrderPriceCurrency + stepCreateNotVariatedResource + stepTic + stepCreateOrderLine + stepSetOrderLineResource + stepSetOrderLineDefaultValues + stepOrderOrder + stepTic + stepCheckDeliveryBuilding + stepConfirmOrder + stepTic + stepCheckOrderRule + stepCheckOrderSimulation + stepCheckDeliveryBuilding + stepTic + """ + +import unittest +def test_suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(TestSaleInvoice)) + suite.addTest(unittest.makeSuite(TestPurchaseInvoice)) + return suite