details:   https://code.tryton.org/tryton/commit/b96191427ff3
branch:    default
user:      Cédric Krier <[email protected]>
date:      Tue Dec 23 12:07:17 2025 +0100
description:
        Add support for Peppyrus webhooks
diffstat:

 modules/edocument_peppol_peppyrus/CHANGELOG                                    
|   1 +
 modules/edocument_peppol_peppyrus/__init__.py                                  
|   4 +
 modules/edocument_peppol_peppyrus/doc/configuration.rst                        
|  17 ++
 modules/edocument_peppol_peppyrus/doc/index.rst                                
|   2 +
 modules/edocument_peppol_peppyrus/doc/reference.rst                            
|  23 +++
 modules/edocument_peppol_peppyrus/edocument.py                                 
|  63 +++++++++-
 modules/edocument_peppol_peppyrus/edocument.xml                                
|   7 +
 modules/edocument_peppol_peppyrus/routes.py                                    
|  66 ++++++++++
 modules/edocument_peppol_peppyrus/tests/scenario_edocument_peppol_peppyrus.rst 
|   8 +-
 modules/edocument_peppol_peppyrus/view/edocument_peppol_service_form.xml       
|   7 +
 10 files changed, 196 insertions(+), 2 deletions(-)

diffs (315 lines):

diff -r f2293673b1b3 -r b96191427ff3 modules/edocument_peppol_peppyrus/CHANGELOG
--- a/modules/edocument_peppol_peppyrus/CHANGELOG       Thu Dec 18 15:18:53 
2025 +0100
+++ b/modules/edocument_peppol_peppyrus/CHANGELOG       Tue Dec 23 12:07:17 
2025 +0100
@@ -1,3 +1,4 @@
+* Add support for webhooks
 
 Version 7.8.0 - 2025-12-15
 --------------------------
diff -r f2293673b1b3 -r b96191427ff3 
modules/edocument_peppol_peppyrus/__init__.py
--- a/modules/edocument_peppol_peppyrus/__init__.py     Thu Dec 18 15:18:53 
2025 +0100
+++ b/modules/edocument_peppol_peppyrus/__init__.py     Tue Dec 23 12:07:17 
2025 +0100
@@ -1,2 +1,6 @@
 # This file is part of Tryton.  The COPYRIGHT file at the top level of
 # this repository contains the full copyright notices and license terms.
+
+from . import routes
+
+__all__ = [routes]
diff -r f2293673b1b3 -r b96191427ff3 
modules/edocument_peppol_peppyrus/doc/configuration.rst
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/edocument_peppol_peppyrus/doc/configuration.rst   Tue Dec 23 
12:07:17 2025 +0100
@@ -0,0 +1,17 @@
+*************
+Configuration
+*************
+
+The *EDocument Peppol Peppyrus Module* uses values from settings in the
+``[edocument_peppol_peppyrus]`` section of the
+:ref:`trytond:topics-configuration`.
+
+.. _config-edocument_peppol_peppyrus.max_size:
+
+``max_size``
+============
+
+The maximum size in bytes of the Peppyrus Webhook request (zero means no
+limit):
+
+The default value is: `trytond:config-request.max_size`
diff -r f2293673b1b3 -r b96191427ff3 
modules/edocument_peppol_peppyrus/doc/index.rst
--- a/modules/edocument_peppol_peppyrus/doc/index.rst   Thu Dec 18 15:18:53 
2025 +0100
+++ b/modules/edocument_peppol_peppyrus/doc/index.rst   Tue Dec 23 12:07:17 
2025 +0100
@@ -10,5 +10,7 @@
    :maxdepth: 2
 
    setup
+   configuration
    design
+   reference
    releases
diff -r f2293673b1b3 -r b96191427ff3 
modules/edocument_peppol_peppyrus/doc/reference.rst
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/edocument_peppol_peppyrus/doc/reference.rst       Tue Dec 23 
12:07:17 2025 +0100
@@ -0,0 +1,23 @@
+*************
+API Reference
+*************
+
+.. _Peppyrus Webhook:
+
+Peppyrus Webhook
+================
+
+The *EDocument Peppol Peppyrus MOdule* defines routes to receive the Peppyrus'
+webhooks:
+
+   - ``POST`` ``/<database_name>/edocument_peppol_peppyrus/<identifier>/in``:
+      Stores the payload message.
+
+      ``identifier`` is the Peppyrus identifier of the `Service
+      <model-edocument.peppol.service>`.
+
+   - ``POST`` ``/<database_name>/edocument_peppol_peppyrus/<identifier>/out``:
+      Update the status of the payload message.
+
+      ``identifier`` is the Peppyrus identifier of the `Service
+      <model-edocument.peppol.service>`.
diff -r f2293673b1b3 -r b96191427ff3 
modules/edocument_peppol_peppyrus/edocument.py
--- a/modules/edocument_peppol_peppyrus/edocument.py    Thu Dec 18 15:18:53 
2025 +0100
+++ b/modules/edocument_peppol_peppyrus/edocument.py    Tue Dec 23 12:07:17 
2025 +0100
@@ -1,10 +1,11 @@
 # This file is part of Tryton.  The COPYRIGHT file at the top level of
 # this repository contains the full copyright notices and license terms.
 
