diff --git a/bt5/erp5_invoicing/SkinTemplateItem/portal_skins/erp5_invoicing/SimulationMovement_testInvoiceTransactionSimulationRule.xml b/bt5/erp5_invoicing/SkinTemplateItem/portal_skins/erp5_invoicing/SimulationMovement_testInvoiceTransactionSimulationRule.xml index cf40612a6f04967c9c51c07166397d23491fea03..b69219c5db21d48c2e778543f5d94df1dc870e44 100644 --- a/bt5/erp5_invoicing/SkinTemplateItem/portal_skins/erp5_invoicing/SimulationMovement_testInvoiceTransactionSimulationRule.xml +++ b/bt5/erp5_invoicing/SkinTemplateItem/portal_skins/erp5_invoicing/SimulationMovement_testInvoiceTransactionSimulationRule.xml @@ -71,7 +71,7 @@ if delivery_movement is not None and (\n and delivery_movement.getPortalType() not in movement.getPortalTaxMovementTypeList()):\n return False\n \n -return rule.getMatchingCell(movement) is not None\n +return True\n </string> </value> </item> <item> @@ -117,6 +117,7 @@ return rule.getMatchingCell(movement) is not None\n <string>parent_rule</string> <string>delivery_movement</string> <string>None</string> + <string>True</string> </tuple> </value> </item> diff --git a/bt5/erp5_legacy/SkinTemplateItem/portal_skins/erp5_legacy.xml b/bt5/erp5_legacy/SkinTemplateItem/portal_skins/erp5_legacy.xml new file mode 100644 index 0000000000000000000000000000000000000000..a758f1ef74844336447ebc2dc2bf7e4b22b6e5af --- /dev/null +++ b/bt5/erp5_legacy/SkinTemplateItem/portal_skins/erp5_legacy.xml @@ -0,0 +1,50 @@ +<?xml version="1.0"?> +<ZopeData> + <record id="1" aka="AAAAAAAAAAE="> + <pickle> + <tuple> + <global name="Folder" module="OFS.Folder"/> + <tuple/> + </tuple> + </pickle> + <pickle> + <dictionary> + <item> + <key> <string>_local_properties</string> </key> + <value> + <tuple> + <dictionary> + <item> + <key> <string>id</string> </key> + <value> <string>business_template_skin_layer_priority</string> </value> + </item> + <item> + <key> <string>type</string> </key> + <value> <string>float</string> </value> + </item> + </dictionary> + </tuple> + </value> + </item> + <item> + <key> <string>_objects</string> </key> + <value> + <tuple/> + </value> + </item> + <item> + <key> <string>business_template_skin_layer_priority</string> </key> + <value> <float>30.0</float> </value> + </item> + <item> + <key> <string>id</string> </key> + <value> <string>erp5_legacy</string> </value> + </item> + <item> + <key> <string>title</string> </key> + <value> <string></string> </value> + </item> + </dictionary> + </pickle> + </record> +</ZopeData> diff --git a/bt5/erp5_legacy/SkinTemplateItem/portal_skins/erp5_legacy/SimulationMovement_testInvoiceTransactionSimulationRule.xml b/bt5/erp5_legacy/SkinTemplateItem/portal_skins/erp5_legacy/SimulationMovement_testInvoiceTransactionSimulationRule.xml new file mode 100644 index 0000000000000000000000000000000000000000..cf40612a6f04967c9c51c07166397d23491fea03 --- /dev/null +++ b/bt5/erp5_legacy/SkinTemplateItem/portal_skins/erp5_legacy/SimulationMovement_testInvoiceTransactionSimulationRule.xml @@ -0,0 +1,147 @@ +<?xml version="1.0"?> +<ZopeData> + <record id="1" aka="AAAAAAAAAAE="> + <pickle> + <tuple> + <global name="PythonScript" module="Products.PythonScripts.PythonScript"/> + <tuple/> + </tuple> + </pickle> + <pickle> + <dictionary> + <item> + <key> <string>Script_magic</string> </key> + <value> <int>3</int> </value> + </item> + <item> + <key> <string>_bind_names</string> </key> + <value> + <object> + <klass> + <global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/> + </klass> + <tuple/> + <state> + <dictionary> + <item> + <key> <string>_asgns</string> </key> + <value> + <dictionary> + <item> + <key> <string>name_container</string> </key> + <value> <string>container</string> </value> + </item> + <item> + <key> <string>name_context</string> </key> + <value> <string>context</string> </value> + </item> + <item> + <key> <string>name_m_self</string> </key> + <value> <string>script</string> </value> + </item> + <item> + <key> <string>name_subpath</string> </key> + <value> <string>traverse_subpath</string> </value> + </item> + </dictionary> + </value> + </item> + </dictionary> + </state> + </object> + </value> + </item> + <item> + <key> <string>_body</string> </key> + <value> <string>movement = context\n +\n +parent = movement.getParentValue()\n +if parent.getPortalType() != \'Applied Rule\':\n + return False\n +\n +parent_rule = parent.getSpecialiseValue()\n +if parent_rule.getPortalType() not in (\'Invoice Root Simulation Rule\',\n + \'Invoice Simulation Rule\',\n + \'Trade Model Simulation Rule\'):\n + return False\n +\n +delivery_movement = movement.getDeliveryValue()\n +if delivery_movement is not None and (\n + delivery_movement.getPortalType() not in movement.getPortalInvoiceMovementTypeList()\n + and delivery_movement.getPortalType() not in movement.getPortalTaxMovementTypeList()):\n + return False\n +\n +return rule.getMatchingCell(movement) is not None\n +</string> </value> + </item> + <item> + <key> <string>_code</string> </key> + <value> + <none/> + </value> + </item> + <item> + <key> <string>_params</string> </key> + <value> <string>rule</string> </value> + </item> + <item> + <key> <string>errors</string> </key> + <value> + <tuple/> + </value> + </item> + <item> + <key> <string>func_code</string> </key> + <value> + <object> + <klass> + <global name="FuncCode" module="Shared.DC.Scripts.Signature"/> + </klass> + <tuple/> + <state> + <dictionary> + <item> + <key> <string>co_argcount</string> </key> + <value> <int>1</int> </value> + </item> + <item> + <key> <string>co_varnames</string> </key> + <value> + <tuple> + <string>rule</string> + <string>context</string> + <string>movement</string> + <string>_getattr_</string> + <string>parent</string> + <string>False</string> + <string>parent_rule</string> + <string>delivery_movement</string> + <string>None</string> + </tuple> + </value> + </item> + </dictionary> + </state> + </object> + </value> + </item> + <item> + <key> <string>func_defaults</string> </key> + <value> + <none/> + </value> + </item> + <item> + <key> <string>id</string> </key> + <value> <string>SimulationMovement_testInvoiceTransactionSimulationRule</string> </value> + </item> + <item> + <key> <string>warnings</string> </key> + <value> + <tuple/> + </value> + </item> + </dictionary> + </pickle> + </record> +</ZopeData> diff --git a/bt5/erp5_legacy/bt/template_skin_id_list b/bt5/erp5_legacy/bt/template_skin_id_list index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5ffa871985746832b2bb973b930a63c395eb34be 100644 --- a/bt5/erp5_legacy/bt/template_skin_id_list +++ b/bt5/erp5_legacy/bt/template_skin_id_list @@ -0,0 +1 @@ +erp5_legacy \ No newline at end of file diff --git a/bt5/erp5_simulation/PathTemplateItem/portal_rules/new_invoice_transaction_simulation_rule.xml b/bt5/erp5_simulation/PathTemplateItem/portal_rules/new_invoice_transaction_simulation_rule.xml index 6d5b0601a96ad554575187c4f96966d62cadd793..47d81b3cd4f1d2cb7e20b83f7a21139b94d70f57 100644 --- a/bt5/erp5_simulation/PathTemplateItem/portal_rules/new_invoice_transaction_simulation_rule.xml +++ b/bt5/erp5_simulation/PathTemplateItem/portal_rules/new_invoice_transaction_simulation_rule.xml @@ -99,6 +99,10 @@ <key> <string>portal_type</string> </key> <value> <string>Invoice Transaction Simulation Rule</string> </value> </item> + <item> + <key> <string>same_total_quantity</string> </key> + <value> <int>0</int> </value> + </item> <item> <key> <string>test_method_id</string> </key> <value> diff --git a/product/ERP5/Document/BusinessProcess.py b/product/ERP5/Document/BusinessProcess.py index d0fc4524047cd35dfee34f6cd868f4deaa6023a6..fe491e6f26d6ff2bab93063862b2940d8360698d 100644 --- a/product/ERP5/Document/BusinessProcess.py +++ b/product/ERP5/Document/BusinessProcess.py @@ -704,6 +704,11 @@ class BusinessProcess(Path, XMLObject): id_index += 1 movement = newTempMovement(trade_model_path, '%s_%s' % (base_id, id_index)) kw = self._getPropertyAndCategoryDict(explanation, amount, trade_model_path, delay_mode=delay_mode) + try: + kw['trade_phase'], = \ + set(trade_phase).intersection(trade_model_path.getTradePhaseList()) + except ValueError: + pass kw.update(update_property_dict) movement._edit(**kw) business_link = self.getBusinessLinkValueList(trade_phase=trade_phase, context=movement) @@ -714,6 +719,9 @@ class BusinessProcess(Path, XMLObject): # result can not be empty if not result: raise ValueError("A Business Process can not erase amounts") + if not explanation.getSpecialiseValue().getSameTotalQuantity(): + return result + # Sort movement list and make sure the total is equal to total_quantity total_quantity = amount.getQuantity() current_quantity = 0 @@ -799,10 +807,7 @@ class BusinessProcess(Path, XMLObject): property_dict['causality'] = trade_model_path.getRelativeUrl() # XXX-JPS Will not work if we do not use real object #(ie. if we use kind of 'temp') - # Set trade_phase to the trade phase of trade_model_path - property_dict['trade_phase'] = trade_model_path.getTradePhase() - - return property_dict + return property_dict # IBusinessProcess global API security.declareProtected(Permissions.AccessContentsInformation, 'isCompleted') diff --git a/product/ERP5/Document/InvoiceTransactionSimulationRule.py b/product/ERP5/Document/InvoiceTransactionSimulationRule.py index 7182b8631b5aebffea2d5adc75cc8e162d069c7a..b3f4da5cb945cdb2a3a002f23934253fb78a68f8 100644 --- a/product/ERP5/Document/InvoiceTransactionSimulationRule.py +++ b/product/ERP5/Document/InvoiceTransactionSimulationRule.py @@ -94,7 +94,56 @@ class InvoiceTransactionSimulationRule(RuleMixin, return (movement.getSource() is None or movement.getDestination() is None) class InvoiceTransactionRuleMovementGenerator(MovementGeneratorMixin): - def getGeneratedMovementList(self, movement_list=None, rounding=False): + + def _getUpdatePropertyDict(self, input_movement): + # get the resource (in that order): + # * resource from the invoice (using deliveryValue) + # * price_currency from the invoice + # * price_currency from the parents simulation movement's + # deliveryValue + # * price_currency from the top level simulation movement's + # orderValue + invoice_line = input_movement.getDeliveryValue() + if invoice_line is None: + resource = None + else: + invoice = invoice_line.getExplanationValue() + resource = invoice.getProperty('resource', + invoice.getProperty('price_currency', None)) + if resource is None: + # search the resource on parents simulation movement's deliveries + simulation_movement = input_movement + portal_simulation = input_movement.getPortalObject().portal_simulation + while resource is None and \ + simulation_movement != portal_simulation : + delivery = simulation_movement.getDeliveryValue() + if delivery is not None: + resource = delivery.getProperty('price_currency', None) + if (resource is None) and \ + (simulation_movement.getParentValue().getParentValue() \ + == portal_simulation) : + # we are on the first simulation movement, we'll try + # to get the resource from it's order price currency. + order = simulation_movement.getOrderValue() + if order is not None: + resource = order.getProperty('price_currency', None) + simulation_movement = simulation_movement\ + .getParentValue().getParentValue() + return {'delivery': None, 'resource': resource, 'price': 1} + + def _getInputMovementList(self, movement_list=None, rounding=False): + simulation_movement = self._applied_rule.getParentValue() + input_movement = simulation_movement.asContext( + quantity=simulation_movement.getCorrectedQuantity() * + simulation_movement.getPrice(0.0)) + # XXX trade_phase category should be added to Simulation Movements + input_movement._setCategoryMembership('trade_phase', + ('default/accounting',)) + return (input_movement,) + + # TODO: add support for assert prices (see old code below) + + def __getGeneratedMovementList(self, movement_list=None, rounding=False): """ Input movement list comes from order @@ -114,38 +163,6 @@ class InvoiceTransactionRuleMovementGenerator(MovementGeneratorMixin): if cell is not None: for accounting_rule_cell_line in cell.objectValues(): - # get the resource (in that order): - # * resource from the invoice (using deliveryValue) - # * price_currency from the invoice - # * price_currency from the parents simulation movement's - # deliveryValue - # * price_currency from the top level simulation movement's - # orderValue - resource = None - invoice_line = input_movement.getDeliveryValue() - if invoice_line is not None : - invoice = invoice_line.getExplanationValue() - resource = invoice.getProperty('resource', - invoice.getProperty('price_currency', None)) - if resource is None : - # search the resource on parents simulation movement's deliveries - simulation_movement = parent_movement - portal_simulation = self._applied_rule.getPortalObject().portal_simulation - while resource is None and \ - simulation_movement != portal_simulation : - delivery = simulation_movement.getDeliveryValue() - if delivery is not None: - resource = delivery.getProperty('price_currency', None) - if (resource is None) and \ - (simulation_movement.getParentValue().getParentValue() \ - == portal_simulation) : - # we are on the first simulation movement, we'll try - # to get the resource from it's order price currency. - order = simulation_movement.getOrderValue() - if order is not None: - resource = order.getProperty('price_currency', None) - simulation_movement = simulation_movement\ - .getParentValue().getParentValue() if resource is None : # last resort : get the resource from the rule resource = accounting_rule_cell_line.getResource() \ diff --git a/product/ERP5/Document/PaymentSimulationRule.py b/product/ERP5/Document/PaymentSimulationRule.py index 6b3a8e1191295cc965834fef979336809972cc00..5beeacf1043c72b2b15acedc9f100c5da65336b7 100644 --- a/product/ERP5/Document/PaymentSimulationRule.py +++ b/product/ERP5/Document/PaymentSimulationRule.py @@ -134,5 +134,8 @@ class PaymentRuleMovementGenerator(MovementGeneratorMixin): ret.append(simulation_movement) return ret + def _getUpdatePropertyDict(self, input_movement): + return {'delivery': None} + def _getInputMovementList(self, movement_list=None, rounding=None): return [self._applied_rule.getParentValue(),] diff --git a/product/ERP5/Document/TradeModelSimulationRule.py b/product/ERP5/Document/TradeModelSimulationRule.py index 771f929d3c589d7274c5c905dd1c0ebf1d63ade4..d904c1f23951f9c64ae88f80952d85f097df4394 100644 --- a/product/ERP5/Document/TradeModelSimulationRule.py +++ b/product/ERP5/Document/TradeModelSimulationRule.py @@ -89,6 +89,11 @@ class TradeModelSimulationRule(RuleMixin, MovementCollectionUpdaterMixin, Predic class TradeModelRuleMovementGenerator(MovementGeneratorMixin): + def _getUpdatePropertyDict(self, input_movement): + return {'delivery': None, + # XXX shouldn't we create a tester for price instead ? + 'price': input_movement.getPrice()} + def _getInputMovementList(self, movement_list=None, rounding=False): simulation_movement = self._applied_rule.getParentValue() trade_model = simulation_movement.asComposedDocument() diff --git a/product/ERP5/PropertySheet/Rule.py b/product/ERP5/PropertySheet/Rule.py index 312e34f668ef61da6062f740e4450effe89bfd53..c638314973f9d21ac04402f3615b4c30f6c75e50 100644 --- a/product/ERP5/PropertySheet/Rule.py +++ b/product/ERP5/PropertySheet/Rule.py @@ -43,6 +43,13 @@ class Rule: 'default' : [], 'multivalued' : 1, 'mode' : 'w' }, + { 'id' : 'same_total_quantity', + 'default' : 'Automatically fix quantities of generated movements' + ' so that total quantity is the same as input' + ' movement', + 'type' : 'boolean', + 'default' : True, + 'mode' : 'w' }, ) _categories = ('trade_phase', ) diff --git a/product/ERP5/tests/testBPMCore.py b/product/ERP5/tests/testBPMCore.py index 36f3971960e8ec6e4265705159941c09065f740c..12a243a7e6aedd8c5dcdcccec4166341004cbcb1 100644 --- a/product/ERP5/tests/testBPMCore.py +++ b/product/ERP5/tests/testBPMCore.py @@ -76,8 +76,55 @@ class TestBPMMixin(ERP5TypeTestCase): def createBusinessProcess(self, **kw): module = self.portal.getDefaultModule( portal_type=self.business_process_portal_type) - return module.newContent(portal_type=self.business_process_portal_type, - **kw) + business_process = module.newContent( + portal_type=self.business_process_portal_type, **kw) + trade_phase = self.getCategoryTool().trade_phase + self.createTradeModelPath(business_process, + reference='default_path', + trade_phase_value_list=[x for x in trade_phase.default.objectValues() + if x.getId() != 'accounting'], + trade_date='trade_phase/default/order') + kw = dict(business_process=business_process, + trade_phase='default/accounting', + trade_date='trade_phase/default/order', + membership_criterion_base_category='resource_use') + self.createTradeModelPath(reference='acounting_tax1', + efficiency=-1, + source_value=self.receivable_account, + destination_value=self.payable_account, + membership_criterion_category='resource_use/use/tax', + **kw) + self.createTradeModelPath(reference='acounting_tax2', + efficiency=1, + source_value=self.collected_tax_account, + destination_value=self.refundable_tax_account, + membership_criterion_category='resource_use/use/tax', + **kw) + self.createTradeModelPath(reference='acounting_discount1', + efficiency=-1, + source_value=self.receivable_account, + destination_value=self.payable_account, + membership_criterion_category='resource_use/use/discount', + **kw) + self.createTradeModelPath(reference='acounting_discount2', + efficiency=1, + source_value=self.income_account, + destination_value=self.expense_account, + membership_criterion_category='resource_use/use/discount', + **kw) + self.createTradeModelPath(reference='acounting_normal1', + efficiency=-1, + source_value=self.receivable_account, + destination_value=self.payable_account, + membership_criterion_category='resource_use/use/normal', + **kw) + self.createTradeModelPath(reference='acounting_normal2', + efficiency=1, + source_value=self.income_account, + destination_value=self.expense_account, + membership_criterion_category='resource_use/use/normal', + **kw) + return business_process @reindex def createBusinessLink(self, business_process=None, **kw): @@ -103,7 +150,6 @@ class TestBPMMixin(ERP5TypeTestCase): portal_type='Applied Rule') return applied_rule.newContent(portal_type='Simulation Movement') - @reindex def createAndValidateAccount(self, account_id, account_type): account_module = self.portal.account_module account = account_module.newContent(portal_type='Account', @@ -113,7 +159,7 @@ class TestBPMMixin(ERP5TypeTestCase): account.validate() return account - def createInvoiceTransactionRule(self): + def createAndValidateAccounts(self): self.receivable_account = self.createAndValidateAccount('receivable', 'asset/receivable') self.payable_account = self.createAndValidateAccount('payable', @@ -126,90 +172,17 @@ class TestBPMMixin(ERP5TypeTestCase): 'refundable_tax', 'asset/receivable/refundable_vat') - itr = self.portal.portal_rules.newContent( - portal_type='Invoice Transaction Simulation Rule', - reference='default_invoice_transaction_rule', - id='test_invoice_transaction_simulation_rule', - title='Transaction Simulation Rule', - test_method_id= - 'SimulationMovement_testInvoiceTransactionSimulationRule', - version=100) - predicate = itr.newContent(portal_type='Predicate',) - predicate.edit( - string_index='use', - title='tax', - int_index=1, - membership_criterion_base_category='resource_use', - membership_criterion_category='resource_use/use/tax') - predicate = itr.newContent(portal_type='Predicate',) - predicate.edit( - string_index='use', - title='discount', - int_index=2, - membership_criterion_base_category='resource_use', - membership_criterion_category='resource_use/use/discount') - predicate = itr.newContent(portal_type='Predicate',) - predicate.edit( - string_index='use', - title='normal', - int_index=3, - membership_criterion_base_category='resource_use', - membership_criterion_category='resource_use/use/normal') - transaction.commit() - self.tic() - accounting_rule_cell_list = itr.contentValues( - portal_type='Accounting Rule Cell') - self.assertEquals(3, len(accounting_rule_cell_list)) - tax_rule_cell = itr._getOb("movement_0") - self.assertEquals(tax_rule_cell.getTitle(), 'tax') - tax_rule_cell.newContent( - portal_type='Accounting Transaction Line', - source_value=self.receivable_account, - destination_value=self.payable_account, - quantity=-1) - tax_rule_cell.newContent( - portal_type='Accounting Transaction Line', - source_value=self.collected_tax_account, - destination_value=self.refundable_tax_account, - quantity=1) - - discount_rule_cell = itr._getOb("movement_1") - self.assertEquals(discount_rule_cell.getTitle(), 'discount') - discount_rule_cell.newContent( - portal_type='Accounting Transaction Line', - source_value=self.receivable_account, - destination_value=self.payable_account, - quantity=-1) - discount_rule_cell.newContent( - portal_type='Accounting Transaction Line', - source_value=self.income_account, - destination_value=self.expense_account, - quantity=1) - - normal_rule_cell = itr._getOb("movement_2") - self.assertEquals(normal_rule_cell.getTitle(), 'normal') - normal_rule_cell.newContent( - portal_type='Accounting Transaction Line', - source_value=self.receivable_account, - destination_value=self.payable_account, - quantity=-1) - normal_rule_cell.newContent( - portal_type='Accounting Transaction Line', - source_value=self.income_account, - destination_value=self.expense_account, - quantity=1) - - itr.validate() - def afterSetUp(self): rule_tool = self.getRuleTool() for rule in rule_tool.contentValues( portal_type=rule_tool.getPortalRuleTypeList()): - if rule.getId().startswith('new_') and \ - rule.getValidationState() != 'validated': + if (rule.getId().startswith('new_') and + # XXX disable temporarily broken payment rule + rule.getId() != 'new_payment_simulation_rule' and + rule.getValidationState() != 'validated'): rule.validate() self.createCategories() - self.createInvoiceTransactionRule() + self.createAndValidateAccounts() self.stepTic() def beforeTearDown(self): @@ -220,9 +193,6 @@ class TestBPMMixin(ERP5TypeTestCase): for table in 'message', 'message_queue': activity_connection.manage_test( 'delete from %s where processing_node=-2' % table) - # remove not needed rules - self.portal.portal_rules.manage_delObjects( - ids=['test_invoice_transaction_simulation_rule']) self.stepTic() class TestBPMImplementation(TestBPMMixin): diff --git a/product/ERP5/tests/testTradeModelLine.py b/product/ERP5/tests/testTradeModelLine.py index 6338ae3feccc2350fa2bab208cd71d821d1da50b..f2bc8b09ef3c3df70ac6f7188ce9debf71c0cbb2 100644 --- a/product/ERP5/tests/testTradeModelLine.py +++ b/product/ERP5/tests/testTradeModelLine.py @@ -229,6 +229,7 @@ class TestTradeModelLine(TestTradeModelLineMixin): sequence.edit(business_link=None, business_link_taxing=business_link) def stepCreateTradeModelPath(self, sequence): + return trade_phase = self.getCategoryTool().trade_phase sequence.set('trade_model_path_default', self.createTradeModelPath( sequence.get('business_process'),