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.

200 lines
7.6 KiB

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