Skip to content

Commit 08547d9

Browse files
[DON'T MERGE] edi_storage_oca: adapt listeners to new flow
1 parent c57e1a2 commit 08547d9

7 files changed

Lines changed: 219 additions & 23 deletions

File tree

edi_storage_oca/README.rst

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
.. image:: https://odoo-community.org/readme-banner-image
2-
:target: https://odoo-community.org/get-involved?utm_source=readme
3-
:alt: Odoo Community Association
4-
51
===========================
62
EDI Storage backend support
73
===========================
@@ -17,7 +13,7 @@ EDI Storage backend support
1713
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
1814
:target: https://odoo-community.org/page/development-status
1915
:alt: Beta
20-
.. |badge2| image:: https://img.shields.io/badge/license-LGPL--3-blue.png
16+
.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png
2117
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
2218
:alt: License: LGPL-3
2319
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fedi--framework-lightgray.png?logo=github

edi_storage_oca/__manifest__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"depends": ["edi_core_oca", "fs_storage"],
1616
"data": [
1717
"data/cron.xml",
18+
"data/edi_configuration.xml",
1819
"security/ir_model_access.xml",
1920
"views/edi_backend_views.xml",
2021
],
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<odoo noupdate="1">
2+
<record id="edi_config_trigger_storage_done" model="edi.configuration.trigger">
3+
<field name="name">On record exchange done</field>
4+
<field name="code">on_edi_exchange_done</field>
5+
<field name="description">Trigger when a record exchange is done</field>
6+
</record>
7+
<record id="edi_config_trigger_storage_error" model="edi.configuration.trigger">
8+
<field name="name">On record exchange error</field>
9+
<field name="code">on_edi_exchange_error</field>
10+
<field name="description">Trigger when a record exchange has an error</field>
11+
</record>
12+
<record id="edi_config_storage_done" model="edi.configuration">
13+
<field name="name">Storage Move File on Done</field>
14+
<field name="is_global" eval="True" />
15+
<field name="trigger_id" ref="edi_config_trigger_storage_done" />
16+
<field name="snippet_do">record.on_edi_exchange_done()</field>
17+
</record>
18+
<record id="edi_config_storage_error" model="edi.configuration">
19+
<field name="name">Storage Move File on Error</field>
20+
<field name="is_global" eval="True" />
21+
<field name="trigger_id" ref="edi_config_trigger_storage_error" />
22+
<field name="snippet_do">record.on_edi_exchange_error()</field>
23+
</record>
24+
</odoo>

edi_storage_oca/models/edi_exchange_record.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
# Copyright 2024 Camptocamp SA
22
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
33

4+
import functools
5+
import os
6+
from pathlib import PurePath
7+
48
from odoo import fields, models
59

10+
from .. import utils
11+
612

713
class EDIExchangeRecord(models.Model):
814
_inherit = "edi.exchange.record"
@@ -13,3 +19,67 @@ class EDIExchangeRecord(models.Model):
1319
string="FS Storage",
1420
help="Record created from a file found in this FS storage",
1521
)
22+
23+
def _move_file(self, storage, from_dir_str, to_dir_str, filename):
24+
from_dir = PurePath(from_dir_str)
25+
to_dir = PurePath(to_dir_str)
26+
# - storage.list_files now includes path in fs_storage, breaking change
27+
# - we remove path
28+
files = utils.list_files(storage, from_dir.as_posix())
29+
files = [os.path.basename(f) for f in files]
30+
if filename not in files:
31+
return False
32+
self._add_post_commit_hook(
33+
utils.move_files,
34+
storage,
35+
[(from_dir / filename).as_posix()],
36+
to_dir.as_posix(),
37+
)
38+
return True
39+
40+
def _add_post_commit_hook(
41+
self, move_func, storage, sftp_filepath, sftp_destination_path
42+
):
43+
"""Add hook after commit to move the file when transaction is over."""
44+
self.env.cr.postcommit.add(
45+
functools.partial(move_func, storage, sftp_filepath, sftp_destination_path)
46+
)
47+
48+
def on_edi_exchange_done(self):
49+
storage = self.storage_id
50+
res = False
51+
if self.direction == "input" and storage:
52+
file = self.exchange_filename
53+
pending_dir = self.type_id._storage_fullpath(
54+
self.backend_id.input_dir_pending
55+
).as_posix()
56+
done_dir = self.type_id._storage_fullpath(
57+
self.backend_id.input_dir_done
58+
).as_posix()
59+
error_dir = self.type_id._storage_fullpath(
60+
self.backend_id.input_dir_error
61+
).as_posix()
62+
if not done_dir:
63+
return res
64+
res = self._move_file(storage, pending_dir, done_dir, file)
65+
if not res:
66+
# If a file previously failed it should have been previously
67+
# moved to the error dir, therefore it is not present in the
68+
# pending dir and we need to retry from error dir.
69+
res = self._move_file(storage, error_dir, done_dir, file)
70+
return res
71+
72+
def on_edi_exchange_error(self):
73+
storage = self.storage_id
74+
res = False
75+
if self.direction == "input" and storage:
76+
file = self.exchange_filename
77+
pending_dir = self.type_id._storage_fullpath(
78+
self.backend_id.input_dir_pending
79+
).as_posix()
80+
error_dir = self.type_id._storage_fullpath(
81+
self.backend_id.input_dir_error
82+
).as_posix()
83+
if error_dir:
84+
res = self._move_file(storage, pending_dir, error_dir, file)
85+
return res

