Skip to content
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

Open
wants to merge 2 commits into
base: 14.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions edi_oca/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ Contributors

* Simone Orsi <simahawk@gmail.com>
* Enric Tobella <etobella@creublanca.es>
* Thien Vo <thienvh@trobz.com>

Maintainers
~~~~~~~~~~~
Expand Down
2 changes: 2 additions & 0 deletions edi_oca/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@
"data/sequence.xml",
"data/job_channel.xml",
"data/job_function.xml",
"data/edi_configuration.xml",
"security/res_groups.xml",
"security/ir_model_access.xml",
"views/edi_backend_views.xml",
"views/edi_backend_type_views.xml",
"views/edi_exchange_record_views.xml",
"views/edi_exchange_type_views.xml",
"views/edi_exchange_type_rule_views.xml",
"views/edi_configuration_views.xml",
"views/menuitems.xml",
"templates/exchange_chatter_msg.xml",
"templates/exchange_mixin_buttons.xml",
Expand Down
18 changes: 18 additions & 0 deletions edi_oca/data/edi_configuration.xml
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>
1 change: 1 addition & 0 deletions edi_oca/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
from . import edi_exchange_type
from . import edi_exchange_type_rule
from . import edi_id_mixin
from . import edi_configuration
21 changes: 14 additions & 7 deletions edi_oca/models/edi_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,18 @@
for backend in self:
backend._check_output_exchange_sync(**kw)

def exchange_generate_send(self, recordset, skip_generate=False, skip_send=False):
for rec in recordset:
if skip_generate:
job1 = rec

Check warning on line 383 in edi_oca/models/edi_backend.py

View check run for this annotation

Codecov / codecov/patch

edi_oca/models/edi_backend.py#L383

Added line #L383 was not covered by tests
Copy link
Contributor

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? 🤔

else:
job1 = rec.delayable().action_exchange_generate()
if not skip_send:
# Chain send job.
# Raise prio to max to send the record out as fast as possible.
job1.on_done(rec.delayable(priority=0).action_exchange_send())
job1.delay()

def _check_output_exchange_sync(
self, skip_send=False, skip_sent=True, record_ids=None
):
Expand All @@ -396,13 +408,8 @@
"EDI Exchange output sync: found %d new records to process.",
len(new_records),
)
for rec in new_records:
job1 = rec.delayable().action_exchange_generate()
if not skip_send:
# Chain send job.
# Raise prio to max to send the record out as fast as possible.
job1.on_done(rec.delayable(priority=0).action_exchange_send())
job1.delay()
if new_records:
self.exchange_generate_send(new_records, skip_send=skip_send)

if skip_send:
return
Expand Down
193 changes: 193 additions & 0 deletions edi_oca/models/edi_configuration.py
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

Check warning on line 17 in edi_oca/models/edi_configuration.py

View check run for this annotation

Codecov / codecov/patch

edi_oca/models/edi_configuration.py#L16-L17

Added lines #L16 - L17 were not covered by tests


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)

Check warning on line 23 in edi_oca/models/edi_configuration.py

View check run for this annotation

Codecov / codecov/patch

edi_oca/models/edi_configuration.py#L23

Added line #L23 was not covered by tests


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(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

type should be mandatory

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, as per our comments.
Please add a comment here explaining why it is not mandatory ;)

