RoundingModel.py 6.89 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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
##############################################################################
#
# 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):
Yusei Tahara's avatar
Yusei Tahara committed
56 57
    if not value:
      return value
58
    if self.getRoundingMethodId() is not None:
59
      rounding_method = getattr(self, self.getRoundingMethodId(), None)
60 61 62 63 64 65 66 67 68
      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.'
Yusei Tahara's avatar
Yusei Tahara committed
69
      def rounding_method(value, decimal_exponent, precision):
Yusei Tahara's avatar
Yusei Tahara committed
70 71 72
        if precision is None:
          precision = 0
        if decimal_exponent is None:
Yusei Tahara's avatar
Yusei Tahara committed
73 74 75 76 77 78 79 80 81 82 83 84 85
          if precision > 0:
            decimal_exponent = '1.' + '0' * precision
          else:
            decimal_exponent = '1'
        result = float(
          Decimal(str(value)).quantize(Decimal(decimal_exponent),
                                       rounding=decimal_rounding_option))
        if precision < 0:
          # FIXME!!!!!
          result = round(result, precision)
        return result

    return rounding_method(value, self.getDecimalExponent(), self.getPrecision())
86 87 88 89 90 91 92 93 94

  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 = []
95
    rounded_property_special_property_name_list = []
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125

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

    class _RoundingProxy(RoundingProxy):

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

Yusei Tahara's avatar
Yusei Tahara committed
137 138 139 140 141 142 143 144 145 146 147 148
      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

149 150 151 152 153 154 155
      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

156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
      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.
  """