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.
- odoo/addons/automation_oca/README.rst +7 -7
- odoo/addons/automation_oca/__init__.py +1 -0
- odoo/addons/automation_oca/__manifest__.py +1 -1
- odoo/addons/automation_oca/data/cron.xml +0 -5
- odoo/addons/automation_oca/i18n/automation_oca.pot +95 -22
- odoo/addons/automation_oca/i18n/es.po +0 -13
- odoo/addons/automation_oca/i18n/fr.po +0 -13
- odoo/addons/automation_oca/i18n/it.po +2 -15
- odoo/addons/automation_oca/models/automation_configuration.py +28 -26
- odoo/addons/automation_oca/models/automation_configuration_step.py +38 -11
- odoo/addons/automation_oca/models/automation_filter.py +5 -1
- odoo/addons/automation_oca/models/automation_record.py +64 -52
- odoo/addons/automation_oca/models/automation_record_step.py +34 -15
- odoo/addons/automation_oca/models/automation_tag.py +0 -1
- odoo/addons/automation_oca/models/mail_activity.py +3 -3
- odoo/addons/automation_oca/models/mail_mail.py +17 -15
- odoo/addons/automation_oca/models/mail_thread.py +6 -11
- odoo/addons/automation_oca/static/description/index.html +5 -5
- odoo/addons/automation_oca/static/src/fields/automation_activity/automation_activity.esm.js +5 -4
- odoo/addons/automation_oca/static/src/fields/automation_graph/automation_graph.esm.js +21 -25
- odoo/addons/automation_oca/static/src/views/automation_kanban/automation_kanban.scss +3 -0
- odoo/addons/automation_oca/static/src/views/automation_kanban/automation_kanban_compiler.esm.js +2 -3
- odoo/addons/automation_oca/static/src/views/automation_kanban/automation_kanban_record.esm.js +1 -2
- odoo/addons/automation_oca/static/src/views/automation_kanban/automation_kanban_renderer.esm.js +1 -2
- odoo/addons/automation_oca/tests/__init__.py +1 -0
- odoo/addons/automation_oca/tests/common.py +2 -1
- odoo/addons/automation_oca/tests/models.py +10 -0
- odoo/addons/automation_oca/tests/test_automation_action.py +7 -8
- odoo/addons/automation_oca/tests/test_automation_activity.py +122 -10
- odoo/addons/automation_oca/tests/test_automation_base.py +73 -82
- odoo/addons/automation_oca/tests/test_automation_date.py +59 -0
- odoo/addons/automation_oca/tests/test_automation_mail.py +31 -50
- odoo/addons/automation_oca/tests/test_automation_security.py +5 -2
- odoo/addons/automation_oca/utils/__init__.py +1 -0
- odoo/addons/automation_oca/utils/query.py +45 -0
- odoo/addons/automation_oca/views/automation_configuration.xml +60 -51
- odoo/addons/automation_oca/views/automation_configuration_step.xml +86 -49
- odoo/addons/automation_oca/views/automation_filter.xml +1 -3
- odoo/addons/automation_oca/views/automation_record.xml +32 -19
- odoo/addons/automation_oca/views/automation_record_step.xml +4 -14
- odoo/addons/automation_oca/views/automation_tag.xml +3 -4
- odoo/addons/automation_oca/views/link_tracker_clicks.xml +2 -3
- odoo/addons/automation_oca/wizards/automation_configuration_test.py +1 -2
- odoo/addons/automation_oca/wizards/automation_configuration_test.xml +4 -6
- odoo/addons/automation_oca/wizards/mail_compose_message.py +2 -3
- {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
- odoo_addon_automation_oca-17.0.1.0.0.5.dist-info/RECORD +66 -0
- {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
- odoo_addon_automation_oca-17.0.1.0.0.5.dist-info/top_level.txt +1 -0
- odoo_addon_automation_oca-16.0.1.5.2.dist-info/RECORD +0 -62
- 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 = "
|
|
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
|
-
|
|
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
|
-
|
|
94
|
-
|
|
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
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
129
|
+
try:
|
|
130
|
+
self.env[model].check_access_rights("read")
|
|
131
|
+
except AccessError: # no read access rights
|
|
120
132
|
continue
|
|
121
|
-
|
|
122
|
-
recs = self.env[model].browse(res_ids)
|
|
133
|
+
recs = self.env[model].browse(list(targets))
|
|
123
134
|
missing = recs - recs.exists()
|
|
124
|
-
if
|
|
125
|
-
for res_id in
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
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_
|
|
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
|
|
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 =
|
|
326
|
-
|
|
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})
|
|
@@ -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(
|
|
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
|
|
26
|
-
body
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
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="
|
|
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(
|
|
15
|
-
|
|
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",
|
|
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(
|
|
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:
|
|
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/
|
|
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&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/
|
|
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:%
|
|
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/
|
|
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
|
-
|
|
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
|
-
|
|
53
|
+
export const AutomationActivityField = {...x2ManyField, component: AutomationActivity};
|
|
54
|
+
registry.category("fields").add("automation_step", AutomationActivityField);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
|
|
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.
|
|
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.
|
|
36
|
+
data: this.props.record.data[this.props.name].done,
|
|
36
37
|
fill: "start",
|
|
37
|
-
label:
|
|
38
|
+
label: _t("Done"),
|
|
38
39
|
borderWidth: 2,
|
|
39
40
|
},
|
|
40
41
|
{
|
|
41
42
|
backgroundColor: "#F4433680",
|
|
42
43
|
borderColor: "#F44336",
|
|
43
|
-
data: this.props.
|
|
44
|
+
data: this.props.record.data[this.props.name].error,
|
|
44
45
|
fill: "start",
|
|
45
|
-
label:
|
|
46
|
+
label: _t("Error"),
|
|
46
47
|
borderWidth: 2,
|
|
47
48
|
},
|
|
48
49
|
],
|
|
49
50
|
},
|
|
50
51
|
options: {
|
|
51
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
99
|
+
export const AutomationGraphField = {component: AutomationGraph};
|
|
100
|
+
registry.category("fields").add("automation_graph", AutomationGraphField);
|
odoo/addons/automation_oca/static/src/views/automation_kanban/automation_kanban_compiler.esm.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
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
|
-
"() =>
|
|
15
|
+
"() => __comp__.addNewChild({trigger_type: " +
|
|
17
16
|
el.getAttribute("t-att-trigger-type") +
|
|
18
17
|
"})"
|
|
19
18
|
);
|
|
@@ -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" />
|
|
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(
|