odoo-addon-automation-oca 16.0.1.5.2__py3-none-any.whl → 17.0.1.0.0.5__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 (51) hide show
  1. odoo/addons/automation_oca/README.rst +7 -7
  2. odoo/addons/automation_oca/__init__.py +1 -0
  3. odoo/addons/automation_oca/__manifest__.py +1 -1
  4. odoo/addons/automation_oca/data/cron.xml +0 -5
  5. odoo/addons/automation_oca/i18n/automation_oca.pot +95 -22
  6. odoo/addons/automation_oca/i18n/es.po +0 -13
  7. odoo/addons/automation_oca/i18n/fr.po +0 -13
  8. odoo/addons/automation_oca/i18n/it.po +2 -15
  9. odoo/addons/automation_oca/models/automation_configuration.py +28 -26
  10. odoo/addons/automation_oca/models/automation_configuration_step.py +38 -11
  11. odoo/addons/automation_oca/models/automation_filter.py +5 -1
  12. odoo/addons/automation_oca/models/automation_record.py +64 -52
  13. odoo/addons/automation_oca/models/automation_record_step.py +34 -15
  14. odoo/addons/automation_oca/models/automation_tag.py +0 -1
  15. odoo/addons/automation_oca/models/mail_activity.py +3 -3
  16. odoo/addons/automation_oca/models/mail_mail.py +17 -15
  17. odoo/addons/automation_oca/models/mail_thread.py +6 -11
  18. odoo/addons/automation_oca/static/description/index.html +5 -5
  19. odoo/addons/automation_oca/static/src/fields/automation_activity/automation_activity.esm.js +5 -4
  20. odoo/addons/automation_oca/static/src/fields/automation_graph/automation_graph.esm.js +21 -25
  21. odoo/addons/automation_oca/static/src/views/automation_kanban/automation_kanban.scss +3 -0
  22. odoo/addons/automation_oca/static/src/views/automation_kanban/automation_kanban_compiler.esm.js +2 -3
  23. odoo/addons/automation_oca/static/src/views/automation_kanban/automation_kanban_record.esm.js +1 -2
  24. odoo/addons/automation_oca/static/src/views/automation_kanban/automation_kanban_renderer.esm.js +1 -2
  25. odoo/addons/automation_oca/tests/__init__.py +1 -0
  26. odoo/addons/automation_oca/tests/common.py +2 -1
  27. odoo/addons/automation_oca/tests/models.py +10 -0
  28. odoo/addons/automation_oca/tests/test_automation_action.py +7 -8
  29. odoo/addons/automation_oca/tests/test_automation_activity.py +122 -10
  30. odoo/addons/automation_oca/tests/test_automation_base.py +73 -82
  31. odoo/addons/automation_oca/tests/test_automation_date.py +59 -0
  32. odoo/addons/automation_oca/tests/test_automation_mail.py +31 -50
  33. odoo/addons/automation_oca/tests/test_automation_security.py +5 -2
  34. odoo/addons/automation_oca/utils/__init__.py +1 -0
  35. odoo/addons/automation_oca/utils/query.py +45 -0
  36. odoo/addons/automation_oca/views/automation_configuration.xml +60 -51
  37. odoo/addons/automation_oca/views/automation_configuration_step.xml +86 -49
  38. odoo/addons/automation_oca/views/automation_filter.xml +1 -3
  39. odoo/addons/automation_oca/views/automation_record.xml +32 -19
  40. odoo/addons/automation_oca/views/automation_record_step.xml +4 -14
  41. odoo/addons/automation_oca/views/automation_tag.xml +3 -4
  42. odoo/addons/automation_oca/views/link_tracker_clicks.xml +2 -3
  43. odoo/addons/automation_oca/wizards/automation_configuration_test.py +1 -2
  44. odoo/addons/automation_oca/wizards/automation_configuration_test.xml +4 -6
  45. odoo/addons/automation_oca/wizards/mail_compose_message.py +2 -3
  46. {odoo_addon_automation_oca-16.0.1.5.2.dist-info → odoo_addon_automation_oca-17.0.1.0.0.5.dist-info}/METADATA +12 -12
  47. odoo_addon_automation_oca-17.0.1.0.0.5.dist-info/RECORD +66 -0
  48. {odoo_addon_automation_oca-16.0.1.5.2.dist-info → odoo_addon_automation_oca-17.0.1.0.0.5.dist-info}/WHEEL +1 -1
  49. odoo_addon_automation_oca-17.0.1.0.0.5.dist-info/top_level.txt +1 -0
  50. odoo_addon_automation_oca-16.0.1.5.2.dist-info/RECORD +0 -62
  51. odoo_addon_automation_oca-16.0.1.5.2.dist-info/top_level.txt +0 -1
