Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[16.0][MIG] edi_storage_oca: Migration to 16.0 #26

Merged
merged 60 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
abdf629
Add edi_storage
simahawk Nov 27, 2020
90752eb
edi_storage: add base tests for component
simahawk Nov 27, 2020
d25bceb
edi_storage: fix messages test isolation
simahawk Nov 29, 2020
8a374e2
[UPD] Update edi_storage.pot
oca-travis Nov 30, 2020
f63c0e9
edi_storage 13.0.1.1.0
OCA-git-bot Nov 30, 2020
f8ecb62
edi_storage: adapt to edi api changes
simahawk Dec 2, 2020
fadc537
edi_storage 13.0.1.2.0
OCA-git-bot Dec 2, 2020
eadaa9c
edi_storage: put ack handling on hold
simahawk Dec 2, 2020
89951ca
edi_storage 13.0.1.3.0
OCA-git-bot Dec 2, 2020
96c6989
edi_storage: adapt to new components lookup
simahawk Feb 8, 2021
be87c71
edi_storage 13.0.1.4.0
OCA-git-bot Mar 10, 2021
022b812
[CHG] edi: Use more permissive licence: AGPL-> LGPL
etobella Mar 10, 2021
bbdbfef
[UPD] README.rst
OCA-git-bot Mar 15, 2021
632ef0c
edi_storage 13.0.1.4.1
OCA-git-bot Mar 15, 2021
b0cd3f8
[FIX] edi_storage: sort key was not working properly
etobella Apr 7, 2021
ab2c396
edi_storage 13.0.1.5.0
OCA-git-bot Apr 9, 2021
dc6171d
[IMP] edi_storage_oca: isort, black, prettier.
fshah-initos May 5, 2021
b8eb876
[MIG] Migrate module edi_storage_oca to v14.
fshah-initos May 5, 2021
6eda88c
[UPD] Update edi_storage_oca.pot
oca-travis May 27, 2021
99ea22d
[UPD] README.rst
OCA-git-bot May 27, 2021
bc24fd7
[IMP] edi_storage_oca: implement basis of the input process.
LoisRForgeFlow Jun 22, 2021
a9e6cf9
[FIX] edi_oca: show the exchange file name while there is no file yet.
LoisRForgeFlow Jun 22, 2021
8c06095
edi_storage: improve input process
simahawk Jun 23, 2021
c9c1dca
[FIX] edi_storage_oca: support the use or not of filename patern + sm…
LoisRForgeFlow Jun 23, 2021
b7b9077
[IMP] edi_storage_oca: add specific channel and job function definitions
LoisRForgeFlow Jul 1, 2021
52a29f7
[FIX] edi_storage_oca: support regex pattern searching when fetching …
LoisRForgeFlow Jul 1, 2021
6f7f342
[FIX] edi_storage: consider if the exchange type has a specific backe…
LoisRForgeFlow Jul 2, 2021
2ab5d16
[IMP] : black, isort, prettier
LoisRForgeFlow Jul 5, 2021
5d4796d
[UPD] Update edi_storage_oca.pot
oca-travis Jul 6, 2021
4d6e533
[UPD] README.rst
OCA-git-bot Jul 6, 2021
8251ab9
edi_storage_oca 14.0.1.1.0
OCA-git-bot Jul 6, 2021
e7f8b37
[IMP] edi_storage: add listener to move files to done/error dirs
LoisRForgeFlow Jul 5, 2021
11bb2a9
[FIX] edi_storage: `list_files` expects a string.
LoisRForgeFlow Jul 9, 2021
a6824aa
edi_storage_oca 14.0.1.2.0
OCA-git-bot Jul 14, 2021
d6bea24
[14.0][FIX] edi_storage_oca: remove duplicate backend_type_id field i…
JordiMForgeFlow Aug 4, 2021
6964c11
edi_storage_oca 14.0.1.2.1
OCA-git-bot Aug 4, 2021
cd3367d
[FIX] edi_storage: incorrect call to output checker in receive compon…
LoisRForgeFlow Sep 6, 2021
93fb22a
edi_storage_oca 14.0.1.2.2
OCA-git-bot Oct 1, 2021
a9dfd3e
edi: improve backend view extension
simahawk Sep 17, 2021
769cb98
[UPD] Update edi_storage_oca.pot
oca-travis Oct 13, 2021
f90f876
edi_storage_oca 14.0.1.3.0
OCA-git-bot Oct 13, 2021
e3dd1b9
edi: load main vars at init
simahawk Sep 22, 2021
8a81ebf
edi_storage: fix find files test
simahawk Oct 30, 2021
9e53051
edi_storage_oca 14.0.1.4.0
OCA-git-bot Nov 2, 2021
3ec51b9
[MIG][15.0] edi_storage_oca
i-vyshnevska Dec 5, 2021
0c72513
[UPD] Update edi_storage_oca.pot
Mar 15, 2022
efdb3cf
[UPD] README.rst
OCA-git-bot Mar 15, 2022
918f703
edi: get rid of name, use identifier
simahawk Mar 10, 2022
3e2c535
edi_storage: fail gracefully w/o error report
simahawk Mar 10, 2022
7a3825c
edi_storage_oca 15.0.1.1.0
OCA-git-bot Apr 7, 2022
0502568
[IMP] edi_storage_oca: Update development status
JasminSForgeFlow Apr 12, 2022
66ecedb
[UPD] README.rst
OCA-git-bot Apr 12, 2022
5997ae3
edi_storage_oca 15.0.1.1.1
OCA-git-bot Apr 12, 2022
b01c159
edi_storage_oca: improve error handling
jcoux May 17, 2022
f9581ad
edi_storage_oca 15.0.1.1.2
OCA-git-bot May 19, 2022
582397b
edi_storage: path configurable by type and param
simahawk May 6, 2022
91c6e0d
edi_storage_oca 15.0.1.2.0
OCA-git-bot Aug 11, 2022
8235d88
[UPD] README.rst
OCA-git-bot Sep 3, 2023
9d52254
[IMP] edi_storage_oca: pre-commit stuff
Sep 26, 2023
b0e99b8
[MIG] edi_storage_oca: Migration to 16.0
Sep 29, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions edi_storage_oca/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
===========================
EDI Storage backend support
===========================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:3806d6eaf0c3ce46d7d24b5c5e002f0c49a42390385f23e4480a33dd94e85451
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
: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
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fedi--framework-lightgray.png?logo=github
:target: https://github.com/OCA/edi-framework/tree/16.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-16-0/edi-framework-16-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=16.0
:alt: Try me on Runboat

