You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

220 lines
7.4 KiB

  1. # -*- coding: utf-8 -*-
  2. # © 2011 Raphaël Valyi, Renato Lima, Guewen Baconnier, Sodexis
  3. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
  4. import time
  5. from functools import wraps
  6. from odoo import api, models, fields, _
  7. from odoo.exceptions import UserError, ValidationError
  8. from odoo.tools.safe_eval import safe_eval
  9. def implemented_by_base_exception(func):
  10. """Call a prefixed function based on 'namespace'."""
  11. @wraps(func)
  12. def wrapper(cls, *args, **kwargs):
  13. fun_name = func.__name__
  14. fun = '_%s%s' % (cls.rule_group, fun_name)
  15. if not hasattr(cls, fun):
  16. fun = '_default%s' % (fun_name)
  17. return getattr(cls, fun)(*args, **kwargs)
  18. return wrapper
  19. class ExceptionRule(models.Model):
  20. _name = 'exception.rule'
  21. _description = "Exception Rules"
  22. _order = 'active desc, sequence asc'
  23. name = fields.Char('Exception Name', required=True, translate=True)
  24. description = fields.Text('Description', translate=True)
  25. sequence = fields.Integer(
  26. string='Sequence',
  27. help="Gives the sequence order when applying the test")
  28. rule_group = fields.Selection(
  29. selection=[],
  30. help="Rule group is used to group the rules that must validated "
  31. "at same time for a target object. Ex: "
  32. "validate sale.order.line rules with sale order rules.",
  33. required=True)
  34. model = fields.Selection(
  35. selection=[],
  36. string='Apply on', required=True)
  37. active = fields.Boolean('Active')
  38. code = fields.Text(
  39. 'Python Code',
  40. help="Python code executed to check if the exception apply or "
  41. "not. The code must apply block = True to apply the "
  42. "exception.",
  43. default="""
  44. # Python code. Use failed = True to block the base.exception.
  45. # You can use the following variables :
  46. # - self: ORM model of the record which is checked
  47. # - "rule_group" or "rule_group_"line:
  48. # browse_record of the base.exception or
  49. # base.exception line (ex rule_group = sale for sale order)
  50. # - object: same as order or line, browse_record of the base.exception or
  51. # base.exception line
  52. # - pool: ORM model pool (i.e. self.pool, deprecated in new api)
  53. # - obj: same as object
  54. # - env: ORM model pool (i.e. self.env)
  55. # - time: Python time module
  56. # - cr: database cursor
  57. # - uid: current user id
  58. # - context: current context
  59. """)
  60. class BaseException(models.AbstractModel):
  61. _name = 'base.exception'
  62. _order = 'main_exception_id asc'
  63. main_exception_id = fields.Many2one(
  64. 'exception.rule',
  65. compute='_compute_main_error',
  66. string='Main Exception',
  67. store=True)
  68. rule_group = fields.Selection(
  69. [],
  70. readonly=True,
  71. )
  72. exception_ids = fields.Many2many(
  73. 'exception.rule',
  74. string='Exceptions')
  75. ignore_exception = fields.Boolean('Ignore Exceptions', copy=False)
  76. @api.depends('exception_ids', 'ignore_exception')
  77. def _compute_main_error(self):
  78. for obj in self:
  79. if not obj.ignore_exception and obj.exception_ids:
  80. obj.main_exception_id = obj.exception_ids[0]
  81. else:
  82. obj.main_exception_id = False
  83. @api.multi
  84. def _popup_exceptions(self):
  85. action = self._get_popup_action()
  86. action = action.read()[0]
  87. action.update({
  88. 'context': {
  89. 'active_model': self._name,
  90. 'active_id': self.ids[0],
  91. 'active_ids': self.ids
  92. }
  93. })
  94. return action
  95. @api.model
  96. def _get_popup_action(self):
  97. action = self.env.ref('base_exception.action_exception_rule_confirm')
  98. return action
  99. @api.multi
  100. def _check_exception(self):
  101. """
  102. This method must be used in a constraint that must be created in the
  103. object that inherits for base.exception.
  104. for sale :
  105. @api.constrains('ignore_exception',)
  106. def sale_check_exception(self):
  107. ...
  108. ...
  109. self._check_exception
  110. """
  111. exception_ids = self.detect_exceptions()
  112. if exception_ids:
  113. exceptions = self.env['exception.rule'].browse(exception_ids)
  114. raise ValidationError('\n'.join(exceptions.mapped('name')))
  115. @api.multi
  116. def test_exceptions(self):
  117. """
  118. Condition method for the workflow from draft to confirm
  119. """
  120. if self.detect_exceptions():
  121. return False
  122. return True
  123. @api.multi
  124. def detect_exceptions(self):
  125. """returns the list of exception_ids for all the considered base.exceptions
  126. """
  127. if not self:
  128. return []
  129. exception_obj = self.env['exception.rule']
  130. all_exceptions = exception_obj.sudo().search(
  131. [('rule_group', '=', self[0].rule_group)])
  132. model_exceptions = all_exceptions.filtered(
  133. lambda ex: ex.model == self._name)
  134. sub_exceptions = all_exceptions.filtered(
  135. lambda ex: ex.model != self._name)
  136. all_exception_ids = []
  137. for obj in self:
  138. if obj.ignore_exception:
  139. continue
  140. exception_ids = obj._detect_exceptions(
  141. model_exceptions, sub_exceptions)
  142. obj.exception_ids = [(6, 0, exception_ids)]
  143. all_exception_ids += exception_ids
  144. return all_exception_ids
  145. @api.model
  146. def _exception_rule_eval_context(self, obj_name, rec):
  147. return {obj_name: rec,
  148. 'self': self.pool.get(rec._name),
  149. 'object': rec,
  150. 'obj': rec,
  151. 'pool': self.pool,
  152. 'env': self.env,
  153. 'cr': self.env.cr,
  154. 'uid': self.env.uid,
  155. 'user': self.env.user,
  156. 'time': time,
  157. # copy context to prevent side-effects of eval
  158. 'context': self.env.context.copy()}
  159. @api.model
  160. def _rule_eval(self, rule, obj_name, rec):
  161. expr = rule.code
  162. space = self._exception_rule_eval_context(obj_name, rec)
  163. try:
  164. safe_eval(expr,
  165. space,
  166. mode='exec',
  167. nocopy=True) # nocopy allows to return 'result'
  168. except Exception, e:
  169. raise UserError(
  170. _('Error when evaluating the exception.rule '
  171. 'rule:\n %s \n(%s)') % (rule.name, e))
  172. return space.get('failed', False)
  173. @api.multi
  174. def _detect_exceptions(self, model_exceptions,
  175. sub_exceptions):
  176. self.ensure_one()
  177. exception_ids = []
  178. for rule in model_exceptions:
  179. if self._rule_eval(rule, self.rule_group, self):
  180. exception_ids.append(rule.id)
  181. if sub_exceptions:
  182. for obj_line in self._get_lines():
  183. for rule in sub_exceptions:
  184. if rule.id in exception_ids:
  185. # we do not matter if the exception as already been
  186. # found for an line of this object
  187. # (ex sale order line if obj is sale order)
  188. continue
  189. group_line = self.rule_group + '_line'
  190. if self._rule_eval(rule, group_line, obj_line):
  191. exception_ids.append(rule.id)
  192. return exception_ids
  193. @implemented_by_base_exception
  194. def _get_lines(self):
  195. pass
  196. def _default_get_lines(self):
  197. return []