edi_storage_oca/static/description/index.html

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<head>
44
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
55
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
6-
<title>README.rst</title>
6+
<title>EDI Storage backend support</title>
77
<style type="text/css">
88

99
/*
@@ -360,21 +360,16 @@
360360
</style>
361361
</head>
362362
<body>
363-
<div class="document">
363+
<div class="document" id="edi-storage-backend-support">
364+
<h1 class="title">EDI Storage backend support</h1>
364365

365-
366-
<a class="reference external image-reference" href="https://odoo-community.org/get-involved?utm_source=readme">
367-
<img alt="Odoo Community Association" src="https://odoo-community.org/readme-banner-image" />
368-
</a>
369-
<div class="section" id="edi-storage-backend-support">
370-
<h1>EDI Storage backend support</h1>
371366
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
372367
!! This file is generated by oca-gen-addon-readme !!
373368
!! changes will be overwritten. !!
374369
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
375370
!! source digest: sha256:ae52c0238c8bab278e4e2d6a48d86d4222672b893df1f171be046f3b7a3c58ae
376371
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
377-
<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/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/license-LGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/edi-framework/tree/18.0/edi_storage_oca"><img alt="OCA/edi-framework" src="https://img.shields.io/badge/github-OCA%2Fedi--framework-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/edi-framework-18-0/edi-framework-18-0-edi_storage_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/edi-framework&amp;target_branch=18.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/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/licence-LGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/edi-framework/tree/18.0/edi_storage_oca"><img alt="OCA/edi-framework" src="https://img.shields.io/badge/github-OCA%2Fedi--framework-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/edi-framework-18-0/edi-framework-18-0-edi_storage_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/edi-framework&amp;target_branch=18.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
378373
<p>Allow exchange files using storage backends from OCA/storage.</p>
379374
<p>This module adds a storage backend relation on the EDI backend. There
380375
you can configure the backend to be used (most often and SFTP) and the
@@ -411,34 +406,34 @@ <h1>EDI Storage backend support</h1>
411406
</ul>
412407
</div>
413408
<div class="section" id="usage">
414-
<h2><a class="toc-backref" href="#toc-entry-1">Usage</a></h2>
409+
<h1><a class="toc-backref" href="#toc-entry-1">Usage</a></h1>
415410
<p>Go to “EDI -&gt; EDI backend” then configure your backend to use a storage
416411
backend.</p>
417412
</div>
418413
<div class="section" id="known-issues-roadmap">
419-
<h2><a class="toc-backref" href="#toc-entry-2">Known issues / Roadmap</a></h2>
414+
<h1><a class="toc-backref" href="#toc-entry-2">Known issues / Roadmap</a></h1>
420415
<ul class="simple">
421416
<li>clean deprecated methods in the storage</li>
422417
</ul>
423418
</div>
424419
<div class="section" id="bug-tracker">
425-
<h2><a class="toc-backref" href="#toc-entry-3">Bug Tracker</a></h2>
420+
<h1><a class="toc-backref" href="#toc-entry-3">Bug Tracker</a></h1>
426421
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/edi-framework/issues">GitHub Issues</a>.
427422
In case of trouble, please check there if your issue has already been reported.
428423
If you spotted it first, help us to smash it by providing a detailed and welcomed
429424
<a class="reference external" href="https://github.com/OCA/edi-framework/issues/new?body=module:%20edi_storage_oca%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
430425
<p>Do not contact contributors directly about support or help with technical issues.</p>
431426
</div>
432427
<div class="section" id="credits">
433-
<h2><a class="toc-backref" href="#toc-entry-4">Credits</a></h2>
428+
<h1><a class="toc-backref" href="#toc-entry-4">Credits</a></h1>
434429
<div class="section" id="authors">
435-
<h3><a class="toc-backref" href="#toc-entry-5">Authors</a></h3>
430+
<h2><a class="toc-backref" href="#toc-entry-5">Authors</a></h2>
436431
<ul class="simple">
437432
<li>ACSONE</li>
438433
</ul>
439434
</div>
440435
<div class="section" id="contributors">
441-
<h3><a class="toc-backref" href="#toc-entry-6">Contributors</a></h3>
436+
<h2><a class="toc-backref" href="#toc-entry-6">Contributors</a></h2>
442437
<ul class="simple">
443438
<li>Simone Orsi &lt;<a class="reference external" href="mailto:simahawk&#64;gmail.com">simahawk&#64;gmail.com</a>&gt;</li>
444439
<li>Foram Shah &lt;<a class="reference external" href="mailto:foram.shah&#64;initos.com">foram.shah&#64;initos.com</a>&gt;</li>
@@ -451,12 +446,12 @@ <h3><a class="toc-backref" href="#toc-entry-6">Contributors</a></h3>
451446
</ul>
452447
</div>
453448
<div class="section" id="other-credits">
454-
<h3><a class="toc-backref" href="#toc-entry-7">Other credits</a></h3>
449+
<h2><a class="toc-backref" href="#toc-entry-7">Other credits</a></h2>
455450
<p>The migration of this module from 15.0 to 16.0 was financially supported
456451
by Camptocamp.</p>
457452
</div>
458453
<div class="section" id="maintainers">
459-
<h3><a class="toc-backref" href="#toc-entry-8">Maintainers</a></h3>
454+
<h2><a class="toc-backref" href="#toc-entry-8">Maintainers</a></h2>
460455
<p>This module is maintained by the OCA.</p>
461456
<a class="reference external image-reference" href="https://odoo-community.org">
462457
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
@@ -469,6 +464,5 @@ <h3><a class="toc-backref" href="#toc-entry-8">Maintainers</a></h3>
469464
</div>
470465
</div>
471466
</div>
472-
</div>
473467
</body>
474468
</html>

edi_storage_oca/tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
from . import test_edi_backend_storage
22
from . import test_exchange_type
3+
from . import test_edi_event_listenner
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# Copyright 2026 ForgeFlow S.L. (https://www.forgeflow.com)
2+
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
3+
4+
import base64
5+
from unittest import mock
6+
7+
from odoo.tests.common import TransactionCase
8+
9+
RECORD_MOCK_PATH = (
10+
"odoo.addons.edi_storage_oca.models.edi_exchange_record.EDIExchangeRecord"
11+
)
12+
BACKEND_MOCK_PATH = (
13+
"odoo.addons.edi_core_oca.models.edi_backend.EDIBackend._get_exec_handler"
14+
)
15+
16+
17+
class EDIBackendTestCase(TransactionCase):
18+
@classmethod
19+
def setUpClass(cls):
20+
super().setUpClass()
21+
cls.backend = cls.env.ref("edi_storage_oca.demo_edi_backend_storage")
22+
cls.exchange_type = cls.env.ref(
23+
"edi_storage_oca.demo_edi_type_csv_input", raise_if_not_found=False
24+
)
25+
if not cls.exchange_type:
26+
cls.exchange_type = cls.env["edi.exchange.type"].create(
27+
{
28+
"name": "Test CSV Input",
29+
"code": "test_csv_input",
30+
"direction": "input",
31+
"backend_id": cls.backend.id,
32+
"backend_type_id": cls.backend.backend_type_id.id,
33+
}
34+
)
35+
cls.record = cls.env["edi.exchange.record"].create(
36+
{
37+
"type_id": cls.exchange_type.id,
38+
"backend_id": cls.backend.id,
39+
"exchange_file": base64.b64encode(b"1234"),
40+
}
41+
)
42+
43+
def setUp(self):
44+
super().setUp()
45+
self.fake_move_args = None
46+
47+
def _mock_listener_move_file(self):
48+
def _move_file_mocked(record, storage, from_dir_str, to_dir_str, filename):
49+
if not self.fake_move_args:
50+
self.fake_move_args = [storage, from_dir_str, to_dir_str, filename]
51+
return True
52+
53+
return mock.patch(
54+
RECORD_MOCK_PATH + "._move_file",
55+
autospec=True,
56+
side_effect=_move_file_mocked,
57+
)
58+
59+
def _mock_process_handler(self):
60+
def _fake_exec_handler(backend, record, step):
61+
def handler(rec):
62+
if rec.env.context.get("test_break_process"):
63+
rec.write({"edi_exchange_state": "input_processed_error"})
64+
rec._notify_error("process_ko")
65+
return False
66+
rec.write({"edi_exchange_state": "input_processed"})
67+
rec._notify_done()
68+
return True
69+
70+
return handler
71+
72+
return mock.patch(
73+
BACKEND_MOCK_PATH, autospec=True, side_effect=_fake_exec_handler
74+
)
75+
76+
def test_01_process_record_success(self):
77+
with self._mock_listener_move_file(), self._mock_process_handler():
78+
self.record.write(
79+
{
80+
"edi_exchange_state": "input_received",
81+
"storage_id": self.backend.storage_id.id,
82+
}
83+
)
84+
self.record._set_file_content("TEST %d" % self.record.id)
85+
self.record.action_exchange_process()
86+
87+
storage, from_dir_str, to_dir_str, filename = self.fake_move_args
88+
self.assertEqual(storage, self.backend.storage_id)
89+
self.assertEqual(from_dir_str, self.backend.input_dir_pending)
90+
self.assertEqual(to_dir_str, self.backend.input_dir_done)
91+
self.assertEqual(filename, self.record.exchange_filename)
92+
93+
def test_02_process_record_with_error(self):
94+
with self._mock_listener_move_file(), self._mock_process_handler():
95+
self.record.write(
96+
{
97+
"edi_exchange_state": "input_received",
98+
"storage_id": self.backend.storage_id.id,
99+
}
100+
)
101+
self.record._set_file_content("TEST %d" % self.record.id)
102+
self.record.with_context(
103+
test_break_process="OOPS!"
104+
).action_exchange_process()
105+
106+
storage, from_dir_str, to_dir_str, filename = self.fake_move_args
107+
self.assertEqual(storage, self.backend.storage_id)
108+
self.assertEqual(from_dir_str, self.backend.input_dir_pending)
109+
self.assertEqual(to_dir_str, self.backend.input_dir_error)
110+
self.assertEqual(filename, self.record.exchange_filename)

0 commit comments

Comments
 (0)