Skip to content
Merged
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ addon | version | maintainers | summary
[edi_sale_ubl_output_oca](edi_sale_ubl_output_oca/) | 18.0.1.0.1 | | Configuration and special behaviors for EDI on sales.
[edi_state_oca](edi_state_oca/) | 18.0.1.0.3 | <a href='https://github.com/simahawk'><img src='https://github.com/simahawk.png' width='32' height='32' style='border-radius:50%;' alt='simahawk'/></a> | Allow to assign specific EDI states to related records.
[edi_stock_oca](edi_stock_oca/) | 18.0.1.0.1 | | Define EDI Configuration for Stock
[edi_storage_oca](edi_storage_oca/) | 18.0.1.0.4 | | Base module to allow exchanging files via storage backend (eg: SFTP).
[edi_storage_oca](edi_storage_oca/) | 18.0.1.1.0 | | Base module to allow exchanging files via storage backend (eg: SFTP).
[edi_storage_queue_oca](edi_storage_queue_oca/) | 18.0.1.0.0 | | Integrates EDI Storage with Queue
[edi_ubl_oca](edi_ubl_oca/) | 18.0.1.0.1 | <a href='https://github.com/simahawk'><img src='https://github.com/simahawk.png' width='32' height='32' style='border-radius:50%;' alt='simahawk'/></a> | Define EDI backend type for UBL.
[edi_webservice_oca](edi_webservice_oca/) | 18.0.1.0.2 | <a href='https://github.com/etobella'><img src='https://github.com/etobella.png' width='32' height='32' style='border-radius:50%;' alt='etobella'/></a> <a href='https://github.com/simahawk'><img src='https://github.com/simahawk.png' width='32' height='32' style='border-radius:50%;' alt='simahawk'/></a> | Defines webservice integration from EDI Exchange records
Expand Down
16 changes: 15 additions & 1 deletion edi_storage_oca/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ EDI Storage backend support
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:ca9c4e0d1ecd9744b5cfa517900a5dace2a206007360ce2c1299dc1da5910bfa
!! source digest: sha256:f0ff3ee30416f5a8090d6c738df2c392381b61bc0d104c7679a147ce3b503018
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
Expand Down Expand Up @@ -62,6 +62,20 @@ in/from the right place and update exchange records data accordingly.
.. contents::
:local:

Configuration
=============

This module has two **inactive** global ``edi.configuration`` records
that move the input file across the storage directories on
``on_edi_exchange_done`` / ``on_edi_exchange_error``:

- \*Storage: move input file, pending → done (fallback error → done)
- \*Storage: move input file, pending → error

Before enabling them you **must** set ``backend_id`` on the record:
otherwise the global match runs against every backend in the database,
including non-storage ones.

Usage
=====

Expand Down
3 changes: 2 additions & 1 deletion edi_storage_oca/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@
"summary": """
Base module to allow exchanging files via storage backend (eg: SFTP).
""",
"version": "18.0.1.0.4",
"version": "18.0.1.1.0",
"development_status": "Beta",
"license": "LGPL-3",
"website": "https://github.com/OCA/edi-framework",
"author": "ACSONE,Odoo Community Association (OCA)",
"depends": ["edi_core_oca", "fs_storage"],
"data": [
"data/cron.xml",
"data/edi_configuration.xml",
"security/ir_model_access.xml",
"views/edi_backend_views.xml",
],
Expand Down
29 changes: 29 additions & 0 deletions edi_storage_oca/data/edi_configuration.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<!--
IMPORTANT: these configurations are *inactive* by default. Before
activating them, set 'backend_id' to the specific storage backend
whose directories should be used, otherwise the snippet will not
know which input_dir_pending / input_dir_done / input_dir_error to
operate on.
-->
<record id="edi_conf_storage_move_on_done" model="edi.configuration">
<field name="name">Storage: move input file to done</field>
<field name="active" eval="False" />
<field name="is_global" eval="True" />
<field name="trigger_id" ref="edi_core_oca.edi_config_trigger_record_done" />
<field
name="snippet_do"
>record.backend_id._storage_on_edi_exchange_done(record)</field>
</record>

