# -*- coding: utf-8 -*- ############################################################################## # Copyright (c) 2009 Nexedi SA and Contributors. All Rights Reserved. # Ćukasz Nowak <luke@nexedi.com> # Fabien Morin <fabien@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. # ############################################################################## import unittest import transaction from Products.ERP5.tests.testERP5SimulationBPMCore import TestBPMMixin from Products.ERP5Type.tests.Sequence import SequenceList from DateTime import DateTime from Products.CMFCore.utils import getToolByName from Products.ERP5.PropertySheet.TradeModelLine import (TARGET_LEVEL_MOVEMENT, TARGET_LEVEL_DELIVERY) from Products.ERP5Type.tests.utils import createZODBPythonScript class TestTradeModelLineMixin(TestBPMMixin): """Provides methods to implementations sharing similar logic to Trade Model Lines""" # Constants and variables shared by tests base_unit_quantity = 0.01 # Helpers def createResource(self, portal_type, **kw): module = self.portal.getDefaultModule(portal_type=portal_type) return module.newContent(portal_type=portal_type, **kw) # Steps def stepCreatePriceCurrency(self, sequence=None, **kw): sequence.edit(price_currency = self.createResource('Currency', \ title='Currency', base_unit_quantity=self.base_unit_quantity)) def stepCreateBusinessProcess(self, sequence=None, **kw): sequence.edit(business_process=self.createBusinessProcess( title=self.id())) def stepCreateBusinessPath(self, sequence=None, **kw): business_process = sequence.get('business_process') sequence.edit(business_path=self.createBusinessPath(business_process)) class TestTradeModelLine(TestTradeModelLineMixin): quiet = True # Constants and variables shared by tests default_discount_ratio = -0.05 # -5% default_tax_ratio = 0.196 # 19,6% new_discount_ratio = -0.04 # -4% new_tax_ratio = 0.22 # 22% node_portal_type = 'Organisation' order_date = DateTime() modified_order_line_price_ratio = 2.0 modified_invoice_line_quantity_ratio = modified_order_line_quantity_ratio \ = 2.5 COMMON_DOCUMENTS_CREATION_SEQUENCE_STRING = """ CreateServiceTax CreateServiceDiscount CreatePriceCurrency CreateProductDiscounted CreateProductTaxed CreateProductDiscountedTaxed CreateSource CreateSourceSection CreateDestination CreateDestinationSection Tic """ AGGREGATED_AMOUNT_LIST_CHECK_SEQUENCE_STRING = """ CheckOrderComplexTradeConditionAggregatedAmountList CheckOrderLineTaxedAggregatedAmountList CheckOrderLineDiscountedTaxedAggregatedAmountList CheckOrderLineDiscountedAggregatedAmountList """ AGGREGATED_AMOUNT_LIST_COMMON_SEQUENCE_STRING = \ COMMON_DOCUMENTS_CREATION_SEQUENCE_STRING + """ CreateBusinessProcess CreateBusinessState ModifyBusinessStateTaxed CreateBusinessState ModifyBusinessStateInvoiced CreateBusinessPath ModifyBusinessPathTaxing CreateBusinessPath ModifyBusinessPathDiscounting CreateTradeCondition SpecialiseTradeConditionWithBusinessProcess CreateTradeModelLine ModifyTradeModelLineTax CreateTradeModelLine ModifyTradeModelLineDiscount Tic CreateOrder SpecialiseOrderTradeCondition FillOrder Tic CreateOrderLine ModifyOrderLineTaxed CreateOrderLine ModifyOrderLineDiscounted CreateOrderLine ModifyOrderLineDiscountedTaxed Tic """ + AGGREGATED_AMOUNT_LIST_CHECK_SEQUENCE_STRING # reset some values def afterSetUp(self): TestTradeModelLineMixin.afterSetUp(self) self.modified_packing_list_line_quantity_ratio = 0.5 # Helper methods def _solveDivergence(self, obj, property, decision, group='line'): kw = {'%s_group_listbox' % group:{}} for divergence in obj.getDivergenceList(): if divergence.getProperty('tested_property') != property: continue sm_url = divergence.getProperty('simulation_movement').getRelativeUrl() kw['line_group_listbox']['%s&%s' % (sm_url, property)] = { 'choice':decision} self.portal.portal_workflow.doActionFor( obj, 'solve_divergence_action', **kw) def createOrder(self): module = self.portal.getDefaultModule(portal_type=self.order_portal_type) return module.newContent(portal_type=self.order_portal_type, title=self.id()) def getTradeModelSimulationMovementList(self, order_line): result_list = [] for line_simulation_movement in order_line.getOrderRelatedValueList( portal_type='Simulation Movement'): invoicing_applied_rule = [x for x in line_simulation_movement.objectValues() if x.getSpecialiseValue().getPortalType() == 'Invoice Simulation Rule'][0] invoicing_movement = invoicing_applied_rule.objectValues()[0] trade_model_rule = [x for x in invoicing_movement.objectValues() if x.getSpecialiseValue().getPortalType() == 'Trade Model Simulation Rule'][0] result_list.append(trade_model_rule.objectValues()) return result_list def checkInvoiceTransactionRule(self, trade_model_simulation_movement): invoice_transaction_rule_list = trade_model_simulation_movement\ .objectValues() self.assertEquals(1, len(invoice_transaction_rule_list)) invoice_transaction_rule = invoice_transaction_rule_list[0] self.assertEqual('Invoice Transaction Simulation Rule', invoice_transaction_rule.getSpecialiseValue().getPortalType()) invoice_transaction_simulation_movement_list = invoice_transaction_rule \ .objectValues() self.assertEqual(2, len(invoice_transaction_simulation_movement_list)) for movement in invoice_transaction_simulation_movement_list: self.assertEqual(abs(movement.getQuantity()), abs(trade_model_simulation_movement.getTotalPrice())) def createTradeCondition(self): module = self.portal.getDefaultModule( portal_type=self.trade_condition_portal_type) trade_condition = module.newContent( portal_type=self.trade_condition_portal_type, title=self.id()) return trade_condition def createTradeModelLine(self, document, **kw): return document.newContent( portal_type='Trade Model Line', **kw) # Steps def stepCreateBusinessState(self, sequence=None, **kw): business_process = sequence.get('business_process') sequence.edit(business_state=self.createBusinessState(business_process)) def stepModifyBusinessPathDiscounting(self, sequence=None, **kw): predecessor = sequence.get('business_state_invoiced') successor = sequence.get('business_state_taxed') business_path = sequence.get('business_path') self.assertNotEqual(None, predecessor) self.assertNotEqual(None, successor) business_path.edit( predecessor_value = predecessor, successor_value = successor, trade_phase = 'default/discount' ) sequence.edit(business_path=None, business_path_discounting=business_path) def stepModifyBusinessPathTaxing(self, sequence=None, **kw): predecessor = sequence.get('business_state_invoiced') successor = sequence.get('business_state_taxed') business_path = sequence.get('business_path') self.assertNotEqual(None, predecessor) self.assertNotEqual(None, successor) business_path.edit( predecessor_value = predecessor, successor_value = successor, trade_phase = 'default/tax' ) sequence.edit(business_path=None, business_path_taxing=business_path) def stepModifyBusinessStateTaxed(self, sequence=None, **kw): business_state = sequence.get('business_state') business_state.edit(reference='taxed') sequence.edit( business_state=None, business_state_taxed=business_state) def stepModifyBusinessStateInvoiced(self, sequence=None, sequence_string=None): business_state = sequence.get('business_state') business_state.edit(reference='invoiced') sequence.edit(business_state=None, business_state_invoiced=business_state) def stepAcceptDecisionQuantityInvoice(self, sequence=None, **kw): invoice = sequence.get('invoice') solver_process_tool = self.portal.portal_solver_processes solver_process = solver_process_tool.newSolverProcess(invoice) for quantity_solver_decision in filter( lambda x:x.getCausalityValue().getTestedProperty()=='quantity', solver_process.contentValues()): # use Trade Model Solver. quantity_solver_decision.setSolverValue(self.portal.portal_solvers['Trade Model Solver']) solver_process.buildTargetSolverList() solver_process.solve() def stepAdoptPrevisionQuantityInvoice(self, sequence=None, **kw): invoice = sequence.get('invoice') self._solveDivergence(invoice, 'quantity', 'adopt') def stepCreateSource(self, sequence=None, **kw): module = self.portal.getDefaultModule(portal_type=self.node_portal_type) node = module.newContent(portal_type=self.node_portal_type) sequence.edit(source = node) def stepCreateSourceSection(self, sequence=None, **kw): module = self.portal.getDefaultModule(portal_type=self.node_portal_type) node = module.newContent(portal_type=self.node_portal_type) sequence.edit(source_section = node) def stepCreateDestination(self, sequence=None, **kw): module = self.portal.getDefaultModule(portal_type=self.node_portal_type) node = module.newContent(portal_type=self.node_portal_type) sequence.edit(destination = node) def stepCreateDestinationSection(self, sequence=None, **kw): module = self.portal.getDefaultModule(portal_type=self.node_portal_type) node = module.newContent(portal_type=self.node_portal_type) sequence.edit(destination_section = node) def stepCreateOrder(self, sequence=None, **kw): sequence.edit(order = self.createOrder()) def stepSpecialiseOrderTradeCondition(self, sequence=None, **kw): order = sequence.get('order') trade_condition = sequence.get('trade_condition') order.edit(specialise_value = trade_condition) def stepSpecialiseInvoiceTradeCondition(self, sequence=None, **kw): invoice = sequence.get('invoice') trade_condition = sequence.get('trade_condition') invoice.edit(specialise_value = trade_condition) def stepPlanOrder(self, sequence=None, **kw): order = sequence.get('order') workflow_tool = getToolByName(self.portal, 'portal_workflow') workflow_tool.doActionFor(order, 'plan_action') def stepStartInvoice(self, sequence=None, **kw): invoice = sequence.get('invoice') workflow_tool = getToolByName(self.portal, 'portal_workflow') workflow_tool.doActionFor(invoice, 'start_action') def stepStopInvoice(self, sequence=None, **kw): invoice = sequence.get('invoice') workflow_tool = getToolByName(self.portal, 'portal_workflow') workflow_tool.doActionFor(invoice, 'stop_action') def stepDeliverInvoice(self, sequence=None, **kw): invoice = sequence.get('invoice') workflow_tool = getToolByName(self.portal, 'portal_workflow') workflow_tool.doActionFor(invoice, 'deliver_action') def stepStartPackingList(self, sequence=None, **kw): packing_list = sequence.get('packing_list') workflow_tool = getToolByName(self.portal, 'portal_workflow') workflow_tool.doActionFor(packing_list, 'start_action') def stepStopPackingList(self, sequence=None, **kw): packing_list = sequence.get('packing_list') workflow_tool = getToolByName(self.portal, 'portal_workflow') workflow_tool.doActionFor(packing_list, 'stop_action') def stepDeliverPackingList(self, sequence=None, **kw): packing_list = sequence.get('packing_list') workflow_tool = getToolByName(self.portal, 'portal_workflow') workflow_tool.doActionFor(packing_list, 'deliver_action') def stepCheckPackingListDiverged(self, sequence=None, **kw): packing_list = sequence.get('packing_list') self.assertEqual( 'diverged', packing_list.getCausalityState() ) def stepSplitAndDeferPackingList(self, sequence=None, **kw): packing_list = sequence.get('packing_list') kw = {'listbox':[ {'listbox_key':line.getRelativeUrl(), 'choice':'SplitAndDefer'} for line in packing_list.getMovementList() \ if line.isDivergent()]} self.portal.portal_workflow.doActionFor( packing_list, 'split_and_defer_action', start_date=packing_list.getStartDate() + 15, stop_date=packing_list.getStopDate() + 25, **kw) def stepDecreasePackingListLineListQuantity(self, sequence=None, **kw): packing_list = sequence.get('packing_list') for movement in packing_list.getMovementList(): movement.edit( quantity = movement.getQuantity() * \ self.modified_packing_list_line_quantity_ratio ) def stepPackPackingList(self, sequence=None, **kw): packing_list = sequence.get('packing_list') if getattr(packing_list,'getContainerState', None) is None: return if packing_list.getContainerState() == 'packed': return packing_list.manage_delObjects(ids=[q.getId() for q in packing_list.objectValues(portal_type='Container')]) transaction.commit() cntr = packing_list.newContent(portal_type='Container') for movement in packing_list.getMovementList( portal_type=self.portal.getPortalMovementTypeList()): cntr.newContent( portal_type='Container Line', resource = movement.getResource(), quantity = movement.getQuantity()) transaction.commit() self.tic() self.assertEqual('packed', packing_list.getContainerState() ) def stepCheckInvoiceNormalMovements(self, sequence=None, **kw): # XXX implement... something, maybe? pass def stepCheckInvoiceAccountingMovements(self, sequence=None, **kw): invoice = sequence.get('invoice') currency = sequence.get('price_currency') currency_precision = currency.getQuantityPrecision() aggregated_amount_list_list = [ (q.getResourceValue().getUse(), q) for q in invoice.getSpecialiseValue().getAggregatedAmountList(invoice)] invoice_line_tax = [q[1] for q in aggregated_amount_list_list if q[0] == 'tax'][0] invoice_line_discount = [q[1] for q in aggregated_amount_list_list if q[0] == 'discount'][0] movement_list = invoice.getMovementList( portal_type=invoice.getPortalAccountingMovementTypeList()) self.assertEqual(3, len(movement_list)) income_expense_line = [q for q in movement_list if q.getSourceValue().getAccountType() in ['income', 'expense']][0] payable_receivable_line = [q for q in movement_list if q.getSourceValue().getAccountType() in ['asset/receivable', 'liability/payable']][0] vat_line = [q for q in movement_list if q.getSourceValue() \ .getAccountType() in ['liability/payable/collected_vat', 'asset/receivable/refundable_vat']][0] # here, the net total price of the invoice is all invoices lines that does # not use a tax or a discount as resource. rounded_total_price = round(sum( [movement.getTotalPrice() for movement in invoice.getMovementList() if movement.getResource() not in (invoice_line_tax.getResource(), invoice_line_discount.getResource())]), currency_precision) rounded_tax_price = round(invoice_line_tax.getTotalPrice(), currency_precision) rounded_discount_price = round(invoice_line_discount.getTotalPrice(), currency_precision) self.assertEqual(abs(payable_receivable_line.getTotalPrice()), rounded_total_price + rounded_tax_price + rounded_discount_price) self.assertEqual(abs(vat_line.getTotalPrice()), rounded_tax_price) self.assertEquals(abs(income_expense_line.getTotalPrice()), rounded_total_price + rounded_discount_price) def stepSetTradeConditionOld(self, sequence=None, **kw): trade_condition = sequence.get('trade_condition') self.assertNotEqual(None, trade_condition) sequence.edit( trade_condition = None, old_trade_condition = trade_condition ) def stepSetTradeConditionNew(self, sequence=None, **kw): trade_condition = sequence.get('trade_condition') self.assertNotEqual(None, trade_condition) sequence.edit( trade_condition = None, new_trade_condition = trade_condition ) def stepGetOldTradeCondition(self, sequence=None, **kw): trade_condition = sequence.get('old_trade_condition') self.assertNotEqual(None, trade_condition) sequence.edit( trade_condition = trade_condition, ) def stepGetNewTradeCondition(self, sequence=None, **kw): trade_condition = sequence.get('new_trade_condition') self.assertNotEqual(None, trade_condition) sequence.edit( trade_condition = trade_condition, ) def stepAcceptDecisionInvoice(self, sequence=None, **kw): invoice = sequence.get('invoice') invoice.portal_workflow.doActionFor(invoice,'accept_decision_action') def stepCheckInvoiceCausalityStateSolved(self, sequence=None, **kw): invoice = sequence.get('invoice') self.assertEqual('solved', invoice.getCausalityState(), invoice.getDivergenceList()) def stepCheckInvoiceCausalityStateDiverged(self, sequence=None, **kw): invoice = sequence.get('invoice') self.assertEqual('diverged', invoice.getCausalityState()) def stepGetInvoice(self, sequence=None, **kw): packing_list = sequence.get('packing_list') invoice_list = packing_list.getCausalityRelatedValueList( portal_type=self.invoice_portal_type) self.assertEqual(1, len(invoice_list)) # XXX 1 HC sequence.edit(invoice = invoice_list[0]) def stepSetNewPackingListAsPackingList(self, sequence=None, **kw): packing_list = sequence.get('packing_list') new_packing_list = sequence.get('new_packing_list') sequence.edit( packing_list = new_packing_list, new_packing_list = None ) def stepGetNewPackingList(self, sequence=None, **kw): order = sequence.get('order') packing_list = sequence.get('packing_list') packing_list_list = order.getCausalityRelatedValueList( portal_type=self.packing_list_portal_type) self.assertEqual(2, len(packing_list_list)) # XXX 2 HC new_packing_list = [q for q in packing_list_list if q != packing_list][0] sequence.edit(new_packing_list = new_packing_list) def stepGetPackingList(self, sequence=None, **kw): order = sequence.get('order') packing_list_list = order.getCausalityRelatedValueList( portal_type=self.packing_list_portal_type) self.assertEqual(1, len(packing_list_list)) # XXX 1 HC sequence.edit(packing_list = packing_list_list[0]) def stepConfirmOrder(self, sequence=None, **kw): order = sequence.get('order') workflow_tool = getToolByName(self.portal, 'portal_workflow') workflow_tool.doActionFor(order, 'confirm_action') def stepCheckOrderTaxNoSimulation(self, sequence=None, **kw): order_line_taxed = sequence.get('order_line_taxed') for trade_model_simulation_movement_list in \ self.getTradeModelSimulationMovementList(order_line_taxed): self.assertEquals(0, len(trade_model_simulation_movement_list)) # XXX: Merge: stepCheckOrderLineDiscountedSimulation stepCheckOrderLineTaxedSimulation stepCheckOrderLineDiscountedTaxedSimulation def stepCheckOrderLineDiscountedTaxedSimulation(self, sequence=None, **kw): order_line = sequence.get('order_line_discounted_taxed') business_path_discounting = sequence.get('business_path_discounting') business_path_taxing = sequence.get('business_path_taxing') price_currency = sequence.get('price_currency') service_tax = sequence.get('service_tax') service_discount = sequence.get('service_discount') self.assertNotEqual(None, business_path_discounting) self.assertNotEqual(None, business_path_taxing) self.assertNotEqual(None, price_currency) for trade_model_simulation_movement_list in \ self.getTradeModelSimulationMovementList(order_line): self.assertEquals(2, len(trade_model_simulation_movement_list)) trade_model_simulation_movement_discount_complex = [q for q in \ trade_model_simulation_movement_list \ if q.getResourceValue() == service_discount][0] trade_model_simulation_movement_tax_complex = [q for q in \ trade_model_simulation_movement_list \ if q.getResourceValue() == service_tax][0] # discount complex self.assertEqual( trade_model_simulation_movement_discount_complex.getParentValue() \ .getParentValue().getTotalPrice() * self.default_discount_ratio, trade_model_simulation_movement_discount_complex.getTotalPrice() ) self.assertEqual( business_path_discounting, trade_model_simulation_movement_discount_complex.getCausalityValue() ) self.assertEqual( price_currency, trade_model_simulation_movement_discount_complex \ .getPriceCurrencyValue() ) self.assertSameSet( ['base_amount/tax'], trade_model_simulation_movement_discount_complex \ .getBaseContributionList() ) self.assertSameSet( ['base_amount/discount'], trade_model_simulation_movement_discount_complex \ .getBaseApplicationList() ) self.checkInvoiceTransactionRule( trade_model_simulation_movement_discount_complex) # TODO: # * trade_phase ??? # * arrow # * dates # tax complex self.assertEqual( (trade_model_simulation_movement_tax_complex.getParentValue()\ .getParentValue().getTotalPrice() + \ trade_model_simulation_movement_tax_complex.getParentValue()\ .getParentValue().getTotalPrice() * self.default_discount_ratio) \ * self.default_tax_ratio, trade_model_simulation_movement_tax_complex.getTotalPrice() ) self.assertEqual( business_path_taxing, trade_model_simulation_movement_tax_complex.getCausalityValue() ) self.assertEqual( price_currency, trade_model_simulation_movement_tax_complex.getPriceCurrencyValue() ) self.assertSameSet( [], trade_model_simulation_movement_tax_complex.getBaseContributionList() ) self.assertSameSet( ['base_amount/tax'], trade_model_simulation_movement_tax_complex.getBaseApplicationList() ) self.checkInvoiceTransactionRule( trade_model_simulation_movement_tax_complex) # TODO: # * trade_phase ??? # * arrow # * dates def stepCheckOrderLineDiscountedSimulation(self, sequence=None, **kw): order_line = sequence.get('order_line_discounted') business_path_discounting = sequence.get('business_path_discounting') business_path_taxing = sequence.get('business_path_taxing') price_currency = sequence.get('price_currency') service_tax = sequence.get('service_tax') service_discount = sequence.get('service_discount') self.assertNotEqual(None, business_path_discounting) self.assertNotEqual(None, business_path_taxing) self.assertNotEqual(None, price_currency) for trade_model_simulation_movement_list in \ self.getTradeModelSimulationMovementList(order_line): self.assertEquals(2, len(trade_model_simulation_movement_list)) trade_model_simulation_movement_discount_only = [q for q in \ trade_model_simulation_movement_list \ if q.getResourceValue() == service_discount][0] trade_model_simulation_movement_tax_only = [q for q in \ trade_model_simulation_movement_list \ if q.getResourceValue() == service_tax][0] # discount only self.assertEqual( trade_model_simulation_movement_discount_only.getParentValue()\ .getParentValue().getTotalPrice() * self.default_discount_ratio, trade_model_simulation_movement_discount_only.getTotalPrice() ) self.assertEqual( business_path_discounting, trade_model_simulation_movement_discount_only.getCausalityValue() ) self.assertEqual( price_currency, trade_model_simulation_movement_discount_only.getPriceCurrencyValue() ) self.assertSameSet( ['base_amount/tax'], trade_model_simulation_movement_discount_only \ .getBaseContributionList() ) self.assertSameSet( ['base_amount/discount'], trade_model_simulation_movement_discount_only.getBaseApplicationList() ) self.checkInvoiceTransactionRule( trade_model_simulation_movement_discount_only) # TODO: # * trade_phase ??? # * arrow # * dates # tax only # below tax is applied only to discount part self.assertEqual(trade_model_simulation_movement_discount_only. \ getTotalPrice() * self.default_tax_ratio, trade_model_simulation_movement_tax_only.getTotalPrice()) self.assertEqual( business_path_taxing, trade_model_simulation_movement_tax_only.getCausalityValue() ) self.assertEqual( price_currency, trade_model_simulation_movement_tax_only.getPriceCurrencyValue() ) self.assertSameSet( [], trade_model_simulation_movement_tax_only.getBaseContributionList() ) self.assertSameSet( ['base_amount/tax'], trade_model_simulation_movement_tax_only.getBaseApplicationList() ) self.checkInvoiceTransactionRule( trade_model_simulation_movement_tax_only) # TODO: # * trade_phase ??? # * arrow # * dates def stepCheckOrderLineTaxedSimulation(self, sequence=None, **kw): order_line = sequence.get('order_line_taxed') business_path = sequence.get('business_path_taxing') price_currency = sequence.get('price_currency') self.assertNotEqual(None, business_path) self.assertNotEqual(None, price_currency) for trade_model_simulation_movement_list in \ self.getTradeModelSimulationMovementList(order_line): self.assertEquals(1, len(trade_model_simulation_movement_list)) trade_model_simulation_movement = \ trade_model_simulation_movement_list[0] self.assertEqual( trade_model_simulation_movement.getParentValue().getParentValue() \ .getTotalPrice() * self.default_tax_ratio, trade_model_simulation_movement.getTotalPrice() ) self.assertEqual( business_path, trade_model_simulation_movement.getCausalityValue() ) self.assertEqual( price_currency, trade_model_simulation_movement.getPriceCurrencyValue() ) self.assertSameSet( [], trade_model_simulation_movement.getBaseContributionList() ) self.assertSameSet( ['base_amount/tax'], trade_model_simulation_movement.getBaseApplicationList() ) self.checkInvoiceTransactionRule(trade_model_simulation_movement) # TODO: # * trade_phase ??? # * arrow # * dates def stepFillOrder(self, sequence=None, **kw): order = sequence.get('order') price_currency = sequence.get('price_currency') source = sequence.get('source') destination = sequence.get('destination') source_section = sequence.get('source_section') destination_section = sequence.get('destination_section') self.assertNotEqual(None, price_currency) self.assertNotEqual(None, source) self.assertNotEqual(None, destination) self.assertNotEqual(None, source_section) self.assertNotEqual(None, destination_section) order.edit( source_value=source, destination_value=destination, source_section_value=source_section, destination_section_value=destination_section, start_date=self.order_date, price_currency_value = price_currency) def stepCreateProductTaxed(self, sequence=None, **kw): sequence.edit(product_taxed = self.createResource('Product', title='Product Taxed', base_contribution=['base_amount/tax'], use='normal', )) def stepCreateProductDiscounted(self, sequence=None, **kw): sequence.edit(product_discounted = self.createResource('Product', title='Product Discounted', base_contribution=['base_amount/discount'], use='normal', )) def stepCreateProductDiscountedTaxed(self, sequence=None, **kw): sequence.edit(product_discounted_taxed = self.createResource('Product', title='Product Discounted & Taxed', base_contribution=['base_amount/discount', 'base_amount/tax'], use='normal', )) def stepCreateServiceTax(self, sequence=None, **kw): sequence.edit(service_tax = self.createResource('Service', title='Tax', use='tax', )) def stepCreateServiceDiscount(self, sequence=None, **kw): sequence.edit(service_discount = self.createResource('Service', title='Discount', use='discount', )) def stepCreateTradeCondition(self, sequence=None, **kw): sequence.edit(trade_condition = self.createTradeCondition()) def stepCreateInvoiceLine(self, sequence=None, **kw): invoice = sequence.get('invoice') invoice_line = invoice.newContent(portal_type=self.invoice_line_portal_type) sequence.edit(invoice_line = invoice_line) def stepCreateOrderLine(self, sequence=None, **kw): order = sequence.get('order') order_line = order.newContent(portal_type=self.order_line_portal_type) sequence.edit(order_line = order_line) def stepGetInvoiceLineDiscounted(self, sequence=None, **kw): invoice = sequence.get('invoice') resource = sequence.get('product_discounted') self.assertNotEqual(None, resource) sequence.edit(invoice_line_discounted = [m for m in invoice.getMovementList() if m.getResourceValue() == resource][0]) def stepGetInvoiceLineDiscountedTaxed(self, sequence=None, **kw): invoice = sequence.get('invoice') resource = sequence.get('product_discounted_taxed') self.assertNotEqual(None, resource) sequence.edit(invoice_line_discounted_taxed = [m for m in invoice.getMovementList() if m.getResourceValue() == resource][0]) def stepGetInvoiceLineTaxed(self, sequence=None, **kw): invoice = sequence.get('invoice') resource = sequence.get('product_taxed') self.assertNotEqual(None, resource) sequence.edit(invoice_line_taxed = [m for m in invoice.getMovementList() if m.getResourceValue() == resource][0]) def stepModifyQuantityInvoiceLineTaxed(self, sequence=None, **kw): invoice_line = sequence.get('invoice_line_taxed') invoice_line.edit( quantity=invoice_line.getQuantity() * \ self.modified_invoice_line_quantity_ratio, ) def stepModifyQuantityInvoiceLineDiscounted(self, sequence=None, **kw): invoice_line = sequence.get('invoice_line_discounted') invoice_line.edit( quantity=invoice_line.getQuantity() * \ self.modified_invoice_line_quantity_ratio, ) def stepModifyQuantityInvoiceLineDiscountedTaxed(self, sequence=None, **kw): invoice_line = sequence.get('invoice_line_discounted_taxed') invoice_line.edit( quantity=invoice_line.getQuantity() * \ self.modified_invoice_line_quantity_ratio, ) def stepModifyAgainOrderLineTaxed(self, sequence=None, **kw): order_line = sequence.get('order_line_taxed') order_line.edit( price=order_line.getPrice() * self.modified_order_line_price_ratio, quantity=order_line.getQuantity() * \ self.modified_order_line_quantity_ratio, ) def stepModifyAgainOrderLineDiscounted(self, sequence=None, **kw): order_line = sequence.get('order_line_discounted') order_line.edit( price=order_line.getPrice() * self.modified_order_line_price_ratio, quantity=order_line.getQuantity() * \ self.modified_order_line_quantity_ratio, ) def stepModifyAgainOrderLineDiscountedTaxed(self, sequence=None, **kw): order_line = sequence.get('order_line_discounted_taxed') order_line.edit( price=order_line.getPrice() * self.modified_order_line_price_ratio, quantity=order_line.getQuantity() * \ self.modified_order_line_quantity_ratio, ) def stepModifyOrderLineTaxed(self, sequence=None, **kw): order_line = sequence.get('order_line') resource = sequence.get('product_taxed') self.assertNotEqual(None, resource) order_line.edit( price=1.0, quantity=2.0, resource_value=resource ) sequence.edit( order_line = None, order_line_taxed = order_line ) def stepModifyOrderLineDiscounted(self, sequence=None, **kw): order_line = sequence.get('order_line') resource = sequence.get('product_discounted') self.assertNotEqual(None, resource) order_line.edit( price=3.0, quantity=4.0, resource_value=resource ) sequence.edit( order_line = None, order_line_discounted = order_line ) def stepModifyOrderLineDiscountedTaxed(self, sequence=None, **kw): order_line = sequence.get('order_line') resource = sequence.get('product_discounted_taxed') self.assertNotEqual(None, resource) order_line.edit( price=5.0, quantity=6.0, resource_value=resource ) sequence.edit( order_line = None, order_line_discounted_taxed = order_line ) def stepModifyInvoiceLineTaxed(self, sequence=None, **kw): invoice_line = sequence.get('invoice_line') resource = sequence.get('product_taxed') self.assertNotEqual(None, resource) invoice_line.edit( price=1.0, quantity=2.0, resource_value=resource ) sequence.edit( invoice_line = None, invoice_line_taxed = invoice_line ) def stepModifyInvoiceLineDiscounted(self, sequence=None, **kw): invoice_line = sequence.get('invoice_line') resource = sequence.get('product_discounted') self.assertNotEqual(None, resource) invoice_line.edit( price=3.0, quantity=4.0, resource_value=resource ) sequence.edit( invoice_line = None, invoice_line_discounted = invoice_line ) def stepModifyInvoiceLineDiscountedTaxed(self, sequence=None, **kw): invoice_line = sequence.get('invoice_line') resource = sequence.get('product_discounted_taxed') self.assertNotEqual(None, resource) invoice_line.edit( price=5.0, quantity=6.0, resource_value=resource ) sequence.edit( invoice_line = None, invoice_line_discounted_taxed = invoice_line ) def stepOrderCreateTradeModelLine(self, sequence=None, **kw): order = sequence.get('order') sequence.edit(trade_model_line = self.createTradeModelLine(order)) def stepCreateTradeModelLine(self, sequence=None, **kw): trade_condition = sequence.get('trade_condition') sequence.edit( trade_model_line = self.createTradeModelLine(trade_condition)) def stepSpecialiseTradeConditionWithBusinessProcess(self, sequence=None, **kw): business_process = sequence.get('business_process') trade_condition = sequence.get('trade_condition') self.assertNotEqual(None, business_process) trade_condition.setSpecialiseValue(business_process) def stepModifyTradeModelLineNewDiscount(self, sequence=None, **kw): trade_model_line = sequence.get('trade_model_line') service_discount = sequence.get('service_discount') trade_model_line.edit( price=self.new_discount_ratio, base_application='base_amount/discount', base_contribution='base_amount/tax', trade_phase='default/discount', resource_value=service_discount, reference='service_discount', ) sequence.edit( trade_model_line = None, trade_model_line_discount = trade_model_line ) def stepModifyTradeModelLineDiscount(self, sequence=None, **kw): trade_model_line = sequence.get('trade_model_line') service_discount = sequence.get('service_discount') trade_model_line.edit( price=self.default_discount_ratio, base_application='base_amount/discount', base_contribution='base_amount/tax', trade_phase='default/discount', resource_value=service_discount, reference='discount', ) sequence.edit( trade_model_line = None, trade_model_line_discount = trade_model_line ) def stepModifyTradeModelLineTotalDiscount(self, sequence=None, **kw): trade_model_line = sequence.get('trade_model_line') service_discount = sequence.get('service_discount') trade_model_line.edit( price=0.8, base_application='base_amount/total_discount', trade_phase='default/discount', resource_value=service_discount, reference='total_discount', ) sequence.edit( trade_model_line = None, trade_model_line_discount = trade_model_line ) def stepModifyTradeModelLineDiscountContributingToTotalDiscount(self, sequence=None, **kw): trade_model_line = sequence.get('trade_model_line') service_discount = sequence.get('service_discount') trade_model_line.edit( price=0.32, base_application='base_amount/discount', base_contribution='base_amount/total_discount', trade_phase='default/discount', resource_value=service_discount, reference='total_dicount_2', ) sequence.edit( trade_model_line = None, trade_model_line_discount = trade_model_line ) def stepModifyTradeModelLineNewTax(self, sequence=None, **kw): trade_model_line = sequence.get('trade_model_line') service_tax = sequence.get('service_tax') trade_model_line.edit( price=self.new_tax_ratio, base_application='base_amount/tax', trade_phase='default/tax', resource_value=service_tax, reference='tax_2', ) sequence.edit( trade_model_line = None, trade_model_line_tax = trade_model_line ) def stepModifyTradeModelLineTax(self, sequence=None, **kw): trade_model_line = sequence.get('trade_model_line') service_tax = sequence.get('service_tax') trade_model_line.edit( price=self.default_tax_ratio, base_application='base_amount/tax', trade_phase='default/tax', resource_value=service_tax, reference='tax', ) sequence.edit( trade_model_line = None, trade_model_line_tax = trade_model_line ) def stepModifyTradeModelLineTotalTax(self, sequence=None, **kw): trade_model_line = sequence.get('trade_model_line') service_tax = sequence.get('service_tax') trade_model_line.edit( price=0.12, base_application='base_amount/total_tax', base_contribution='base_amount/total_discount', trade_phase='default/tax', resource_value=service_tax, reference='tax_3', ) sequence.edit( trade_model_line = None, trade_model_line_tax = trade_model_line ) def stepModifyTradeModelLineTaxContributingToTotalTax(self, sequence=None, **kw): trade_model_line = sequence.get('trade_model_line') service_tax = sequence.get('service_tax') trade_model_line.edit( price=0.2, base_application='base_amount/tax', base_contribution='base_amount/total_tax', trade_phase='default/tax', resource_value=service_tax, reference='service_tax', ) sequence.edit( trade_model_line = None, trade_model_line_tax = trade_model_line ) def stepModifyTradeModelLineTaxContributingToTotalTax2(self, sequence=None, **kw): trade_model_line = sequence.get('trade_model_line') service_tax = sequence.get('service_tax') trade_model_line.edit( price=0.2, base_application='base_amount/tax', base_contribution='base_amount/total_tax', trade_phase='default/tax', resource_value=service_tax, reference='service_tax_2', ) sequence.edit( trade_model_line = None, trade_model_line_tax = trade_model_line ) def stepUpdateAggregatedAmountListOnOrder(self, sequence=None, **kw): order = sequence.get('order') order.Delivery_updateAggregatedAmountList(batch_mode=1) def stepCheckOrderLineTaxedAggregatedAmountList(self, sequence=None, **kw): order_line = sequence.get('order_line_taxed') trade_condition = sequence.get('trade_condition') trade_model_line_tax = sequence.get('trade_model_line_tax') amount_list = trade_condition.getAggregatedAmountList(order_line) self.assertEquals(1, len(amount_list)) tax_amount_list = [q for q in amount_list if q.getBaseApplication() == 'base_amount/tax'] self.assertEquals(1, len(tax_amount_list)) tax_amount = tax_amount_list[0] self.assertEqual(tax_amount.getReference(), trade_model_line_tax.getReference()) self.assertSameSet(['base_amount/tax'], tax_amount.getBaseApplicationList()) self.assertSameSet([], tax_amount.getBaseContributionList()) self.assertEqual(order_line.getTotalPrice() * self.default_tax_ratio, tax_amount.getTotalPrice()) def stepCheckOrderLineDiscountedTaxedAggregatedAmountList(self, sequence=None, **kw): order_line = sequence.get('order_line_discounted_taxed') trade_condition = sequence.get('trade_condition') trade_model_line_discount = sequence.get('trade_model_line_discount') trade_model_line_tax = sequence.get('trade_model_line_tax') amount_list = trade_condition.getAggregatedAmountList(order_line) self.assertEquals(2, len(amount_list)) tax_amount_list = [q for q in amount_list if q.getBaseApplication() == 'base_amount/tax'] self.assertEquals(1, len(tax_amount_list)) tax_amount = tax_amount_list[0] discount_amount_list = [q for q in amount_list if q.getBaseApplication() == 'base_amount/discount'] self.assertEquals(1, len(discount_amount_list)) discount_amount = discount_amount_list[0] self.assertEqual(tax_amount.getReference(), trade_model_line_tax.getReference()) self.assertSameSet(['base_amount/tax'], tax_amount. \ getBaseApplicationList()) self.assertSameSet([], tax_amount.getBaseContributionList()) self.assertEqual(discount_amount.getReference(), trade_model_line_discount.getReference()) self.assertSameSet(['base_amount/discount'], discount_amount. \ getBaseApplicationList()) self.assertSameSet(['base_amount/tax'], discount_amount. \ getBaseContributionList()) self.assertEqual(order_line.getTotalPrice() * \ self.default_discount_ratio, discount_amount.getTotalPrice()) self.assertEqual((order_line.getTotalPrice() + discount_amount. \ getTotalPrice()) * self.default_tax_ratio, tax_amount.getTotalPrice()) def stepCheckOrderLineDiscountedAggregatedAmountList(self, sequence=None, **kw): order_line = sequence.get('order_line_discounted') trade_condition = sequence.get('trade_condition') trade_model_line_discount = sequence.get('trade_model_line_discount') amount_list = trade_condition.getAggregatedAmountList(order_line) self.assertEquals(2, len(amount_list)) tax_amount_list = [q for q in amount_list if q.getBaseApplication() == 'base_amount/tax'] self.assertEquals(1, len(tax_amount_list)) tax_amount = tax_amount_list[0] discount_amount_list = [q for q in amount_list if q.getBaseApplication() == 'base_amount/discount'] self.assertEquals(1, len(discount_amount_list)) discount_amount = discount_amount_list[0] self.assertEqual(discount_amount.getReference(), trade_model_line_discount.getReference()) self.assertSameSet(['base_amount/tax'], tax_amount. \ getBaseApplicationList()) self.assertSameSet([], tax_amount.getBaseContributionList()) self.assertSameSet(['base_amount/discount'], discount_amount. \ getBaseApplicationList()) self.assertSameSet(['base_amount/tax'], discount_amount. \ getBaseContributionList()) self.assertEqual(order_line.getTotalPrice() * \ self.default_discount_ratio, discount_amount.getTotalPrice()) # below tax is applied only to discount part self.assertEqual(discount_amount.getTotalPrice() * self.default_tax_ratio, tax_amount.getTotalPrice()) def stepCheckOrderComplexTradeConditionAggregatedAmountList(self, sequence=None, **kw): trade_condition = sequence.get('trade_condition') order = sequence.get('order') order_line_discounted = sequence.get('order_line_discounted') order_line_discounted_taxed = sequence.get('order_line_discounted_taxed') order_line_taxed = sequence.get('order_line_taxed') trade_model_line_tax = sequence.get('trade_model_line_tax') trade_model_line_discount = sequence.get('trade_model_line_discount') amount_list = trade_condition.getAggregatedAmountList(order) self.assertEquals(2, len(amount_list)) discount_amount_list = [q for q in amount_list if q.getBaseApplication() == 'base_amount/discount'] tax_amount_list = [q for q in amount_list if q.getBaseApplication() == 'base_amount/tax'] self.assertEquals(1, len(discount_amount_list)) self.assertEquals(1, len(tax_amount_list)) discount_amount = discount_amount_list[0] tax_amount = tax_amount_list[0] self.assertEqual(discount_amount.getReference(), trade_model_line_discount.getReference()) self.assertSameSet(['base_amount/discount'], discount_amount. \ getBaseApplicationList()) self.assertSameSet(['base_amount/tax'], discount_amount. \ getBaseContributionList()) self.assertSameSet(['base_amount/tax'], tax_amount. \ getBaseApplicationList()) self.assertSameSet([], tax_amount.getBaseContributionList()) self.assertEqual(tax_amount.getReference(), trade_model_line_tax.getReference()) self.assertEqual( discount_amount.getTotalPrice(), (order_line_discounted.getTotalPrice() + order_line_discounted_taxed.getTotalPrice() ) * self.default_discount_ratio ) self.assertEqual( tax_amount.getTotalPrice(), (order_line_taxed.getTotalPrice() + order_line_discounted_taxed.getTotalPrice() + discount_amount.getTotalPrice()) * self.default_tax_ratio ) def stepCheckAggregatedAmountListWithComplexBaseContributionBaseApplication(self, sequence=None, **kw): trade_condition = sequence.get('trade_condition') order = sequence.get('order') order_line_discounted = sequence.get('order_line_discounted') order_line_discounted_taxed = sequence.get('order_line_discounted_taxed') order_line_taxed = sequence.get('order_line_taxed') amount_list = trade_condition.getAggregatedAmountList(order) self.assertEquals(5, len(amount_list)) tax_amount_list = [q for q in amount_list if q.getBaseApplication() == 'base_amount/tax'] total_tax_amount_list = [q for q in amount_list if q.getBaseApplication() == 'base_amount/total_tax'] discount_amount_list = [q for q in amount_list if q.getBaseApplication() == 'base_amount/discount'] total_discount_amount_list = [q for q in amount_list if q.getBaseApplication() == 'base_amount/total_discount'] self.assertEquals(2, len(tax_amount_list)) self.assertEquals(1, len(total_tax_amount_list)) self.assertEquals(1, len(discount_amount_list)) self.assertEquals(1, len(total_discount_amount_list)) total_tax_amount = total_tax_amount_list[0] discount_amount = discount_amount_list[0] total_discount_amount = total_discount_amount_list[0] self.assertSameSet(['base_amount/total_tax'], total_tax_amount. \ getBaseApplicationList()) self.assertSameSet(['base_amount/total_discount'], total_tax_amount. \ getBaseContributionList()) self.assertSameSet(['base_amount/discount'], discount_amount. \ getBaseApplicationList()) self.assertSameSet(['base_amount/total_discount'], discount_amount. \ getBaseContributionList()) self.assertSameSet(['base_amount/total_discount'], total_discount_amount. \ getBaseApplicationList()) self.assertSameSet([], total_discount_amount.getBaseContributionList()) for tax_amount in tax_amount_list: self.assertSameSet(['base_amount/tax'], tax_amount. \ getBaseApplicationList()) self.assertSameSet(['base_amount/total_tax'], tax_amount. \ getBaseContributionList()) for tax_amount in tax_amount_list: self.assertEqual( tax_amount.getTotalPrice(), order_line_taxed.getTotalPrice() * 0.2 ) self.assertEqual( total_tax_amount.getTotalPrice(), (order_line_taxed.getTotalPrice() * 0.2) * 2 * 0.12 ) self.assertEqual( discount_amount.getTotalPrice(), order_line_discounted.getTotalPrice() * 0.32 ) self.assertEqual( total_discount_amount.getTotalPrice(), ((order_line_taxed.getTotalPrice() * 0.2) * 2 * 0.12 + \ order_line_discounted.getTotalPrice() * 0.32) * 0.8 ) def assertSameUidSet(self, a, b, msg=None): self.assertEqual(set(x.uid for x in a), set(x.uid for x in b), msg) # Tests def test_TradeConditionTradeModelLineBasicCompositionWithOrder(self): trade_condition_1 = self.createTradeCondition() trade_condition_2 = self.createTradeCondition() order = self.createOrder() trade_condition_1.setSpecialiseValue(trade_condition_2) order.setSpecialiseValue(trade_condition_1) trade_condition_1_trade_model_line = self.createTradeModelLine( trade_condition_1, reference='A') trade_condition_2_trade_model_line = self.createTradeModelLine( trade_condition_2, reference='B') order_trade_model_line = self.createTradeModelLine( order, reference='C') self.assertSameUidSet( [trade_condition_1_trade_model_line, trade_condition_2_trade_model_line, order_trade_model_line], trade_condition_1.getTradeModelLineComposedList(context=order) ) def test_TradeConditionCircularCompositionIsSafe(self): order = self.createOrder() trade_condition_1 = self.createTradeCondition() trade_condition_2 = self.createTradeCondition() order.setSpecialiseValue(trade_condition_1) trade_condition_1.setSpecialiseValue(trade_condition_2) trade_condition_2.setSpecialiseValue(trade_condition_1) self.assertEqual(trade_condition_1.findEffectiveSpecialiseValueList(order), [trade_condition_1, trade_condition_2] ) def test_findEffectiveSpecialiseValueListWithPortalType(self): ''' check that findSpecialiseValueList is able to return all the inheritance model tree using Depth-first search with a specific portal_type asked trade_condition_1 / \ / \ / \ trade_condition_2 trade_condition_3 | | | trade_condition_4 | | | business_process As only business_process will be a "Business Process" and we search for business process the result must be [business_process] ''' order = self.createOrder() trade_condition_1 = self.createTradeCondition() trade_condition_2 = self.createTradeCondition() trade_condition_3 = self.createTradeCondition() trade_condition_4 = self.createTradeCondition() business_process = self.createBusinessProcess() order.setSpecialiseValue(trade_condition_1) trade_condition_1.setSpecialiseValueList((trade_condition_2, trade_condition_3)) trade_condition_2.setSpecialiseValue(trade_condition_4) trade_condition_4.setSpecialiseValue(business_process) self.assertEqual(trade_condition_1.findEffectiveSpecialiseValueList(order), [trade_condition_1, trade_condition_2, trade_condition_3, trade_condition_4]) self.assertEqual(trade_condition_1.findEffectiveSpecialiseValueList(order, portal_type_list = ['Business Process']), [business_process]) def test_TradeConditionTradeModelLineReferenceIsShadowingComposition(self): trade_condition_1 = self.createTradeCondition() trade_condition_2 = self.createTradeCondition() order = self.createOrder() trade_condition_1.setSpecialiseValue(trade_condition_2) order.setSpecialiseValue(trade_condition_1) trade_condition_1_trade_model_line = self.createTradeModelLine( trade_condition_1, reference = 'A') trade_condition_2_trade_model_line = self.createTradeModelLine( trade_condition_2, reference = 'B') order_trade_model_line = self.createTradeModelLine( order, reference = 'B') self.assertSameUidSet( [trade_condition_1_trade_model_line, order_trade_model_line], trade_condition_1.getTradeModelLineComposedList(context=order) ) def test_simpleGetTradeModelLineComposedList(self): """ Test list of contribution/application relation is well sorted in a simple case where we create trade model line in a wrong order in comparison to application relations We have a contribution graph like this A ---> C ---> B so final order must be A, C, B """ order = self.createOrder() trade_condition = self.createTradeCondition() order.setSpecialiseValue(trade_condition) A = self.createTradeModelLine(trade_condition, reference='A', id=1, base_contribution_list=['base_amount/total']) B = self.createTradeModelLine(trade_condition, reference='B', id=2, base_contribution_list=['base_amount/total_amount'], base_application_list=['base_amount/total_tax']) C = self.createTradeModelLine(trade_condition, reference='C', id=3, base_contribution_list=['base_amount/total_tax'], base_application_list=['base_amount/total']) trade_model_line_list = trade_condition.getTradeModelLineComposedList(order) self.assertEquals([q.getReference() for q in trade_model_line_list], [q.getReference() for q in [A, C, B,]]) def assertMatchesPossibleSortList(self, candidate, expected_sort_list): """ expected_sort_list is a list of possible sort. Example of a sort: (DEFG) (BC) A where everything in parenthesis can be not sorted Each possible sort is represented as a list of list For example, the possible sort (FG) C (DE) B A is represented as [ [F, G], [C], [D, E], [B], [A] ] candidate, in the other hand, is a simple list, for example [F, G, C, D, E, B, A] This function raises AssertionError if candidate does not match one of the possible sorts """ candidate_length = len(candidate) for expected_sort in expected_sort_list: i = 0 matching = True while expected_sort and i < candidate_length: current_head = expected_sort[0] if candidate[i] in current_head: current_head.remove(candidate[i]) if len(current_head) == 0: expected_sort.pop(0) i += 1 else: matching = False break if matching and len(expected_sort) == 0 and i == candidate_length: # we found a matching sort return # None of the possibilities matched, raise an error: sort_list_representation = "\n".join(map(repr, expected_sort_list)) raise AssertionError("%s does not match one of the possible sorts:\n%s" % (candidate, sort_list_representation)) def test_getTradeModelLineComposedList(self): """Test that list of contribution/application relations is sorted to do easy traversal Let assume such graph of contribution/application dependency: D -----> B / \ E ---/ > A / F -----> C / G ---/ It shall return list which is sorted like: * (DE) B (FG) C A or * (FG) C (DE) B A or * (DEFG) (BC) A where everything in parenthesis can be not sorted """ order = self.createOrder() trade_condition = self.createTradeCondition() order.setSpecialiseValue(trade_condition) A = self.createTradeModelLine(trade_condition, reference='A', base_application_list=['base_amount/total']) B = self.createTradeModelLine(trade_condition, reference='B', base_contribution_list=['base_amount/total'], base_application_list=['base_amount/total_tax']) C = self.createTradeModelLine(trade_condition, reference='C', base_contribution_list=['base_amount/total'], base_application_list=['base_amount/total_discount']) D = self.createTradeModelLine(trade_condition, reference='D', base_contribution_list=['base_amount/total_tax'], base_application_list=['base_amount/tax']) E = self.createTradeModelLine(trade_condition, reference='E', base_contribution_list=['base_amount/total_tax'], base_application_list=['base_amount/tax']) F = self.createTradeModelLine(trade_condition, reference='F', base_contribution_list=['base_amount/total_discount'], base_application_list=['base_amount/discount']) G = self.createTradeModelLine(trade_condition, reference='G', base_contribution_list=['base_amount/total_discount'], base_application_list=['base_amount/discount']) trade_model_line_list = trade_condition.getTradeModelLineComposedList(order) possible_sort_list = [ [[D,E], [B], [F, G], [C], [A]], [[F,G], [C], [D, E], [B], [A]], [[D,E,F,G], [B,C], [A]], ] def get_ref(l): return map(lambda x:x.getReference(), l) possible_sort_ref_list = [map(get_ref, sort) for sort in possible_sort_list] self.assertMatchesPossibleSortList(get_ref(trade_model_line_list), possible_sort_ref_list) def test_getComplexTradeModelLineComposedList(self): """Test that list of contribution/application relations is sorted to do easy traversal Let assume such graph of contribution/application dependency: /--------\ / \ A----+ -----B-----+-D \ / \----C---/ It shall return list which is sorted like: * A (BC) D where everything in parenthesis can be not sorted """ order = self.createOrder() trade_condition = self.createTradeCondition() order.setSpecialiseValue(trade_condition) C = self.createTradeModelLine(trade_condition, reference='C', base_contribution_list=['base_amount/total'], base_application_list=['base_amount/total_discount']) A = self.createTradeModelLine(trade_condition, reference='A', base_contribution_list=['base_amount/total', 'base_amount/total_tax', 'base_amount/total_discount'], base_application_list=['base_amount/tax']) D = self.createTradeModelLine(trade_condition, reference='D', base_application_list=['base_amount/total']) B = self.createTradeModelLine(trade_condition, reference='B', base_contribution_list=['base_amount/total'], base_application_list=['base_amount/total_tax']) trade_model_line_list = trade_condition.getTradeModelLineComposedList(order) possible_sort_list = [ [[A], [B,C], [D]] ] def get_ref(l): return map(lambda x:x.getReference(), l) possible_sort_ref_list = [map(get_ref, sort) for sort in possible_sort_list] self.assertMatchesPossibleSortList(get_ref(trade_model_line_list), possible_sort_ref_list) def test_tradeModelLineWithFixedPrice(self): """ Check it's possible to have fixed quantity on lines. Sometimes we want to say "discount 10 euros" or "pay more 10 euros" instead of saying "10% discount from total" """ trade_condition = self.createTradeCondition() # create a model line with 100 euros A = self.createTradeModelLine(trade_condition, reference='A', base_contribution_list=['base_amount/total']) A.edit(quantity=100, price=1) # add a discount of 10 euros B = self.createTradeModelLine(trade_condition, reference='B', base_contribution_list=['base_amount/total']) B.edit(quantity=10, price=-1) order = self.createOrder() order.setSpecialiseValue(trade_condition) amount_list = trade_condition.getAggregatedAmountList(order) self.assertEquals(2, len(amount_list)) total_amount_list = [q for q in amount_list if q.getBaseContribution() == 'base_amount/total'] self.assertEquals(2, len(total_amount_list)) # the total amount for base_amount/total should be of 100 - 10 = 90 euros total_amount = 0 for amount in total_amount_list: total_amount += amount.getTotalPrice() self.assertEqual(total_amount, 100 - 10) def test_getAggregatedAmountList(self, quiet=quiet): """ Test for case, when discount contributes to tax, and order has mix of contributing lines """ sequence_list = SequenceList() sequence_string = self.AGGREGATED_AMOUNT_LIST_COMMON_SEQUENCE_STRING sequence_list.addSequenceString(sequence_string) sequence_list.play(self, quiet=quiet) ORDER_SPECIALISE_AGGREGATED_AMOUNT_COMMON_SEQUENCE_STRING = \ COMMON_DOCUMENTS_CREATION_SEQUENCE_STRING + """ CreateBusinessProcess CreateBusinessState ModifyBusinessStateTaxed CreateBusinessState ModifyBusinessStateInvoiced CreateBusinessPath ModifyBusinessPathTaxing CreateBusinessPath ModifyBusinessPathDiscounting CreateTradeCondition SpecialiseTradeConditionWithBusinessProcess CreateTradeModelLine ModifyTradeModelLineTax Tic CreateOrder OrderCreateTradeModelLine ModifyTradeModelLineDiscount SpecialiseOrderTradeCondition FillOrder Tic CreateOrderLine ModifyOrderLineTaxed CreateOrderLine ModifyOrderLineDiscounted CreateOrderLine ModifyOrderLineDiscountedTaxed Tic """ + AGGREGATED_AMOUNT_LIST_CHECK_SEQUENCE_STRING def test_getAggregatedAmountListOrderSpecialise(self, quiet=quiet): """ Test for case, when discount contributes to tax, and order has mix of contributing lines and order itself defines Trade Model Line """ sequence_list = SequenceList() sequence_string = self\ .ORDER_SPECIALISE_AGGREGATED_AMOUNT_COMMON_SEQUENCE_STRING sequence_list.addSequenceString(sequence_string) sequence_list.play(self, quiet=quiet) def test_getAggregatedAmountList_afterUpdateAggregatedAmountList(self, quiet=quiet): """ Test for case, when discount contributes to tax, and order has mix of contributing lines Check if it is stable if updateAggregatedAmountList was invoked. Note: This test assumes, that somethings contributes after update, shall be rewritten in a way, that adds explicitly movement which shall not be aggregated. """ sequence_list = SequenceList() sequence_string = self.AGGREGATED_AMOUNT_LIST_COMMON_SEQUENCE_STRING + """ UpdateAggregatedAmountListOnOrder Tic """ + self.AGGREGATED_AMOUNT_LIST_CHECK_SEQUENCE_STRING sequence_list.addSequenceString(sequence_string) sequence_list.play(self, quiet=quiet) AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING = """ CheckOrderLineTaxedSimulation CheckOrderLineDiscountedSimulation CheckOrderLineDiscountedTaxedSimulation """ TRADE_MODEL_RULE_SIMULATION_SEQUENCE_STRING = \ AGGREGATED_AMOUNT_LIST_COMMON_SEQUENCE_STRING + """ Tic PlanOrder Tic """ + AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING def test_TradeModelRuleSimulationExpand(self, quiet=quiet): """Tests tree of simulations from Trade Model Rule""" sequence_list = SequenceList() sequence_string = self.TRADE_MODEL_RULE_SIMULATION_SEQUENCE_STRING sequence_list.addSequenceString(sequence_string) sequence_list.play(self, quiet=quiet) def test_TradeModelRuleSimulationReexpand(self, quiet=quiet): """Tests tree of simulations from Trade Model Rule with reexpanding""" sequence_list = SequenceList() sequence_string = self.TRADE_MODEL_RULE_SIMULATION_SEQUENCE_STRING + """ ModifyAgainOrderLineTaxed ModifyAgainOrderLineDiscounted ModifyAgainOrderLineDiscountedTaxed Tic """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING sequence_list.addSequenceString(sequence_string) sequence_list.play(self, quiet=quiet) def test_TradeModelRuleSimulationReexpandResourceChange(self, quiet=quiet): """Tests tree of simulations from Trade Model Rule with reexpanding when resource changes on model""" sequence_list = SequenceList() sequence_string = self.TRADE_MODEL_RULE_SIMULATION_SEQUENCE_STRING + """ CreateServiceTax CreateServiceDiscount OrderCreateTradeModelLine ModifyTradeModelLineDiscount OrderCreateTradeModelLine ModifyTradeModelLineTax Tic """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING sequence_list.addSequenceString(sequence_string) sequence_list.play(self, quiet=quiet) TRADE_MODEL_RULE_SIMULATION_ORDER_SPECIALISED_SEQUENCE_STRING = \ ORDER_SPECIALISE_AGGREGATED_AMOUNT_COMMON_SEQUENCE_STRING + """ Tic PlanOrder Tic """ + AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING def test_TradeModelRuleSimulationExpandOrderSpecialise(self, quiet=quiet): sequence_list = SequenceList() sequence_string = self \ .TRADE_MODEL_RULE_SIMULATION_ORDER_SPECIALISED_SEQUENCE_STRING sequence_list.addSequenceString(sequence_string) sequence_list.play(self, quiet=quiet) def test_TradeModelRuleSimulationReexpandOrderSpecialise(self, quiet=quiet): sequence_list = SequenceList() sequence_string = self \ .TRADE_MODEL_RULE_SIMULATION_ORDER_SPECIALISED_SEQUENCE_STRING+ """ ModifyAgainOrderLineTaxed ModifyAgainOrderLineDiscounted ModifyAgainOrderLineDiscountedTaxed Tic """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING sequence_list.addSequenceString(sequence_string) sequence_list.play(self, quiet=quiet) def test_TradeModelRuleSimulationWithoutBPM(self, quiet=quiet): """Tests tree of simulations from Trade Model Rule when there is no BPM""" sequence_list = SequenceList() sequence_string = self.COMMON_DOCUMENTS_CREATION_SEQUENCE_STRING + """ CreateTradeCondition CreateTradeModelLine ModifyTradeModelLineTax Tic CreateOrder SpecialiseOrderTradeCondition FillOrder Tic CreateOrderLine ModifyOrderLineTaxed Tic PlanOrder Tic CheckOrderTaxNoSimulation """ sequence_list.addSequenceString(sequence_string) sequence_list.play(self, quiet=quiet) def test_TradeModelRuleSimulationWithoutTradeCondition(self, quiet=quiet): """Tests tree of simulations from Trade Model Rule when there is no Trade Condition""" sequence_list = SequenceList() sequence_string = self.COMMON_DOCUMENTS_CREATION_SEQUENCE_STRING + """ CreateOrder FillOrder Tic CreateOrderLine ModifyOrderLineTaxed Tic PlanOrder Tic CheckOrderTaxNoSimulation """ sequence_list.addSequenceString(sequence_string) sequence_list.play(self, quiet=quiet) def test_TradeModelRuleSimulationBuildInvoice(self, quiet=quiet): sequence_list = SequenceList() sequence_string = self.TRADE_MODEL_RULE_SIMULATION_SEQUENCE_STRING sequence_string += """ ConfirmOrder Tic """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """ GetPackingList PackPackingList Tic """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """ StartPackingList StopPackingList DeliverPackingList Tic """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """ GetInvoice CheckInvoiceCausalityStateSolved CheckInvoiceNormalMovements """ sequence_list.addSequenceString(sequence_string) sequence_list.play(self, quiet=quiet) def test_TradeModelRuleSimulationBuildInvoiceOrderSpecialise(self, quiet=quiet): sequence_list = SequenceList() sequence_string = self\ .TRADE_MODEL_RULE_SIMULATION_ORDER_SPECIALISED_SEQUENCE_STRING sequence_string += """ ConfirmOrder Tic """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """ GetPackingList PackPackingList Tic """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """ StartPackingList StopPackingList DeliverPackingList Tic """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """ GetInvoice CheckInvoiceCausalityStateSolved CheckInvoiceNormalMovements """ sequence_list.addSequenceString(sequence_string) sequence_list.play(self, quiet=quiet) def test_TradeModelRuleSimulationBuildInvoiceNewTradeCondition(self, quiet=quiet): """Check that after changing trade condition invoice is diverged""" sequence_list = SequenceList() sequence_string = self.TRADE_MODEL_RULE_SIMULATION_SEQUENCE_STRING sequence_string += """ ConfirmOrder Tic """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """ GetPackingList PackPackingList Tic """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """ StartPackingList StopPackingList DeliverPackingList Tic """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """ GetInvoice CheckInvoiceCausalityStateSolved CheckInvoiceNormalMovements SetTradeConditionOld CreateTradeCondition SpecialiseTradeConditionWithBusinessProcess CreateTradeModelLine ModifyTradeModelLineNewTax CreateTradeModelLine ModifyTradeModelLineNewDiscount Tic SpecialiseInvoiceTradeCondition Tic CheckInvoiceCausalityStateDiverged """ sequence_list.addSequenceString(sequence_string) sequence_list.play(self, quiet=quiet) def test_TradeModelRuleSimulationBuildInvoiceNewInvoiceLineSupport(self, quiet=quiet): """Check how is supported addition of invoice line to invoice build from order""" sequence_list = SequenceList() sequence_string = self.TRADE_MODEL_RULE_SIMULATION_SEQUENCE_STRING sequence_string += """ ConfirmOrder Tic """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """ GetPackingList PackPackingList Tic """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """ StartPackingList StopPackingList DeliverPackingList Tic """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """ GetInvoice CheckInvoiceCausalityStateSolved CheckInvoiceNormalMovements CreateInvoiceLine ModifyInvoiceLineDiscounted CreateInvoiceLine ModifyInvoiceLineDiscountedTaxed CreateInvoiceLine ModifyInvoiceLineTaxed Tic CheckInvoiceCausalityStateSolved StartInvoice Tic CheckInvoiceCausalityStateSolved CheckInvoiceNormalMovements CheckInvoiceAccountingMovements StopInvoice DeliverInvoice Tic """ sequence_list.addSequenceString(sequence_string) sequence_list.play(self, quiet=quiet) def test_TradeModelRuleSimulationBuildInvoiceInvoiceLineModifyDivergencyAndSolving(self, quiet=quiet): """Check that after changing invoice line invoice is properly diverged and it is possible to solve""" sequence_list = SequenceList() sequence_string = self.TRADE_MODEL_RULE_SIMULATION_SEQUENCE_STRING sequence_string += """ ConfirmOrder Tic """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """ GetPackingList PackPackingList Tic """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """ StartPackingList StopPackingList DeliverPackingList Tic """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """ GetInvoice CheckInvoiceCausalityStateSolved CheckInvoiceNormalMovements GetInvoiceLineDiscounted GetInvoiceLineDiscountedTaxed GetInvoiceLineTaxed ModifyQuantityInvoiceLineDiscounted ModifyQuantityInvoiceLineDiscountedTaxed ModifyQuantityInvoiceLineTaxed Tic CheckInvoiceCausalityStateDiverged AcceptDecisionQuantityInvoice Tic CheckInvoiceCausalityStateSolved CheckInvoiceNormalMovements """ sequence_list.addSequenceString(sequence_string) sequence_list.play(self, quiet=quiet) def test_TradeModelRuleSimulationBuildInvoiceBuildInvoiceTransactionLines(self, quiet=quiet): """Check that having properly configured invoice transaction rule it invoice transaction lines are nicely generated and have proper amounts""" sequence_list = SequenceList() sequence_string = self.TRADE_MODEL_RULE_SIMULATION_SEQUENCE_STRING sequence_string += """ ConfirmOrder Tic """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """ GetPackingList PackPackingList Tic """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """ StartPackingList StopPackingList DeliverPackingList Tic """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """ GetInvoice CheckInvoiceCausalityStateSolved CheckInvoiceNormalMovements StartInvoice Tic CheckInvoiceCausalityStateSolved CheckInvoiceNormalMovements CheckInvoiceAccountingMovements StopInvoice DeliverInvoice Tic """ sequence_list.addSequenceString(sequence_string) sequence_list.play(self, quiet=quiet) PACKING_LIST_SPLIT_INVOICE_BUILD_SEQUENCE_STRING = \ TRADE_MODEL_RULE_SIMULATION_SEQUENCE_STRING + """ ConfirmOrder Tic """ + AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """ GetPackingList DecreasePackingListLineListQuantity Tic """ + AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """ CheckPackingListDiverged SplitAndDeferPackingList Tic """ + AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """ GetNewPackingList PackPackingList Tic """ + AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """ StartPackingList StopPackingList DeliverPackingList Tic """ + AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """ GetInvoice CheckInvoiceCausalityStateSolved CheckInvoiceNormalMovements SetNewPackingListAsPackingList PackPackingList Tic StartPackingList """ + AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """ StopPackingList DeliverPackingList Tic """ + AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """ GetInvoice CheckInvoiceCausalityStateSolved CheckInvoiceNormalMovements """ def test_TradeModelRuleSimulationPackingListSplitBuildInvoiceBuildDifferentRatio(self, quiet=quiet): """Check building invoice after splitting packing list using different ratio""" self.modified_packing_list_line_quantity_ratio = 0.4 sequence_list = SequenceList() sequence_list.addSequenceString( self.PACKING_LIST_SPLIT_INVOICE_BUILD_SEQUENCE_STRING) sequence_list.play(self, quiet=quiet) def test_TradeModelRuleSimulationPackingListSplitBuildInvoiceBuild(self, quiet=quiet): """Check building invoice after splitting packing list""" sequence_list = SequenceList() sequence_list.addSequenceString( self.PACKING_LIST_SPLIT_INVOICE_BUILD_SEQUENCE_STRING) sequence_list.play(self, quiet=quiet) def test_getAggregatedAmountListWithComplexModelLinesCreateInEasyOrder(self, quiet=quiet): """ Test the return of getAggregatedAmountList in the case of many model lines depending each others. In this test, lines are created in the order of the dependencies (it means that if a line A depend of a line B, line B is created before A). This is the most easy case. Dependency tree : ModelLineTaxContributingToTotalTax : A ModelLineDiscountContributingToTotalDiscount : B ModelLineTaxContributingToTotalTax2 : C ModelLineTotalTax : D ModelLineTotalDiscount : E D E \ / \ / \ / C B \ / \ / \/ A Model line creation order : E, D, C, B, A """ sequence_list = SequenceList() sequence_string = self.COMMON_DOCUMENTS_CREATION_SEQUENCE_STRING + """ CreateBusinessProcess CreateBusinessState ModifyBusinessStateTaxed CreateBusinessState ModifyBusinessStateInvoiced CreateBusinessPath ModifyBusinessPathTaxing CreateBusinessPath ModifyBusinessPathDiscounting CreateTradeCondition SpecialiseTradeConditionWithBusinessProcess CreateTradeModelLine ModifyTradeModelLineTotalDiscount CreateTradeModelLine ModifyTradeModelLineTotalTax CreateTradeModelLine ModifyTradeModelLineTaxContributingToTotalTax2 CreateTradeModelLine ModifyTradeModelLineDiscountContributingToTotalDiscount CreateTradeModelLine ModifyTradeModelLineTaxContributingToTotalTax Tic CreateOrder SpecialiseOrderTradeCondition FillOrder Tic CreateOrderLine ModifyOrderLineTaxed CreateOrderLine ModifyOrderLineDiscounted Tic CheckAggregatedAmountListWithComplexBaseContributionBaseApplication """ sequence_list.addSequenceString(sequence_string) sequence_list.play(self, quiet=quiet) def test_getAggregatedAmountListWithComplexModelLinesCreateInRandomOrder(self, quiet=quiet): """ Test the return of getAggregatedAmountList in the case of many model lines depending each others. In this test, lines are created in a random order, not in the dependencies order (it means that if a line A depend of a line B, line A can be created before line B). getAggregatedAmountList should be able to handle this case and redo calculation until all dependencies are satisfied Dependency tree : ModelLineTaxContributingToTotalTax : A ModelLineDiscountContributingToTotalDiscount : B ModelLineTaxContributingToTotalTax2 : C ModelLineTotalTax : D ModelLineTotalDiscount : E D E \ / \ / \ / C B \ / \ / \/ A Model line creation order : A, C, D, B, E """ sequence_list = SequenceList() sequence_string = self.COMMON_DOCUMENTS_CREATION_SEQUENCE_STRING + """ CreateBusinessProcess CreateBusinessState ModifyBusinessStateTaxed CreateBusinessState ModifyBusinessStateInvoiced CreateBusinessPath ModifyBusinessPathTaxing CreateBusinessPath ModifyBusinessPathDiscounting CreateTradeCondition SpecialiseTradeConditionWithBusinessProcess CreateTradeModelLine ModifyTradeModelLineTaxContributingToTotalTax CreateTradeModelLine ModifyTradeModelLineTaxContributingToTotalTax2 CreateTradeModelLine ModifyTradeModelLineTotalTax CreateTradeModelLine ModifyTradeModelLineDiscountContributingToTotalDiscount CreateTradeModelLine ModifyTradeModelLineTotalDiscount Tic CreateOrder SpecialiseOrderTradeCondition FillOrder Tic CreateOrderLine ModifyOrderLineTaxed CreateOrderLine ModifyOrderLineDiscounted Tic CheckAggregatedAmountListWithComplexBaseContributionBaseApplication """ sequence_list.addSequenceString(sequence_string) sequence_list.play(self, quiet=quiet) def test_BuildTradeModelLineAndAccountingFromOrder(self): business_process = self.createBusinessProcess() business_path = self.createBusinessPath(business_process, trade_phase='default/tax') product = self.createResource('Product', title='Product', use='normal', base_contribution='base_amount/tax') tax = self.createResource('Service', title='Tax', use='tax') currency = self.createResource('Currency', title='EUR') trade_condition = self.createTradeCondition() trade_condition.setSpecialiseValue(business_process) trade_model_line = self.createTradeModelLine( trade_condition, reference='VAT', price=.15, resource_value=tax, trade_phase='default/tax', base_application='base_amount/tax',) source = self.portal.organisation_module.newContent( portal_type='Organisation') destination = self.portal.organisation_module.newContent( portal_type='Organisation') order = self.createOrder() order.edit(source_value=source, destination_value=destination, source_section_value=source, destination_section_value=destination, specialise_value=trade_condition, price_currency_value=currency, start_date=self.order_date, stop_date=self.order_date,) order.newContent( portal_type=self.order_line_portal_type, resource_value=product, quantity=10, price=100) order.plan() order.confirm() transaction.commit() self.tic() packing_list = order.getCausalityRelatedValue( portal_type=self.packing_list_portal_type) self.assertNotEquals(packing_list, None) self.assertEquals(1000, packing_list.getTotalPrice()) packing_list.start() packing_list.stop() packing_list.deliver() transaction.commit() self.tic() invoice = packing_list.getCausalityRelatedValue( portal_type=self.invoice_portal_type) self.assertNotEquals(invoice, None) self.assertEquals(2, len(invoice.getMovementList())) self.assertEquals(1150, invoice.getTotalPrice()) self.assertEquals([], invoice.getDivergenceList()) invoice.start() transaction.commit() self.tic() self.assertEquals([], invoice.getDivergenceList()) accounting_line_list = invoice.getMovementList( portal_type=self.invoice_transaction_line_portal_type) self.assertEquals(3, len(accounting_line_list)) receivable_movement_list = [m for m in accounting_line_list if m.getSourceValue() == self.receivable_account] self.assertEquals(1, len(receivable_movement_list)) receivable_movement = receivable_movement_list[0] self.assertEquals(receivable_movement.getDestinationValue(), self.payable_account) self.assertEquals(1150, receivable_movement.getSourceDebit()) collected_movement_list = [m for m in accounting_line_list if m.getSourceValue() == self.collected_tax_account] self.assertEquals(1, len(collected_movement_list)) collected_movement = collected_movement_list[0] self.assertEquals(collected_movement.getDestinationValue(), self.refundable_tax_account) self.assertEquals(150, collected_movement.getSourceCredit()) income_movement_list = [m for m in accounting_line_list if m.getSourceValue() == self.income_account] self.assertEquals(1, len(income_movement_list)) income_movement = income_movement_list[0] self.assertEquals(income_movement.getDestinationValue(), self.expense_account) self.assertEquals(1000, income_movement.getSourceCredit()) def test_BuildTradeModelLineAndAccountingFromInvoice(self): business_process = self.createBusinessProcess() business_path = self.createBusinessPath(business_process, trade_phase='default/tax') product = self.createResource('Product', title='Product', use='normal', base_contribution='base_amount/tax') tax = self.createResource('Service', title='Tax', use='tax') currency = self.createResource('Currency', title='EUR') trade_condition = self.createTradeCondition() trade_condition.setSpecialiseValue(business_process) trade_model_line = self.createTradeModelLine( trade_condition, reference='VAT', price=.15, resource_value=tax, trade_phase='default/tax', base_application='base_amount/tax',) source = self.portal.organisation_module.newContent( portal_type='Organisation') destination = self.portal.organisation_module.newContent( portal_type='Organisation') invoice = self.portal.accounting_module.newContent( portal_type=self.invoice_portal_type, source_value=source, destination_value=destination, source_section_value=source, destination_section_value=destination, specialise_value=trade_condition, price_currency_value=currency, start_date=self.order_date, stop_date=self.order_date, created_by_builder=True) invoice.newContent( portal_type=self.invoice_line_portal_type, resource_value=product, quantity=10, price=100) invoice.plan() invoice.confirm() transaction.commit() self.tic() self.assertEquals(2, len(invoice.getMovementList())) self.assertEquals(1150, invoice.getTotalPrice()) self.assertEquals([], invoice.getDivergenceList()) invoice.start() transaction.commit() self.tic() self.assertEquals([], invoice.getDivergenceList()) accounting_line_list = invoice.getMovementList( portal_type=self.invoice_transaction_line_portal_type) self.assertEquals(3, len(accounting_line_list)) receivable_movement_list = [m for m in accounting_line_list if m.getSourceValue() == self.receivable_account] self.assertEquals(1, len(receivable_movement_list)) receivable_movement = receivable_movement_list[0] self.assertEquals(receivable_movement.getDestinationValue(), self.payable_account) self.assertEquals(1150, receivable_movement.getSourceDebit()) collected_movement_list = [m for m in accounting_line_list if m.getSourceValue() == self.collected_tax_account] self.assertEquals(1, len(collected_movement_list)) collected_movement = collected_movement_list[0] self.assertEquals(collected_movement.getDestinationValue(), self.refundable_tax_account) self.assertEquals(150, collected_movement.getSourceCredit()) income_movement_list = [m for m in accounting_line_list if m.getSourceValue() == self.income_account] self.assertEquals(1, len(income_movement_list)) income_movement = income_movement_list[0] self.assertEquals(income_movement.getDestinationValue(), self.expense_account) self.assertEquals(1000, income_movement.getSourceCredit()) def test_tradeModelLineWithTargetLevelSetting(self): """ Test that target level setting can specify a target of trade model line and trade model line can works with appropriate context(delivery or movement) only. """ ## XXX int_index are used in this test to order Trade Model Lines to make # sure that Discount is applied first. trade_condition = self.createTradeCondition() # create a model line and set target level to `delivery`. tax = self.createTradeModelLine(trade_condition, reference='TAX', base_application_list=['base_amount/tax'], base_contribution_list=['base_amount/total_tax']) tax.edit(price=0.05, target_level=TARGET_LEVEL_DELIVERY, int_index=4) # create an order. resource_A = self.createResource('Product', title='A') resource_B = self.createResource('Product', title='B') order = self.createOrder() order.setSpecialiseValue(trade_condition) order_line_1 = order.newContent(portal_type=self.order_line_portal_type, price=1000, quantity=1, resource_value=resource_A, base_contribution_list=['base_amount/tax']) order_line_2 = order.newContent(portal_type=self.order_line_portal_type, price=500, quantity=1, resource_value=resource_B, base_contribution_list=['base_amount/tax']) amount_list = trade_condition.getAggregatedAmountList(order) self.assertEqual(1, len(amount_list)) self.assertEqual(set([order_line_1, order_line_2]), set(amount_list[0].getCausalityValueList())) self.assertEqual(75.0, amount_list[0].getTotalPrice()) # change target level to `movement`. tax.edit(target_level=TARGET_LEVEL_MOVEMENT) amount_list = trade_condition.getAggregatedAmountList(order) self.assertEqual(2, len(amount_list)) self.assertEqual(1, len([1 for amount in amount_list if amount.getCausalityValueList() == [order_line_1]])) self.assertEqual(1, len([1 for amount in amount_list if amount.getCausalityValueList() == [order_line_2]])) # check getAggregatedAmountList result of order line. amount_list = trade_condition.getAggregatedAmountList(order_line_1) self.assertEqual(1, len(amount_list)) self.assertEqual([order_line_1], amount_list[0].getCausalityValueList()) amount_list = trade_condition.getAggregatedAmountList(order_line_2) self.assertEqual(1, len(amount_list)) self.assertEqual([order_line_2], amount_list[0].getCausalityValueList()) # create other trade model lines. # for movement extra_fee_a = self.createTradeModelLine(trade_condition, reference='EXTRA_FEE_A', base_application_list=['base_amount/tax'], base_contribution_list=['base_amount/total']) # Use custom script to return a movement which has a fixed value of quantity. # If a fixed quantity value is set to trade model line directly then it is # applied to all the movements without matching base_application category. createZODBPythonScript( self.portal.portal_skins.custom, 'TradeModelLine_calculateExtraFeeA', 'current_aggregated_amount_list, current_movement, aggregated_movement_list', """\ current_movement.setQuantity(100) return current_movement """) extra_fee_a.edit(price=1, target_level=TARGET_LEVEL_MOVEMENT, int_index=3, calculation_script_id='TradeModelLine_calculateExtraFeeA') # Extra fee b has a fixed quantity so that this trade model line is applied # to all movements by force. extra_fee_b = self.createTradeModelLine(trade_condition, reference='EXTRA_FEE_B', base_contribution_list=['base_amount/total']) extra_fee_b.edit(quantity=1, price=1, target_level=TARGET_LEVEL_MOVEMENT, int_index=2) # for delivery level discount = self.createTradeModelLine(trade_condition, reference='DISCOUNT_B', base_contribution_list=['base_amount/total'],) discount.edit(quantity=10, price=-1, target_level=TARGET_LEVEL_DELIVERY, int_index=1) transaction.commit() # flush transactional cache def getTotalAmount(amount_list): result = 0 for amount in amount_list: if amount.getBaseContribution() in ('base_amount/total', 'base_amount/total_tax'): result += amount.getTotalPrice() return result amount_list = trade_condition.getAggregatedAmountList(order) self.assertEqual(8, len(amount_list)) self.assertEqual(100 + 100 + 1 + 1 + 1 - 10 + 1000*0.05 + 500*0.05, getTotalAmount(amount_list)) # Make sure that getAggregatedAmountList of movement uses movement # level trade model line only. def getMovementFromAmountListByReference(amount_list, reference): for amount in amount_list: if amount.getReference()==reference: return amount amount_list = trade_condition.getAggregatedAmountList(order_line_1) self.assertEqual(3, len(amount_list)) extra_fee_a_amount = getMovementFromAmountListByReference(amount_list, 'EXTRA_FEE_A') self.assertEqual([order_line_1], extra_fee_a_amount.getCausalityValueList()) extra_fee_b_amount = getMovementFromAmountListByReference(amount_list, 'EXTRA_FEE_B') self.assertEqual([], extra_fee_b_amount.getCausalityValueList()) tax_amount = getMovementFromAmountListByReference(amount_list, 'TAX') self.assertEqual([order_line_1], tax_amount.getCausalityValueList()) amount_list = trade_condition.getAggregatedAmountList(order_line_2) self.assertEqual(3, len(amount_list)) extra_fee_a_amount = getMovementFromAmountListByReference(amount_list, 'EXTRA_FEE_A') self.assertEqual([order_line_2], extra_fee_a_amount.getCausalityValueList()) extra_fee_b_amount = getMovementFromAmountListByReference(amount_list, 'EXTRA_FEE_B') self.assertEqual([], extra_fee_b_amount.getCausalityValueList()) tax_amount = getMovementFromAmountListByReference(amount_list, 'TAX') self.assertEqual([order_line_2], tax_amount.getCausalityValueList()) # Change target level extra_fee_a.edit(target_level=TARGET_LEVEL_DELIVERY) extra_fee_b.edit(target_level=TARGET_LEVEL_DELIVERY) tax.edit(target_level=TARGET_LEVEL_DELIVERY) amount_list = trade_condition.getAggregatedAmountList(order) self.assertEqual(4, len(amount_list)) self.assertEqual(100 + 1 - 10 + 1500*0.05, getTotalAmount(amount_list)) def test_tradeModelLineWithRounding(self): """ Test if trade model line works with rounding. """ trade_condition = self.createTradeCondition() # create a model line and set target level to `delivery` tax = self.createTradeModelLine(trade_condition, reference='TAX', base_application_list=['base_amount/tax'], base_contribution_list=['base_amount/total_tax']) tax.edit(price=0.05, target_level=TARGET_LEVEL_DELIVERY) # create a rounding model for tax rounding_model = self.portal.portal_roundings.newContent(portal_type='Rounding Model') rounding_model.setDecimalRoundingOption('ROUND_DOWN') rounding_model.setPrecision(1) rounding_model.setRoundedPropertyId('total_price') rounding_model._setMembershipCriterionCategoryList(['base_contribution/base_amount/total_tax']) rounding_model._setMembershipCriterionBaseCategoryList(['base_contribution']) rounding_model.validate() # create an order resource_A = self.createResource('Product', title='A') resource_B = self.createResource('Product', title='B') order = self.createOrder() order.setSpecialiseValue(trade_condition) order_line_1 = order.newContent(portal_type=self.order_line_portal_type, price=3333, quantity=1, resource_value=resource_A, base_contribution_list=['base_amount/tax']) order_line_2 = order.newContent(portal_type=self.order_line_portal_type, price=171, quantity=1, resource_value=resource_B, base_contribution_list=['base_amount/tax']) transaction.commit() self.tic() # check the result without rounding amount_list = trade_condition.getAggregatedAmountList(order, rounding=False) self.assertEqual(1, len(amount_list)) self.assertEqual(set([order_line_1, order_line_2]), set(amount_list[0].getCausalityValueList())) self.assertEqual((3333+171)*0.05, amount_list[0].getTotalPrice()) # check the result with rounding amount_list = trade_condition.getAggregatedAmountList(order, rounding=True) self.assertEqual(1, len(amount_list)) self.assertEqual(set([order_line_1, order_line_2]), set(amount_list[0].getCausalityValueList())) self.assertEqual(175, amount_list[0].getTotalPrice()) # change tax trade model line to `movement` level tax.edit(target_level=TARGET_LEVEL_MOVEMENT) def getTotalAmount(amount_list): result = 0 for amount in amount_list: if amount.getBaseContribution() in ('base_amount/total', 'base_amount/total_tax'): result += amount.getTotalPrice() return result # check the result without rounding amount_list = trade_condition.getAggregatedAmountList(order, rounding=False) self.assertEqual(2, len(amount_list)) self.assertEqual(3333*0.05+171*0.05, getTotalAmount(amount_list)) # check the result with rounding amount_list = trade_condition.getAggregatedAmountList(order, rounding=True) self.assertEqual(2, len(amount_list)) self.assertEqual(174, getTotalAmount(amount_list)) # check getAggregatedAmountList result of each movement # order line 1 amount_list = trade_condition.getAggregatedAmountList(order_line_1, rounding=False) self.assertEqual(1, len(amount_list)) self.assertEqual(3333*0.05, amount_list[0].getTotalPrice()) amount_list = trade_condition.getAggregatedAmountList(order_line_1, rounding=True) self.assertEqual(1, len(amount_list)) self.assertEqual(166, amount_list[0].getTotalPrice()) # order line 2 amount_list = trade_condition.getAggregatedAmountList(order_line_2, rounding=False) self.assertEqual(1, len(amount_list)) self.assertEqual(171*0.05, amount_list[0].getTotalPrice()) amount_list = trade_condition.getAggregatedAmountList(order_line_2, rounding=True) self.assertEqual(1, len(amount_list)) self.assertEqual(8, amount_list[0].getTotalPrice()) # change rounding model definition rounding_model.setDecimalRoundingOption('ROUND_UP') rounding_model.setPrecision(1) rounding_model.setRoundedPropertyIdList(['total_price', 'quantity']) # change quantity order_line_1.edit(quantity=3.3333) transaction.commit() self.tic() # check the result without rounding amount_list = trade_condition.getAggregatedAmountList(order, rounding=False) self.assertEqual(2, len(amount_list)) self.assertEqual(3.3333*3333*0.05+171*0.05, getTotalAmount(amount_list)) # check the result with rounding # both quantity and total price will be rounded so that the expression # should be "round_up(round_up(3.3333 * 3333) * 0.05) + round_up(round_up # (1* 171) * 0.05)" amount_list = trade_condition.getAggregatedAmountList(order, rounding=True) self.assertEqual(2, len(amount_list)) self.assertEqual(565, getTotalAmount(amount_list)) # create a rounding model to round quantity property of order line rounding_model_for_quantity = self.portal.portal_roundings.newContent(portal_type='Rounding Model') rounding_model_for_quantity.setDecimalRoundingOption('ROUND_DOWN') rounding_model_for_quantity.setPrecision(1) rounding_model_for_quantity.setRoundedPropertyId('quantity') rounding_model_for_quantity._setMembershipCriterionCategoryList(['base_contribution/base_amount/tax']) rounding_model_for_quantity._setMembershipCriterionBaseCategoryList(['base_contribution']) rounding_model_for_quantity.validate() transaction.commit() self.tic() amount_list = trade_condition.getAggregatedAmountList(order, rounding=True) # The expression should be "round_up(round_up(round_down(3.3333) * 3333) # * 0.05) + round_up(round_up(round_down(1) * 171) * 0.05)" self.assertEqual(2, len(amount_list)) self.assertEqual(509, getTotalAmount(amount_list)) # create a rounding model to round price property of order line rounding_model_for_price = self.portal.portal_roundings.newContent(portal_type='Rounding Model') rounding_model_for_price.setDecimalRoundingOption('ROUND_UP') rounding_model_for_price.setPrecision(0.1) rounding_model_for_price.setRoundedPropertyId('price') rounding_model_for_price._setMembershipCriterionCategoryList(['base_contribution/base_amount/tax']) rounding_model_for_price._setMembershipCriterionBaseCategoryList(['base_contribution']) rounding_model_for_price.validate() # change price order_line_2.edit(price=171.1234) # invalidate rounding model for total price rounding_model.invalidate() transaction.commit() self.tic() # check the result without rounding amount_list = trade_condition.getAggregatedAmountList(order, rounding=False) self.assertEqual(2, len(amount_list)) self.assertEqual(3.3333*3333*0.05+171.1234*0.05, getTotalAmount(amount_list)) # check the result with rounding amount_list = trade_condition.getAggregatedAmountList(order, rounding=True) # The expression should be "round_down(3.3333) * round_up(3333) * 0.05 + # round_down(1) * round_up(171.1234) * 0.05" self.assertEqual(2, len(amount_list)) self.assertEqual(508.51000000000005, getTotalAmount(amount_list)) def test_tradeModelLineWithEmptyBaseContributionMovement(self): """ Make sure that a movement which does not have any base_contribution values does not match to any trade model lines. """ trade_condition = self.createTradeCondition() # create a model line tax = self.createTradeModelLine(trade_condition, reference='TAX', base_application_list=['base_amount/tax'], base_contribution_list=['base_amount/total_tax']) tax.edit(price=0.05) # create an order resource_A = self.createResource('Product', title='A') order = self.createOrder() order.setSpecialiseValue(trade_condition) # create a movement which should be aggregated order_line_1 = order.newContent(portal_type=self.order_line_portal_type, price=100, quantity=1, resource_value=resource_A, base_contribution_list=['base_amount/tax']) # create a movement which base contribution is empty and shoud not be # aggregated order_line_2 = order.newContent(portal_type=self.order_line_portal_type, price=50, quantity=1, resource_value=resource_A, base_contribution_list=[]) transaction.commit() self.tic() # check the result amount_list = trade_condition.getAggregatedAmountList(order) self.assertEqual(1, len(amount_list)) self.assertEqual(set([order_line_1]), set(amount_list[0].getCausalityValueList())) self.assertEqual(100*0.05, amount_list[0].getTotalPrice()) class TestTradeModelLineSale(TestTradeModelLine): invoice_portal_type = 'Sale Invoice Transaction' invoice_line_portal_type = 'Invoice Line' invoice_transaction_line_portal_type = 'Sale Invoice Transaction Line' order_portal_type = 'Sale Order' order_line_portal_type = 'Sale Order Line' packing_list_portal_type = 'Sale Packing List' trade_condition_portal_type = 'Sale Trade Condition' def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestTradeModelLineSale)) return suite