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.

188 lines
7.3 KiB

  1. # -*- coding: utf-8 -*-
  2. # Copyright 2017 Eficent Business and IT Consulting Services S.L.
  3. # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
  4. from odoo import api, fields, models, _
  5. from odoo.exceptions import ValidationError, UserError
  6. from odoo.tools.safe_eval import safe_eval
  7. class TierValidation(models.AbstractModel):
  8. _name = "tier.validation"
  9. _state_field = 'state'
  10. _state_from = ['draft']
  11. _state_to = ['confirmed']
  12. _cancel_state = 'cancel'
  13. # TODO: step by step validation?
  14. review_ids = fields.One2many(
  15. comodel_name='tier.review', inverse_name='res_id',
  16. string='Validations',
  17. domain=lambda self: [('model', '=', self._name)],
  18. auto_join=True,
  19. )
  20. validated = fields.Boolean(
  21. compute="_compute_validated_rejected",
  22. search="_search_validated",
  23. )
  24. need_validation = fields.Boolean(compute="_compute_need_validation")
  25. rejected = fields.Boolean(compute="_compute_validated_rejected")
  26. reviewer_ids = fields.Many2many(
  27. string="Reviewers", comodel_name="res.users",
  28. compute="_compute_reviewer_ids",
  29. search="_search_reviewer_ids",
  30. )
  31. @api.multi
  32. @api.depends('review_ids')
  33. def _compute_reviewer_ids(self):
  34. for rec in self:
  35. rec.reviewer_ids = rec.review_ids.filtered(
  36. lambda r: r.status == 'pending').mapped('reviewer_ids')
  37. @api.model
  38. def _search_validated(self, operator, value):
  39. assert operator in ('=', '!='), 'Invalid domain operator'
  40. assert value in (True, False), 'Invalid domain value'
  41. pos = self.search([
  42. (self._state_field, 'in', self._state_from)]).filtered(
  43. lambda r: r.review_ids and r.validated == value)
  44. return [('id', 'in', pos.ids)]
  45. @api.model
  46. def _search_reviewer_ids(self, operator, value):
  47. reviews = self.env['tier.review'].search([
  48. ('model', '=', self._name),
  49. ('reviewer_ids', operator, value),
  50. ('status', '=', 'pending')])
  51. return [('id', 'in', list(set(reviews.mapped('res_id'))))]
  52. @api.multi
  53. def _compute_validated_rejected(self):
  54. for rec in self:
  55. rec.validated = self._calc_reviews_validated(rec.review_ids)
  56. rec.rejected = self._calc_reviews_rejected(rec.review_ids)
  57. @api.model
  58. def _calc_reviews_validated(self, reviews):
  59. """Override for different validation policy."""
  60. if not reviews:
  61. return False
  62. return not any([s != 'approved' for s in reviews.mapped('status')])
  63. @api.model
  64. def _calc_reviews_rejected(self, reviews):
  65. """Override for different rejection policy."""
  66. return any([s == 'rejected' for s in reviews.mapped('status')])
  67. @api.multi
  68. def _compute_need_validation(self):
  69. for rec in self:
  70. tiers = self.env[
  71. 'tier.definition'].search([('model', '=', self._name)])
  72. valid_tiers = any([rec.evaluate_tier(tier) for tier in tiers])
  73. rec.need_validation = not rec.review_ids and valid_tiers and \
  74. getattr(rec, self._state_field) in self._state_from
  75. @api.multi
  76. def evaluate_tier(self, tier):
  77. try:
  78. res = safe_eval(tier.python_code, globals_dict={'rec': self})
  79. except Exception, error:
  80. raise UserError(_(
  81. "Error evaluating tier validation conditions.\n %s") % error)
  82. return res
  83. @api.model
  84. def _get_under_validation_exceptions(self):
  85. """Extend for more field exceptions."""
  86. return ['message_follower_ids']
  87. @api.multi
  88. def _check_allow_write_under_validation(self, vals):
  89. """Allow to add exceptions for fields that are allowed to be written
  90. even when the record is under validation."""
  91. exceptions = self._get_under_validation_exceptions()
  92. for val in vals:
  93. if val not in exceptions:
  94. return False
  95. return True
  96. @api.multi
  97. def write(self, vals):
  98. for rec in self:
  99. if (getattr(rec, self._state_field) in self._state_from and
  100. vals.get(self._state_field) in self._state_to):
  101. if rec.need_validation:
  102. # try to validate operation
  103. reviews = rec.request_validation()
  104. rec._validate_tier(reviews)
  105. if not self._calc_reviews_validated(reviews):
  106. raise ValidationError(_(
  107. "This action needs to be validated for at least "
  108. "one record. \nPlease request a validation."))
  109. if rec.review_ids and not rec.validated:
  110. raise ValidationError(_(
  111. "A validation process is still open for at least "
  112. "one record."))
  113. if (rec.review_ids and getattr(rec, self._state_field) in
  114. self._state_from and not vals.get(self._state_field) in
  115. (self._state_to + [self._cancel_state]) and not
  116. self._check_allow_write_under_validation(vals)):
  117. raise ValidationError(_("The operation is under validation."))
  118. if vals.get(self._state_field) in self._state_from:
  119. self.mapped('review_ids').unlink()
  120. return super(TierValidation, self).write(vals)
  121. def _validate_tier(self, tiers=False):
  122. self.ensure_one()
  123. tier_reviews = tiers or self.review_ids
  124. user_reviews = tier_reviews.filtered(
  125. lambda r: r.status in ('pending', 'rejected') and
  126. (r.reviewer_id == self.env.user or
  127. r.reviewer_group_id in self.env.user.groups_id))
  128. user_reviews.write({'status': 'approved'})
  129. @api.multi
  130. def validate_tier(self):
  131. for rec in self:
  132. rec._validate_tier()
  133. @api.multi
  134. def reject_tier(self):
  135. for rec in self:
  136. user_reviews = rec.review_ids.filtered(
  137. lambda r: r.status in ('pending', 'approved') and
  138. (r.reviewer_id == self.env.user or
  139. r.reviewer_group_id in self.env.user.groups_id))
  140. user_reviews.write({'status': 'rejected'})
  141. @api.multi
  142. def request_validation(self):
  143. td_obj = self.env['tier.definition']
  144. tr_obj = created_trs = self.env['tier.review']
  145. for rec in self:
  146. if getattr(rec, self._state_field) in self._state_from:
  147. if rec.need_validation:
  148. tier_definitions = td_obj.search([
  149. ('model', '=', self._name)], order="sequence desc")
  150. sequence = 0
  151. for td in tier_definitions:
  152. if self.evaluate_tier(td):
  153. sequence += 1
  154. created_trs += tr_obj.create({
  155. 'model': self._name,
  156. 'res_id': rec.id,
  157. 'definition_id': td.id,
  158. 'sequence': sequence,
  159. })
  160. # TODO: notify? post some msg in chatter?
  161. return created_trs
  162. @api.multi
  163. def restart_validation(self):
  164. for rec in self:
  165. if getattr(rec, self._state_field) in self._state_from:
  166. rec.mapped('review_ids').unlink()