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.
- odoo/addons/base_tier_validation/README.rst +11 -6
- odoo/addons/base_tier_validation/__manifest__.py +1 -1
- odoo/addons/base_tier_validation/i18n/base_tier_validation.pot +104 -6
- odoo/addons/base_tier_validation/i18n/es.po +147 -73
- odoo/addons/base_tier_validation/i18n/es_MX.po +104 -6
- odoo/addons/base_tier_validation/i18n/fr.po +186 -78
- odoo/addons/base_tier_validation/i18n/it.po +159 -48
- odoo/addons/base_tier_validation/i18n/nl_NL.po +159 -37
- odoo/addons/base_tier_validation/i18n/sv.po +140 -54
- odoo/addons/base_tier_validation/i18n/tr.po +107 -6
- odoo/addons/base_tier_validation/i18n/zh_CN.po +107 -6
- odoo/addons/base_tier_validation/models/res_config_settings.py +3 -3
- odoo/addons/base_tier_validation/models/res_users.py +5 -0
- odoo/addons/base_tier_validation/models/tier_definition.py +18 -0
- odoo/addons/base_tier_validation/models/tier_review.py +18 -3
- odoo/addons/base_tier_validation/models/tier_validation.py +183 -63
- odoo/addons/base_tier_validation/readme/CONTRIBUTORS.rst +2 -0
- odoo/addons/base_tier_validation/static/description/index.html +68 -65
- odoo/addons/base_tier_validation/static/src/xml/tier_review_template.xml +41 -17
- odoo/addons/base_tier_validation/templates/tier_validation_templates.xml +7 -6
- odoo/addons/base_tier_validation/tests/common.py +19 -6
- odoo/addons/base_tier_validation/tests/test_tier_validation.py +415 -0
- odoo/addons/base_tier_validation/tests/tier_validation_tester.py +13 -2
- odoo/addons/base_tier_validation/views/res_config_settings_views.xml +6 -6
- odoo/addons/base_tier_validation/views/tier_definition_view.xml +14 -1
- {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
- {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
- {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
- {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(
|
108
|
-
|
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(
|
117
|
-
|
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(
|
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
|
-
|
253
|
-
|
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
|
-
|
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
|
-
|
277
|
-
|
278
|
-
|
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(
|
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
|
-
|
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
|
-
|
381
|
-
|
382
|
-
|
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
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
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 =
|
496
|
+
tr_obj = self.env["tier.review"]
|
497
|
+
vals_list = []
|
407
498
|
for rec in self:
|
408
|
-
if
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
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
|
-
|
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
|
-
"
|
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
|
-
|
485
|
-
|
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
|
-
|
494
|
-
|
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
|
-
|
502
|
-
|
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:
|