odoo-addon-base-tier-validation 16.0.1.0.4.29__py3-none-any.whl → 16.0.1.5.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. odoo/addons/base_tier_validation/README.rst +11 -6
  2. odoo/addons/base_tier_validation/__manifest__.py +1 -1
  3. odoo/addons/base_tier_validation/i18n/base_tier_validation.pot +104 -6
  4. odoo/addons/base_tier_validation/i18n/es.po +147 -73
  5. odoo/addons/base_tier_validation/i18n/es_MX.po +104 -6
  6. odoo/addons/base_tier_validation/i18n/fr.po +186 -78
  7. odoo/addons/base_tier_validation/i18n/it.po +159 -48
  8. odoo/addons/base_tier_validation/i18n/nl_NL.po +159 -37
  9. odoo/addons/base_tier_validation/i18n/sv.po +140 -54
  10. odoo/addons/base_tier_validation/i18n/tr.po +107 -6
  11. odoo/addons/base_tier_validation/i18n/zh_CN.po +107 -6
  12. odoo/addons/base_tier_validation/models/res_config_settings.py +3 -3
  13. odoo/addons/base_tier_validation/models/res_users.py +5 -0
  14. odoo/addons/base_tier_validation/models/tier_definition.py +18 -0
  15. odoo/addons/base_tier_validation/models/tier_review.py +18 -3
  16. odoo/addons/base_tier_validation/models/tier_validation.py +183 -63
  17. odoo/addons/base_tier_validation/readme/CONTRIBUTORS.rst +2 -0
  18. odoo/addons/base_tier_validation/static/description/index.html +68 -65
  19. odoo/addons/base_tier_validation/static/src/xml/tier_review_template.xml +41 -17
  20. odoo/addons/base_tier_validation/templates/tier_validation_templates.xml +7 -6
  21. odoo/addons/base_tier_validation/tests/common.py +19 -6
  22. odoo/addons/base_tier_validation/tests/test_tier_validation.py +415 -0
  23. odoo/addons/base_tier_validation/tests/tier_validation_tester.py +13 -2
  24. odoo/addons/base_tier_validation/views/res_config_settings_views.xml +6 -6
  25. odoo/addons/base_tier_validation/views/tier_definition_view.xml +14 -1
  26. {odoo_addon_base_tier_validation-16.0.1.0.4.29.dist-info → odoo_addon_base_tier_validation-16.0.1.5.1.dist-info}/METADATA +13 -8
  27. {odoo_addon_base_tier_validation-16.0.1.0.4.29.dist-info → odoo_addon_base_tier_validation-16.0.1.5.1.dist-info}/RECORD +29 -29
  28. {odoo_addon_base_tier_validation-16.0.1.0.4.29.dist-info → odoo_addon_base_tier_validation-16.0.1.5.1.dist-info}/WHEEL +1 -1
  29. {odoo_addon_base_tier_validation-16.0.1.0.4.29.dist-info → odoo_addon_base_tier_validation-16.0.1.5.1.dist-info}/top_level.txt +0 -0
@@ -32,15 +32,28 @@ class TierValidation(models.AbstractModel):
32
32
  auto_join=True,
33
33
  )
34
34
  to_validate_message = fields.Html(compute="_compute_validated_rejected")
35
+ # TODO: Delete in v17 in favor of validation_status field
35
36
  validated = fields.Boolean(
36
37
  compute="_compute_validated_rejected", search="_search_validated"
37
38
  )
38
39
  validated_message = fields.Html(compute="_compute_validated_rejected")
39
40
  need_validation = fields.Boolean(compute="_compute_need_validation")
41
+ # TODO: Delete in v17 in favor of validation_status field
40
42
  rejected = fields.Boolean(
41
43
  compute="_compute_validated_rejected", search="_search_rejected"
42
44
  )
43
45
  rejected_message = fields.Html(compute="_compute_validated_rejected")
