RoundingModel.py 7.04 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
##############################################################################
#
# 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
30
from Products.ERP5Type.Core.Predicate import Predicate
31
from Products.ERP5Type.Utils import UpperCase
Yoshinori Okuji's avatar
Yoshinori Okuji committed
32 33
from decimal import Decimal
from Products.ERP5.Tool.RoundingTool import ROUNDING_OPTION_DICT
34
import ExtensionClass
Yoshinori Okuji's avatar
Yoshinori Okuji committed
35
from math import log
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53

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,
Yusei Tahara's avatar
Yusei Tahara committed
54
                     PropertySheet.Reference,
55 56 57 58 59
                     PropertySheet.RoundingModel,
                     )

  security.declareProtected(Permissions.AccessContentsInformation, 'roundValue')
  def roundValue(self, value):
Yusei Tahara's avatar
Yusei Tahara committed
60 61
    if not value:
      return value
Yoshinori Okuji's avatar
Yoshinori Okuji committed
62 63 64 65

    rounding_method_id = self.getRoundingMethodId()
    if rounding_method_id is not None:
      rounding_method = getattr(self, rounding_method_id, None)
66
      if rounding_method is None:
Yoshinori Okuji's avatar
Yoshinori Okuji committed
67 68
        raise ValueError('Rounding method (%s) was not found.' \
                % (rounding_method_id,))
69 70
    else:
      decimal_rounding_option = self.getDecimalRoundingOption()
Yoshinori Okuji's avatar
Yoshinori Okuji committed
71 72 73 74
      if decimal_rounding_option not in ROUNDING_OPTION_DICT:
        raise ValueError('Decimal rounding option must be selected.')

      def rounding_method(value, precision):
Yusei Tahara's avatar
Yusei Tahara committed
75
        if precision is None:
Yoshinori Okuji's avatar
Yoshinori Okuji committed
76 77 78
          precision = 1

        scale = int(log(precision, 10))
79
        if scale > 0 or (scale==0 and precision>=1):
Yoshinori Okuji's avatar
Yoshinori Okuji committed
80 81 82 83 84 85 86 87 88 89 90
          value = Decimal(str(value))
          scale = Decimal(str(int(precision))).quantize(value)
          precision = Decimal('1')
          value /= scale
          value = value.quantize(precision, rounding=decimal_rounding_option)
          value *= scale
          result = float(value.quantize(precision))
        else:
          result = float(
            Decimal(str(value)).quantize(Decimal(str(precision)),
                                         rounding=decimal_rounding_option))
Yusei Tahara's avatar
Yusei Tahara committed
91 92
        return result

Yoshinori Okuji's avatar
Yoshinori Okuji committed
93
    return rounding_method(value, self.getPrecision())
94 95 96 97 98 99 100 101 102

  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 = []
103
    rounded_property_special_property_name_list = []
104 105 106 107 108

    if isinstance(document, RoundingProxy):
      temp_document = document._getOriginalDocument()
      original_document = document
    else:
Yoshinori Okuji's avatar
Yoshinori Okuji committed
109
      temp_document = document.asContext()
110 111 112 113
      original_document = temp_document

    for property_id in rounding_model.getRoundedPropertyIdList():
      getter_name = 'get%s' % UpperCase(property_id)
Yoshinori Okuji's avatar
Yoshinori Okuji committed
114
      getter = getattr(temp_document, getter_name, None)
115
      setter_name = 'set%s' % UpperCase(property_id)
Yoshinori Okuji's avatar
Yoshinori Okuji committed
116
      setter = getattr(temp_document, setter_name, None)
117 118 119 120 121 122 123 124

      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)
125 126
        if getter is not None and setter is None:
          rounded_property_special_property_name_list.append(property_id)
127 128 129 130 131

    class _RoundingProxy(RoundingProxy):

      def _getOriginalDocument(self):
        if isinstance(original_document, RoundingProxy):
Yusei Tahara's avatar
Yusei Tahara committed
132
          return original_document._getOriginalDocument()
133 134 135
        else:
          return original_document

Yusei Tahara's avatar
Yusei Tahara committed
136 137 138 139 140 141 142 143 144 145 146 147
      def getRoundingModelPrecision(self, property_id):
        """
        Return precision value of rounding model. This is useful for
        float field.
        """
        if property_id in rounding_model.getRoundedPropertyIdList():
          return rounding_model.getPrecision()
        elif isinstance(original_document, RoundingProxy):
          return original_document.getRoundingModelPrecision(property_id)
        else:
          return None

148 149 150 151 152 153 154
      def getProperty(self, key, *args, **kw):
        result = original_document.getProperty(key, *args, **kw)
        if key in rounded_property_special_property_name_list:
          return rounding_model.roundValue(result)
        else:
          return result

155 156 157 158 159
      # XXX not sure why but getObject may return original_document
      # unless it is overridden here.
      def getObject(self, *args, **kw):
        return self

160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
      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()

175 176 177
# Use the Extension Class only because it is necessary to allow an instance
# of this class to be wrapped in an acquisition wrapper.
class RoundingProxy(ExtensionClass.Base):
178 179 180 181
  """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.
  """