<record id="edi_conf_storage_move_on_error" model="edi.configuration">
<field name="name">Storage: move input file to error</field>
<field name="active" eval="False" />
<field name="is_global" eval="True" />
<field name="trigger_id" ref="edi_core_oca.edi_config_trigger_record_error" />
<field
name="snippet_do"
>record.backend_id._storage_on_edi_exchange_error(record)</field>
</record>
</odoo>
50 changes: 50 additions & 0 deletions edi_storage_oca/models/edi_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,53 @@ def _storage_new_exchange_record_vals(self, file_name):
"edi_exchange_state": "input_pending",
"storage_id": self.storage_id.id,
}

def _storage_on_edi_exchange_done(self, exchange_record):
"""
Move an input file from the pending dir to the done dir.

Intended to be invoked from a global 'edi.configuration' snippet
bound to the 'on_edi_exchange_done' trigger.
"""
self.ensure_one()
storage = exchange_record.storage_id
if exchange_record.direction != "input" or not storage:
return False
if not self.input_dir_done:
return False
file_name = exchange_record.exchange_filename
pending_dir = exchange_record.type_id._storage_fullpath(
self.input_dir_pending
).as_posix()
done_dir = exchange_record.type_id._storage_fullpath(
self.input_dir_done
).as_posix()
error_dir = exchange_record.type_id._storage_fullpath(
self.input_dir_error
).as_posix()
res = utils.move_file(storage, pending_dir, done_dir, file_name)
if not res and self.input_dir_error:
res = utils.move_file(storage, error_dir, done_dir, file_name)
return res

def _storage_on_edi_exchange_error(self, exchange_record):
"""
Move an input file from the pending dir to the error dir.

Intended to be invoked from a global 'edi.configuration' snippet
bound to the 'on_edi_exchange_error' trigger.
"""
self.ensure_one()
storage = exchange_record.storage_id
if exchange_record.direction != "input" or not storage:
return False
if not self.input_dir_error:
return False
file_name = exchange_record.exchange_filename
pending_dir = exchange_record.type_id._storage_fullpath(
self.input_dir_pending
).as_posix()
error_dir = exchange_record.type_id._storage_fullpath(
self.input_dir_error
).as_posix()
return utils.move_file(storage, pending_dir, error_dir, file_name)
10 changes: 10 additions & 0 deletions edi_storage_oca/readme/CONFIGURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
This module has two **inactive** global `edi.configuration` records
that move the input file across the storage directories on
`on_edi_exchange_done` / `on_edi_exchange_error`:

- *Storage: move input file, pending → done (fallback error → done)
- *Storage: move input file, pending → error