46
+ # Informative field (used in purchase_tier_validation), will be reliable as of v17
47
+ validation_status = fields.Selection(
48
+ selection=[
49
+ ("no", "Without validation"),
50
+ ("pending", "Pending"),
51
+ ("rejected", "Rejected"),
52
+ ("validated", "Validated"),
53
+ ],
54
+ default="no",
55
+ compute="_compute_validation_status",
56
+ )
44
57
  reviewer_ids = fields.Many2many(
45
58
  string="Reviewers",
46
59
  comodel_name="res.users",
@@ -104,18 +117,18 @@ class TierValidation(models.AbstractModel):
104
117
  def _search_validated(self, operator, value):
105
118
  assert operator in ("=", "!="), "Invalid domain operator"
106
119
  assert value in (True, False), "Invalid domain value"
107
- pos = self.search([(self._state_field, "in", self._state_from)]).filtered(
108
- lambda r: r.review_ids and r.validated == value
109
- )
120
+ pos = self.search(
121
+ [(self._state_field, "in", self._state_from), ("review_ids", "!=", False)]
122
+ ).filtered(lambda r: r.validated == value)
110
123
  return [("id", "in", pos.ids)]
111
124
 
112
125
  @api.model
113
126
  def _search_rejected(self, operator, value):
114
127
  assert operator in ("=", "!="), "Invalid domain operator"
115
128
  assert value in (True, False), "Invalid domain value"
116
- pos = self.search([(self._state_field, "in", self._state_from)]).filtered(
117
- lambda r: r.review_ids and r.rejected == value
118
- )
129
+ pos = self.search(
130
+ [(self._state_field, "in", self._state_from), ("review_ids", "!=", False)]
131
+ ).filtered(lambda r: r.rejected == value)
119
132
  return [("id", "in", pos.ids)]
120
133
 
121
134
  @api.model
@@ -166,6 +179,21 @@ class TierValidation(models.AbstractModel):
166
179
  rec.rejected_message = rec._get_rejected_message()
167
180
  rec.to_validate_message = rec._get_to_validate_message()
168
181
 
182
+ def _compute_validation_status(self):
183
+ for item in self:
184
+ if item.validated and not item.rejected:
185
+ item.validation_status = "validated"
186
+ elif not item.validated and item.rejected:
187
+ item.validation_status = "rejected"
188
+ elif (
189
+ not item.validated
190
+ and not item.rejected
191
+ and any(item.review_ids.filtered(lambda x: x.status == "pending"))
192
+ ):
193
+ item.validation_status = "pending"
194
+ else:
195
+ item.validation_status = "no"
196
+
169
197
  def _compute_next_review(self):
170
198
  for rec in self:
171
199
  review = rec.review_ids.sorted("sequence").filtered(
@@ -190,12 +218,15 @@ class TierValidation(models.AbstractModel):
190
218
  if isinstance(rec.id, models.NewId):
191
219
  rec.need_validation = False
192
220
  continue
193
- tiers = self.env["tier.definition"].search([("model", "=", self._name)])
221
+ tiers = self.env["tier.definition"].search(
222
+ [
223
+ ("model", "=", self._name),
224
+ ("company_id", "in", [False] + self.env.company.ids),
225
+ ]
226
+ )
194
227
  valid_tiers = any([rec.evaluate_tier(tier) for tier in tiers])
195
228
  rec.need_validation = (
196
- not rec.review_ids
197
- and valid_tiers
198
- and getattr(rec, self._state_field) in self._state_from
229
+ not rec.review_ids and valid_tiers and rec._check_state_from_condition()
199
230
  )
200
231
 
201
232
  def evaluate_tier(self, tier):
@@ -249,14 +280,35 @@ class TierValidation(models.AbstractModel):
249
280
  and not rec._context.get("skip_validation_check")
250
281
  ):
251
282
  raise ValidationError(_("The operation is under validation."))
252
- if vals.get(self._state_field) in self._state_from:
253
- self.mapped("review_ids").unlink()
283
+ if rec._allow_to_remove_reviews(vals):
284
+ rec.mapped("review_ids").unlink()
254
285
  return super(TierValidation, self).write(vals)
255
286
 
287
+ def _allow_to_remove_reviews(self, values):
288
+ """Method for deciding whether the elimination of revisions is necessary."""
289
+ self.ensure_one()
290
+ state_to = values.get(self._state_field)
291
+ if not state_to:
292
+ return False
293
+ state_from = self[self._state_field]
294
+ # If you change to _cancel_state
295
+ if state_to in (self._cancel_state):
296
+ return True
297
+ # If it is changed to _state_from and it was not in _state_from
298
+ if state_to in self._state_from and state_from not in self._state_from:
299
+ return True
300
+ return False
301
+
302
+ def _check_state_from_condition(self):
303
+ return self.env.context.get("skip_check_state_condition") or (
304
+ self._state_field in self._fields
305
+ and getattr(self, self._state_field) in self._state_from
306
+ )
307
+
256
308
  def _check_state_conditions(self, vals):
257
309
  self.ensure_one()
258
310
  return (
259
- getattr(self, self._state_field) in self._state_from
311
+ self._check_state_from_condition()
260
312
  and vals.get(self._state_field) in self._state_to
261
313
  )
262
314
 
@@ -273,9 +325,20 @@ class TierValidation(models.AbstractModel):
273
325
  "reviewed_date": fields.Datetime.now(),
274
326
  }