+import uuid
 from base64 import b64decode, b64encode
 from functools import wraps
 from io import BytesIO
-from urllib.parse import urljoin
+from urllib.parse import quote, urljoin
 
 import requests
 from lxml import etree
@@ -16,6 +17,7 @@
 from trytond.protocols.wrappers import HTTPStatus
 from trytond.pyson import Eval
 from trytond.transaction import Transaction
+from trytond.url import http_host
 
 from .exceptions import PeppyrusCredentialWarning, PeppyrusError
 
@@ -57,11 +59,32 @@
             'invisible': Eval('service') != 'peppyrus',
             'required': Eval('service') == 'peppyrus',
             })
+    peppyrus_identifier = fields.Char(
+        "Identifier", readonly=True,
+        states={
+            'invisible': Eval('service') != 'peppyrus',
+            })
+    peppyrus_incoming_webhook = fields.Function(
+        fields.Char(
+            "Incoming webhook",
+            help="The URL for the incoming documents webhook."),
+        'on_change_with_peppyrus_incoming_webhook')
+    peppyrus_outgoing_webhook = fields.Function(
+        fields.Char(
+            "Outgoing webhook",
+            help="The URL for the outgoing documents webhook."),
+        'on_change_with_peppyrus_outgoing_webhook')
 
     @classmethod
     def __setup__(cls):
         super().__setup__()
         cls.service.selection.append(('peppyrus', "Peppyrus"))
+        cls._buttons.update(
+            peppyrus_new_identifier={
+                'invisible': Eval('service') != 'peppyrus',
+                'icon': 'tryton-refresh',
+                },
+            )
 
     @fields.depends('service', 'peppyrus_server')
     def on_change_service(self):
@@ -69,6 +92,41 @@
                 and not self.peppyrus_server):
             self.peppyrus_server = 'testing'
 
+    @fields.depends('peppyrus_identifier')
+    def on_change_with_peppyrus_incoming_webhook(self, name=None):
+        if self.peppyrus_identifier:
+            url_part = {
+                'identifier': self.peppyrus_identifier,
+                'database_name': Transaction().database.name,
+                }
+            return http_host() + (
+                quote(
+                    '/%(database_name)s/edocument_peppol_peppyrus/'
+                    '%(identifier)s/in'
+                    % url_part))
+
+    @fields.depends('peppyrus_identifier')
+    def on_change_with_peppyrus_outgoing_webhook(self, name=None):
+        if self.peppyrus_identifier:
+            url_part = {
+                'identifier': self.peppyrus_identifier,
+                'database_name': Transaction().database.name,
+                }
+            return http_host() + (
+                quote(
+                    '/%(database_name)s/edocument_peppol_peppyrus/'
+                    '%(identifier)s/out'
+                    % url_part))
+
+    @classmethod
+    def peppyrus_new_identifier(cls, services):
+        for service in services:
+            if service.peppyrus_identifier:
+                service.peppyrus_identifier = None
+            else:
+                service.peppyrus_identifier = uuid.uuid4().hex
+        cls.save(services)
+
     @peppyrus_api
     def _post_peppyrus(self, document):
         tree = etree.parse(BytesIO(document.data))
@@ -133,6 +191,9 @@
                 })
         response.raise_for_status()
         message = response.json()
+        self.peppyrus_update(document, message)
+
+    def peppyrus_update(self, document, message):
         assert message['id'] == document.transmission_id
         if message['folder'] == 'sent':
             document.succeed()
diff -r f2293673b1b3 -r b96191427ff3 
modules/edocument_peppol_peppyrus/edocument.xml
--- a/modules/edocument_peppol_peppyrus/edocument.xml   Thu Dec 18 15:18:53 
2025 +0100
+++ b/modules/edocument_peppol_peppyrus/edocument.xml   Tue Dec 23 12:07:17 
2025 +0100
@@ -8,6 +8,13 @@
             <field name="inherit" 
ref="edocument_peppol.edocument_peppol_service_view_form"/>
             <field name="name">edocument_peppol_service_form</field>
         </record>
+
+        <record model="ir.model.button" 
id="edocument_peppol_service_new_identifier_button">
+            <field name="model">edocument.peppol.service</field>
+            <field name="name">peppyrus_new_identifier</field>
+            <field name="string">New URLs</field>
+            <field name="confirm">This action will make the previous URLs 
unusable. Do you want to continue?</field>
+        </record>
     </data>
 
     <data noupdate="1">
