diff --git a/pingen/__init__.py b/pingen/__init__.py
new file mode 100644
index 0000000..a00e542
--- /dev/null
+++ b/pingen/__init__.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Author: Guewen Baconnier
+# Copyright 2012 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+import ir_attachment
+import pingen
+import pingen_document
+import res_company
+
diff --git a/pingen/__openerp__.py b/pingen/__openerp__.py
new file mode 100644
index 0000000..0283c60
--- /dev/null
+++ b/pingen/__openerp__.py
@@ -0,0 +1,119 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Author: Guewen Baconnier
+# Copyright 2012 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+{
+ 'name': 'pingen.com integration',
+ 'version': '1.0',
+ 'author': 'Camptocamp',
+ 'maintainer': 'Camptocamp',
+ 'license': 'AGPL-3',
+ 'category': 'Reporting',
+ 'complexity': 'easy',
+ 'depends': [],
+ 'external_dependencies': {
+ 'python': ['requests'],
+ },
+ 'description': """
+Integration with pingen.com
+===========================
+
+What is pingen.com
+------------------
+
+Pingen.com is a paid online service.
+It sends uploaded documents by letter post.
+
+Scope of the integration
+------------------------
+
+One can decide, per document / attachment, if it should be pushed
+to pingen.com. The documents are pushed asynchronously.
+
+A second cron updates the informations of the documents from pingen.com, so we
+know which of them have been sent.
+
+Configuration
+-------------
+
+The authentication token 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.
+
+The setup of the 2 crons can be changed as well:
+
+ * Run Pingen Document Push
+ * Run Pingen Document Update
+
+Usage
+-----
+
+On the attachment view, a new pingen.com tab 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 `Settings > Customization > Low Level Objets > Pingen
+Documents` 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 `_
+ * The PDF files sent to pingen.com have to respect some `formatting rules
+ `_.
+ * The address must be in a format accepted by pingen.com: the last line
+ is the country in English or German.
+
+""",
+ 'website': 'http://www.camptocamp.com',
+ 'data': [
+ 'ir_attachment_view.xml',
+ 'pingen_document_view.xml',
+ 'pingen_data.xml',
+ 'res_company_view.xml',
+ 'security/ir.model.access.csv',
+ ],
+ 'tests': [],
+ 'installable': True,
+ 'auto_install': False,
+ 'application': True,
+}
diff --git a/pingen/i18n/fr.po b/pingen/i18n/fr.po
new file mode 100644
index 0000000..6b141e2
--- /dev/null
+++ b/pingen/i18n/fr.po
@@ -0,0 +1,390 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+# * pingen
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 6.1\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2012-11-26 10:54+0000\n"
+"PO-Revision-Date: 2012-11-26 10:54+0000\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: pingen
+#: code:addons/pingen/pingen_document.py:177
+#: code:addons/pingen/pingen_document.py:307
+#: code:addons/pingen/pingen_document.py:414
+#, python-format
+msgid "Unexcepted Error when updating the status of Document %s"
+msgstr "Erreur inattendue lors de la mise à jour du document %s"
+
+#. module: pingen
+#: field:pingen.document,push_date:0
+msgid "Push Date"
+msgstr "Date d'ajout"
+
+#. module: pingen
+#: view:pingen.document:0
+msgid "Errors"
+msgstr "Erreurs"
+
+#. module: pingen
+#: field:pingen.document,pingen_id:0
+msgid "Pingen ID"
+msgstr "ID Pingen"
+
+#. module: pingen
+#: constraint:res.company:0
+msgid "Error! You can not create recursive companies."
+msgstr "Error! You can not create recursive companies."
+
+#. module: pingen
+#: field:ir.attachment,pingen_send:0
+msgid "Send"
+msgstr "Envoyer"
+
+#. module: pingen
+#: code:addons/pingen/pingen_document.py:399
+#, python-format
+msgid "Connection Error when updating the status of Document %s from Pingen"
+msgstr "Erreur de connexion lors de la mise à jour de l'état du document %s depuis Pingen"
+
+#. module: pingen
+#: field:pingen.document,state:0
+msgid "State"
+msgstr "État"
+
+#. module: pingen
+#: field:ir.attachment,pingen_color:0
+msgid "Type of print"
+msgstr "Type d'impression"
+
+#. module: pingen
+#: view:pingen.document:0
+msgid "Attachment"
+msgstr "Attachement"
+
+#. module: pingen
+#: code:addons/pingen/pingen_document.py:405
+#, python-format
+msgid "Error when updating the status of Document %s from Pingen: \n"
+"%s"
+msgstr "Erreur lors de la mise à jour de l'état du document %s depuis Pingen: \n"
+"%s"
+
+#. module: pingen
+#: model:ir.actions.act_window,name:pingen.act_attachment_to_pingen_document
+#: field:ir.attachment,pingen_document_ids:0
+#: view:pingen.document:0
+msgid "Pingen Document"
+msgstr "Document Pingen"
+
+#. module: pingen
+#: field:pingen.document,attachment_id:0
+msgid "Document"
+msgstr "Document"
+
+#. module: pingen
+#: help:ir.attachment,pingen_send:0
+msgid "Defines if a document is merely uploaded or also sent"
+msgstr "Définit si un fichier est juste ajouté ou également envoyé"
+
+#. module: pingen
+#: view:pingen.document:0
+#: selection:pingen.document,state:0
+msgid "Pending"
+msgstr "En attente"
+
+#. module: pingen
+#: selection:ir.attachment,pingen_speed:0
+msgid "Economy"
+msgstr "Économique"
+
+#. module: pingen
+#: view:pingen.document:0
+msgid "Errors resolved"
+msgstr "Erreurs résolues"
+
+#. module: pingen
+#: sql_constraint:pingen.document:0
+msgid "Only one Pingen document is allowed per attachment."
+msgstr "Uniquement un document Pingen est autorisé par attachement."
+
+#. module: pingen
+#: code:addons/pingen/pingen_document.py:168
+#: code:addons/pingen/pingen_document.py:298
+#, python-format
+msgid "Error when asking Pingen to send the document %s: \n"
+"%s"
+msgstr "Erreurs lors de l'envoi du document par Pingen %s: \n"
+"%s"
+
+#. module: pingen
+#: view:pingen.document:0
+msgid "Update the letter's informations"
+msgstr "Mettre à jour les informations de la lettre"
+
+#. module: pingen
+#: view:pingen.document:0
+#: selection:pingen.document,state:0
+msgid "Canceled"
+msgstr "Annulé"
+
+#. module: pingen
+#: selection:pingen.document,state:0
+msgid "Connection Error"
+msgstr "Erreur de connexion"
+
+#. module: pingen
+#: model:ir.actions.act_window,name:pingen.action_pingen_document
+#: model:ir.ui.menu,name:pingen.menu_pingen_document
+msgid "Pingen Documents"
+msgstr "Documents Pingen"
+
+#. module: pingen
+#: field:pingen.document,last_error_message:0
+msgid "Error Message"
+msgstr "Message d'erreur"
+
+#. module: pingen
+#: selection:ir.attachment,pingen_color:0
+msgid "B/W"
+msgstr "N/B"
+
+#. module: pingen
+#: field:res.company,pingen_staging:0
+msgid "Pingen Staging"
+msgstr "Staging Pingen"
+
+#. module: pingen
+#: model:ir.model,name:pingen.model_ir_attachment
+msgid "ir.attachment"
+msgstr "ir.attachment"
+
+#. module: pingen
+#: view:pingen.document:0
+#: selection:pingen.document,state:0
+msgid "In Sendcenter"
+msgstr "Dans le Sendcenter"
+
+#. module: pingen
+#: view:pingen.document:0
+#: selection:pingen.document,state:0
+msgid "Sent"
+msgstr "Envoyé"
+
+#. module: pingen
+#: field:pingen.document,post_status:0
+msgid "Post Status"
+msgstr "État de la lettre"
+
+#. module: pingen
+#: code:addons/pingen/pingen_document.py:163
+#: code:addons/pingen/pingen_document.py:292
+#, python-format
+msgid "Connection Error when asking for sending the document %s to Pingen"
+msgstr "Erreur de connexion avec Pingen lors de l'envoi de %s"
+
+#. module: pingen
+#: view:res.company:0
+msgid "Configuration"
+msgstr "Configuration"
+
+#. module: pingen
+#: view:pingen.document:0
+msgid "Data"
+msgstr "Données"
+
+#. module: pingen
+#: view:pingen.document:0
+msgid "Options"
+msgstr "Options"
+
+#. module: pingen
+#: field:res.company,pingen_token:0
+msgid "Pingen Token"
+msgstr "Token Pingen"
+
+#. module: pingen
+#: field:ir.attachment,send_to_pingen:0
+msgid "Send to Pingen.com"
+msgstr "Ajouter sur Pingen.com"
+
+#. module: pingen
+#: model:ir.model,name:pingen.model_pingen_document
+msgid "pingen.document"
+msgstr "pingen.document"
+
+#. module: pingen
+#: view:pingen.document:0
+msgid "Dates"
+msgstr "Dates"
+
+#. module: pingen
+#: view:pingen.document:0
+msgid "Sendcenter"
+msgstr "Sendcenter"
+
+#. module: pingen
+#: sql_constraint:res.company:0
+msgid "The company name must be unique !"
+msgstr "The company name must be unique !"
+
+#. module: pingen
+#: field:pingen.document,parsed_address:0
+msgid "Parsed Address"
+msgstr "Adresse analysée"
+
+#. module: pingen
+#: view:ir.attachment:0
+#: view:pingen.document:0
+#: view:res.company:0
+msgid "Pingen.com"
+msgstr "Pingen.com"
+
+#. module: pingen
+#: field:pingen.document,country_id:0
+msgid "Country"
+msgstr "Pays"
+
+#. module: pingen
+#: view:ir.attachment:0
+msgid "Notes"
+msgstr "Notes"
+
+#. module: pingen
+#: view:pingen.document:0
+#: selection:pingen.document,state:0
+msgid "Pushed"
+msgstr "Ajouté"
+
+#. module: pingen
+#: model:ir.model,name:pingen.model_res_company
+msgid "Companies"
+msgstr "Compagnies"
+
+#. module: pingen
+#: code:addons/pingen/ir_attachment.py:90
+#, python-format
+msgid "The attachment %s is already pushed to pingen.com."
+msgstr "L'attachement %s est déjà envoyé sur pingen.com."
+
+#. module: pingen
+#: view:pingen.document:0
+msgid "Attached To"
+msgstr "Attaché à "
+
+#. module: pingen
+#: code:addons/pingen/pingen_document.py:136
+#, python-format
+msgid "The document does not meet the Pingen requirements."
+msgstr "Le document ne remplit pas les exigences de Pingen"
+
+#. module: pingen
+#: code:addons/pingen/ir_attachment.py:89
+#: code:addons/pingen/pingen_document.py:176
+#: code:addons/pingen/pingen_document.py:306
+#: code:addons/pingen/pingen_document.py:413
+#: view:pingen.document:0
+#, python-format
+msgid "Error"
+msgstr "Erreur"
+
+#. module: pingen
+#: code:addons/pingen/pingen_document.py:162
+#: code:addons/pingen/pingen_document.py:291
+#: code:addons/pingen/pingen_document.py:398
+#, python-format
+msgid "Pingen Connection Error"
+msgstr "Erreur de connexion avec Pingen"
+
+#. module: pingen
+#: field:pingen.document,send_date:0
+msgid "Date of sending"
+msgstr "Date d'envoi"
+
+#. module: pingen
+#: selection:ir.attachment,pingen_speed:0
+msgid "Priority"
+msgstr "Priorité"
+
+#. module: pingen
+#: selection:ir.attachment,pingen_color:0
+msgid "Color"
+msgstr "Color"
+
+#. module: pingen
+#: model:ir.model,name:pingen.model_pingen_task
+msgid "pingen.task"
+msgstr "pingen.task"
+
+#. module: pingen
+#: help:pingen.document,pingen_id:0
+msgid "ID of the document in the Pingen Documents"
+msgstr "ID du document sur Pingen"
+
+#. module: pingen
+#: code:addons/pingen/pingen_document.py:167
+#: code:addons/pingen/pingen_document.py:297
+#: code:addons/pingen/pingen_document.py:404
+#: view:pingen.document:0
+#: selection:pingen.document,state:0
+#, python-format
+msgid "Pingen Error"
+msgstr "Erreur Pingen"
+
+#. module: pingen
+#: view:pingen.document:0
+msgid "Actions"
+msgstr "Actions"
+
+#. module: pingen
+#: field:pingen.document,pages:0
+msgid "Pages"
+msgstr "Pages"
+
+#. module: pingen
+#: field:pingen.document,currency_id:0
+msgid "Currency"
+msgstr "Devise"
+
+#. module: pingen
+#: field:pingen.document,cost:0
+msgid "Cost"
+msgstr "Coût"
+
+#. module: pingen
+#: view:pingen.document:0
+msgid "Push to pingen.com"
+msgstr "Ajouter sur pingen.com"
+
+#. module: pingen
+#: view:pingen.document:0
+msgid "Ask pingen.com to send the document"
+msgstr "Demander à pingen.com d'envoyer le document"
+
+#. module: pingen
+#: help:pingen.document,post_id:0
+msgid "ID of the document in the Pingen Sendcenter"
+msgstr "ID du document dans le Sendcenter Pingen"
+
+#. module: pingen
+#: field:ir.attachment,pingen_speed:0
+msgid "Speed"
+msgstr "Vitesse"
+
+#. module: pingen
+#: field:pingen.document,post_id:0
+msgid "Pingen Post ID"
+msgstr "ID de lettre Pingen"
+
+#. module: pingen
+#: help:ir.attachment,pingen_speed:0
+msgid "Defines the sending speed if the document is automatically sent"
+msgstr "Définit la vitesse d'envoi si le document est automatiquement envoyé"
+
diff --git a/pingen/i18n/pingen.pot b/pingen/i18n/pingen.pot
new file mode 100644
index 0000000..ce80661
--- /dev/null
+++ b/pingen/i18n/pingen.pot
@@ -0,0 +1,388 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+# * pingen
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 6.1\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2012-11-26 10:55+0000\n"
+"PO-Revision-Date: 2012-11-26 10:55+0000\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: pingen
+#: code:addons/pingen/pingen_document.py:177
+#: code:addons/pingen/pingen_document.py:307
+#: code:addons/pingen/pingen_document.py:414
+#, python-format
+msgid "Unexcepted Error when updating the status of Document %s"
+msgstr ""
+
+#. module: pingen
+#: field:pingen.document,push_date:0
+msgid "Push Date"
+msgstr ""
+
+#. module: pingen
+#: view:pingen.document:0
+msgid "Errors"
+msgstr ""
+
+#. module: pingen
+#: field:pingen.document,pingen_id:0
+msgid "Pingen ID"
+msgstr ""
+
+#. module: pingen
+#: constraint:res.company:0
+msgid "Error! You can not create recursive companies."
+msgstr ""
+
+#. module: pingen
+#: field:ir.attachment,pingen_send:0
+msgid "Send"
+msgstr ""
+
+#. module: pingen
+#: code:addons/pingen/pingen_document.py:399
+#, python-format
+msgid "Connection Error when updating the status of Document %s from Pingen"
+msgstr ""
+
+#. module: pingen
+#: field:pingen.document,state:0
+msgid "State"
+msgstr ""
+
+#. module: pingen
+#: field:ir.attachment,pingen_color:0
+msgid "Type of print"
+msgstr ""
+
+#. module: pingen
+#: view:pingen.document:0
+msgid "Attachment"
+msgstr ""
+
+#. module: pingen
+#: code:addons/pingen/pingen_document.py:405
+#, python-format
+msgid "Error when updating the status of Document %s from Pingen: \n"
+"%s"
+msgstr ""
+
+#. module: pingen
+#: model:ir.actions.act_window,name:pingen.act_attachment_to_pingen_document
+#: field:ir.attachment,pingen_document_ids:0
+#: view:pingen.document:0
+msgid "Pingen Document"
+msgstr ""
+
+#. module: pingen
+#: field:pingen.document,attachment_id:0
+msgid "Document"
+msgstr ""
+
+#. module: pingen
+#: help:ir.attachment,pingen_send:0
+msgid "Defines if a document is merely uploaded or also sent"
+msgstr ""
+
+#. module: pingen
+#: view:pingen.document:0
+#: selection:pingen.document,state:0
+msgid "Pending"
+msgstr ""
+
+#. module: pingen
+#: selection:ir.attachment,pingen_speed:0
+msgid "Economy"
+msgstr ""
+
+#. module: pingen
+#: view:pingen.document:0
+msgid "Errors resolved"
+msgstr ""
+
+#. module: pingen
+#: sql_constraint:pingen.document:0
+msgid "Only one Pingen document is allowed per attachment."
+msgstr ""
+
+#. module: pingen
+#: code:addons/pingen/pingen_document.py:168
+#: code:addons/pingen/pingen_document.py:298
+#, python-format
+msgid "Error when asking Pingen to send the document %s: \n"
+"%s"
+msgstr ""
+
+#. module: pingen
+#: view:pingen.document:0
+msgid "Update the letter's informations"
+msgstr ""
+
+#. module: pingen
+#: view:pingen.document:0
+#: selection:pingen.document,state:0
+msgid "Canceled"
+msgstr ""
+
+#. module: pingen
+#: selection:pingen.document,state:0
+msgid "Connection Error"
+msgstr ""
+
+#. module: pingen
+#: model:ir.actions.act_window,name:pingen.action_pingen_document
+#: model:ir.ui.menu,name:pingen.menu_pingen_document
+msgid "Pingen Documents"
+msgstr ""
+
+#. module: pingen
+#: field:pingen.document,last_error_message:0
+msgid "Error Message"
+msgstr ""
+
+#. module: pingen
+#: selection:ir.attachment,pingen_color:0
+msgid "B/W"
+msgstr ""
+
+#. module: pingen
+#: field:res.company,pingen_staging:0
+msgid "Pingen Staging"
+msgstr ""
+
+#. module: pingen
+#: model:ir.model,name:pingen.model_ir_attachment
+msgid "ir.attachment"
+msgstr ""
+
+#. module: pingen
+#: view:pingen.document:0
+#: selection:pingen.document,state:0
+msgid "In Sendcenter"
+msgstr ""
+
+#. module: pingen
+#: view:pingen.document:0
+#: selection:pingen.document,state:0
+msgid "Sent"
+msgstr ""
+
+#. module: pingen
+#: field:pingen.document,post_status:0
+msgid "Post Status"
+msgstr ""
+
+#. module: pingen
+#: code:addons/pingen/pingen_document.py:163
+#: code:addons/pingen/pingen_document.py:292
+#, python-format
+msgid "Connection Error when asking for sending the document %s to Pingen"
+msgstr ""
+
+#. module: pingen
+#: view:res.company:0
+msgid "Configuration"
+msgstr ""
+
+#. module: pingen
+#: view:pingen.document:0
+msgid "Data"
+msgstr ""
+
+#. module: pingen
+#: view:pingen.document:0
+msgid "Options"
+msgstr ""
+
+#. module: pingen
+#: field:res.company,pingen_token:0
+msgid "Pingen Token"
+msgstr ""
+
+#. module: pingen
+#: field:ir.attachment,send_to_pingen:0
+msgid "Send to Pingen.com"
+msgstr ""
+
+#. module: pingen
+#: model:ir.model,name:pingen.model_pingen_document
+msgid "pingen.document"
+msgstr ""
+
+#. module: pingen
+#: view:pingen.document:0
+msgid "Dates"
+msgstr ""
+
+#. module: pingen
+#: view:pingen.document:0
+msgid "Sendcenter"
+msgstr ""
+
+#. module: pingen
+#: sql_constraint:res.company:0
+msgid "The company name must be unique !"
+msgstr ""
+
+#. module: pingen
+#: field:pingen.document,parsed_address:0
+msgid "Parsed Address"
+msgstr ""
+
+#. module: pingen
+#: view:ir.attachment:0
+#: view:pingen.document:0
+#: view:res.company:0
+msgid "Pingen.com"
+msgstr ""
+
+#. module: pingen
+#: field:pingen.document,country_id:0
+msgid "Country"
+msgstr ""
+
+#. module: pingen
+#: view:ir.attachment:0
+msgid "Notes"
+msgstr ""
+
+#. module: pingen
+#: view:pingen.document:0
+#: selection:pingen.document,state:0
+msgid "Pushed"
+msgstr ""
+
+#. module: pingen
+#: model:ir.model,name:pingen.model_res_company
+msgid "Companies"
+msgstr ""
+
+#. module: pingen
+#: code:addons/pingen/ir_attachment.py:90
+#, python-format
+msgid "The attachment %s is already pushed to pingen.com."
+msgstr ""
+
+#. module: pingen
+#: view:pingen.document:0
+msgid "Attached To"
+msgstr ""
+
+#. module: pingen
+#: code:addons/pingen/pingen_document.py:136
+#, python-format
+msgid "The document does not meet the Pingen requirements."
+msgstr ""
+
+#. module: pingen
+#: code:addons/pingen/ir_attachment.py:89
+#: code:addons/pingen/pingen_document.py:176
+#: code:addons/pingen/pingen_document.py:306
+#: code:addons/pingen/pingen_document.py:413
+#: view:pingen.document:0
+#, python-format
+msgid "Error"
+msgstr ""
+
+#. module: pingen
+#: code:addons/pingen/pingen_document.py:162
+#: code:addons/pingen/pingen_document.py:291
+#: code:addons/pingen/pingen_document.py:398
+#, python-format
+msgid "Pingen Connection Error"
+msgstr ""
+
+#. module: pingen
+#: field:pingen.document,send_date:0
+msgid "Date of sending"
+msgstr ""
+
+#. module: pingen
+#: selection:ir.attachment,pingen_speed:0
+msgid "Priority"
+msgstr ""
+
+#. module: pingen
+#: selection:ir.attachment,pingen_color:0
+msgid "Color"
+msgstr ""
+
+#. module: pingen
+#: model:ir.model,name:pingen.model_pingen_task
+msgid "pingen.task"
+msgstr ""
+
+#. module: pingen
+#: help:pingen.document,pingen_id:0
+msgid "ID of the document in the Pingen Documents"
+msgstr ""
+
+#. module: pingen
+#: code:addons/pingen/pingen_document.py:167
+#: code:addons/pingen/pingen_document.py:297
+#: code:addons/pingen/pingen_document.py:404
+#: view:pingen.document:0
+#: selection:pingen.document,state:0
+#, python-format
+msgid "Pingen Error"
+msgstr ""
+
+#. module: pingen
+#: view:pingen.document:0
+msgid "Actions"
+msgstr ""
+
+#. module: pingen
+#: field:pingen.document,pages:0
+msgid "Pages"
+msgstr ""
+
+#. module: pingen
+#: field:pingen.document,currency_id:0
+msgid "Currency"
+msgstr ""
+
+#. module: pingen
+#: field:pingen.document,cost:0
+msgid "Cost"
+msgstr ""
+
+#. module: pingen
+#: view:pingen.document:0
+msgid "Push to pingen.com"
+msgstr ""
+
+#. module: pingen
+#: view:pingen.document:0
+msgid "Ask pingen.com to send the document"
+msgstr ""
+
+#. module: pingen
+#: help:pingen.document,post_id:0
+msgid "ID of the document in the Pingen Sendcenter"
+msgstr ""
+
+#. module: pingen
+#: field:ir.attachment,pingen_speed:0
+msgid "Speed"
+msgstr ""
+
+#. module: pingen
+#: field:pingen.document,post_id:0
+msgid "Pingen Post ID"
+msgstr ""
+
+#. module: pingen
+#: help:ir.attachment,pingen_speed:0
+msgid "Defines the sending speed if the document is automatically sent"
+msgstr ""
+
diff --git a/pingen/ir_attachment.py b/pingen/ir_attachment.py
new file mode 100644
index 0000000..cbb950d
--- /dev/null
+++ b/pingen/ir_attachment.py
@@ -0,0 +1,124 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Author: Guewen Baconnier
+# Copyright 2012 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+import requests
+import base64
+
+from openerp.osv import osv, orm, fields
+from openerp.tools.translate import _
+
+
+class ir_attachment(orm.Model):
+
+ _inherit = 'ir.attachment'
+
+ _columns = {
+ 'send_to_pingen': fields.boolean('Send to Pingen.com'),
+ 'pingen_document_ids': fields.one2many(
+ 'pingen.document', 'attachment_id',
+ string='Pingen Document', readonly=True),
+ 'pingen_send': fields.boolean(
+ 'Send',
+ help="Defines if a document is merely uploaded or also sent"),
+ 'pingen_speed': fields.selection(
+ [('1', 'Priority'), ('2', 'Economy')],
+ 'Speed',
+ help="Defines the sending speed if the document is automatically sent"),
+ 'pingen_color': fields.selection( [('0', 'B/W'), ('1', 'Color')], 'Type of print'),
+ }
+
+ _defaults = {
+ 'pingen_send': True,
+ 'pingen_color': '0',
+ 'pingen_speed': '2',
+ }
+
+ def _prepare_pingen_document_vals(self, cr, uid, attachment, context=None):
+ return {'attachment_id': attachment.id,
+ 'config': 'created from attachment'}
+
+ def _handle_pingen_document(self, cr, uid, attachment_id, context=None):
+ """ Reponsible of the related ``pingen.document`` when the ``send_to_pingen``
+ field is modified.
+
+ Only one pingen document can be created per attachment.
+
+ When ``send_to_pingen`` is activated:
+ * Create a ``pingen.document`` if it does not already exist
+ * Put the related ``pingen.document`` to ``pending`` if it already exist
+ When it is deactivated:
+ * Do nothing if no related ``pingen.document`` exists
+ * Or cancel it
+ * If it has already been pushed to pingen.com, raises
+ an `osv.except_osv` exception
+ """
+ pingen_document_obj = self.pool.get('pingen.document')
+ attachment = self.browse(cr, uid, attachment_id, context=context)
+ document = attachment.pingen_document_ids[0] if attachment.pingen_document_ids else None
+ if attachment.send_to_pingen:
+ if document:
+ document.write({'state': 'pending'}, context=context)
+ else:
+ pingen_document_obj.create(
+ cr, uid,
+ self._prepare_pingen_document_vals(
+ cr, uid, attachment, context=context),
+ context=context)
+ else:
+ if document:
+ if document.state == 'pushed':
+ raise osv.except_osv(
+ _('Error'),
+ _('The attachment %s is already pushed to pingen.com.') % \
+ attachment.name)
+ document.write({'state': 'canceled'}, context=context)
+ return
+
+ def create(self, cr, uid, vals, context=None):
+ attachment_id = super(ir_attachment, self).create(cr, uid, vals, context=context)
+ if 'send_to_pingen' in vals:
+ self._handle_pingen_document(cr, uid, attachment_id, context=context)
+ return attachment_id
+
+ def write(self, cr, uid, ids, vals, context=None):
+ res = super(ir_attachment, self).write(cr, uid, ids, vals, context=context)
+ if 'send_to_pingen' in vals:
+ for attachment_id in ids:
+ self._handle_pingen_document(cr, uid, attachment_id, context=context)
+ return res
+
+ def _decoded_content(self, cr, uid, attachment, context=None):
+ """ Returns the decoded content of an attachment (stored or url)
+
+ Returns None if the type is 'url' and the url is not reachable.
+ """
+ decoded_document = None
+ if attachment.type == 'binary':
+ decoded_document = base64.decodestring(attachment.datas)
+ elif attachment.type == 'url':
+ response = requests.get(attachment.url)
+ if response.ok:
+ decoded_document = requests.content
+ else:
+ raise Exception(
+ 'The type of attachment %s is not handled' % attachment.type)
+ return decoded_document
+
diff --git a/pingen/ir_attachment_view.xml b/pingen/ir_attachment_view.xml
new file mode 100644
index 0000000..d51a906
--- /dev/null
+++ b/pingen/ir_attachment_view.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+ ir.attachment.pingen.view
+ ir.attachment
+ form
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pingen/pingen.py b/pingen/pingen.py
new file mode 100644
index 0000000..b460618
--- /dev/null
+++ b/pingen/pingen.py
@@ -0,0 +1,209 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Author: Guewen Baconnier
+# Copyright 2012 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+import requests
+import logging
+import urlparse
+import json
+
+from requests.packages.urllib3.filepost import encode_multipart_formdata
+
+_logger = logging.getLogger(__name__)
+
+POST_SENDING_STATUS = {
+ 100: 'Ready/Pending',
+ 101: 'Processing',
+ 102: 'Waiting for confirmation',
+ 200: 'Sent',
+ 300: 'Some error occured and object wasn\'t sent',
+ 400: 'Sending cancelled',
+}
+
+
+class PingenException(RuntimeError):
+ """There was an ambiguous exception that occurred while handling your
+ request."""
+
+
+class ConnectionError(PingenException):
+ """An Error occured with the pingen API"""
+
+
+class APIError(PingenException):
+ """An Error occured with the pingen API"""
+
+
+class Pingen(object):
+ """ Interface to the pingen.com API """
+
+ def __init__(self, token, staging=True):
+ self._token = token
+ self.staging = staging
+ self._session = None
+ super(Pingen, self).__init__()
+
+ @property
+ def url(self):
+ if self.staging:
+ return 'https://stage-api.pingen.com'
+ return 'https://api.pingen.com'
+
+ @property
+ def session(self):
+ """ Build a requests session """
+ if self._session is not None:
+ return self._session
+ self._session = requests.Session(
+ params={'token': self._token},
+ # with safe_mode, requests catch errors and
+ # returns a blank response with an error
+ config={'safe_mode': True},
+ # verify = False required for staging environment
+ # because the SSL certificate is wrong
+ verify=not self.staging)
+ return self._session
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *args):
+ self.close()
+
+ def close(self):
+ """Dispose of any internal state. """
+ if self._session:
+ self._session.close()
+
+ def _send(self, method, endpoint, **kwargs):
+ """ Send a request to the pingen API using requests
+
+ Add necessary boilerplate to call pingen.com API
+ (authentication, configuration, ...)
+
+ :param boundmethod method: requests method to call
+ :param str endpoint: endpoint to call
+ :param kwargs: additional arguments forwarded to the requests method
+ """
+ complete_url = urlparse.urljoin(self.url, endpoint)
+
+ response = method(complete_url, **kwargs)
+
+ if not response.ok:
+ raise ConnectionError(
+ "%s: %s" % (response.json['errorcode'],
+ response.json['errormessage']))
+
+ if response.json['error']:
+ raise APIError(
+ "%s: %s" % (response.json['errorcode'], response.json['errormessage']))
+
+ return response
+
+ def push_document(self, filename, filestream, send=None, speed=None, color=None):
+ """ Upload a document to pingen.com and eventually ask to send it
+
+ :param str filename: name of the file to push
+ :param StringIO filestream: file to push
+ :param boolean send: if True, the document will be sent by pingen.com
+ :param int/str speed: sending speed of the document if it is send
+ 1 = Priority, 2 = Economy
+ :param int/str color: type of print, 0 = B/W, 1 = Color
+ :return: tuple with 3 items:
+ 1. document_id on pingen.com
+ 2. post_id on pingen.com if it has been sent or None
+ 3. dict of the created item on pingen (details)
+ """
+ data = {
+ 'send': send,
+ 'speed': speed,
+ 'color': color,
+ }
+
+ # we cannot use the `files` param alongside
+ # with the `datas`param when data is a
+ # JSON-encoded data. We have to construct
+ # the entire body and send it to `data`
+ # https://github.com/kennethreitz/requests/issues/950
+ formdata = {
+ 'file': (filename, filestream.read()),
+ 'data': json.dumps(data),
+ }
+
+ multipart, content_type = encode_multipart_formdata(formdata)
+
+ response = self._send(
+ self.session.post,
+ 'document/upload',
+ headers={'Content-Type': content_type},
+ data=multipart)
+
+ rjson = response.json
+
+ document_id = rjson['id']
+ if rjson.get('send'):
+ # confusing name but send_id is the posted id
+ posted_id = rjson['send'][0]['send_id']
+ item = rjson['item']
+
+ return document_id, posted_id, item
+
+ def send_document(self, document_id, speed=None, color=None):
+ """ Send a uploaded document to pingen.com
+
+ :param int document_id: id of the document to send
+ :param int/str speed: sending speed of the document if it is send
+ 1 = Priority, 2 = Economy
+ :param int/str color: type of print, 0 = B/W, 1 = Color
+ :return: id of the post on pingen.com
+ """
+ data = {
+ 'speed': speed,
+ 'color': color,
+ }
+ response = self._send(
+ self.session.post,
+ 'document/send',
+ params={'id': document_id},
+ data={'data': json.dumps(data)})
+
+ return response.json['id']
+
+ def post_infos(self, post_id):
+ """ Return the information of a post
+
+ :param int post_id: id of the document to send
+ :return: dict of infos of the post
+ """
+ response = self._send(
+ self.session.get,
+ 'post/get',
+ params={'id': post_id})
+
+ return response.json['item']
+
+ @staticmethod
+ def is_posted(post_infos):
+ """ return True if the post has been sent
+
+ :param dict post_infos: post infos returned by `post_infos`
+ """
+ return post_infos['status'] == 200
+
diff --git a/pingen/pingen_data.xml b/pingen/pingen_data.xml
new file mode 100644
index 0000000..5227210
--- /dev/null
+++ b/pingen/pingen_data.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+ Run Pingen Document Push
+
+
+ 1
+ hours
+ -1
+
+ pingen.document
+ _push_and_send_to_pingen_cron
+ (None,)
+
+
+
+ Run Pingen Document Update
+
+
+ 1
+ days
+ -1
+
+ pingen.document
+ _update_post_infos_cron
+ (None,)
+
+
+
+
diff --git a/pingen/pingen_document.py b/pingen/pingen_document.py
new file mode 100644
index 0000000..522b2c2
--- /dev/null
+++ b/pingen/pingen_document.py
@@ -0,0 +1,420 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Author: Guewen Baconnier
+# Copyright 2012 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+import logging
+
+from cStringIO import StringIO
+
+from contextlib import closing
+from openerp.osv import osv, orm, fields
+from openerp.tools.translate import _
+from openerp import pooler
+from .pingen import APIError, ConnectionError, POST_SENDING_STATUS
+
+_logger = logging.getLogger(__name__)
+
+
+class pingen_document(orm.Model):
+ """ A pingen document is the state of the synchronization of
+ an attachment with pingen.com
+
+ It stores the configuration and the current state of the synchronization.
+ It also serves as a queue of documents to push to pingen.com
+ """
+
+ _name = 'pingen.document'
+ _inherits = {'ir.attachment': 'attachment_id'}
+
+ _columns = {
+ 'attachment_id': fields.many2one(
+ 'ir.attachment', 'Document',
+ required=True, readonly=True,
+ ondelete='cascade'),
+ 'state': fields.selection(
+ [('pending', 'Pending'),
+ ('pushed', 'Pushed'),
+ ('sendcenter', 'In Sendcenter'),
+ ('sent', 'Sent'),
+ ('error', 'Connection Error'),
+ ('pingen_error', 'Pingen Error'),
+ ('canceled', 'Canceled')],
+ string='State', readonly=True, required=True),
+ 'push_date': fields.datetime('Push Date', readonly=True),
+
+ # for `error` and `pingen_error` states when we push
+ 'last_error_message': fields.text('Error Message', readonly=True),
+
+ # pingen IDs
+ 'pingen_id': fields.integer(
+ 'Pingen ID', readonly=True,
+ help="ID of the document in the Pingen Documents"),
+ 'post_id': fields.integer(
+ 'Pingen Post ID', readonly=True,
+ help="ID of the document in the Pingen Sendcenter"),
+
+ # sendcenter infos
+ 'post_status': fields.char('Post Status', size=128, readonly=True),
+ 'parsed_address': fields.text('Parsed Address', readonly=True),
+ 'cost': fields.float('Cost', readonly=True),
+ 'currency_id': fields.many2one('res.currency', 'Currency', readonly=True),
+ 'country_id': fields.many2one('res.country', 'Country', readonly=True),
+ 'send_date': fields.datetime('Date of sending', readonly=True),
+ 'pages': fields.integer('Pages', readonly=True),
+ }
+
+ _defaults = {
+ 'state': 'pending',
+ }
+
+ _sql_constraints = [
+ ('pingen_document_attachment_uniq',
+ 'unique (attachment_id)',
+ 'Only one Pingen document is allowed per attachment.'),
+ ]
+
+ def _get_pingen_session(self, cr, uid, context=None):
+ """ Returns a pingen session for a user """
+ company = self.pool.get('res.users').browse(
+ cr, uid, uid, context=context).company_id
+ return self.pool.get('res.company')._pingen(cr, uid, company, context=context)
+
+ def _push_to_pingen(self, cr, uid, document, pingen=None, context=None):
+ """ Push a document to pingen.com
+
+ :param Pingen pingen: optional pingen object to reuse session
+ """
+ attachment_obj = self.pool.get('ir.attachment')
+
+ decoded_document = attachment_obj._decoded_content(
+ cr, uid, document.attachment_id, context=context)
+
+ if pingen is None:
+ pingen = self._get_pingen_session(cr, uid, context=context)
+ try:
+ doc_id, post_id, infos = pingen.push_document(
+ document.datas_fname,
+ StringIO(decoded_document),
+ document.pingen_send,
+ document.pingen_speed,
+ document.pingen_color)
+ except ConnectionError as e:
+ _logger.exception(
+ 'Connection Error when pushing Pingen Document %s to %s.' %
+ (document.id, pingen.url))
+ raise
+
+ except APIError as e:
+ _logger.error(
+ 'API Error when pushing Pingen Document %s to %s.' %
+ (document.id, pingen.url))
+ raise
+
+ error = False
+ state = 'pushed'
+ if post_id:
+ state = 'sendcenter'
+ elif infos['requirement_failure']:
+ state = 'pingen_error'
+ error = _('The document does not meet the Pingen requirements.')
+
+ document.write(
+ {'last_error_message': error,
+ 'state': state,
+ 'push_date': infos['date'],
+ 'pingen_id': doc_id,
+ 'post_id': post_id},
+ context=context)
+ _logger.info('Pingen Document %s: pushed to %s' % (document.id, pingen.url))
+
+ def push_to_pingen(self, cr, uid, ids, context=None):
+ """ Push a document to pingen.com
+
+ Convert errors to osv.except_osv to be handled by the client.
+
+ Wrapper method for multiple ids (when triggered from button for
+ instance) for public interface.
+ """
+ assert len(ids) == 1, "Only 1 id is allowed"
+ with self._get_pingen_session(cr, uid, context=context) as session:
+ for document in self.browse(cr, uid, ids, context=context):
+ try:
+ self._push_to_pingen(
+ cr, uid, document, pingen=session, context=context)
+ except ConnectionError as e:
+ raise osv.except_osv(
+ _('Pingen Connection Error'),
+ _('Connection Error when asking for sending the document %s to Pingen') % document.name)
+
+ except APIError as e:
+ raise osv.except_osv(
+ _('Pingen Error'),
+ _('Error when asking Pingen to send the document %s: '
+ '\n%s') % (document.name, e))
+
+ except:
+ _logger.exception(
+ 'Unexcepted Error when updating the status of pingen.document %s: ' %
+ document.id)
+ raise osv.except_osv(
+ _('Error'),
+ _('Unexcepted Error when updating the status of Document %s') % document.name)
+ return True
+
+ def _push_and_send_to_pingen_cron(self, cr, uid, ids, context=None):
+ """ Push a document to pingen.com
+
+ Intended to be used in a cron.
+
+ Commit after each record
+
+ Instead of raising, store the error in the pingen.document
+ """
+ if not ids:
+ ids = self.search(
+ cr, uid,
+ # do not retry pingen_error, they should be treated manually
+ [('state', 'in', ['pending', 'pushed', 'error'])],
+ limit=100,
+ context=context)
+
+ with closing(pooler.get_db(cr.dbname).cursor()) as loc_cr, \
+ self._get_pingen_session(cr, uid, context=context) as session:
+ for document in self.browse(loc_cr, uid, ids, context=context):
+
+ if document.state == 'error':
+ self._resolve_error(loc_cr, uid, document, context=context)
+ document.refresh()
+
+ try:
+ if document.state == 'pending':
+ self._push_to_pingen(
+ loc_cr, uid, document, pingen=session, context=context)
+
+ elif document.state == 'pushed':
+ self._ask_pingen_send(
+ loc_cr, uid, document, pingen=session, context=context)
+ except ConnectionError as e:
+ document.write({'last_error_message': e,
+ 'state': 'error'},
+ context=context)
+ except APIError as e:
+ document.write({'last_error_message': e,
+ 'state': 'pingen_error'},
+ context=context)
+ except:
+ _logger.error('Unexcepted error in pingen cron')
+ loc_cr.rollback()
+ raise
+
+ else:
+ loc_cr.commit()
+
+ return True
+
+ def _resolve_error(self, cr, uid, document, context=None):
+ """ A document as resolved, put in the correct state """
+ if document.post_id:
+ state = 'sendcenter'
+ elif document.pingen_id:
+ state = 'pushed'
+ else:
+ state = 'pending'
+ document.write({'state': state}, context=context)
+
+ def resolve_error(self, cr, uid, ids, context=None):
+ """ A document as resolved, put in the correct state """
+ for document in self.browse(cr, uid, ids, context=context):
+ self._resolve_error(cr, uid, document, context=context)
+ return True
+
+ def _ask_pingen_send(self, cr, uid, document, pingen, context=None):
+ """ For a document already pushed to pingen, ask to send it.
+
+ :param Pingen pingen: pingen object to reuse
+ """
+ # sending has been explicitely asked so we change the option
+ # for consistency
+ if not document.pingen_send:
+ document.write({'pingen_send': True}, context=context)
+
+ try:
+ post_id = pingen.send_document(
+ document.pingen_id,
+ document.pingen_speed,
+ document.pingen_color)
+ except ConnectionError as e:
+ _logger.exception('Connection Error when asking for sending Pingen Document %s to %s.' %
+ (document.id, pingen.url))
+ raise
+ except APIError as e:
+ _logger.exception('API Error when asking for sending Pingen Document %s to %s.' %
+ (document.id, pingen.url))
+ raise
+
+ document.write(
+ {'last_error_message': False,
+ 'state': 'sendcenter',
+ 'post_id': post_id},
+ context=context)
+ _logger.info('Pingen Document %s: asked for sending to %s' % (document.id, pingen.url))
+
+ return True
+
+ def ask_pingen_send(self, cr, uid, ids, context=None):
+ """ For a document already pushed to pingen, ask to send it.
+
+ Wrapper method for multiple ids (when triggered from button for
+ instance) for public interface.
+ """
+ assert len(ids) == 1, "Only 1 id is allowed"
+ with self._get_pingen_session(cr, uid, context=context) as session:
+ for document in self.browse(cr, uid, ids, context=context):
+ try:
+ self._ask_pingen_send(cr, uid, document, pingen=session, context=context)
+ except ConnectionError as e:
+ raise osv.except_osv(
+ _('Pingen Connection Error'),
+ _('Connection Error when asking for '
+ 'sending the document %s to Pingen') % document.name)
+
+ except APIError as e:
+ raise osv.except_osv(
+ _('Pingen Error'),
+ _('Error when asking Pingen to send the document %s: '
+ '\n%s') % (document.name, e))
+
+ except:
+ _logger.exception(
+ 'Unexcepted Error when updating the status of pingen.document %s: ' %
+ document.id)
+ raise osv.except_osv(
+ _('Error'),
+ _('Unexcepted Error when updating the status of Document %s') % document.name)
+ return True
+
+ def _update_post_infos(self, cr, uid, document, pingen, context=None):
+ """ Update the informations from pingen of a document in the Sendcenter
+
+ :param Pingen pingen: pingen object to reuse
+ """
+ if not document.post_id:
+ return
+
+ try:
+ post_infos = pingen.post_infos(document.post_id)
+ except ConnectionError as e:
+ _logger.exception(
+ 'Connection Error when asking for '
+ 'sending Pingen Document %s to %s.' %
+ (document.id, pingen.url))
+ raise
+ except APIError as e:
+ _logger.exception(
+ 'API Error when asking for sending Pingen Document %s to %s.' %
+ (document.id, pingen.url))
+ raise
+
+ currency_ids = self.pool.get('res.currency').search(
+ cr, uid, [('name', '=', post_infos['currency'])], context=context)
+ country_ids = self.pool.get('res.country').search(
+ cr, uid, [('code', '=', post_infos['country'])], context=context)
+ vals = {
+ 'post_status': POST_SENDING_STATUS[post_infos['status']],
+ 'cost': post_infos['cost'],
+ 'currency_id': currency_ids[0] if currency_ids else False,
+ 'parsed_address': post_infos['address'],
+ 'country_id': country_ids[0] if country_ids else False,
+ 'send_date': post_infos['date'],
+ 'pages': post_infos['pages'],
+ 'last_error_message': False,
+ }
+ if pingen.is_posted(post_infos):
+ vals['state'] = 'sent'
+
+ document.write(vals, context=context)
+ _logger.info('Pingen Document %s: status updated' % document.id)
+
+ def _update_post_infos_cron(self, cr, uid, ids, context=None):
+ """ Update the informations from pingen of a document in the Sendcenter
+
+ Intended to be used in a cron.
+
+ Commit after each record
+
+ Do not raise errors, only skip the update of the record.
+ """
+ if not ids:
+ ids = self.search(
+ cr, uid,
+ [('state', '=', 'sendcenter')],
+ context=context)
+
+ with closing(pooler.get_db(cr.dbname).cursor()) as loc_cr, \
+ self._get_pingen_session(cr, uid, context=context) as session:
+ for document in self.browse(loc_cr, uid, ids, context=context):
+ try:
+ self._update_post_infos(
+ loc_cr, uid, document, pingen=session, context=context)
+ except (ConnectionError, APIError):
+ # will be retried the next time
+ # In any case, the error has been logged by _update_post_infos
+ loc_cr.rollback()
+ except:
+ _logger.error('Unexcepted error in pingen cron')
+ loc_cr.rollback()
+ raise
+ else:
+ loc_cr.commit()
+ return True
+
+ def update_post_infos(self, cr, uid, ids, context=None):
+ """ Update the informations from pingen of a document in the Sendcenter
+
+ Wrapper method for multiple ids (when triggered from button for
+ instance) for public interface.
+ """
+ assert len(ids) == 1, "Only 1 id is allowed"
+ with self._get_pingen_session(cr, uid, context=context) as session:
+ for document in self.browse(cr, uid, ids, context=context):
+ try:
+ self._update_post_infos(
+ cr, uid, document, pingen=session, context=context)
+ except ConnectionError as e:
+ raise osv.except_osv(
+ _('Pingen Connection Error'),
+ _('Connection Error when updating the status of Document %s'
+ ' from Pingen') % document.name)
+
+ except APIError as e:
+ raise osv.except_osv(
+ _('Pingen Error'),
+ _('Error when updating the status of Document %s from Pingen: '
+ '\n%s') % (document.name, e))
+
+ except:
+ _logger.exception(
+ 'Unexcepted Error when updating the status of pingen.document %s: ' %
+ document.id)
+ raise osv.except_osv(
+ _('Error'),
+ _('Unexcepted Error when updating the status of Document %s') % document.name)
+ return True
+
diff --git a/pingen/pingen_document_view.xml b/pingen/pingen_document_view.xml
new file mode 100644
index 0000000..1686167
--- /dev/null
+++ b/pingen/pingen_document_view.xml
@@ -0,0 +1,161 @@
+
+
+
+
+
+ pingen.document.tree
+ pingen.document
+ tree
+
+
+
+
+
+
+
+
+
+
+
+
+
+ pingen.document.form
+ pingen.document
+ form
+
+
+
+
+
+
+ pingen.document.search
+ pingen.document
+ search
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Pingen Documents
+ ir.actions.act_window
+ pingen.document
+ form
+ tree,form
+
+
+
+
+
+
+
diff --git a/pingen/res_company.py b/pingen/res_company.py
new file mode 100644
index 0000000..47040b3
--- /dev/null
+++ b/pingen/res_company.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Author: Guewen Baconnier
+# Copyright 2012 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from openerp.osv import orm, fields
+from openerp.osv.orm import browse_record
+from .pingen import Pingen
+
+class res_company(orm.Model):
+
+ _inherit = 'res.company'
+
+ _columns = {
+ 'pingen_token': fields.char('Pingen Token', size=32),
+ 'pingen_staging': fields.boolean('Pingen Staging')
+ }
+
+ def _pingen(self, cr, uid, company, context=None):
+ """ Return a Pingen instance to work on """
+ assert isinstance(company, (int, long, browse_record)), \
+ "one id or browse_record expected"
+ if not isinstance(company, browse_record):
+ company = self.browse(cr, uid, company_id, context=context)
+ return Pingen(company.pingen_token, staging=company.pingen_staging)
+
diff --git a/pingen/res_company_view.xml b/pingen/res_company_view.xml
new file mode 100644
index 0000000..d915f74
--- /dev/null
+++ b/pingen/res_company_view.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+ res.company.form.inherit
+ res.company
+ form
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pingen/security/ir.model.access.csv b/pingen/security/ir.model.access.csv
new file mode 100644
index 0000000..4b6e683
--- /dev/null
+++ b/pingen/security/ir.model.access.csv
@@ -0,0 +1,3 @@
+"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
+"access_pingen_document_all","pingen_document all","model_pingen_document",,1,0,0,0
+"access_pingen_document_group_user","pingen_document group_user","model_pingen_document","base.group_user",1,1,1,1
diff --git a/pingen_document/__init__.py b/pingen_document/__init__.py
new file mode 100644
index 0000000..b35b46c
--- /dev/null
+++ b/pingen_document/__init__.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Author: Guewen Baconnier
+# Copyright 2012 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
diff --git a/pingen_document/__openerp__.py b/pingen_document/__openerp__.py
new file mode 100644
index 0000000..e49efbf
--- /dev/null
+++ b/pingen_document/__openerp__.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Author: Guewen Baconnier
+# Copyright 2012 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+{
+ 'name' : 'pingen.com integration (document)',
+ 'version' : '1.0',
+ 'author' : 'Camptocamp',
+ 'maintainer': 'Camptocamp',
+ 'license': 'AGPL-3',
+ 'category': 'Reporting',
+ 'complexity': 'easy',
+ 'depends' : ['document', 'pingen'],
+ 'description': """
+Glue module between the `pingen` and the `document` modules.
+""",
+ 'website': 'http://www.camptocamp.com',
+ 'data': [
+ 'pingen_document_view.xml',
+ ],
+ 'tests': [],
+ 'installable': True,
+ 'auto_install': True,
+}
diff --git a/pingen_document/i18n/de.po b/pingen_document/i18n/de.po
new file mode 100644
index 0000000..fc4bf6a
--- /dev/null
+++ b/pingen_document/i18n/de.po
@@ -0,0 +1,32 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+# * pingen_document
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 6.1\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2012-11-26 10:54+0000\n"
+"PO-Revision-Date: 2012-11-26 10:54+0000\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: pingen_document
+#: view:ir.attachment:0
+msgid "Notes"
+msgstr "Notes"
+
+#. module: pingen_document
+#: model:ir.ui.menu,name:pingen_document.menu_pingen_document_document
+msgid "Pingen Documents"
+msgstr "Pingen Documents"
+
+#. module: pingen_document
+#: view:ir.attachment:0
+msgid "Pingen.com"
+msgstr "Pingen.com"
+
diff --git a/pingen_document/i18n/en.po b/pingen_document/i18n/en.po
new file mode 100644
index 0000000..fc4bf6a
--- /dev/null
+++ b/pingen_document/i18n/en.po
@@ -0,0 +1,32 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+# * pingen_document
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 6.1\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2012-11-26 10:54+0000\n"
+"PO-Revision-Date: 2012-11-26 10:54+0000\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: pingen_document
+#: view:ir.attachment:0
+msgid "Notes"
+msgstr "Notes"
+
+#. module: pingen_document
+#: model:ir.ui.menu,name:pingen_document.menu_pingen_document_document
+msgid "Pingen Documents"
+msgstr "Pingen Documents"
+
+#. module: pingen_document
+#: view:ir.attachment:0
+msgid "Pingen.com"
+msgstr "Pingen.com"
+
diff --git a/pingen_document/i18n/es.po b/pingen_document/i18n/es.po
new file mode 100644
index 0000000..fc4bf6a
--- /dev/null
+++ b/pingen_document/i18n/es.po
@@ -0,0 +1,32 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+# * pingen_document
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 6.1\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2012-11-26 10:54+0000\n"
+"PO-Revision-Date: 2012-11-26 10:54+0000\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: pingen_document
+#: view:ir.attachment:0
+msgid "Notes"
+msgstr "Notes"
+
+#. module: pingen_document
+#: model:ir.ui.menu,name:pingen_document.menu_pingen_document_document
+msgid "Pingen Documents"
+msgstr "Pingen Documents"
+
+#. module: pingen_document
+#: view:ir.attachment:0
+msgid "Pingen.com"
+msgstr "Pingen.com"
+
diff --git a/pingen_document/i18n/fr.po b/pingen_document/i18n/fr.po
new file mode 100644
index 0000000..eeac4ef
--- /dev/null
+++ b/pingen_document/i18n/fr.po
@@ -0,0 +1,32 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+# * pingen_document
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 6.1\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2012-11-26 10:56+0000\n"
+"PO-Revision-Date: 2012-11-26 10:56+0000\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: pingen_document
+#: view:ir.attachment:0
+msgid "Notes"
+msgstr "Notes"
+
+#. module: pingen_document
+#: model:ir.ui.menu,name:pingen_document.menu_pingen_document_document
+msgid "Pingen Documents"
+msgstr "Documents Pingen"
+
+#. module: pingen_document
+#: view:ir.attachment:0
+msgid "Pingen.com"
+msgstr "Pingen.com"
+
diff --git a/pingen_document/i18n/pingen_document.pot b/pingen_document/i18n/pingen_document.pot
new file mode 100644
index 0000000..616770d
--- /dev/null
+++ b/pingen_document/i18n/pingen_document.pot
@@ -0,0 +1,32 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+# * pingen_document
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 6.1\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2012-11-26 10:56+0000\n"
+"PO-Revision-Date: 2012-11-26 10:56+0000\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: pingen_document
+#: view:ir.attachment:0
+msgid "Notes"
+msgstr ""
+
+#. module: pingen_document
+#: model:ir.ui.menu,name:pingen_document.menu_pingen_document_document
+msgid "Pingen Documents"
+msgstr ""
+
+#. module: pingen_document
+#: view:ir.attachment:0
+msgid "Pingen.com"
+msgstr ""
+
diff --git a/pingen_document/pingen_document_view.xml b/pingen_document/pingen_document_view.xml
new file mode 100644
index 0000000..1424ef9
--- /dev/null
+++ b/pingen_document/pingen_document_view.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+ ir.attachment.document.pingen.view
+ ir.attachment
+ form
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+