275
327
  )
276
- for review in user_reviews:
277
- rec = self.env[review.model].browse(review.res_id)
278
- rec._notify_accepted_reviews()
328
+ reviews_to_notify = user_reviews.filtered(
329
+ lambda r: r.definition_id.notify_on_accepted
330
+ )
331
+ if reviews_to_notify:
332
+ subscribe = "message_subscribe"
333
+ if hasattr(self, subscribe):
334
+ getattr(self, subscribe)(
335
+ partner_ids=reviews_to_notify.mapped("reviewer_ids")
336
+ .mapped("partner_id")
337
+ .ids
338
+ )
339
+ for review in reviews_to_notify:
340
+ rec = self.env[review.model].browse(review.res_id)
341
+ rec._notify_accepted_reviews()
279
342
 
280
343
  def _get_requested_notification_subtype(self):
281
344
  return "base_tier_validation.mt_tier_validation_requested"
@@ -328,9 +391,14 @@ class TierValidation(models.AbstractModel):
328
391
  def validate_tier(self):
329
392
  self.ensure_one()
330
393
  sequences = self._get_sequences_to_approve(self.env.user)
331
- reviews = self.review_ids.filtered(lambda l: l.sequence in sequences)
394
+ reviews = self.review_ids.filtered(
395
+ lambda l: l.sequence in sequences or l.approve_sequence_bypass
396
+ )
332
397
  if self.has_comment:
333
- return self._add_comment("validate", reviews)
398
+ user_reviews = reviews.filtered(
399
+ lambda r: r.status == "pending" and (self.env.user in r.reviewer_ids)
400
+ )
401
+ return self._add_comment("validate", user_reviews)
334
402
  self._validate_tier(reviews)
335
403
  self._update_counter({"review_deleted": True})
336
404
 
@@ -377,9 +445,21 @@ class TierValidation(models.AbstractModel):
377
445
  "reviewed_date": fields.Datetime.now(),
378
446
  }
379
447
  )
380
- for review in user_reviews:
381
- rec = self.env[review.model].browse(review.res_id)
382
- rec._notify_rejected_review()
448
+
449
+ reviews_to_notify = user_reviews.filtered(
450
+ lambda r: r.definition_id.notify_on_rejected
451
+ )
452
+ if reviews_to_notify:
453
+ subscribe = "message_subscribe"
454
+ if hasattr(self, subscribe):
455
+ getattr(self, subscribe)(
456
+ partner_ids=reviews_to_notify.mapped("reviewer_ids")
457
+ .mapped("partner_id")
458
+ .ids
459
+ )
460
+ for review in reviews_to_notify:
461
+ rec = self.env[review.model].browse(review.res_id)
462
+ rec._notify_rejected_review()
383
463
 
384
464
  def _notify_requested_review_body(self):
385
465
  return _("A review has been requested by %s.") % (self.env.user.name)
@@ -388,42 +468,49 @@ class TierValidation(models.AbstractModel):
388
468
  subscribe = "message_subscribe"
389
469
  post = "message_post"