Before enabling them you **must** set `backend_id` on the record:
otherwise the global match runs against every backend in the database,
including non-storage ones.
48 changes: 31 additions & 17 deletions edi_storage_oca/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ <h1>EDI Storage backend support</h1>
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:ca9c4e0d1ecd9744b5cfa517900a5dace2a206007360ce2c1299dc1da5910bfa
!! source digest: sha256:f0ff3ee30416f5a8090d6c738df2c392381b61bc0d104c7679a147ce3b503018
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<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>
<p>Allow exchange files using storage backends from OCA/storage.</p>
Expand All @@ -398,47 +398,61 @@ <h1>EDI Storage backend support</h1>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#usage" id="toc-entry-1">Usage</a></li>
<li><a class="reference internal" href="#known-issues-roadmap" id="toc-entry-2">Known issues / Roadmap</a></li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-3">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-4">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-5">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-6">Contributors</a></li>
<li><a class="reference internal" href="#other-credits" id="toc-entry-7">Other credits</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-8">Maintainers</a></li>
<li><a class="reference internal" href="#configuration" id="toc-entry-1">Configuration</a></li>
<li><a class="reference internal" href="#usage" id="toc-entry-2">Usage</a></li>
<li><a class="reference internal" href="#known-issues-roadmap" id="toc-entry-3">Known issues / Roadmap</a></li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-4">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-5">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-6">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-7">Contributors</a></li>
<li><a class="reference internal" href="#other-credits" id="toc-entry-8">Other credits</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-9">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="configuration">
<h2><a class="toc-backref" href="#toc-entry-1">Configuration</a></h2>
<p>This module has two <strong>inactive</strong> global <tt class="docutils literal">edi.configuration</tt> records
that move the input file across the storage directories on
<tt class="docutils literal">on_edi_exchange_done</tt> / <tt class="docutils literal">on_edi_exchange_error</tt>:</p>
<ul class="simple">
<li>*Storage: move input file, pending → done (fallback error → done)</li>
<li>*Storage: move input file, pending → error</li>
</ul>
<p>Before enabling them you <strong>must</strong> set <tt class="docutils literal">backend_id</tt> on the record:
otherwise the global match runs against every backend in the database,
including non-storage ones.</p>
</div>
<div class="section" id="usage">
<h2><a class="toc-backref" href="#toc-entry-1">Usage</a></h2>
<h2><a class="toc-backref" href="#toc-entry-2">Usage</a></h2>
<p>Go to “EDI -&gt; EDI backend” then configure your backend to use a storage
backend.</p>
</div>
<div class="section" id="known-issues-roadmap">
<h2><a class="toc-backref" href="#toc-entry-2">Known issues / Roadmap</a></h2>
<h2><a class="toc-backref" href="#toc-entry-3">Known issues / Roadmap</a></h2>
<ul class="simple">
<li>clean deprecated methods in the storage</li>
</ul>
</div>
<div class="section" id="bug-tracker">
<h2><a class="toc-backref" href="#toc-entry-3">Bug Tracker</a></h2>
<h2><a class="toc-backref" href="#toc-entry-4">Bug Tracker</a></h2>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/edi-framework/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
<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>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h2><a class="toc-backref" href="#toc-entry-4">Credits</a></h2>
<h2><a class="toc-backref" href="#toc-entry-5">Credits</a></h2>
<div class="section" id="authors">
<h3><a class="toc-backref" href="#toc-entry-5">Authors</a></h3>
<h3><a class="toc-backref" href="#toc-entry-6">Authors</a></h3>
<ul class="simple">
<li>ACSONE</li>
</ul>
</div>
<div class="section" id="contributors">
<h3><a class="toc-backref" href="#toc-entry-6">Contributors</a></h3>
<h3><a class="toc-backref" href="#toc-entry-7">Contributors</a></h3>
<ul class="simple">
<li>Simone Orsi &lt;<a class="reference external" href="mailto:simahawk&#64;gmail.com">simahawk&#64;gmail.com</a>&gt;</li>
<li>Foram Shah &lt;<a class="reference external" href="mailto:foram.shah&#64;initos.com">foram.shah&#64;initos.com</a>&gt;</li>
Expand All @@ -451,12 +465,12 @@ <h3><a class="toc-backref" href="#toc-entry-6">Contributors</a></h3>
</ul>
</div>
<div class="section" id="other-credits">
<h3><a class="toc-backref" href="#toc-entry-7">Other credits</a></h3>
<h3><a class="toc-backref" href="#toc-entry-8">Other credits</a></h3>
<p>The migration of this module from 15.0 to 16.0 was financially supported
by Camptocamp.</p>
</div>
<div class="section" id="maintainers">
<h3><a class="toc-backref" href="#toc-entry-8">Maintainers</a></h3>
<h3><a class="toc-backref" href="#toc-entry-9">Maintainers</a></h3>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
Expand Down
1 change: 1 addition & 0 deletions edi_storage_oca/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
from . import test_edi_backend_storage
from . import test_event_listener
from . import test_exchange_type
88 changes: 88 additions & 0 deletions edi_storage_oca/tests/test_event_listener.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Copyright 2020 ACSONE
# @author: Simone Orsi <simahawk@gmail.com>
# Copyright 2026 ForgeFlow S.L. (https://www.forgeflow.com)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

