From 4d348b578bb7b89263c688d4081f1d48786ea2e8 Mon Sep 17 00:00:00 2001
From: Simone Orsi
Date: Fri, 27 Nov 2020 10:34:24 +0100
Subject: [PATCH 01/77] Add edi_storage
---
edi_storage_oca/README.rst | 105 ++++
edi_storage_oca/__init__.py | 2 +
edi_storage_oca/__manifest__.py | 17 +
edi_storage_oca/components/__init__.py | 3 +
edi_storage_oca/components/base.py | 54 +++
edi_storage_oca/components/check.py | 83 ++++
edi_storage_oca/components/send.py | 39 ++
edi_storage_oca/demo/edi_backend_demo.xml | 14 +
edi_storage_oca/models/__init__.py | 1 +
edi_storage_oca/models/edi_backend.py | 67 +++
edi_storage_oca/readme/CONTRIBUTORS.rst | 1 +
edi_storage_oca/readme/DESCRIPTION.rst | 23 +
edi_storage_oca/readme/USAGE.rst | 1 +
edi_storage_oca/security/ir_model_access.xml | 12 +
edi_storage_oca/static/description/icon.png | Bin 0 -> 9455 bytes
edi_storage_oca/static/description/index.html | 450 ++++++++++++++++++
.../tests/_TODO_test_components_base.py | 43 ++
edi_storage_oca/tests/__init__.py | 1 +
edi_storage_oca/tests/common.py | 116 +++++
.../tests/test_edi_backend_storage.py | 204 ++++++++
edi_storage_oca/views/edi_backend_views.xml | 21 +
21 files changed, 1257 insertions(+)
create mode 100644 edi_storage_oca/README.rst
create mode 100644 edi_storage_oca/__init__.py
create mode 100644 edi_storage_oca/__manifest__.py
create mode 100644 edi_storage_oca/components/__init__.py
create mode 100644 edi_storage_oca/components/base.py
create mode 100644 edi_storage_oca/components/check.py
create mode 100644 edi_storage_oca/components/send.py
create mode 100644 edi_storage_oca/demo/edi_backend_demo.xml
create mode 100644 edi_storage_oca/models/__init__.py
create mode 100644 edi_storage_oca/models/edi_backend.py
create mode 100644 edi_storage_oca/readme/CONTRIBUTORS.rst
create mode 100644 edi_storage_oca/readme/DESCRIPTION.rst
create mode 100644 edi_storage_oca/readme/USAGE.rst
create mode 100644 edi_storage_oca/security/ir_model_access.xml
create mode 100644 edi_storage_oca/static/description/icon.png
create mode 100644 edi_storage_oca/static/description/index.html
create mode 100644 edi_storage_oca/tests/_TODO_test_components_base.py
create mode 100644 edi_storage_oca/tests/__init__.py
create mode 100644 edi_storage_oca/tests/common.py
create mode 100644 edi_storage_oca/tests/test_edi_backend_storage.py
create mode 100644 edi_storage_oca/views/edi_backend_views.xml
diff --git a/edi_storage_oca/README.rst b/edi_storage_oca/README.rst
new file mode 100644
index 000000000..fffe95837
--- /dev/null
+++ b/edi_storage_oca/README.rst
@@ -0,0 +1,105 @@
+===========================
+EDI Storage backend support
+===========================
+
+.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! This file is generated by oca-gen-addon-readme !!
+ !! changes will be overwritten. !!
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png
+ :target: https://odoo-community.org/page/development-status
+ :alt: Alpha
+.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
+ :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
+ :alt: License: AGPL-3
+.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fedi-lightgray.png?logo=github
+ :target: https://github.com/OCA/edi/tree/13.0/edi_storage
+ :alt: OCA/edi
+.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
+ :target: https://translation.odoo-community.org/projects/edi-13-0/edi-13-0-edi_storage
+ :alt: Translate me on Weblate
+.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
+ :target: https://runbot.odoo-community.org/runbot/226/13.0
+ :alt: Try me on Runbot
+
+|badge1| |badge2| |badge3| |badge4| |badge5|
+
+Allow exchange files using storage backends from `OCA/storage`.
+
+This module adds a storage backend relation on the EDI backend.
+There you can configure the backend to be used (most often and SFTP)
+and the paths where to read or put files.
+
+Often the convention when exchanging files via SFTP
+is to have one input forder (to receive files)
+and an output folder (to send files).
+
+Inside this folder you have this hierarchy::
+
+ input/output folder
+ |- pending
+ |- done
+ |- error
+
+* `pending` folder contains files that have been just sent
+* `done` folder contains files that have been processes successfully
+* `error` folder contains files with errors and cannot be processed
+
+The storage handlers take care of reading files and putting files
+in/from the right place and update exchange records data accordingly.
+
+.. IMPORTANT::
+ This is an alpha version, the data model and design can change at any time without warning.
+ Only for development or testing purpose, do not use in production.
+ `More details on development status `_
+
+**Table of contents**
+
+.. contents::
+ :local:
+
+Usage
+=====
+
+Go to "EDI -> EDI backend" then configure your backend to use a storage backend.
+
+Bug Tracker
+===========
+
+Bugs are tracked on `GitHub Issues `_.
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us smashing it by providing a detailed and welcomed
+`feedback `_.
+
+Do not contact contributors directly about support or help with technical issues.
+
+Credits
+=======
+
+Authors
+~~~~~~~
+
+* ACSONE
+
+Contributors
+~~~~~~~~~~~~
+
+* Simone Orsi
+
+Maintainers
+~~~~~~~~~~~
+
+This module is maintained by the OCA.
+
+.. image:: https://odoo-community.org/logo.png
+ :alt: Odoo Community Association
+ :target: https://odoo-community.org
+
+OCA, or the Odoo Community Association, is a nonprofit organization whose
+mission is to support the collaborative development of Odoo features and
+promote its widespread use.
+
+This module is part of the `OCA/edi `_ project on GitHub.
+
+You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/edi_storage_oca/__init__.py b/edi_storage_oca/__init__.py
new file mode 100644
index 000000000..f24d3e242
--- /dev/null
+++ b/edi_storage_oca/__init__.py
@@ -0,0 +1,2 @@
+from . import components
+from . import models
diff --git a/edi_storage_oca/__manifest__.py b/edi_storage_oca/__manifest__.py
new file mode 100644
index 000000000..9d4ff373f
--- /dev/null
+++ b/edi_storage_oca/__manifest__.py
@@ -0,0 +1,17 @@
+# Copyright 2020 ACSONE
+# @author: Simone Orsi
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+{
+ "name": "EDI Storage backend support",
+ "summary": """
+ Base module to allow exchanging files via storage backend (eg: SFTP).
+ """,
+ "version": "13.0.1.0.0",
+ "development_status": "Alpha",
+ "license": "AGPL-3",
+ "author": "ACSONE,Odoo Community Association (OCA)",
+ "depends": ["edi", "storage_backend", "component"],
+ "data": ["security/ir_model_access.xml", "views/edi_backend_views.xml"],
+ "demo": ["demo/edi_backend_demo.xml"],
+}
diff --git a/edi_storage_oca/components/__init__.py b/edi_storage_oca/components/__init__.py
new file mode 100644
index 000000000..9f64b792b
--- /dev/null
+++ b/edi_storage_oca/components/__init__.py
@@ -0,0 +1,3 @@
+from . import base
+from . import check
+from . import send
diff --git a/edi_storage_oca/components/base.py b/edi_storage_oca/components/base.py
new file mode 100644
index 000000000..2a337fed1
--- /dev/null
+++ b/edi_storage_oca/components/base.py
@@ -0,0 +1,54 @@
+# Copyright 2020 ACSONE
+# @author: Simone Orsi
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+from pathlib import PurePath
+
+from odoo.addons.component.core import AbstractComponent
+
+
+class EDIStorageSendComponentMixin(AbstractComponent):
+
+ _name = "edi.storage.component.mixin"
+
+ @property
+ def storage(self):
+ return self.backend.storage_id
+
+ def _dir_by_state(self, direction, state):
+ """Return remote directory path by direction and state.
+
+ :param direction: string stating direction of the exchange
+ :param state: string stating state of the exchange
+ :return: PurePath object
+ """
+ assert direction in ("input", "output")
+ assert state in ("pending", "done", "error")
+ return PurePath((self.backend[direction + "_dir_" + state] or "").strip(" /"))
+
+ def _remote_file_path(self, direction, state, filename):
+ """Return remote file path by direction and state for give filename.
+
+ :param direction: string stating direction of the exchange
+ :param state: string stating state of the exchange
+ :param filename: string for file name
+ :return: PurePath object
+ """
+ return self._dir_by_state(direction, state) / filename.strip("/ ")
+
+ def _get_remote_file(self, state, filename=None, binary=False):
+ """Get file for current exchange_record in the given destination state.
+
+ :param state: string ("pending", "done", "error")
+ :param filename: custom file name, exchange_record filename used by default
+ :return: remote file content as string
+ """
+ filename = filename or self.exchange_record.exchange_filename
+ path = self._remote_file_path(self.exchange_record.direction, state, filename)
+ try:
+ # TODO: support match via pattern (eg: filename-prefix-*)
+ # otherwise is impossible to retrieve input files and acks
+ # (the date will never match)
+ return self.storage.get(path.as_posix(), binary=binary)
+ except FileNotFoundError:
+ return None
diff --git a/edi_storage_oca/components/check.py b/edi_storage_oca/components/check.py
new file mode 100644
index 000000000..e76ec75aa
--- /dev/null
+++ b/edi_storage_oca/components/check.py
@@ -0,0 +1,83 @@
+# Copyright 2020 ACSONE
+# @author: Simone Orsi
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+import logging
+
+from odoo.tools import pycompat
+
+from odoo.addons.component.core import Component
+
+_logger = logging.getLogger(__name__)
+
+
+class EDIStorageCheckComponentMixin(Component):
+
+ _name = "edi.storage.component.check"
+ _inherit = [
+ "edi.component.check.mixin",
+ "edi.storage.component.mixin",
+ ]
+ _usage = "edi.storage.check"
+
+ def check(self):
+ return self._exchange_output_check()
+
+ def _exchange_output_check(self):
+ """Check status output exchange and update record.
+
+ 1. check if the file has been processed already (done)
+ 2. if yes, post message and exit
+ 3. if not, check for errors
+ 4. if no errors, return
+
+ :return: boolean
+ * False if there's nothing else to be done
+ * True if file still need action
+ """
+ if self._get_remote_file("done"):
+ _logger.info(
+ "%s done for: %s",
+ self.exchange_record.model,
+ self.exchange_record.name,
+ )
+ if (
+ not self.exchange_record.edi_exchange_state
+ == "output_sent_and_processed"
+ ):
+ self.exchange_record.edi_exchange_state = "output_sent_and_processed"
+ self.backend._notify_done(self.exchange_record)
+ if self.exchange_record.type_id.ack_needed:
+ self._exchange_output_handle_ack()
+ return False
+
+ error = self._get_remote_file("error")
+ if error:
+ _logger.info(
+ "%s error for: %s",
+ self.exchange_record.model,
+ self.exchange_record.name,
+ )
+ # Assume a text file will be placed there w/ the same name and error suffix
+ err_filename = self.exchange_record.exchange_filename + ".error"
+ error_report = self._get_remote_file("error", filename=err_filename)
+ if self.exchange_record.edi_exchange_state == "output_sent":
+ self.exchange_record.update(
+ {
+ "edi_exchange_state": "output_sent_and_error",
+ "exchange_error": pycompat.to_text(error_report),
+ }
+ )
+ self.backend._notify_error(self.exchange_record, "process_ko")
+ return False
+ return True
+
+ def _exchange_output_handle_ack(self):
+ ack_file = self._get_remote_file(
+ "done", filename=self.exchange_record.ack_filename
+ )
+ if ack_file:
+ self.exchange_record._set_file_content(ack_file, field_name="ack_file")
+ self.backend._notify_ack_received(self.exchange_record)
+ else:
+ self.backend._notify_ack_missing(self.exchange_record)
diff --git a/edi_storage_oca/components/send.py b/edi_storage_oca/components/send.py
new file mode 100644
index 000000000..4d3f37675
--- /dev/null
+++ b/edi_storage_oca/components/send.py
@@ -0,0 +1,39 @@
+# Copyright 2020 ACSONE
+# @author: Simone Orsi
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+from odoo.addons.component.core import Component
+
+
+class EDIStorageSendComponent(Component):
+
+ _name = "edi.storage.component.send"
+ _inherit = [
+ "edi.component.send.mixin",
+ "edi.storage.component.mixin",
+ ]
+ _usage = "edi.storage.send"
+
+ def send(self):
+ # If the file has been sent already, refresh its state
+ # TODO: double check if this is useless
+ # since the backend checks the state already
+ checker = self.component(usage="edi.storage.check")
+ result = checker.check()
+ if not result:
+ # all good here
+ return True
+
+ direction = self.exchange_record.direction
+ filename = self.exchange_record.exchange_filename
+ filedata = self.exchange_record.exchange_file
+ path = self._remote_file_path(direction, "pending", filename)
+ self.storage.add(path.as_posix(), filedata, binary=False)
+ # TODO: delegate this to generic storage backend
+ # except paramiko.ssh_exception.AuthenticationException:
+ # # TODO this exc handling should be moved to sftp backend IMO
+ # error = _("Authentication error")
+ # state = "error_on_send"
+ # TODO: catch other specific exceptions
+ # this will swallow all the exceptions!
+ return True
diff --git a/edi_storage_oca/demo/edi_backend_demo.xml b/edi_storage_oca/demo/edi_backend_demo.xml
new file mode 100644
index 000000000..d8ff5d972
--- /dev/null
+++ b/edi_storage_oca/demo/edi_backend_demo.xml
@@ -0,0 +1,14 @@
+
+
+
+ Storage Demo EDI backend
+
+
+ demo_in/pending
+ demo_in/done
+ demo_in/error
+ demo_out/pending
+ demo_out/done
+ demo_out/error
+
+
diff --git a/edi_storage_oca/models/__init__.py b/edi_storage_oca/models/__init__.py
new file mode 100644
index 000000000..4b2c54ac3
--- /dev/null
+++ b/edi_storage_oca/models/__init__.py
@@ -0,0 +1 @@
+from . import edi_backend
diff --git a/edi_storage_oca/models/edi_backend.py b/edi_storage_oca/models/edi_backend.py
new file mode 100644
index 000000000..5ba933639
--- /dev/null
+++ b/edi_storage_oca/models/edi_backend.py
@@ -0,0 +1,67 @@
+# Copyright 2020 ACSONE SA
+# @author Simone Orsi
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+from odoo import fields, models
+
+
+class EDIBackend(models.Model):
+
+ _inherit = "edi.backend"
+
+ storage_id = fields.Many2one(
+ string="Storage backend",
+ comodel_name="storage.backend",
+ help="Storage for in-out files",
+ ondelete="restrict",
+ )
+ """
+ We assume the exchanges happen it 2 ways (input, output)
+ and we have a hierarchy of directory like:
+
+ from_A_to_B
+ |- pending
+ |- done
+ |- error
+ from_B_to_A
+ |- pending
+ |- done
+ |- error
+
+ where A and B are the partners exchanging data and they are in turn
+ sender and receiver and vice versa.
+ """
+ # TODO: these paths should probably be by type instead
+ # Here we can maybe set a common root folder for this exchange.
+ input_dir_pending = fields.Char(
+ "Input pending directory", help="Path to folder for pending operations"
+ )
+ input_dir_done = fields.Char(
+ "Input done directory", help="Path to folder for doneful operations"
+ )
+ input_dir_error = fields.Char(
+ "Input error directory", help="Path to folder for error operations"
+ )
+ output_dir_pending = fields.Char(
+ "Output pending directory", help="Path to folder for pending operations"
+ )
+ output_dir_done = fields.Char(
+ "Output done directory", help="Path to folder for doneful operations"
+ )
+ output_dir_error = fields.Char(
+ "Output error directory", help="Path to folder for error operations"
+ )
+
+ def _get_component_usage_candidates(self, exchange_record, key):
+ candidates = super()._get_component_usage_candidates(exchange_record, key)
+ if not self.storage_id:
+ return candidates
+ storage_generic = "edi.storage.{}".format(key)
+ storage_by_backend_type = storage_generic + "." + self.storage_id.backend_type
+ type_code = exchange_record.type_id.code
+ return [
+ storage_by_backend_type + "." + type_code,
+ storage_by_backend_type,
+ storage_generic + "." + exchange_record.direction,
+ storage_generic,
+ ] + candidates
diff --git a/edi_storage_oca/readme/CONTRIBUTORS.rst b/edi_storage_oca/readme/CONTRIBUTORS.rst
new file mode 100644
index 000000000..f583948be
--- /dev/null
+++ b/edi_storage_oca/readme/CONTRIBUTORS.rst
@@ -0,0 +1 @@
+* Simone Orsi
diff --git a/edi_storage_oca/readme/DESCRIPTION.rst b/edi_storage_oca/readme/DESCRIPTION.rst
new file mode 100644
index 000000000..bdef14a8a
--- /dev/null
+++ b/edi_storage_oca/readme/DESCRIPTION.rst
@@ -0,0 +1,23 @@
+Allow exchange files using storage backends from `OCA/storage`.
+
+This module adds a storage backend relation on the EDI backend.
+There you can configure the backend to be used (most often and SFTP)
+and the paths where to read or put files.
+
+Often the convention when exchanging files via SFTP
+is to have one input forder (to receive files)
+and an output folder (to send files).
+
+Inside this folder you have this hierarchy::
+
+ input/output folder
+ |- pending
+ |- done
+ |- error
+
+* `pending` folder contains files that have been just sent
+* `done` folder contains files that have been processes successfully
+* `error` folder contains files with errors and cannot be processed
+
+The storage handlers take care of reading files and putting files
+in/from the right place and update exchange records data accordingly.
diff --git a/edi_storage_oca/readme/USAGE.rst b/edi_storage_oca/readme/USAGE.rst
new file mode 100644
index 000000000..a47c64c84
--- /dev/null
+++ b/edi_storage_oca/readme/USAGE.rst
@@ -0,0 +1 @@
+Go to "EDI -> EDI backend" then configure your backend to use a storage backend.
diff --git a/edi_storage_oca/security/ir_model_access.xml b/edi_storage_oca/security/ir_model_access.xml
new file mode 100644
index 000000000..b7d7b8498
--- /dev/null
+++ b/edi_storage_oca/security/ir_model_access.xml
@@ -0,0 +1,12 @@
+
+
+
+ access_storage_backend EDI manager
+
+
+
+
+
+
+
+
diff --git a/edi_storage_oca/static/description/icon.png b/edi_storage_oca/static/description/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d
GIT binary patch
literal 9455
zcmW++2RxMjAAjx~&dlBk9S+%}OXg)AGE&Cb*&}d0jUxM@u(PQx^-s)697TX`ehR4?GS^qbkof1cslKgkU)h65qZ9Oc=ml_0temigYLJfnz{IDzUf>bGs4N!v3=Z3jMq&A#7%rM5eQ#dc?k~!
zVpnB`o+K7|Al`Q_U;eD$B
zfJtP*jH`siUq~{KE)`jP2|#TUEFGRryE2`i0**z#*^6~AI|YzIWy$Cu#CSLW3q=GA
z6`?GZymC;dCPk~rBS%eCb`5OLr;RUZ;D`}um=H)BfVIq%7VhiMr)_#G0N#zrNH|__
zc+blN2UAB0=617@>_u;MPHN;P;N#YoE=)R#i$k_`UAA>WWCcEVMh~L_
zj--gtp&|K1#58Yz*AHCTMziU1Jzt_jG0I@qAOHsk$2}yTmVkBp_eHuY$A9)>P6o~I
z%aQ?!(GqeQ-Y+b0I(m9pwgi(IIZZzsbMv+9w{PFtd_<_(LA~0H(xz{=FhLB@(1&qHA5EJw1>>=%q2f&^X>IQ{!GJ4e9U
z&KlB)z(84HmNgm2hg2C0>WM{E(DdPr+EeU_N@57;PC2&DmGFW_9kP&%?X4}+xWi)(
z;)z%wI5>D4a*5XwD)P--sPkoY(a~WBw;E~AW`Yue4kFa^LM3X`8x|}ZUeMnqr}>kH
zG%WWW>3ml$Yez?i%)2pbKPI7?5o?hydokgQyZsNEr{a|mLdt;X2TX(#B1j35xPnPW
z*bMSSOauW>o;*=kO8ojw91VX!qoOQb)zHJ!odWB}d+*K?#sY_jqPdg{Sm2HdYzdEx
zOGVPhVRTGPtv0o}RfVP;Nd(|CB)I;*t&QO8h
zFfekr30S!-LHmV_Su-W+rEwYXJ^;6&3|L$mMC8*bQptyOo9;>Qb9Q9`ySe3%V$A*9
zeKEe+b0{#KWGp$F+tga)0RtI)nhMa-K@JS}2krK~n8vJ=Ngm?R!9G<~RyuU0d?nz#
z-5EK$o(!F?hmX*2Yt6+coY`6jGbb7tF#6nHA
zuKk=GGJ;ZwON1iAfG$E#Y7MnZVmrY|j0eVI(DN_MNFJmyZ|;w4tf@=CCDZ#5N_0K=
z$;R~bbk?}TpfDjfB&aiQ$VA}s?P}xPERJG{kxk5~R`iRS(SK5d+Xs9swCozZISbnS
zk!)I0>t=A<-^z(cmSFz3=jZ23u13X><0b)P)^1T_))Kr`e!-pb#q&J*Q`p+B6la%C
zuVl&0duN<;uOsB3%T9Fp8t{ED108)`y_~Hnd9AUX7h-H?jVuU|}My+C=TjH(jKz
zqMVr0re3S$H@t{zI95qa)+Crz*5Zj}Ao%4Z><+W(nOZd?gDnfNBC3>M8WE61$So|P
zVvqH0SNtDTcsUdzaMDpT=Ty0pDHHNL@Z0w$Y`XO
z2M-_r1S+GaH%pz#Uy0*w$Vdl=X=rQXEzO}d6J^R6zjM1u&c9vYLvLp?W7w(?np9x1
zE_0JSAJCPB%i7p*Wvg)pn5T`8k3-uR?*NT|J`eS#_#54p>!p(mLDvmc-3o0mX*mp_
zN*AeS<>#^-{S%W<*mz^!X$w_2dHWpcJ6^j64qFBft-o}o_Vx80o0>}Du;>kLts;$8
zC`7q$QI(dKYG`Wa8#wl@V4jVWBRGQ@1dr-hstpQL)Tl+aqVpGpbSfN>5i&QMXfiZ>
zaA?T1VGe?rpQ@;+pkrVdd{klI&jVS@I5_iz!=UMpTsa~mBga?1r}aRBm1WS;TT*s0f0lY=JBl66Upy)-k4J}lh=P^8(SXk~0xW=T9v*B|gzIhN
z>qsO7dFd~mgxAy4V?&)=5ieYq?zi?ZEoj)&2o)RLy=@hbCRcfT5jigwtQGE{L*8<@Yd{zg;CsL5mvzfDY}P-wos_6PfprFVaeqNE%h
zKZhLtcQld;ZD+>=nqN~>GvROfueSzJD&BE*}XfU|H&(FssBqY=hPCt`d
zH?@s2>I(|;fcW&YM6#V#!kUIP8$Nkdh0A(bEVj``-AAyYgwY~jB
zT|I7Bf@%;7aL7Wf4dZ%VqF$eiaC38OV6oy3Z#TER2G+fOCd9Iaoy6aLYbPTN{XRPz
z;U!V|vBf%H!}52L2gH_+j;`bTcQRXB+y9onc^wLm5wi3-Be}U>k_u>2Eg$=k!(l@I
zcCg+flakT2Nej3i0yn+g+}%NYb?ta;R?(g5SnwsQ49U8Wng8d|{B+lyRcEDvR3+`O{zfmrmvFrL6acVP%yG98X
zo&+VBg@px@i)%o?dG(`T;n*$S5*rnyiR#=wW}}GsAcfyQpE|>a{=$Hjg=-*_K;UtD
z#z-)AXwSRY?OPefw^iI+
z)AXz#PfEjlwTes|_{sB?4(O@fg0AJ^g8gP}ex9Ucf*@_^J(s_5jJV}c)s$`Myn|Kd
z$6>}#q^n{4vN@+Os$m7KV+`}c%4)4pv@06af4-x5#wj!KKb%caK{A&Y#Rfs
z-po?Dcb1({W=6FKIUirH&(yg=*6aLCekcKwyfK^JN5{wcA3nhO(o}SK#!CINhI`-I
z1)6&n7O&ZmyFMuNwvEic#IiOAwNkR=u5it{B9n2sAJV5pNhar=j5`*N!Na;c7g!l$
z3aYBqUkqqTJ=Re-;)s!EOeij=7SQZ3Hq}ZRds%IM*PtM$wV
z@;rlc*NRK7i3y5BETSKuumEN`Xu_8GP1Ri=OKQ$@I^ko8>H6)4rjiG5{VBM>B|%`&&s^)jS|-_95&yc=GqjNo{zFkw%%HHhS~e=s
zD#sfS+-?*t|J!+ozP6KvtOl!R)@@-z24}`9{QaVLD^9VCSR2b`b!KC#o;Ki<+wXB6
zx3&O0LOWcg4&rv4QG0)4yb}7BFSEg~=IR5#ZRj8kg}dS7_V&^%#Do==#`u
zpy6{ox?jWuR(;pg+f@mT>#HGWHAJRRDDDv~@(IDw&R>9643kK#HN`!1vBJHnC+RM&yIh8{gG2q
zA%e*U3|N0XSRa~oX-3EAneep)@{h2vvd3Xvy$7og(sayr@95+e6~Xvi1tUqnIxoIH
zVWo*OwYElb#uyW{Imam6f2rGbjR!Y3`#gPqkv57dB6K^wRGxc9B(t|aYDGS=m$&S!NmCtrMMaUg(c
zc2qC=2Z`EEFMW-me5B)24AqF*bV5Dr-M5ig(l-WPS%CgaPzs6p_gnCIvTJ=Y<6!gT
zVt@AfYCzjjsMEGi=rDQHo0yc;HqoRNnNFeWZgcm?f;cp(6CNylj36DoL(?TS7eU#+
z7&mfr#y))+CJOXQKUMZ7QIdS9@#-}7y2K1{8)cCt0~-X0O!O?Qx#E4Og+;A2SjalQ
zs7r?qn0H044=sDN$SRG$arw~n=+T_DNdSrarmu)V6@|?1-ZB#hRn`uilTGPJ@fqEy
zGt(f0B+^JDP&f=r{#Y_wi#AVDf-y!RIXU^0jXsFpf>=Ji*TeqSY!H~AMbJdCGLhC)
zn7Rx+sXw6uYj;WRYrLd^5IZq@6JI1C^YkgnedZEYy<&4(z%Q$5yv#Boo{AH8n$a
zhb4Y3PWdr269&?V%uI$xMcUrMzl=;w<_nm*qr=c3Rl@i5wWB;e-`t7D&c-mcQl7x!
zZWB`UGcw=Y2=}~wzrfLx=uet<;m3~=8I~ZRuzvMQUQdr+yTV|ATf1Uuomr__nDf=X
zZ3WYJtHp_ri(}SQAPjv+Y+0=fH4krOP@S&=zZ-t1jW1o@}z;xk8
z(Nz1co&El^HK^NrhVHa-_;&88vTU>_J33=%{if;BEY*J#1n59=07jrGQ#IP>@u#3A
z;!q+E1Rj3ZJ+!4bq9F8PXJ@yMgZL;>&gYA0%_Kbi8?S=XGM~dnQZQ!yBSgcZhY96H
zrWnU;k)qy`rX&&xlDyA%(a1Hhi5CWkmg(`Gb%m(HKi-7Z!LKGRP_B8@`7&hdDy5n=
z`OIxqxiVfX@OX1p(mQu>0Ai*v_cTMiw4qRt3~NBvr9oBy0)r>w3p~V0SCm=An6@3n)>@z!|o-$HvDK
z|3D2ZMJkLE5loMKl6R^ez@Zz%S$&mbeoqH5`Bb){Ei21q&VP)hWS2tjShfFtGE+$z
zzCR$P#uktu+#!w)cX!lWN1XU%K-r=s{|j?)Akf@q#3b#{6cZCuJ~gCxuMXRmI$nGtnH+-h
z+GEi!*X=AP<|fG`1>MBdTb?28JYc=fGvAi2I<$B(rs$;eoJCyR6_bc~p!XR@O-+sD
z=eH`-ye})I5ic1eL~TDmtfJ|8`0VJ*Yr=hNCd)G1p2MMz4C3^Mj?7;!w|Ly%JqmuW
zlIEW^Ft%z?*|fpXda>Jr^1noFZEwFgVV%|*XhH@acv8rdGxeEX{M$(vG{Zw+x(ei@
zmfXb22}8-?Fi`vo-YVrTH*C?a8%M=Hv9MqVH7H^J$KsD?>!SFZ;ZsvnHr_gn=7acz
z#W?0eCdVhVMWN12VV^$>WlQ?f;P^{(&pYTops|btm6aj>_Uz+hqpGwB)vWp0Cf5y<
zft8-je~nn?W11plq}N)4A{l8I7$!ks_x$PXW-2XaRFswX_BnF{R#6YIwMhAgd5F9X
zGmwdadS6(a^fjHtXg8=l?Rc0Sm%hk6E9!5cLVloEy4eh(=FwgP`)~I^5~pBEWo+F6
zSf2ncyMurJN91#cJTy_u8Y}@%!bq1RkGC~-bV@SXRd4F{R-*V`bS+6;W5vZ(&+I<9$;-V|eNfLa5n-6%
z2(}&uGRF;p92eS*sE*oR$@pexaqr*meB)VhmIg@h{uzkk$9~qh#cHhw#>O%)b@+(|
z^IQgqzuj~Sk(J;swEM-3TrJAPCq9k^^^`q{IItKBRXYe}e0Tdr=Huf7da3$l4PdpwWDop%^}n;dD#K4s#DYA8SHZ
z&1!riV4W4R7R#C))JH1~axJ)RYnM$$lIR%6fIVA@zV{XVyx}C+a-Dt8Y9M)^KU0+H
zR4IUb2CJ{Hg>CuaXtD50jB(_Tcx=Z$^WYu2u5kubqmwp%drJ6
z?Fo40g!Qd<-l=TQxqHEOuPX0;^z7iX?Ke^a%XT<13TA^5`4Xcw6D@Ur&VT&CUe0d}
z1GjOVF1^L@>O)l@?bD~$wzgf(nxX1OGD8fEV?TdJcZc2KoUe|oP1#=$$7ee|xbY)A
zDZq+cuTpc(fFdj^=!;{k03C69lMQ(|>uhRfRu%+!k&YOi-3|1QKB
z
z?n?eq1XP>p-IM$Z^C;2L3itnbJZAip*Zo0aw2bs8@(s^~*8T9go!%dHcAz2lM;`yp
zD=7&xjFV$S&5uDaiScyD?B-i1ze`+CoRtz`Wn+Zls4&}MO{@N!ufrzjG$B79)Y2d3tBk&)TxUTw@QS0TEL_?njX|@vq?Uz(nBFK5Pq7*xj#u*R&i|?7+6#
z+|r_n#SW&LXhtheZdah{ZVoqwyT{D>MC3nkFF#N)xLi{p7J1jXlmVeb;cP5?e(=f#
zuT7fvjSbjS781v?7{)-X3*?>tq?)Yd)~|1{BDS(pqC
zC}~H#WXlkUW*H5CDOo<)#x7%RY)A;ShGhI5s*#cRDA8YgqG(HeKDx+#(ZQ?386dv!
zlXCO)w91~Vw4AmOcATuV653fa9R$fyK8ul%rG
z-wfS
zihugoZyr38Im?Zuh6@RcF~t1anQu7>#lPpb#}4cOA!EM11`%f*07RqOVkmX{p~KJ9
z^zP;K#|)$`^Rb{rnHGH{~>1(fawV0*Z#)}M`m8-?ZJV<+e}s9wE#
z)l&az?w^5{)`S(%MRzxdNqrs1n*-=jS^_jqE*5XDrA0+VE`5^*p3CuM<&dZEeCjoz
zR;uu_H9ZPZV|fQq`Cyw4nscrVwi!fE6ciMmX$!_hN7uF;jjKG)d2@aC4ropY)8etW=xJvni)8eHi`H$%#zn^WJ5NLc-rqk|u&&4Z6fD_m&JfSI1Bvb?b<*n&sfl0^t
z=HnmRl`XrFvMKB%9}>PaA`m-fK6a0(8=qPkWS5bb4=v?XcWi&hRY?O5HdulRi4?fN
zlsJ*N-0Qw+Yic@s0(2uy%F@ib;GjXt01Fmx5XbRo6+n|pP(&nodMoap^z{~q
ziEeaUT@Mxe3vJSfI6?uLND(CNr=#^W<1b}jzW58bIfyWTDle$mmS(|x-0|2UlX+9k
zQ^EX7Nw}?EzVoBfT(-LT|=9N@^hcn-_p&sqG
z&*oVs2JSU+N4ZD`FhCAWaS;>|wH2G*Id|?pa#@>tyxX`+4HyIArWDvVrX)2WAOQff
z0qyHu&-S@i^MS-+j--!pr4fPBj~_8({~e1bfcl0wI1kaoN>mJL6KUPQm5N7lB(ui1
zE-o%kq)&djzWJ}ob<-GfDlkB;F31j-VHKvQUGQ3sp`CwyGJk_i!y^sD0fqC@$9|jO
zOqN!r!8-p==F@ZVP=U$qSpY(gQ0)59P1&t@y?5rvg<}E+GB}26NYPp4f2YFQrQtot5mn3wu_qprZ=>Ig-$
zbW26Ws~IgY>}^5w`vTB(G`PTZaDiGBo5o(tp)qli|NeV(
z@H_=R8V39rt5J5YB2Ky?4eJJ#b`_iBe2ot~6%7mLt5t8Vwi^Jy7|jWXqa3amOIoRb
zOr}WVFP--DsS`1WpN%~)t3R!arKF^Q$e12KEqU36AWwnCBICpH4XCsfnyrHr>$I$4
z!DpKX$OKLWarN7nv@!uIA+~RNO)l$$w}p(;b>mx8pwYvu;dD_unryX_NhT8*Tj>BTrTTL&!?O+%Rv;b?B??gSzdp?6Uug9{
zd@V08Z$BdI?fpoCS$)t4mg4rT8Q_I}h`0d-vYZ^|dOB*Q^S|xqTV*vIg?@fVFSmMpaw0qtTRbx}
z({Pg?#{2`sc9)M5N$*N|4;^t$+QP?#mov
zGVC@I*lBVrOU-%2y!7%)fAKjpEFsgQc4{amtiHb95KQEwvf<(3T<9-Zm$xIew#P22
zc2Ix|App^>v6(3L_MCU0d3W##AB0M~3D00EWoKZqsJYT(#@w$Y_H7G22M~ApVFTRHMI_3be)Lkn#0F*V8Pq
zc}`Cjy$bE;FJ6H7p=0y#R>`}-m4(0F>%@P|?7fx{=R^uFdISRnZ2W_xQhD{YuR3t<
z{6yxu=4~JkeA;|(J6_nv#>Nvs&FuLA&PW^he@t(UwFFE8)|a!R{`E`K`i^ZnyE4$k
z;(749Ix|oi$c3QbEJ3b~D_kQsPz~fIUKym($a_7dJ?o+40*OLl^{=&oq$<#Q(yyrp
z{J-FAniyAw9tPbe&IhQ|a`DqFTVQGQ&Gq3!C2==4x{6EJwiPZ8zub-iXoUtkJiG{}
zPaR&}_fn8_z~(=;5lD-aPWD3z8PZS@AaUiomF!G8I}Mf>e~0g#BelA-5#`cj;O5>N
Xviia!U7SGha1wx#SCgwmn*{w2TRX*I
literal 0
HcmV?d00001
diff --git a/edi_storage_oca/static/description/index.html b/edi_storage_oca/static/description/index.html
new file mode 100644
index 000000000..70dda24be
--- /dev/null
+++ b/edi_storage_oca/static/description/index.html
@@ -0,0 +1,450 @@
+
+
+
+
+
+
+EDI Storage backend support
+
+
+
+
+
EDI Storage backend support
+
+
+

+
Allow exchange files using storage backends from OCA/storage.
+
This module adds a storage backend relation on the EDI backend.
+There you can configure the backend to be used (most often and SFTP)
+and the paths where to read or put files.
+
Often the convention when exchanging files via SFTP
+is to have one input forder (to receive files)
+and an output folder (to send files).
+
Inside this folder you have this hierarchy:
+
+input/output folder
+ |- pending
+ |- done
+ |- error
+
+
+- pending folder contains files that have been just sent
+- done folder contains files that have been processes successfully
+- error folder contains files with errors and cannot be processed
+
+
The storage handlers take care of reading files and putting files
+in/from the right place and update exchange records data accordingly.
+
+
Important
+
This is an alpha version, the data model and design can change at any time without warning.
+Only for development or testing purpose, do not use in production.
+More details on development status
+
+
Table of contents
+
+
+
+
Go to “EDI -> EDI backend” then configure your backend to use a storage backend.
+
+
+
+
Bugs are tracked on GitHub Issues.
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us smashing it by providing a detailed and welcomed
+feedback.
+
Do not contact contributors directly about support or help with technical issues.
+
+
+
+
+
+
+
+
This module is maintained by the OCA.
+

+
OCA, or the Odoo Community Association, is a nonprofit organization whose
+mission is to support the collaborative development of Odoo features and
+promote its widespread use.
+
This module is part of the OCA/edi project on GitHub.
+
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
+
+
+
+
+
diff --git a/edi_storage_oca/tests/_TODO_test_components_base.py b/edi_storage_oca/tests/_TODO_test_components_base.py
new file mode 100644
index 000000000..ede7d946e
--- /dev/null
+++ b/edi_storage_oca/tests/_TODO_test_components_base.py
@@ -0,0 +1,43 @@
+# Copyright 2020 ACSONE
+# @author: Simone Orsi
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+from freezegun import freeze_time
+
+from .common import EDIBackendCommonTestCase
+
+
+class EDIBackendTestCase(EDIBackendCommonTestCase):
+ def test_remote_file_path(self):
+ to_test = (
+ (("input", "pending", "foo.csv"), "demo_in/pending/foo.csv"),
+ (("input", "done", "foo.csv"), "demo_in/done/foo.csv"),
+ (("input", "error", "foo.csv"), "demo_in/error/foo.csv"),
+ (("output", "pending", "foo.csv"), "demo_out/pending/foo.csv"),
+ (("output", "done", "foo.csv"), "demo_out/done/foo.csv"),
+ (("output", "error", "foo.csv"), "demo_out/error/foo.csv"),
+ )
+ for _args, expected in to_test:
+ path_obj = self.backend._remote_file_path(*_args)
+ self.assertEqual(path_obj.as_posix(), expected)
+
+ with self.assertRaises(AssertionError):
+ self.backend._remote_file_path("WHATEVER", "error", "foo.csv")
+
+ with self.assertRaises(AssertionError):
+ self.backend._remote_file_path("input", "WHATEVER", "foo.csv")
+
+ @freeze_time("2020-10-21 10:00:00")
+ def test_create_record(self):
+ vals = {
+ "model": self.partner._name,
+ "res_id": self.partner.id,
+ }
+ record = self.backend.create_record("test_csv_input", vals)
+ expected = {
+ "type_id": self.exchange_type_in.id,
+ "record_id": self.partner,
+ "edi_exchange_state": "new",
+ "exchange_filename": "EDI_EXC_TEST-test_csv_input-2020-10-21-10-00-00.csv",
+ }
+ self.assertRecordValues(record, [expected])
diff --git a/edi_storage_oca/tests/__init__.py b/edi_storage_oca/tests/__init__.py
new file mode 100644
index 000000000..d81ae344d
--- /dev/null
+++ b/edi_storage_oca/tests/__init__.py
@@ -0,0 +1 @@
+from . import test_edi_backend_storage
diff --git a/edi_storage_oca/tests/common.py b/edi_storage_oca/tests/common.py
new file mode 100644
index 000000000..2d02206ab
--- /dev/null
+++ b/edi_storage_oca/tests/common.py
@@ -0,0 +1,116 @@
+# Copyright 2020 ACSONE SA/NV ()
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+import base64
+import functools
+
+import mock
+
+from odoo.addons.edi.tests.common import EDIBackendCommonComponentTestCase
+
+
+class TestEDIStorageBase(EDIBackendCommonComponentTestCase):
+ @classmethod
+ def _get_backend(cls):
+ return cls.env.ref("edi_storage.demo_edi_backend_storage")
+
+ @classmethod
+ def _setup_records(cls):
+ super()._setup_records()
+ cls.filedata = base64.b64encode(b"This is a simple file")
+ vals = {
+ "model": cls.partner._name,
+ "res_id": cls.partner.id,
+ "exchange_file": cls.filedata,
+ }
+ cls.record = cls.backend.create_record("test_csv_output", vals)
+
+ cls.fakepath = "/tmp/{}".format(cls._filename(cls))
+ with open(cls.fakepath, "w+b") as fakefile:
+ fakefile.write(b"filecontent")
+
+ cls.fakepath_ack = "/tmp/{}.ack".format(cls._filename(cls))
+ with open(cls.fakepath_ack, "w+b") as fakefile:
+ fakefile.write(b"ACK filecontent")
+
+ cls.fakepath_error = "/tmp/{}.error".format(cls._filename(cls))
+ with open(cls.fakepath_error, "w+b") as fakefile:
+ fakefile.write(b"ERROR XYZ: line 2 broken on bla bla")
+
+ cls.checker = cls.backend._get_component(
+ ["edi.storage.check"], work_ctx={"exchange_record": cls.record}
+ )
+ cls.sender = cls.backend._get_component(
+ ["edi.storage.send"], work_ctx={"exchange_record": cls.record}
+ )
+
+ def setUp(self):
+ super().setUp()
+ self._storage_backend_calls = []
+
+ def _filename(self, record=None, ack=False):
+ record = record or self.record
+ return record.exchange_filename if not ack else record.ack_filename
+
+ def _file_fullpath(self, state, record=None, ack=False):
+ record = record or self.record
+ fname = self._filename(record, ack=ack)
+ if state == "error-report":
+ # Exception as we read from the same path but w/ error suffix
+ state = "error"
+ fname += ".error"
+ return (
+ self.checker._remote_file_path(record.direction, state, fname)
+ ).as_posix()
+
+ def _mocked_backend_get(self, mocked_paths, path, **kwargs):
+ self._storage_backend_calls.append(path)
+ if mocked_paths.get(path):
+ with open(mocked_paths.get(path), "rb") as remote_file:
+ return remote_file.read()
+ raise FileNotFoundError()
+
+ def _mocked_backend_add(self, path, data, **kwargs):
+ self._storage_backend_calls.append(path)
+
+ def _mock_storage_backend_get(self, mocked_paths):
+ mock_path = "odoo.addons.storage_backend.models.storage_backend.StorageBackend"
+ mocked = functools.partial(self._mocked_backend_get, mocked_paths)
+ return mock.patch(mock_path + ".get", mocked)
+
+ def _mock_storage_backend_add(self):
+ mock_path = "odoo.addons.storage_backend.models.storage_backend.StorageBackend"
+ return mock.patch(mock_path + ".add", self._mocked_backend_add)
+
+ def _test_result(
+ self, record, expected_values, expected_messages=None, state_paths=None,
+ ):
+ state_paths = state_paths or ("done", "pending", "error")
+ # Paths will be something like:
+ # [
+ # 'demo_out/pending/$filename.csv',
+ # 'demo_out/pending/$filename.csv',
+ # 'demo_out/error/$filename.csv',
+ # ]
+ for state in state_paths:
+ path = self._file_fullpath(state, record=record)
+ self.assertIn(path, self._storage_backend_calls)
+ self.assertRecordValues(record, [expected_values])
+ if expected_messages:
+ self.assertEqual(len(record.record.message_ids), len(expected_messages))
+ for msg_rec, expected in zip(record.record.message_ids, expected_messages):
+ self.assertIn(expected["message"], msg_rec.body)
+ self.assertIn("level-" + expected["level"], msg_rec.body)
+ # TODO: test content of file sent
+
+ def _test_send(self, record, mocked_paths=None):
+ with self._mock_storage_backend_add():
+ if mocked_paths:
+ with self._mock_storage_backend_get(mocked_paths):
+ self.backend.exchange_send(record)
+ else:
+ self.backend.exchange_send(record)
+
+ def _test_run_cron(self, mocked_paths):
+ with self._mock_storage_backend_add():
+ with self._mock_storage_backend_get(mocked_paths):
+ self.backend._cron_check_output_exchange_sync()
diff --git a/edi_storage_oca/tests/test_edi_backend_storage.py b/edi_storage_oca/tests/test_edi_backend_storage.py
new file mode 100644
index 000000000..de240c955
--- /dev/null
+++ b/edi_storage_oca/tests/test_edi_backend_storage.py
@@ -0,0 +1,204 @@
+# Copyright 2020 ACSONE SA/NV ()
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+import base64
+
+from freezegun import freeze_time
+
+from odoo.tools import mute_logger
+
+from .common import TestEDIStorageBase
+
+LOGGERS = (
+ "odoo.addons.edi_storage.components.check",
+ "odoo.addons.edi.models.edi_backend",
+)
+
+
+@freeze_time("2020-10-21 10:30:00")
+class TestEDIBackendOutput(TestEDIStorageBase):
+ @mute_logger(*LOGGERS)
+ def test_export_file_sent(self):
+ """Send, no errors."""
+ self.record.edi_exchange_state = "output_pending"
+ mocked_paths = {self._file_fullpath("pending"): self.fakepath}
+ # TODO: test send only w/out cron (make sure check works)
+ # self._test_send(self.record, mocked_paths=mocked_paths)
+ self._test_run_cron(mocked_paths)
+ self._test_result(
+ self.record,
+ {"edi_exchange_state": "output_sent"},
+ expected_messages=[
+ {
+ "message": self.record._exchange_status_message("send_ok"),
+ "level": "info",
+ }
+ ],
+ )
+
+ @mute_logger(*LOGGERS)
+ def test_export_file_already_done(self):
+ """Already sent, successfully."""
+ self.record.edi_exchange_state = "output_sent"
+ mocked_paths = {self._file_fullpath("done"): self.fakepath}
+ # TODO: test send only w/out cron (make sure check works)
+ self._test_run_cron(mocked_paths)
+ # As we simulate to find a file in `done` folder,
+ # we should get the final good state
+ # and only one call to ftp
+ self._test_result(
+ self.record,
+ {"edi_exchange_state": "output_sent_and_processed", "ack_file": False},
+ state_paths=("done",),
+ expected_messages=[
+ {
+ "message": self.record._exchange_status_message("process_ok"),
+ "level": "info",
+ }
+ ],
+ )
+
+ @mute_logger(*LOGGERS)
+ def test_export_file_already_done_ack_needed_not_found(self):
+ self.record.edi_exchange_state = "output_sent"
+ self.record.type_id.ack_needed = True
+ mocked_paths = {
+ self._file_fullpath("done"): self.fakepath,
+ }
+ self._test_run_cron(mocked_paths)
+ # No ack file found, warning message is posted
+ self._test_result(
+ self.record,
+ {"edi_exchange_state": "output_sent_and_processed"},
+ state_paths=("done",),
+ expected_messages=[
+ {
+ "message": self.record._exchange_status_message("ack_missing"),
+ "level": "warning",
+ },
+ {
+ "message": self.record._exchange_status_message("process_ok"),
+ "level": "info",
+ },
+ ],
+ )
+
+ @mute_logger(*LOGGERS)
+ def test_export_file_already_done_ack_needed_found(self):
+ self.record.edi_exchange_state = "output_sent"
+ self.record.type_id.ack_needed = True
+ mocked_paths = {
+ self._file_fullpath("done"): self.fakepath,
+ self._file_fullpath("done", ack=True): self.fakepath_ack,
+ }
+ self._test_run_cron(mocked_paths)
+ # Found ack file, set on record
+ self._test_result(
+ self.record,
+ {
+ "edi_exchange_state": "output_sent_and_processed",
+ "ack_file": base64.b64encode(b"ACK filecontent"),
+ },
+ state_paths=("done",),
+ expected_messages=[
+ {
+ "message": self.record._exchange_status_message("ack_received"),
+ "level": "info",
+ },
+ {
+ "message": self.record._exchange_status_message("process_ok"),
+ "level": "info",
+ },
+ ],
+ )
+
+ @mute_logger(*LOGGERS)
+ def test_already_sent_process_error(self):
+ """Already sent, error process."""
+ self.record.edi_exchange_state = "output_sent"
+ mocked_paths = {
+ self._file_fullpath("error"): self.fakepath,
+ self._file_fullpath("error-report"): self.fakepath_error,
+ }
+ self._test_run_cron(mocked_paths)
+ # As we simulate to find a file in `error` folder,
+ # we should get a call for: done, error and then the read of the report.
+ self._test_result(
+ self.record,
+ {
+ "edi_exchange_state": "output_sent_and_error",
+ "exchange_error": "ERROR XYZ: line 2 broken on bla bla",
+ },
+ state_paths=("done", "error", "error-report"),
+ expected_messages=[
+ {
+ "message": self.record._exchange_status_message("process_ko"),
+ "level": "error",
+ }
+ ],
+ )
+
+ @mute_logger(*LOGGERS)
+ def test_cron_full_flow(self):
+ """Already sent, update the state via cron."""
+ self.record.edi_exchange_state = "output_sent"
+ rec1 = self.record
+ partner2 = self.env.ref("base.res_partner_2")
+ partner3 = self.env.ref("base.res_partner_3")
+ rec2 = self.record.copy(
+ {
+ "model": partner2._name,
+ "res_id": partner2.id,
+ "exchange_filename": "rec2.csv",
+ }
+ )
+ rec3 = self.record.copy(
+ {
+ "model": partner3._name,
+ "res_id": partner3.id,
+ "exchange_filename": "rec3.csv",
+ "edi_exchange_state": "output_sent_and_error",
+ }
+ )
+ mocked_paths = {
+ self._file_fullpath("done", record=rec1): self.fakepath,
+ self._file_fullpath("error", record=rec2): self.fakepath,
+ self._file_fullpath("error-report", record=rec2): self.fakepath_error,
+ self._file_fullpath("done", record=rec3): self.fakepath,
+ }
+ self._test_run_cron(mocked_paths)
+ self._test_result(
+ rec1,
+ {"edi_exchange_state": "output_sent_and_processed"},
+ state_paths=("done",),
+ expected_messages=[
+ {
+ "message": rec1._exchange_status_message("process_ok"),
+ "level": "info",
+ }
+ ],
+ )
+ self._test_result(
+ rec2,
+ {
+ "edi_exchange_state": "output_sent_and_error",
+ "exchange_error": "ERROR XYZ: line 2 broken on bla bla",
+ },
+ state_paths=("done", "error", "error-report"),
+ expected_messages=[
+ {
+ "message": rec2._exchange_status_message("process_ko"),
+ "level": "error",
+ }
+ ],
+ )
+ self._test_result(
+ rec3,
+ {"edi_exchange_state": "output_sent_and_processed"},
+ state_paths=("done",),
+ expected_messages=[
+ {
+ "message": rec3._exchange_status_message("process_ok"),
+ "level": "info",
+ }
+ ],
+ )
diff --git a/edi_storage_oca/views/edi_backend_views.xml b/edi_storage_oca/views/edi_backend_views.xml
new file mode 100644
index 000000000..5c7916eb1
--- /dev/null
+++ b/edi_storage_oca/views/edi_backend_views.xml
@@ -0,0 +1,21 @@
+
+
+
+ edi.backend
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
From cb14b9057cf2a50fab4e55394a076fab9200ce59 Mon Sep 17 00:00:00 2001
From: Simone Orsi
Date: Fri, 27 Nov 2020 13:50:23 +0100
Subject: [PATCH 02/77] edi_storage: add base tests for component
---
.../tests/_TODO_test_components_base.py | 43 -------------------
edi_storage_oca/tests/__init__.py | 1 +
edi_storage_oca/tests/common.py | 10 +++--
edi_storage_oca/tests/test_components_base.py | 43 +++++++++++++++++++
4 files changed, 50 insertions(+), 47 deletions(-)
delete mode 100644 edi_storage_oca/tests/_TODO_test_components_base.py
create mode 100644 edi_storage_oca/tests/test_components_base.py
diff --git a/edi_storage_oca/tests/_TODO_test_components_base.py b/edi_storage_oca/tests/_TODO_test_components_base.py
deleted file mode 100644
index ede7d946e..000000000
--- a/edi_storage_oca/tests/_TODO_test_components_base.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# Copyright 2020 ACSONE
-# @author: Simone Orsi
-# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-
-from freezegun import freeze_time
-
-from .common import EDIBackendCommonTestCase
-
-
-class EDIBackendTestCase(EDIBackendCommonTestCase):
- def test_remote_file_path(self):
- to_test = (
- (("input", "pending", "foo.csv"), "demo_in/pending/foo.csv"),
- (("input", "done", "foo.csv"), "demo_in/done/foo.csv"),
- (("input", "error", "foo.csv"), "demo_in/error/foo.csv"),
- (("output", "pending", "foo.csv"), "demo_out/pending/foo.csv"),
- (("output", "done", "foo.csv"), "demo_out/done/foo.csv"),
- (("output", "error", "foo.csv"), "demo_out/error/foo.csv"),
- )
- for _args, expected in to_test:
- path_obj = self.backend._remote_file_path(*_args)
- self.assertEqual(path_obj.as_posix(), expected)
-
- with self.assertRaises(AssertionError):
- self.backend._remote_file_path("WHATEVER", "error", "foo.csv")
-
- with self.assertRaises(AssertionError):
- self.backend._remote_file_path("input", "WHATEVER", "foo.csv")
-
- @freeze_time("2020-10-21 10:00:00")
- def test_create_record(self):
- vals = {
- "model": self.partner._name,
- "res_id": self.partner.id,
- }
- record = self.backend.create_record("test_csv_input", vals)
- expected = {
- "type_id": self.exchange_type_in.id,
- "record_id": self.partner,
- "edi_exchange_state": "new",
- "exchange_filename": "EDI_EXC_TEST-test_csv_input-2020-10-21-10-00-00.csv",
- }
- self.assertRecordValues(record, [expected])
diff --git a/edi_storage_oca/tests/__init__.py b/edi_storage_oca/tests/__init__.py
index d81ae344d..b788a0071 100644
--- a/edi_storage_oca/tests/__init__.py
+++ b/edi_storage_oca/tests/__init__.py
@@ -1 +1,2 @@
from . import test_edi_backend_storage
+from . import test_components_base
diff --git a/edi_storage_oca/tests/common.py b/edi_storage_oca/tests/common.py
index 2d02206ab..a6af8cb00 100644
--- a/edi_storage_oca/tests/common.py
+++ b/edi_storage_oca/tests/common.py
@@ -7,6 +7,10 @@
from odoo.addons.edi.tests.common import EDIBackendCommonComponentTestCase
+STORAGE_BACKEND_MOCK_PATH = (
+ "odoo.addons.storage_backend.models.storage_backend.StorageBackend"
+)
+
class TestEDIStorageBase(EDIBackendCommonComponentTestCase):
@classmethod
@@ -73,13 +77,11 @@ def _mocked_backend_add(self, path, data, **kwargs):
self._storage_backend_calls.append(path)
def _mock_storage_backend_get(self, mocked_paths):
- mock_path = "odoo.addons.storage_backend.models.storage_backend.StorageBackend"
mocked = functools.partial(self._mocked_backend_get, mocked_paths)
- return mock.patch(mock_path + ".get", mocked)
+ return mock.patch(STORAGE_BACKEND_MOCK_PATH + ".get", mocked)
def _mock_storage_backend_add(self):
- mock_path = "odoo.addons.storage_backend.models.storage_backend.StorageBackend"
- return mock.patch(mock_path + ".add", self._mocked_backend_add)
+ return mock.patch(STORAGE_BACKEND_MOCK_PATH + ".add", self._mocked_backend_add)
def _test_result(
self, record, expected_values, expected_messages=None, state_paths=None,
diff --git a/edi_storage_oca/tests/test_components_base.py b/edi_storage_oca/tests/test_components_base.py
new file mode 100644
index 000000000..c2ba7275f
--- /dev/null
+++ b/edi_storage_oca/tests/test_components_base.py
@@ -0,0 +1,43 @@
+# Copyright 2020 ACSONE
+# @author: Simone Orsi
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+import mock
+
+from .common import STORAGE_BACKEND_MOCK_PATH, TestEDIStorageBase
+
+
+class EDIBackendTestCase(TestEDIStorageBase):
+ def test_remote_file_path(self):
+ to_test = (
+ (("input", "pending", "foo.csv"), "demo_in/pending/foo.csv"),
+ (("input", "done", "foo.csv"), "demo_in/done/foo.csv"),
+ (("input", "error", "foo.csv"), "demo_in/error/foo.csv"),
+ (("output", "pending", "foo.csv"), "demo_out/pending/foo.csv"),
+ (("output", "done", "foo.csv"), "demo_out/done/foo.csv"),
+ (("output", "error", "foo.csv"), "demo_out/error/foo.csv"),
+ )
+ for _args, expected in to_test:
+ path_obj = self.checker._remote_file_path(*_args)
+ self.assertEqual(path_obj.as_posix(), expected)
+
+ with self.assertRaises(AssertionError):
+ self.checker._remote_file_path("WHATEVER", "error", "foo.csv")
+
+ with self.assertRaises(AssertionError):
+ self.checker._remote_file_path("input", "WHATEVER", "foo.csv")
+
+ def test_get_remote_file(self):
+ with mock.patch(STORAGE_BACKEND_MOCK_PATH + ".get") as mocked:
+ self.checker._get_remote_file("pending")
+ mocked.assert_called_with(
+ "demo_out/pending/{}".format(self._filename(self.record)), binary=False
+ )
+ self.checker._get_remote_file("done")
+ mocked.assert_called_with(
+ "demo_out/done/{}".format(self._filename(self.record)), binary=False
+ )
+ self.checker._get_remote_file("error")
+ mocked.assert_called_with(
+ "demo_out/error/{}".format(self._filename(self.record)), binary=False
+ )
From 05a4b25239ad91499ac878bee5832f62ee4e9621 Mon Sep 17 00:00:00 2001
From: Simone Orsi
Date: Sun, 29 Nov 2020 18:58:26 +0100
Subject: [PATCH 03/77] edi_storage: fix messages test isolation
---
edi_storage_oca/tests/common.py | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/edi_storage_oca/tests/common.py b/edi_storage_oca/tests/common.py
index a6af8cb00..d49b443a5 100644
--- a/edi_storage_oca/tests/common.py
+++ b/edi_storage_oca/tests/common.py
@@ -98,8 +98,12 @@ def _test_result(
self.assertIn(path, self._storage_backend_calls)
self.assertRecordValues(record, [expected_values])
if expected_messages:
- self.assertEqual(len(record.record.message_ids), len(expected_messages))
- for msg_rec, expected in zip(record.record.message_ids, expected_messages):
+ # consider only edi related messages
+ messages = record.record.message_ids.filtered(
+ lambda x: "edi-exchange" in x.body
+ )
+ self.assertEqual(len(messages), len(expected_messages))
+ for msg_rec, expected in zip(messages, expected_messages):
self.assertIn(expected["message"], msg_rec.body)
self.assertIn("level-" + expected["level"], msg_rec.body)
# TODO: test content of file sent
From fcfdad9cb51bcc870267cafd145b6cd74565398b Mon Sep 17 00:00:00 2001
From: oca-travis
Date: Mon, 30 Nov 2020 11:50:12 +0000
Subject: [PATCH 04/77] [UPD] Update edi_storage.pot
---
edi_storage_oca/__manifest__.py | 2 +-
edi_storage_oca/i18n/edi_storage.pot | 77 ++++++++++++++++++++++++++++
2 files changed, 78 insertions(+), 1 deletion(-)
create mode 100644 edi_storage_oca/i18n/edi_storage.pot
diff --git a/edi_storage_oca/__manifest__.py b/edi_storage_oca/__manifest__.py
index 9d4ff373f..0d0500a2f 100644
--- a/edi_storage_oca/__manifest__.py
+++ b/edi_storage_oca/__manifest__.py
@@ -7,7 +7,7 @@
"summary": """
Base module to allow exchanging files via storage backend (eg: SFTP).
""",
- "version": "13.0.1.0.0",
+ "version": "13.0.1.1.0",
"development_status": "Alpha",
"license": "AGPL-3",
"author": "ACSONE,Odoo Community Association (OCA)",
diff --git a/edi_storage_oca/i18n/edi_storage.pot b/edi_storage_oca/i18n/edi_storage.pot
new file mode 100644
index 000000000..d6944e83b
--- /dev/null
+++ b/edi_storage_oca/i18n/edi_storage.pot
@@ -0,0 +1,77 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * edi_storage
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 13.0\n"
+"Report-Msgid-Bugs-To: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: edi_storage
+#: model:ir.model,name:edi_storage.model_edi_backend
+msgid "EDI Backend"
+msgstr ""
+
+#. module: edi_storage
+#: model:ir.model.fields,field_description:edi_storage.field_edi_backend__input_dir_done
+msgid "Input done directory"
+msgstr ""
+
+#. module: edi_storage
+#: model:ir.model.fields,field_description:edi_storage.field_edi_backend__input_dir_error
+msgid "Input error directory"
+msgstr ""
+
+#. module: edi_storage
+#: model:ir.model.fields,field_description:edi_storage.field_edi_backend__input_dir_pending
+msgid "Input pending directory"
+msgstr ""
+
+#. module: edi_storage
+#: model:ir.model.fields,field_description:edi_storage.field_edi_backend__output_dir_done
+msgid "Output done directory"
+msgstr ""
+
+#. module: edi_storage
+#: model:ir.model.fields,field_description:edi_storage.field_edi_backend__output_dir_error
+msgid "Output error directory"
+msgstr ""
+
+#. module: edi_storage
+#: model:ir.model.fields,field_description:edi_storage.field_edi_backend__output_dir_pending
+msgid "Output pending directory"
+msgstr ""
+
+#. module: edi_storage
+#: model:ir.model.fields,help:edi_storage.field_edi_backend__input_dir_done
+#: model:ir.model.fields,help:edi_storage.field_edi_backend__output_dir_done
+msgid "Path to folder for doneful operations"
+msgstr ""
+
+#. module: edi_storage
+#: model:ir.model.fields,help:edi_storage.field_edi_backend__input_dir_error
+#: model:ir.model.fields,help:edi_storage.field_edi_backend__output_dir_error
+msgid "Path to folder for error operations"
+msgstr ""
+
+#. module: edi_storage
+#: model:ir.model.fields,help:edi_storage.field_edi_backend__input_dir_pending
+#: model:ir.model.fields,help:edi_storage.field_edi_backend__output_dir_pending
+msgid "Path to folder for pending operations"
+msgstr ""
+
+#. module: edi_storage
+#: model:ir.model.fields,field_description:edi_storage.field_edi_backend__storage_id
+msgid "Storage backend"
+msgstr ""
+
+#. module: edi_storage
+#: model:ir.model.fields,help:edi_storage.field_edi_backend__storage_id
+msgid "Storage for in-out files"
+msgstr ""
From 6889af8e2a88f509b24d3b4719ac65b23cb9dcad Mon Sep 17 00:00:00 2001
From: Simone Orsi
Date: Wed, 2 Dec 2020 08:04:25 +0100
Subject: [PATCH 05/77] edi_storage: adapt to edi api changes
---
edi_storage_oca/__manifest__.py | 2 +-
edi_storage_oca/components/check.py | 8 ++++----
edi_storage_oca/tests/common.py | 4 ++--
3 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/edi_storage_oca/__manifest__.py b/edi_storage_oca/__manifest__.py
index 0d0500a2f..494055c6c 100644
--- a/edi_storage_oca/__manifest__.py
+++ b/edi_storage_oca/__manifest__.py
@@ -7,7 +7,7 @@
"summary": """
Base module to allow exchanging files via storage backend (eg: SFTP).
""",
- "version": "13.0.1.1.0",
+ "version": "13.0.1.2.0",
"development_status": "Alpha",
"license": "AGPL-3",
"author": "ACSONE,Odoo Community Association (OCA)",
diff --git a/edi_storage_oca/components/check.py b/edi_storage_oca/components/check.py
index e76ec75aa..835f979b8 100644
--- a/edi_storage_oca/components/check.py
+++ b/edi_storage_oca/components/check.py
@@ -46,7 +46,7 @@ def _exchange_output_check(self):
== "output_sent_and_processed"
):
self.exchange_record.edi_exchange_state = "output_sent_and_processed"
- self.backend._notify_done(self.exchange_record)
+ self.exchange_record._notify_done()
if self.exchange_record.type_id.ack_needed:
self._exchange_output_handle_ack()
return False
@@ -68,7 +68,7 @@ def _exchange_output_check(self):
"exchange_error": pycompat.to_text(error_report),
}
)
- self.backend._notify_error(self.exchange_record, "process_ko")
+ self.exchange_record._notify_error("process_ko")
return False
return True
@@ -78,6 +78,6 @@ def _exchange_output_handle_ack(self):
)
if ack_file:
self.exchange_record._set_file_content(ack_file, field_name="ack_file")
- self.backend._notify_ack_received(self.exchange_record)
+ self.exchange_record._notify_ack_received()
else:
- self.backend._notify_ack_missing(self.exchange_record)
+ self.exchange_record._notify_ack_missing()
diff --git a/edi_storage_oca/tests/common.py b/edi_storage_oca/tests/common.py
index d49b443a5..d55fe6774 100644
--- a/edi_storage_oca/tests/common.py
+++ b/edi_storage_oca/tests/common.py
@@ -40,10 +40,10 @@ def _setup_records(cls):
with open(cls.fakepath_error, "w+b") as fakefile:
fakefile.write(b"ERROR XYZ: line 2 broken on bla bla")
- cls.checker = cls.backend._get_component(
+ cls.checker = cls.backend._find_component(
["edi.storage.check"], work_ctx={"exchange_record": cls.record}
)
- cls.sender = cls.backend._get_component(
+ cls.sender = cls.backend._find_component(
["edi.storage.send"], work_ctx={"exchange_record": cls.record}
)
From a33e85f0cc9b096e5a8e159afad3a22d28588af6 Mon Sep 17 00:00:00 2001
From: Simone Orsi
Date: Wed, 2 Dec 2020 08:38:55 +0100
Subject: [PATCH 06/77] edi_storage: put ack handling on hold
Ack files should be treated as incoming records.
Receive files is not handled yet by the framework
and will be added soon.
---
edi_storage_oca/__manifest__.py | 2 +-
edi_storage_oca/components/check.py | 21 +++-
edi_storage_oca/tests/common.py | 4 +-
.../tests/test_edi_backend_storage.py | 109 +++++++++---------
4 files changed, 73 insertions(+), 63 deletions(-)
diff --git a/edi_storage_oca/__manifest__.py b/edi_storage_oca/__manifest__.py
index 494055c6c..838e6579a 100644
--- a/edi_storage_oca/__manifest__.py
+++ b/edi_storage_oca/__manifest__.py
@@ -7,7 +7,7 @@
"summary": """
Base module to allow exchanging files via storage backend (eg: SFTP).
""",
- "version": "13.0.1.2.0",
+ "version": "13.0.1.3.0",
"development_status": "Alpha",
"license": "AGPL-3",
"author": "ACSONE,Odoo Community Association (OCA)",
diff --git a/edi_storage_oca/components/check.py b/edi_storage_oca/components/check.py
index 835f979b8..965b01dfe 100644
--- a/edi_storage_oca/components/check.py
+++ b/edi_storage_oca/components/check.py
@@ -47,8 +47,6 @@ def _exchange_output_check(self):
):
self.exchange_record.edi_exchange_state = "output_sent_and_processed"
self.exchange_record._notify_done()
- if self.exchange_record.type_id.ack_needed:
- self._exchange_output_handle_ack()
return False
error = self._get_remote_file("error")
@@ -72,12 +70,23 @@ def _exchange_output_check(self):
return False
return True
+ # FIXME: this is not used ATM -> should be refactored
+ # into an incoming exchange.
+ # The backend will look for records needing an ack
+ # and generate and ack record.
def _exchange_output_handle_ack(self):
- ack_file = self._get_remote_file(
- "done", filename=self.exchange_record.ack_filename
- )
+ ack_type = self.exchange_record.type_id.ack_type_id
+ filename = ack_type._make_exchange_filename(self.exchange_record)
+ ack_file = self._get_remote_file("done", filename=filename)
if ack_file:
- self.exchange_record._set_file_content(ack_file, field_name="ack_file")
+ self.backend.create_record(
+ ack_type.code,
+ {
+ "parent_id": self.exchange_record.id,
+ "exchange_file": ack_file,
+ "edi_exchange_state": "input_received",
+ },
+ )
self.exchange_record._notify_ack_received()
else:
self.exchange_record._notify_ack_missing()
diff --git a/edi_storage_oca/tests/common.py b/edi_storage_oca/tests/common.py
index d55fe6774..992c4b44f 100644
--- a/edi_storage_oca/tests/common.py
+++ b/edi_storage_oca/tests/common.py
@@ -53,7 +53,9 @@ def setUp(self):
def _filename(self, record=None, ack=False):
record = record or self.record
- return record.exchange_filename if not ack else record.ack_filename
+ if ack:
+ record.type_id.ack_type_id._make_exchange_filename(record)
+ return record.exchange_filename
def _file_fullpath(self, state, record=None, ack=False):
record = record or self.record
diff --git a/edi_storage_oca/tests/test_edi_backend_storage.py b/edi_storage_oca/tests/test_edi_backend_storage.py
index de240c955..1eda66121 100644
--- a/edi_storage_oca/tests/test_edi_backend_storage.py
+++ b/edi_storage_oca/tests/test_edi_backend_storage.py
@@ -1,7 +1,5 @@
# Copyright 2020 ACSONE SA/NV ()
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-import base64
-
from freezegun import freeze_time
from odoo.tools import mute_logger
@@ -47,7 +45,7 @@ def test_export_file_already_done(self):
# and only one call to ftp
self._test_result(
self.record,
- {"edi_exchange_state": "output_sent_and_processed", "ack_file": False},
+ {"edi_exchange_state": "output_sent_and_processed"},
state_paths=("done",),
expected_messages=[
{
@@ -57,59 +55,60 @@ def test_export_file_already_done(self):
],
)
- @mute_logger(*LOGGERS)
- def test_export_file_already_done_ack_needed_not_found(self):
- self.record.edi_exchange_state = "output_sent"
- self.record.type_id.ack_needed = True
- mocked_paths = {
- self._file_fullpath("done"): self.fakepath,
- }
- self._test_run_cron(mocked_paths)
- # No ack file found, warning message is posted
- self._test_result(
- self.record,
- {"edi_exchange_state": "output_sent_and_processed"},
- state_paths=("done",),
- expected_messages=[
- {
- "message": self.record._exchange_status_message("ack_missing"),
- "level": "warning",
- },
- {
- "message": self.record._exchange_status_message("process_ok"),
- "level": "info",
- },
- ],
- )
+ # FIXME: ack should be handle as an incoming record (new machinery to be added)
+ # @mute_logger(*LOGGERS)
+ # def test_export_file_already_done_ack_needed_not_found(self):
+ # self.record.edi_exchange_state = "output_sent"
+ # self.record.type_id.ack_needed = True
+ # mocked_paths = {
+ # self._file_fullpath("done"): self.fakepath,
+ # }
+ # self._test_run_cron(mocked_paths)
+ # # No ack file found, warning message is posted
+ # self._test_result(
+ # self.record,
+ # {"edi_exchange_state": "output_sent_and_processed"},
+ # state_paths=("done",),
+ # expected_messages=[
+ # {
+ # "message": self.record._exchange_status_message("ack_missing"),
+ # "level": "warning",
+ # },
+ # {
+ # "message": self.record._exchange_status_message("process_ok"),
+ # "level": "info",
+ # },
+ # ],
+ # )
- @mute_logger(*LOGGERS)
- def test_export_file_already_done_ack_needed_found(self):
- self.record.edi_exchange_state = "output_sent"
- self.record.type_id.ack_needed = True
- mocked_paths = {
- self._file_fullpath("done"): self.fakepath,
- self._file_fullpath("done", ack=True): self.fakepath_ack,
- }
- self._test_run_cron(mocked_paths)
- # Found ack file, set on record
- self._test_result(
- self.record,
- {
- "edi_exchange_state": "output_sent_and_processed",
- "ack_file": base64.b64encode(b"ACK filecontent"),
- },
- state_paths=("done",),
- expected_messages=[
- {
- "message": self.record._exchange_status_message("ack_received"),
- "level": "info",
- },
- {
- "message": self.record._exchange_status_message("process_ok"),
- "level": "info",
- },
- ],
- )
+ # @mute_logger(*LOGGERS)
+ # def test_export_file_already_done_ack_needed_found(self):
+ # self.record.edi_exchange_state = "output_sent"
+ # self.record.type_id.ack_needed = True
+ # mocked_paths = {
+ # self._file_fullpath("done"): self.fakepath,
+ # self._file_fullpath("done", ack=True): self.fakepath_ack,
+ # }
+ # self._test_run_cron(mocked_paths)
+ # # Found ack file, set on record
+ # self._test_result(
+ # self.record,
+ # {
+ # "edi_exchange_state": "output_sent_and_processed",
+ # "ack_file": base64.b64encode(b"ACK filecontent"),
+ # },
+ # state_paths=("done",),
+ # expected_messages=[
+ # {
+ # "message": self.record._exchange_status_message("ack_received"),
+ # "level": "info",
+ # },
+ # {
+ # "message": self.record._exchange_status_message("process_ok"),
+ # "level": "info",
+ # },
+ # ],
+ # )
@mute_logger(*LOGGERS)
def test_already_sent_process_error(self):
From d6497ae890a2b87d8a0cacfce597c5ecabd1c2f0 Mon Sep 17 00:00:00 2001
From: Simone Orsi
Date: Mon, 8 Feb 2021 09:47:58 +0100
Subject: [PATCH 07/77] edi_storage: adapt to new components lookup
---
edi_storage_oca/__manifest__.py | 2 +-
edi_storage_oca/components/base.py | 14 ++-
edi_storage_oca/components/check.py | 2 +-
edi_storage_oca/components/send.py | 4 +-
edi_storage_oca/models/edi_backend.py | 29 ++++---
edi_storage_oca/tests/common.py | 8 +-
edi_storage_oca/tests/test_component_match.py | 86 +++++++++++++++++++
edi_storage_oca/tests/test_components_base.py | 2 +-
8 files changed, 129 insertions(+), 18 deletions(-)
create mode 100644 edi_storage_oca/tests/test_component_match.py
diff --git a/edi_storage_oca/__manifest__.py b/edi_storage_oca/__manifest__.py
index 838e6579a..20db765f1 100644
--- a/edi_storage_oca/__manifest__.py
+++ b/edi_storage_oca/__manifest__.py
@@ -7,7 +7,7 @@
"summary": """
Base module to allow exchanging files via storage backend (eg: SFTP).
""",
- "version": "13.0.1.3.0",
+ "version": "13.0.1.4.0",
"development_status": "Alpha",
"license": "AGPL-3",
"author": "ACSONE,Odoo Community Association (OCA)",
diff --git a/edi_storage_oca/components/base.py b/edi_storage_oca/components/base.py
index 2a337fed1..c6e67c148 100644
--- a/edi_storage_oca/components/base.py
+++ b/edi_storage_oca/components/base.py
@@ -7,9 +7,21 @@
from odoo.addons.component.core import AbstractComponent
-class EDIStorageSendComponentMixin(AbstractComponent):
+class EDIStorageComponentMixin(AbstractComponent):
_name = "edi.storage.component.mixin"
+ _inherit = "edi.component.mixin"
+ # Components having `_storage_backend_type` will have precedence.
+ # If the value is not set, generic components will be used.
+ _storage_backend_type = None
+
+ @classmethod
+ def _component_match(cls, work, usage=None, model_name=None, **kw):
+ res = super()._component_match(work, usage=usage, model_name=model_name, **kw)
+ storage_type = kw.get("storage_backend_type")
+ if storage_type and cls._storage_backend_type:
+ return cls._storage_backend_type == storage_type
+ return res
@property
def storage(self):
diff --git a/edi_storage_oca/components/check.py b/edi_storage_oca/components/check.py
index 965b01dfe..6e3cfa99a 100644
--- a/edi_storage_oca/components/check.py
+++ b/edi_storage_oca/components/check.py
@@ -18,7 +18,7 @@ class EDIStorageCheckComponentMixin(Component):
"edi.component.check.mixin",
"edi.storage.component.mixin",
]
- _usage = "edi.storage.check"
+ _usage = "storage.check"
def check(self):
return self._exchange_output_check()
diff --git a/edi_storage_oca/components/send.py b/edi_storage_oca/components/send.py
index 4d3f37675..b5ec45b5c 100644
--- a/edi_storage_oca/components/send.py
+++ b/edi_storage_oca/components/send.py
@@ -12,13 +12,13 @@ class EDIStorageSendComponent(Component):
"edi.component.send.mixin",
"edi.storage.component.mixin",
]
- _usage = "edi.storage.send"
+ _usage = "storage.send"
def send(self):
# If the file has been sent already, refresh its state
# TODO: double check if this is useless
# since the backend checks the state already
- checker = self.component(usage="edi.storage.check")
+ checker = self.component(usage="storage.check")
result = checker.check()
if not result:
# all good here
diff --git a/edi_storage_oca/models/edi_backend.py b/edi_storage_oca/models/edi_backend.py
index 5ba933639..63aa10249 100644
--- a/edi_storage_oca/models/edi_backend.py
+++ b/edi_storage_oca/models/edi_backend.py
@@ -52,16 +52,25 @@ class EDIBackend(models.Model):
"Output error directory", help="Path to folder for error operations"
)
+ _storage_actions = ("check", "send", "receive")
+
def _get_component_usage_candidates(self, exchange_record, key):
candidates = super()._get_component_usage_candidates(exchange_record, key)
- if not self.storage_id:
+ if not self.storage_id or key not in self._storage_actions:
return candidates
- storage_generic = "edi.storage.{}".format(key)
- storage_by_backend_type = storage_generic + "." + self.storage_id.backend_type
- type_code = exchange_record.type_id.code
- return [
- storage_by_backend_type + "." + type_code,
- storage_by_backend_type,
- storage_generic + "." + exchange_record.direction,
- storage_generic,
- ] + candidates
+ return ["storage.{}".format(key)] + candidates
+
+ def _component_match_attrs(self, exchange_record, key):
+ # Override to inject storage_backend_type
+ res = super()._component_match_attrs(exchange_record, key)
+ if not self.storage_id or key not in self._storage_actions:
+ return res
+ res["storage_backend_type"] = self.storage_id.backend_type
+ return res
+
+ def _component_sort_key(self, component_class):
+ res = super()._component_sort_key(component_class)
+ # Override to give precedence by storage_backend_type when needed.
+ if not self.storage_id:
+ return res
+ return (1 if component_class._storage_backend_type else 0,) + res
diff --git a/edi_storage_oca/tests/common.py b/edi_storage_oca/tests/common.py
index 992c4b44f..9cd239422 100644
--- a/edi_storage_oca/tests/common.py
+++ b/edi_storage_oca/tests/common.py
@@ -41,10 +41,14 @@ def _setup_records(cls):
fakefile.write(b"ERROR XYZ: line 2 broken on bla bla")
cls.checker = cls.backend._find_component(
- ["edi.storage.check"], work_ctx={"exchange_record": cls.record}
+ cls.partner._name,
+ ["storage.check"],
+ work_ctx={"exchange_record": cls.record},
)
cls.sender = cls.backend._find_component(
- ["edi.storage.send"], work_ctx={"exchange_record": cls.record}
+ cls.partner._name,
+ ["storage.send"],
+ work_ctx={"exchange_record": cls.record},
)
def setUp(self):
diff --git a/edi_storage_oca/tests/test_component_match.py b/edi_storage_oca/tests/test_component_match.py
new file mode 100644
index 000000000..4cd1bad87
--- /dev/null
+++ b/edi_storage_oca/tests/test_component_match.py
@@ -0,0 +1,86 @@
+# Copyright 2020 ACSONE
+# @author: Simone Orsi
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+from odoo.addons.component.core import Component
+from odoo.addons.edi.tests.common import EDIBackendCommonComponentRegistryTestCase
+
+
+class EDIBackendTestCase(EDIBackendCommonComponentRegistryTestCase):
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ cls._load_module_components(cls, "edi_storage")
+
+ @classmethod
+ def _get_backend(cls):
+ return cls.env.ref("edi_storage.demo_edi_backend_storage")
+
+ def test_component_match(self):
+ """Lookup with special match method."""
+
+ class SFTPCheck(Component):
+ _name = "sftp.check"
+ _inherit = "edi.storage.component.check"
+ _usage = "storage.check"
+ # _backend_type = "demo_backend"
+ _storage_backend_type = "sftp"
+
+ class SFTPSend(Component):
+ _name = "sftp.send"
+ _inherit = "edi.storage.component.send"
+ _usage = "storage.send"
+ # _backend_type = "demo_backend"
+ _storage_backend_type = "sftp"
+
+ class S3Check(Component):
+ _name = "s3.check"
+ _inherit = "edi.storage.component.check"
+ _usage = "storage.check"
+ # _exchange_type = "test_csv_output"
+ _storage_backend_type = "s3"
+
+ class S3Send(Component):
+ _name = "s3.send"
+ _inherit = "edi.storage.component.send"
+ _usage = "storage.send"
+ # _exchange_type = "test_csv_output"
+ _storage_backend_type = "s3"
+
+ self._build_components(SFTPCheck, SFTPSend, S3Check, S3Send)
+
+ component = self.backend._find_component(
+ "res.partner",
+ ["storage.check"],
+ backend_type="demo_backend",
+ exchange_type="test_csv_output",
+ storage_backend_type="s3",
+ )
+ self.assertEqual(component._name, S3Check._name)
+
+ component = self.backend._find_component(
+ "res.partner",
+ ["storage.check"],
+ backend_type="demo_backend",
+ exchange_type="test_csv_output",
+ storage_backend_type="sftp",
+ )
+ self.assertEqual(component._name, SFTPCheck._name)
+
+ component = self.backend._find_component(
+ "res.partner",
+ ["storage.send"],
+ backend_type="demo_backend",
+ exchange_type="test_csv_output",
+ storage_backend_type="sftp",
+ )
+ self.assertEqual(component._name, SFTPSend._name)
+
+ component = self.backend._find_component(
+ "res.partner",
+ ["storage.send"],
+ backend_type="demo_backend",
+ exchange_type="test_csv_output",
+ storage_backend_type="s3",
+ )
+ self.assertEqual(component._name, S3Send._name)
diff --git a/edi_storage_oca/tests/test_components_base.py b/edi_storage_oca/tests/test_components_base.py
index c2ba7275f..27969a605 100644
--- a/edi_storage_oca/tests/test_components_base.py
+++ b/edi_storage_oca/tests/test_components_base.py
@@ -7,7 +7,7 @@
from .common import STORAGE_BACKEND_MOCK_PATH, TestEDIStorageBase
-class EDIBackendTestCase(TestEDIStorageBase):
+class EDIStorageComponentTestCase(TestEDIStorageBase):
def test_remote_file_path(self):
to_test = (
(("input", "pending", "foo.csv"), "demo_in/pending/foo.csv"),
From 543db9b251a7e4e7164632394ae5627e06bf6498 Mon Sep 17 00:00:00 2001
From: Enric Tobella
Date: Wed, 10 Mar 2021 13:56:20 +0100
Subject: [PATCH 08/77] [CHG] edi: Use more permissive licence: AGPL-> LGPL
---
edi_storage_oca/README.rst | 8 ++++----
edi_storage_oca/__manifest__.py | 4 ++--
edi_storage_oca/components/base.py | 2 +-
edi_storage_oca/components/check.py | 2 +-
edi_storage_oca/components/send.py | 2 +-
edi_storage_oca/models/edi_backend.py | 2 +-
edi_storage_oca/static/description/index.html | 2 +-
edi_storage_oca/tests/common.py | 2 +-
edi_storage_oca/tests/test_component_match.py | 2 +-
edi_storage_oca/tests/test_components_base.py | 2 +-
edi_storage_oca/tests/test_edi_backend_storage.py | 2 +-
11 files changed, 15 insertions(+), 15 deletions(-)
diff --git a/edi_storage_oca/README.rst b/edi_storage_oca/README.rst
index fffe95837..cc387fb2d 100644
--- a/edi_storage_oca/README.rst
+++ b/edi_storage_oca/README.rst
@@ -10,9 +10,9 @@ EDI Storage backend support
.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png
:target: https://odoo-community.org/page/development-status
:alt: Alpha
-.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
- :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
- :alt: License: AGPL-3
+.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png
+ :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
+ :alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fedi-lightgray.png?logo=github
:target: https://github.com/OCA/edi/tree/13.0/edi_storage
:alt: OCA/edi
@@ -23,7 +23,7 @@ EDI Storage backend support
:target: https://runbot.odoo-community.org/runbot/226/13.0
:alt: Try me on Runbot
-|badge1| |badge2| |badge3| |badge4| |badge5|
+|badge1| |badge2| |badge3| |badge4| |badge5|
Allow exchange files using storage backends from `OCA/storage`.
diff --git a/edi_storage_oca/__manifest__.py b/edi_storage_oca/__manifest__.py
index 20db765f1..503ae5cfe 100644
--- a/edi_storage_oca/__manifest__.py
+++ b/edi_storage_oca/__manifest__.py
@@ -1,6 +1,6 @@
# Copyright 2020 ACSONE
# @author: Simone Orsi
-# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
{
"name": "EDI Storage backend support",
@@ -9,7 +9,7 @@
""",
"version": "13.0.1.4.0",
"development_status": "Alpha",
- "license": "AGPL-3",
+ "license": "LGPL-3",
"author": "ACSONE,Odoo Community Association (OCA)",
"depends": ["edi", "storage_backend", "component"],
"data": ["security/ir_model_access.xml", "views/edi_backend_views.xml"],
diff --git a/edi_storage_oca/components/base.py b/edi_storage_oca/components/base.py
index c6e67c148..01d0c8b2c 100644
--- a/edi_storage_oca/components/base.py
+++ b/edi_storage_oca/components/base.py
@@ -1,6 +1,6 @@
# Copyright 2020 ACSONE
# @author: Simone Orsi
-# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from pathlib import PurePath
diff --git a/edi_storage_oca/components/check.py b/edi_storage_oca/components/check.py
index 6e3cfa99a..878cfffcb 100644
--- a/edi_storage_oca/components/check.py
+++ b/edi_storage_oca/components/check.py
@@ -1,6 +1,6 @@
# Copyright 2020 ACSONE
# @author: Simone Orsi
-# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
import logging
diff --git a/edi_storage_oca/components/send.py b/edi_storage_oca/components/send.py
index b5ec45b5c..cbb6ed309 100644
--- a/edi_storage_oca/components/send.py
+++ b/edi_storage_oca/components/send.py
@@ -1,6 +1,6 @@
# Copyright 2020 ACSONE
# @author: Simone Orsi
-# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from odoo.addons.component.core import Component
diff --git a/edi_storage_oca/models/edi_backend.py b/edi_storage_oca/models/edi_backend.py
index 63aa10249..2437a6bc1 100644
--- a/edi_storage_oca/models/edi_backend.py
+++ b/edi_storage_oca/models/edi_backend.py
@@ -1,6 +1,6 @@
# Copyright 2020 ACSONE SA
# @author Simone Orsi
-# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from odoo import fields, models
diff --git a/edi_storage_oca/static/description/index.html b/edi_storage_oca/static/description/index.html
index 70dda24be..b034d89fe 100644
--- a/edi_storage_oca/static/description/index.html
+++ b/edi_storage_oca/static/description/index.html
@@ -367,7 +367,7 @@ EDI Storage backend support
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
-

+

Allow exchange files using storage backends from OCA/storage.
This module adds a storage backend relation on the EDI backend.
There you can configure the backend to be used (most often and SFTP)
diff --git a/edi_storage_oca/tests/common.py b/edi_storage_oca/tests/common.py
index 9cd239422..134c807a9 100644
--- a/edi_storage_oca/tests/common.py
+++ b/edi_storage_oca/tests/common.py
@@ -1,5 +1,5 @@
# Copyright 2020 ACSONE SA/NV ()
-# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
import base64
import functools
diff --git a/edi_storage_oca/tests/test_component_match.py b/edi_storage_oca/tests/test_component_match.py
index 4cd1bad87..e422c9698 100644
--- a/edi_storage_oca/tests/test_component_match.py
+++ b/edi_storage_oca/tests/test_component_match.py
@@ -1,6 +1,6 @@
# Copyright 2020 ACSONE
# @author: Simone Orsi
-# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from odoo.addons.component.core import Component
from odoo.addons.edi.tests.common import EDIBackendCommonComponentRegistryTestCase
diff --git a/edi_storage_oca/tests/test_components_base.py b/edi_storage_oca/tests/test_components_base.py
index 27969a605..57abd1385 100644
--- a/edi_storage_oca/tests/test_components_base.py
+++ b/edi_storage_oca/tests/test_components_base.py
@@ -1,6 +1,6 @@
# Copyright 2020 ACSONE
# @author: Simone Orsi
-# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
import mock
diff --git a/edi_storage_oca/tests/test_edi_backend_storage.py b/edi_storage_oca/tests/test_edi_backend_storage.py
index 1eda66121..f49138fc0 100644
--- a/edi_storage_oca/tests/test_edi_backend_storage.py
+++ b/edi_storage_oca/tests/test_edi_backend_storage.py
@@ -1,5 +1,5 @@
# Copyright 2020 ACSONE SA/NV ()
-# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
from freezegun import freeze_time
from odoo.tools import mute_logger
From 698aee32a3a54a4aed708cf5f28e0521f98a6cd1 Mon Sep 17 00:00:00 2001
From: OCA-git-bot
Date: Mon, 15 Mar 2021 21:46:44 +0000
Subject: [PATCH 09/77] [UPD] README.rst
---
edi_storage_oca/README.rst | 2 +-
edi_storage_oca/__manifest__.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/edi_storage_oca/README.rst b/edi_storage_oca/README.rst
index cc387fb2d..0cceccfbf 100644
--- a/edi_storage_oca/README.rst
+++ b/edi_storage_oca/README.rst
@@ -23,7 +23,7 @@ EDI Storage backend support
:target: https://runbot.odoo-community.org/runbot/226/13.0
:alt: Try me on Runbot
-|badge1| |badge2| |badge3| |badge4| |badge5|
+|badge1| |badge2| |badge3| |badge4| |badge5|
Allow exchange files using storage backends from `OCA/storage`.
diff --git a/edi_storage_oca/__manifest__.py b/edi_storage_oca/__manifest__.py
index 503ae5cfe..0df82391d 100644
--- a/edi_storage_oca/__manifest__.py
+++ b/edi_storage_oca/__manifest__.py
@@ -7,7 +7,7 @@
"summary": """
Base module to allow exchanging files via storage backend (eg: SFTP).
""",
- "version": "13.0.1.4.0",
+ "version": "13.0.1.4.1",
"development_status": "Alpha",
"license": "LGPL-3",
"author": "ACSONE,Odoo Community Association (OCA)",
From 20f3289a4e7c9367613a5d5a1a41cfcbd9681ec1 Mon Sep 17 00:00:00 2001
From: Enric Tobella
Date: Wed, 7 Apr 2021 10:35:07 +0200
Subject: [PATCH 10/77] [FIX] edi_storage: sort key was not working properly
---
edi_storage_oca/__manifest__.py | 2 +-
edi_storage_oca/models/edi_backend.py | 4 +++-
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/edi_storage_oca/__manifest__.py b/edi_storage_oca/__manifest__.py
index 0df82391d..0243ab8cf 100644
--- a/edi_storage_oca/__manifest__.py
+++ b/edi_storage_oca/__manifest__.py
@@ -7,7 +7,7 @@
"summary": """
Base module to allow exchanging files via storage backend (eg: SFTP).
""",
- "version": "13.0.1.4.1",
+ "version": "13.0.1.5.0",
"development_status": "Alpha",
"license": "LGPL-3",
"author": "ACSONE,Odoo Community Association (OCA)",
diff --git a/edi_storage_oca/models/edi_backend.py b/edi_storage_oca/models/edi_backend.py
index 2437a6bc1..0ba578fa0 100644
--- a/edi_storage_oca/models/edi_backend.py
+++ b/edi_storage_oca/models/edi_backend.py
@@ -73,4 +73,6 @@ def _component_sort_key(self, component_class):
# Override to give precedence by storage_backend_type when needed.
if not self.storage_id:
return res
- return (1 if component_class._storage_backend_type else 0,) + res
+ return (
+ 1 if getattr(component_class, "_storage_backend_type", False) else 0,
+ ) + res
From 3316a5c94c642da32875d266259a5d9ec865c95c Mon Sep 17 00:00:00 2001
From: fshah
Date: Wed, 5 May 2021 14:43:32 +0530
Subject: [PATCH 11/77] [IMP] edi_storage_oca: isort, black, prettier.
---
edi_storage_oca/__manifest__.py | 1 +
edi_storage_oca/tests/common.py | 6 +++++-
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/edi_storage_oca/__manifest__.py b/edi_storage_oca/__manifest__.py
index 0243ab8cf..488f60613 100644
--- a/edi_storage_oca/__manifest__.py
+++ b/edi_storage_oca/__manifest__.py
@@ -10,6 +10,7 @@
"version": "13.0.1.5.0",
"development_status": "Alpha",
"license": "LGPL-3",
+ "website": "https://github.com/OCA/edi",
"author": "ACSONE,Odoo Community Association (OCA)",
"depends": ["edi", "storage_backend", "component"],
"data": ["security/ir_model_access.xml", "views/edi_backend_views.xml"],
diff --git a/edi_storage_oca/tests/common.py b/edi_storage_oca/tests/common.py
index 134c807a9..c5ed77c45 100644
--- a/edi_storage_oca/tests/common.py
+++ b/edi_storage_oca/tests/common.py
@@ -90,7 +90,11 @@ def _mock_storage_backend_add(self):
return mock.patch(STORAGE_BACKEND_MOCK_PATH + ".add", self._mocked_backend_add)
def _test_result(
- self, record, expected_values, expected_messages=None, state_paths=None,
+ self,
+ record,
+ expected_values,
+ expected_messages=None,
+ state_paths=None,
):
state_paths = state_paths or ("done", "pending", "error")
# Paths will be something like:
From f4a6c7815c3d097ca1810d03217be7d4ff8867a4 Mon Sep 17 00:00:00 2001
From: fshah
Date: Wed, 5 May 2021 15:07:19 +0530
Subject: [PATCH 12/77] [MIG] Migrate module edi_storage_oca to v14.
---
edi_storage_oca/README.rst | 11 ++++++-----
edi_storage_oca/__manifest__.py | 4 ++--
edi_storage_oca/demo/edi_backend_demo.xml | 2 +-
edi_storage_oca/readme/CONTRIBUTORS.rst | 1 +
edi_storage_oca/static/description/index.html | 6 +++---
edi_storage_oca/tests/common.py | 4 ++--
edi_storage_oca/tests/test_component_match.py | 4 ++--
edi_storage_oca/tests/test_edi_backend_storage.py | 4 ++--
edi_storage_oca/views/edi_backend_views.xml | 2 +-
9 files changed, 20 insertions(+), 18 deletions(-)
diff --git a/edi_storage_oca/README.rst b/edi_storage_oca/README.rst
index 0cceccfbf..14f8cd692 100644
--- a/edi_storage_oca/README.rst
+++ b/edi_storage_oca/README.rst
@@ -14,13 +14,13 @@ EDI Storage backend support
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fedi-lightgray.png?logo=github
- :target: https://github.com/OCA/edi/tree/13.0/edi_storage
+ :target: https://github.com/OCA/edi/tree/14.0/edi_storage_oca
:alt: OCA/edi
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
- :target: https://translation.odoo-community.org/projects/edi-13-0/edi-13-0-edi_storage
+ :target: https://translation.odoo-community.org/projects/edi-14-0/edi-14-0-edi_storage_oca
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
- :target: https://runbot.odoo-community.org/runbot/226/13.0
+ :target: https://runbot.odoo-community.org/runbot/226/14.0
:alt: Try me on Runbot
|badge1| |badge2| |badge3| |badge4| |badge5|
@@ -70,7 +70,7 @@ Bug Tracker
Bugs are tracked on `GitHub Issues `_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
-`feedback `_.
+`feedback `_.
Do not contact contributors directly about support or help with technical issues.
@@ -86,6 +86,7 @@ Contributors
~~~~~~~~~~~~
* Simone Orsi
+* Foram Shah
Maintainers
~~~~~~~~~~~
@@ -100,6 +101,6 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
-This module is part of the `OCA/edi `_ project on GitHub.
+This module is part of the `OCA/edi `_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/edi_storage_oca/__manifest__.py b/edi_storage_oca/__manifest__.py
index 488f60613..b8606ae4c 100644
--- a/edi_storage_oca/__manifest__.py
+++ b/edi_storage_oca/__manifest__.py
@@ -7,12 +7,12 @@
"summary": """
Base module to allow exchanging files via storage backend (eg: SFTP).
""",
- "version": "13.0.1.5.0",
+ "version": "14.0.1.0.0",
"development_status": "Alpha",
"license": "LGPL-3",
"website": "https://github.com/OCA/edi",
"author": "ACSONE,Odoo Community Association (OCA)",
- "depends": ["edi", "storage_backend", "component"],
+ "depends": ["edi_oca", "storage_backend", "component"],
"data": ["security/ir_model_access.xml", "views/edi_backend_views.xml"],
"demo": ["demo/edi_backend_demo.xml"],
}
diff --git a/edi_storage_oca/demo/edi_backend_demo.xml b/edi_storage_oca/demo/edi_backend_demo.xml
index d8ff5d972..5b003460f 100644
--- a/edi_storage_oca/demo/edi_backend_demo.xml
+++ b/edi_storage_oca/demo/edi_backend_demo.xml
@@ -2,7 +2,7 @@
Storage Demo EDI backend
-
+
demo_in/pending
demo_in/done
diff --git a/edi_storage_oca/readme/CONTRIBUTORS.rst b/edi_storage_oca/readme/CONTRIBUTORS.rst
index f583948be..e3fa6d8e9 100644
--- a/edi_storage_oca/readme/CONTRIBUTORS.rst
+++ b/edi_storage_oca/readme/CONTRIBUTORS.rst
@@ -1 +1,2 @@
* Simone Orsi
+* Foram Shah
diff --git a/edi_storage_oca/static/description/index.html b/edi_storage_oca/static/description/index.html
index b034d89fe..da7347ce6 100644
--- a/edi_storage_oca/static/description/index.html
+++ b/edi_storage_oca/static/description/index.html
@@ -367,7 +367,7 @@ EDI Storage backend support
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
-

+

Allow exchange files using storage backends from OCA/storage.
This module adds a storage backend relation on the EDI backend.
There you can configure the backend to be used (most often and SFTP)
@@ -417,7 +417,7 @@
Bugs are tracked on GitHub Issues.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
-feedback.
+feedback.
Do not contact contributors directly about support or help with technical issues.
@@ -441,7 +441,7 @@
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
-
This module is part of the OCA/edi project on GitHub.
+
This module is part of the OCA/edi project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/edi_storage_oca/tests/common.py b/edi_storage_oca/tests/common.py
index c5ed77c45..4be4c1daf 100644
--- a/edi_storage_oca/tests/common.py
+++ b/edi_storage_oca/tests/common.py
@@ -5,7 +5,7 @@
import mock
-from odoo.addons.edi.tests.common import EDIBackendCommonComponentTestCase
+from odoo.addons.edi_oca.tests.common import EDIBackendCommonComponentTestCase
STORAGE_BACKEND_MOCK_PATH = (
"odoo.addons.storage_backend.models.storage_backend.StorageBackend"
@@ -15,7 +15,7 @@
class TestEDIStorageBase(EDIBackendCommonComponentTestCase):
@classmethod
def _get_backend(cls):
- return cls.env.ref("edi_storage.demo_edi_backend_storage")
+ return cls.env.ref("edi_storage_oca.demo_edi_backend_storage")
@classmethod
def _setup_records(cls):
diff --git a/edi_storage_oca/tests/test_component_match.py b/edi_storage_oca/tests/test_component_match.py
index e422c9698..e4fbfbe82 100644
--- a/edi_storage_oca/tests/test_component_match.py
+++ b/edi_storage_oca/tests/test_component_match.py
@@ -10,11 +10,11 @@ class EDIBackendTestCase(EDIBackendCommonComponentRegistryTestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
- cls._load_module_components(cls, "edi_storage")
+ cls._load_module_components(cls, "edi_storage_oca")
@classmethod
def _get_backend(cls):
- return cls.env.ref("edi_storage.demo_edi_backend_storage")
+ return cls.env.ref("edi_storage_oca.demo_edi_backend_storage")
def test_component_match(self):
"""Lookup with special match method."""
diff --git a/edi_storage_oca/tests/test_edi_backend_storage.py b/edi_storage_oca/tests/test_edi_backend_storage.py
index f49138fc0..319d6e1cd 100644
--- a/edi_storage_oca/tests/test_edi_backend_storage.py
+++ b/edi_storage_oca/tests/test_edi_backend_storage.py
@@ -7,8 +7,8 @@
from .common import TestEDIStorageBase
LOGGERS = (
- "odoo.addons.edi_storage.components.check",
- "odoo.addons.edi.models.edi_backend",
+ "odoo.addons.edi_storage_oca.components.check",
+ "odoo.addons.edi_oca.models.edi_backend",
)
diff --git a/edi_storage_oca/views/edi_backend_views.xml b/edi_storage_oca/views/edi_backend_views.xml
index 5c7916eb1..6d2e4d5d3 100644
--- a/edi_storage_oca/views/edi_backend_views.xml
+++ b/edi_storage_oca/views/edi_backend_views.xml
@@ -2,7 +2,7 @@
edi.backend
-
+
From 99eed9281b82c44a3c7701d3d83ea499b34e1835 Mon Sep 17 00:00:00 2001
From: oca-travis
Date: Thu, 27 May 2021 13:11:31 +0000
Subject: [PATCH 13/77] [UPD] Update edi_storage_oca.pot
---
edi_storage_oca/i18n/edi_storage_oca.pot | 92 ++++++++++++++++++++++++
1 file changed, 92 insertions(+)
create mode 100644 edi_storage_oca/i18n/edi_storage_oca.pot
diff --git a/edi_storage_oca/i18n/edi_storage_oca.pot b/edi_storage_oca/i18n/edi_storage_oca.pot
new file mode 100644
index 000000000..fdd8638b1
--- /dev/null
+++ b/edi_storage_oca/i18n/edi_storage_oca.pot
@@ -0,0 +1,92 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * edi_storage_oca
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 14.0\n"
+"Report-Msgid-Bugs-To: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: edi_storage_oca
+#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_backend__display_name
+msgid "Display Name"
+msgstr ""
+
+#. module: edi_storage_oca
+#: model:ir.model,name:edi_storage_oca.model_edi_backend
+msgid "EDI Backend"
+msgstr ""
+
+#. module: edi_storage_oca
+#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_backend__id
+msgid "ID"
+msgstr ""
+
+#. module: edi_storage_oca
+#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_backend__input_dir_done
+msgid "Input done directory"
+msgstr ""
+
+#. module: edi_storage_oca
+#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_backend__input_dir_error
+msgid "Input error directory"
+msgstr ""
+
+#. module: edi_storage_oca
+#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_backend__input_dir_pending
+msgid "Input pending directory"
+msgstr ""
+
+#. module: edi_storage_oca
+#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_backend____last_update
+msgid "Last Modified on"
+msgstr ""
+
+#. module: edi_storage_oca
+#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_backend__output_dir_done
+msgid "Output done directory"
+msgstr ""
+
+#. module: edi_storage_oca
+#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_backend__output_dir_error
+msgid "Output error directory"
+msgstr ""
+
+#. module: edi_storage_oca
+#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_backend__output_dir_pending
+msgid "Output pending directory"
+msgstr ""
+
+#. module: edi_storage_oca
+#: model:ir.model.fields,help:edi_storage_oca.field_edi_backend__input_dir_done
+#: model:ir.model.fields,help:edi_storage_oca.field_edi_backend__output_dir_done
+msgid "Path to folder for doneful operations"
+msgstr ""
+
+#. module: edi_storage_oca
+#: model:ir.model.fields,help:edi_storage_oca.field_edi_backend__input_dir_error
+#: model:ir.model.fields,help:edi_storage_oca.field_edi_backend__output_dir_error
+msgid "Path to folder for error operations"
+msgstr ""
+
+#. module: edi_storage_oca
+#: model:ir.model.fields,help:edi_storage_oca.field_edi_backend__input_dir_pending
+#: model:ir.model.fields,help:edi_storage_oca.field_edi_backend__output_dir_pending
+msgid "Path to folder for pending operations"
+msgstr ""
+
+#. module: edi_storage_oca
+#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_backend__storage_id
+msgid "Storage backend"
+msgstr ""
+
+#. module: edi_storage_oca
+#: model:ir.model.fields,help:edi_storage_oca.field_edi_backend__storage_id
+msgid "Storage for in-out files"
+msgstr ""
From 481d5925531550501582de3fed80c5a6ced48063 Mon Sep 17 00:00:00 2001
From: OCA-git-bot
Date: Thu, 27 May 2021 13:16:44 +0000
Subject: [PATCH 14/77] [UPD] README.rst
---
edi_storage_oca/README.rst | 2 +-
edi_storage_oca/static/description/index.html | 5 +++--
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/edi_storage_oca/README.rst b/edi_storage_oca/README.rst
index 14f8cd692..516004710 100644
--- a/edi_storage_oca/README.rst
+++ b/edi_storage_oca/README.rst
@@ -70,7 +70,7 @@ Bug Tracker
Bugs are tracked on `GitHub Issues `_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
-`feedback `_.
+`feedback `_.
Do not contact contributors directly about support or help with technical issues.
diff --git a/edi_storage_oca/static/description/index.html b/edi_storage_oca/static/description/index.html
index da7347ce6..a52f95fda 100644
--- a/edi_storage_oca/static/description/index.html
+++ b/edi_storage_oca/static/description/index.html
@@ -367,7 +367,7 @@ EDI Storage backend support
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
-

+

Allow exchange files using storage backends from OCA/storage.
This module adds a storage backend relation on the EDI backend.
There you can configure the backend to be used (most often and SFTP)
@@ -417,7 +417,7 @@
Bugs are tracked on GitHub Issues.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
-feedback.
+feedback.
Do not contact contributors directly about support or help with technical issues.
From ebc3bcee9789cf734ea19b512df17d926039ea19 Mon Sep 17 00:00:00 2001
From: Lois Rilo
Date: Tue, 22 Jun 2021 13:32:31 +0200
Subject: [PATCH 15/77] [IMP] edi_storage_oca: implement basis of the input
process.
* A cron job check related storage on edi backends to create the
needed exchange files.
* Add basic receive component.
---
edi_storage_oca/__manifest__.py | 6 ++-
edi_storage_oca/components/__init__.py | 1 +
edi_storage_oca/components/base.py | 8 ++--
edi_storage_oca/components/receive.py | 27 ++++++++++++++
edi_storage_oca/data/cron.xml | 15 ++++++++
edi_storage_oca/models/edi_backend.py | 49 +++++++++++++++++++++++++
edi_storage_oca/readme/CONTRIBUTORS.rst | 1 +
7 files changed, 103 insertions(+), 4 deletions(-)
create mode 100644 edi_storage_oca/components/receive.py
create mode 100644 edi_storage_oca/data/cron.xml
diff --git a/edi_storage_oca/__manifest__.py b/edi_storage_oca/__manifest__.py
index b8606ae4c..c15efdd67 100644
--- a/edi_storage_oca/__manifest__.py
+++ b/edi_storage_oca/__manifest__.py
@@ -13,6 +13,10 @@
"website": "https://github.com/OCA/edi",
"author": "ACSONE,Odoo Community Association (OCA)",
"depends": ["edi_oca", "storage_backend", "component"],
- "data": ["security/ir_model_access.xml", "views/edi_backend_views.xml"],
+ "data": [
+ "data/cron.xml",
+ "security/ir_model_access.xml",
+ "views/edi_backend_views.xml",
+ ],
"demo": ["demo/edi_backend_demo.xml"],
}
diff --git a/edi_storage_oca/components/__init__.py b/edi_storage_oca/components/__init__.py
index 9f64b792b..7333ccfb3 100644
--- a/edi_storage_oca/components/__init__.py
+++ b/edi_storage_oca/components/__init__.py
@@ -1,3 +1,4 @@
from . import base
from . import check
from . import send
+from . import receive
diff --git a/edi_storage_oca/components/base.py b/edi_storage_oca/components/base.py
index 01d0c8b2c..ca0fa5cef 100644
--- a/edi_storage_oca/components/base.py
+++ b/edi_storage_oca/components/base.py
@@ -27,7 +27,7 @@ def _component_match(cls, work, usage=None, model_name=None, **kw):
def storage(self):
return self.backend.storage_id
- def _dir_by_state(self, direction, state):
+ def _dir_by_state(self, direction, state, absolute=False):
"""Return remote directory path by direction and state.
:param direction: string stating direction of the exchange
@@ -36,9 +36,11 @@ def _dir_by_state(self, direction, state):
"""
assert direction in ("input", "output")
assert state in ("pending", "done", "error")
+ if absolute:
+ return PurePath(self.backend[direction + "_dir_" + state] or "")
return PurePath((self.backend[direction + "_dir_" + state] or "").strip(" /"))
- def _remote_file_path(self, direction, state, filename):
+ def _remote_file_path(self, direction, state, filename, absolute=False):
"""Return remote file path by direction and state for give filename.
:param direction: string stating direction of the exchange
@@ -46,7 +48,7 @@ def _remote_file_path(self, direction, state, filename):
:param filename: string for file name
:return: PurePath object
"""
- return self._dir_by_state(direction, state) / filename.strip("/ ")
+ return self._dir_by_state(direction, state, absolute) / filename.strip("/ ")
def _get_remote_file(self, state, filename=None, binary=False):
"""Get file for current exchange_record in the given destination state.
diff --git a/edi_storage_oca/components/receive.py b/edi_storage_oca/components/receive.py
new file mode 100644
index 000000000..e421df38a
--- /dev/null
+++ b/edi_storage_oca/components/receive.py
@@ -0,0 +1,27 @@
+# Copyright 2021 ForgeFlow S.L. (https://www.forgeflow.com)
+# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
+
+from odoo.addons.component.core import Component
+
+
+class EDIStorageReceiveComponent(Component):
+
+ _name = "edi.storage.component.receive"
+ _inherit = [
+ "edi.component.receive.mixin",
+ "edi.storage.component.mixin",
+ ]
+ _usage = "storage.receive"
+
+ def receive(self):
+ checker = self.component(usage="storage.check")
+ result = checker.check()
+ if not result:
+ # all good here
+ return True
+
+ direction = self.exchange_record.direction
+ filename = self.exchange_record.exchange_filename
+ path = self._remote_file_path(direction, "pending", filename, absolute=True)
+ filedata = self.storage.get(path.as_posix(), binary=True)
+ return filedata
diff --git a/edi_storage_oca/data/cron.xml b/edi_storage_oca/data/cron.xml
new file mode 100644
index 000000000..0dad93786
--- /dev/null
+++ b/edi_storage_oca/data/cron.xml
@@ -0,0 +1,15 @@
+
+
+
+ EDI backend storage check pending input
+
+
+ 1
+ hours
+ -1
+
+
+ code
+ model.search([])._cron_check_storage_pending_input()
+
+
diff --git a/edi_storage_oca/models/edi_backend.py b/edi_storage_oca/models/edi_backend.py
index 0ba578fa0..315b63b16 100644
--- a/edi_storage_oca/models/edi_backend.py
+++ b/edi_storage_oca/models/edi_backend.py
@@ -1,9 +1,14 @@
# Copyright 2020 ACSONE SA
# @author Simone Orsi
+# Copyright 2021 ForgeFlow S.L. (https://www.forgeflow.com)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
+import logging
+
from odoo import fields, models
+_logger = logging.getLogger(__name__)
+
class EDIBackend(models.Model):
@@ -76,3 +81,47 @@ def _component_sort_key(self, component_class):
return (
1 if getattr(component_class, "_storage_backend_type", False) else 0,
) + res
+
+ def _cron_check_storage_pending_input(self, **kw):
+ for backend in self.filtered(lambda b: b.storage_id):
+ backend._check_storage_pending_input(**kw)
+
+ def _check_storage_pending_input(self, **kw):
+ self.ensure_one()
+ if not self.storage_id or not self.input_dir_pending:
+ _logger.info(
+ "%s ignored. No storage and/or input directory specified.", self.name
+ )
+ return False
+
+ pending_files = self.storage_id.list_files(self.input_dir_pending)
+ exchange_type = self.env["edi.exchange.type"].search(
+ self._domain_exchange_type_storage_pending_input()
+ )
+ if not len(exchange_type) == 1:
+ _logger.info("%s ignored. More than one exchange type found.", self.name)
+ return False
+ for file_name in pending_files:
+ existing = self.env["edi.exchange.record"].search(
+ [
+ ("backend_id", "=", self.id),
+ ("type_id", "=", exchange_type.id),
+ ("exchange_filename", "=", file_name),
+ ]
+ )
+ if existing:
+ continue
+ self.create_record(
+ exchange_type.code, self._get_exchange_record_vals(file_name)
+ )
+ _logger.debug("%s: new exchange record generated.", self.name)
+ return True
+
+ def _domain_exchange_type_storage_pending_input(self):
+ return [
+ ("backend_type_id", "=", self.backend_type_id.id),
+ ("direction", "=", "input"),
+ ]
+
+ def _get_exchange_record_vals(self, file_name):
+ return {"exchange_filename": file_name, "edi_exchange_state": "input_pending"}
diff --git a/edi_storage_oca/readme/CONTRIBUTORS.rst b/edi_storage_oca/readme/CONTRIBUTORS.rst
index e3fa6d8e9..55ca1c7e0 100644
--- a/edi_storage_oca/readme/CONTRIBUTORS.rst
+++ b/edi_storage_oca/readme/CONTRIBUTORS.rst
@@ -1,2 +1,3 @@
* Simone Orsi
* Foram Shah
+* Lois Rilo
From 2f004bfd0588d6719cde27a0278d711e8d4c489d Mon Sep 17 00:00:00 2001
From: Lois Rilo
Date: Tue, 22 Jun 2021 13:34:51 +0200
Subject: [PATCH 16/77] [FIX] edi_oca: show the exchange file name while there
is no file yet.
---
edi_storage_oca/components/base.py | 12 +++---
edi_storage_oca/components/receive.py | 4 +-
edi_storage_oca/models/edi_backend.py | 4 +-
edi_storage_oca/tests/common.py | 38 ++++++++++++++++---
.../tests/test_edi_backend_storage.py | 29 ++++++++++++++
5 files changed, 72 insertions(+), 15 deletions(-)
diff --git a/edi_storage_oca/components/base.py b/edi_storage_oca/components/base.py
index ca0fa5cef..5010e6844 100644
--- a/edi_storage_oca/components/base.py
+++ b/edi_storage_oca/components/base.py
@@ -27,7 +27,7 @@ def _component_match(cls, work, usage=None, model_name=None, **kw):
def storage(self):
return self.backend.storage_id
- def _dir_by_state(self, direction, state, absolute=False):
+ def _dir_by_state(self, direction, state):
"""Return remote directory path by direction and state.
:param direction: string stating direction of the exchange
@@ -36,11 +36,11 @@ def _dir_by_state(self, direction, state, absolute=False):
"""
assert direction in ("input", "output")
assert state in ("pending", "done", "error")
- if absolute:
- return PurePath(self.backend[direction + "_dir_" + state] or "")
- return PurePath((self.backend[direction + "_dir_" + state] or "").strip(" /"))
+ return PurePath(
+ (self.backend[direction + "_dir_" + state] or "").strip().rstrip("/")
+ )
- def _remote_file_path(self, direction, state, filename, absolute=False):
+ def _remote_file_path(self, direction, state, filename):
"""Return remote file path by direction and state for give filename.
:param direction: string stating direction of the exchange
@@ -48,7 +48,7 @@ def _remote_file_path(self, direction, state, filename, absolute=False):
:param filename: string for file name
:return: PurePath object
"""
- return self._dir_by_state(direction, state, absolute) / filename.strip("/ ")
+ return self._dir_by_state(direction, state) / filename.strip("/ ")
def _get_remote_file(self, state, filename=None, binary=False):
"""Get file for current exchange_record in the given destination state.
diff --git a/edi_storage_oca/components/receive.py b/edi_storage_oca/components/receive.py
index e421df38a..695c40778 100644
--- a/edi_storage_oca/components/receive.py
+++ b/edi_storage_oca/components/receive.py
@@ -22,6 +22,6 @@ def receive(self):
direction = self.exchange_record.direction
filename = self.exchange_record.exchange_filename
- path = self._remote_file_path(direction, "pending", filename, absolute=True)
- filedata = self.storage.get(path.as_posix(), binary=True)
+ path = self._remote_file_path(direction, "pending", filename)
+ filedata = self.storage.get(path.as_posix())
return filedata
diff --git a/edi_storage_oca/models/edi_backend.py b/edi_storage_oca/models/edi_backend.py
index 315b63b16..451495876 100644
--- a/edi_storage_oca/models/edi_backend.py
+++ b/edi_storage_oca/models/edi_backend.py
@@ -83,7 +83,7 @@ def _component_sort_key(self, component_class):
) + res
def _cron_check_storage_pending_input(self, **kw):
- for backend in self.filtered(lambda b: b.storage_id):
+ for backend in self:
backend._check_storage_pending_input(**kw)
def _check_storage_pending_input(self, **kw):
@@ -102,7 +102,7 @@ def _check_storage_pending_input(self, **kw):
_logger.info("%s ignored. More than one exchange type found.", self.name)
return False
for file_name in pending_files:
- existing = self.env["edi.exchange.record"].search(
+ existing = self.env["edi.exchange.record"].search_count(
[
("backend_id", "=", self.id),
("type_id", "=", exchange_type.id),
diff --git a/edi_storage_oca/tests/common.py b/edi_storage_oca/tests/common.py
index 4be4c1daf..7519ccfba 100644
--- a/edi_storage_oca/tests/common.py
+++ b/edi_storage_oca/tests/common.py
@@ -40,6 +40,14 @@ def _setup_records(cls):
with open(cls.fakepath_error, "w+b") as fakefile:
fakefile.write(b"ERROR XYZ: line 2 broken on bla bla")
+ cls.fakepath_input_pending_1 = "/tmp/test-input-001.csv"
+ with open(cls.fakepath_input_pending_1, "w+b") as fakefile:
+ fakefile.write(b"I received this in my storage.")
+
+ cls.fakepath_input_pending_2 = "/tmp/test-input-002.csv"
+ with open(cls.fakepath_input_pending_2, "w+b") as fakefile:
+ fakefile.write(b"I received that in my storage.")
+
cls.checker = cls.backend._find_component(
cls.partner._name,
["storage.check"],
@@ -61,16 +69,19 @@ def _filename(self, record=None, ack=False):
record.type_id.ack_type_id._make_exchange_filename(record)
return record.exchange_filename
- def _file_fullpath(self, state, record=None, ack=False):
+ def _file_fullpath(
+ self, state, record=None, ack=False, direction=False, fname=None
+ ):
record = record or self.record
- fname = self._filename(record, ack=ack)
+ if not fname:
+ fname = self._filename(record, ack=ack)
+ if not direction:
+ direction = record.direction
if state == "error-report":
# Exception as we read from the same path but w/ error suffix
state = "error"
fname += ".error"
- return (
- self.checker._remote_file_path(record.direction, state, fname)
- ).as_posix()
+ return (self.checker._remote_file_path(direction, state, fname)).as_posix()
def _mocked_backend_get(self, mocked_paths, path, **kwargs):
self._storage_backend_calls.append(path)
@@ -82,6 +93,14 @@ def _mocked_backend_get(self, mocked_paths, path, **kwargs):
def _mocked_backend_add(self, path, data, **kwargs):
self._storage_backend_calls.append(path)
+ def _mocked_backend_list_files(self, mocked_paths, path, **kwargs):
+ files = []
+ path_length = len(path)
+ for p in mocked_paths.keys():
+ if path in p and path != p:
+ files.append(p[path_length:])
+ return files
+
def _mock_storage_backend_get(self, mocked_paths):
mocked = functools.partial(self._mocked_backend_get, mocked_paths)
return mock.patch(STORAGE_BACKEND_MOCK_PATH + ".get", mocked)
@@ -89,6 +108,10 @@ def _mock_storage_backend_get(self, mocked_paths):
def _mock_storage_backend_add(self):
return mock.patch(STORAGE_BACKEND_MOCK_PATH + ".add", self._mocked_backend_add)
+ def _mock_storage_backend_list_files(self, mocked_paths):
+ mocked = functools.partial(self._mocked_backend_list_files, mocked_paths)
+ return mock.patch(STORAGE_BACKEND_MOCK_PATH + ".list_files", mocked)
+
def _test_result(
self,
record,
@@ -130,3 +153,8 @@ def _test_run_cron(self, mocked_paths):
with self._mock_storage_backend_add():
with self._mock_storage_backend_get(mocked_paths):
self.backend._cron_check_output_exchange_sync()
+
+ def _test_run_cron_pending_input(self, mocked_paths):
+ with self._mock_storage_backend_add():
+ with self._mock_storage_backend_list_files(mocked_paths):
+ self.backend._cron_check_storage_pending_input()
diff --git a/edi_storage_oca/tests/test_edi_backend_storage.py b/edi_storage_oca/tests/test_edi_backend_storage.py
index 319d6e1cd..d4779a704 100644
--- a/edi_storage_oca/tests/test_edi_backend_storage.py
+++ b/edi_storage_oca/tests/test_edi_backend_storage.py
@@ -201,3 +201,32 @@ def test_cron_full_flow(self):
}
],
)
+
+ @mute_logger(*LOGGERS)
+ def test_create_input_exchange_file_from_file_received(self):
+ exch_type = self.exchange_type_in
+ input_dir = "/test_input/pending/"
+ file_names = ["some-file.csv", "another-file.csv"]
+ self.backend.input_dir_pending = input_dir
+ mocked_paths = {
+ input_dir: "/tmp/",
+ self._file_fullpath(
+ "pending", direction="input", fname=file_names[0]
+ ): self.fakepath_input_pending_1,
+ self._file_fullpath(
+ "pending", direction="input", fname=file_names[1]
+ ): self.fakepath_input_pending_2,
+ }
+ existing = self.env["edi.exchange.record"].search_count(
+ [("backend_id", "=", self.backend.id), ("type_id", "=", exch_type.id)]
+ )
+ self.assertEqual(existing, 0)
+ # Run cron action:
+ self._test_run_cron_pending_input(mocked_paths)
+ new_records = self.env["edi.exchange.record"].search(
+ [("backend_id", "=", self.backend.id), ("type_id", "=", exch_type.id)]
+ )
+ self.assertEqual(len(new_records), 2)
+ for rec in new_records:
+ self.assertIn(rec.exchange_filename, file_names)
+ self.assertEqual(rec.edi_exchange_state, "input_pending")
From b386bf5b14c84340ffc719c81ea3e90a62f93b9f Mon Sep 17 00:00:00 2001
From: Simone Orsi
Date: Wed, 23 Jun 2021 15:46:00 +0200
Subject: [PATCH 17/77] edi_storage: improve input process
---
edi_storage_oca/data/cron.xml | 2 +-
edi_storage_oca/models/edi_backend.py | 78 ++++++++++++++++++---------
2 files changed, 54 insertions(+), 26 deletions(-)
diff --git a/edi_storage_oca/data/cron.xml b/edi_storage_oca/data/cron.xml
index 0dad93786..d57316cd4 100644
--- a/edi_storage_oca/data/cron.xml
+++ b/edi_storage_oca/data/cron.xml
@@ -10,6 +10,6 @@
code
- model.search([])._cron_check_storage_pending_input()
+ model.search([('storage_backend_id', '!=', False)])._cron_check_storage_pending_input()
diff --git a/edi_storage_oca/models/edi_backend.py b/edi_storage_oca/models/edi_backend.py
index 451495876..4f14f9935 100644
--- a/edi_storage_oca/models/edi_backend.py
+++ b/edi_storage_oca/models/edi_backend.py
@@ -4,6 +4,7 @@
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
import logging
+import os
from odoo import fields, models
@@ -82,46 +83,73 @@ def _component_sort_key(self, component_class):
1 if getattr(component_class, "_storage_backend_type", False) else 0,
) + res
- def _cron_check_storage_pending_input(self, **kw):
+ def _storage_cron_check_pending_input(self, **kw):
for backend in self:
- backend._check_storage_pending_input(**kw)
+ backend._storage_check_pending_input(**kw)
- def _check_storage_pending_input(self, **kw):
+ def _storage_check_pending_input(self, **kw):
+ """Create new exchange records if new files found.
+
+ Collect input exchange types and for each of them,
+ check by pattern if the a new exchange record is required.
+ """
self.ensure_one()
if not self.storage_id or not self.input_dir_pending:
_logger.info(
- "%s ignored. No storage and/or input directory specified.", self.name
+ "%s ignored: no storage and/or input directory specified.", self.name
)
return False
- pending_files = self.storage_id.list_files(self.input_dir_pending)
- exchange_type = self.env["edi.exchange.type"].search(
- self._domain_exchange_type_storage_pending_input()
+ exchange_types = self.env["edi.exchange.type"].search(
+ self._storage_exchange_type_pending_input_domain()
)
- if not len(exchange_type) == 1:
- _logger.info("%s ignored. More than one exchange type found.", self.name)
- return False
- for file_name in pending_files:
- existing = self.env["edi.exchange.record"].search_count(
- [
- ("backend_id", "=", self.id),
- ("type_id", "=", exchange_type.id),
- ("exchange_filename", "=", file_name),
- ]
- )
- if existing:
- continue
- self.create_record(
- exchange_type.code, self._get_exchange_record_vals(file_name)
+ for exchange_type in exchange_types:
+ # NOTE: this call might keep hanging the cron
+ # if the remote storage is slow (eg: too many files)
+ # We should probably run this code in a separate job per exchange type.
+ file_names = self._storage_get_input_filenames(exchange_type)
+ _logger.info(
+ "Processing exchange type '%s': found %s files to process",
+ exchange_type.display_name,
+ len(file_names),
)
- _logger.debug("%s: new exchange record generated.", self.name)
+ for file_name in file_names:
+ # TODO: add config for job
+ self.with_delay()._storage_create_record_if_missing(
+ exchange_type, file_name
+ )
return True
- def _domain_exchange_type_storage_pending_input(self):
+ def _storage_exchange_type_pending_input_domain(self):
+ """Domain for retrieving input exchange types.
+ """
return [
("backend_type_id", "=", self.backend_type_id.id),
("direction", "=", "input"),
]
- def _get_exchange_record_vals(self, file_name):
+ def _storage_create_record_if_missing(self, exchange_type, remote_file_name):
+ """Create a new exchange record for given type and file name if missing.
+ """
+ file_name = os.path.basename(remote_file_name)
+ extra_domain = [("exchange_filename", "=", file_name)]
+ existing = self._find_existing_exchange_records(
+ exchange_type, extra_domain=extra_domain, count_only=True
+ )
+ if existing:
+ return
+ record = self.create_record(
+ exchange_type.code, self._storage_new_exchange_record_vals(file_name)
+ )
+ _logger.debug("%s: new exchange record generated.", self.name)
+ return record.identifier
+
+ def _storage_get_input_filenames(self, exchange_type):
+ bits = [exchange_type.exchange_filename_pattern]
+ if exchange_type.exchange_file_ext:
+ bits.append("*." + exchange_type.exchange_file_ext)
+ pattern = "".join(bits)
+ return self.storage_id.find_files(self.input_dir_pending, pattern=pattern)
+
+ def _storage_new_exchange_record_vals(self, file_name):
return {"exchange_filename": file_name, "edi_exchange_state": "input_pending"}
From e410999c8e519f50e20d5c3eb7c5a5100e81e681 Mon Sep 17 00:00:00 2001
From: Lois Rilo
Date: Wed, 23 Jun 2021 17:10:11 +0200
Subject: [PATCH 18/77] [FIX] edi_storage_oca: support the use or not of
filename patern + small runtime fixes.
---
edi_storage_oca/data/cron.xml | 4 +++-
edi_storage_oca/models/edi_backend.py | 7 ++++++-
edi_storage_oca/tests/common.py | 2 +-
edi_storage_oca/tests/test_edi_backend_storage.py | 3 ++-
4 files changed, 12 insertions(+), 4 deletions(-)
diff --git a/edi_storage_oca/data/cron.xml b/edi_storage_oca/data/cron.xml
index d57316cd4..df21892a0 100644
--- a/edi_storage_oca/data/cron.xml
+++ b/edi_storage_oca/data/cron.xml
@@ -10,6 +10,8 @@
code
- model.search([('storage_backend_id', '!=', False)])._cron_check_storage_pending_input()
+ model.search([('storage_id', '!=', False)])._storage_cron_check_pending_input()
diff --git a/edi_storage_oca/models/edi_backend.py b/edi_storage_oca/models/edi_backend.py
index 4f14f9935..2b9674a53 100644
--- a/edi_storage_oca/models/edi_backend.py
+++ b/edi_storage_oca/models/edi_backend.py
@@ -145,11 +145,16 @@ def _storage_create_record_if_missing(self, exchange_type, remote_file_name):
return record.identifier
def _storage_get_input_filenames(self, exchange_type):
+ if not exchange_type.exchange_filename_pattern:
+ # If there is not patter, return everything
+ return self.storage_id.list_files(self.input_dir_pending)
+
+ # TODO: validate pattern usage.
bits = [exchange_type.exchange_filename_pattern]
if exchange_type.exchange_file_ext:
bits.append("*." + exchange_type.exchange_file_ext)
pattern = "".join(bits)
- return self.storage_id.find_files(self.input_dir_pending, pattern=pattern)
+ return self.storage_id.find_files(pattern, self.input_dir_pending)
def _storage_new_exchange_record_vals(self, file_name):
return {"exchange_filename": file_name, "edi_exchange_state": "input_pending"}
diff --git a/edi_storage_oca/tests/common.py b/edi_storage_oca/tests/common.py
index 7519ccfba..5aaa9951e 100644
--- a/edi_storage_oca/tests/common.py
+++ b/edi_storage_oca/tests/common.py
@@ -157,4 +157,4 @@ def _test_run_cron(self, mocked_paths):
def _test_run_cron_pending_input(self, mocked_paths):
with self._mock_storage_backend_add():
with self._mock_storage_backend_list_files(mocked_paths):
- self.backend._cron_check_storage_pending_input()
+ self.backend._storage_cron_check_pending_input()
diff --git a/edi_storage_oca/tests/test_edi_backend_storage.py b/edi_storage_oca/tests/test_edi_backend_storage.py
index d4779a704..06448474b 100644
--- a/edi_storage_oca/tests/test_edi_backend_storage.py
+++ b/edi_storage_oca/tests/test_edi_backend_storage.py
@@ -203,8 +203,9 @@ def test_cron_full_flow(self):
)
@mute_logger(*LOGGERS)
- def test_create_input_exchange_file_from_file_received(self):
+ def test_create_input_exchange_file_from_file_received_no_pattern(self):
exch_type = self.exchange_type_in
+ exch_type.exchange_filename_pattern = ""
input_dir = "/test_input/pending/"
file_names = ["some-file.csv", "another-file.csv"]
self.backend.input_dir_pending = input_dir
From 1f5379247861577c5ce65ba48959c267264de5d2 Mon Sep 17 00:00:00 2001
From: Lois Rilo
Date: Thu, 1 Jul 2021 09:29:10 +0200
Subject: [PATCH 19/77] [IMP] edi_storage_oca: add specific channel and job
function definitions
---
edi_storage_oca/__manifest__.py | 2 ++
edi_storage_oca/data/job_channel_data.xml | 6 ++++++
edi_storage_oca/data/queue_job_function_data.xml | 7 +++++++
edi_storage_oca/models/edi_backend.py | 1 -
4 files changed, 15 insertions(+), 1 deletion(-)
create mode 100644 edi_storage_oca/data/job_channel_data.xml
create mode 100644 edi_storage_oca/data/queue_job_function_data.xml
diff --git a/edi_storage_oca/__manifest__.py b/edi_storage_oca/__manifest__.py
index c15efdd67..c950bd37f 100644
--- a/edi_storage_oca/__manifest__.py
+++ b/edi_storage_oca/__manifest__.py
@@ -15,6 +15,8 @@
"depends": ["edi_oca", "storage_backend", "component"],
"data": [
"data/cron.xml",
+ "data/job_channel_data.xml",
+ "data/queue_job_function_data.xml",
"security/ir_model_access.xml",
"views/edi_backend_views.xml",
],
diff --git a/edi_storage_oca/data/job_channel_data.xml b/edi_storage_oca/data/job_channel_data.xml
new file mode 100644
index 000000000..b3b3b770d
--- /dev/null
+++ b/edi_storage_oca/data/job_channel_data.xml
@@ -0,0 +1,6 @@
+
+
+ edi_storage
+
+
+
diff --git a/edi_storage_oca/data/queue_job_function_data.xml b/edi_storage_oca/data/queue_job_function_data.xml
new file mode 100644
index 000000000..196528419
--- /dev/null
+++ b/edi_storage_oca/data/queue_job_function_data.xml
@@ -0,0 +1,7 @@
+
+
+
+ _storage_create_record_if_missing
+
+
+
diff --git a/edi_storage_oca/models/edi_backend.py b/edi_storage_oca/models/edi_backend.py
index 2b9674a53..5e229a8a4 100644
--- a/edi_storage_oca/models/edi_backend.py
+++ b/edi_storage_oca/models/edi_backend.py
@@ -114,7 +114,6 @@ def _storage_check_pending_input(self, **kw):
len(file_names),
)
for file_name in file_names:
- # TODO: add config for job
self.with_delay()._storage_create_record_if_missing(
exchange_type, file_name
)
From f35489f6a854a1a73af0892cdefeb1331211bfd8 Mon Sep 17 00:00:00 2001
From: Lois Rilo
Date: Thu, 1 Jul 2021 12:57:49 +0200
Subject: [PATCH 20/77] [FIX] edi_storage_oca: support regex pattern searching
when fetching filenames from storage.
Also extend help in `exchange_filename_pattern` field to clarify usage.
---
edi_storage_oca/models/__init__.py | 1 +
edi_storage_oca/models/edi_backend.py | 7 ++++---
edi_storage_oca/models/edi_exchange_type.py | 21 +++++++++++++++++++++
3 files changed, 26 insertions(+), 3 deletions(-)
create mode 100644 edi_storage_oca/models/edi_exchange_type.py
diff --git a/edi_storage_oca/models/__init__.py b/edi_storage_oca/models/__init__.py
index 4b2c54ac3..f34a72163 100644
--- a/edi_storage_oca/models/__init__.py
+++ b/edi_storage_oca/models/__init__.py
@@ -1 +1,2 @@
from . import edi_backend
+from . import edi_exchange_type
diff --git a/edi_storage_oca/models/edi_backend.py b/edi_storage_oca/models/edi_backend.py
index 5e229a8a4..21d34095e 100644
--- a/edi_storage_oca/models/edi_backend.py
+++ b/edi_storage_oca/models/edi_backend.py
@@ -148,12 +148,13 @@ def _storage_get_input_filenames(self, exchange_type):
# If there is not patter, return everything
return self.storage_id.list_files(self.input_dir_pending)
- # TODO: validate pattern usage.
bits = [exchange_type.exchange_filename_pattern]
if exchange_type.exchange_file_ext:
- bits.append("*." + exchange_type.exchange_file_ext)
+ bits.append(r"\." + exchange_type.exchange_file_ext)
pattern = "".join(bits)
- return self.storage_id.find_files(pattern, self.input_dir_pending)
+ full_paths = self.storage_id.find_files(pattern, self.input_dir_pending)
+ pending_path_len = len(self.input_dir_pending)
+ return [p[pending_path_len:].strip("/") for p in full_paths]
def _storage_new_exchange_record_vals(self, file_name):
return {"exchange_filename": file_name, "edi_exchange_state": "input_pending"}
diff --git a/edi_storage_oca/models/edi_exchange_type.py b/edi_storage_oca/models/edi_exchange_type.py
new file mode 100644
index 000000000..42a7f7008
--- /dev/null
+++ b/edi_storage_oca/models/edi_exchange_type.py
@@ -0,0 +1,21 @@
+# Copyright 2021 ForgeFlow S.L. (https://www.forgeflow.com)
+# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
+
+from odoo import fields, models
+
+
+class EDIExchangeType(models.Model):
+ _inherit = "edi.exchange.type"
+
+ # Extend help to explain new usage.
+ exchange_filename_pattern = fields.Char(
+ help="For output exchange types this should be a formatting string "
+ "with the following variables available (to be used between "
+ "brackets, `{}`): `exchange_record`, `record_name`, `type` and "
+ "`dt`. For instance, a valid string would be "
+ "{record_name}-{type.code}-{dt}\n"
+ "For input exchange types related to storage backends "
+ "it should be a regex expression to filter "
+ "the files to be fetched from the pending directory in the related "
+ "storage. E.g: `.*my-type-[0-9]*.\\.csv`"
+ )
From 9ff1cae875e6e9b7de74ea786d7266029d5e8149 Mon Sep 17 00:00:00 2001
From: Lois Rilo
Date: Fri, 2 Jul 2021 09:23:52 +0200
Subject: [PATCH 21/77] [FIX] edi_storage: consider if the exchange type has a
specific backend assigned.
---
edi_storage_oca/models/edi_backend.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/edi_storage_oca/models/edi_backend.py b/edi_storage_oca/models/edi_backend.py
index 21d34095e..0f8196be7 100644
--- a/edi_storage_oca/models/edi_backend.py
+++ b/edi_storage_oca/models/edi_backend.py
@@ -125,6 +125,9 @@ def _storage_exchange_type_pending_input_domain(self):
return [
("backend_type_id", "=", self.backend_type_id.id),
("direction", "=", "input"),
+ "|",
+ ("backend_id", "=", False),
+ ("backend_id", "=", self.id),
]
def _storage_create_record_if_missing(self, exchange_type, remote_file_name):
From 9246cc3409f2fa25a431dd5955c37963b5c349a0 Mon Sep 17 00:00:00 2001
From: Lois Rilo
Date: Mon, 5 Jul 2021 15:56:58 +0200
Subject: [PATCH 22/77] [IMP] : black, isort, prettier
---
edi_storage_oca/models/edi_backend.py | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/edi_storage_oca/models/edi_backend.py b/edi_storage_oca/models/edi_backend.py
index 0f8196be7..5f95e3382 100644
--- a/edi_storage_oca/models/edi_backend.py
+++ b/edi_storage_oca/models/edi_backend.py
@@ -120,8 +120,7 @@ def _storage_check_pending_input(self, **kw):
return True
def _storage_exchange_type_pending_input_domain(self):
- """Domain for retrieving input exchange types.
- """
+ """Domain for retrieving input exchange types."""
return [
("backend_type_id", "=", self.backend_type_id.id),
("direction", "=", "input"),
@@ -131,8 +130,7 @@ def _storage_exchange_type_pending_input_domain(self):
]
def _storage_create_record_if_missing(self, exchange_type, remote_file_name):
- """Create a new exchange record for given type and file name if missing.
- """
+ """Create a new exchange record for given type and file name if missing."""
file_name = os.path.basename(remote_file_name)
extra_domain = [("exchange_filename", "=", file_name)]
existing = self._find_existing_exchange_records(
From 144c434e1429b303fe1c36a8120dbcdd273d2e6f Mon Sep 17 00:00:00 2001
From: oca-travis
Date: Tue, 6 Jul 2021 09:43:30 +0000
Subject: [PATCH 23/77] [UPD] Update edi_storage_oca.pot
---
edi_storage_oca/i18n/edi_storage_oca.pot | 27 ++++++++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/edi_storage_oca/i18n/edi_storage_oca.pot b/edi_storage_oca/i18n/edi_storage_oca.pot
index fdd8638b1..784ca4c68 100644
--- a/edi_storage_oca/i18n/edi_storage_oca.pot
+++ b/edi_storage_oca/i18n/edi_storage_oca.pot
@@ -15,6 +15,7 @@ msgstr ""
#. module: edi_storage_oca
#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_backend__display_name
+#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_exchange_type__display_name
msgid "Display Name"
msgstr ""
@@ -23,8 +24,33 @@ msgstr ""
msgid "EDI Backend"
msgstr ""
+#. module: edi_storage_oca
+#: model:ir.model,name:edi_storage_oca.model_edi_exchange_type
+msgid "EDI Exchange Type"
+msgstr ""
+
+#. module: edi_storage_oca
+#: model:ir.actions.server,name:edi_storage_oca.cron_check_storage_pending_input_ir_actions_server
+#: model:ir.cron,cron_name:edi_storage_oca.cron_check_storage_pending_input
+#: model:ir.cron,name:edi_storage_oca.cron_check_storage_pending_input
+msgid "EDI backend storage check pending input"
+msgstr ""
+
+#. module: edi_storage_oca
+#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_exchange_type__exchange_filename_pattern
+msgid "Exchange Filename Pattern"
+msgstr ""
+
+#. module: edi_storage_oca
+#: model:ir.model.fields,help:edi_storage_oca.field_edi_exchange_type__exchange_filename_pattern
+msgid ""
+"For output exchange types this should be a formatting string with the following variables available (to be used between brackets, `{}`): `exchange_record`, `record_name`, `type` and `dt`. For instance, a valid string would be {record_name}-{type.code}-{dt}\n"
+"For input exchange types related to storage backends it should be a regex expression to filter the files to be fetched from the pending directory in the related storage. E.g: `.*my-type-[0-9]*.\\.csv`"
+msgstr ""
+
#. module: edi_storage_oca
#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_backend__id
+#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_exchange_type__id
msgid "ID"
msgstr ""
@@ -45,6 +71,7 @@ msgstr ""
#. module: edi_storage_oca
#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_backend____last_update
+#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_exchange_type____last_update
msgid "Last Modified on"
msgstr ""
From d8fee6396c93784dab6b98652fa9c28843c7dc5e Mon Sep 17 00:00:00 2001
From: OCA-git-bot
Date: Tue, 6 Jul 2021 09:49:11 +0000
Subject: [PATCH 24/77] [UPD] README.rst
---
edi_storage_oca/README.rst | 1 +
edi_storage_oca/__manifest__.py | 2 +-
edi_storage_oca/static/description/index.html | 1 +
3 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/edi_storage_oca/README.rst b/edi_storage_oca/README.rst
index 516004710..dc3a04a12 100644
--- a/edi_storage_oca/README.rst
+++ b/edi_storage_oca/README.rst
@@ -87,6 +87,7 @@ Contributors
* Simone Orsi
* Foram Shah
+* Lois Rilo
Maintainers
~~~~~~~~~~~
diff --git a/edi_storage_oca/__manifest__.py b/edi_storage_oca/__manifest__.py
index c950bd37f..e25ee456d 100644
--- a/edi_storage_oca/__manifest__.py
+++ b/edi_storage_oca/__manifest__.py
@@ -7,7 +7,7 @@
"summary": """
Base module to allow exchanging files via storage backend (eg: SFTP).
""",
- "version": "14.0.1.0.0",
+ "version": "14.0.1.1.0",
"development_status": "Alpha",
"license": "LGPL-3",
"website": "https://github.com/OCA/edi",
diff --git a/edi_storage_oca/static/description/index.html b/edi_storage_oca/static/description/index.html
index a52f95fda..257c2d221 100644
--- a/edi_storage_oca/static/description/index.html
+++ b/edi_storage_oca/static/description/index.html
@@ -433,6 +433,7 @@
From 52f0b8840ff371a6a646ec92e4d738c93de1e2e2 Mon Sep 17 00:00:00 2001
From: Lois Rilo
Date: Mon, 5 Jul 2021 18:36:03 +0200
Subject: [PATCH 25/77] [IMP] edi_storage: add listener to move files to
done/error dirs
when a file is processed the listener will move it to the done
directory or the error directory based on the result of processing
the file.
---
edi_storage_oca/components/__init__.py | 1 +
edi_storage_oca/components/listener.py | 59 +++++++++++++++
edi_storage_oca/tests/__init__.py | 1 +
.../tests/test_edi_storage_listener.py | 71 +++++++++++++++++++
4 files changed, 132 insertions(+)
create mode 100644 edi_storage_oca/components/listener.py
create mode 100644 edi_storage_oca/tests/test_edi_storage_listener.py
diff --git a/edi_storage_oca/components/__init__.py b/edi_storage_oca/components/__init__.py
index 7333ccfb3..7bff9ddfc 100644
--- a/edi_storage_oca/components/__init__.py
+++ b/edi_storage_oca/components/__init__.py
@@ -2,3 +2,4 @@
from . import check
from . import send
from . import receive
+from . import listener
diff --git a/edi_storage_oca/components/listener.py b/edi_storage_oca/components/listener.py
new file mode 100644
index 000000000..a1f248b7a
--- /dev/null
+++ b/edi_storage_oca/components/listener.py
@@ -0,0 +1,59 @@
+# Copyright 2021 ForgeFlow S.L. (https://www.forgeflow.com)
+# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
+
+import functools
+from pathlib import PurePath
+
+from odoo.addons.component.core import Component
+
+
+class EdiStorageListener(Component):
+ _name = "edi.storage.component.listener"
+ _inherit = "base.event.listener"
+
+ def _move_file(self, storage, from_dir_str, to_dir_str, filename):
+ from_dir = PurePath(from_dir_str)
+ to_dir = PurePath(to_dir_str)
+ if filename not in storage.list_files(from_dir):
+ # The file might have been moved after a previous error.
+ return False
+ self._add_after_commit_hook(
+ storage._move_files, [(from_dir / filename).as_posix()], to_dir.as_posix()
+ )
+ return True
+
+ def _add_after_commit_hook(self, move_func, sftp_filepath, sftp_destination_path):
+ """Add hook after commit to move the file when transaction is over."""
+ self.env.cr.after(
+ "commit",
+ functools.partial(move_func, sftp_filepath, sftp_destination_path),
+ )
+
+ def on_edi_exchange_done(self, record):
+ storage = record.backend_id.storage_id
+ res = False
+ if record.direction == "input" and storage:
+ file = record.exchange_filename
+ pending_dir = record.backend_id.input_dir_pending
+ done_dir = record.backend_id.input_dir_done
+ error_dir = record.backend_id.input_dir_error
+ if not done_dir:
+ return res
+ res = self._move_file(storage, pending_dir, done_dir, file)
+ if not res:
+ # If a file previously failed it should have been previously
+ # moved to the error dir, therefore it is not present in the
+ # pending dir and we need to retry from error dir.
+ res = self._move_file(storage, error_dir, done_dir, file)
+ return res
+
+ def on_edi_exchange_error(self, record):
+ storage = record.backend_id.storage_id
+ res = False
+ if record.direction == "input" and storage:
+ file = record.exchange_filename
+ pending_dir = record.backend_id.input_dir_pending
+ error_dir = record.backend_id.input_dir_error
+ if error_dir:
+ res = self._move_file(storage, pending_dir, error_dir, file)
+ return res
diff --git a/edi_storage_oca/tests/__init__.py b/edi_storage_oca/tests/__init__.py
index b788a0071..a91d5f238 100644
--- a/edi_storage_oca/tests/__init__.py
+++ b/edi_storage_oca/tests/__init__.py
@@ -1,2 +1,3 @@
from . import test_edi_backend_storage
from . import test_components_base
+from . import test_edi_storage_listener
diff --git a/edi_storage_oca/tests/test_edi_storage_listener.py b/edi_storage_oca/tests/test_edi_storage_listener.py
new file mode 100644
index 000000000..4f12f3311
--- /dev/null
+++ b/edi_storage_oca/tests/test_edi_storage_listener.py
@@ -0,0 +1,71 @@
+# Copyright 2021 ForgeFlow S.L. (https://www.forgeflow.com)
+# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
+
+import base64
+
+import mock
+
+from odoo.addons.edi_oca.tests.common import EDIBackendCommonComponentRegistryTestCase
+from odoo.addons.edi_oca.tests.fake_components import FakeInputProcess
+
+LISTENER_MOCK_PATH = (
+ "odoo.addons.edi_storage_oca.components.listener.EdiStorageListener"
+)
+
+
+class EDIBackendTestCase(EDIBackendCommonComponentRegistryTestCase):
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ cls._load_module_components(cls, "edi_storage_oca")
+ cls._build_components(
+ cls,
+ FakeInputProcess,
+ )
+ vals = {
+ "model": cls.partner._name,
+ "res_id": cls.partner.id,
+ "exchange_file": base64.b64encode(b"1234"),
+ }
+ cls.record = cls.backend.create_record("test_csv_input", vals)
+ cls.fake_move_args = None
+
+ @classmethod
+ def _get_backend(cls):
+ return cls.env.ref("edi_storage_oca.demo_edi_backend_storage")
+
+ def setUp(self):
+ super().setUp()
+ FakeInputProcess.reset_faked()
+
+ def _move_file_mocked(self, *args):
+ self.fake_move_args = [*args]
+ if not all([*args]):
+ return False
+ return True
+
+ def _mock_listener_move_file(self):
+ return mock.patch(LISTENER_MOCK_PATH + "._move_file", self._move_file_mocked)
+
+ def test_01_process_record_success(self):
+ with self._mock_listener_move_file():
+ self.record.write({"edi_exchange_state": "input_received"})
+ self.record.action_exchange_process()
+ storage, from_dir_str, to_dir_str, filename = self.fake_move_args
+ self.assertEqual(storage, self.backend.storage_id)
+ self.assertEqual(from_dir_str, self.backend.input_dir_pending)
+ self.assertEqual(to_dir_str, self.backend.input_dir_done)
+ self.assertEqual(filename, self.record.exchange_filename)
+
+ def test_02_process_record_with_error(self):
+ with self._mock_listener_move_file():
+ self.record.write({"edi_exchange_state": "input_received"})
+ self.record._set_file_content("TEST %d" % self.record.id)
+ self.record.with_context(
+ test_break_process="OOPS! Something went wrong :("
+ ).action_exchange_process()
+ storage, from_dir_str, to_dir_str, filename = self.fake_move_args
+ self.assertEqual(storage, self.backend.storage_id)
+ self.assertEqual(from_dir_str, self.backend.input_dir_pending)
+ self.assertEqual(to_dir_str, self.backend.input_dir_error)
+ self.assertEqual(filename, self.record.exchange_filename)
From 8cc85e8448cc6008fe1874d1cd8b89baf8e9f47f Mon Sep 17 00:00:00 2001
From: Lois Rilo
Date: Fri, 9 Jul 2021 11:25:05 +0200
Subject: [PATCH 26/77] [FIX] edi_storage: `list_files` expects a string.
---
edi_storage_oca/__manifest__.py | 2 +-
edi_storage_oca/components/listener.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/edi_storage_oca/__manifest__.py b/edi_storage_oca/__manifest__.py
index e25ee456d..fb84ffcbb 100644
--- a/edi_storage_oca/__manifest__.py
+++ b/edi_storage_oca/__manifest__.py
@@ -7,7 +7,7 @@
"summary": """
Base module to allow exchanging files via storage backend (eg: SFTP).
""",
- "version": "14.0.1.1.0",
+ "version": "14.0.1.2.0",
"development_status": "Alpha",
"license": "LGPL-3",
"website": "https://github.com/OCA/edi",
diff --git a/edi_storage_oca/components/listener.py b/edi_storage_oca/components/listener.py
index a1f248b7a..7b551c81e 100644
--- a/edi_storage_oca/components/listener.py
+++ b/edi_storage_oca/components/listener.py
@@ -14,7 +14,7 @@ class EdiStorageListener(Component):
def _move_file(self, storage, from_dir_str, to_dir_str, filename):
from_dir = PurePath(from_dir_str)
to_dir = PurePath(to_dir_str)
- if filename not in storage.list_files(from_dir):
+ if filename not in storage.list_files(from_dir.as_posix()):
# The file might have been moved after a previous error.
return False
self._add_after_commit_hook(
From eb55385fae0b21c67e40ffebb6e2a740591ffd48 Mon Sep 17 00:00:00 2001
From: JordiMForgeFlow
Date: Wed, 4 Aug 2021 09:23:35 +0200
Subject: [PATCH 27/77] [14.0][FIX] edi_storage_oca: remove duplicate
backend_type_id field in edi backend form
---
edi_storage_oca/__manifest__.py | 2 +-
edi_storage_oca/views/edi_backend_views.xml | 1 -
2 files changed, 1 insertion(+), 2 deletions(-)
diff --git a/edi_storage_oca/__manifest__.py b/edi_storage_oca/__manifest__.py
index fb84ffcbb..7fd2405b1 100644
--- a/edi_storage_oca/__manifest__.py
+++ b/edi_storage_oca/__manifest__.py
@@ -7,7 +7,7 @@
"summary": """
Base module to allow exchanging files via storage backend (eg: SFTP).
""",
- "version": "14.0.1.2.0",
+ "version": "14.0.1.2.1",
"development_status": "Alpha",
"license": "LGPL-3",
"website": "https://github.com/OCA/edi",
diff --git a/edi_storage_oca/views/edi_backend_views.xml b/edi_storage_oca/views/edi_backend_views.xml
index 6d2e4d5d3..520965fb3 100644
--- a/edi_storage_oca/views/edi_backend_views.xml
+++ b/edi_storage_oca/views/edi_backend_views.xml
@@ -6,7 +6,6 @@
-
From b9a46402a329c276c3104d593044e9e7c8063f1c Mon Sep 17 00:00:00 2001
From: Lois Rilo
Date: Mon, 6 Sep 2021 12:04:31 +0200
Subject: [PATCH 28/77] [FIX] edi_storage: incorrect call to output checker in
receive component.
checker is only relevant for output direction.
---
edi_storage_oca/__manifest__.py | 2 +-
edi_storage_oca/components/receive.py | 6 ------
2 files changed, 1 insertion(+), 7 deletions(-)
diff --git a/edi_storage_oca/__manifest__.py b/edi_storage_oca/__manifest__.py
index 7fd2405b1..efe19ecca 100644
--- a/edi_storage_oca/__manifest__.py
+++ b/edi_storage_oca/__manifest__.py
@@ -7,7 +7,7 @@
"summary": """
Base module to allow exchanging files via storage backend (eg: SFTP).
""",
- "version": "14.0.1.2.1",
+ "version": "14.0.1.2.2",
"development_status": "Alpha",
"license": "LGPL-3",
"website": "https://github.com/OCA/edi",
diff --git a/edi_storage_oca/components/receive.py b/edi_storage_oca/components/receive.py
index 695c40778..0061ac1d9 100644
--- a/edi_storage_oca/components/receive.py
+++ b/edi_storage_oca/components/receive.py
@@ -14,12 +14,6 @@ class EDIStorageReceiveComponent(Component):
_usage = "storage.receive"
def receive(self):
- checker = self.component(usage="storage.check")
- result = checker.check()
- if not result:
- # all good here
- return True
-
direction = self.exchange_record.direction
filename = self.exchange_record.exchange_filename
path = self._remote_file_path(direction, "pending", filename)
From 241c45bd1b21f765f64843b9a63e9689b276d6e9 Mon Sep 17 00:00:00 2001
From: Simone Orsi
Date: Fri, 17 Sep 2021 17:14:19 +0200
Subject: [PATCH 29/77] edi: improve backend view extension
---
edi_storage_oca/views/edi_backend_views.xml | 24 +++++++++++----------
1 file changed, 13 insertions(+), 11 deletions(-)
diff --git a/edi_storage_oca/views/edi_backend_views.xml b/edi_storage_oca/views/edi_backend_views.xml
index 520965fb3..ead51ef51 100644
--- a/edi_storage_oca/views/edi_backend_views.xml
+++ b/edi_storage_oca/views/edi_backend_views.xml
@@ -4,17 +4,19 @@
edi.backend
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
From 3f2a48ce5b9942b2b1307b83074540a0814451b8 Mon Sep 17 00:00:00 2001
From: oca-travis
Date: Wed, 13 Oct 2021 14:22:09 +0000
Subject: [PATCH 30/77] [UPD] Update edi_storage_oca.pot
---
edi_storage_oca/__manifest__.py | 2 +-
edi_storage_oca/i18n/edi_storage_oca.pot | 5 +++++
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/edi_storage_oca/__manifest__.py b/edi_storage_oca/__manifest__.py
index efe19ecca..3481bef65 100644
--- a/edi_storage_oca/__manifest__.py
+++ b/edi_storage_oca/__manifest__.py
@@ -7,7 +7,7 @@
"summary": """
Base module to allow exchanging files via storage backend (eg: SFTP).
""",
- "version": "14.0.1.2.2",
+ "version": "14.0.1.3.0",
"development_status": "Alpha",
"license": "LGPL-3",
"website": "https://github.com/OCA/edi",
diff --git a/edi_storage_oca/i18n/edi_storage_oca.pot b/edi_storage_oca/i18n/edi_storage_oca.pot
index 784ca4c68..eb9e80688 100644
--- a/edi_storage_oca/i18n/edi_storage_oca.pot
+++ b/edi_storage_oca/i18n/edi_storage_oca.pot
@@ -108,6 +108,11 @@ msgstr ""
msgid "Path to folder for pending operations"
msgstr ""
+#. module: edi_storage_oca
+#: model_terms:ir.ui.view,arch_db:edi_storage_oca.edi_backend_view_form
+msgid "Storage"
+msgstr ""
+
#. module: edi_storage_oca
#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_backend__storage_id
msgid "Storage backend"
From da890c2fc03fa129ca48a9e887c479210b709b20 Mon Sep 17 00:00:00 2001
From: Simone Orsi
Date: Wed, 22 Sep 2021 20:07:37 +0200
Subject: [PATCH 31/77] edi: load main vars at init
---
edi_storage_oca/tests/__init__.py | 1 +
edi_storage_oca/tests/test_component_match.py | 9 ++++++++-
2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/edi_storage_oca/tests/__init__.py b/edi_storage_oca/tests/__init__.py
index a91d5f238..678979f8c 100644
--- a/edi_storage_oca/tests/__init__.py
+++ b/edi_storage_oca/tests/__init__.py
@@ -1,3 +1,4 @@
from . import test_edi_backend_storage
from . import test_components_base
+from . import test_component_match
from . import test_edi_storage_listener
diff --git a/edi_storage_oca/tests/test_component_match.py b/edi_storage_oca/tests/test_component_match.py
index e4fbfbe82..f0a776ae9 100644
--- a/edi_storage_oca/tests/test_component_match.py
+++ b/edi_storage_oca/tests/test_component_match.py
@@ -3,7 +3,7 @@
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from odoo.addons.component.core import Component
-from odoo.addons.edi.tests.common import EDIBackendCommonComponentRegistryTestCase
+from odoo.addons.edi_oca.tests.common import EDIBackendCommonComponentRegistryTestCase
class EDIBackendTestCase(EDIBackendCommonComponentRegistryTestCase):
@@ -49,9 +49,13 @@ class S3Send(Component):
self._build_components(SFTPCheck, SFTPSend, S3Check, S3Send)
+ # Record not relevant for these tests
+ work_ctx = {"exchange_record": self.env["edi.exchange.record"].browse()}
+
component = self.backend._find_component(
"res.partner",
["storage.check"],
+ work_ctx=work_ctx,
backend_type="demo_backend",
exchange_type="test_csv_output",
storage_backend_type="s3",
@@ -61,6 +65,7 @@ class S3Send(Component):
component = self.backend._find_component(
"res.partner",
["storage.check"],
+ work_ctx=work_ctx,
backend_type="demo_backend",
exchange_type="test_csv_output",
storage_backend_type="sftp",
@@ -70,6 +75,7 @@ class S3Send(Component):
component = self.backend._find_component(
"res.partner",
["storage.send"],
+ work_ctx=work_ctx,
backend_type="demo_backend",
exchange_type="test_csv_output",
storage_backend_type="sftp",
@@ -79,6 +85,7 @@ class S3Send(Component):
component = self.backend._find_component(
"res.partner",
["storage.send"],
+ work_ctx=work_ctx,
backend_type="demo_backend",
exchange_type="test_csv_output",
storage_backend_type="s3",
From 422d8d193bd0822c0e26d0f6c4846615b7d404fd Mon Sep 17 00:00:00 2001
From: Simone Orsi
Date: Sat, 30 Oct 2021 08:52:28 +0200
Subject: [PATCH 32/77] edi_storage: fix find files test
---
edi_storage_oca/__manifest__.py | 2 +-
edi_storage_oca/tests/common.py | 6 ++++++
edi_storage_oca/tests/test_edi_backend_storage.py | 4 +++-
3 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/edi_storage_oca/__manifest__.py b/edi_storage_oca/__manifest__.py
index 3481bef65..d0cb44158 100644
--- a/edi_storage_oca/__manifest__.py
+++ b/edi_storage_oca/__manifest__.py
@@ -7,7 +7,7 @@
"summary": """
Base module to allow exchanging files via storage backend (eg: SFTP).
""",
- "version": "14.0.1.3.0",
+ "version": "14.0.1.4.0",
"development_status": "Alpha",
"license": "LGPL-3",
"website": "https://github.com/OCA/edi",
diff --git a/edi_storage_oca/tests/common.py b/edi_storage_oca/tests/common.py
index 5aaa9951e..31e4077c0 100644
--- a/edi_storage_oca/tests/common.py
+++ b/edi_storage_oca/tests/common.py
@@ -112,6 +112,12 @@ def _mock_storage_backend_list_files(self, mocked_paths):
mocked = functools.partial(self._mocked_backend_list_files, mocked_paths)
return mock.patch(STORAGE_BACKEND_MOCK_PATH + ".list_files", mocked)
+ def _mock_storage_backend_find_files(self, result):
+ def _result(self, pattern, relative_path=None, **kw):
+ return result
+
+ return mock.patch(STORAGE_BACKEND_MOCK_PATH + ".find_files", _result)
+
def _test_result(
self,
record,
diff --git a/edi_storage_oca/tests/test_edi_backend_storage.py b/edi_storage_oca/tests/test_edi_backend_storage.py
index 06448474b..2f396d39d 100644
--- a/edi_storage_oca/tests/test_edi_backend_storage.py
+++ b/edi_storage_oca/tests/test_edi_backend_storage.py
@@ -223,7 +223,9 @@ def test_create_input_exchange_file_from_file_received_no_pattern(self):
)
self.assertEqual(existing, 0)
# Run cron action:
- self._test_run_cron_pending_input(mocked_paths)
+ found_files = [input_dir + fname for fname in file_names]
+ with self._mock_storage_backend_find_files(found_files):
+ self._test_run_cron_pending_input(mocked_paths)
new_records = self.env["edi.exchange.record"].search(
[("backend_id", "=", self.backend.id), ("type_id", "=", exch_type.id)]
)
From 0c5d63d42d293d8abc5c1f5f255e58598d62c1a2 Mon Sep 17 00:00:00 2001
From: Iryna Vyshnevska
Date: Mon, 6 Dec 2021 00:28:47 +0200
Subject: [PATCH 33/77] [MIG][15.0] edi_storage_oca
---
edi_storage_oca/__manifest__.py | 2 +-
edi_storage_oca/i18n/edi_storage.pot | 77 --------------
edi_storage_oca/i18n/edi_storage_oca.pot | 124 -----------------------
edi_storage_oca/tests/common.py | 1 +
4 files changed, 2 insertions(+), 202 deletions(-)
delete mode 100644 edi_storage_oca/i18n/edi_storage.pot
delete mode 100644 edi_storage_oca/i18n/edi_storage_oca.pot
diff --git a/edi_storage_oca/__manifest__.py b/edi_storage_oca/__manifest__.py
index d0cb44158..a7480dafd 100644
--- a/edi_storage_oca/__manifest__.py
+++ b/edi_storage_oca/__manifest__.py
@@ -7,7 +7,7 @@
"summary": """
Base module to allow exchanging files via storage backend (eg: SFTP).
""",
- "version": "14.0.1.4.0",
+ "version": "15.0.1.0.0",
"development_status": "Alpha",
"license": "LGPL-3",
"website": "https://github.com/OCA/edi",
diff --git a/edi_storage_oca/i18n/edi_storage.pot b/edi_storage_oca/i18n/edi_storage.pot
deleted file mode 100644
index d6944e83b..000000000
--- a/edi_storage_oca/i18n/edi_storage.pot
+++ /dev/null
@@ -1,77 +0,0 @@
-# Translation of Odoo Server.
-# This file contains the translation of the following modules:
-# * edi_storage
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: Odoo Server 13.0\n"
-"Report-Msgid-Bugs-To: \n"
-"Last-Translator: \n"
-"Language-Team: \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: \n"
-"Plural-Forms: \n"
-
-#. module: edi_storage
-#: model:ir.model,name:edi_storage.model_edi_backend
-msgid "EDI Backend"
-msgstr ""
-
-#. module: edi_storage
-#: model:ir.model.fields,field_description:edi_storage.field_edi_backend__input_dir_done
-msgid "Input done directory"
-msgstr ""
-
-#. module: edi_storage
-#: model:ir.model.fields,field_description:edi_storage.field_edi_backend__input_dir_error
-msgid "Input error directory"
-msgstr ""
-
-#. module: edi_storage
-#: model:ir.model.fields,field_description:edi_storage.field_edi_backend__input_dir_pending
-msgid "Input pending directory"
-msgstr ""
-
-#. module: edi_storage
-#: model:ir.model.fields,field_description:edi_storage.field_edi_backend__output_dir_done
-msgid "Output done directory"
-msgstr ""
-
-#. module: edi_storage
-#: model:ir.model.fields,field_description:edi_storage.field_edi_backend__output_dir_error
-msgid "Output error directory"
-msgstr ""
-
-#. module: edi_storage
-#: model:ir.model.fields,field_description:edi_storage.field_edi_backend__output_dir_pending
-msgid "Output pending directory"
-msgstr ""
-
-#. module: edi_storage
-#: model:ir.model.fields,help:edi_storage.field_edi_backend__input_dir_done
-#: model:ir.model.fields,help:edi_storage.field_edi_backend__output_dir_done
-msgid "Path to folder for doneful operations"
-msgstr ""
-
-#. module: edi_storage
-#: model:ir.model.fields,help:edi_storage.field_edi_backend__input_dir_error
-#: model:ir.model.fields,help:edi_storage.field_edi_backend__output_dir_error
-msgid "Path to folder for error operations"
-msgstr ""
-
-#. module: edi_storage
-#: model:ir.model.fields,help:edi_storage.field_edi_backend__input_dir_pending
-#: model:ir.model.fields,help:edi_storage.field_edi_backend__output_dir_pending
-msgid "Path to folder for pending operations"
-msgstr ""
-
-#. module: edi_storage
-#: model:ir.model.fields,field_description:edi_storage.field_edi_backend__storage_id
-msgid "Storage backend"
-msgstr ""
-
-#. module: edi_storage
-#: model:ir.model.fields,help:edi_storage.field_edi_backend__storage_id
-msgid "Storage for in-out files"
-msgstr ""
diff --git a/edi_storage_oca/i18n/edi_storage_oca.pot b/edi_storage_oca/i18n/edi_storage_oca.pot
deleted file mode 100644
index eb9e80688..000000000
--- a/edi_storage_oca/i18n/edi_storage_oca.pot
+++ /dev/null
@@ -1,124 +0,0 @@
-# Translation of Odoo Server.
-# This file contains the translation of the following modules:
-# * edi_storage_oca
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: Odoo Server 14.0\n"
-"Report-Msgid-Bugs-To: \n"
-"Last-Translator: \n"
-"Language-Team: \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: \n"
-"Plural-Forms: \n"
-
-#. module: edi_storage_oca
-#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_backend__display_name
-#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_exchange_type__display_name
-msgid "Display Name"
-msgstr ""
-
-#. module: edi_storage_oca
-#: model:ir.model,name:edi_storage_oca.model_edi_backend
-msgid "EDI Backend"
-msgstr ""
-
-#. module: edi_storage_oca
-#: model:ir.model,name:edi_storage_oca.model_edi_exchange_type
-msgid "EDI Exchange Type"
-msgstr ""
-
-#. module: edi_storage_oca
-#: model:ir.actions.server,name:edi_storage_oca.cron_check_storage_pending_input_ir_actions_server
-#: model:ir.cron,cron_name:edi_storage_oca.cron_check_storage_pending_input
-#: model:ir.cron,name:edi_storage_oca.cron_check_storage_pending_input
-msgid "EDI backend storage check pending input"
-msgstr ""
-
-#. module: edi_storage_oca
-#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_exchange_type__exchange_filename_pattern
-msgid "Exchange Filename Pattern"
-msgstr ""
-
-#. module: edi_storage_oca
-#: model:ir.model.fields,help:edi_storage_oca.field_edi_exchange_type__exchange_filename_pattern
-msgid ""
-"For output exchange types this should be a formatting string with the following variables available (to be used between brackets, `{}`): `exchange_record`, `record_name`, `type` and `dt`. For instance, a valid string would be {record_name}-{type.code}-{dt}\n"
-"For input exchange types related to storage backends it should be a regex expression to filter the files to be fetched from the pending directory in the related storage. E.g: `.*my-type-[0-9]*.\\.csv`"
-msgstr ""
-
-#. module: edi_storage_oca
-#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_backend__id
-#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_exchange_type__id
-msgid "ID"
-msgstr ""
-
-#. module: edi_storage_oca
-#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_backend__input_dir_done
-msgid "Input done directory"
-msgstr ""
-
-#. module: edi_storage_oca
-#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_backend__input_dir_error
-msgid "Input error directory"
-msgstr ""
-
-#. module: edi_storage_oca
-#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_backend__input_dir_pending
-msgid "Input pending directory"
-msgstr ""
-
-#. module: edi_storage_oca
-#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_backend____last_update
-#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_exchange_type____last_update
-msgid "Last Modified on"
-msgstr ""
-
-#. module: edi_storage_oca
-#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_backend__output_dir_done
-msgid "Output done directory"
-msgstr ""
-
-#. module: edi_storage_oca
-#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_backend__output_dir_error
-msgid "Output error directory"
-msgstr ""
-
-#. module: edi_storage_oca
-#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_backend__output_dir_pending
-msgid "Output pending directory"
-msgstr ""
-
-#. module: edi_storage_oca
-#: model:ir.model.fields,help:edi_storage_oca.field_edi_backend__input_dir_done
-#: model:ir.model.fields,help:edi_storage_oca.field_edi_backend__output_dir_done
-msgid "Path to folder for doneful operations"
-msgstr ""
-
-#. module: edi_storage_oca
-#: model:ir.model.fields,help:edi_storage_oca.field_edi_backend__input_dir_error
-#: model:ir.model.fields,help:edi_storage_oca.field_edi_backend__output_dir_error
-msgid "Path to folder for error operations"
-msgstr ""
-
-#. module: edi_storage_oca
-#: model:ir.model.fields,help:edi_storage_oca.field_edi_backend__input_dir_pending
-#: model:ir.model.fields,help:edi_storage_oca.field_edi_backend__output_dir_pending
-msgid "Path to folder for pending operations"
-msgstr ""
-
-#. module: edi_storage_oca
-#: model_terms:ir.ui.view,arch_db:edi_storage_oca.edi_backend_view_form
-msgid "Storage"
-msgstr ""
-
-#. module: edi_storage_oca
-#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_backend__storage_id
-msgid "Storage backend"
-msgstr ""
-
-#. module: edi_storage_oca
-#: model:ir.model.fields,help:edi_storage_oca.field_edi_backend__storage_id
-msgid "Storage for in-out files"
-msgstr ""
diff --git a/edi_storage_oca/tests/common.py b/edi_storage_oca/tests/common.py
index 31e4077c0..385b1a5f5 100644
--- a/edi_storage_oca/tests/common.py
+++ b/edi_storage_oca/tests/common.py
@@ -58,6 +58,7 @@ def _setup_records(cls):
["storage.send"],
work_ctx={"exchange_record": cls.record},
)
+ return
def setUp(self):
super().setUp()
From f623aaeb1b626f7fe4239407cf9fc081c616b15c Mon Sep 17 00:00:00 2001
From: oca-ci
Date: Tue, 15 Mar 2022 12:12:00 +0000
Subject: [PATCH 34/77] [UPD] Update edi_storage_oca.pot
---
edi_storage_oca/i18n/edi_storage_oca.pot | 106 +++++++++++++++++++++++
1 file changed, 106 insertions(+)
create mode 100644 edi_storage_oca/i18n/edi_storage_oca.pot
diff --git a/edi_storage_oca/i18n/edi_storage_oca.pot b/edi_storage_oca/i18n/edi_storage_oca.pot
new file mode 100644
index 000000000..3458c862e
--- /dev/null
+++ b/edi_storage_oca/i18n/edi_storage_oca.pot
@@ -0,0 +1,106 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * edi_storage_oca
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 15.0\n"
+"Report-Msgid-Bugs-To: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: edi_storage_oca
+#: model:ir.model,name:edi_storage_oca.model_edi_backend
+msgid "EDI Backend"
+msgstr ""
+
+#. module: edi_storage_oca
+#: model:ir.model,name:edi_storage_oca.model_edi_exchange_type
+msgid "EDI Exchange Type"
+msgstr ""
+
+#. module: edi_storage_oca
+#: model:ir.actions.server,name:edi_storage_oca.cron_check_storage_pending_input_ir_actions_server
+#: model:ir.cron,cron_name:edi_storage_oca.cron_check_storage_pending_input
+#: model:ir.cron,name:edi_storage_oca.cron_check_storage_pending_input
+msgid "EDI backend storage check pending input"
+msgstr ""
+
+#. module: edi_storage_oca
+#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_exchange_type__exchange_filename_pattern
+msgid "Exchange Filename Pattern"
+msgstr ""
+
+#. module: edi_storage_oca
+#: model:ir.model.fields,help:edi_storage_oca.field_edi_exchange_type__exchange_filename_pattern
+msgid ""
+"For output exchange types this should be a formatting string with the following variables available (to be used between brackets, `{}`): `exchange_record`, `record_name`, `type` and `dt`. For instance, a valid string would be {record_name}-{type.code}-{dt}\n"
+"For input exchange types related to storage backends it should be a regex expression to filter the files to be fetched from the pending directory in the related storage. E.g: `.*my-type-[0-9]*.\\.csv`"
+msgstr ""
+
+#. module: edi_storage_oca
+#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_backend__input_dir_done
+msgid "Input done directory"
+msgstr ""
+
+#. module: edi_storage_oca
+#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_backend__input_dir_error
+msgid "Input error directory"
+msgstr ""
+
+#. module: edi_storage_oca
+#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_backend__input_dir_pending
+msgid "Input pending directory"
+msgstr ""
+
+#. module: edi_storage_oca
+#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_backend__output_dir_done
+msgid "Output done directory"
+msgstr ""
+
+#. module: edi_storage_oca
+#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_backend__output_dir_error
+msgid "Output error directory"
+msgstr ""
+
+#. module: edi_storage_oca
+#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_backend__output_dir_pending
+msgid "Output pending directory"
+msgstr ""
+
+#. module: edi_storage_oca
+#: model:ir.model.fields,help:edi_storage_oca.field_edi_backend__input_dir_done
+#: model:ir.model.fields,help:edi_storage_oca.field_edi_backend__output_dir_done
+msgid "Path to folder for doneful operations"
+msgstr ""
+
+#. module: edi_storage_oca
+#: model:ir.model.fields,help:edi_storage_oca.field_edi_backend__input_dir_error
+#: model:ir.model.fields,help:edi_storage_oca.field_edi_backend__output_dir_error
+msgid "Path to folder for error operations"
+msgstr ""
+
+#. module: edi_storage_oca
+#: model:ir.model.fields,help:edi_storage_oca.field_edi_backend__input_dir_pending
+#: model:ir.model.fields,help:edi_storage_oca.field_edi_backend__output_dir_pending
+msgid "Path to folder for pending operations"
+msgstr ""
+
+#. module: edi_storage_oca
+#: model_terms:ir.ui.view,arch_db:edi_storage_oca.edi_backend_view_form
+msgid "Storage"
+msgstr ""
+
+#. module: edi_storage_oca
+#: model:ir.model.fields,field_description:edi_storage_oca.field_edi_backend__storage_id
+msgid "Storage backend"
+msgstr ""
+
+#. module: edi_storage_oca
+#: model:ir.model.fields,help:edi_storage_oca.field_edi_backend__storage_id
+msgid "Storage for in-out files"
+msgstr ""
From c63f7945c4e32b5b2511fbab558a21b32f3a730d Mon Sep 17 00:00:00 2001
From: OCA-git-bot
Date: Tue, 15 Mar 2022 12:15:16 +0000
Subject: [PATCH 35/77] [UPD] README.rst
---
edi_storage_oca/README.rst | 10 +++++-----
edi_storage_oca/static/description/index.html | 6 +++---
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/edi_storage_oca/README.rst b/edi_storage_oca/README.rst
index dc3a04a12..e75484b38 100644
--- a/edi_storage_oca/README.rst
+++ b/edi_storage_oca/README.rst
@@ -14,13 +14,13 @@ EDI Storage backend support
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fedi-lightgray.png?logo=github
- :target: https://github.com/OCA/edi/tree/14.0/edi_storage_oca
+ :target: https://github.com/OCA/edi/tree/15.0/edi_storage_oca
:alt: OCA/edi
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
- :target: https://translation.odoo-community.org/projects/edi-14-0/edi-14-0-edi_storage_oca
+ :target: https://translation.odoo-community.org/projects/edi-15-0/edi-15-0-edi_storage_oca
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
- :target: https://runbot.odoo-community.org/runbot/226/14.0
+ :target: https://runbot.odoo-community.org/runbot/226/15.0
:alt: Try me on Runbot
|badge1| |badge2| |badge3| |badge4| |badge5|
@@ -70,7 +70,7 @@ Bug Tracker
Bugs are tracked on `GitHub Issues `_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
-`feedback `_.
+`feedback `_.
Do not contact contributors directly about support or help with technical issues.
@@ -102,6 +102,6 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
-This module is part of the `OCA/edi `_ project on GitHub.
+This module is part of the `OCA/edi `_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/edi_storage_oca/static/description/index.html b/edi_storage_oca/static/description/index.html
index 257c2d221..8fa1825de 100644
--- a/edi_storage_oca/static/description/index.html
+++ b/edi_storage_oca/static/description/index.html
@@ -367,7 +367,7 @@ EDI Storage backend support
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
-

+

Allow exchange files using storage backends from OCA/storage.
This module adds a storage backend relation on the EDI backend.
There you can configure the backend to be used (most often and SFTP)
@@ -417,7 +417,7 @@
Bugs are tracked on GitHub Issues.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
-feedback.
+feedback.
Do not contact contributors directly about support or help with technical issues.
@@ -443,7 +443,7 @@
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
-
This module is part of the OCA/edi project on GitHub.
+
This module is part of the OCA/edi project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
From f79424372caa0b0c028488bbefb2c6de8213ffda Mon Sep 17 00:00:00 2001
From: Simone Orsi
Date: Thu, 10 Mar 2022 12:41:35 +0100
Subject: [PATCH 36/77] edi: get rid of name, use identifier
Name contained redundant information like type name and related record.
On for and tree view is totally useless.
To still provide information on linked resource name
the 'related_name' field has been added.
---
edi_storage_oca/components/check.py | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
diff --git a/edi_storage_oca/components/check.py b/edi_storage_oca/components/check.py
index 878cfffcb..90f13c951 100644
--- a/edi_storage_oca/components/check.py
+++ b/edi_storage_oca/components/check.py
@@ -37,9 +37,8 @@ def _exchange_output_check(self):
"""
if self._get_remote_file("done"):
_logger.info(
- "%s done for: %s",
- self.exchange_record.model,
- self.exchange_record.name,
+ "%s done",
+ self.exchange_record.identifier,
)
if (
not self.exchange_record.edi_exchange_state
@@ -52,9 +51,8 @@ def _exchange_output_check(self):
error = self._get_remote_file("error")
if error:
_logger.info(
- "%s error for: %s",
- self.exchange_record.model,
- self.exchange_record.name,
+ "%s error",
+ self.exchange_record.identifier,
)
# Assume a text file will be placed there w/ the same name and error suffix
err_filename = self.exchange_record.exchange_filename + ".error"
From 199b44664ba0526fecfa89746f26e58425eb72f3 Mon Sep 17 00:00:00 2001
From: Simone Orsi
Date: Thu, 10 Mar 2022 16:28:30 +0100
Subject: [PATCH 37/77] edi_storage: fail gracefully w/o error report
---
edi_storage_oca/__manifest__.py | 2 +-
edi_storage_oca/components/check.py | 4 +++-
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/edi_storage_oca/__manifest__.py b/edi_storage_oca/__manifest__.py
index a7480dafd..f9b30ed42 100644
--- a/edi_storage_oca/__manifest__.py
+++ b/edi_storage_oca/__manifest__.py
@@ -7,7 +7,7 @@
"summary": """
Base module to allow exchanging files via storage backend (eg: SFTP).
""",
- "version": "15.0.1.0.0",
+ "version": "15.0.1.1.0",
"development_status": "Alpha",
"license": "LGPL-3",
"website": "https://github.com/OCA/edi",
diff --git a/edi_storage_oca/components/check.py b/edi_storage_oca/components/check.py
index 90f13c951..2cd97fb4a 100644
--- a/edi_storage_oca/components/check.py
+++ b/edi_storage_oca/components/check.py
@@ -56,7 +56,9 @@ def _exchange_output_check(self):
)
# Assume a text file will be placed there w/ the same name and error suffix
err_filename = self.exchange_record.exchange_filename + ".error"
- error_report = self._get_remote_file("error", filename=err_filename)
+ error_report = (
+ self._get_remote_file("error", filename=err_filename) or "no-report"
+ )
if self.exchange_record.edi_exchange_state == "output_sent":
self.exchange_record.update(
{
From e9449d003a5d1993181fd27721b2f9101725559f Mon Sep 17 00:00:00 2001
From: Jasmin Solanki
Date: Tue, 12 Apr 2022 16:45:22 +0530
Subject: [PATCH 38/77] [IMP] edi_storage_oca: Update development status
---
edi_storage_oca/README.rst | 6 +++---
edi_storage_oca/__manifest__.py | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/edi_storage_oca/README.rst b/edi_storage_oca/README.rst
index e75484b38..335f991fe 100644
--- a/edi_storage_oca/README.rst
+++ b/edi_storage_oca/README.rst
@@ -7,9 +7,9 @@ EDI Storage backend support
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png
+.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
- :alt: Alpha
+ :alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
@@ -23,7 +23,7 @@ EDI Storage backend support
:target: https://runbot.odoo-community.org/runbot/226/15.0
:alt: Try me on Runbot
-|badge1| |badge2| |badge3| |badge4| |badge5|
+|badge1| |badge2| |badge3| |badge4| |badge5|
Allow exchange files using storage backends from `OCA/storage`.
diff --git a/edi_storage_oca/__manifest__.py b/edi_storage_oca/__manifest__.py
index f9b30ed42..92310bce1 100644
--- a/edi_storage_oca/__manifest__.py
+++ b/edi_storage_oca/__manifest__.py
@@ -8,7 +8,7 @@
Base module to allow exchanging files via storage backend (eg: SFTP).
""",
"version": "15.0.1.1.0",
- "development_status": "Alpha",
+ "development_status": "Beta",
"license": "LGPL-3",
"website": "https://github.com/OCA/edi",
"author": "ACSONE,Odoo Community Association (OCA)",
From df13900246294ed47dd3022ba72986f169268cc1 Mon Sep 17 00:00:00 2001
From: OCA-git-bot
Date: Tue, 12 Apr 2022 14:39:57 +0000
Subject: [PATCH 39/77] [UPD] README.rst
---
edi_storage_oca/README.rst | 7 +------
edi_storage_oca/__manifest__.py | 2 +-
edi_storage_oca/static/description/index.html | 8 +-------
3 files changed, 3 insertions(+), 14 deletions(-)
diff --git a/edi_storage_oca/README.rst b/edi_storage_oca/README.rst
index 335f991fe..c182ec9be 100644
--- a/edi_storage_oca/README.rst
+++ b/edi_storage_oca/README.rst
@@ -23,7 +23,7 @@ EDI Storage backend support
:target: https://runbot.odoo-community.org/runbot/226/15.0
:alt: Try me on Runbot
-|badge1| |badge2| |badge3| |badge4| |badge5|
+|badge1| |badge2| |badge3| |badge4| |badge5|
Allow exchange files using storage backends from `OCA/storage`.
@@ -49,11 +49,6 @@ Inside this folder you have this hierarchy::
The storage handlers take care of reading files and putting files
in/from the right place and update exchange records data accordingly.
-.. IMPORTANT::
- This is an alpha version, the data model and design can change at any time without warning.
- Only for development or testing purpose, do not use in production.
- `More details on development status `_
-
**Table of contents**
.. contents::
diff --git a/edi_storage_oca/__manifest__.py b/edi_storage_oca/__manifest__.py
index 92310bce1..6eeee4e89 100644
--- a/edi_storage_oca/__manifest__.py
+++ b/edi_storage_oca/__manifest__.py
@@ -7,7 +7,7 @@
"summary": """
Base module to allow exchanging files via storage backend (eg: SFTP).
""",
- "version": "15.0.1.1.0",
+ "version": "15.0.1.1.1",
"development_status": "Beta",
"license": "LGPL-3",
"website": "https://github.com/OCA/edi",
diff --git a/edi_storage_oca/static/description/index.html b/edi_storage_oca/static/description/index.html
index 8fa1825de..d08dd56bb 100644
--- a/edi_storage_oca/static/description/index.html
+++ b/edi_storage_oca/static/description/index.html
@@ -367,7 +367,7 @@ EDI Storage backend support
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
-

+

Allow exchange files using storage backends from OCA/storage.
This module adds a storage backend relation on the EDI backend.
There you can configure the backend to be used (most often and SFTP)
@@ -389,12 +389,6 @@
EDI Storage backend support
The storage handlers take care of reading files and putting files
in/from the right place and update exchange records data accordingly.
-
-
Important
-
This is an alpha version, the data model and design can change at any time without warning.
-Only for development or testing purpose, do not use in production.
-More details on development status
-
Table of contents
From 67f110e7239390396c5f933f02cbeaada0649ce3 Mon Sep 17 00:00:00 2001
From: jcoux
Date: Tue, 17 May 2022 16:50:06 +0200
Subject: [PATCH 40/77] edi_storage_oca: improve error handling
When trying to get done/failed directories,
if we don't found it on SFTP server, we ignore the error.
Current implementation only ignores FileNotFoundError,
but depending on the SFTP server, we can have OSError.
---
edi_storage_oca/__manifest__.py | 2 +-
edi_storage_oca/components/base.py | 19 ++++++++++++++++++-
2 files changed, 19 insertions(+), 2 deletions(-)
diff --git a/edi_storage_oca/__manifest__.py b/edi_storage_oca/__manifest__.py
index 6eeee4e89..d9715bb91 100644
--- a/edi_storage_oca/__manifest__.py
+++ b/edi_storage_oca/__manifest__.py
@@ -7,7 +7,7 @@
"summary": """
Base module to allow exchanging files via storage backend (eg: SFTP).
""",
- "version": "15.0.1.1.1",
+ "version": "15.0.1.1.2",
"development_status": "Beta",
"license": "LGPL-3",
"website": "https://github.com/OCA/edi",
diff --git a/edi_storage_oca/components/base.py b/edi_storage_oca/components/base.py
index 5010e6844..40e1dcb2f 100644
--- a/edi_storage_oca/components/base.py
+++ b/edi_storage_oca/components/base.py
@@ -1,11 +1,13 @@
# Copyright 2020 ACSONE
# @author: Simone Orsi
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
-
+import logging
from pathlib import PurePath
from odoo.addons.component.core import AbstractComponent
+_logger = logging.getLogger(__file__)
+
class EDIStorageComponentMixin(AbstractComponent):
@@ -65,4 +67,19 @@ def _get_remote_file(self, state, filename=None, binary=False):
# (the date will never match)
return self.storage.get(path.as_posix(), binary=binary)
except FileNotFoundError:
+ _logger.info(
+ "Ignored FileNotFoundError when trying "
+ "to get file %s into path %s for state %s",
+ filename,
+ path,
+ state,
+ )
+ return None
+ except OSError:
+ _logger.info(
+ "Ignored OSError when trying to get file %s into path %s for state %s",
+ filename,
+ path,
+ state,
+ )
return None
From c33fa860271279450c7de33e0bab432ff629f1be Mon Sep 17 00:00:00 2001
From: Simone Orsi
Date: Fri, 6 May 2022 12:20:57 +0200
Subject: [PATCH 41/77] edi_storage: path configurable by type and param
By setting specific storage path on exchange type advanced settings, we
can configure specific path for each exchange type (input or output).
Path can also be configured by config parameters to allow to easily set
different values depending of the environment.
---
edi_storage_oca/__manifest__.py | 2 +-
edi_storage_oca/components/base.py | 18 ++++-
edi_storage_oca/components/listener.py | 20 ++++--
edi_storage_oca/components/receive.py | 4 +-
edi_storage_oca/components/send.py | 5 +-
edi_storage_oca/models/edi_backend.py | 16 +++--
edi_storage_oca/models/edi_exchange_type.py | 41 ++++++++++++
edi_storage_oca/tests/__init__.py | 1 +
edi_storage_oca/tests/common.py | 15 +++--
edi_storage_oca/tests/test_components_base.py | 12 ++--
.../tests/test_edi_backend_storage.py | 13 ++--
edi_storage_oca/tests/test_exchange_type.py | 67 +++++++++++++++++++
12 files changed, 179 insertions(+), 35 deletions(-)
create mode 100644 edi_storage_oca/tests/test_exchange_type.py
diff --git a/edi_storage_oca/__manifest__.py b/edi_storage_oca/__manifest__.py
index d9715bb91..2e490068d 100644
--- a/edi_storage_oca/__manifest__.py
+++ b/edi_storage_oca/__manifest__.py
@@ -7,7 +7,7 @@
"summary": """
Base module to allow exchanging files via storage backend (eg: SFTP).
""",
- "version": "15.0.1.1.2",
+ "version": "15.0.1.2.0",
"development_status": "Beta",
"license": "LGPL-3",
"website": "https://github.com/OCA/edi",
diff --git a/edi_storage_oca/components/base.py b/edi_storage_oca/components/base.py
index 40e1dcb2f..db1a2c8a3 100644
--- a/edi_storage_oca/components/base.py
+++ b/edi_storage_oca/components/base.py
@@ -1,4 +1,5 @@
# Copyright 2020 ACSONE
+# Copyright 2022 Camptocamp
# @author: Simone Orsi
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
import logging
@@ -50,8 +51,22 @@ def _remote_file_path(self, direction, state, filename):
:param filename: string for file name
:return: PurePath object
"""
+ _logger.warning(
+ "Call of deprecated function `_remote_file_path`. "
+ "Please use `_get_remote_file_path` instead.",
+ )
return self._dir_by_state(direction, state) / filename.strip("/ ")
+ def _get_remote_file_path(self, state, filename=None):
+ """Retrieve remote path for current exchange record."""
+ filename = filename or self.exchange_record.exchange_filename
+ direction = self.exchange_record.direction
+ directory = self._dir_by_state(direction, state).as_posix()
+ path = self.exchange_record.type_id._storage_fullpath(
+ directory=directory, filename=filename
+ )
+ return path
+
def _get_remote_file(self, state, filename=None, binary=False):
"""Get file for current exchange_record in the given destination state.
@@ -59,8 +74,7 @@ def _get_remote_file(self, state, filename=None, binary=False):
:param filename: custom file name, exchange_record filename used by default
:return: remote file content as string
"""
- filename = filename or self.exchange_record.exchange_filename
- path = self._remote_file_path(self.exchange_record.direction, state, filename)
+ path = self._get_remote_file_path(state, filename=filename)
try:
# TODO: support match via pattern (eg: filename-prefix-*)
# otherwise is impossible to retrieve input files and acks
diff --git a/edi_storage_oca/components/listener.py b/edi_storage_oca/components/listener.py
index 7b551c81e..54ad1ec01 100644
--- a/edi_storage_oca/components/listener.py
+++ b/edi_storage_oca/components/listener.py
@@ -34,9 +34,15 @@ def on_edi_exchange_done(self, record):
res = False
if record.direction == "input" and storage:
file = record.exchange_filename
- pending_dir = record.backend_id.input_dir_pending
- done_dir = record.backend_id.input_dir_done
- error_dir = record.backend_id.input_dir_error
+ pending_dir = record.type_id._storage_fullpath(
+ record.backend_id.input_dir_pending
+ ).as_posix()
+ done_dir = record.type_id._storage_fullpath(
+ record.backend_id.input_dir_done
+ ).as_posix()
+ error_dir = record.type_id._storage_fullpath(
+ record.backend_id.input_dir_error
+ ).as_posix()
if not done_dir:
return res
res = self._move_file(storage, pending_dir, done_dir, file)
@@ -52,8 +58,12 @@ def on_edi_exchange_error(self, record):
res = False
if record.direction == "input" and storage:
file = record.exchange_filename
- pending_dir = record.backend_id.input_dir_pending
- error_dir = record.backend_id.input_dir_error
+ pending_dir = record.type_id._storage_fullpath(
+ record.backend_id.input_dir_pending
+ ).as_posix()
+ error_dir = record.type_id._storage_fullpath(
+ record.backend_id.input_dir_error
+ ).as_posix()
if error_dir:
res = self._move_file(storage, pending_dir, error_dir, file)
return res
diff --git a/edi_storage_oca/components/receive.py b/edi_storage_oca/components/receive.py
index 0061ac1d9..b0ab8257e 100644
--- a/edi_storage_oca/components/receive.py
+++ b/edi_storage_oca/components/receive.py
@@ -14,8 +14,6 @@ class EDIStorageReceiveComponent(Component):
_usage = "storage.receive"
def receive(self):
- direction = self.exchange_record.direction
- filename = self.exchange_record.exchange_filename
- path = self._remote_file_path(direction, "pending", filename)
+ path = self._get_remote_file_path("pending")
filedata = self.storage.get(path.as_posix())
return filedata
diff --git a/edi_storage_oca/components/send.py b/edi_storage_oca/components/send.py
index cbb6ed309..95c6ecdc6 100644
--- a/edi_storage_oca/components/send.py
+++ b/edi_storage_oca/components/send.py
@@ -23,11 +23,8 @@ def send(self):
if not result:
# all good here
return True
-
- direction = self.exchange_record.direction
- filename = self.exchange_record.exchange_filename
filedata = self.exchange_record.exchange_file
- path = self._remote_file_path(direction, "pending", filename)
+ path = self._get_remote_file_path("pending")
self.storage.add(path.as_posix(), filedata, binary=False)
# TODO: delegate this to generic storage backend
# except paramiko.ssh_exception.AuthenticationException:
diff --git a/edi_storage_oca/models/edi_backend.py b/edi_storage_oca/models/edi_backend.py
index 5f95e3382..3669eabf9 100644
--- a/edi_storage_oca/models/edi_backend.py
+++ b/edi_storage_oca/models/edi_backend.py
@@ -145,16 +145,24 @@ def _storage_create_record_if_missing(self, exchange_type, remote_file_name):
return record.identifier
def _storage_get_input_filenames(self, exchange_type):
+ full_input_dir_pending = exchange_type._storage_fullpath(
+ self.input_dir_pending
+ ).as_posix()
if not exchange_type.exchange_filename_pattern:
- # If there is not patter, return everything
- return self.storage_id.list_files(self.input_dir_pending)
+ # If there is not pattern, return everything
+ filenames = [
+ x
+ for x in self.storage_id.list_files(full_input_dir_pending)
+ if x.strip("/")
+ ]
+ return filenames
bits = [exchange_type.exchange_filename_pattern]
if exchange_type.exchange_file_ext:
bits.append(r"\." + exchange_type.exchange_file_ext)
pattern = "".join(bits)
- full_paths = self.storage_id.find_files(pattern, self.input_dir_pending)
- pending_path_len = len(self.input_dir_pending)
+ full_paths = self.storage_id.find_files(pattern, full_input_dir_pending)
+ pending_path_len = len(full_input_dir_pending)
return [p[pending_path_len:].strip("/") for p in full_paths]
def _storage_new_exchange_record_vals(self, file_name):
diff --git a/edi_storage_oca/models/edi_exchange_type.py b/edi_storage_oca/models/edi_exchange_type.py
index 42a7f7008..258576edc 100644
--- a/edi_storage_oca/models/edi_exchange_type.py
+++ b/edi_storage_oca/models/edi_exchange_type.py
@@ -1,6 +1,8 @@
# Copyright 2021 ForgeFlow S.L. (https://www.forgeflow.com)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
+from pathlib import PurePath
+
from odoo import fields, models
@@ -19,3 +21,42 @@ class EDIExchangeType(models.Model):
"the files to be fetched from the pending directory in the related "
"storage. E.g: `.*my-type-[0-9]*.\\.csv`"
)
+
+ def _storage_path(self):
+ """Retrieve specific path for current exchange type.
+
+ In your exchange type you can pass this config:
+
+ storage:
+ # simple string
+ path: path/to/file
+
+ Or
+
+ storage:
+ # name of the param containing the path
+ path_config_param: path/to/file
+
+ Thanks to the param you could even configure it by env.
+ """
+ self.ensure_one()
+ storage_settings = self.advanced_settings.get("storage", {})
+ path = storage_settings.get("path")
+ if path:
+ return PurePath(path)
+ path_config_param = storage_settings.get("path_config_param")
+ if path_config_param:
+ icp = self.env["ir.config_parameter"].sudo()
+ path = icp.get_param(path_config_param)
+ if path:
+ return PurePath(path)
+
+ def _storage_fullpath(self, directory=None, filename=None):
+ self.ensure_one()
+ path_prefix = self._storage_path()
+ path = PurePath((directory or "").strip().rstrip("/"))
+ if path_prefix:
+ path = path_prefix / path
+ if filename:
+ path = path / filename.strip("/")
+ return path
diff --git a/edi_storage_oca/tests/__init__.py b/edi_storage_oca/tests/__init__.py
index 678979f8c..18bc95300 100644
--- a/edi_storage_oca/tests/__init__.py
+++ b/edi_storage_oca/tests/__init__.py
@@ -2,3 +2,4 @@
from . import test_components_base
from . import test_component_match
from . import test_edi_storage_listener
+from . import test_exchange_type
diff --git a/edi_storage_oca/tests/common.py b/edi_storage_oca/tests/common.py
index 385b1a5f5..cdca0f2e0 100644
--- a/edi_storage_oca/tests/common.py
+++ b/edi_storage_oca/tests/common.py
@@ -27,6 +27,7 @@ def _setup_records(cls):
"exchange_file": cls.filedata,
}
cls.record = cls.backend.create_record("test_csv_output", vals)
+ cls.record_input = cls.backend.create_record("test_csv_input", vals)
cls.fakepath = "/tmp/{}".format(cls._filename(cls))
with open(cls.fakepath, "w+b") as fakefile:
@@ -53,6 +54,11 @@ def _setup_records(cls):
["storage.check"],
work_ctx={"exchange_record": cls.record},
)
+ cls.checker_input = cls.backend._find_component(
+ cls.partner._name,
+ ["storage.check"],
+ work_ctx={"exchange_record": cls.record_input},
+ )
cls.sender = cls.backend._find_component(
cls.partner._name,
["storage.send"],
@@ -70,19 +76,16 @@ def _filename(self, record=None, ack=False):
record.type_id.ack_type_id._make_exchange_filename(record)
return record.exchange_filename
- def _file_fullpath(
- self, state, record=None, ack=False, direction=False, fname=None
- ):
+ def _file_fullpath(self, state, record=None, ack=False, fname=None, checker=None):
record = record or self.record
+ checker = checker or self.checker
if not fname:
fname = self._filename(record, ack=ack)
- if not direction:
- direction = record.direction
if state == "error-report":
# Exception as we read from the same path but w/ error suffix
state = "error"
fname += ".error"
- return (self.checker._remote_file_path(direction, state, fname)).as_posix()
+ return checker._get_remote_file_path(state, filename=fname).as_posix()
def _mocked_backend_get(self, mocked_paths, path, **kwargs):
self._storage_backend_calls.append(path)
diff --git a/edi_storage_oca/tests/test_components_base.py b/edi_storage_oca/tests/test_components_base.py
index 57abd1385..9388993b0 100644
--- a/edi_storage_oca/tests/test_components_base.py
+++ b/edi_storage_oca/tests/test_components_base.py
@@ -18,14 +18,16 @@ def test_remote_file_path(self):
(("output", "error", "foo.csv"), "demo_out/error/foo.csv"),
)
for _args, expected in to_test:
- path_obj = self.checker._remote_file_path(*_args)
+ direction, state, filename = _args
+ if direction == "input":
+ checker = self.checker_input
+ else:
+ checker = self.checker
+ path_obj = checker._get_remote_file_path(state, filename)
self.assertEqual(path_obj.as_posix(), expected)
with self.assertRaises(AssertionError):
- self.checker._remote_file_path("WHATEVER", "error", "foo.csv")
-
- with self.assertRaises(AssertionError):
- self.checker._remote_file_path("input", "WHATEVER", "foo.csv")
+ self.checker_input._get_remote_file_path("WHATEVER", "foo.csv")
def test_get_remote_file(self):
with mock.patch(STORAGE_BACKEND_MOCK_PATH + ".get") as mocked:
diff --git a/edi_storage_oca/tests/test_edi_backend_storage.py b/edi_storage_oca/tests/test_edi_backend_storage.py
index 2f396d39d..0d4793d3f 100644
--- a/edi_storage_oca/tests/test_edi_backend_storage.py
+++ b/edi_storage_oca/tests/test_edi_backend_storage.py
@@ -212,22 +212,25 @@ def test_create_input_exchange_file_from_file_received_no_pattern(self):
mocked_paths = {
input_dir: "/tmp/",
self._file_fullpath(
- "pending", direction="input", fname=file_names[0]
+ "pending", fname=file_names[0], checker=self.checker_input
): self.fakepath_input_pending_1,
self._file_fullpath(
- "pending", direction="input", fname=file_names[1]
+ "pending", fname=file_names[1], checker=self.checker_input
): self.fakepath_input_pending_2,
}
- existing = self.env["edi.exchange.record"].search_count(
+ existing_records = self.env["edi.exchange.record"].search(
[("backend_id", "=", self.backend.id), ("type_id", "=", exch_type.id)]
)
- self.assertEqual(existing, 0)
# Run cron action:
found_files = [input_dir + fname for fname in file_names]
with self._mock_storage_backend_find_files(found_files):
self._test_run_cron_pending_input(mocked_paths)
new_records = self.env["edi.exchange.record"].search(
- [("backend_id", "=", self.backend.id), ("type_id", "=", exch_type.id)]
+ [
+ ("backend_id", "=", self.backend.id),
+ ("type_id", "=", exch_type.id),
+ ("id", "not in", existing_records.ids),
+ ]
)
self.assertEqual(len(new_records), 2)
for rec in new_records:
diff --git a/edi_storage_oca/tests/test_exchange_type.py b/edi_storage_oca/tests/test_exchange_type.py
new file mode 100644
index 000000000..6b886eab3
--- /dev/null
+++ b/edi_storage_oca/tests/test_exchange_type.py
@@ -0,0 +1,67 @@
+# Copyright 2022 Camptocamp SA (https://www.camptocamp.com).
+# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
+
+from odoo.addons.edi_oca.tests.common import EDIBackendCommonTestCase
+
+
+class EDIExchangeTypeTestCase(EDIBackendCommonTestCase):
+ def _check_test_storage_fullpath(self, wanted_fullpath, directory, filename):
+ fullpath = self.exchange_type_out._storage_fullpath(directory, filename)
+ self.assertEqual(fullpath.as_posix(), wanted_fullpath)
+
+ def _do_test_storage_fullpath(self, prefix=""):
+ # Test with no directory and no filename
+ wanted_fullpath = prefix or "."
+ self._check_test_storage_fullpath(wanted_fullpath, None, None)
+
+ # Test with directory
+ directory = "test_directory"
+ wanted_fullpath = f"{prefix}/{directory}" if prefix else directory
+ self._check_test_storage_fullpath(wanted_fullpath, directory, None)
+
+ # Test with filename
+ filename = "test_filename.csv"
+ wanted_fullpath = f"{prefix}/{filename}" if prefix else filename
+ self._check_test_storage_fullpath(wanted_fullpath, None, filename)
+
+ # Test with directory and filename
+ wanted_fullpath = (
+ f"{prefix}/{directory}/{filename}" if prefix else f"{directory}/{filename}"
+ )
+ self._check_test_storage_fullpath(wanted_fullpath, directory, filename)
+
+ def test_storage_fullpath(self):
+ """
+ Test storage fullpath defined into advanced settings.
+ Example of pattern:
+ storage:
+ # simple string
+ path: path/to/file
+ # name of the param containing the path
+ path_config_param: path/to/file
+ """
+
+ # Test without any prefix
+ self._do_test_storage_fullpath()
+
+ # Force path on advanced settings
+ prefix = "prefix/path"
+ self.exchange_type_out.advanced_settings_edit = f"""
+ storage:
+ path: {prefix}
+ """
+ self._do_test_storage_fullpath(prefix=prefix)
+
+ # Force path on advanced settings using config param, but not defined
+ self.exchange_type_out.advanced_settings_edit = """
+ storage:
+ path_config_param: prefix_path_config_param
+ """
+ self._do_test_storage_fullpath()
+
+ # Define config param
+ prefix = "prefix/path/by/config/param"
+ self.env["ir.config_parameter"].sudo().set_param(
+ "prefix_path_config_param", prefix
+ )
+ self._do_test_storage_fullpath(prefix=prefix)
From 13fef0df2f4000b22e58bd52f5129a602e6ad987 Mon Sep 17 00:00:00 2001
From: OCA-git-bot
Date: Sun, 3 Sep 2023 12:44:19 +0000
Subject: [PATCH 42/77] [UPD] README.rst
---
edi_storage_oca/README.rst | 15 +++++---
edi_storage_oca/static/description/index.html | 38 ++++++++++---------
2 files changed, 29 insertions(+), 24 deletions(-)
diff --git a/edi_storage_oca/README.rst b/edi_storage_oca/README.rst
index c182ec9be..dcd6d47fd 100644
--- a/edi_storage_oca/README.rst
+++ b/edi_storage_oca/README.rst
@@ -2,10 +2,13 @@
EDI Storage backend support
===========================
-.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+..
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! source digest: sha256:f2718fe12ff53362a2da3e253d22e0641ebcd6a01195db14f8d9c8c71878080d
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
@@ -19,11 +22,11 @@ EDI Storage backend support
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/edi-15-0/edi-15-0-edi_storage_oca
:alt: Translate me on Weblate
-.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
- :target: https://runbot.odoo-community.org/runbot/226/15.0
- :alt: Try me on Runbot
+.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
+ :target: https://runboat.odoo-community.org/builds?repo=OCA/edi&target_branch=15.0
+ :alt: Try me on Runboat
-|badge1| |badge2| |badge3| |badge4| |badge5|
+|badge1| |badge2| |badge3| |badge4| |badge5|
Allow exchange files using storage backends from `OCA/storage`.
@@ -64,7 +67,7 @@ Bug Tracker
Bugs are tracked on `GitHub Issues `_.
In case of trouble, please check there if your issue has already been reported.
-If you spotted it first, help us smashing it by providing a detailed and welcomed
+If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback `_.
Do not contact contributors directly about support or help with technical issues.
diff --git a/edi_storage_oca/static/description/index.html b/edi_storage_oca/static/description/index.html
index d08dd56bb..3a323e627 100644
--- a/edi_storage_oca/static/description/index.html
+++ b/edi_storage_oca/static/description/index.html
@@ -1,20 +1,20 @@
-
+
-
+
EDI Storage backend support
-
-
EDI Storage backend support
+
+
+
+
+
+
+
EDI Storage backend support
-

+

Allow exchange files using storage backends from OCA/storage.
This module adds a storage backend relation on the EDI backend. There
you can configure the backend to be used (most often and SFTP) and the
@@ -406,18 +411,18 @@
EDI Storage backend support
-
+
Go to “EDI -> EDI backend” then configure your backend to use a storage
backend.
-
+
- clean deprecated methods in the storage
-
+
Bugs are tracked on GitHub Issues.
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
@@ -425,15 +430,15 @@
Do not contact contributors directly about support or help with technical issues.
-
+
-
+
The migration of this module from 15.0 to 16.0 was financially supported
by Camptocamp.
-
+
This module is maintained by the OCA.
@@ -464,5 +469,6 @@
+
From 386db9f65012edb7aba448ada927035fee916b38 Mon Sep 17 00:00:00 2001
From: Hector Vior
Date: Sat, 6 Dec 2025 21:21:46 +0100
Subject: [PATCH 63/77] [FIX] edi_storage_oca: Remove legacy storage component
code
---
edi_storage_oca/models/edi_backend.py | 21 ---------------------
1 file changed, 21 deletions(-)
diff --git a/edi_storage_oca/models/edi_backend.py b/edi_storage_oca/models/edi_backend.py
index ba983524f..e6767a789 100644
--- a/edi_storage_oca/models/edi_backend.py
+++ b/edi_storage_oca/models/edi_backend.py
@@ -59,27 +59,6 @@ class EDIBackend(models.Model):
"Output error directory", help="Path to folder for error operations"
)
- def _get_component_usage_candidates(self, exchange_record, key):
- candidates = super()._get_component_usage_candidates(exchange_record, key)
- if not self.storage_id or key not in self._storage_actions:
- return candidates
- return [f"storage.{key}"] + candidates
-
- def _component_match_attrs(self, exchange_record, key):
- # Override to inject storage_type
- res = super()._component_match_attrs(exchange_record, key)
- if not self.storage_id or key not in self._storage_actions:
- return res
- res["storage_type"] = self.sudo().storage_id.protocol
- return res
-
- def _component_sort_key(self, component_class):
- res = super()._component_sort_key(component_class)
- # Override to give precedence by storage_type when needed.
- if not self.storage_id:
- return res
- return (1 if getattr(component_class, "_storage_type", False) else 0,) + res
-
def _storage_cron_check_pending_input(self, **kw):
for backend in self:
backend._storage_check_pending_input(**kw)
From a9f086a17430ef9792c5810b1d83c0ed12a0aafa Mon Sep 17 00:00:00 2001
From: JordiMForgeFlow
Date: Tue, 9 Dec 2025 11:35:36 +0100
Subject: [PATCH 64/77] [FIX] edi_storage_oca: fix migration script
---
edi_storage_oca/migrations/18.0.1.0.0/post-migration.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/edi_storage_oca/migrations/18.0.1.0.0/post-migration.py b/edi_storage_oca/migrations/18.0.1.0.0/post-migration.py
index 7f1454af1..245c80bc4 100644
--- a/edi_storage_oca/migrations/18.0.1.0.0/post-migration.py
+++ b/edi_storage_oca/migrations/18.0.1.0.0/post-migration.py
@@ -42,6 +42,7 @@ def migrate(env, version):
AND edi_backend.backend_type_id = edi_exchange_type.backend_type_id
)
AND edi_backend.active IS TRUE
+ )
)
""",
)
From c5daa2c6fa086051446cf851d8f5cc57b5fecda9 Mon Sep 17 00:00:00 2001
From: OCA-git-bot
Date: Tue, 9 Dec 2025 11:16:17 +0000
Subject: [PATCH 65/77] [BOT] post-merge updates
---
edi_storage_oca/README.rst | 2 +-
edi_storage_oca/__manifest__.py | 2 +-
edi_storage_oca/static/description/index.html | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/edi_storage_oca/README.rst b/edi_storage_oca/README.rst
index 7457af899..b3fb70c5c 100644
--- a/edi_storage_oca/README.rst
+++ b/edi_storage_oca/README.rst
@@ -11,7 +11,7 @@ EDI Storage backend support
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- !! source digest: sha256:c9c2898d23ca2c445e2088cefb226956d525da3fb8928068c1c4bcbb9d668f3c
+ !! source digest: sha256:a485ae05d5e7c51de633c8061f680348cad8cac308760bffb793e179e70c5c38
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
diff --git a/edi_storage_oca/__manifest__.py b/edi_storage_oca/__manifest__.py
index 151985031..95c5186ce 100644
--- a/edi_storage_oca/__manifest__.py
+++ b/edi_storage_oca/__manifest__.py
@@ -7,7 +7,7 @@
"summary": """
Base module to allow exchanging files via storage backend (eg: SFTP).
""",
- "version": "18.0.1.0.0",
+ "version": "18.0.1.0.1",
"development_status": "Beta",
"license": "LGPL-3",
"website": "https://github.com/OCA/edi-framework",
diff --git a/edi_storage_oca/static/description/index.html b/edi_storage_oca/static/description/index.html
index 080e90e83..fb16aaae0 100644
--- a/edi_storage_oca/static/description/index.html
+++ b/edi_storage_oca/static/description/index.html
@@ -372,7 +372,7 @@ EDI Storage backend support
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-!! source digest: sha256:c9c2898d23ca2c445e2088cefb226956d525da3fb8928068c1c4bcbb9d668f3c
+!! source digest: sha256:a485ae05d5e7c51de633c8061f680348cad8cac308760bffb793e179e70c5c38
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Allow exchange files using storage backends from OCA/storage.
From a6bd6512e3081295d50563e95976f2e958aff1df Mon Sep 17 00:00:00 2001
From: OCA-git-bot
Date: Tue, 9 Dec 2025 12:44:24 +0000
Subject: [PATCH 66/77] [BOT] post-merge updates
---
edi_storage_oca/README.rst | 2 +-
edi_storage_oca/__manifest__.py | 2 +-
edi_storage_oca/static/description/index.html | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/edi_storage_oca/README.rst b/edi_storage_oca/README.rst
index b3fb70c5c..52b9f7ece 100644
--- a/edi_storage_oca/README.rst
+++ b/edi_storage_oca/README.rst
@@ -11,7 +11,7 @@ EDI Storage backend support
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- !! source digest: sha256:a485ae05d5e7c51de633c8061f680348cad8cac308760bffb793e179e70c5c38
+ !! source digest: sha256:ae52c0238c8bab278e4e2d6a48d86d4222672b893df1f171be046f3b7a3c58ae
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
diff --git a/edi_storage_oca/__manifest__.py b/edi_storage_oca/__manifest__.py
index 95c5186ce..39f47e247 100644
--- a/edi_storage_oca/__manifest__.py
+++ b/edi_storage_oca/__manifest__.py
@@ -7,7 +7,7 @@
"summary": """
Base module to allow exchanging files via storage backend (eg: SFTP).
""",
- "version": "18.0.1.0.1",
+ "version": "18.0.1.0.2",
"development_status": "Beta",
"license": "LGPL-3",
"website": "https://github.com/OCA/edi-framework",
diff --git a/edi_storage_oca/static/description/index.html b/edi_storage_oca/static/description/index.html
index fb16aaae0..87b66f49d 100644
--- a/edi_storage_oca/static/description/index.html
+++ b/edi_storage_oca/static/description/index.html
@@ -372,7 +372,7 @@ EDI Storage backend support
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-!! source digest: sha256:a485ae05d5e7c51de633c8061f680348cad8cac308760bffb793e179e70c5c38
+!! source digest: sha256:ae52c0238c8bab278e4e2d6a48d86d4222672b893df1f171be046f3b7a3c58ae
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Allow exchange files using storage backends from OCA/storage.
From a557fe75f57824c5316f728145e2a2fa0bb5ada3 Mon Sep 17 00:00:00 2001
From: Arnau
Date: Tue, 13 Jan 2026 15:37:24 +0100
Subject: [PATCH 67/77] [FIX] edi_storage_oca: ls whitespace filename with ftp
protocol
---
edi_storage_oca/utils.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/edi_storage_oca/utils.py b/edi_storage_oca/utils.py
index 0b93e5da0..0b791c09b 100644
--- a/edi_storage_oca/utils.py
+++ b/edi_storage_oca/utils.py
@@ -48,6 +48,8 @@ def list_files(storage, relative_path="", pattern=False):
if pattern:
relative_path = fs.sep.join([relative_path, pattern])
return fs.glob(relative_path)
+ if fs.protocol == "ftp" and fs.ftp:
+ return fs.ftp.nlst(relative_path)
return fs.ls(relative_path, detail=False)
From 8021d65c4925affc9472483c14c50450e10b74cd Mon Sep 17 00:00:00 2001
From: Arnau
Date: Tue, 12 May 2026 14:19:38 +0200
Subject: [PATCH 68/77] [FIX] edi_storage_oca: raise error if file is missing
when performing receive
---
.../models/edi_oca_storage_handler.py | 34 ++++++++++++++++---
1 file changed, 29 insertions(+), 5 deletions(-)
diff --git a/edi_storage_oca/models/edi_oca_storage_handler.py b/edi_storage_oca/models/edi_oca_storage_handler.py
index ce0f7682f..3e2a3d938 100644
--- a/edi_storage_oca/models/edi_oca_storage_handler.py
+++ b/edi_storage_oca/models/edi_oca_storage_handler.py
@@ -9,6 +9,7 @@
from pathlib import PurePath
from odoo import models
+from odoo.exceptions import UserError
from .. import utils
@@ -34,7 +35,9 @@ def send(self, exchange_record):
utils.add_file(exchange_record.backend_id.storage_id, path.as_posix(), filedata)
def receive(self, exchange_record):
- return self._get_remote_file(exchange_record, "pending", binary=True)
+ return self._get_remote_file(
+ exchange_record, "pending", binary=True, raise_if_missing=True
+ )
def _dir_by_state(self, backend, direction, state):
"""Return remote directory path by direction and state.
@@ -61,11 +64,21 @@ def _get_remote_file_path(self, exchange_record, state, filename=None):
)
return path
- def _get_remote_file(self, exchange_record, state, filename=None, binary=False):
+ def _get_remote_file(
+ self,
+ exchange_record,
+ state,
+ filename=None,
+ binary=False,
+ raise_if_missing=False,
+ ):
"""Get file for current exchange_record in the given destination state.
:param state: string ("pending", "done", "error")
:param filename: custom file name, exchange_record filename used by default
+ :param raise_if_missing: when True, storage errors are re-raised as
+ swallable exceptions so the caller transitions the record to an
+ error state instead of treating "no content" as a successful read.
:return: remote file content as string
"""
path = self._get_remote_file_path(exchange_record, state, filename=filename)
@@ -84,14 +97,25 @@ def _get_remote_file(self, exchange_record, state, filename=None, binary=False):
path,
state,
)
+ if raise_if_missing:
+ raise
return None
- except OSError:
- _logger.info(
- "Ignored OSError when trying to get file %s into path %s for state %s",
+ except OSError as err:
+ _logger.warning(
+ "OSError when trying to get file %s into path %s for state %s: %s",
filename,
path,
state,
+ err,
)
+ if raise_if_missing:
+ raise UserError(
+ self.env._(
+ "Storage error while reading %(path)s: %(error)s",
+ path=path.as_posix(),
+ error=str(err),
+ )
+ ) from err
return None
def check(self, exchange_record):
From 92de08643b5d571a264373b87c0ca24ab372e341 Mon Sep 17 00:00:00 2001
From: OCA-git-bot
Date: Wed, 27 May 2026 06:41:14 +0000
Subject: [PATCH 69/77] [BOT] post-merge updates
---
edi_storage_oca/README.rst | 2 +-
edi_storage_oca/__manifest__.py | 2 +-
edi_storage_oca/static/description/index.html | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/edi_storage_oca/README.rst b/edi_storage_oca/README.rst
index 52b9f7ece..ff34c4027 100644
--- a/edi_storage_oca/README.rst
+++ b/edi_storage_oca/README.rst
@@ -11,7 +11,7 @@ EDI Storage backend support
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- !! source digest: sha256:ae52c0238c8bab278e4e2d6a48d86d4222672b893df1f171be046f3b7a3c58ae
+ !! source digest: sha256:2511ea918ba213d754909a73c92b80e89a7ab320816acd446f9e2dcf26c538d0
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
diff --git a/edi_storage_oca/__manifest__.py b/edi_storage_oca/__manifest__.py
index 39f47e247..1f7b1d333 100644
--- a/edi_storage_oca/__manifest__.py
+++ b/edi_storage_oca/__manifest__.py
@@ -7,7 +7,7 @@
"summary": """
Base module to allow exchanging files via storage backend (eg: SFTP).
""",
- "version": "18.0.1.0.2",
+ "version": "18.0.1.0.3",
"development_status": "Beta",
"license": "LGPL-3",
"website": "https://github.com/OCA/edi-framework",
diff --git a/edi_storage_oca/static/description/index.html b/edi_storage_oca/static/description/index.html
index 87b66f49d..9d7a9d130 100644
--- a/edi_storage_oca/static/description/index.html
+++ b/edi_storage_oca/static/description/index.html
@@ -372,7 +372,7 @@ EDI Storage backend support
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-!! source digest: sha256:ae52c0238c8bab278e4e2d6a48d86d4222672b893df1f171be046f3b7a3c58ae
+!! source digest: sha256:2511ea918ba213d754909a73c92b80e89a7ab320816acd446f9e2dcf26c538d0
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Allow exchange files using storage backends from OCA/storage.
From 121f538972a1b8cbea3c053f36adc62706d4de17 Mon Sep 17 00:00:00 2001
From: oca-ci
Date: Wed, 27 May 2026 06:49:48 +0000
Subject: [PATCH 70/77] [UPD] Update edi_storage_oca.pot
---
edi_storage_oca/i18n/edi_storage_oca.pot | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/edi_storage_oca/i18n/edi_storage_oca.pot b/edi_storage_oca/i18n/edi_storage_oca.pot
index cba90f769..7b3703040 100644
--- a/edi_storage_oca/i18n/edi_storage_oca.pot
+++ b/edi_storage_oca/i18n/edi_storage_oca.pot
@@ -114,6 +114,12 @@ msgstr ""
msgid "Storage Handler for EDI"
msgstr ""
+#. module: edi_storage_oca
+#. odoo-python
+#: code:addons/edi_storage_oca/models/edi_oca_storage_handler.py:0
+msgid "Storage error while reading %(path)s: %(error)s"
+msgstr ""
+
#. module: edi_storage_oca
#: model:ir.model.fields,help:edi_storage_oca.field_edi_backend__storage_id
msgid "Storage for in-out files"
From 0dbd5dfdbf43a7ac9f11bf16a76b09e32e7c4de7 Mon Sep 17 00:00:00 2001
From: OCA-git-bot
Date: Wed, 27 May 2026 06:56:45 +0000
Subject: [PATCH 71/77] [BOT] post-merge updates
---
edi_storage_oca/README.rst | 2 +-
edi_storage_oca/__manifest__.py | 2 +-
edi_storage_oca/static/description/index.html | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/edi_storage_oca/README.rst b/edi_storage_oca/README.rst
index ff34c4027..f880503b3 100644
--- a/edi_storage_oca/README.rst
+++ b/edi_storage_oca/README.rst
@@ -11,7 +11,7 @@ EDI Storage backend support
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- !! source digest: sha256:2511ea918ba213d754909a73c92b80e89a7ab320816acd446f9e2dcf26c538d0
+ !! source digest: sha256:ca9c4e0d1ecd9744b5cfa517900a5dace2a206007360ce2c1299dc1da5910bfa
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
diff --git a/edi_storage_oca/__manifest__.py b/edi_storage_oca/__manifest__.py
index 1f7b1d333..87b4c5f92 100644
--- a/edi_storage_oca/__manifest__.py
+++ b/edi_storage_oca/__manifest__.py
@@ -7,7 +7,7 @@
"summary": """
Base module to allow exchanging files via storage backend (eg: SFTP).
""",
- "version": "18.0.1.0.3",
+ "version": "18.0.1.0.4",
"development_status": "Beta",
"license": "LGPL-3",
"website": "https://github.com/OCA/edi-framework",
diff --git a/edi_storage_oca/static/description/index.html b/edi_storage_oca/static/description/index.html
index 9d7a9d130..fe56fe211 100644
--- a/edi_storage_oca/static/description/index.html
+++ b/edi_storage_oca/static/description/index.html
@@ -372,7 +372,7 @@ EDI Storage backend support
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-!! source digest: sha256:2511ea918ba213d754909a73c92b80e89a7ab320816acd446f9e2dcf26c538d0
+!! source digest: sha256:ca9c4e0d1ecd9744b5cfa517900a5dace2a206007360ce2c1299dc1da5910bfa
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Allow exchange files using storage backends from OCA/storage.
From a9dec245a9f3b32ad388305196a51331c8c9e105 Mon Sep 17 00:00:00 2001
From: Weblate
Date: Wed, 27 May 2026 06:56:55 +0000
Subject: [PATCH 72/77] Update translation files
Updated by "Update PO files to match POT (msgmerge)" hook in Weblate.
Translation: edi-framework-18.0/edi-framework-18.0-edi_storage_oca
Translate-URL: https://translation.odoo-community.org/projects/edi-framework-18-0/edi-framework-18-0-edi_storage_oca/
---
edi_storage_oca/i18n/it.po | 21 ++++++++++++++++++---
1 file changed, 18 insertions(+), 3 deletions(-)
diff --git a/edi_storage_oca/i18n/it.po b/edi_storage_oca/i18n/it.po
index e35011ab4..ce7ead09f 100644
--- a/edi_storage_oca/i18n/it.po
+++ b/edi_storage_oca/i18n/it.po
@@ -28,7 +28,6 @@ msgstr "Tipo scambio EDI"
#. module: edi_storage_oca
#: model:ir.actions.server,name:edi_storage_oca.cron_check_storage_pending_input_ir_actions_server
-#: model:ir.cron,cron_name:edi_storage_oca.cron_check_storage_pending_input
msgid "EDI backend storage check pending input"
msgstr "Controllo ingressi in attesa deposito backend EDI"
@@ -51,8 +50,13 @@ msgstr "Deposito FS"
#. module: edi_storage_oca
#: model:ir.model.fields,help:edi_storage_oca.field_edi_exchange_type__exchange_filename_pattern
msgid ""
-"For output exchange types this should be a formatting string with the following variables available (to be used between brackets, `{}`): `exchange_record`, `record_name`, `type` and `dt`. For instance, a valid string would be {record_name}-{type.code}-{dt}\n"
-"For input exchange types related to storage backends it should be a regex expression to filter the files to be fetched from the pending directory in the related storage. E.g: `.*my-type-[0-9]*.\\.csv`"
+"For output exchange types this should be a formatting string with the "
+"following variables available (to be used between brackets, `{}`): "
+"`exchange_record`, `record_name`, `type` and `dt`. For instance, a valid "
+"string would be {record_name}-{type.code}-{dt}\n"
+"For input exchange types related to storage backends it should be a regex "
+"expression to filter the files to be fetched from the pending directory in "
+"the related storage. E.g: `.*my-type-[0-9]*.\\.csv`"
msgstr ""
"Per i tipi di scambio in uscita questo dovrebbe essere una stringa di "
"formattazione con le seguenti variabili disponibili (da utilizzare tra "
@@ -120,6 +124,17 @@ msgstr "Record creato da un file trovato nel deposito FS"
msgid "Storage"
msgstr "Deposito"
+#. module: edi_storage_oca
+#: model:ir.model,name:edi_storage_oca.model_edi_oca_storage_handler
+msgid "Storage Handler for EDI"
+msgstr ""
+
+#. module: edi_storage_oca
+#. odoo-python
+#: code:addons/edi_storage_oca/models/edi_oca_storage_handler.py:0
+msgid "Storage error while reading %(path)s: %(error)s"
+msgstr ""
+
#. module: edi_storage_oca
#: model:ir.model.fields,help:edi_storage_oca.field_edi_backend__storage_id
msgid "Storage for in-out files"
From 23643be20e820719f01dd6c797376ebc446fe474 Mon Sep 17 00:00:00 2001
From: Arnau
Date: Tue, 19 May 2026 13:01:31 +0200
Subject: [PATCH 73/77] [IMP] edi_storage_oca: post-process input file via conf
---
edi_storage_oca/__manifest__.py | 1 +
edi_storage_oca/data/edi_configuration.xml | 29 +++++++
edi_storage_oca/models/edi_backend.py | 50 +++++++++++
edi_storage_oca/readme/CONFIGURE.md | 10 +++
edi_storage_oca/tests/__init__.py | 1 +
edi_storage_oca/tests/test_event_listener.py | 88 ++++++++++++++++++++
edi_storage_oca/utils.py | 12 +++
7 files changed, 191 insertions(+)
create mode 100644 edi_storage_oca/data/edi_configuration.xml
create mode 100644 edi_storage_oca/readme/CONFIGURE.md
create mode 100644 edi_storage_oca/tests/test_event_listener.py
diff --git a/edi_storage_oca/__manifest__.py b/edi_storage_oca/__manifest__.py
index 87b4c5f92..7f971e130 100644
--- a/edi_storage_oca/__manifest__.py
+++ b/edi_storage_oca/__manifest__.py
@@ -15,6 +15,7 @@
"depends": ["edi_core_oca", "fs_storage"],
"data": [
"data/cron.xml",
+ "data/edi_configuration.xml",
"security/ir_model_access.xml",
"views/edi_backend_views.xml",
],
diff --git a/edi_storage_oca/data/edi_configuration.xml b/edi_storage_oca/data/edi_configuration.xml
new file mode 100644
index 000000000..53956f2eb
--- /dev/null
+++ b/edi_storage_oca/data/edi_configuration.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ Storage: move input file to done
+
+
+
+ record.backend_id._storage_on_edi_exchange_done(record)
+
+
+
+ Storage: move input file to error
+
+
+
+ record.backend_id._storage_on_edi_exchange_error(record)
+
+
diff --git a/edi_storage_oca/models/edi_backend.py b/edi_storage_oca/models/edi_backend.py
index e6767a789..a6d5b40fc 100644
--- a/edi_storage_oca/models/edi_backend.py
+++ b/edi_storage_oca/models/edi_backend.py
@@ -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)
diff --git a/edi_storage_oca/readme/CONFIGURE.md b/edi_storage_oca/readme/CONFIGURE.md
new file mode 100644
index 000000000..4050a7424
--- /dev/null
+++ b/edi_storage_oca/readme/CONFIGURE.md
@@ -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.
diff --git a/edi_storage_oca/tests/__init__.py b/edi_storage_oca/tests/__init__.py
index 0a73fb8a1..54510bc1a 100644
--- a/edi_storage_oca/tests/__init__.py
+++ b/edi_storage_oca/tests/__init__.py
@@ -1,2 +1,3 @@
from . import test_edi_backend_storage
+from . import test_event_listener
from . import test_exchange_type
diff --git a/edi_storage_oca/tests/test_event_listener.py b/edi_storage_oca/tests/test_event_listener.py
new file mode 100644
index 000000000..af9be8741
--- /dev/null
+++ b/edi_storage_oca/tests/test_event_listener.py
@@ -0,0 +1,88 @@
+# Copyright 2020 ACSONE
+# @author: Simone Orsi
+# 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)
diff --git a/edi_storage_oca/utils.py b/edi_storage_oca/utils.py
index 0b791c09b..179c25904 100644
--- a/edi_storage_oca/utils.py
+++ b/edi_storage_oca/utils.py
@@ -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):
@@ -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
From 8bef029996948b05ac11aace59ef9de2d3fcfe15 Mon Sep 17 00:00:00 2001
From: mymage
Date: Fri, 29 May 2026 11:39:58 +0000
Subject: [PATCH 74/77] Translated using Weblate (Italian)
Currently translated at 100.0% (21 of 21 strings)
Translation: edi-framework-18.0/edi-framework-18.0-edi_storage_oca
Translate-URL: https://translation.odoo-community.org/projects/edi-framework-18-0/edi-framework-18-0-edi_storage_oca/it/
---
edi_storage_oca/i18n/it.po | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/edi_storage_oca/i18n/it.po b/edi_storage_oca/i18n/it.po
index ce7ead09f..64925eccc 100644
--- a/edi_storage_oca/i18n/it.po
+++ b/edi_storage_oca/i18n/it.po
@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
-"PO-Revision-Date: 2024-11-15 17:06+0000\n"
+"PO-Revision-Date: 2026-05-29 13:46+0000\n"
"Last-Translator: mymage \n"
"Language-Team: none\n"
"Language: it\n"
@@ -14,7 +14,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
-"X-Generator: Weblate 5.6.2\n"
+"X-Generator: Weblate 5.15.2\n"
#. module: edi_storage_oca
#: model:ir.model,name:edi_storage_oca.model_edi_backend
@@ -127,13 +127,13 @@ msgstr "Deposito"
#. module: edi_storage_oca
#: model:ir.model,name:edi_storage_oca.model_edi_oca_storage_handler
msgid "Storage Handler for EDI"
-msgstr ""
+msgstr "Gestore archivio per EDI"
#. module: edi_storage_oca
#. odoo-python
#: code:addons/edi_storage_oca/models/edi_oca_storage_handler.py:0
msgid "Storage error while reading %(path)s: %(error)s"
-msgstr ""
+msgstr "Errore archivio nella lettura di %(path)s: %(error)s"
#. module: edi_storage_oca
#: model:ir.model.fields,help:edi_storage_oca.field_edi_backend__storage_id
From 21394fc1ffd58df3371c57581d9632a15edddd38 Mon Sep 17 00:00:00 2001
From: OCA-git-bot
Date: Wed, 3 Jun 2026 14:28:54 +0000
Subject: [PATCH 75/77] [BOT] post-merge updates
---
edi_storage_oca/README.rst | 16 ++++++-
edi_storage_oca/__manifest__.py | 2 +-
edi_storage_oca/static/description/index.html | 48 ++++++++++++-------
3 files changed, 47 insertions(+), 19 deletions(-)
diff --git a/edi_storage_oca/README.rst b/edi_storage_oca/README.rst
index f880503b3..1194f6dae 100644
--- a/edi_storage_oca/README.rst
+++ b/edi_storage_oca/README.rst
@@ -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
@@ -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
=====
diff --git a/edi_storage_oca/__manifest__.py b/edi_storage_oca/__manifest__.py
index 7f971e130..ea681386e 100644
--- a/edi_storage_oca/__manifest__.py
+++ b/edi_storage_oca/__manifest__.py
@@ -7,7 +7,7 @@
"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",
diff --git a/edi_storage_oca/static/description/index.html b/edi_storage_oca/static/description/index.html
index fe56fe211..77611d9c7 100644
--- a/edi_storage_oca/static/description/index.html
+++ b/edi_storage_oca/static/description/index.html
@@ -372,7 +372,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
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Allow exchange files using storage backends from OCA/storage.
@@ -398,31 +398,45 @@ EDI Storage backend support
Table of contents
+
+
+
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.
+
-
+
Go to “EDI -> EDI backend” then configure your backend to use a storage
backend.
-
+
- clean deprecated methods in the storage
-
+
Bugs are tracked on GitHub Issues.
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
@@ -430,15 +444,15 @@
Do not contact contributors directly about support or help with technical issues.
-
+
-
+
The migration of this module from 15.0 to 16.0 was financially supported
by Camptocamp.
-
+
This module is maintained by the OCA.
From 680c18fd3017fe87085fba32d3062a94a37c80e5 Mon Sep 17 00:00:00 2001
From: Sergij Pfaifer
Date: Mon, 8 Jun 2026 15:59:09 +0300
Subject: [PATCH 76/77] [IMP] edi_storage_oca: pre-commit auto fixes
---
edi_storage_oca/migrations/18.0.1.0.0/post-migration.py | 2 +-
edi_storage_oca/tests/test_event_listener.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/edi_storage_oca/migrations/18.0.1.0.0/post-migration.py b/edi_storage_oca/migrations/18.0.1.0.0/post-migration.py
index 245c80bc4..f33bd2d76 100644
--- a/edi_storage_oca/migrations/18.0.1.0.0/post-migration.py
+++ b/edi_storage_oca/migrations/18.0.1.0.0/post-migration.py
@@ -8,7 +8,7 @@ def migrate(env, version):
env.cr,
f"""
UPDATE edi_exchange_type
- SET send_model_id = { model.id }
+ SET send_model_id = {model.id}
WHERE direction = 'output'
AND send_model_id IS NULL
AND EXISTS (
diff --git a/edi_storage_oca/tests/test_event_listener.py b/edi_storage_oca/tests/test_event_listener.py
index af9be8741..2a756b9c2 100644
--- a/edi_storage_oca/tests/test_event_listener.py
+++ b/edi_storage_oca/tests/test_event_listener.py
@@ -73,7 +73,7 @@ def test_01_process_record_success(self):
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)
+ self.record._set_file_content("TEST %d")
with self._patch_move_file() as mocked:
self.record.with_context(
test_break_process="OOPS! Something went wrong :("
From 700bb1c5d0d4b1327e7bd47d8956e0b9ca2c2c36 Mon Sep 17 00:00:00 2001
From: Sergij Pfaifer
Date: Mon, 8 Jun 2026 17:04:53 +0300
Subject: [PATCH 77/77] [MIG] edi_storage_oca: Migration to 19.0
---
edi_storage_oca/README.rst | 10 ++--
edi_storage_oca/__manifest__.py | 3 +-
edi_storage_oca/demo/edi_backend_demo.xml | 14 -----
.../migrations/18.0.1.0.0/post-migration.py | 48 ----------------
edi_storage_oca/static/description/index.html | 6 +-
edi_storage_oca/tests/common.py | 27 ++++++++-
.../tests/test_edi_backend_storage.py | 14 ++++-
edi_storage_oca/tests/test_event_listener.py | 57 +++++++++++++------
8 files changed, 88 insertions(+), 91 deletions(-)
delete mode 100644 edi_storage_oca/demo/edi_backend_demo.xml
delete mode 100644 edi_storage_oca/migrations/18.0.1.0.0/post-migration.py
diff --git a/edi_storage_oca/README.rst b/edi_storage_oca/README.rst
index 1194f6dae..16a20af7c 100644
--- a/edi_storage_oca/README.rst
+++ b/edi_storage_oca/README.rst
@@ -21,13 +21,13 @@ EDI Storage backend support
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fedi--framework-lightgray.png?logo=github
- :target: https://github.com/OCA/edi-framework/tree/18.0/edi_storage_oca
+ :target: https://github.com/OCA/edi-framework/tree/19.0/edi_storage_oca
:alt: OCA/edi-framework
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
- :target: https://translation.odoo-community.org/projects/edi-framework-18-0/edi-framework-18-0-edi_storage_oca
+ :target: https://translation.odoo-community.org/projects/edi-framework-19-0/edi-framework-19-0-edi_storage_oca
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
- :target: https://runboat.odoo-community.org/builds?repo=OCA/edi-framework&target_branch=18.0
+ :target: https://runboat.odoo-community.org/builds?repo=OCA/edi-framework&target_branch=19.0
:alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5|
@@ -93,7 +93,7 @@ Bug Tracker
Bugs are tracked on `GitHub Issues `_.
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
-`feedback `_.
+`feedback `_.
Do not contact contributors directly about support or help with technical issues.
@@ -135,6 +135,6 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
-This module is part of the `OCA/edi-framework `_ project on GitHub.
+This module is part of the `OCA/edi-framework `_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/edi_storage_oca/__manifest__.py b/edi_storage_oca/__manifest__.py
index ea681386e..d0dd35013 100644
--- a/edi_storage_oca/__manifest__.py
+++ b/edi_storage_oca/__manifest__.py
@@ -7,7 +7,7 @@
"summary": """
Base module to allow exchanging files via storage backend (eg: SFTP).
""",
- "version": "18.0.1.1.0",
+ "version": "19.0.1.0.0",
"development_status": "Beta",
"license": "LGPL-3",
"website": "https://github.com/OCA/edi-framework",
@@ -19,5 +19,4 @@
"security/ir_model_access.xml",
"views/edi_backend_views.xml",
],
- "demo": ["demo/edi_backend_demo.xml"],
}
diff --git a/edi_storage_oca/demo/edi_backend_demo.xml b/edi_storage_oca/demo/edi_backend_demo.xml
deleted file mode 100644
index a8740b0d4..000000000
--- a/edi_storage_oca/demo/edi_backend_demo.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
- Storage Demo EDI backend
-
-
- demo_in/pending
- demo_in/done
- demo_in/error
- demo_out/pending
- demo_out/done
- demo_out/error
-
-
diff --git a/edi_storage_oca/migrations/18.0.1.0.0/post-migration.py b/edi_storage_oca/migrations/18.0.1.0.0/post-migration.py
deleted file mode 100644
index f33bd2d76..000000000
--- a/edi_storage_oca/migrations/18.0.1.0.0/post-migration.py
+++ /dev/null
@@ -1,48 +0,0 @@
-from openupgradelib import openupgrade
-
-
-@openupgrade.migrate()
-def migrate(env, version):
- model = env.ref("edi_storage_oca.model_edi_oca_storage_handler")
- openupgrade.logged_query(
- env.cr,
- f"""
- UPDATE edi_exchange_type
- SET send_model_id = {model.id}
- WHERE direction = 'output'
- AND send_model_id IS NULL
- AND EXISTS (
- SELECT 1
- FROM edi_backend
- WHERE (
- edi_backend.id = edi_exchange_type.backend_id
- OR (
- edi_exchange_type.backend_id IS NULL
- AND edi_backend.backend_type_id = edi_exchange_type.backend_type_id
- )
- AND edi_backend.active IS TRUE
- )
- )
- """,
- )
- openupgrade.logged_query(
- env.cr,
- f"""
- UPDATE edi_exchange_type
- SET receive_model_id = {model.id}
- WHERE direction = 'input'
- AND receive_model_id IS NULL
- AND EXISTS (
- SELECT 1
- FROM edi_backend
- WHERE (
- edi_backend.id = edi_exchange_type.backend_id
- OR (
- edi_exchange_type.backend_id IS NULL
- AND edi_backend.backend_type_id = edi_exchange_type.backend_type_id
- )
- AND edi_backend.active IS TRUE
- )
- )
- """,
- )
diff --git a/edi_storage_oca/static/description/index.html b/edi_storage_oca/static/description/index.html
index 77611d9c7..45a839ea3 100644
--- a/edi_storage_oca/static/description/index.html
+++ b/edi_storage_oca/static/description/index.html
@@ -374,7 +374,7 @@ EDI Storage backend support
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:f0ff3ee30416f5a8090d6c738df2c392381b61bc0d104c7679a147ce3b503018
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
-

+

Allow exchange files using storage backends from OCA/storage.
This module adds a storage backend relation on the EDI backend. There
you can configure the backend to be used (most often and SFTP) and the
@@ -440,7 +440,7 @@
Bugs are tracked on GitHub Issues.
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
-feedback.
+feedback.
Do not contact contributors directly about support or help with technical issues.
@@ -478,7 +478,7 @@
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
-
This module is part of the OCA/edi-framework project on GitHub.
+
This module is part of the OCA/edi-framework project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/edi_storage_oca/tests/common.py b/edi_storage_oca/tests/common.py
index d8606d4ba..64e8a159d 100644
--- a/edi_storage_oca/tests/common.py
+++ b/edi_storage_oca/tests/common.py
@@ -12,7 +12,32 @@
class TestEDIStorageBase(EDIBackendCommonTestCase):
@classmethod
def _get_backend(cls):
- return cls.env.ref("edi_storage_oca.demo_edi_backend_storage")
+ backend_type = cls.env["edi.backend.type"].create(
+ {
+ "name": "Demo EDI backend type",
+ "code": "demo_backend",
+ }
+ )
+ storage = cls.env["fs.storage"].create(
+ {
+ "name": "Odoo Filesystem Backend",
+ "protocol": "odoofs",
+ "code": "odoofs",
+ }
+ )
+ return cls.env["edi.backend"].create(
+ {
+ "name": "Storage Demo EDI backend",
+ "backend_type_id": backend_type.id,
+ "storage_id": storage.id,
+ "input_dir_pending": "demo_in/pending",
+ "input_dir_done": "demo_in/done",
+ "input_dir_error": "demo_in/error",
+ "output_dir_pending": "demo_out/pending",
+ "output_dir_done": "demo_out/done",
+ "output_dir_error": "demo_out/error",
+ }
+ )
@classmethod
def _setup_records(cls):
diff --git a/edi_storage_oca/tests/test_edi_backend_storage.py b/edi_storage_oca/tests/test_edi_backend_storage.py
index e5fbe0a21..b84bd7bed 100644
--- a/edi_storage_oca/tests/test_edi_backend_storage.py
+++ b/edi_storage_oca/tests/test_edi_backend_storage.py
@@ -141,8 +141,18 @@ def test_cron_full_flow(self):
"""Already sent, update the state via cron."""
self.record.edi_exchange_state = "output_sent"
rec1 = self.record
- partner2 = self.env.ref("base.res_partner_2")
- partner3 = self.env.ref("base.res_partner_3")
+ partner2, partner3 = self.env["res.partner"].create(
+ [
+ {
+ "name": "Acme Corporation",
+ "is_company": True,
+ },
+ {
+ "name": "Gemini Furniture",
+ "is_company": True,
+ },
+ ]
+ )
rec2 = self.record.copy(
{
"model": partner2._name,
diff --git a/edi_storage_oca/tests/test_event_listener.py b/edi_storage_oca/tests/test_event_listener.py
index 2a756b9c2..0f1221888 100644
--- a/edi_storage_oca/tests/test_event_listener.py
+++ b/edi_storage_oca/tests/test_event_listener.py
@@ -6,10 +6,9 @@
import base64
from unittest import mock
-from odoo_test_helper import FakeModelLoader
+from odoo.orm.model_classes import add_to_registry
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"
@@ -17,7 +16,32 @@
class TestStorageEventListener(EDIBackendCommonTestCase):
@classmethod
def _get_backend(cls):
- return cls.env.ref("edi_storage_oca.demo_edi_backend_storage")
+ backend_type = cls.env["edi.backend.type"].create(
+ {
+ "name": "Demo EDI backend type",
+ "code": "demo_backend",
+ }
+ )
+ storage = cls.env["fs.storage"].create(
+ {
+ "name": "Odoo Filesystem Backend",
+ "protocol": "odoofs",
+ "code": "odoofs",
+ }
+ )
+ return cls.env["edi.backend"].create(
+ {
+ "name": "Storage Demo EDI backend",
+ "backend_type_id": backend_type.id,
+ "storage_id": storage.id,
+ "input_dir_pending": "demo_in/pending",
+ "input_dir_done": "demo_in/done",
+ "input_dir_error": "demo_in/error",
+ "output_dir_pending": "demo_out/pending",
+ "output_dir_done": "demo_out/done",
+ "output_dir_error": "demo_out/error",
+ }
+ )
@classmethod
def setUpClass(cls):
@@ -36,21 +60,22 @@ def setUpClass(cls):
}
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()
+ @classmethod
+ def _setup_records(cls): # pylint:disable=missing-return
+ super()._setup_records()
+ # Load fake models ->/
+ from odoo.addons.edi_core_oca.tests.fake_models import EdiTestExecution
- self.loader.update_registry((EdiTestExecution,))
- fake_model = self.env["ir.model"].search(
- [("model", "=", "edi.framework.test.execution")]
+ add_to_registry(cls.registry, EdiTestExecution)
+ cls.registry._setup_models__(cls.env.cr, ["edi.framework.test.execution"])
+ cls.registry.init_models(
+ cls.env.cr, ["edi.framework.test.execution"], {"models_to_check": True}
)
- 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()
+ cls.addClassCleanup(cls.registry.__delitem__, "edi.framework.test.execution")
+ cls.ExecutionAbstractModel = cls.env["edi.framework.test.execution"]
+ cls.model = cls.env["ir.model"]._get("edi.framework.test.execution")
+ cls.exchange_type_in.process_model_id = cls.model
+ cls.exchange_type_in.input_validate_model_id = cls.model
def _patch_move_file(self):
return mock.patch(STORAGE_MOVE_FILE_PATH, autospec=True, return_value=True)