diff -r f2293673b1b3 -r b96191427ff3 modules/edocument_peppol_peppyrus/routes.py
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/edocument_peppol_peppyrus/routes.py       Tue Dec 23 12:07:17 
2025 +0100
@@ -0,0 +1,66 @@
+# This file is part of Tryton.  The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+import json
+import logging
+
+from trytond import config
+from trytond.protocols.wrappers import (
+    HTTPStatus, Response, abort, set_max_request_size, with_pool,
+    with_transaction)
+from trytond.wsgi import app
+
+logger = logging.getLogger(__name__)
+
+
[email protected](
+    '/<database_name>/edocument_peppol_peppyrus/<identifier>/in',
+    methods={'POST'})
+@set_max_request_size(config.getint(
+        'edocument_peppol_peppyrus', 'max_size',
+        default=config.getint('request', 'max_size')))
+@with_pool
+@with_transaction()
+def incoming(request, pool, identifier):
+    Service = pool.get('edocument.peppol.service')
+
+    try:
+        service, = Service.search([
+                ('peppyrus_identifier', '=', identifier),
+                ])
+    except ValueError:
+        abort(HTTPStatus.NOT_FOUND)
+
+    request_body = request.get_data(as_text=True)
+    message = json.loads(request_body)
+    Service.peppyrus_store(message)
+    return Response(status=HTTPStatus.NO_CONTENT)
+
+
[email protected](
+    '/<database_name>/edocument_peppol_peppyrus/<identifier>/out',
+    methods={'POST'})
+@set_max_request_size(config.getint(
+        'edocument_peppol_peppyrus', 'max_size',
+        default=config.getint('request', 'max_size')))
+@with_pool
+@with_transaction()
+def outgoing(request, pool, identifier):
+    Service = pool.get('edocument.peppol.service')
+    Document = pool.get('edocument.peppol')
+
+    try:
+        service, = Service.search([
+                ('peppyrus_identifier', '=', identifier),
+                ])
+    except ValueError:
+        abort(HTTPStatus.NOT_FOUND)
+
+    request_body = request.get_data(as_text=True)
+    message = json.loads(request_body)
+    document, = Document.search([
+            ('service', '=', service),
+            ('transmission_id', '=', message['id']),
+            ])
+    service.peppyrus_update(document, message)
+    return Response(status=HTTPStatus.NO_CONTENT)
diff -r f2293673b1b3 -r b96191427ff3 
modules/edocument_peppol_peppyrus/tests/scenario_edocument_peppol_peppyrus.rst
--- 
a/modules/edocument_peppol_peppyrus/tests/scenario_edocument_peppol_peppyrus.rst
    Thu Dec 18 15:18:53 2025 +0100
+++ 
b/modules/edocument_peppol_peppyrus/tests/scenario_edocument_peppol_peppyrus.rst
    Tue Dec 23 12:07:17 2025 +0100
@@ -11,7 +11,7 @@
     >>> from proteus import Model
     >>> from trytond.modules.company.tests.tools import create_company
     >>> from trytond.modules.edocument_peppol.edocument import Peppol
-    >>> from trytond.tests.tools import activate_modules
+    >>> from trytond.tests.tools import activate_modules, assertTrue
     >>> from trytond.tools import file_open
 
     >>> FETCH_SLEEP, MAX_SLEEP = 1, 20
@@ -48,6 +48,12 @@
     >>> peppol_service.peppyrus_api_key = os.getenv('PEPPYRUS_API_KEY')
     >>> peppol_service.save()
 
+Setup webhook::
+
+    >>> peppol_service.click('peppyrus_new_identifier')
+    >>> assertTrue(peppol_service.peppyrus_incoming_webhook)
+    >>> assertTrue(peppol_service.peppyrus_outgoing_webhook)
+
 Send out a Peppol document::
 
     >>> peppol = Peppol(direction='out')
diff -r f2293673b1b3 -r b96191427ff3 
modules/edocument_peppol_peppyrus/view/edocument_peppol_service_form.xml
--- a/modules/edocument_peppol_peppyrus/view/edocument_peppol_service_form.xml  
Thu Dec 18 15:18:53 2025 +0100
+++ b/modules/edocument_peppol_peppyrus/view/edocument_peppol_service_form.xml  
Tue Dec 23 12:07:17 2025 +0100
@@ -8,6 +8,13 @@
         <field name="peppyrus_server"/>
         <label name="peppyrus_api_key"/>
         <field name="peppyrus_api_key"/>
+
+        <label name="peppyrus_incoming_webhook"/>
+        <field name="peppyrus_incoming_webhook"/>
+        <label name="peppyrus_outgoing_webhook"/>
+        <field name="peppyrus_outgoing_webhook"/>
+
+        <button name="peppyrus_new_identifier" colspan="4"/>
         <newline/>
     </xpath>
 </data>

Reply via email to