Skip to content

Commit 963f766

Browse files
thienvh332simahawk
authored andcommitted
edi_oca: Add new model edi.configuration
The aim of this model is to ease configuration for all kind of exchanges in particular at partner level.
1 parent 6b1e895 commit 963f766

16 files changed

+648
-15
lines changed

edi_oca/README.rst

+1
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ Contributors
166166

167167
* Simone Orsi <simahawk@gmail.com>
168168
* Enric Tobella <etobella@creublanca.es>
169+
* Thien Vo <thienvh@trobz.com>
169170

170171
Maintainers
171172
~~~~~~~~~~~

edi_oca/__manifest__.py

+2
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,15 @@
3030
"data/sequence.xml",
3131
"data/job_channel.xml",
3232
"data/job_function.xml",
33+
"data/edi_configuration.xml",
3334
"security/res_groups.xml",
3435
"security/ir_model_access.xml",
3536
"views/edi_backend_views.xml",
3637
"views/edi_backend_type_views.xml",
3738
"views/edi_exchange_record_views.xml",
3839
"views/edi_exchange_type_views.xml",
3940
"views/edi_exchange_type_rule_views.xml",
41+
"views/edi_configuration_views.xml",
4042
"views/res_partner.xml",
4143
"views/menuitems.xml",
4244
"templates/exchange_chatter_msg.xml",

edi_oca/data/edi_configuration.xml

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<odoo>
3+
<!-- The `ir_action` parameter must be passed in order to use `send_via_email`
4+
Examlple:
5+
<field name="snippet_do">record._edi_send_via_email(ir_action)</field>
6+
-->
7+
<record id="edi_conf_send_via_email" model="edi.configuration">
8+
<field name="name">Send Via Email</field>
9+
<field name="active">False</field>
10+
<field name="code">on_send_via_email</field>
11+
<field name="trigger">on_send_via_email</field>
12+
<field name="snippet_do">record._edi_send_via_email()</field>
13+
</record>
14+
15+
<!-- Add type_id to use Send Via EDI -->
16+
<record id="edi_conf_send_via_edi" model="edi.configuration">
17+
<field name="name">Send Via EDI</field>
18+
<field name="active">False</field>
19+
<field name="code">on_send_via_edi</field>
20+
<field name="trigger">on_send_via_edi</field>
21+
<field name="snippet_do">record._edi_send_via_edi(conf.type_id)</field>
22+
</record>
23+
</odoo>

edi_oca/models/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55
from . import edi_exchange_type
66
from . import edi_exchange_type_rule
77
from . import edi_id_mixin
8+
from . import edi_configuration

edi_oca/models/edi_backend.py

+15-7
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,19 @@ def _cron_check_output_exchange_sync(self, **kw):
398398
for backend in self:
399399
backend._check_output_exchange_sync(**kw)
400400

401+
def exchange_generate_send(self, recordset, skip_generate=False, skip_send=False):
402+
for rec in recordset:
403+
if skip_generate:
404+
job1 = rec
405+
else:
406+
job1 = rec.delayable().action_exchange_generate()
407+
if hasattr(job1, "on_done"):
408+
if not skip_send:
409+
# Chain send job.
410+
# Raise prio to max to send the record out as fast as possible.
411+
job1.on_done(rec.delayable(priority=0).action_exchange_send())
412+
job1.delay()
413+
401414
def _check_output_exchange_sync(
402415
self, skip_send=False, skip_sent=True, record_ids=None
403416
):
@@ -415,13 +428,8 @@ def _check_output_exchange_sync(
415428
"EDI Exchange output sync: found %d new records to process.",
416429
len(new_records),
417430
)
418-
for rec in new_records:
419-
job1 = rec.delayable().action_exchange_generate()
420-
if not skip_send:
421-
# Chain send job.
422-
# Raise prio to max to send the record out as fast as possible.
423-
job1.on_done(rec.delayable(priority=0).action_exchange_send())
424-
job1.delay()
431+
if new_records:
432+
self.exchange_generate_send(new_records, skip_send=skip_send)
425433

426434
if skip_send:
427435
return