@@ -5,12 +5,12 @@ import logging
5
5
  from collections import defaultdict
6
6
 
7
7
  from odoo import _, api, fields, models
8
+ from odoo.exceptions import AccessError
8
9
 
9
10
  _logger = logging.getLogger(__name__)
10
11
 
11
12
 
12
13
  class AutomationRecord(models.Model):
13
-
14
14
  _name = "automation.record"
15
15
  _description = "Automation Record"
16
16
 
@@ -68,7 +68,7 @@ class AutomationRecord(models.Model):
68
68
  def _compute_resource_ref(self):
69
69
  for record in self:
70
70
  if record.model and record.model in self.env:
71
- record.resource_ref = "%s,%s" % (record.model, record.res_id or 0)
71
+ record.resource_ref = f"{record.model},{record.res_id or 0}"
72
72
  else:
73
73
  record.resource_ref = None
74
74
 
@@ -83,86 +83,98 @@ class AutomationRecord(models.Model):
83
83
  @api.model
84
84
  def _search(
85
85
  self,
86
- args,
86
+ domain,
87
87
  offset=0,
88
88
  limit=None,
89
89
  order=None,
90
- count=False,
91
- access_rights_uid=None,
92
90
  ):
93
- ids = super()._search(
94
- args,
91
+ query = super()._search(
92
+ domain=domain,
95
93
  offset=offset,
96
94
  limit=limit,
97
95
  order=order,
98
- count=False,
99
- access_rights_uid=access_rights_uid,
100
96
  )
101
- if not ids:
102
- return 0 if count else []
103
- orig_ids = ids
104
- ids = set(ids)
97
+ if self.env.is_superuser():
98
+ # restrictions do not apply for the superuser
99
+ return query
100
+
101
+ # TODO highlight orphaned EDI records in UI:
102
+ # - self.model + self.res_id are set
103
+ # - self.record returns empty recordset
104
+ # Remark: self.record is @property, not field
105
+
106
+ if query.is_empty():
107
+ return query
108
+ orig_ids = list(query)
109
+ ids = set(orig_ids)
105
110
  result = []
106
111
  model_data = defaultdict(lambda: defaultdict(set))
112
+ sub_query = """
113
+ SELECT id, res_id, model
114
+ FROM %(table)s
115
+ WHERE id = ANY (%%(ids)s)
116
+ """
107
117
  for sub_ids in self._cr.split_for_in_conditions(ids):
108
118
  self._cr.execute(
109
- """
110
- SELECT id, res_id, model
111
- FROM "%s"
112
- WHERE id = ANY (%%(ids)s)"""
113
- % self._table,
119
+ sub_query % {"table": self._table},
114
120
  dict(ids=list(sub_ids)),
115
121
  )
116
122
  for eid, res_id, model in self._cr.fetchall():
123
+ if not model:
124
+ result.append(eid)
125
+ continue
117
126
  model_data[model][res_id].add(eid)
127
+
118
128
  for model, targets in model_data.items():
119
- if not self.env[model].check_access_rights("read", False):
129
+ try:
130
+ self.env[model].check_access_rights("read")
131
+ except AccessError: # no read access rights
120
132
  continue
121
- res_ids = targets.keys()
122
- recs = self.env[model].browse(res_ids)
133
+ recs = self.env[model].browse(list(targets))
123
134
  missing = recs - recs.exists()
124
- if len(missing) > 0:
125
- for res_id in targets.keys():
126
- if res_id and res_id not in missing.ids:
127
- continue
128
- automation_record = self.env["automation.record"].browse(
129
- list(targets[res_id])
135
+ if missing:
136
+ for res_id in missing.ids:
137
+ _logger.warning(
138
+ "Deleted record %s,%s is referenced by automation.record %s",
139
+ model,
140
+ res_id,
141
+ list(targets[res_id]),
130
142
  )
131
- if not automation_record.is_orphan_record:
132
- _logger.info(
133
- "Deleted record %s,%s is referenced by automation.record",
134
- model,
135
- res_id,
136
- )
137
- # sudo to avoid access rights check on the record
138
- automation_record.sudo().write(
139
- {
140
- "is_orphan_record": True,
141
- "res_id": False,
142
- }
143
- )
144
- result += list(targets[res_id])
145
- allowed = (
143
+ self.sudo().search(
144
+ [("model", "=", model), ("res_id", "=", res_id)]
145
+ ).write(
146
+ {
147
+ "is_orphan_record": True,
148
+ "res_id": False,
149
+ }
150
+ )
151
+ recs = recs - missing
152
+ allowed = list(
146
153
  self.env[model]
147
154
  .with_context(active_test=False)
148
155
  ._search([("id", "in", recs.ids)])
149
156
  )
157
+ if self.env.is_system():
158
+ # Group "Settings" can list exchanges where record is deleted
159
+ allowed.extend(missing.ids)
150
160
  for target_id in allowed:
151
161
  result += list(targets.get(target_id, {}))
152
162
  if len(orig_ids) == limit and len(result) < len(orig_ids):
153
- result.extend(
154
- self._search(
155
- args,
156
- offset=offset + len(orig_ids),
157
- limit=limit,
158
- order=order,
159
- count=count,
160
- access_rights_uid=access_rights_uid,
161
- )[: limit - len(result)]
163
+ extend_query = self._search(
164
+ domain,
165
+ offset=offset + len(orig_ids),
166
+ limit=limit,
167
+ order=order,
162
168
  )
169
+ extend_ids = list(extend_query)
170
+ result.extend(extend_ids[: limit - len(result)])
171
+
163
172
  # Restore original ordering
164
173
  result = [x for x in orig_ids if x in result]
165
- return len(result) if count else list(result)
174
+ if set(orig_ids) != set(result):
175
+ # Create a virgin query
176
+ query = self.browse(result)._as_query()
177
+ return query
166
178
 
167
179
  def read(self, fields=None, load="_classic_read"):
168
180
  """Override to explicitely call check_access_rule, that is not called
@@ -9,6 +9,7 @@ import werkzeug.urls
9
9
  from dateutil.relativedelta import relativedelta
10
10
 
11
11
  from odoo import _, api, fields, models, tools
12
+ from odoo.exceptions import ValidationError
12
13
  from odoo.tools.safe_eval import safe_eval
13
14
 
14
15
 
@@ -156,7 +157,7 @@ class AutomationRecordStep(models.Model):
156
157
  self._reject()
157
158
  return self.browse()
158
159
  try:
159
- result = getattr(self, "_run_%s" % self.configuration_step_id.step_type)()
160
+ result = getattr(self, f"_run_{self.configuration_step_id.step_type}")()
160
161
  self.write({"state": "done", "processed_on": fields.Datetime.now()})
161
162
  if result:
162
163
  childs = self._fill_childs()
@@ -217,29 +218,24 @@ class AutomationRecordStep(models.Model):
217
218
  return True
218
219
 
219
220
  def _run_mail(self):
220
- author_id = self.configuration_step_id.mail_author_id.id
221
221
  composer_values = {
222
- "author_id": author_id,
223
222
  "record_name": False,
224
223
  "model": self.record_id.model,
225
224
  "composition_mode": "mass_mail",
226
225
  "template_id": self.configuration_step_id.mail_template_id.id,
227
226
  "automation_record_step_id": self.id,
228
227
  }
228
+ if self.configuration_step_id.mail_author_id:
229
+ composer_values["author_id"] = self.configuration_step_id.mail_author_id.id
230
+ composer_values[
231
+ "email_from"
232
+ ] = self.configuration_step_id.mail_author_id.email_formatted
229
233
  res_ids = [self.record_id.res_id]
230
234
  composer = (
231
235
  self.env["mail.compose.message"]
232
236
  .with_context(active_ids=res_ids)
233
237
  .create(composer_values)
234
238
  )
235
- composer.write(
236
- composer._onchange_template_id(
237
- self.configuration_step_id.mail_template_id.id,
238
- "mass_mail",
239
- self.record_id.model,
240
- self.record_id.res_id,
241
- )["value"]
242
- )
243
239
  # composer.body =
244
240
  extra_context = self._run_mail_context()
245
241
  composer = composer.with_context(active_ids=res_ids, **extra_context)
@@ -257,8 +253,7 @@ class AutomationRecordStep(models.Model):
257
253
  def _get_mail_tracking_url(self):
258
254
  return werkzeug.urls.url_join(
259
255
  self.get_base_url(),
260
- "automation_oca/track/%s/%s/blank.gif"
261
- % (self.id, self._get_mail_tracking_token()),
256
+ f"automation_oca/track/{self.id}/{self._get_mail_tracking_token()}/blank.gif",
262
257
  )
263
258
 
264
259
  def _run_mail_context(self):
@@ -322,12 +317,23 @@ class AutomationRecordStep(models.Model):
322
317
  todo = self.filtered(lambda r: not r.scheduled_date)
323
318
  for record in todo:
324
319
  config = record.configuration_step_id
325
- record.scheduled_date = fields.Datetime.now() + relativedelta(
326
- **{config.trigger_interval_type: config.trigger_interval}
320
+ record.scheduled_date = config._get_record_activity_scheduled_date(
321
+ record.record_id.resource_ref, force=True
327
322
  )
328
323
  todo._trigger_activities()
329
324
 
330
325
  def _set_activity_done(self):
326
+ domain = safe_eval(
327
+ self.configuration_step_id.activity_verification_domain or "[]",
328
+ self.configuration_step_id.configuration_id._get_eval_context(),
329
+ )
330
+ if domain and not self.record_id.resource_ref.filtered_domain(domain):
331
+ raise ValidationError(
332
+ _(
333
+ "The record does not fulfill the expected domain:\n%(domain)s",
334
+ domain=self.configuration_step_id.activity_verification_domain_error,
335
+ )
336
+ )
331
337
  self.write({"activity_done_on": fields.Datetime.now()})
332
338
  self.child_ids.filtered(
333
339
  lambda r: r.trigger_type == "activity_done"
@@ -450,3 +456,16 @@ class AutomationRecordStep(models.Model):
450
456
  },
451
457
  ]
452
458
  return []
459
+
460
+ def retry(self):
461
+ """
462
+ Retry the record step
463
+ """
464
+ if self.state not in ["error", "rejected", "expired", "cancel"]:
465
+ raise ValidationError(
466
+ _(
467
+ "You can only retry a record step in a rejected, "
468
+ "expired, cancelled or error state."
469
+ )
470
+ )
471
+ self.write({"state": "scheduled", "processed_on": False})
@@ -7,7 +7,6 @@ from odoo import api, fields, models
7
7
 
8
8
 
9
9
  class AutomationTag(models.Model):
10
-
11
10
  _name = "automation.tag"
12
11
  _description = "Automation Tag"
13
12
 
@@ -11,7 +11,7 @@ class MailActivity(models.Model):
11
11
 
12
12
  def _action_done(self, *args, **kwargs):
13
13
  if self.automation_record_step_id:
14
- self.automation_record_step_id._set_activity_done()
14
+ self.automation_record_step_id.sudo()._set_activity_done()
15
15
  return super(
16
16
  MailActivity,
17
17
  self.with_context(
@@ -23,5 +23,5 @@ class MailActivity(models.Model):
23
23
  if self.automation_record_step_id and not self.env.context.get(
24
24
  "automation_done"
25
25
  ):
26
- self.automation_record_step_id._set_activity_cancel()
27
- return super(MailActivity, self).unlink()
26
+ self.automation_record_step_id.sudo()._set_activity_cancel()
27
+ return super().unlink()
@@ -3,14 +3,12 @@
3
3
 
4
4
  import re
5
5
 
6
- import markupsafe
7
6
  import werkzeug.urls
8
7
 
9
8
  from odoo import api, fields, models, tools
10
9
 
11
10
 
12
11
  class MailMail(models.Model):
13
-
14
12
  _inherit = "mail.mail"
15
13
 
16
14
  automation_record_step_id = fields.Many2one("automation.record.step")
@@ -22,12 +20,17 @@ class MailMail(models.Model):
22
20
  record.automation_record_step_id.message_id = record.message_id
23
21
  return records
24
22
 
25
- def _send_prepare_body(self):
26
- body = super()._send_prepare_body()
27
- if self.automation_record_step_id:
28
- body = self.env["mail.render.mixin"]._shorten_links(body, {}, blacklist=[])
23
+ def _prepare_outgoing_body(self):
24
+ """Override to add the tracking URL to the body and to add trace ID in
25
+ shortened urls"""
26
+ self.ensure_one()
27
+ # super() already cleans pseudo-void content from editor
28
+ body = super()._prepare_outgoing_body()
29
+ if body and self.automation_record_step_id:
30
+ body = self.env["mail.render.mixin"]._shorten_links(body, {})
31
+ Wrapper = body.__class__
29
32
  token = self.automation_record_step_id._get_mail_tracking_token()
30
- for match in set(re.findall(tools.URL_REGEX, body)):
33
+ for match in set(re.findall(tools.mail.URL_REGEX, body)):
31
34
  href = match[0]
32
35
  url = match[1]
33
36
 
@@ -36,16 +39,15 @@ class MailMail(models.Model):
36
39
  if parsed.scheme.startswith("http") and parsed.path.startswith("/r/"):
37
40
  new_href = href.replace(
38
41
  url,
39
- "%s/au/%s/%s"
40
- % (url, str(self.automation_record_step_id.id), token),
41
- )
42
- body = body.replace(
43
- markupsafe.Markup(href), markupsafe.Markup(new_href)
42
+ f"{url}/au/{str(self.automation_record_step_id.id)}/{token}",
44
43
  )
45
- body = tools.append_content_to_html(
44
+ body = body.replace(Wrapper(href), Wrapper(new_href))
45
+
46
+ # generate tracking URL
47
+ tracking_url = self.automation_record_step_id._get_mail_tracking_url()
48
+ body = tools.mail.append_content_to_html(
46
49
  body,
47
- '<img src="%s"/>'
48
- % self.automation_record_step_id._get_mail_tracking_url(),
50
+ f'<img src="{tracking_url}"/>',
49
51
  plaintext=False,
50
52
  )
51
53
  return body
@@ -5,19 +5,16 @@ from odoo import api, models, tools
5
5
 
6
6
 
7
7
  class MailThread(models.AbstractModel):
8
-
9
8
  _inherit = "mail.thread"
10
9
 
11
10
  @api.model
12
11
  def _routing_handle_bounce(self, email_message, message_dict):
13
12
  """We want to mark the bounced email"""
14
- result = super(MailThread, self)._routing_handle_bounce(
15
- email_message, message_dict
16
- )
17
- bounced_msg_id = message_dict.get("bounced_msg_id")
18
- if bounced_msg_id:
13
+ result = super()._routing_handle_bounce(email_message, message_dict)
14
+ bounced_msg_ids = message_dict["bounced_msg_ids"]
15
+ if bounced_msg_ids:
19
16
  self.env["automation.record.step"].search(
20
- [("message_id", "in", bounced_msg_id)]
17
+ [("message_id", "in", bounced_msg_ids)]
21
18
  )._set_mail_bounced()
22
19
  return result
23
20
 
@@ -30,16 +27,14 @@ class MailThread(models.AbstractModel):
30
27
  thread_references = (
31
28
  message_dict["references"] or message_dict["in_reply_to"]
32
29
  )
33
- msg_references = tools.mail_header_msgid_re.findall(thread_references)
30
+ msg_references = tools.mail.mail_header_msgid_re.findall(thread_references)
34
31
  if msg_references:
35
32
  records = self.env["automation.record.step"].search(
36
33
  [("message_id", "in", msg_references)]
37
34
  )
38
35
  records._set_mail_open()
39
36
  records._set_mail_reply()
40
- return super(MailThread, self)._message_route_process(
41
- message, message_dict, routes
42
- )
37
+ return super()._message_route_process(message, message_dict, routes)
43
38
 
44
39
  @api.model
45
40
  def get_automation_access(self, doc_ids, operation, model_name=False):
@@ -367,9 +367,9 @@ ul.auto-toc {
367
367
  !! This file is generated by oca-gen-addon-readme !!
368
368
  !! changes will be overwritten. !!
369
369
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
370
- !! source digest: sha256:f8228197da48ef63f6c812bc2a6912523ee657a854f934dad2a70e2ef71f3c6b
370
+ !! source digest: sha256:c8c3d74102d915073579646e8aea543542fc9733f3787a6565ef71d913578080
371
371
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
372
- <p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/automation/tree/16.0/automation_oca"><img alt="OCA/automation" src="https://img.shields.io/badge/github-OCA%2Fautomation-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/automation-16-0/automation-16-0-automation_oca"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/automation&amp;target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
372
+ <p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/automation/tree/17.0/automation_oca"><img alt="OCA/automation" src="https://img.shields.io/badge/github-OCA%2Fautomation-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/automation-17-0/automation-17-0-automation_oca"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/automation&amp;target_branch=17.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
373
373
  <p>This module allows to automate several process according to some rules.</p>
374
374
  <p>This is useful for creating automated actions on your database like:</p>
375
375
  <ul class="simple">
@@ -424,7 +424,7 @@ new records need to be created.</li>
424
424
  <li>Press <tt class="docutils literal">Start</tt>.</li>
425
425
  <li>Inside the process, you can check all the created items.</li>
426
426
  </ol>
427
- <p><img alt="Configuration Screenshot" src="https://raw.githubusercontent.com/OCA/automation/16.0/automation_oca/static/description/configuration.png" /></p>
427
+ <p><img alt="Configuration Screenshot" src="https://raw.githubusercontent.com/OCA/automation/17.0/automation_oca/static/description/configuration.png" /></p>
428
428
  </div>
429
429
  <div class="section" id="configuration-of-steps">
430
430
  <h2><a class="toc-backref" href="#toc-entry-3">Configuration of steps</a></h2>
@@ -491,7 +491,7 @@ immediate without a cron.</p>
491
491
  <p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/automation/issues">GitHub Issues</a>.
492
492
  In case of trouble, please check there if your issue has already been reported.
493
493
  If you spotted it first, help us to smash it by providing a detailed and welcomed
494
- <a class="reference external" href="https://github.com/OCA/automation/issues/new?body=module:%20automation_oca%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
494
+ <a class="reference external" href="https://github.com/OCA/automation/issues/new?body=module:%20automation_oca%0Aversion:%2017.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
495
495
  <p>Do not contact contributors directly about support or help with technical issues.</p>
496
496
  </div>
497
497
  <div class="section" id="credits">
@@ -524,7 +524,7 @@ If you spotted it first, help us to smash it by providing a detailed and welcome
524
524
  <p>OCA, or the Odoo Community Association, is a nonprofit organization whose
525
525
  mission is to support the collaborative development of Odoo features and
526
526
  promote its widespread use.</p>
527
- <p>This module is part of the <a class="reference external" href="https://github.com/OCA/automation/tree/16.0/automation_oca">OCA/automation</a> project on GitHub.</p>
527
+ <p>This module is part of the <a class="reference external" href="https://github.com/OCA/automation/tree/17.0/automation_oca">OCA/automation</a> project on GitHub.</p>
528
528
  <p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
529
529
  </div>
530
530
  </div>
@@ -1,8 +1,8 @@
1
- /** @odoo-module **/
2
- import {useOpenX2ManyRecord, useX2ManyCrud} from "@web/views/fields/relational_utils";
1
+ /* @odoo-module */
3
2
 
3
+ import {X2ManyField, x2ManyField} from "@web/views/fields/x2many/x2many_field";
4
+ import {useOpenX2ManyRecord, useX2ManyCrud} from "@web/views/fields/relational_utils";
4
5
  import {AutomationKanbanRenderer} from "../../views/automation_kanban/automation_kanban_renderer.esm";
5
- import {X2ManyField} from "@web/views/fields/x2many/x2many_field";
6
6
  import {registry} from "@web/core/registry";
7
7
 
8
8
  const {useSubEnv} = owl;
@@ -50,4 +50,5 @@ AutomationActivity.components = {
50
50
  KanbanRenderer: AutomationKanbanRenderer,
51
51
  };
52
52
 
53
- registry.category("fields").add("automation_step", AutomationActivity);
53
+ export const AutomationActivityField = {...x2ManyField, component: AutomationActivity};
54
+ registry.category("fields").add("automation_step", AutomationActivityField);
@@ -1,6 +1,7 @@
1
- /** @odoo-module **/
1
+ /* @odoo-module */
2
2
  /* global Chart*/
3
3
 
4
+ import {_t} from "@web/core/l10n/translation";
4
5
  import {loadJS} from "@web/core/assets";
5
6
  import {registry} from "@web/core/registry";
6
7
  import {standardFieldProps} from "@web/views/fields/standard_field_props";
@@ -25,51 +26,46 @@ export class AutomationGraph extends Component {
25
26
  return {
26
27
  type: "line",
27
28
  data: {
28
- labels: this.props.value.done.map(function (pt) {
29
+ labels: this.props.record.data[this.props.name].done.map(function (pt) {
29
30
  return pt.x;
30
31
  }),
31
32
  datasets: [
32
33
  {
33
34
  backgroundColor: "#4CAF5080",
34
35
  borderColor: "#4CAF50",
35
- data: this.props.value.done,
36
+ data: this.props.record.data[this.props.name].done,
36
37
  fill: "start",
37
- label: this.env._t("Done"),
38
+ label: _t("Done"),
38
39
  borderWidth: 2,
39
40
  },
40
41
  {
41
42
  backgroundColor: "#F4433680",
42
43
  borderColor: "#F44336",
43
- data: this.props.value.error,
44
+ data: this.props.record.data[this.props.name].error,
44
45
  fill: "start",
45
- label: this.env._t("Error"),
46
+ label: _t("Error"),
46
47
  borderWidth: 2,
47
48
  },
48
49
  ],
49
50
  },
50
51
  options: {
51
- legend: {display: false},
52
-
52
+ plugins: {
53
+ legend: {display: false},
54
+ },
53
55
  layout: {
54
56
  padding: {left: 10, right: 10, top: 10, bottom: 10},
55
57
  },
56
58
  scales: {
57
- yAxes: [
58
- {
59
- type: "linear",
60
- display: false,
61
- ticks: {
62
- beginAtZero: true,
63
- },
64
- },
65
- ],
66
- xAxes: [
67
- {
68
- ticks: {
69
- maxRotation: 0,
70
- },
59
+ y: {
60
+ type: "linear",
61
+ display: false,
62
+ beginAtZero: true,
63
+ },
64
+ x: {
65
+ ticks: {
66
+ maxRotation: 0,
71
67
  },
72
- ],
68
+ },
73
69
  },
74
70
  maintainAspectRatio: false,
75
71
  elements: {
@@ -92,7 +88,6 @@ export class AutomationGraph extends Component {
92
88
  }
93
89
  var config = this._getChartConfig();
94
90
  this.chart = new Chart(this.canvasRef.el, config);
95
- Chart.animationService.advance();
96
91
  }
97
92
  }
98
93
 
@@ -101,4 +96,5 @@ AutomationGraph.props = {
101
96
  ...standardFieldProps,
102
97
  };
103
98
 
104
- registry.category("fields").add("automation_graph", AutomationGraph);
99
+ export const AutomationGraphField = {component: AutomationGraph};
100
+ registry.category("fields").add("automation_graph", AutomationGraphField);
@@ -126,3 +126,6 @@
126
126
  .filter-left {
127
127
  text-align: left;
128
128
  }
129
+ .o_kanban_renderer .o_kanban_record:not(.o_legacy_kanban_record):not(.o_kanban_ghost) {
130
+ border: none !important;
131
+ }
@@ -1,5 +1,4 @@
1
- /** @odoo-module */
2
-
1
+ /* @odoo-module */
3
2
  import {KanbanCompiler} from "@web/views/kanban/kanban_compiler";
4
3
 
5
4
  export class AutomationKanbanCompiler extends KanbanCompiler {
@@ -13,7 +12,7 @@ export class AutomationKanbanCompiler extends KanbanCompiler {
13
12
  compileHierarchyAddButton(el) {
14
13
  el.setAttribute(
15
14
  "t-on-click",
16
- "() => this.addNewChild({trigger_type: " +
15
+ "() => __comp__.addNewChild({trigger_type: " +
17
16
  el.getAttribute("t-att-trigger-type") +
18
17
  "})"
19
18
  );
@@ -1,5 +1,4 @@
1
- /** @odoo-module */
2
-
1
+ /* @odoo-module */
3
2
  import {AutomationKanbanCompiler} from "./automation_kanban_compiler.esm";
4
3
  import {KanbanRecord} from "@web/views/kanban/kanban_record";
5
4
 
@@ -1,5 +1,4 @@
1
- /** @odoo-module */
2
-
1
+ /* @odoo-module */
3
2
  import {AutomationKanbanRecord} from "./automation_kanban_record.esm";
4
3
  import {KanbanRenderer} from "@web/views/kanban/kanban_renderer";
5
4
 
@@ -1,5 +1,6 @@
1
1
  from . import test_automation_action
2
2
  from . import test_automation_activity
3
3
  from . import test_automation_base
4
+ from . import test_automation_date
4
5
  from . import test_automation_mail
5
6
  from . import test_automation_security
@@ -32,7 +32,8 @@ class AutomationTestCase(TransactionCase):
32
32
  "model_id": cls.env.ref("base.model_res_partner").id,
33
33
  "subject": "Subject",
34
34
  "partner_to": "{{ object.id }}",
35
- "body_html": 'My template <a href="https://www.twitter.com" /> with link',
35
+ "body_html": 'My template <a href="https://www.twitter.com" /> '
36
+ "with link",
36
37
  }
37
38
  )
38
39
  cls.partner_01 = cls.env["res.partner"].create(
@@ -0,0 +1,10 @@
1
+ # Copyright 2024 Dixmit
2
+ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
3
+
4
+ from odoo import fields, models
5
+
6
+
7
+ class ResPartner(models.Model):
8
+ _inherit = "res.partner"
9
+
10
+ date = fields.Date()