diff --git a/product/ERP5/Document/DeliveryRule.py b/product/ERP5/Document/DeliveryRule.py index 156c2bdb130c5767328a360e265acf010b316dbf..6fb118b6b8d0fd12a4224d01d1b880c82b21c9e6 100644 --- a/product/ERP5/Document/DeliveryRule.py +++ b/product/ERP5/Document/DeliveryRule.py @@ -132,6 +132,8 @@ class DeliveryRule(Rule): quantity_unit=deliv_mvt.getQuantityUnit(), price=deliv_mvt.getPrice(), price_currency=deliv_mvt.getPriceCurrency(), + base_contribution_list=deliv_mvt.getBaseContributionList(), + base_application_list=deliv_mvt.getBaseApplicationList(), ) elif sim_mvt in existing_movement_list: if sim_mvt not in immutable_movement_list: @@ -158,6 +160,8 @@ class DeliveryRule(Rule): quantity_unit=deliv_mvt.getQuantityUnit(), price=deliv_mvt.getPrice(), price_currency=deliv_mvt.getPriceCurrency(), + base_contribution_list=deliv_mvt.getBaseContributionList(), + base_application_list=deliv_mvt.getBaseApplicationList(), force_update=1) else: # modification disallowed, must compensate diff --git a/product/ERP5/Document/InvoicingRule.py b/product/ERP5/Document/InvoicingRule.py index e01d1608117d4f11f224774f5c9c6c3c0039d2e5..71ac4125924528e94a4249574135bb4feb65fba2 100644 --- a/product/ERP5/Document/InvoicingRule.py +++ b/product/ERP5/Document/InvoicingRule.py @@ -97,6 +97,7 @@ class InvoicingRule(Rule): 'resource': context_movement.getResource(), 'variation_category_list': context_movement.getVariationCategoryList(), 'variation_property_dict': context_movement.getVariationPropertyDict(), + 'base_contribution_list': context_movement.getBaseContributionList(), 'aggregate_list': context_movement.getAggregateList(), 'quantity': context_movement.getCorrectedQuantity(), 'quantity_unit': context_movement.getQuantityUnit(), diff --git a/product/ERP5/Document/OrderRule.py b/product/ERP5/Document/OrderRule.py index 4da3e492c7da5810580100d5384abaec9e9e693c..da8ae1e7cac1ae8725235e437ddb5467ee8ec833 100644 --- a/product/ERP5/Document/OrderRule.py +++ b/product/ERP5/Document/OrderRule.py @@ -106,6 +106,9 @@ class OrderRule(DeliveryRule): order_movement_dict[order_movement.getPath()] = s_m # Create or modify movements for movement in order_movement_list: + # FIXME: to be improved later + if movement.getPortalType() not in ('Tax Line', ): + continue related_order = order_movement_dict.get(movement.getPath(), None) if related_order is None: related_order = movement.getOrderRelatedValue() @@ -181,6 +184,7 @@ class OrderRule(DeliveryRule): 'resource', 'variation_category_list', 'variation_property_dict', + 'base_contribution_list', 'aggregate_list', 'price', 'price_currency', diff --git a/product/ERP5/Document/TaxLine.py b/product/ERP5/Document/TaxLine.py new file mode 100644 index 0000000000000000000000000000000000000000..616c3b7d6a232ea99dd631f98ce674a56fbc007e --- /dev/null +++ b/product/ERP5/Document/TaxLine.py @@ -0,0 +1,83 @@ +############################################################################## +# +# Copyright (c) 2008 Nexedi SA and Contributors. All Rights Reserved. +# Jerome Perrin <jerome@nexedi.com> +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contract a Free Software +# Service Company +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +from AccessControl import ClassSecurityInfo + +from Products.ERP5Type import Permissions, PropertySheet, Constraint, Interface + +from Products.ERP5.Document.DeliveryLine import DeliveryLine + + +class TaxLine(DeliveryLine): + """ Tax Line + """ + meta_type = 'ERP5 Tax Line' + portal_type = 'Tax Line' + + # Declarative security + security = ClassSecurityInfo() + security.declareObjectProtected(Permissions.AccessContentsInformation) + + # Declarative interfaces + __implements__ = ( Interface.Variated, ) + + # Declarative properties + property_sheets = ( PropertySheet.Base + , PropertySheet.XMLObject + , PropertySheet.CategoryCore + , PropertySheet.Amount + , PropertySheet.Task + , PropertySheet.Arrow + , PropertySheet.Movement + , PropertySheet.Price + , PropertySheet.VariationRange + , PropertySheet.ItemAggregation + , PropertySheet.Reference + , PropertySheet.SortIndex + ) + + security.declareProtected(Permissions.AccessContentsInformation, + 'isAccountable') + def isAccountable(self): + """ """ + return 1 # XXX not sure + + security.declareProtected(Permissions.AccessContentsInformation, + 'hasCellContent') + def hasCellContent(self, base_id='movement'): + """Tax line does not contain cell + """ + return 0 + + security.declareProtected(Permissions.AccessContentsInformation, + 'isMovement' ) + def isMovement(self): + """Tax lines are movements + """ + return 1 + diff --git a/product/ERP5/Document/TaxRule.py b/product/ERP5/Document/TaxRule.py new file mode 100644 index 0000000000000000000000000000000000000000..48b01d256f73afccc9d2014f1d6b0067c72cfbed --- /dev/null +++ b/product/ERP5/Document/TaxRule.py @@ -0,0 +1,107 @@ +############################################################################## +# +# Copyright (c) 2002, 2005 Nexedi SARL and Contributors. All Rights Reserved. +# Jean-Paul Smets-Solanes <jp@nexedi.com> +# Romain Courteaud <romain@nexedi.com> +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contract a Free Software +# Service Company +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +from AccessControl import ClassSecurityInfo +from Products.ERP5Type import Permissions, PropertySheet, Constraint, Interface +from Products.ERP5.Document.Rule import Rule +from Products.ERP5.Document.DeliveryRule import DeliveryRule + +class TaxRule(DeliveryRule): + """ + """ + # CMF Type Definition + meta_type = 'ERP5 Tax Rule' + portal_type = 'Tax Rule' + + # Declarative security + security = ClassSecurityInfo() + security.declareObjectProtected(Permissions.AccessContentsInformation) + + __implements__ = ( Interface.Predicate, + Interface.Rule ) + + # Default Properties + property_sheets = ( PropertySheet.Base + , PropertySheet.XMLObject + , PropertySheet.CategoryCore + , PropertySheet.DublinCore + , PropertySheet.Task + ) + + security.declareProtected(Permissions.ModifyPortalContent, 'expand') + def expand(self, applied_rule, force=0, **kw): + """ """ + movement_type = 'Simulation Movement' + immutable_movement_list = [] + + parent_simulation_movement = applied_rule.getParentValue() + order_movement = parent_simulation_movement.getDefaultOrderValue() + + order_movement_dict = {} + for s_m in applied_rule.objectValues(): + order_movement_dict.setdefault(s_m.getOrder(), []).append(s_m) + + order_movement_total_price = order_movement.getTotalPrice() + parent_simulation_movement_total_price = \ + parent_simulation_movement.getTotalPrice() + + # XXX round + if order_movement_total_price != 0 and \ + parent_simulation_movement_total_price != 0: + + ratio = parent_simulation_movement_total_price / \ + order_movement_total_price + for tax_movement in order_movement\ + .DeliveryMovement_getCorrespondingTaxLineList(): + existing_simulation_movement_list = order_movement_dict.get( + tax_movement.getRelativeUrl(), []) + + property_dict = dict() + for prop in ('price', 'base_application_list', + 'base_contribution_list', 'resource'): + property_dict[prop] = tax_movement.getProperty(prop) + + property_dict['quantity'] = tax_movement.getQuantity() * ratio + + if not existing_simulation_movement_list: + applied_rule.newContent( + portal_type=movement_type, + order_value=tax_movement, + order_ratio=1, + delivery_ratio=1, + deliverable=1, + **property_dict ) + else: + for existing_simulation_movement in \ + existing_simulation_movement_list: + existing_simulation_movement.edit(**property_dict) + + # Pass to base class + Rule.expand(self, applied_rule, force=force, **kw) + diff --git a/product/ERP5/Document/TradeCondition.py b/product/ERP5/Document/TradeCondition.py index 7ab8a9a507aa5a091671f8371fc4c97176d045bc..df037f9d6c3f5856c9aaf31a60d8b19dd0e9957f 100644 --- a/product/ERP5/Document/TradeCondition.py +++ b/product/ERP5/Document/TradeCondition.py @@ -32,8 +32,9 @@ from AccessControl import ClassSecurityInfo from Products.ERP5Type import Permissions, PropertySheet, Constraint, Interface from Products.ERP5.Document.Path import Path +from Products.ERP5.Document.Delivery import Delivery -class TradeCondition(Path): +class TradeCondition(Delivery, Path): """ Trade Conditions are used to store the conditions (payment, logistic,...) which should be applied (and used in the orders) when two companies make diff --git a/product/ERP5/ERP5Defaults.py b/product/ERP5/ERP5Defaults.py index 56b96d0bfd4c47d68eaa06a259a62d539b796017..ba78ec8688eec979a0eb412cb3c5a1f57f28dc9f 100644 --- a/product/ERP5/ERP5Defaults.py +++ b/product/ERP5/ERP5Defaults.py @@ -92,6 +92,9 @@ portal_invoice_movement_type_list = ( 'Pay Sheet Cell', ) +portal_tax_movement_type_list = ( 'Tax Line', 'Discount Line', + 'Pay Sheet Line', ) + portal_order_movement_type_list = ( 'Purchase Order Line', 'Purchase Order Cell', diff --git a/product/ERP5/ERP5Site.py b/product/ERP5/ERP5Site.py index 922c59152a1b52238b15a879d601591293643d80..2ee14682498a6bec932728601b07ad99f3b3a4e7 100644 --- a/product/ERP5/ERP5Site.py +++ b/product/ERP5/ERP5Site.py @@ -609,6 +609,16 @@ class ERP5Site(FolderMixIn, CMFSite): return self._getPortalGroupedTypeList('invoice_movement') or \ self._getPortalConfiguration('portal_invoice_movement_type_list') + security.declareProtected(Permissions.AccessContentsInformation, + 'getPortalTaxMovementTypeList') + def getPortalTaxMovementTypeList(self): + """ + Return tax movement types. + """ + return self._getPortalGroupedTypeList('tax_movement') or \ + self._getPortalConfiguration('portal_tax_movement_type_list') + + security.declareProtected(Permissions.AccessContentsInformation, 'getPortalOrderMovementTypeList') def getPortalOrderMovementTypeList(self): diff --git a/product/ERP5/MovementGroup.py b/product/ERP5/MovementGroup.py index d7d4b4f6e038054125be8398b0512a942ed7b5a1..cb84c0240fe0498ab71936d72a05666d2fd6c757 100644 --- a/product/ERP5/MovementGroup.py +++ b/product/ERP5/MovementGroup.py @@ -1177,6 +1177,18 @@ class RequirementMovementGroup(RootMovementGroup): def test(self,movement): return self.getRequirementList(movement) == self.requirement_list +class BaseContributionMovementGroup(PropertyMovementGroup): + """ Group movements that have the same base contributions.""" + _property = 'base_contribution_list' + +class BaseApplicationMovementGroup(PropertyMovementGroup): + """ Group movements that have the same base applications.""" + _property = 'base_application_list' + +class PriceCurrencyMovementGroup(PropertyMovementGroup): + """ Group movements that have the same price currency.""" + _property = 'price_currency' + class QuantityUnitMovementGroup(PropertyMovementGroup): """ Group movements that have the same quantity unit.""" _property = 'quantity_unit' diff --git a/product/ERP5/PropertySheet/Amount.py b/product/ERP5/PropertySheet/Amount.py index 08ec38400785f6d373f3e87706a127505b959de0..d6ffd4ca11f0cf228728727c173fc04645105bd5 100644 --- a/product/ERP5/PropertySheet/Amount.py +++ b/product/ERP5/PropertySheet/Amount.py @@ -126,6 +126,7 @@ class Amount: ) _categories = ('resource', 'quantity_unit', + 'base_application', 'base_contribution', # Acquired categories 'product_line', ) diff --git a/product/ERP5/PropertySheet/Resource.py b/product/ERP5/PropertySheet/Resource.py index c921e2cefb3c4333f9faed1bee1415cf60164d36..0c032e7b38e9e0a1efb01730b2ffe0b8d9bf727b 100644 --- a/product/ERP5/PropertySheet/Resource.py +++ b/product/ERP5/PropertySheet/Resource.py @@ -182,6 +182,7 @@ class Resource: _categories = ( 'source', 'destination', 'quantity_unit', 'price_unit', 'weight_unit', 'length_unit', 'height_unit', 'width_unit', 'volume_unit', + 'base_contribution', 'price_currency', 'source_price_currency', 'destination_price_currency', 'product_line', 'industrial_phase') diff --git a/product/ERP5/tests/testTradeCondition.py b/product/ERP5/tests/testTradeCondition.py new file mode 100644 index 0000000000000000000000000000000000000000..96d4c32766eb49d4ec87a184c88967fe68acba0e --- /dev/null +++ b/product/ERP5/tests/testTradeCondition.py @@ -0,0 +1,1290 @@ +############################################################################## +# +# Copyright (c) 2008 Nexedi SA and Contributors. All Rights Reserved. +# Jerome Perrin <jerome@nexedi.com> +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contract a Free Software +# Service Company +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +import unittest + +from DateTime import DateTime + +from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase + +try: + from transaction import get as get_transaction +except ImportError: + pass + +class TradeConditionTestCase(ERP5TypeTestCase): + """Tests for Trade Conditions and Tax + """ + def getBusinessTemplateList(self): + return ('erp5_base', 'erp5_pdm', 'erp5_trade', 'erp5_accounting', 'erp5_invoicing') + + def setUp(self): + ERP5TypeTestCase.setUp(self) + for rule in self.portal.portal_rules.contentValues(): + if rule.getPortalType() != 'Payment Rule': + rule.validate() # XXX this should be enabled ! + self.base_amount = self.portal.portal_categories.base_amount + self.tax = self.portal.tax_module.newContent( + portal_type='Tax', + title='Tax') + self.client = self.portal.organisation_module.newContent( + portal_type='Organisation', + title='Client') + self.vendor = self.portal.organisation_module.newContent( + portal_type='Organisation', + title='Vendor') + self.resource = self.portal.product_module.newContent( + portal_type='Product', + title='Resource') + self.currency = self.portal.currency_module.newContent( + portal_type='Currency', + title='Currency') + self.trade_condition_module = self.portal.getDefaultModule( + self.trade_condition_type) + self.trade_condition = self.trade_condition_module.newContent( + portal_type=self.trade_condition_type, + title='Trade Condition') + self.order_module = self.portal.getDefaultModule( + self.order_type) + self.order = self.order_module.newContent( + portal_type=self.order_type, + created_by_builder=1, + title='Order') + + def tearDown(self): + get_transaction().abort() + for module in (self.portal.tax_module, + self.portal.organisation_module, + self.portal.currency_module, + self.portal.product_module, + self.portal.accounting_module, + self.portal.account_module, + self.portal.portal_simulation, + self.trade_condition_module, + self.order_module, + self.portal.portal_categories.base_amount, + self.portal.portal_categories.product_line): + module.manage_delObjects(list(module.objectIds())) + if 'test_invoice_transaction_rule' in self.portal.portal_rules.objectIds(): + self.portal.portal_rules.manage_delObjects('test_invoice_transaction_rule') + get_transaction().commit() + self.tic() + ERP5TypeTestCase.tearDown(self) + + +class AccountingBuildTestCase(TradeConditionTestCase): + """Same as TradeConditionTestCase, but with a rule to generate + accounting. + """ + def setUp(self): + TradeConditionTestCase.setUp(self) + self.receivable_account = self.portal.account_module.newContent( + id='receivable', + title='Receivable', + account_type='asset/receivable') + self.payable_account = self.portal.account_module.newContent( + id='payable', + title='Payable', + account_type='liability/payable') + self.income_account = self.portal.account_module.newContent( + id='income', + title='Income', + account_type='income') + self.expense_account = self.portal.account_module.newContent( + id='expense', + title='Expense', + account_type='expense') + self.collected_tax_account = self.portal.account_module.newContent( + id='collected_tax', + title='Collected Tax', + account_type='liability/payable/collected_vat') + self.refundable_tax_account = self.portal.account_module.newContent( + id='refundable_tax', + title='Refundable Tax', + account_type='asset/receivable/refundable_vat') + + for account in self.portal.account_module.contentValues(): + self.assertNotEquals(account.getAccountTypeValue(), None) + account.validate() + + dummy_resource = self.portal.portal_categories.product_line.newContent( + id='dummy_resource', + title='Dummy Resource') + self.resource.setProductLineValue(dummy_resource) + dummy_tax = self.portal.portal_categories.product_line.newContent( + id='dummy_tax', + title='Dummy Tax') + # FIXME: tax should not have a product line + self.tax.setProductLineValue(dummy_tax) + + itr = self.portal.portal_rules.newContent( + portal_type='Invoice Transaction Rule', + reference='default_invoice_transaction_rule', + id='test_invoice_transaction_rule', + title='Transaction Rule', + test_method_id='SimulationMovement_testInvoiceTransactionRule', + version=100) + predicate = itr.newContent(portal_type='Predicate',) + predicate.edit( + string_index='resource_type', + title='Resource Product', + int_index=1, + membership_criterion_base_category_list=['product_line',], + membership_criterion_category_list=['product_line/dummy_resource'],) + predicate = itr.newContent(portal_type='Predicate') + predicate.edit( + string_index='resource_type', + title='Resource Tax', + int_index=2, + membership_criterion_base_category_list=['product_line',], + membership_criterion_category_list=['product_line/dummy_tax'],) + get_transaction().commit() + self.tic() + accounting_rule_cell_list = itr.contentValues( + portal_type='Accounting Rule Cell') + self.assertEquals(2, len(accounting_rule_cell_list)) + product_rule_cell = itr._getOb("movement_0") + self.assertEquals(product_rule_cell.getTitle(), 'Resource Product') + product_rule_cell.newContent( + portal_type='Accounting Transaction Line', + source_value=self.receivable_account, + destination_value=self.payable_account, + quantity=-1) + product_rule_cell.newContent( + portal_type='Accounting Transaction Line', + source_value=self.income_account, + destination_value=self.expense_account, + quantity=1) + + tax_rule_cell = itr._getOb("movement_1") + self.assertEquals(tax_rule_cell.getTitle(), 'Resource 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) + itr.validate() + get_transaction().commit() + self.tic() + + +class TestApplyTradeCondition(TradeConditionTestCase): + """Tests Applying Trade Conditions + """ + def test_apply_trade_condition_set_categories(self): + self.trade_condition.setSourceSectionValue(self.vendor) + self.trade_condition.setDestinationSectionValue(self.client) + self.trade_condition.setSourceValue(self.vendor) + self.trade_condition.setDestinationValue(self.client) + self.trade_condition.setPriceCurrencyValue(self.currency) + self.order.setSpecialiseValue(self.trade_condition) + + self.order.Order_applyTradeCondition(self.trade_condition, force=1) + + self.assertEquals(self.vendor, self.order.getSourceSectionValue()) + self.assertEquals(self.vendor, self.order.getSourceValue()) + self.assertEquals(self.client, self.order.getDestinationSectionValue()) + self.assertEquals(self.client, self.order.getDestinationValue()) + self.assertEquals(self.currency, self.order.getPriceCurrencyValue()) + + def test_apply_trade_condition_keep_categories(self): + # source section & source are set on the order, not on the TC + self.order.setSourceSectionValue(self.vendor) + self.order.setSourceValue(self.vendor) + + self.trade_condition.setSourceSectionValue(None) + self.trade_condition.setSourceValue(None) + self.trade_condition.setDestinationSectionValue(self.client) + self.trade_condition.setDestinationValue(self.client) + self.trade_condition.setPriceCurrencyValue(self.currency) + self.order.setSpecialiseValue(self.trade_condition) + + self.order.Order_applyTradeCondition(self.trade_condition, force=1) + + # Applying the TC keeps values on the order + self.assertEquals(self.vendor, self.order.getSourceSectionValue()) + self.assertEquals(self.vendor, self.order.getSourceValue()) + self.assertEquals(self.client, self.order.getDestinationSectionValue()) + self.assertEquals(self.client, self.order.getDestinationValue()) + self.assertEquals(self.currency, self.order.getPriceCurrencyValue()) + + def test_apply_trade_condition_set_categories_with_hierarchy(self): + trade_condition_source = self.trade_condition_module.newContent( + portal_type=self.trade_condition.getPortalType(), + title='Trade Condition Source', + source_value=self.vendor, + source_section_value=self.vendor) + trade_condition_dest = self.trade_condition_module.newContent( + portal_type=self.trade_condition.getPortalType(), + title='Trade Condition Destination', + destination_value=self.client, + destination_section_value=self.client, + price_currency_value=self.currency, + # also set a source, it should not be used + source_value=self.client) + self.trade_condition.setSpecialiseValueList( + (trade_condition_source, trade_condition_dest)) + + self.order.Order_applyTradeCondition(self.trade_condition, force=1) + + self.assertEquals(self.vendor, self.order.getSourceSectionValue()) + self.assertEquals(self.vendor, self.order.getSourceValue()) + self.assertEquals(self.client, self.order.getDestinationSectionValue()) + self.assertEquals(self.client, self.order.getDestinationValue()) + self.assertEquals(self.currency, self.order.getPriceCurrencyValue()) + + def test_apply_trade_condition_copy_subobjects(self): + self.trade_condition.setPaymentConditionTradeDate('custom') + self.trade_condition.setPaymentConditionPaymentDate(DateTime(2001, 01, 01)) + self.order.setSpecialiseValue(self.trade_condition) + + self.order.Order_applyTradeCondition(self.trade_condition, force=1) + + self.assertEquals('custom', self.order.getPaymentConditionTradeDate()) + self.assertEquals(DateTime(2001, 01, 01), + self.order.getPaymentConditionPaymentDate()) + + def test_apply_trade_condition_copy_subobjects_with_hierarchy(self): + other_trade_condition = self.trade_condition_module.newContent( + portal_type=self.trade_condition.getPortalType(), + title='Other Trade Condition') + other_trade_condition.setPaymentConditionTradeDate('custom') + other_trade_condition.setPaymentConditionPaymentDate( + DateTime(2001, 01, 01)) + + self.trade_condition.setSpecialiseValue(other_trade_condition) + self.order.setSpecialiseValue(self.trade_condition) + + self.order.Order_applyTradeCondition(self.trade_condition, force=1) + + self.assertEquals('custom', self.order.getPaymentConditionTradeDate()) + self.assertEquals(DateTime(2001, 01, 01), + self.order.getPaymentConditionPaymentDate()) + + def test_tax_model_line_consistency(self): + base_1 = self.base_amount.newContent( + portal_type='Category', + title='Base 1') + tax_model_line = self.trade_condition.newContent( + portal_type='Tax Model Line', + base_application_value=base_1, + float_index=1, + efficiency=0.2, + resource_value=self.tax) + self.assertEquals([], tax_model_line.checkConsistency()) + self.assertEquals([], self.trade_condition.checkConsistency()) + + def test_view_tax_model_line(self): + base_1 = self.base_amount.newContent( + portal_type='Category', + title='Base 1') + tax_model_line = self.trade_condition.newContent( + portal_type='Tax Model Line', + base_application_value=base_1, + float_index=1, + efficiency=0.2, + resource_value=self.tax) + # TODO: fail if a field has an error + tax_model_line.view() + self.trade_condition.TradeCondition_viewTax() + + def test_tax_line_consistency(self): + base_1 = self.base_amount.newContent( + portal_type='Category', + title='Base 1') + tax_line = self.order.newContent( + portal_type='Tax Line', + resource_value=self.tax, + base_application_value=base_1, + quantity=0, + efficiency=5.5) + self.assertEquals([], tax_line.checkConsistency()) + + def test_view_tax_line(self): + base_1 = self.base_amount.newContent( + portal_type='Category', + title='Base 1') + tax_line = self.order.newContent( + portal_type='Tax Line', + resource_value=self.tax, + base_application_value=base_1, + quantity=0, + efficiency=5.5) + # TODO: fail if a field has an error + tax_line.view() + self.order.Delivery_viewTax() + + +class TestTaxLineCalculation(TradeConditionTestCase): + """Test calculating Tax Lines. + """ + def test_simple_tax_model_line_calculation(self): + base_1 = self.base_amount.newContent( + portal_type='Category', + title='Base 1') + self.resource.setBaseContributionValue(base_1) + tax_model_line = self.trade_condition.newContent( + portal_type='Tax Model Line', + base_application_value=base_1, + float_index=1, + efficiency=0.2, + resource_value=self.tax) + + self.order.Order_applyTradeCondition(self.trade_condition, force=1) + + # this creates a tax line, with quantity 0, and it will be updated when + # needed + tax_line_list = self.order.contentValues(portal_type='Tax Line') + self.assertEquals(1, len(tax_line_list)) + tax_line = tax_line_list[0] + self.assertEquals(0, tax_line.getQuantity()) + self.assertEquals(self.tax, tax_line.getResourceValue()) + self.assertEquals(0.2, tax_line.getPrice()) + + order_line = self.order.newContent( + portal_type=self.order_line_type, + resource_value=self.resource, + quantity=10, + price=10,) + + # now tax lines are updated + tax_line_list = self.order.contentValues(portal_type='Tax Line') + self.assertEquals(1, len(tax_line_list)) + tax_line = tax_line_list[0] + self.assertEquals(100, tax_line.getQuantity()) + self.assertEquals(0.2, tax_line.getPrice()) + self.assertEquals(20, tax_line.getTotalPrice()) + + def test_tax_model_line_calculation_with_two_lines(self): + base_1 = self.base_amount.newContent( + portal_type='Category', + title='Base 1') + self.resource.setBaseContributionValue(base_1) + tax_model_line = self.trade_condition.newContent( + portal_type='Tax Model Line', + base_application_value=base_1, + float_index=1, + efficiency=0.2, + resource_value=self.tax) + + self.order.Order_applyTradeCondition(self.trade_condition, force=1) + + # this creates a tax line, with quantity 0, and it will be updated when + # needed + tax_line_list = self.order.contentValues(portal_type='Tax Line') + self.assertEquals(1, len(tax_line_list)) + tax_line = tax_line_list[0] + self.assertEquals(0, tax_line.getQuantity()) + self.assertEquals(self.tax, tax_line.getResourceValue()) + self.assertEquals(0.2, tax_line.getPrice()) + + order_line_1 = self.order.newContent( + portal_type=self.order_line_type, + resource_value=self.resource, + quantity=3, + price=10,) + order_line_2 = self.order.newContent( + portal_type=self.order_line_type, + resource_value=self.resource, + quantity=7, + price=10,) + + # now tax lines are updated + tax_line_list = self.order.contentValues(portal_type='Tax Line') + self.assertEquals(1, len(tax_line_list)) + tax_line = tax_line_list[0] + self.assertEquals(100, tax_line.getQuantity()) + self.assertEquals(0.2, tax_line.getPrice()) + self.assertEquals(20, tax_line.getTotalPrice()) + + order_line_1_tax_line_list = \ + order_line_1.DeliveryMovement_getCorrespondingTaxLineList() + self.assertEquals(1, len(order_line_1_tax_line_list)) + tax_line = order_line_1_tax_line_list[0] + self.assertEquals(30, tax_line.getQuantity()) + self.assertEquals(0.2, tax_line.getPrice()) + self.assertEquals(6, tax_line.getTotalPrice()) + + order_line_2_tax_line_list = \ + order_line_2.DeliveryMovement_getCorrespondingTaxLineList() + self.assertEquals(1, len(order_line_2_tax_line_list)) + tax_line = order_line_2_tax_line_list[0] + self.assertEquals(70, tax_line.getQuantity()) + self.assertEquals(0.2, tax_line.getPrice()) + self.assertEquals(14, tax_line.getTotalPrice()) + + def test_tax_on_tax(self): + base_1 = self.base_amount.newContent( + portal_type='Category', + title='Base 1') + base_2 = self.base_amount.newContent( + portal_type='Category', + title='Base 2') + tax2 = self.portal.tax_module.newContent( + portal_type='Tax', + title='Tax 2') + self.resource.setBaseContributionValue(base_1) + tax_model_line = self.trade_condition.newContent( + portal_type='Tax Model Line', + base_application_value=base_1, + base_contribution_value=base_2, + float_index=1, + efficiency=0.2, + resource_value=self.tax) + tax_model_line = self.trade_condition.newContent( + portal_type='Tax Model Line', + base_application_value=base_2, + float_index=2, + efficiency=0.5, + resource_value=tax2) + + self.order.Order_applyTradeCondition(self.trade_condition, force=1) + + tax_line_list = self.order.contentValues(portal_type='Tax Line') + self.assertEquals(2, len(tax_line_list)) + tax_line1 = [tl for tl in tax_line_list if + tl.getResourceValue() == self.tax][0] + self.assertEquals(0, tax_line1.getQuantity()) + self.assertEquals(0.2, tax_line1.getPrice()) + self.assertEquals(1, tax_line1.getFloatIndex()) + self.assertEquals([base_1], tax_line1.getBaseApplicationValueList()) + self.assertEquals([base_2], tax_line1.getBaseContributionValueList()) + + tax_line2 = [tl for tl in tax_line_list if + tl.getResourceValue() == tax2][0] + self.assertEquals(0, tax_line2.getQuantity()) + self.assertEquals(0.5, tax_line2.getPrice()) + self.assertEquals(2, tax_line2.getFloatIndex()) + self.assertEquals([base_2], tax_line2.getBaseApplicationValueList()) + + order_line = self.order.newContent( + portal_type=self.order_line_type, + resource_value=self.resource, + quantity=3, + price=10,) + + self.assertEquals(30, tax_line1.getQuantity()) + self.assertEquals((30*0.2), tax_line2.getQuantity()) + + order_line.setQuantity(5) + self.assertEquals(50, tax_line1.getQuantity()) + self.assertEquals((50*0.2), tax_line2.getQuantity()) + + + def test_update_order_line_quantity_update_tax_line(self): + base_1 = self.base_amount.newContent( + portal_type='Category', + title='Base 1') + self.resource.setBaseContributionValue(base_1) + tax_model_line = self.trade_condition.newContent( + portal_type='Tax Model Line', + base_application_value=base_1, + float_index=1, + efficiency=0.2, + resource_value=self.tax) + + self.order.Order_applyTradeCondition(self.trade_condition, force=1) + + # this creates a tax line, with quantity 0, and it will be updated when + # needed + tax_line_list = self.order.contentValues(portal_type='Tax Line') + self.assertEquals(1, len(tax_line_list)) + tax_line = tax_line_list[0] + self.assertEquals(0, tax_line.getQuantity()) + self.assertEquals(self.tax, tax_line.getResourceValue()) + self.assertEquals(0.2, tax_line.getPrice()) + + order_line = self.order.newContent( + portal_type=self.order_line_type, + resource_value=self.resource, + quantity=10, + price=10,) + + # tax lines are updated + tax_line_list = self.order.contentValues(portal_type='Tax Line') + self.assertEquals(1, len(tax_line_list)) + tax_line = tax_line_list[0] + self.assertEquals(100, tax_line.getQuantity()) + self.assertEquals(0.2, tax_line.getPrice()) + self.assertEquals(20, tax_line.getTotalPrice()) + + # change the quantity on order_line, + order_line.setQuantity(20) + # the tax line is updated + self.assertEquals(200, tax_line.getQuantity()) + self.assertEquals(40, tax_line.getTotalPrice()) + + def test_order_cell_and_tax_line(self): + base_1 = self.base_amount.newContent( + portal_type='Category', + title='Base 1') + self.resource.setBaseContributionValue(base_1) + # make a resource with size variation + self.portal.portal_categories.size.newContent(id='small', title='Small') + self.portal.portal_categories.size.newContent(id='big', title='Big') + self.resource.setVariationBaseCategoryList(('size',)) + self.resource.setVariationCategoryList(('size/big', 'size/small')) + + tax_model_line = self.trade_condition.newContent( + portal_type='Tax Model Line', + base_application_value=base_1, + float_index=1, + efficiency=0.2, + resource_value=self.tax) + + self.order.Order_applyTradeCondition(self.trade_condition, force=1) + + # this creates a tax line, with quantity 0, and it will be updated when + # needed + tax_line_list = self.order.contentValues(portal_type='Tax Line') + self.assertEquals(1, len(tax_line_list)) + tax_line = tax_line_list[0] + self.assertEquals(0, tax_line.getQuantity()) + self.assertEquals(self.tax, tax_line.getResourceValue()) + self.assertEquals(0.2, tax_line.getPrice()) + + order_line = self.order.newContent( + portal_type=self.order_line_type, + resource_value=self.resource,) + order_line.setVariationCategoryList(('size/big', 'size/small')) + order_line.updateCellRange(base_id='movement') + cell_red = order_line.newCell('size/big', + portal_type=self.order_cell_type, + base_id='movement') + cell_red.setMappedValuePropertyList(['quantity', 'price']) + cell_red.setPrice(5) + cell_red.setQuantity(10) + cell_blue = order_line.newCell('size/small', + portal_type=self.order_cell_type, + base_id='movement') + cell_blue.setMappedValuePropertyList(['quantity', 'price']) + cell_blue.setPrice(2) + cell_blue.setQuantity(25) + self.assertEquals(100, order_line.getTotalPrice(fast=0)) + + tax_line_list = self.order.contentValues(portal_type='Tax Line') + self.assertEquals(1, len(tax_line_list)) + tax_line = tax_line_list[0] + self.assertEquals(100, tax_line.getQuantity()) + self.assertEquals(self.tax, tax_line.getResourceValue()) + self.assertEquals(0.2, tax_line.getPrice()) + + # TODO: discuss this behaviour, and what about getTotalNetPrice ? + #self.assertEquals(120, self.order.getTotalPrice(fast=0)) + + + def test_hierarchical_order_line_and_tax_line(self): + base_1 = self.base_amount.newContent( + portal_type='Category', + title='Base 1') + self.resource.setBaseContributionValue(base_1) + + tax_model_line = self.trade_condition.newContent( + portal_type='Tax Model Line', + base_application_value=base_1, + float_index=1, + efficiency=0.2, + resource_value=self.tax) + + self.order.Order_applyTradeCondition(self.trade_condition, force=1) + + # this creates a tax line, with quantity 0, and it will be updated when + # needed + tax_line_list = self.order.contentValues(portal_type='Tax Line') + self.assertEquals(1, len(tax_line_list)) + tax_line = tax_line_list[0] + self.assertEquals(0, tax_line.getQuantity()) + self.assertEquals(self.tax, tax_line.getResourceValue()) + self.assertEquals(0.2, tax_line.getPrice()) + + order_line = self.order.newContent( + portal_type=self.order_line_type, + resource_value=self.resource,) + suborder_line1 = order_line.newContent( + portal_type=self.order_line_type, + quantity=4, + price=5) + suborder_line2 = order_line.newContent( + portal_type=self.order_line_type, + quantity=2, + price=40) + + tax_line_list = self.order.contentValues(portal_type='Tax Line') + self.assertEquals(1, len(tax_line_list)) + tax_line = tax_line_list[0] + self.assertEquals(100, tax_line.getQuantity()) + self.assertEquals(self.tax, tax_line.getResourceValue()) + self.assertEquals(0.2, tax_line.getPrice()) + + def test_base_contribution_pseudo_acquisition(self): + base_1 = self.base_amount.newContent(portal_type='Category', + title='Base 1') + self.resource.setBaseContributionValueList((base_1,)) + line = self.order.newContent(portal_type=self.order_line_type) + self.assertEquals([], line.getBaseContributionValueList()) + line.setResourceValue(self.resource) + self.assertEquals([base_1], line.getBaseContributionValueList()) + line.setBaseContributionValueList([]) + self.assertEquals([], line.getBaseContributionValueList()) + + def test_multiple_order_line_multiple_tax_line(self): + base_1 = self.base_amount.newContent( + portal_type='Category', + title='Base 1') + base_2 = self.base_amount.newContent( + portal_type='Category', + title='Base 2') + self.resource.setBaseContributionValueList((base_1, base_2)) + tax_model_line_1 = self.trade_condition.newContent( + portal_type='Tax Model Line', + base_application_value=base_1, + float_index=1, + efficiency=0.1, + resource_value=self.tax) + tax_model_line_2 = self.trade_condition.newContent( + portal_type='Tax Model Line', + base_application_value=base_2, + float_index=2, + efficiency=0.2, + resource_value=self.tax) + tax_model_line_1_2 = self.trade_condition.newContent( + portal_type='Tax Model Line', + base_application_value_list=(base_1, base_2), + float_index=3, + efficiency=0.3, + resource_value=self.tax) + + self.order.Order_applyTradeCondition(self.trade_condition, force=1) + line_1 = self.order.newContent( + portal_type=self.order_line_type, + quantity=1, price=1, + resource_value=self.resource, + base_contribution_value_list=(base_1,)) + # -> tax_model_line_1 and tax_model_line_1_2 are applicable + line_2 = self.order.newContent( + portal_type=self.order_line_type, + quantity=2, price=2, + resource_value=self.resource, + base_contribution_value_list=(base_2,)) + # -> tax_model_line_2 and tax_model_line_1_2 are applicable + line_3 = self.order.newContent( + portal_type=self.order_line_type, + quantity=3, price=3, + resource_value=self.resource, + base_contribution_value_list=(base_1, base_2)) + # -> tax_model_line_1, tax_model_line_2 and tax_model_line_1_2 are applicable + # (but they are not applied twice) + + tax_line_list = self.order.contentValues(portal_type='Tax Line') + self.assertEquals(3, len(tax_line_list)) + tax_line_1 = [x for x in tax_line_list if x.getPrice() == 0.1][0] + tax_line_2 = [x for x in tax_line_list if x.getPrice() == 0.2][0] + tax_line_3 = [x for x in tax_line_list if x.getPrice() == 0.3][0] + + self.assertEquals(sum([line_1.getTotalPrice(), + line_3.getTotalPrice()]), tax_line_1.getQuantity()) + self.assertEquals(sum([line_2.getTotalPrice(), + line_3.getTotalPrice()]), tax_line_2.getQuantity()) + self.assertEquals(sum([line_1.getTotalPrice(), + line_2.getTotalPrice(), + line_3.getTotalPrice()]), tax_line_3.getQuantity()) + + # TODO: test DeliveryMovement_getCorrespondingTaxLineList + tax_movement_list = line_1.DeliveryMovement_getCorrespondingTaxLineList() + self.assertEquals(2, len(tax_movement_list)) + tax_1_movement = [m for m in tax_movement_list if m.getPrice() == 0.1][0] +# self.assertEquals( + + + +class TestTaxLineOrderSimulation(TradeConditionTestCase): + """Test Simulation of Tax Lines on Orders + """ + def test_tax_line_simulation(self): + base_1 = self.base_amount.newContent( + portal_type='Category', + title='Base 1') + self.resource.setBaseContributionValue(base_1) + tax_model_line = self.trade_condition.newContent( + portal_type='Tax Model Line', + base_application_value=base_1, + float_index=1, + efficiency=0.2, + resource_value=self.tax) + + order = self.order + order.Order_applyTradeCondition(self.trade_condition, force=1) + order.setSourceSectionValue(self.vendor) + order.setSourceValue(self.vendor) + order.setDestinationSectionValue(self.client) + order.setDestinationValue(self.client) + order.setStartDate(DateTime(2001, 1, 1)) + order_line = order.newContent( + portal_type=self.order_line_type, + resource_value=self.resource, + quantity=10, + price=10,) + order.plan() + order.confirm() + self.assertEquals('confirmed', order.getSimulationState()) + get_transaction().commit() + self.tic() + related_applied_rule_list = order.getCausalityRelatedValueList( + portal_type='Applied Rule') + self.assertEquals(1, len(related_applied_rule_list)) + root_applied_rule = related_applied_rule_list[0] + simulation_movement_list = root_applied_rule.contentValues( + portal_type='Simulation Movement') + self.assertEquals(1, len(simulation_movement_list)) + level2_applied_rule_list = simulation_movement_list[0].contentValues() + self.assertEquals(2, len(level2_applied_rule_list)) + # first test the invoice movement, they should have base_contribution set + # correctly + invoice_rule_list = [ar for ar in level2_applied_rule_list if + ar.getSpecialiseValue().getPortalType() == 'Invoicing Rule'] + self.assertEquals(1, len(invoice_rule_list)) + invoice_simulation_movement_list = invoice_rule_list[0].contentValues() + self.assertEquals(1, len(invoice_simulation_movement_list)) + invoice_simulation_movement = invoice_simulation_movement_list[0] + self.assertEquals(self.resource, + invoice_simulation_movement.getResourceValue()) + self.assertEquals([base_1], + invoice_simulation_movement.getBaseContributionValueList()) + + # now test the tax movement + applied_tax_rule_list = [ar for ar in level2_applied_rule_list if + ar.getSpecialiseValue().getPortalType() == 'Tax Rule'] + self.assertEquals(1, len(applied_tax_rule_list)) + tax_simulation_movement_list = applied_tax_rule_list[0].contentValues() + self.assertEquals(1, len(tax_simulation_movement_list)) + tax_simulation_movement = tax_simulation_movement_list[0] + + self.assertEquals(self.tax, tax_simulation_movement.getResourceValue()) + self.assertEquals([base_1], + tax_simulation_movement.getBaseApplicationValueList()) + self.assertEquals(100, tax_simulation_movement.getQuantity()) + self.assertEquals(0.2, tax_simulation_movement.getPrice()) + + # reexpand and check nothing changed + root_applied_rule.expand() + applied_tax_rule_list = [ar for ar in level2_applied_rule_list if + ar.getSpecialiseValue().getPortalType() == 'Tax Rule'] + self.assertEquals(1, len(applied_tax_rule_list)) + tax_simulation_movement_list = applied_tax_rule_list[0].contentValues() + self.assertEquals(1, len(tax_simulation_movement_list)) + tax_simulation_movement = tax_simulation_movement_list[0] + + self.assertEquals(self.tax, tax_simulation_movement.getResourceValue()) + self.assertEquals([base_1], + tax_simulation_movement.getBaseApplicationValueList()) + self.assertEquals(100, tax_simulation_movement.getQuantity()) + self.assertEquals(0.2, tax_simulation_movement.getPrice()) + + def test_2_tax_lines_simulation(self): + base_1 = self.base_amount.newContent( + portal_type='Category', + title='Base 1') + self.resource.setBaseContributionValue(base_1) + tax_model_line = self.trade_condition.newContent( + portal_type='Tax Model Line', + base_application_value=base_1, + float_index=1, + efficiency=0.2, + resource_value=self.tax) + + order = self.order + order.Order_applyTradeCondition(self.trade_condition, force=1) + order.setSourceSectionValue(self.vendor) + order.setSourceValue(self.vendor) + order.setDestinationSectionValue(self.client) + order.setDestinationValue(self.client) + order.setStartDate(DateTime(2001, 1, 1)) + order_line1 = order.newContent( + portal_type=self.order_line_type, + resource_value=self.resource, + quantity=2, + price=15,) + order_line2 = order.newContent( + portal_type=self.order_line_type, + resource_value=self.resource, + quantity=7, + price=10,) + order.plan() + order.confirm() + self.assertEquals('confirmed', order.getSimulationState()) + get_transaction().commit() + self.tic() + related_applied_rule_list = order.getCausalityRelatedValueList( + portal_type='Applied Rule') + self.assertEquals(1, len(related_applied_rule_list)) + root_applied_rule = related_applied_rule_list[0] + simulation_movement_list = root_applied_rule.contentValues( + portal_type='Simulation Movement') + self.assertEquals(2, len(simulation_movement_list)) + # line 1 + line1_simulation_movement_list = [sm for sm in simulation_movement_list + if sm.getOrderValue() == order_line1] + self.assertEquals(1, len(line1_simulation_movement_list)) + simulation_movement = line1_simulation_movement_list[0] + self.assertEquals(2.0, simulation_movement.getQuantity()) + applied_tax_rule_list = [ar for ar in simulation_movement.objectValues() + if ar.getSpecialiseValue().getPortalType() == 'Tax Rule'] + self.assertEquals(1, len(applied_tax_rule_list)) + tax_simulation_movement_list = applied_tax_rule_list[0].contentValues() + self.assertEquals(1, len(tax_simulation_movement_list)) + tax_simulation_movement = tax_simulation_movement_list[0] + self.assertEquals(self.tax, tax_simulation_movement.getResourceValue()) + self.assertEquals([base_1], + tax_simulation_movement.getBaseApplicationValueList()) + self.assertEquals(30, tax_simulation_movement.getQuantity()) + self.assertEquals(0.2, tax_simulation_movement.getPrice()) + + # line 2 + line2_simulation_movement_list = [sm for sm in simulation_movement_list + if sm.getOrderValue() == order_line2] + self.assertEquals(1, len(line2_simulation_movement_list)) + simulation_movement = line2_simulation_movement_list[0] + self.assertEquals(7., simulation_movement.getQuantity()) + applied_tax_rule_list = [ar for ar in simulation_movement.objectValues() + if ar.getSpecialiseValue().getPortalType() == 'Tax Rule'] + self.assertEquals(1, len(applied_tax_rule_list)) + tax_simulation_movement_list = applied_tax_rule_list[0].contentValues() + self.assertEquals(1, len(tax_simulation_movement_list)) + tax_simulation_movement = tax_simulation_movement_list[0] + self.assertEquals(self.tax, tax_simulation_movement.getResourceValue()) + self.assertEquals([base_1], + tax_simulation_movement.getBaseApplicationValueList()) + self.assertEquals(70, tax_simulation_movement.getQuantity()) + self.assertEquals(0.2, tax_simulation_movement.getPrice()) + + + def test_tax_line_build(self): + base_1 = self.base_amount.newContent( + portal_type='Category', + title='Base 1') + self.resource.setBaseContributionValue(base_1) + tax_model_line = self.trade_condition.newContent( + portal_type='Tax Model Line', + base_application_value=base_1, + float_index=1, + efficiency=0.2, + resource_value=self.tax) + + order = self.order + order.Order_applyTradeCondition(self.trade_condition, force=1) + order.setSourceSectionValue(self.vendor) + order.setSourceValue(self.vendor) + order.setDestinationSectionValue(self.client) + order.setDestinationValue(self.client) + order.setPriceCurrencyValue(self.currency) + order.setStartDate(DateTime(2001, 1, 1)) + order_line = order.newContent( + portal_type=self.order_line_type, + resource_value=self.resource, + quantity=2, + price=15,) + order.plan() + order.confirm() + self.assertEquals('confirmed', order.getSimulationState()) + get_transaction().commit() + self.tic() + related_delivery = order.getCausalityRelatedValue( + portal_type=('Purchase Packing List', 'Sale Packing List')) + self.assertNotEquals(related_delivery, None) + related_delivery.setReady() + related_delivery.start() + related_delivery.stop() + related_delivery.deliver() + self.assertEquals('delivered', related_delivery.getSimulationState()) + get_transaction().commit() + self.tic() + + related_invoice = related_delivery.getCausalityRelatedValue( + portal_type=('Purchase Invoice Transaction', + 'Sale Invoice Transaction')) + self.assertNotEquals(related_invoice, None) + invoice_line_list = related_invoice.contentValues( + portal_type='Invoice Line') + tax_line_list = related_invoice.contentValues( + portal_type='Tax Line') + + self.assertEquals(1, len(invoice_line_list)) + invoice_line = invoice_line_list[0] + self.assertEquals(2, invoice_line.getQuantity()) + self.assertEquals(15, invoice_line.getPrice()) + self.assertEquals(self.resource, invoice_line.getResourceValue()) + self.assertEquals([base_1], invoice_line.getBaseContributionValueList()) + + self.assertEquals(1, len(tax_line_list)) + tax_line = tax_line_list[0] + self.assertEquals(30, tax_line.getQuantity()) + self.assertEquals(0.2, tax_line.getPrice()) + self.assertEquals(self.tax, tax_line.getResourceValue()) + self.assertEquals([base_1], tax_line.getBaseApplicationValueList()) + self.assertEquals([], tax_line.getBaseContributionValueList()) + + self.assertEquals('solved', related_invoice.getCausalityState()) + + # Of course, this invoice does not generate simulation again + self.assertEquals([], related_invoice.getCausalityRelatedValueList( + portal_type='Applied Rule')) + + + def test_tax_line_merged_build(self): + # an order with 2 lines and 1 tax line will later be built in an invoice + # with 2 lines and 1 tax line + base_1 = self.base_amount.newContent( + portal_type='Category', + title='Base 1') + self.resource.setBaseContributionValue(base_1) + resource2 = self.portal.product_module.newContent( + portal_type='Product', + title='Resource 2', + base_contribution_value_list=[base_1]) + tax_model_line = self.trade_condition.newContent( + portal_type='Tax Model Line', + base_application_value=base_1, + float_index=1, + efficiency=0.2, + resource_value=self.tax) + + order = self.order + order.Order_applyTradeCondition(self.trade_condition, force=1) + order.setSourceSectionValue(self.vendor) + order.setSourceValue(self.vendor) + order.setDestinationSectionValue(self.client) + order.setDestinationValue(self.client) + order.setPriceCurrencyValue(self.currency) + order.setStartDate(DateTime(2001, 1, 1)) + order_line1 = order.newContent( + portal_type=self.order_line_type, + resource_value=self.resource, + quantity=2, + price=15,) + order_line2 = order.newContent( + portal_type=self.order_line_type, + resource_value=resource2, + quantity=7, + price=10,) + # check existing tax line + tax_line_list = order.contentValues(portal_type='Tax Line') + self.assertEquals(1, len(tax_line_list)) + tax_line = tax_line_list[0] + self.assertEquals(self.tax, tax_line.getResourceValue()) + self.assertEquals(2*15 + 7*10, tax_line.getQuantity()) + self.assertEquals(0.2, tax_line.getPrice()) + + order.plan() + order.confirm() + self.assertEquals('confirmed', order.getSimulationState()) + get_transaction().commit() + self.tic() + related_delivery = order.getCausalityRelatedValue( + portal_type=('Purchase Packing List', 'Sale Packing List')) + self.assertNotEquals(related_delivery, None) + related_delivery.setReady() + related_delivery.start() + related_delivery.stop() + related_delivery.deliver() + self.assertEquals('delivered', related_delivery.getSimulationState()) + get_transaction().commit() + self.tic() + + related_invoice = related_delivery.getCausalityRelatedValue( + portal_type=('Purchase Invoice Transaction', + 'Sale Invoice Transaction')) + self.assertNotEquals(related_invoice, None) + invoice_line_list = related_invoice.contentValues( + portal_type='Invoice Line') + tax_line_list = related_invoice.contentValues( + portal_type='Tax Line') + + self.assertEquals(2, len(invoice_line_list)) + + self.assertEquals(1, len(tax_line_list)) + tax_line = tax_line_list[0] + self.assertEquals(100, tax_line.getQuantity()) + self.assertEquals(0.2, tax_line.getPrice()) + self.assertEquals(self.tax, tax_line.getResourceValue()) + self.assertEquals([base_1], tax_line.getBaseApplicationValueList()) + self.assertEquals([], tax_line.getBaseContributionValueList()) + + self.assertEquals('solved', related_invoice.getCausalityState()) + + def test_tax_line_updated_on_invoice_line_change(self): + base_1 = self.base_amount.newContent( + portal_type='Category', + title='Base 1') + self.resource.setBaseContributionValue(base_1) + tax_model_line = self.trade_condition.newContent( + portal_type='Tax Model Line', + base_application_value=base_1, + float_index=1, + efficiency=0.2, + resource_value=self.tax) + + order = self.order + order.Order_applyTradeCondition(self.trade_condition, force=1) + order.setSourceSectionValue(self.vendor) + order.setSourceValue(self.vendor) + order.setDestinationSectionValue(self.client) + order.setDestinationValue(self.client) + order.setPriceCurrencyValue(self.currency) + order.setStartDate(DateTime(2001, 1, 1)) + order_line = order.newContent( + portal_type=self.order_line_type, + resource_value=self.resource, + quantity=2, + price=15,) + order.plan() + order.confirm() + self.assertEquals('confirmed', order.getSimulationState()) + get_transaction().commit() + self.tic() + related_delivery = order.getCausalityRelatedValue( + portal_type=('Purchase Packing List', 'Sale Packing List')) + self.assertNotEquals(related_delivery, None) + related_delivery.setReady() + related_delivery.start() + related_delivery.stop() + related_delivery.deliver() + self.assertEquals('delivered', related_delivery.getSimulationState()) + get_transaction().commit() + self.tic() + + related_invoice = related_delivery.getCausalityRelatedValue( + portal_type=('Purchase Invoice Transaction', + 'Sale Invoice Transaction')) + self.assertNotEquals(related_invoice, None) + self.assertEquals('solved', related_invoice.getCausalityState()) + invoice_line_list = related_invoice.contentValues( + portal_type='Invoice Line') + tax_line_list = related_invoice.contentValues( + portal_type='Tax Line') + + self.assertEquals(1, len(tax_line_list)) + tax_line = tax_line_list[0] + self.assertEquals(30, tax_line.getQuantity()) + self.assertEquals(0.2, tax_line.getPrice()) + self.assertEquals(self.tax, tax_line.getResourceValue()) + self.assertEquals([base_1], tax_line.getBaseApplicationValueList()) + self.assertEquals([], tax_line.getBaseContributionValueList()) + + self.assertEquals(1, len(invoice_line_list)) + invoice_line = invoice_line_list[0] + # change a total price on the invoice_line, + invoice_line.setQuantity(3) + get_transaction().commit() + self.tic() + # it will be reflected on the tax line + self.assertEquals(45, tax_line.getQuantity()) + self.assertTrue(tax_line.isDivergent()) + # and the invoice is diverged + self.assertEquals('diverged', related_invoice.getCausalityState()) + + +class TestTaxLineInvoiceSimulation(AccountingBuildTestCase): + """Test Simulation of Tax Lines on Invoices + """ + def test_tax_line_simulation(self): + base_1 = self.base_amount.newContent( + portal_type='Category', + title='Base 1') + self.resource.setBaseContributionValue(base_1) + tax_model_line = self.trade_condition.newContent( + portal_type='Tax Model Line', + base_application_value=base_1, + float_index=1, + efficiency=0.2, + resource_value=self.tax) + + invoice = self.order + invoice.Order_applyTradeCondition(self.trade_condition, force=1) + invoice.setSourceSectionValue(self.vendor) + invoice.setSourceValue(self.vendor) + invoice.setDestinationSectionValue(self.client) + invoice.setDestinationValue(self.client) + invoice.setStartDate(DateTime(2001, 1, 1)) + invoice.setPriceCurrencyValue(self.currency) + invoice_line = invoice.newContent( + portal_type=self.order_line_type, + resource_value=self.resource, + quantity=10, + price=10,) + tax_line_list = invoice.contentValues(portal_type='Tax Line') + self.assertEquals(1, len(tax_line_list)) + tax_line = tax_line_list[0] + + invoice.plan() + invoice.confirm() + self.assertEquals('confirmed', invoice.getSimulationState()) + get_transaction().commit() + self.tic() + related_applied_rule_list = invoice.getCausalityRelatedValueList( + portal_type='Applied Rule') + self.assertEquals(1, len(related_applied_rule_list)) + root_applied_rule = related_applied_rule_list[0] + simulation_movement_list = root_applied_rule.contentValues( + portal_type='Simulation Movement') + self.assertEquals(2, len(simulation_movement_list)) + tax_simulation_movement_list = [m for m in simulation_movement_list + if m.getOrderValue() == tax_line] + self.assertEquals(1, len(tax_simulation_movement_list)) + tax_simulation_movement = tax_simulation_movement_list[0] + self.assertEquals([base_1], + tax_simulation_movement.getBaseApplicationValueList()) + self.assertEquals(100, tax_simulation_movement.getQuantity()) + self.assertEquals(0.2, tax_simulation_movement.getPrice()) + self.assertEquals(self.currency, + tax_simulation_movement.getPriceCurrencyValue()) + + invoice_simulation_movement_list = [m for m in simulation_movement_list + if m.getOrderValue() == invoice_line] + self.assertEquals(1, len(invoice_simulation_movement_list)) + invoice_simulation_movement = invoice_simulation_movement_list[0] + self.assertEquals([base_1], + invoice_simulation_movement.getBaseContributionValueList()) + self.assertEquals(10, invoice_simulation_movement.getQuantity()) + self.assertEquals(10, invoice_simulation_movement.getPrice()) + self.assertEquals(self.currency, + invoice_simulation_movement.getPriceCurrencyValue()) + self.assertEquals(self.resource, + invoice_simulation_movement.getResourceValue()) + invoice.start() + self.assertEquals('started', invoice.getSimulationState()) + get_transaction().commit() + self.tic() + accounting_line_list = invoice.getMovementList( + portal_type=('Sale Invoice Transaction Line', + 'Purchase Invoice Transaction Line')) + self.assertEquals(3, len(accounting_line_list)) + receivable_line = [l for l in accounting_line_list if + l.getSourceValue() == self.receivable_account][0] + self.assertEquals(self.payable_account, + receivable_line.getDestinationValue()) + self.assertEquals(120, receivable_line.getSourceDebit()) + + tax_line = [l for l in accounting_line_list if + l.getSourceValue() == self.collected_tax_account][0] + self.assertEquals(self.refundable_tax_account, + tax_line.getDestinationValue()) + self.assertEquals(20, tax_line.getSourceCredit()) + + self.assertEquals('solved', invoice.getCausalityState()) + + +class DiscountCalculation: + """Test Calculating Discount + """ + def test_simple_discount_model_line_calculation(self): + discount_line =self.trade_condition.newContent( + portal_type='Discount Model Line') + + +class TestWithSaleOrder: + order_type = 'Sale Order' + order_line_type = 'Sale Order Line' + order_cell_type = 'Sale Order Cell' + trade_condition_type = 'Sale Trade Condition' + +class TestWithPurchaseOrder: + order_type = 'Purchase Order' + order_line_type = 'Purchase Order Line' + order_cell_type = 'Purchase Order Cell' + trade_condition_type = 'Purchase Trade Condition' + +class TestWithSaleInvoice: + order_type = 'Sale Invoice Transaction' + order_line_type = 'Invoice Line' + order_cell_type = 'Invoice Cell' + trade_condition_type = 'Sale Trade Condition' + +class TestWithPurchaseInvoice: + order_type = 'Purchase Invoice Transaction' + order_line_type = 'Invoice Line' + order_cell_type = 'Invoice Cell' + trade_condition_type = 'Purchase Trade Condition' + + +class TestApplyTradeConditionSaleOrder( + TestApplyTradeCondition, TestWithSaleOrder): + pass +class TestApplyTradeConditionPurchaseOrder( + TestApplyTradeCondition, TestWithPurchaseOrder): + pass + +class TestTaxLineCalculationSaleOrder( + TestTaxLineCalculation, TestWithSaleOrder): + pass +class TestTaxLineCalculationPurchaseOrder( + TestTaxLineCalculation, TestWithPurchaseOrder): + pass +class TestTaxLineCalculationSaleInvoice( + TestTaxLineCalculation, TestWithSaleInvoice): + def not_available(self): + pass + test_hierarchical_order_line_and_tax_line = not_available +class TestTaxLineCalculationPurchaseInvoice( + TestTaxLineCalculation, TestWithPurchaseInvoice): + def not_available(self): + pass + test_hierarchical_order_line_and_tax_line = not_available + +class TestTaxLineOrderSimulationSaleOrder( + TestTaxLineOrderSimulation, TestWithSaleOrder): + pass +class TestTaxLineOrderSimulationPurchaseOrder( + TestTaxLineOrderSimulation, TestWithPurchaseOrder): + pass + +class TestTaxLineInvoiceSimulationPurchaseInvoice( + TestTaxLineInvoiceSimulation, TestWithPurchaseInvoice): + pass +class TestTaxLineInvoiceSimulationSaleInvoice( + TestTaxLineInvoiceSimulation, TestWithSaleInvoice): + pass + +def test_suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(TestApplyTradeConditionSaleOrder)) + suite.addTest(unittest.makeSuite(TestApplyTradeConditionPurchaseOrder)) + suite.addTest(unittest.makeSuite(TestTaxLineCalculationSaleOrder)) + suite.addTest(unittest.makeSuite(TestTaxLineCalculationPurchaseOrder)) + suite.addTest(unittest.makeSuite(TestTaxLineCalculationSaleInvoice)) + suite.addTest(unittest.makeSuite(TestTaxLineCalculationPurchaseInvoice)) + suite.addTest(unittest.makeSuite(TestTaxLineOrderSimulationSaleOrder)) + suite.addTest(unittest.makeSuite(TestTaxLineOrderSimulationPurchaseOrder)) + suite.addTest(unittest.makeSuite(TestTaxLineInvoiceSimulationPurchaseInvoice)) + suite.addTest(unittest.makeSuite(TestTaxLineInvoiceSimulationSaleInvoice)) + return suite +