Skip to content

Commit

Permalink
Merge PR #315 into 16.0
Browse files Browse the repository at this point in the history
Signed-off-by pedrobaeza
  • Loading branch information
OCA-git-bot committed Dec 28, 2023
2 parents d0723af + 0d86765 commit d73e606
Show file tree
Hide file tree
Showing 31 changed files with 4,781 additions and 0 deletions.
131 changes: 131 additions & 0 deletions pingen/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
======================
pingen.com integration
======================

.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |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-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%2Freport--print--send-lightgray.png?logo=github
:target: https://github.com/OCA/report-print-send/tree/16.0-mig-pingen/pingen
:alt: OCA/report-print-send
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/report-print-send-16-0-mig-pingen/report-print-send-16-0-mig-pingen-pingen
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
:target: https://runbot.odoo-community.org/runbot/144/16.0-mig-pingen
:alt: Try me on Runbot

|badge1| |badge2| |badge3| |badge4| |badge5|

Pingen.com is a paid online service.
It sends uploaded documents by letter post.

One can decide, per document / attachment, if it should be pushed
to pingen.com. The documents are pushed asynchronously.

The informations of the documents from pingen.com are updated through webhook calls.

**Table of contents**

.. contents::
:local:

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

The authentication token, client ID, organization ID and webhook secret is configured
on the company's view. You can also tick a checkbox if the staging environment
(https://stage-api.pingen.com) should be used.

Webhooks should be configured on pingen account. Organization ID and webhook secret must match.

Usage
=====

On the attachment view, a new pingen.com section has been added.
You can tick a box to push the document to pingen.com.

There is 3 additional options:

* Send: the document will not be only uploaded, but will be also be sent
* Speed: priority or economy
* Type of print: color or black and white

Once the configuration is done and the attachment saved, a Pingen Document
is created. You can directly access to the latter on the Link on the right on
the attachment view.

You can find them in `Pingen Documents` App or in the more convenient `Documents` menu if you have installed the
`document` module.

Errors
======

Sometimes, pingen.com will refuse to send a document because it does not meet
its requirements. In such case, the document's state becomes "Pingen Error"
and you will need to manually handle the case, either from the pingen.com
backend, or by changing the document on OpenERP and resolving the error on the
Pingen Document.

When a connection error occurs, the action will be retried on the next
scheduler run.


Dependencies
============

* Require the Python library `requests_oauthlib <https://github.com/requests/requests-oauthlib>`_
* The address must be in a format accepted by pingen.com: the last line
is the country in English or German.

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

Bugs are tracked on `GitHub Issues <https://github.com/OCA/report-print-send/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 <https://github.com/OCA/report-print-send/issues/new?body=module:%20pingen%0Aversion:%2016.0-mig-pingen%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
~~~~~~~

* Camptocamp

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

* Guewen Baconnier <guewen.baconnier@camptocamp.com>
* Anar Baghirli <a.baghirli@mobilunity.com>
* Akim Juillerat <akim.juillerat@camptocamp.com>
* Anna Janiszewska <anna.janiszewska@camptocamp.com>


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/report-print-send <https://github.com/OCA/report-print-send/tree/16.0-mig-pingen/pingen>`_ 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 pingen/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import controllers
from . import models
27 changes: 27 additions & 0 deletions pingen/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Author: Guewen Baconnier
# Copyright 2012-2023 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

{
"name": "pingen.com integration",
"version": "16.0.1.0.0",
"author": "Camptocamp,Odoo Community Association (OCA)",
"maintainers": ["ajaniszewska-dev", "grindtildeath"],
"license": "AGPL-3",
"category": "Reporting",
"maturity": "Production/Stable",
"depends": ["base_setup"],
"external_dependencies": {
"python": ["requests_oauthlib", "oauthlib"],
},
"website": "https://github.com/OCA/report-print-send",
"data": [
"views/ir_attachment_view.xml",
"views/pingen_document_view.xml",
"data/pingen_data.xml",
"views/base_config_settings.xml",
"security/ir.model.access.csv",
],
"installable": True,
"application": True,
}
1 change: 1 addition & 0 deletions pingen/controllers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import main
141 changes: 141 additions & 0 deletions pingen/controllers/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# Copyright 2022 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
import hashlib
import hmac
import json
import logging

import werkzeug

from odoo import http

from ..models.pingen import pingen_datetime_to_utc

_logger = logging.getLogger(__name__)


class PingenController(http.Controller):
def _verify_signature(self, request_content):
webhook_signature = http.request.httprequest.headers.get("Signature")
companies = http.request.env["res.company"].sudo().search([])
for company in companies:
# We could not search on `pingen_webhook_secret
# if this field is computed (e.g. env field)
if not company.pingen_webhook_secret:
continue
secret_signature = hmac.new(
company.pingen_webhook_secret.encode("utf-8"),
request_content,
hashlib.sha256,
).hexdigest()
if webhook_signature == secret_signature:
return company
msg = "Webhook signature does not match with any company secret"
_logger.warning(msg)
raise werkzeug.exceptions.Forbidden()

def _get_request_content(self):
return http.request.httprequest.stream.read()

def _get_json_content(self, request_content):
return json.loads(request_content)

def _get_document_uuid(self, json_content):
return (
json_content.get("data", {})
.get("relationships", {})
.get("letter", {})
.get("data", {})
.get("id", "")
)

def _find_pingen_document(self, document_uuid):
if document_uuid:
return http.request.env["pingen.document"].search(
[("pingen_uuid", "=", document_uuid)]
)
return http.request.env["pingen.document"].browse()

def _get_error_reason(self, json_content):
return json_content.get("data", {}).get("attributes", {}).get("reason", "")

def _get_letter_infos(self, json_content, document_uuid):
for node in json_content.get("included", {}):
if node.get("type") == "letters" and node.get("id") == document_uuid:
return node.get("attributes", {})
return {}

def _get_emitted_date(self, json_content):
emitted_at = ""
for node in json_content.get("included", {}):
if node.get("type") == "letters_events":
attributes = node.get("attributes", {})
if attributes.get("code") == "transferred_to_distributor":
emitted_at = attributes.get("emitted_at", "")
break
return pingen_datetime_to_utc(emitted_at.encode())

def _update_pingen_document(self, request_content, values):
json_content = self._get_json_content(request_content)
document_uuid = self._get_document_uuid(json_content)
pingen_doc = self._find_pingen_document(document_uuid)
if pingen_doc:
info_values = pingen_doc._prepare_values_from_post_infos(
self._get_letter_infos(json_content, document_uuid)
)
info_values.update(values)
pingen_doc.sudo().write(info_values)
msg = "Pingen document with UUID %s updated successfully" % document_uuid
_logger.info(msg)
return msg
msg = "Could not find related Pingen document for UUID %s" % document_uuid
_logger.warning(msg)
return msg

@http.route(
"/pingen/letter_issues", type="http", auth="none", methods=["POST"], csrf=False
)
def letter_issues(self, **post):
_logger.info("Webhook call received on /pingen/letter_issues")
request_content = self._get_request_content()
json_content = self._get_json_content(request_content)
self._verify_signature(request_content)
values = {
"state": "pingen_error",
"last_error_message": self._get_error_reason(json_content),
}
self._update_pingen_document(request_content, values)

@http.route(
"/pingen/sent_letters", type="http", auth="none", methods=["POST"], csrf=False
)
def sent_letters(self, **post):
_logger.info("Webhook call received on /pingen/sent_letters")
request_content = self._get_request_content()
json_content = self._get_json_content(request_content)
self._verify_signature(request_content)
emitted_date = self._get_emitted_date(json_content)
values = {
"state": "sent",
}
if emitted_date:
values["send_date"] = emitted_date
self._update_pingen_document(request_content, values)

@http.route(
"/pingen/undeliverable_letters",
type="http",
auth="none",
methods=["POST"],
csrf=False,
)
def undeliverable_letters(self, **post):
_logger.info("Webhook call received on /pingen/undeliverable_letters")
request_content = self._get_request_content()
json_content = self._get_json_content(request_content)
self._verify_signature(request_content)
values = {
"state": "error_undeliverable",
"last_error_message": self._get_error_reason(json_content),
}
self._update_pingen_document(request_content, values)
30 changes: 30 additions & 0 deletions pingen/data/pingen_data.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">


<record forcecreate="True" id="ir_cron_push_pingen" model="ir.cron">
<field name="name">Run Pingen Document Push</field>
<field eval="True" name="active" />
<field name="user_id" ref="base.user_root" />
<field name="interval_number">1</field>
<field name="interval_type">hours</field>
<field name="numbercall">-1</field>
<field eval="False" name="doall" />
<field name="model_id" ref="model_pingen_document" />
<field name="code">model._push_and_send_to_pingen_cron</field>
</record>

<record forcecreate="True" id="ir_cron_update_pingen" model="ir.cron">
<field name="name">Run Pingen Document Update</field>
<field eval="False" name="active" />
<field name="user_id" ref="base.user_root" />
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field eval="False" name="doall" />
<field name="model_id" ref="model_pingen_document" />
<field name="code">model._update_post_infos_cron</field>
</record>


</odoo>
Loading

0 comments on commit d73e606

Please sign in to comment.