diff --git a/product/ERP5/Document/RoundingModel.py b/product/ERP5/Document/RoundingModel.py
new file mode 100644
index 0000000000000000000000000000000000000000..bf476b876360c2f50e2bb9be047f222c48fe82f5
--- /dev/null
+++ b/product/ERP5/Document/RoundingModel.py
@@ -0,0 +1,141 @@
+##############################################################################
+#
+# Copyright (c) 2009 Nexedi KK and Contributors. All Rights Reserved.
+#
+# 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., 51 Franklin Street - Fifth Floor, Boston, MA
+# 02110-1301, USA.
+#
+##############################################################################
+from AccessControl import ClassSecurityInfo
+from Products.ERP5Type import PropertySheet, Permissions
+from Products.ERP5.Document.Predicate import Predicate
+from Products.ERP5Type.Utils import UpperCase
+
+
+class RoundingModel(Predicate):
+  """
+  A Rounding Model class which defines rounding rule.
+  """
+  meta_type = 'ERP5 Rounding Model'
+  portal_type = 'Rounding Model'
+  add_permission = Permissions.AddPortalContent
+
+  security = ClassSecurityInfo()
+
+  property_sheets = (PropertySheet.Base,
+                     PropertySheet.SimpleItem,
+                     PropertySheet.XMLObject,
+                     PropertySheet.CategoryCore,
+                     PropertySheet.DublinCore,
+                     PropertySheet.Predicate,
+                     PropertySheet.SortIndex,
+                     PropertySheet.RoundingModel,
+                     )
+
+  security.declareProtected(Permissions.AccessContentsInformation, 'roundValue')
+  def roundValue(self, value):
+    """
+    Return rounded value.
+    """
+    if self.getRoundingMethodId() is not None:
+      rounding_method = getattr(self, 'RoundingModel_%s' % self.getRoundingMethodId(), None)
+      if rounding_method is None:
+        raise ValueError, 'Rounding method (%s) was not found.'
+    else:
+      from decimal import Decimal
+      from Products.ERP5.Tool.RoundingTool import ROUNDING_OPTION_DICT
+      decimal_rounding_option = self.getDecimalRoundingOption()
+      if (decimal_rounding_option is None or
+          decimal_rounding_option not in ROUNDING_OPTION_DICT):
+        raise ValueError, 'Decimal rounding option must be selected.'
+      def rounding_method(value, decimal_exponent):
+        return float(Decimal(str(value)).quantize(Decimal(decimal_exponent),
+                                                  rounding=decimal_rounding_option))
+    return rounding_method(value, self.getDecimalExponent())
+
+  security.declareProtected(Permissions.AccessContentsInformation, 'getRoundingProxy')
+  def getRoundingProxy(self, document):
+    """
+    Return a rounding proxy object which getter methods returns rounded
+    value by following the rounding model definition.
+    """
+    rounding_model = self
+    rounded_property_getter_method_name_list = []
+
+    if isinstance(document, RoundingProxy):
+      temp_document = document._getOriginalDocument()
+      original_document = document
+    else:
+      from Products.ERP5Type import Document
+      if document.__class__.__name__ == 'TempDocument':
+        class_ = document.__class__.__bases__[0]
+      else:
+        class_ = document.__class__
+      constructor = getattr(Document, 'newTemp%s' % class_.__name__)
+      temp_document = constructor(document.getParentValue(), 'id')
+      temp_document.__dict__.update(document.__dict__)
+      original_document = temp_document
+
+    for property_id in rounding_model.getRoundedPropertyIdList():
+      getter_name = 'get%s' % UpperCase(property_id)
+      getter = getattr(temp_document,
+                       getter_name, None)
+      setter_name = 'set%s' % UpperCase(property_id)
+      setter = getattr(temp_document,
+                       setter_name, None)
+
+      if getter is not None and setter is not None:
+        # round the property value itself
+        setter(self.roundValue(getter()))
+      else:
+        # cannot round the property value so that the return value of getter
+        # will be rounded
+        rounded_property_getter_method_name_list.append(getter_name)
+
+    class _RoundingProxy(RoundingProxy):
+
+      def _getOriginalDocument(self):
+        if isinstance(original_document, RoundingProxy):
+          return original_document._editOriginalDocument()
+        else:
+          return original_document
+
+      def __getattr__(self, name):
+        attribute = getattr(original_document, name)
+        if getattr(attribute, 'DUMMY_ROUNDING_METHOD_MARK', None) is DUMMY_ROUNDING_METHOD_MARK:
+          return attribute
+        if name in rounded_property_getter_method_name_list:
+          def dummyMethod(*args, **kw):
+            return rounding_model.roundValue(attribute(*args, **kw))
+          dummyMethod.DUMMY_ROUNDING_METHOD_MARK = DUMMY_ROUNDING_METHOD_MARK
+          return dummyMethod
+        else:
+          return attribute
+    return _RoundingProxy()
+
+DUMMY_ROUNDING_METHOD_MARK = object()
+
+class RoundingProxy(object):
+  """Super class of _RoundingProxy class defined above. Use this class for
+  isinstance method to check if object is a real instance or a rounding proxy
+  instance.
+  """
diff --git a/product/ERP5/Document/TradeModelLine.py b/product/ERP5/Document/TradeModelLine.py
index 7f949b2106b2a4af8e8d8e003ff7cac76b9b1218..b101b884bd6abe64a4f8f8734ef610358eadc473 100644
--- a/product/ERP5/Document/TradeModelLine.py
+++ b/product/ERP5/Document/TradeModelLine.py
@@ -29,6 +29,7 @@
 ##############################################################################
 
 from AccessControl import ClassSecurityInfo