import base64
from unittest import mock

from odoo_test_helper import FakeModelLoader

from odoo.addons.edi_core_oca.tests.common import EDIBackendCommonTestCase
from odoo.addons.edi_core_oca.tests.fake_models import EdiTestExecution

STORAGE_MOVE_FILE_PATH = "odoo.addons.edi_storage_oca.utils.move_file"


class TestStorageEventListener(EDIBackendCommonTestCase):
@classmethod
def _get_backend(cls):
return cls.env.ref("edi_storage_oca.demo_edi_backend_storage")

@classmethod
def setUpClass(cls):
super().setUpClass()
cls.conf_done = cls.env.ref("edi_storage_oca.edi_conf_storage_move_on_done")
cls.conf_error = cls.env.ref("edi_storage_oca.edi_conf_storage_move_on_error")
(cls.conf_done | cls.conf_error).write(
{"active": True, "backend_id": cls.backend.id}
)

vals = {
"model": cls.partner._name,
"res_id": cls.partner.id,
"exchange_file": base64.b64encode(b"1234"),
"storage_id": cls.backend.storage_id.id,
}
cls.record = cls.backend.create_record("test_csv_input", vals)

def setUp(self):
super().setUp()
self.loader = FakeModelLoader(self.env, self.__module__)
self.loader.backup_registry()

self.loader.update_registry((EdiTestExecution,))
fake_model = self.env["ir.model"].search(
[("model", "=", "edi.framework.test.execution")]
)
self.exchange_type_in.process_model_id = fake_model
self.exchange_type_in.input_validate_model_id = fake_model

def tearDown(self):
self.loader.restore_registry()
super().tearDown()

def _patch_move_file(self):
return mock.patch(STORAGE_MOVE_FILE_PATH, autospec=True, return_value=True)

def _expected_dir(self, raw_dir):
return self.exchange_type_in._storage_fullpath(raw_dir).as_posix()

def test_01_process_record_success(self):
self.record.write({"edi_exchange_state": "input_received"})
with self._patch_move_file() as mocked:
self.record.action_exchange_process()
mocked.assert_called_once()
storage, from_dir_str, to_dir_str, filename = mocked.call_args[0]
self.assertEqual(storage, self.backend.storage_id)
self.assertEqual(
from_dir_str, self._expected_dir(self.backend.input_dir_pending)
)
self.assertEqual(to_dir_str, self._expected_dir(self.backend.input_dir_done))
self.assertEqual(filename, self.record.exchange_filename)

def test_02_process_record_with_error(self):
self.record.write({"edi_exchange_state": "input_received"})
self.record._set_file_content("TEST %d" % self.record.id)
with self._patch_move_file() as mocked:
self.record.with_context(
test_break_process="OOPS! Something went wrong :("
).action_exchange_process()
mocked.assert_called_once()
storage, from_dir_str, to_dir_str, filename = mocked.call_args[0]
self.assertEqual(storage, self.backend.storage_id)
self.assertEqual(
from_dir_str, self._expected_dir(self.backend.input_dir_pending)
)
self.assertEqual(to_dir_str, self._expected_dir(self.backend.input_dir_error))
self.assertEqual(filename, self.record.exchange_filename)
12 changes: 12 additions & 0 deletions edi_storage_oca/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl)

import base64
import functools
import os
import re
from pathlib import PurePath


def add_file(storage, path, filedata, binary=False):
Expand Down Expand Up @@ -60,3 +62,13 @@ def move_files(storage, files, destination_path, **kw):
storage.fs.sep.join([destination_path, os.path.basename(file_path)]),
**kw,
)


# TODO: drop this helper once https://github.com/OCA/storage/pull/606 is merged.
def move_file(storage, from_dir_str, to_dir_str, filename):
src = (PurePath(from_dir_str) / filename).as_posix()
if not storage.fs.exists(src):
return False
dst = (PurePath(to_dir_str) / filename).as_posix()
storage.env.cr.postcommit.add(functools.partial(storage.fs.move, src, dst))
return True
Loading