|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.

**Table of contents**

.. contents::
:local:

Usage
=====

Go to "EDI -> EDI backend" then configure your backend to use a storage backend.

Known issues / Roadmap
======================

* clean deprecated methods in the storage

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/edi-framework/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 <https://github.com/OCA/edi-framework/issues/new?body=module:%20edi_storage_oca%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
~~~~~~~

* ACSONE

Contributors
~~~~~~~~~~~~

* Simone Orsi <[email protected]>
* Foram Shah <[email protected]>
* Lois Rilo <[email protected]>
* Duong (Tran Quoc) <[email protected]>

Other credits
~~~~~~~~~~~~~

The migration of this module from 15.0 to 16.0 was financially supported by Camptocamp.

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-framework <https://github.com/OCA/edi-framework/tree/16.0/edi_storage_oca>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
2 changes: 2 additions & 0 deletions edi_storage_oca/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import components
from . import models
24 changes: 24 additions & 0 deletions edi_storage_oca/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Copyright 2020 ACSONE
# @author: Simone Orsi <[email protected]>
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

{
"name": "EDI Storage backend support",
"summary": """
Base module to allow exchanging files via storage backend (eg: SFTP).
""",
"version": "16.0.1.0.0",
"development_status": "Beta",
"license": "LGPL-3",
"website": "https://github.com/OCA/edi-framework",
"author": "ACSONE,Odoo Community Association (OCA)",
"depends": ["edi_oca", "fs_storage", "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",
],
"demo": ["demo/edi_backend_demo.xml"],
}
5 changes: 5 additions & 0 deletions edi_storage_oca/components/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from . import base
from . import check
from . import send
from . import receive
from . import listener
86 changes: 86 additions & 0 deletions edi_storage_oca/components/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Copyright 2020 ACSONE
# Copyright 2022 Camptocamp
# @author: Simone Orsi <[email protected]>
# 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):

_name = "edi.storage.component.mixin"
_inherit = "edi.component.mixin"
# Components having `_storage_type` will have precedence.
# If the value is not set, generic components will be used.
_storage_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_type")
if storage_type and cls._storage_type:
return cls._storage_type == storage_type
return res

@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().rstrip("/")
)

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.