+from Products.CMFCore.utils import getToolByName
 from Products.ERP5Type import Permissions, PropertySheet, interfaces
 from Products.ERP5Type.XMLMatrix import XMLMatrix
 from Products.ERP5.Document.Amount import Amount
@@ -168,6 +169,14 @@ class TradeModelLine(Predicate, XMLMatrix, Amount):
                                base_id='movement', rounding=False, **kw):
     from Products.ERP5Type.Document import newTempSimulationMovement
 
+    # Define rounding stuff
+    portal_roundings = getToolByName(self, 'portal_roundings', None)
+
+    # ROUNDING
+    if rounding:
+      movement_list = [portal_roundings.getRoundingProxy(movement, context=self)
+                       for movement in movement_list]
+
     aggregated_amount_list = AggregatedAmountList()
     base_application_list = self.getBaseApplicationList()
 
@@ -247,6 +256,7 @@ class TradeModelLine(Predicate, XMLMatrix, Amount):
 
       update = 0
       base_category_list = self.getVariationBaseCategoryList()
+      
       # get cells categories cartesian product
       cell_key_list = self.getCellKeyList(base_id='movement')
       if len(cell_key_list) > 0:
@@ -260,6 +270,17 @@ class TradeModelLine(Predicate, XMLMatrix, Amount):
                                                       cell_coordinates))
           tmp_movement = newTempSimulationMovement(self.getPortalObject(),
               self_id)
+
+          # ROUNDING
+          if rounding:
+            # Once tmp_movement is replaced with the proxy, then the proxy
+            # object returns rounded value.
+            # For example, if rounding model is defined as
+            # rounded_property_id='total_price', then proxied
+            # tmp_movement.getTotalPrice() returns rounded result.
+            # If rounded_property_id='quantity', then
+            # tmp_movement.getQuantity() will be rounded.
+            tmp_movement = portal_roundings.getRoundingProxy(tmp_movement, context=self)
           tmp_movement.edit(
               variation_base_category_list = cell.getVariationBaseCategoryList(),
               variation_category_list = cell.getVariationCategoryList(),
@@ -275,6 +296,12 @@ class TradeModelLine(Predicate, XMLMatrix, Amount):
           price = self.getPrice(),
           **common_params
         )
+
+        # ROUNDING
+        if rounding:
+          # Replace temporary movement with rounding proxy so that target
+          # property value will be rounded.
+          tmp_movement = portal_roundings.getRoundingProxy(tmp_movement, context=self)
         tmp_movement_list.append(tmp_movement)
     modified = 0
     for tmp_movement in tmp_movement_list:
diff --git a/product/ERP5/PropertySheet/RoundingModel.py b/product/ERP5/PropertySheet/RoundingModel.py
index c56cf29e721fe0dbeac83d36c3cd02d5b69923b4..ab11b966108e278ab0cec29185aacd1579e82569 100644
--- a/product/ERP5/PropertySheet/RoundingModel.py
+++ b/product/ERP5/PropertySheet/RoundingModel.py
@@ -42,7 +42,7 @@ class RoundingModel(DecimalOption):
     },
     { 'id'          : 'rounded_property_id',
       'description' : 'The property name which value is rounded. Note that some property is virtual, like total_price.',
-      'type'        : 'string',
+      'type'        : 'tokens',
       'mode'        : 'w',
       'default'     : None,
     },
diff --git a/product/ERP5/Tool/RoundingTool.py b/product/ERP5/Tool/RoundingTool.py
new file mode 100644
index 0000000000000000000000000000000000000000..b4a4e8e6ebd2eecb798b34fe41c15d0bc2d2a52e
--- /dev/null
+++ b/product/ERP5/Tool/RoundingTool.py
@@ -0,0 +1,115 @@
+# -*- coding: utf-8 -*-
+#############################################################################
+#
+# Copyright (c) 2009 Nexedi KK and Contributors. All Rights Reserved.
+#                    Yusei TAHARA <yusei@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 zope.interface
+from AccessControl import ClassSecurityInfo
+from Products.ERP5Type.Tool.BaseTool import BaseTool
+from Products.ERP5.interfaces.rounding_tool import IRoundingTool
+from decimal import (ROUND_DOWN, ROUND_UP, ROUND_CEILING, ROUND_FLOOR,
+                     ROUND_HALF_DOWN, ROUND_HALF_EVEN, ROUND_HALF_UP)
+
+ROUNDING_OPTION_DICT = {'ROUND_DOWN':ROUND_DOWN,
+                        'ROUND_UP':ROUND_UP,
+                        'ROUND_CEILING':ROUND_CEILING,
+                        'ROUND_FLOOR':ROUND_FLOOR,
+                        'ROUND_HALF_DOWN':ROUND_HALF_DOWN,
+                        'ROUND_HALF_EVEN':ROUND_HALF_EVEN,
+                        'ROUND_HALF_UP':ROUND_HALF_UP}
+
+class RoundingTool(BaseTool):
+  """Rounding Tool"""
+  id = 'portal_roundings'
+  title = 'Rounding Tool'
+  meta_type = 'ERP5 Rounding Tool'
+  portal_type = 'Rounding Tool'
+
+  zope.interface.implements(IRoundingTool)
+
+  security = ClassSecurityInfo()
+
+  security.declarePublic('findRoundingModel')
+  def findRoundingModelValueList(self, document, property_id=None, context=None):
+    """
+    Return a list of matched rounding models for `document` which is ordered
+    by increasing distance from `context`.
+    """
+    portal = self.getPortalObject()
+    parent_uid_list = [portal.portal_roundings.getUid()]
+    kw = {}
+
+    if context is not None:
+      current_document = context
+      while True:
+        if (current_document is None or current_document is portal or
+            not current_document.getUid() or
+            current_document.getUid() in parent_uid_list):
+          break
+        else:
+          parent_uid_list.append(current_document.getUid())
+          current_document = current_document.aq_parent
+
+      def sortMethod(document_a, document_b):
+        def score(document):
+          context_path = context.getPhysicalPath()
+          result = len(context_path)
+          for a, b in zip(context_path,
+                          document.getPhysicalPath()):
+            if a==b:
+              result -= 1
+            else:
+              break
+          return result
+        return cmp(score(document_a), score(document_b))
+      kw['sort_method'] = sortMethod
+
+    result = portal.portal_domains.searchPredicateList(
+      context=document,
+      parent_uid=parent_uid_list,
+      portal_type='Rounding Model',
+      validation_state='validated',
+      **kw)
+    return result
+
+  security.declarePublic('getRoundingProxy')
+  def getRoundingProxy(self, document, context=None):
+    """
+    Return a rounding proxy object which getter methods returns rounded
+    value by following matched rounding model definition.
+    """
+    target_object = document
+    for rounding_model in self.findRoundingModelValueList(document, context=context):
+      target_object = rounding_model.getRoundingProxy(target_object)
+    return target_object
+
+  security.declarePublic('getDecimalRoundingOptionList')
+  def getDecimalRoundingOptionItemList(self):
+    """
+    Return the possible decimal rounding option item list which is provided
+    by python standard decimal module.
+    """
+    return ROUNDING_OPTION_DICT.items()
diff --git a/product/ERP5/__init__.py b/product/ERP5/__init__.py
index 6598a07dedaf02b683f8bf86bc3f140c598f8d65..507406da106da6ecee622224c746c4a4ef71057d 100644
--- a/product/ERP5/__init__.py
+++ b/product/ERP5/__init__.py
@@ -49,7 +49,7 @@ from Tool import CategoryTool, SimulationTool, RuleTool, IdTool, TemplateTool,\
                  TestTool, DomainTool, AlarmTool, OrderTool, DeliveryTool,\
                  TrashTool, ContributionTool, NotificationTool, PasswordTool,\
                  GadgetTool, ContributionRegistryTool, IntrospectionTool,\