edi_oca/models/edi_configuration.py

+209
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
# Copyright 2024 Camptocamp SA
2+
# @author Simone Orsi <simahawk@gmail.com>
3+
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
4+
5+
import datetime
6+
7+
import pytz
8+
9+
from odoo import _, api, exceptions, fields, models
10+
from odoo.tools import DotDict, safe_eval
11+
12+
13+
def date_to_datetime(dt):
14+
"""Convert date to datetime."""
15+
if isinstance(dt, datetime.date):
16+
return datetime.datetime.combine(dt, datetime.datetime.min.time())
17+
return dt
18+
19+
20+
def to_utc(dt):
21+
"""Convert date or datetime to UTC."""
22+
# Gracefully convert to datetime if needed 1st
23+
return date_to_datetime(dt).astimezone(pytz.UTC)
24+
25+
26+
class EdiConfiguration(models.Model):
27+
_name = "edi.configuration"
28+
_description = """
29+
This model is used to configure EDI (Electronic Data Interchange) flows.
30+
It allows users to create their own configurations, which can be tailored
31+
to meet the specific needs of their business processes.
32+
"""
33+
34+
name = fields.Char(string="Name", required=True)
35+
active = fields.Boolean(default=True)
36+
code = fields.Char(required=True, copy=False, index=True, unique=True)
37+
description = fields.Char(help="Describe what the conf is for")
38+
backend_id = fields.Many2one(string="Backend", comodel_name="edi.backend")
39+
# Field `type_id` is not a mandatory field because we will create 2 common confs
40+
# for EDI (`send_via_email` and `send_via_edi`). So type_id is
41+
# a mandatory field will create unwanted data for users when installing this module.
42+
type_id = fields.Many2one(
43+
string="Exchange Type",
44+
comodel_name="edi.exchange.type",
45+
ondelete="cascade",
46+
auto_join=True,
47+
index=True,
48+
)
49+
model_id = fields.Many2one(
50+
"ir.model",
51+
string="Model",
52+
help="Model the conf applies to. Leave blank to apply for all models",
53+
)
54+
model_name = fields.Char(related="model_id.model", store=True)
55+
trigger = fields.Selection(
56+
# The selections below are intended to assist with basic operations
57+
# and are used to setup common configuration.
58+
[
59+
("on_record_write", "Update Record"),
60+
("on_record_create", "Create Record"),
61+
("on_send_via_email", "Send Via Email"),
62+
("on_send_via_edi", "Send Via EDI"),
63+
("disabled", "Disabled"),
64+
],
65+
string="Trigger",
66+
# The default selection will be disabled.
67+
# which would allow to keep the conf visible but disabled.
68+
required=True,
69+
default="disabled",
70+
ondelete="on default",
71+
)
72+
snippet_before_do = fields.Text(
73+
string="Snippet Before Do",
74+
help="Snippet to validate the state and collect records to do",
75+
)
76+
snippet_do = fields.Text(
77+
string="Snippet Do",
78+
help="""Used to do something specific here.
79+
Receives: operation, edi_action, vals, old_vals.""",
80+
)
81+
82+
@api.constrains("backend_id", "type_id")
83+
def _constrains_backend(self):
84+
for rec in self:
85+
if rec.type_id.backend_id:
86+
if rec.type_id.backend_id != rec.backend_id:
87+
raise exceptions.ValidationError(
88+
_("Backend must match with exchange type's backend!")
89+
)
90+
else:
91+
if rec.type_id.backend_type_id != rec.backend_id.backend_type_id:
92+
raise exceptions.ValidationError(
93+
_("Backend type must match with exchange type's backend type!")
94+
)
95+
96+
# TODO: This function is also available in `edi_exchange_template`.
97+
# Consider adding this to util or mixin
98+
def _code_snippet_valued(self, snippet):
99+
snippet = snippet or ""
100+
return bool(
101+
[
102+
not line.startswith("#")
103+
for line in (snippet.splitlines())
104+
if line.strip("")
105+
]
106+
)
107+
108+
@staticmethod
109+
def _date_to_string(dt, utc=True):
110+
if not dt:
111+
return ""
112+
if utc:
113+
dt = to_utc(dt)
114+
return fields.Date.to_string(dt)
115+
116+
@staticmethod
117+
def _datetime_to_string(dt, utc=True):
118+
if not dt:
119+
return ""
120+
if utc:
121+
dt = to_utc(dt)
122+
return fields.Datetime.to_string(dt)
123+
124+
def _time_utils(self):
125+
return {
126+
"datetime": safe_eval.datetime,
127+
"dateutil": safe_eval.dateutil,
128+
"time": safe_eval.time,
129+
"utc_now": fields.Datetime.now(),
130+
"date_to_string": self._date_to_string,
131+
"datetime_to_string": self._datetime_to_string,
132+
"time_to_string": lambda dt: dt.strftime("%H:%M:%S") if dt else "",
133+
"first_of": fields.first,
134+
}
135+
136+
def _get_code_snippet_eval_context(self):
137+
"""Prepare the context used when evaluating python code
138+
139+
:returns: dict -- evaluation context given to safe_eval
140+
"""
141+
ctx = {
142+
"uid": self.env.uid,
143+
"user": self.env.user,
144+
"DotDict": DotDict,
145+
"conf": self,
146+
}
147+
ctx.update(self._time_utils())
148+
return ctx
149+
150+
def _evaluate_code_snippet(self, snippet, **render_values):
151+
if not self._code_snippet_valued(snippet):
152+
return {}
153+
eval_ctx = dict(render_values, **self._get_code_snippet_eval_context())
154+
safe_eval.safe_eval(snippet, eval_ctx, mode="exec", nocopy=True)
155+
result = eval_ctx.get("result", {})
156+
if not isinstance(result, dict):
157+
return {}
158+
return result
159+
160+
def edi_exec_snippet_before_do(self, record, **kwargs):
161+
self.ensure_one()
162+
# Execute snippet before do
163+
vals_before_do = self._evaluate_code_snippet(
164+
self.snippet_before_do, record=record, **kwargs
165+
)
166+
167+
# Prepare data
168+
vals = {
169+
"todo": vals_before_do.get("todo", True),
170+
"snippet_do_vars": vals_before_do.get("snippet_do_vars", False),
171+
"event_only": vals_before_do.get("event_only", False),
172+
"tracked_fields": vals_before_do.get("tracked_fields", False),
173+
"edi_action": vals_before_do.get("edi_action", False),
174+
}
175+
return vals
176+
177+
def edi_exec_snippet_do(self, record, **kwargs):
178+
self.ensure_one()
179+
if self.trigger == "disabled":
180+
return False
181+
182+
old_value = kwargs.get("old_vals", {}).get(record.id, {})
183+
new_value = kwargs.get("vals", {}).get(record.id, {})
184+
vals = {
185+
"todo": True,
186+
"record": record,
187+
"operation": kwargs.get("operation", False),
188+
"edi_action": kwargs.get("edi_action", False),
189+
"old_value": old_value,
190+
"vals": new_value,
191+
}
192+
if self.snippet_before_do:
193+
before_do_vals = self.edi_exec_snippet_before_do(record, **kwargs)
194+
vals.update(before_do_vals)
195+
if vals["todo"]:
196+
return self._evaluate_code_snippet(self.snippet_do, **vals)
197+
return True
198+
199+
def edi_get_conf(self, trigger, backend=None):
200+
domain = [("trigger", "=", trigger)]
201+
if backend:
202+
domain.append(("backend_id", "=", backend.id))
203+
else:
204+
# We will only get confs that have backend_id = False
205+
# or are equal to self.type_id.backend_id.id
206+
backend_ids = self.mapped("type_id.backend_id.id")
207+
backend_ids.append(False)
208+
domain.append(("backend_id", "in", backend_ids))
209+
return self.filtered_domain(domain)