390
470
  if hasattr(self, post) and hasattr(self, subscribe):
391
- for rec in self:
471
+ for rec in self.sudo():
392
472
  users_to_notify = tier_reviews.filtered(
393
473
  lambda r: r.definition_id.notify_on_create and r.res_id == rec.id
394
474
  ).mapped("reviewer_ids")
395
475
  # Subscribe reviewers and notify
396
- getattr(rec, subscribe)(
397
- partner_ids=users_to_notify.mapped("partner_id").ids
398
- )
399
- getattr(rec, post)(
400
- subtype_xmlid=self._get_requested_notification_subtype(),
401
- body=rec._notify_requested_review_body(),
402
- )
476
+ if len(users_to_notify) > 0:
477
+ getattr(rec, subscribe)(
478
+ partner_ids=users_to_notify.mapped("partner_id").ids
479
+ )
480
+ getattr(rec, post)(
481
+ subtype_xmlid=self._get_requested_notification_subtype(),
482
+ body=rec._notify_requested_review_body(),
483
+ )
484
+
485
+ def _prepare_tier_review_vals(self, definition, sequence):
486
+ return {
487
+ "model": self._name,
488
+ "res_id": self.id,
489
+ "definition_id": definition.id,
490
+ "requested_by": self.env.uid,
491
+ "sequence": sequence,
492
+ }
403
493
 
404
494
  def request_validation(self):
405
495
  td_obj = self.env["tier.definition"]
406
- tr_obj = created_trs = self.env["tier.review"]
496
+ tr_obj = self.env["tier.review"]
497
+ vals_list = []
407
498
  for rec in self:
408
- if getattr(rec, self._state_field) in self._state_from:
409
- if rec.need_validation:
410
- tier_definitions = td_obj.search(
411
- [("model", "=", self._name)], order="sequence desc"
412
- )
413
- sequence = 0
414
- for td in tier_definitions:
415
- if rec.evaluate_tier(td):
416
- sequence += 1
417
- created_trs += tr_obj.create(
418
- {
419
- "model": self._name,
420
- "res_id": rec.id,
421
- "definition_id": td.id,
422
- "sequence": sequence,
423
- "requested_by": self.env.uid,
424
- }
425
- )
426
- self._update_counter({"review_created": True})
499
+ if rec._check_state_from_condition() and rec.need_validation:
500
+ tier_definitions = td_obj.search(
501
+ [
502
+ ("model", "=", self._name),
503
+ ("company_id", "in", [False] + self.env.company.ids),
504
+ ],
505
+ order="sequence desc",
506
+ )
507
+ sequence = 0
508
+ for td in tier_definitions:
509
+ if rec.evaluate_tier(td):
510
+ sequence += 1
511
+ vals_list.append(rec._prepare_tier_review_vals(td, sequence))
512
+ self._update_counter({"review_created": True})
513
+ created_trs = tr_obj.create(vals_list)
427
514
  self._notify_review_requested(created_trs)
428
515
  return created_trs
429
516
 
@@ -440,16 +527,33 @@ class TierValidation(models.AbstractModel):
440
527
 
441
528
  def restart_validation(self):
442
529
  for rec in self:
530
+ partners_to_notify_ids = False
443
531
  if getattr(rec, self._state_field) in self._state_from:
444
532
  to_update_counter = (
445
533
  rec.mapped("review_ids").filtered(lambda a: a.status == "pending")
446
534
  and True
447
535
  or False
448
536
  )
537
+ reviews_to_notify = rec.review_ids.filtered(
538
+ lambda r: r.definition_id.notify_on_restarted
539
+ )
540
+ if reviews_to_notify:
541
+ partners_to_notify_ids = (
542
+ reviews_to_notify.mapped("reviewer_ids")
543
+ .mapped("partner_id")
544
+ .ids
545
+ )
449
546
  rec.mapped("review_ids").unlink()
450
547
  if to_update_counter:
451
548
  self._update_counter({"review_deleted": True})