-                 AcknowledgementTool, SolverTool, ConversionTool
+                 AcknowledgementTool, SolverTool, ConversionTool, RoundingTool
 import ERP5Site
 object_classes = ( ERP5Site.ERP5Site,
                  )
@@ -73,6 +73,7 @@ portal_tools = ( CategoryTool.CategoryTool,
                  AcknowledgementTool.AcknowledgementTool,
                  SolverTool.SolverTool,
                  ConversionTool.ConversionTool,
+                 RoundingTool.RoundingTool,
                 )
 content_classes = ()
 content_constructors = ()
@@ -107,4 +108,4 @@ def initialize( context ):
 
 # backward compatibility names
 XML = None
-UI = None
\ No newline at end of file
+UI = None
diff --git a/product/ERP5/interfaces/roundable.py b/product/ERP5/interfaces/roundable.py
new file mode 100644
index 0000000000000000000000000000000000000000..d75bdcec53570ecb7417922bffe1766a565cda2a
--- /dev/null
+++ b/product/ERP5/interfaces/roundable.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Copyright (c) 2009 Nexedi KK and Contributors. All Rights Reserved.
+#                    Yusei Tahara <yusei@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 advised 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 zope.interface import Interface
+
+class IRoundable(Interface):
+  """
+  Roundable interface
+  """
+
+  def asRoundingProxy(context=None):
+    """
+    Return a proxy object with getter methods which returns rounded value.
+    """
diff --git a/product/ERP5/interfaces/rounding_tool.py b/product/ERP5/interfaces/rounding_tool.py
index a8216d65526ca74bd3d2185013538216f8434b64..a13b11b6fdd53589e392105516fd26720d0cfa4a 100644
--- a/product/ERP5/interfaces/rounding_tool.py
+++ b/product/ERP5/interfaces/rounding_tool.py
@@ -34,9 +34,9 @@ class IRoundingTool(Interface):
   Rounding tool interface
   """
 
-  def findRoundingModel(document, property_id, context=None):
+  def findRoundingModelValueList(document, property_id=None, context=None):
     """
-    Find matched rounding model for context and property id.
+    Find matched rounding models for context and property id.
 
     Parameters:
 
@@ -44,18 +44,25 @@ class IRoundingTool(Interface):
       This is the object which contains value to be rounded.
 
     property_id
+      XXX I'm not quite sure if this is really necessary or not...
       This indicates which property value is rounded.
 
     context
-      This indicates where lookup starts from. If this is None, then rounding tool itself
-      is used.
+      This indicates where lookup starts from. If this is None, then rounding
+      tool itself is used.
 
     Example:
 
-      temporary_movement is generated by getAggregatedAmountList from a set of movements
-      and represents total price with tax. trade_model_line contains a rounding model to
-      be used here to round the total price with tax.
+      temporary_movement is generated by getAggregatedAmountList from a set of
+      movements and represents total price with tax. trade_model_line contains
+      a rounding model to be used here to round the total price with tax.
     
       portal_roundings.findRoundingModel(temporary_movement, 'total_price',
                                          context=trade_model_line)
     """
+
+  def getRoundingProxy(document, context=None):
+    """
+    Find matched rounding models from context and return proxy object for
+    `document`. The proxy object returns rounded value through getters.
+    """