edi_oca/models/edi_exchange_record.py

+3
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,9 @@ def action_exchange_generate(self, **kw):
315315
self.ensure_one()
316316
return self.backend_id.exchange_generate(self, **kw)
317317

318+
def action_exchange_generate_send(self, **kw):
319+
return self.backend_id.exchange_generate_send(self, **kw)
320+
318321
def action_exchange_send(self):
319322
self.ensure_one()
320323
return self.backend_id.exchange_send(self)

edi_oca/readme/CONTRIBUTORS.rst

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
* Simone Orsi <simahawk@gmail.com>
22
* Enric Tobella <etobella@creublanca.es>
3+
* Thien Vo <thienvh@trobz.com>

edi_oca/security/ir_model_access.xml

+18
Original file line numberDiff line numberDiff line change
@@ -127,4 +127,22 @@
127127
name="domain_force"
128128
>['|',('company_id','=',False),('company_id', 'in', company_ids)]</field>
129129
</record>
130+
<record model="ir.model.access" id="access_edi_configuration_manager">
131+
<field name="name">access_edi_configuration manager</field>
132+
<field name="model_id" ref="model_edi_configuration" />
133+
<field name="group_id" ref="base_edi.group_edi_manager" />
134+
<field name="perm_read" eval="1" />
135+
<field name="perm_create" eval="1" />
136+
<field name="perm_write" eval="1" />
137+
<field name="perm_unlink" eval="1" />
138+
</record>
139+
<record model="ir.model.access" id="access_edi_configuration_user">
140+
<field name="name">access_edi_configuration user</field>
141+
<field name="model_id" ref="model_edi_configuration" />
142+
<field name="group_id" ref="base.group_user" />
143+
<field name="perm_read" eval="1" />
144+
<field name="perm_create" eval="0" />
145+
<field name="perm_write" eval="0" />
146+
<field name="perm_unlink" eval="0" />
147+
</record>
130148
</odoo>

