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.

237 lines
8.9 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
  5. from ast import literal_eval
  6. class TierValidation(models.AbstractModel):
  7. _name = "tier.validation"
  8. _description = "Tier Validation (abstract)"
  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. review_ids_dropdown = fields.One2many(
  21. related='review_ids',
  22. help="Field needed to display the dropdown menu correctly"
  23. )
  24. validated = fields.Boolean(
  25. compute="_compute_validated_rejected",
  26. search="_search_validated",
  27. )
  28. need_validation = fields.Boolean(compute="_compute_need_validation")
  29. rejected = fields.Boolean(compute="_compute_validated_rejected")
  30. reviewer_ids = fields.Many2many(
  31. string="Reviewers", comodel_name="res.users",
  32. compute="_compute_reviewer_ids",
  33. search="_search_reviewer_ids",
  34. )
  35. can_review = fields.Boolean(compute="_compute_can_review")
  36. @api.multi
  37. def _compute_can_review(self):
  38. for rec in self:
  39. rec.can_review = self.env.user in rec.reviewer_ids
  40. @api.multi
  41. @api.depends('review_ids')
  42. def _compute_reviewer_ids(self):
  43. for rec in self:
  44. rec.reviewer_ids = rec.review_ids.filtered(
  45. lambda r: r.status == 'pending').mapped('reviewer_ids')
  46. @api.model
  47. def _search_validated(self, operator, value):
  48. assert operator in ('=', '!='), 'Invalid domain operator'
  49. assert value in (True, False), 'Invalid domain value'
  50. pos = self.search([
  51. (self._state_field, 'in', self._state_from)]).filtered(
  52. lambda r: r.review_ids and r.validated == value)
  53. return [('id', 'in', pos.ids)]
  54. @api.model
  55. def _search_reviewer_ids(self, operator, value):
  56. reviews = self.env['tier.review'].search([
  57. ('model', '=', self._name),
  58. ('reviewer_ids', operator, value),
  59. ('status', '=', 'pending')])
  60. return [('id', 'in', list(set(reviews.mapped('res_id'))))]
  61. @api.multi
  62. def _compute_validated_rejected(self):
  63. for rec in self:
  64. rec.validated = self._calc_reviews_validated(rec.review_ids)
  65. rec.rejected = self._calc_reviews_rejected(rec.review_ids)
  66. @api.model
  67. def _calc_reviews_validated(self, reviews):
  68. """Override for different validation policy."""
  69. if not reviews:
  70. return False
  71. return not any([s != 'approved' for s in reviews.mapped('status')])
  72. @api.model
  73. def _calc_reviews_rejected(self, reviews):
  74. """Override for different rejection policy."""
  75. return any([s == 'rejected' for s in reviews.mapped('status')])
  76. @api.multi
  77. def _compute_need_validation(self):
  78. for rec in self:
  79. tiers = self.env[
  80. 'tier.definition'].search([('model', '=', self._name)])
  81. valid_tiers = any([self.evaluate_tier(tier) for tier in tiers])
  82. rec.need_validation = not rec.review_ids and valid_tiers and \
  83. getattr(rec, self._state_field) in self._state_from
  84. @api.multi
  85. def evaluate_tier(self, tier):
  86. domain = []
  87. if tier.definition_domain:
  88. domain = literal_eval(tier.definition_domain)
  89. return self.search([('id', '=', self.id)] + domain)
  90. @api.model
  91. def _get_under_validation_exceptions(self):
  92. """Extend for more field exceptions."""
  93. return ['message_follower_ids']
  94. @api.multi
  95. def _check_allow_write_under_validation(self, vals):
  96. """Allow to add exceptions for fields that are allowed to be written
  97. even when the record is under validation."""
  98. exceptions = self._get_under_validation_exceptions()
  99. for val in vals:
  100. if val not in exceptions:
  101. return False
  102. return True
  103. @api.multi
  104. def write(self, vals):
  105. for rec in self:
  106. if (getattr(rec, self._state_field) in self._state_from and
  107. vals.get(self._state_field) in self._state_to):
  108. if rec.need_validation:
  109. # try to validate operation
  110. reviews = rec.request_validation()
  111. rec._validate_tier(reviews)
  112. if not self._calc_reviews_validated(reviews):
  113. raise ValidationError(_(
  114. "This action needs to be validated for at least "
  115. "one record. \nPlease request a validation."))
  116. if rec.review_ids and not rec.validated:
  117. raise ValidationError(_(
  118. "A validation process is still open for at least "
  119. "one record."))
  120. if (rec.review_ids and getattr(rec, self._state_field) in
  121. self._state_from and not vals.get(self._state_field) in
  122. (self._state_to + [self._cancel_state]) and not
  123. self._check_allow_write_under_validation(vals)):
  124. raise ValidationError(_("The operation is under validation."))
  125. if vals.get(self._state_field) in self._state_from:
  126. self.mapped('review_ids').unlink()
  127. return super(TierValidation, self).write(vals)
  128. def _validate_tier(self, tiers=False):
  129. self.ensure_one()
  130. tier_reviews = tiers or self.review_ids
  131. user_reviews = tier_reviews.filtered(
  132. lambda r: r.status in ('pending', 'rejected') and
  133. (self.env.user in r.reviewer_ids))
  134. user_reviews.write({
  135. 'status': 'approved',
  136. 'done_by': self.env.user.id,
  137. 'reviewed_date': fields.Datetime.now(),
  138. })
  139. for review in user_reviews:
  140. rec = self.env[review.model].browse(review.res_id)
  141. rec._notify_accepted_reviews()
  142. def _notify_accepted_reviews(self):
  143. if hasattr(self, 'message_post'):
  144. # Notify state change
  145. getattr(self, 'message_post')(
  146. subtype='mt_comment',
  147. body=self._notify_accepted_reviews_body()
  148. )
  149. def _notify_accepted_reviews_body(self):
  150. return _('A review was accepted')
  151. @api.multi
  152. def validate_tier(self):
  153. for rec in self:
  154. rec._validate_tier()
  155. @api.multi
  156. def reject_tier(self):
  157. for rec in self:
  158. user_reviews = rec.review_ids.filtered(
  159. lambda r: r.status in ('pending', 'approved') and
  160. (r.reviewer_id == self.env.user or
  161. r.reviewer_group_id in self.env.user.groups_id))
  162. user_reviews.write({
  163. 'status': 'rejected',
  164. 'done_by': self.env.user.id,
  165. 'reviewed_date': fields.Datetime.now(),
  166. })
  167. rec._notify_rejected_review()
  168. def _notify_rejected_review_body(self):
  169. return _('A review was rejected by %s.') % (self.env.user.name)
  170. def _notify_rejected_review(self):
  171. if hasattr(self, 'message_post'):
  172. # Notify state change
  173. getattr(self, 'message_post')(
  174. subtype='mt_comment',
  175. body=self._notify_rejected_review_body()
  176. )
  177. @api.multi
  178. def request_validation(self):
  179. td_obj = self.env['tier.definition']
  180. tr_obj = created_trs = self.env['tier.review']
  181. for rec in self:
  182. if getattr(rec, self._state_field) in self._state_from:
  183. if rec.need_validation:
  184. tier_definitions = td_obj.search([
  185. ('model', '=', self._name)], order="sequence desc")
  186. sequence = 0
  187. for td in tier_definitions:
  188. if self.evaluate_tier(td):
  189. sequence += 1
  190. created_trs += tr_obj.create({
  191. 'model': self._name,
  192. 'res_id': rec.id,
  193. 'definition_id': td.id,
  194. 'sequence': sequence,
  195. 'requested_by': self.env.uid,
  196. })
  197. self._update_counter()
  198. return created_trs
  199. @api.multi
  200. def restart_validation(self):
  201. for rec in self:
  202. if getattr(rec, self._state_field) in self._state_from:
  203. rec.mapped('review_ids').unlink()
  204. self._update_counter()
  205. def _update_counter(self):
  206. notifications = []
  207. channel = 'base.tier.validation'
  208. notifications.append([channel, {}])
  209. self.env['bus.bus'].sendmany(notifications)