string="Exchange Type",
comodel_name="edi.exchange.type",
ondelete="cascade",
auto_join=True,
index=True,
)
model = fields.Many2one(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
model = fields.Many2one(
model_id = fields.Many2one(

"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"),
Copy link
Contributor

Choose a reason for hiding this comment

The 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.
As there's no specific implementation here, I would add a comment that explain why this is here.

Also, I think trigger should be mandatory otherwise the conf is useless, no?
Maybe we can add another option like ("disabled", "Disabled") which would allow to keep the conf visible but disabled.

],
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(

Check warning on line 76 in edi_oca/models/edi_configuration.py

View check run for this annotation

Codecov / codecov/patch

edi_oca/models/edi_configuration.py#L76

Added line #L76 was not covered by tests
_("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(

Check warning on line 81 in edi_oca/models/edi_configuration.py

View check run for this annotation

Codecov / codecov/patch

edi_oca/models/edi_configuration.py#L81

Added line #L81 was not covered by tests
_("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 ""

Check warning on line 100 in edi_oca/models/edi_configuration.py

View check run for this annotation

Codecov / codecov/patch

edi_oca/models/edi_configuration.py#L100

Added line #L100 was not covered by tests
if utc:
dt = to_utc(dt)
return fields.Date.to_string(dt)

Check warning on line 103 in edi_oca/models/edi_configuration.py

View check run for this annotation

Codecov / codecov/patch

edi_oca/models/edi_configuration.py#L102-L103

Added lines #L102 - L103 were not covered by tests

@staticmethod
def _datetime_to_string(dt, utc=True):
if not dt:
return ""

Check warning on line 108 in edi_oca/models/edi_configuration.py

View check run for this annotation

Codecov / codecov/patch

edi_oca/models/edi_configuration.py#L108

Added line #L108 was not covered by tests
if utc:
dt = to_utc(dt)
return fields.Datetime.to_string(dt)

Check warning on line 111 in edi_oca/models/edi_configuration.py

View check run for this annotation

Codecov / codecov/patch

edi_oca/models/edi_configuration.py#L110-L111

Added lines #L110 - L111 were not covered by tests

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 {}

Check warning on line 146 in edi_oca/models/edi_configuration.py

View check run for this annotation

Codecov / codecov/patch

edi_oca/models/edi_configuration.py#L146

Added line #L146 was not covered by tests
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

Check warning on line 184 in edi_oca/models/edi_configuration.py

View check run for this annotation

Codecov / codecov/patch

edi_oca/models/edi_configuration.py#L184

Added line #L184 was not covered by tests

def edi_get_conf(self, trigger, backend=None):
domain = [("trigger", "=", trigger)]
backend_ids = self.mapped("type_id.backend_id.id")
Copy link
Contributor

Choose a reason for hiding this comment

The 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))

Check warning on line 190 in edi_oca/models/edi_configuration.py

View check run for this annotation

Codecov / codecov/patch

edi_oca/models/edi_configuration.py#L190

Added line #L190 was not covered by tests
elif backend_ids:
domain.append(("backend_id", "in", backend_ids))
return self.filtered_domain(domain)
44 changes: 43 additions & 1 deletion edi_oca/models/edi_exchange_consumer_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not only generated but generated and sent.
I wonder if we need this custom message...
The backend will call notify_action_complete when is done...

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
Copy link
Contributor

Choose a reason for hiding this comment

The 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 action_send_email anywhere.

# to be able to generalize across models.
if hasattr(self, "action_send_email"):
ir_action = self.action_send_email()

Check warning on line 310 in edi_oca/models/edi_exchange_consumer_mixin.py

View check run for this annotation

Codecov / codecov/patch

edi_oca/models/edi_exchange_consumer_mixin.py#L310

Added line #L310 was not covered by tests
else:
return False

Check warning on line 312 in edi_oca/models/edi_exchange_consumer_mixin.py

View check run for this annotation

Codecov / codecov/patch

edi_oca/models/edi_exchange_consumer_mixin.py#L312

Added line #L312 was not covered by tests
# Retrieve context and composer model
ctx = ir_action.get("context", {})
composer_model = self.env[ir_action["res_model"]].with_context(ctx)

Check warning on line 315 in edi_oca/models/edi_exchange_consumer_mixin.py

View check run for this annotation

Codecov / codecov/patch

edi_oca/models/edi_exchange_consumer_mixin.py#L314-L315

Added lines #L314 - L315 were not covered by tests

# Determine subtype and partner_ids dynamically based on model-specific logic
subtype = subtype_ref and self.env.ref(subtype_ref) or None

Check warning on line 318 in edi_oca/models/edi_exchange_consumer_mixin.py

View check run for this annotation

Codecov / codecov/patch

edi_oca/models/edi_exchange_consumer_mixin.py#L318

Added line #L318 was not covered by tests
if not subtype:
return False

Check warning on line 320 in edi_oca/models/edi_exchange_consumer_mixin.py

View check run for this annotation

Codecov / codecov/patch

edi_oca/models/edi_exchange_consumer_mixin.py#L320

Added line #L320 was not covered by tests

composer = composer_model.create({"subtype_id": subtype.id})
composer.onchange_template_id_wrapper()

Check warning on line 323 in edi_oca/models/edi_exchange_consumer_mixin.py

View check run for this annotation

Codecov / codecov/patch

edi_oca/models/edi_exchange_consumer_mixin.py#L322-L323

Added lines #L322 - L323 were not covered by tests

# 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

Check warning on line 327 in edi_oca/models/edi_exchange_consumer_mixin.py

View check run for this annotation

Codecov / codecov/patch

edi_oca/models/edi_exchange_consumer_mixin.py#L327

Added line #L327 was not covered by tests
elif partners:
composer.partner_ids = partners.ids

Check warning on line 329 in edi_oca/models/edi_exchange_consumer_mixin.py

View check run for this annotation

Codecov / codecov/patch

edi_oca/models/edi_exchange_consumer_mixin.py#L329

Added line #L329 was not covered by tests
else:
return False

Check warning on line 331 in edi_oca/models/edi_exchange_consumer_mixin.py

View check run for this annotation

Codecov / codecov/patch

edi_oca/models/edi_exchange_consumer_mixin.py#L331

Added line #L331 was not covered by tests

# Send the email
composer.send_mail()
return True

Check warning on line 335 in edi_oca/models/edi_exchange_consumer_mixin.py

View check run for this annotation

Codecov / codecov/patch

edi_oca/models/edi_exchange_consumer_mixin.py#L334-L335

Added lines #L334 - L335 were not covered by tests
3 changes: 3 additions & 0 deletions edi_oca/models/edi_exchange_record.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,9 @@ def action_exchange_generate(self, **kw):
self.ensure_one()
return self.backend_id.exchange_generate(self, **kw)

def action_exchange_generate_send(self):
return self.backend_id.exchange_generate_send(self)

def action_exchange_send(self):
self.ensure_one()
return self.backend_id.exchange_send(self)
Expand Down
1 change: 1 addition & 0 deletions edi_oca/readme/CONTRIBUTORS.rst
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>
18 changes: 18 additions & 0 deletions edi_oca/security/ir_model_access.xml
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,22 @@
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(4, ref('base_edi.group_edi_manager'))]" />
</record>
<record model="ir.model.access" id="access_edi_configuration_manager">
<field name="name">access_edi_configuration manager</field>
<field name="model_id" ref="model_edi_configuration" />
<field name="group_id" ref="base_edi.group_edi_manager" />
<field name="perm_read" eval="1" />
<field name="perm_create" eval="1" />
<field name="perm_write" eval="1" />
<field name="perm_unlink" eval="1" />
</record>
<record model="ir.model.access" id="access_edi_configuration_user">
<field name="name">access_edi_configuration user</field>
<field name="model_id" ref="model_edi_configuration" />
<field name="group_id" ref="base.group_user" />
<field name="perm_read" eval="1" />
<field name="perm_create" eval="0" />
<field name="perm_write" eval="0" />
<field name="perm_unlink" eval="0" />
</record>
</odoo>
Loading
Loading