edi_oca/static/description/index.html

+5-7
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,10 @@
88

99
/*
1010
:Author: David Goodger (goodger@python.org)
11-
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
11+
:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $
1212
:Copyright: This stylesheet has been placed in the public domain.
1313
1414
Default cascading style sheet for the HTML output of Docutils.
15-
Despite the name, some widely supported CSS2 features are used.
1615
1716
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
1817
customize this style sheet.
@@ -275,7 +274,7 @@
275274
margin-left: 2em ;
276275
margin-right: 2em }
277276

278-
pre.code .ln { color: gray; } /* line numbers */
277+
pre.code .ln { color: grey; } /* line numbers */
279278
pre.code, code { background-color: #eeeeee }
280279
pre.code .comment, code .comment { color: #5C6576 }
281280
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
@@ -301,7 +300,7 @@
301300
span.pre {
302301
white-space: pre }
303302

304-
span.problematic, pre.problematic {
303+
span.problematic {
305304
color: red }
306305

307306
span.section-subtitle {
@@ -516,14 +515,13 @@ <h2><a class="toc-backref" href="#toc-entry-13">Contributors</a></h2>
516515
<ul class="simple">
517516
<li>Simone Orsi &lt;<a class="reference external" href="mailto:simahawk&#64;gmail.com">simahawk&#64;gmail.com</a>&gt;</li>
518517
<li>Enric Tobella &lt;<a class="reference external" href="mailto:etobella&#64;creublanca.es">etobella&#64;creublanca.es</a>&gt;</li>
518+
<li>Thien Vo &lt;<a class="reference external" href="mailto:thienvh&#64;trobz.com">thienvh&#64;trobz.com</a>&gt;</li>
519519
</ul>
520520
</div>
521521
<div class="section" id="maintainers">
522522
<h2><a class="toc-backref" href="#toc-entry-14">Maintainers</a></h2>
523523
<p>This module is maintained by the OCA.</p>
524-
<a class="reference external image-reference" href="https://odoo-community.org">
525-
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
526-
</a>
524+
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
527525
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
528526
mission is to support the collaborative development of Odoo features and
529527
promote its widespread use.</p>

edi_oca/tests/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@
1414
from . import test_quick_exec
1515
from . import test_exchange_type_deprecated_fields
1616
from . import test_exchange_type_encoding
17+
from . import test_edi_configuration

0 commit comments

Comments
 (0)