452
- rec._notify_restarted_review()
549
+ if partners_to_notify_ids:
550
+ subscribe = "message_subscribe"
551
+ reviews_to_notify = rec.review_ids.filtered(
552
+ lambda r: r.definition_id.notify_on_restarted
553
+ )
554
+ if hasattr(self, subscribe):
555
+ getattr(self, subscribe)(partner_ids=partners_to_notify_ids)
556
+ rec._notify_restarted_review()
453
557
 
454
558
  @api.model
455
559
  def _update_counter(self, review_counter):
@@ -463,6 +567,27 @@ class TierValidation(models.AbstractModel):
463
567
  self.mapped("review_ids").unlink()
464
568
  return super().unlink()
465
569
 
570
+ def _add_tier_validation_buttons(self, node, params):
571
+ str_element = self.env["ir.qweb"]._render(
572
+ "base_tier_validation.tier_validation_buttons", params
573
+ )
574
+ new_node = etree.fromstring(str_element)
575
+ return new_node
576
+
577
+ def _add_tier_validation_label(self, node, params):
578
+ str_element = self.env["ir.qweb"]._render(
579
+ "base_tier_validation.tier_validation_label", params
580
+ )
581
+ new_node = etree.fromstring(str_element)
582
+ return new_node
583
+
584
+ def _add_tier_validation_reviews(self, node, params):
585
+ str_element = self.env["ir.qweb"]._render(
586
+ "base_tier_validation.tier_validation_reviews", params
587
+ )
588
+ new_node = etree.fromstring(str_element)
589
+ return new_node
590
+
466
591
  @api.model
467
592
  def get_view(self, view_id=None, view_type="form", **options):
468
593
  res = super().get_view(view_id=view_id, view_type=view_type, **options)
@@ -476,32 +601,27 @@ class TierValidation(models.AbstractModel):
476
601
  doc = etree.XML(res["arch"])
477
602
  params = {
478
603
  "state_field": self._state_field,
479
- "state_from": ",".join("'%s'" % state for state in self._state_from),
604
+ "state_operator": "not in",
605
+ "state_value": self._state_from,
480
606
  }
481
607
  all_models = res["models"].copy()
482
608
  for node in doc.xpath(self._tier_validation_buttons_xpath):
483
609
  # By default, after the last button of the header
484
- str_element = self.env["ir.qweb"]._render(
485
- "base_tier_validation.tier_validation_buttons", params
486
- )
487
- new_node = etree.fromstring(str_element)
610
+ # _add_tier_validation_buttons process
611
+ new_node = self._add_tier_validation_buttons(node, params)
488
612
  new_arch, new_models = View.postprocess_and_fields(new_node, self._name)
489
613
  new_node = etree.fromstring(new_arch)
490
614
  for new_element in new_node:
491
615
  node.addnext(new_element)
492
616
  for node in doc.xpath("/form/sheet"):
493
- str_element = self.env["ir.qweb"]._render(
494
- "base_tier_validation.tier_validation_label", params
495
- )
496
- new_node = etree.fromstring(str_element)
617
+ # _add_tier_validation_label process
618
+ new_node = self._add_tier_validation_label(node, params)
497
619
  new_arch, new_models = View.postprocess_and_fields(new_node, self._name)
498
620
  new_node = etree.fromstring(new_arch)
499
621
  for new_element in new_node:
500
622
  node.addprevious(new_element)
501
- str_element = self.env["ir.qweb"]._render(
502
- "base_tier_validation.tier_validation_reviews", params
503
- )
504
- new_node = etree.fromstring(str_element)
623
+ # _add_tier_validation_reviews process
624
+ new_node = self._add_tier_validation_reviews(node, params)
505
625
  new_arch, new_models = View.postprocess_and_fields(new_node, self._name)
506
626
  for model in new_models:
507
627
  if model in all_models:
@@ -5,3 +5,5 @@
5
5
  * Pedro Gonzalez <pedro.gonzalez@pesol.es>
6
6
  * Kitti U. <kittiu@ecosoft.co.th>
7
7
  * Saran Lim. <saranl@ecosoft.co.th>
8
+ * Evan Soh <evan.soh@omnisoftsolution.com>
9
+ * Manuel Regidor <manuel.regidor@sygel.es>