-
-
Notifications
You must be signed in to change notification settings - Fork 305
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[14.0][IMP] edi_oca: Add new model edi.configuration #1035
base: 14.0
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<?xml version="1.0" encoding="utf-8" ?> | ||
<odoo> | ||
<record id="edi_conf_send_via_email" model="edi.configuration"> | ||
<field name="name">Send Via Email</field> | ||
<field name="active">False</field> | ||
<field name="code">send_via_email</field> | ||
<field name="trigger">on_email_send</field> | ||
<field name="snippet_do">record._edi_send_via_email()</field> | ||
</record> | ||
|
||
<!-- Add type_id to use Send Via EDI --> | ||
<record id="edi_conf_send_via_edi" model="edi.configuration"> | ||
<field name="name">Send Via EDI</field> | ||
<field name="active">False</field> | ||
<field name="code">send_via_edi</field> | ||
<field name="snippet_do">record._edi_send_via_edi(conf.type_id)</field> | ||
</record> | ||
</odoo> |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,193 @@ | ||||||
# Copyright 2024 Camptocamp SA | ||||||
simahawk marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
# @author Simone Orsi <simahawk@gmail.com> | ||||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). | ||||||
|
||||||
import datetime | ||||||
|
||||||
import pytz | ||||||
|
||||||
from odoo import _, api, exceptions, fields, models | ||||||
from odoo.tools import DotDict, safe_eval | ||||||
|
||||||
|
||||||
def date_to_datetime(dt): | ||||||
"""Convert date to datetime.""" | ||||||
if isinstance(dt, datetime.date): | ||||||
return datetime.datetime.combine(dt, datetime.datetime.min.time()) | ||||||
return dt | ||||||
|
||||||
|
||||||
def to_utc(dt): | ||||||
"""Convert date or datetime to UTC.""" | ||||||
# Gracefully convert to datetime if needed 1st | ||||||
return date_to_datetime(dt).astimezone(pytz.UTC) | ||||||
|
||||||
|
||||||
class EdiConfiguration(models.Model): | ||||||
_name = "edi.configuration" | ||||||
_description = """ | ||||||
This model is used to configure EDI (Electronic Data Interchange) flows. | ||||||
It allows users to create their own configurations, which can be tailored | ||||||
to meet the specific needs of their business processes. | ||||||
""" | ||||||
|
||||||
name = fields.Char(string="Name", required=True) | ||||||
active = fields.Boolean(default=True) | ||||||
code = fields.Char(required=True, copy=False, index=True, unique=True) | ||||||
description = fields.Char(help="Describe what the conf is for") | ||||||
backend_id = fields.Many2one(string="Backend", comodel_name="edi.backend") | ||||||
type_id = fields.Many2one( | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. type should be mandatory There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no, as per our comments. |
||||||
string="Exchange Type", | ||||||
comodel_name="edi.exchange.type", | ||||||
ondelete="cascade", | ||||||
auto_join=True, | ||||||
index=True, | ||||||
) | ||||||
model = fields.Many2one( | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
"ir.model", | ||||||
string="Model", | ||||||
help="Model the conf applies to. Leave blank to apply for all models", | ||||||
) | ||||||
model_name = fields.Char(related="model.model", store=True) | ||||||
trigger = fields.Selection( | ||||||
[ | ||||||
("on_record_write", "Update Record"), | ||||||
("on_record_create", "Create Record"), | ||||||
("on_email_send", "Send Email"), | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess this is to ease the configuration for models like SO, PO, INV, that can be sent by email. Also, I think |
||||||
], | ||||||
string="Trigger", | ||||||
default=False, | ||||||
) | ||||||
snippet_before_do = fields.Text( | ||||||
string="Snippet Before Do", | ||||||
help="Snippet to validate the state and collect records to do", | ||||||
) | ||||||
snippet_do = fields.Text( | ||||||
string="Snippet Do", | ||||||
help="""Used to do something specific here. | ||||||
Receives: operation, edi_action, vals, old_vals.""", | ||||||
) | ||||||
|
||||||
@api.constrains("backend_id", "type_id") | ||||||
def _constrains_backend(self): | ||||||
for rec in self: | ||||||
if rec.type_id.backend_id: | ||||||
if rec.type_id.backend_id != rec.backend_id: | ||||||
raise exceptions.ValidationError( | ||||||
_("Backend must match with exchange type's backend!") | ||||||
) | ||||||
else: | ||||||
if rec.type_id.backend_type_id != rec.backend_id.backend_type_id: | ||||||
raise exceptions.ValidationError( | ||||||
_("Backend type must match with exchange type's backend type!") | ||||||
) | ||||||
|
||||||
# TODO: This function is also available in `edi_exchange_template`. | ||||||
# Consider adding this to util or mixin | ||||||
def _code_snippet_valued(self, snippet): | ||||||
thienvh332 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
snippet = snippet or "" | ||||||
return bool( | ||||||
[ | ||||||
not line.startswith("#") | ||||||
for line in (snippet.splitlines()) | ||||||
if line.strip("") | ||||||
] | ||||||
) | ||||||
|
||||||
@staticmethod | ||||||
def _date_to_string(dt, utc=True): | ||||||
if not dt: | ||||||
return "" | ||||||
if utc: | ||||||
dt = to_utc(dt) | ||||||
return fields.Date.to_string(dt) | ||||||
|
||||||
@staticmethod | ||||||
def _datetime_to_string(dt, utc=True): | ||||||
if not dt: | ||||||
return "" | ||||||
if utc: | ||||||
dt = to_utc(dt) | ||||||
return fields.Datetime.to_string(dt) | ||||||
|
||||||
def _time_utils(self): | ||||||
return { | ||||||
"datetime": safe_eval.datetime, | ||||||
"dateutil": safe_eval.dateutil, | ||||||
"time": safe_eval.time, | ||||||
"utc_now": fields.Datetime.now(), | ||||||
"date_to_string": self._date_to_string, | ||||||
"datetime_to_string": self._datetime_to_string, | ||||||
"time_to_string": lambda dt: dt.strftime("%H:%M:%S") if dt else "", | ||||||
"first_of": fields.first, | ||||||
} | ||||||
|
||||||
def _get_code_snippet_eval_context(self): | ||||||
"""Prepare the context used when evaluating python code | ||||||
|
||||||
:returns: dict -- evaluation context given to safe_eval | ||||||
""" | ||||||
ctx = { | ||||||
"uid": self.env.uid, | ||||||
"user": self.env.user, | ||||||
"DotDict": DotDict, | ||||||
"conf": self, | ||||||
} | ||||||
ctx.update(self._time_utils()) | ||||||
return ctx | ||||||
|
||||||
def _evaluate_code_snippet(self, snippet, **render_values): | ||||||
if not self._code_snippet_valued(snippet): | ||||||
return {} | ||||||
eval_ctx = dict(render_values, **self._get_code_snippet_eval_context()) | ||||||
safe_eval.safe_eval(snippet, eval_ctx, mode="exec", nocopy=True) | ||||||
result = eval_ctx.get("result", {}) | ||||||
if not isinstance(result, dict): | ||||||
return {} | ||||||
return result | ||||||
|
||||||
def edi_exec_snippet_before_do(self, record, **kwargs): | ||||||
self.ensure_one() | ||||||
# Execute snippet before do | ||||||
vals_before_do = self._evaluate_code_snippet( | ||||||
self.snippet_before_do, record=record, **kwargs | ||||||
) | ||||||
|
||||||
# Prepare data | ||||||
vals = { | ||||||
"todo": vals_before_do.get("todo", True), | ||||||
"snippet_do_vars": vals_before_do.get("snippet_do_vars", False), | ||||||
"event_only": vals_before_do.get("event_only", False), | ||||||
"tracked_fields": vals_before_do.get("tracked_fields", False), | ||||||
"edi_action": vals_before_do.get("edi_action", False), | ||||||
} | ||||||
return vals | ||||||
|
||||||
def edi_exec_snippet_do(self, record, **kwargs): | ||||||
self.ensure_one() | ||||||
|
||||||
old_value = kwargs.get("old_vals", {}).get(record.id, {}) | ||||||
new_value = kwargs.get("vals", {}).get(record.id, {}) | ||||||
vals = { | ||||||
"todo": True, | ||||||
"record": record, | ||||||
"operation": kwargs.get("operation", False), | ||||||
"edi_action": kwargs.get("edi_action", False), | ||||||
"old_value": old_value, | ||||||
"vals": new_value, | ||||||
} | ||||||
if self.snippet_before_do: | ||||||
before_do_vals = self.edi_exec_snippet_before_do(record, **kwargs) | ||||||
vals.update(before_do_vals) | ||||||
if vals["todo"]: | ||||||
return self._evaluate_code_snippet(self.snippet_do, **vals) | ||||||
return True | ||||||
|
||||||
def edi_get_conf(self, trigger, backend=None): | ||||||
domain = [("trigger", "=", trigger)] | ||||||
backend_ids = self.mapped("type_id.backend_id.id") | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what's the point on filtering on all the backends assigned to the current recordset? |
||||||
if backend: | ||||||
domain.append(("backend_id", "=", backend.id)) | ||||||
elif backend_ids: | ||||||
domain.append(("backend_id", "in", backend_ids)) | ||||||
return self.filtered_domain(domain) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,7 +7,7 @@ | |
|
||
from lxml import etree | ||
|
||
from odoo import api, fields, models | ||
from odoo import _, api, fields, models | ||
from odoo.tools import safe_eval | ||
|
||
from odoo.addons.base_sparse_field.models.fields import Serialized | ||
|
@@ -291,3 +291,45 @@ | |
def _edi_get_origin(self): | ||
self.ensure_one() | ||
return self.origin_exchange_record_id | ||
|
||
def _edi_send_via_edi(self, exchange_type): | ||
exchange_record = self._edi_create_exchange_record(exchange_type) | ||
exchange_record.action_exchange_generate_send() | ||
msg = _("EDI auto: output generated.") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not only generated but generated and sent. |
||
exchange_record._notify_related_record(msg) | ||
exchange_record._trigger_edi_event("generated") | ||
|
||
def _edi_send_via_email( | ||
self, ir_action=None, subtype_ref=None, partner_method=None, partners=None | ||
): | ||
# Default action if not provided | ||
if ir_action is None: | ||
# `action_send_email` is just an action name I created | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't get what's the need for this ... there's no |
||
# to be able to generalize across models. | ||
if hasattr(self, "action_send_email"): | ||
ir_action = self.action_send_email() | ||
else: | ||
return False | ||
# Retrieve context and composer model | ||
ctx = ir_action.get("context", {}) | ||
composer_model = self.env[ir_action["res_model"]].with_context(ctx) | ||
|
||
# Determine subtype and partner_ids dynamically based on model-specific logic | ||
subtype = subtype_ref and self.env.ref(subtype_ref) or None | ||
if not subtype: | ||
return False | ||
|
||
composer = composer_model.create({"subtype_id": subtype.id}) | ||
composer.onchange_template_id_wrapper() | ||
|
||
# Dynamically retrieve partners based on the provided method or fallback to parameter | ||
if partner_method and hasattr(self, partner_method): | ||
composer.partner_ids = getattr(self, partner_method)().ids | ||
elif partners: | ||
composer.partner_ids = partners.ids | ||
else: | ||
return False | ||
|
||
# Send the email | ||
composer.send_mail() | ||
return True | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
* Simone Orsi <simahawk@gmail.com> | ||
* Enric Tobella <etobella@creublanca.es> | ||
* Thien Vo <thienvh@trobz.com> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what would be delayed in this case if we skip_send too? 🤔