:param state: string ("pending", "done", "error")
:param filename: custom file name, exchange_record filename used by default
:return: remote file content as string
"""
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
# (the date will never match)
# TODO: clean this up, .get is deprecated in fs_storage
return self.storage.get(path.as_posix(), binary=binary)
Copy link
Contributor

Choose a reason for hiding this comment

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

@simahawk @QuocDuong1306 another issue that I am seeing is when sending a file. In the send component, a check is done before sending to make sure a file is not sent twice. The check ends up in this line, doing a storage.get.

I suppose it has to do with the new fs storage, but doing the get for a file that does not exist raises a different error than FileNotFoundError. The error is:

ValueError: <class 'ftplib.error_perm'>: "550 Can't open *****: No such file or directory" (being ***** the filepath being accessed)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have created a PR at here: fsspec/filesystem_spec#1494

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(

Check warning on line 80 in edi_storage_oca/components/base.py

View check run for this annotation

Codecov / codecov/patch

edi_storage_oca/components/base.py#L79-L80

Added lines #L79 - L80 were not covered by tests
"Ignored OSError when trying to get file %s into path %s for state %s",
filename,
path,
state,
)
return None

Check warning on line 86 in edi_storage_oca/components/base.py

View check run for this annotation

Codecov / codecov/patch

edi_storage_oca/components/base.py#L86

Added line #L86 was not covered by tests
71 changes: 71 additions & 0 deletions edi_storage_oca/components/check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Copyright 2020 ACSONE
# @author: Simone Orsi <[email protected]>
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

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 = "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",
self.exchange_record.identifier,
)
if (
not self.exchange_record.edi_exchange_state
== "output_sent_and_processed"
):
self.exchange_record.edi_exchange_state = "output_sent_and_processed"
self.exchange_record._notify_done()
return False

error = self._get_remote_file("error")
if error:
_logger.info(
"%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"
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(
{
"edi_exchange_state": "output_sent_and_error",
"exchange_error": pycompat.to_text(error_report),
}
)
self.exchange_record._notify_error("process_ko")
return False
return True
74 changes: 74 additions & 0 deletions edi_storage_oca/components/listener.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Copyright 2021 ForgeFlow S.L. (https://www.forgeflow.com)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

import functools
import os
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)

Check warning on line 17 in edi_storage_oca/components/listener.py

View check run for this annotation

Codecov / codecov/patch

edi_storage_oca/components/listener.py#L16-L17

Added lines #L16 - L17 were not covered by tests
# - storage.list_files now includes path in fs_storage, breaking change
# - we remove path
simahawk marked this conversation as resolved.
Show resolved Hide resolved
# TODO: clean this up, .list_files is deprecated in fs_storage
files = storage.list_files(from_dir.as_posix())

Check warning on line 21 in edi_storage_oca/components/listener.py

View check run for this annotation

Codecov / codecov/patch

edi_storage_oca/components/listener.py#L21

Added line #L21 was not covered by tests
files = [os.path.basename(f) for f in files]
if filename not in files:
return False

Check warning on line 24 in edi_storage_oca/components/listener.py

View check run for this annotation

Codecov / codecov/patch

edi_storage_oca/components/listener.py#L24

Added line #L24 was not covered by tests
# TODO: clean this up, .move_files is deprecated in fs_storage
self._add_post_commit_hook(

Check warning on line 26 in edi_storage_oca/components/listener.py

View check run for this annotation

Codecov / codecov/patch

edi_storage_oca/components/listener.py#L26

Added line #L26 was not covered by tests
storage.move_files, [(from_dir / filename).as_posix()], to_dir.as_posix()
)
return True

Check warning on line 29 in edi_storage_oca/components/listener.py

View check run for this annotation

Codecov / codecov/patch

edi_storage_oca/components/listener.py#L29

Added line #L29 was not covered by tests

def _add_post_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.postcommit.add(

Check warning on line 33 in edi_storage_oca/components/listener.py

View check run for this annotation

Codecov / codecov/patch

edi_storage_oca/components/listener.py#L33

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

Check warning on line 52 in edi_storage_oca/components/listener.py

View check run for this annotation

Codecov / codecov/patch

edi_storage_oca/components/listener.py#L52

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

Check warning on line 58 in edi_storage_oca/components/listener.py

View check run for this annotation

Codecov / codecov/patch

edi_storage_oca/components/listener.py#L58

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