[IMP] pingen: pre-commit stuff

This commit is contained in:
Anna Janiszewska
2023-01-26 12:55:11 +01:00
parent eb4a9f3b5a
commit 0529340243
17 changed files with 471 additions and 385 deletions

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Author: Guewen Baconnier # Author: Guewen Baconnier
# Copyright 2012-2017 Camptocamp SA # Copyright 2012-2017 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

View File

@@ -1,30 +1,29 @@
# -*- coding: utf-8 -*-
# Author: Guewen Baconnier # Author: Guewen Baconnier
# Copyright 2012-2017 Camptocamp SA # Copyright 2012-2017 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
{ {
'name': 'pingen.com integration', "name": "pingen.com integration",
'version': '10.0.1.0.0', "version": "10.0.1.0.0",
'author': "Camptocamp,Odoo Community Association (OCA)", "author": "Camptocamp,Odoo Community Association (OCA)",
'maintainer': 'Camptocamp', "maintainer": "Camptocamp",
'license': 'AGPL-3', "license": "AGPL-3",
'category': 'Reporting', "category": "Reporting",
'complexity': 'easy', "complexity": "easy",
'depends': ['base_setup'], "depends": ["base_setup"],
'external_dependencies': { "external_dependencies": {
'python': ['requests'], "python": ["requests"],
}, },
'website': 'http://www.camptocamp.com', "website": "https://github.com/OCA/report-print-send",
'data': [ "data": [
'views/ir_attachment_view.xml', "views/ir_attachment_view.xml",
'views/pingen_document_view.xml', "views/pingen_document_view.xml",
'data/pingen_data.xml', "data/pingen_data.xml",
'views/base_config_settings.xml', "views/base_config_settings.xml",
'security/ir.model.access.csv', "security/ir.model.access.csv",
], ],
'tests': [], "tests": [],
'installable': True, "installable": True,
'auto_install': False, "auto_install": False,
'application': True, "application": True,
} }

View File

@@ -1,15 +1,15 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1"> <odoo noupdate="1">
<record forcecreate="True" id="ir_cron_push_pingen" model="ir.cron"> <record forcecreate="True" id="ir_cron_push_pingen" model="ir.cron">
<field name="name">Run Pingen Document Push</field> <field name="name">Run Pingen Document Push</field>
<field eval="True" name="active"/> <field eval="True" name="active" />
<field name="user_id" ref="base.user_root"/> <field name="user_id" ref="base.user_root" />
<field name="interval_number">1</field> <field name="interval_number">1</field>
<field name="interval_type">hours</field> <field name="interval_type">hours</field>
<field name="numbercall">-1</field> <field name="numbercall">-1</field>
<field eval="False" name="doall"/> <field eval="False" name="doall" />
<field name="model">pingen.document</field> <field name="model">pingen.document</field>
<field name="function">_push_and_send_to_pingen_cron</field> <field name="function">_push_and_send_to_pingen_cron</field>
<field name="args">()</field> <field name="args">()</field>
@@ -17,12 +17,12 @@
<record forcecreate="True" id="ir_cron_update_pingen" model="ir.cron"> <record forcecreate="True" id="ir_cron_update_pingen" model="ir.cron">
<field name="name">Run Pingen Document Update</field> <field name="name">Run Pingen Document Update</field>
<field eval="True" name="active"/> <field eval="True" name="active" />
<field name="user_id" ref="base.user_root"/> <field name="user_id" ref="base.user_root" />
<field name="interval_number">1</field> <field name="interval_number">1</field>
<field name="interval_type">days</field> <field name="interval_type">days</field>
<field name="numbercall">-1</field> <field name="numbercall">-1</field>
<field eval="False" name="doall"/> <field eval="False" name="doall" />
<field name="model">pingen.document</field> <field name="model">pingen.document</field>
<field name="function">_update_post_infos_cron</field> <field name="function">_update_post_infos_cron</field>
<field name="args">()</field> <field name="args">()</field>

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright 2018 Camptocamp SA # Copyright 2018 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import ir_attachment from . import ir_attachment

View File

@@ -1,11 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright 2018 Camptocamp SA # Copyright 2018 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models from odoo import fields, models
class BaseConfigSettings(models.TransientModel): class BaseConfigSettings(models.TransientModel):
_inherit = 'base.config.settings' _inherit = "base.config.settings"
pingen_token = fields.Char(related='company_id.pingen_token') pingen_token = fields.Char(related="company_id.pingen_token")
pingen_staging = fields.Boolean(related='company_id.pingen_staging') pingen_staging = fields.Boolean(related="company_id.pingen_staging")

View File

@@ -1,40 +1,43 @@
# -*- coding: utf-8 -*-
# Author: Guewen Baconnier # Author: Guewen Baconnier
# Copyright 2012-2017 Camptocamp SA # Copyright 2012-2017 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import requests
import base64 import base64
from odoo import models, fields, _, api import requests
from odoo import _, api, fields, models
from odoo.exceptions import UserError from odoo.exceptions import UserError
class IrAttachment(models.Model): class IrAttachment(models.Model):
_inherit = 'ir.attachment' _inherit = "ir.attachment"
send_to_pingen = fields.Boolean("Send to Pingen.com") send_to_pingen = fields.Boolean("Send to Pingen.com")
pingen_document_ids = fields.One2many( pingen_document_ids = fields.One2many(
'pingen.document', 'attachment_id', "pingen.document", "attachment_id", string="Pingen Document", readonly=True
string='Pingen Document', readonly=True) )
pingen_send = fields.Boolean( pingen_send = fields.Boolean(
'Send', help="Defines if a document is merely uploaded or also sent", "Send",
default=True) help="Defines if a document is merely uploaded or also sent",
default=True,
)
pingen_speed = fields.Selection( pingen_speed = fields.Selection(
[('1', 'Priority'), ('2', 'Economy')], [("1", "Priority"), ("2", "Economy")],
'Speed', default='2', "Speed",
help='Defines the sending speed if the document is automatically sent') default="2",
pingen_color = fields.Selection([('0', 'B/W'), ('1', 'Color')], help="Defines the sending speed if the document is automatically sent",
'Type of print', )
default='0') pingen_color = fields.Selection(
[("0", "B/W"), ("1", "Color")], "Type of print", default="0"
)
def _prepare_pingen_document_vals(self): def _prepare_pingen_document_vals(self):
return {'attachment_id': self.id, return {"attachment_id": self.id, "config": "created from attachment"}
'config': 'created from attachment'}
def _handle_pingen_document(self): def _handle_pingen_document(self):
""" Reponsible of the related ``pingen.document`` """Reponsible of the related ``pingen.document``
when the ``send_to_pingen`` when the ``send_to_pingen``
field is modified. field is modified.
Only one pingen document can be created per attachment. Only one pingen document can be created per attachment.
@@ -48,52 +51,52 @@ class IrAttachment(models.Model):
* If it has already been pushed to pingen.com, raises * If it has already been pushed to pingen.com, raises
an `osv.except_osv` exception an `osv.except_osv` exception
""" """
pingen_document_obj = self.env['pingen.document'] pingen_document_obj = self.env["pingen.document"]
document = self.pingen_document_ids[0] if \ document = self.pingen_document_ids[0] if self.pingen_document_ids else None
self.pingen_document_ids else None
if self.send_to_pingen: if self.send_to_pingen:
if document: if document:
document.write({'state': 'pending'}) document.write({"state": "pending"})
else: else:
pingen_document_obj.create( pingen_document_obj.create(self._prepare_pingen_document_vals())
self._prepare_pingen_document_vals())
else: else:
if document: if document:
if document.state == 'pushed': if document.state == "pushed":
raise UserError( raise UserError(
_('Error. The attachment %s is ' _(
'already pushed to pingen.com.') % self.name) "Error. The attachment %s is "
document.write({'state': 'canceled'}) "already pushed to pingen.com."
)
% self.name
)
document.write({"state": "canceled"})
return return
@api.model @api.model
def create(self, vals): def create(self, vals):
attachment = super(IrAttachment, self).create(vals) attachment = super(IrAttachment, self).create(vals)
if 'send_to_pingen' in vals: if "send_to_pingen" in vals:
attachment._handle_pingen_document() attachment._handle_pingen_document()
return attachment return attachment
@api.multi @api.multi
def write(self, vals): def write(self, vals):
res = super(IrAttachment, self).write(vals) res = super(IrAttachment, self).write(vals)
if 'send_to_pingen' in vals: if "send_to_pingen" in vals:
for attachment in self: for attachment in self:
attachment._handle_pingen_document() attachment._handle_pingen_document()
return res return res
def _decoded_content(self): def _decoded_content(self):
""" Returns the decoded content of an attachment (stored or url) """Returns the decoded content of an attachment (stored or url)
Returns None if the type is 'url' and the url is not reachable. Returns None if the type is 'url' and the url is not reachable.
""" """
decoded_document = None decoded_document = None
if self.type == 'binary': if self.type == "binary":
decoded_document = base64.b64decode(self.datas) decoded_document = base64.b64decode(self.datas)
elif self.type == 'url': elif self.type == "url":
response = requests.get(self.url) response = requests.get(self.url)
if response.ok: if response.ok:
decoded_document = requests.content decoded_document = requests.content
else: else:
raise UserError( raise UserError(_("The type of attachment %s is not handled") % self.type)
_('The type of attachment %s is not handled')
% self.type)
return decoded_document return decoded_document

View File

@@ -1,35 +1,34 @@
# -*- coding: utf-8 -*-
# Author: Guewen Baconnier # Author: Guewen Baconnier
# Copyright 2012-2017 Camptocamp SA # Copyright 2012-2017 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import requests
import logging
import urlparse
import json import json
import pytz import logging
from datetime import datetime from datetime import datetime
import pytz
import requests
import urlparse
from requests.packages.urllib3.filepost import encode_multipart_formdata from requests.packages.urllib3.filepost import encode_multipart_formdata
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
POST_SENDING_STATUS = { POST_SENDING_STATUS = {
100: 'Ready/Pending', 100: "Ready/Pending",
101: 'Processing', 101: "Processing",
102: 'Waiting for confirmation', 102: "Waiting for confirmation",
1: 'Sent', 1: "Sent",
300: 'Some error occured and object wasn\'t sent', 300: "Some error occured and object wasn't sent",
400: 'Sending cancelled', 400: "Sending cancelled",
} }
DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S' # this is the format used by pingen API DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S" # this is the format used by pingen API
TZ = pytz.timezone('Europe/Zurich') # this is the timezone of the pingen API TZ = pytz.timezone("Europe/Zurich") # this is the timezone of the pingen API
def pingen_datetime_to_utc(dt): def pingen_datetime_to_utc(dt):
""" Convert a date/time used by pingen.com to UTC timezone """Convert a date/time used by pingen.com to UTC timezone
:param dt: pingen date/time as string (as received from the API) :param dt: pingen date/time as string (as received from the API)
to convert to UTC to convert to UTC
@@ -51,7 +50,7 @@ class APIError(PingenException):
class Pingen(object): class Pingen(object):
""" Interface to the pingen.com API """ """Interface to the pingen.com API"""
def __init__(self, token, staging=True): def __init__(self, token, staging=True):
self._token = token self._token = token
@@ -62,16 +61,16 @@ class Pingen(object):
@property @property
def url(self): def url(self):
if self.staging: if self.staging:
return 'https://stage-api.pingen.com' return "https://stage-api.pingen.com"
return 'https://api.pingen.com' return "https://api.pingen.com"
@property @property
def session(self): def session(self):
""" Build a requests session """ """Build a requests session"""
if self._session is not None: if self._session is not None:
return self._session return self._session
self._session = requests.Session() self._session = requests.Session()
self._session.params = {'token': self._token} self._session.params = {"token": self._token}
self._session.verify = not self.staging self._session.verify = not self.staging
return self._session return self._session
@@ -82,12 +81,12 @@ class Pingen(object):
self.close() self.close()
def close(self): def close(self):
"""Dispose of any internal state. """ """Dispose of any internal state."""
if self._session: if self._session:
self._session.close() self._session.close()
def _send(self, method, endpoint, **kwargs): def _send(self, method, endpoint, **kwargs):
""" Send a request to the pingen API using requests """Send a request to the pingen API using requests
Add necessary boilerplate to call pingen.com API Add necessary boilerplate to call pingen.com API
(authentication, configuration, ...) (authentication, configuration, ...)
@@ -99,29 +98,25 @@ class Pingen(object):
p_url = urlparse.urljoin(self.url, endpoint) p_url = urlparse.urljoin(self.url, endpoint)
if endpoint == 'document/get': if endpoint == "document/get":
complete_url = '{}{}{}{}{}'.format(p_url, complete_url = "{}{}{}{}{}".format(
'/id/', p_url, "/id/", kwargs["params"]["id"], "/token/", self._token
kwargs['params']['id'], )
'/token/',
self._token)
else: else:
complete_url = '{}{}{}'.format(p_url, complete_url = "{}{}{}".format(p_url, "/token/", self._token)
'/token/',
self._token)
response = method(complete_url, **kwargs) response = method(complete_url, **kwargs)
if response.json()['error']: if response.json()["error"]:
raise APIError( raise APIError(
"%s: %s" % (response.json()['errorcode'], "%s: %s"
response.json()['errormessage'])) % (response.json()["errorcode"], response.json()["errormessage"])
)
return response return response
def push_document(self, filename, filestream, def push_document(self, filename, filestream, send=None, speed=None, color=None):
send=None, speed=None, color=None): """Upload a document to pingen.com and eventually ask to send it
""" Upload a document to pingen.com and eventually ask to send it
:param str filename: name of the file to push :param str filename: name of the file to push
:param StringIO filestream: file to push :param StringIO filestream: file to push
@@ -135,10 +130,10 @@ class Pingen(object):
3. dict of the created item on pingen (details) 3. dict of the created item on pingen (details)
""" """
data = { data = {
'send': send, "send": send,
'speed': speed, "speed": speed,
'color': color, "color": color,
} }
# we cannot use the `files` param alongside # we cannot use the `files` param alongside
# with the `datas`param when data is a # with the `datas`param when data is a
@@ -146,30 +141,31 @@ class Pingen(object):
# the entire body and send it to `data` # the entire body and send it to `data`
# https://github.com/kennethreitz/requests/issues/950 # https://github.com/kennethreitz/requests/issues/950
formdata = { formdata = {
'file': (filename, filestream.read()), "file": (filename, filestream.read()),
'data': json.dumps(data), "data": json.dumps(data),
} }
multipart, content_type = encode_multipart_formdata(formdata) multipart, content_type = encode_multipart_formdata(formdata)
response = self._send( response = self._send(
self.session.post, self.session.post,
'document/upload', "document/upload",
headers={'Content-Type': content_type}, headers={"Content-Type": content_type},
data=multipart) data=multipart,
)
rjson = response.json() rjson = response.json()
document_id = rjson['id'] document_id = rjson["id"]
if rjson.get('send'): if rjson.get("send"):
# confusing name but send_id is the posted id # confusing name but send_id is the posted id
posted_id = rjson['send'][0]['send_id'] posted_id = rjson["send"][0]["send_id"]
item = rjson['item'] item = rjson["item"]
return document_id, posted_id, item return document_id, posted_id, item
def send_document(self, document_id, speed=None, color=None): def send_document(self, document_id, speed=None, color=None):
""" Send a uploaded document to pingen.com """Send a uploaded document to pingen.com
:param int document_id: id of the document to send :param int document_id: id of the document to send
:param int/str speed: sending speed of the document if it is send :param int/str speed: sending speed of the document if it is send
@@ -178,34 +174,32 @@ class Pingen(object):
:return: id of the post on pingen.com :return: id of the post on pingen.com
""" """
data = { data = {
'speed': speed, "speed": speed,
'color': color, "color": color,
} }
response = self._send( response = self._send(
self.session.post, self.session.post,
'document/send', "document/send",
params={'id': document_id}, params={"id": document_id},
data={'data': json.dumps(data)}) data={"data": json.dumps(data)},
)
return response.json()['id'] return response.json()["id"]
def post_infos(self, post_id): def post_infos(self, post_id):
""" Return the information of a post """Return the information of a post
:param int post_id: id of the document to send :param int post_id: id of the document to send
:return: dict of infos of the post :return: dict of infos of the post
""" """
response = self._send( response = self._send(self.session.get, "document/get", params={"id": post_id})
self.session.get,
'document/get',
params={'id': post_id})
return response.json()['item'] return response.json()["item"]
@staticmethod @staticmethod
def is_posted(post_infos): def is_posted(post_infos):
""" return True if the post has been sent """return True if the post has been sent
:param dict post_infos: post infos returned by `post_infos` :param dict post_infos: post infos returned by `post_infos`
""" """
return post_infos['status'] == 1 return post_infos["status"] == 1

View File

@@ -1,77 +1,85 @@
# -*- coding: utf-8 -*-
# Author: Guewen Baconnier # Author: Guewen Baconnier
# Copyright 2012-2017 Camptocamp SA # Copyright 2012-2017 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import logging import logging
from requests.exceptions import ConnectionError
from cStringIO import StringIO from cStringIO import StringIO
from requests.exceptions import ConnectionError
import odoo import odoo
from odoo import models, fields, _ from odoo import _, fields, models
from odoo.exceptions import UserError from odoo.exceptions import UserError
from .pingen import APIError, pingen_datetime_to_utc, POST_SENDING_STATUS
from .pingen import POST_SENDING_STATUS, APIError, pingen_datetime_to_utc
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
class PingenDocument(models.Model): class PingenDocument(models.Model):
""" A pingen document is the state of the synchronization of """A pingen document is the state of the synchronization of
an attachment with pingen.com an attachment with pingen.com
It stores the configuration and the current state of the synchronization. It stores the configuration and the current state of the synchronization.
It also serves as a queue of documents to push to pingen.com It also serves as a queue of documents to push to pingen.com
""" """
_name = 'pingen.document' _name = "pingen.document"
_inherits = {'ir.attachment': 'attachment_id'} _inherits = {"ir.attachment": "attachment_id"}
attachment_id = fields.Many2one( attachment_id = fields.Many2one(
'ir.attachment', 'Document', "ir.attachment", "Document", required=True, readonly=True, ondelete="cascade"
required=True, readonly=True, )
ondelete='cascade')
state = fields.Selection( state = fields.Selection(
[('pending', 'Pending'), [
('pushed', 'Pushed'), ("pending", "Pending"),
('sendcenter', 'In Sendcenter'), ("pushed", "Pushed"),
('sent', 'Sent'), ("sendcenter", "In Sendcenter"),
('error', 'Connection Error'), ("sent", "Sent"),
('pingen_error', 'Pingen Error'), ("error", "Connection Error"),
('canceled', 'Canceled')], ("pingen_error", "Pingen Error"),
string='State', readonly=True, ("canceled", "Canceled"),
required=True, default='pending') ],
push_date = fields.Datetime('Push Date', readonly=True) string="State",
readonly=True,
required=True,
default="pending",
)
push_date = fields.Datetime("Push Date", readonly=True)
# for `error` and `pingen_error` states when we push # for `error` and `pingen_error` states when we push
last_error_message = fields.Text('Error Message', readonly=True) last_error_message = fields.Text("Error Message", readonly=True)
# pingen IDs # pingen IDs
pingen_id = fields.Integer( pingen_id = fields.Integer(
'Pingen ID', readonly=True, "Pingen ID", readonly=True, help="ID of the document in the Pingen Documents"
help="ID of the document in the Pingen Documents") )
post_id = fields.Integer( post_id = fields.Integer(
'Pingen Post ID', readonly=True, "Pingen Post ID",
help="ID of the document in the Pingen Sendcenter") readonly=True,
help="ID of the document in the Pingen Sendcenter",
)
# sendcenter infos # sendcenter infos
post_status = fields.Char('Post Status', size=128, readonly=True) post_status = fields.Char("Post Status", size=128, readonly=True)
parsed_address = fields.Text('Parsed Address', readonly=True) parsed_address = fields.Text("Parsed Address", readonly=True)
cost = fields.Float('Cost', readonly=True) cost = fields.Float("Cost", readonly=True)
currency_id = fields.Many2one('res.currency', 'Currency', readonly=True) currency_id = fields.Many2one("res.currency", "Currency", readonly=True)
country_id = fields.Many2one('res.country', 'Country', readonly=True) country_id = fields.Many2one("res.country", "Country", readonly=True)
send_date = fields.Datetime('Date of sending', readonly=True) send_date = fields.Datetime("Date of sending", readonly=True)
pages = fields.Integer('Pages', readonly=True) pages = fields.Integer("Pages", readonly=True)
_sql_constraints = [ _sql_constraints = [
('pingen_document_attachment_uniq', (
'unique (attachment_id)', "pingen_document_attachment_uniq",
'Only one Pingen document is allowed per attachment.'), "unique (attachment_id)",
"Only one Pingen document is allowed per attachment.",
),
] ]
def _get_pingen_session(self): def _get_pingen_session(self):
""" Returns a pingen session for a user """ """Returns a pingen session for a user"""
return self.company_id._pingen() return self.company_id._pingen()
def _push_to_pingen(self, pingen=None): def _push_to_pingen(self, pingen=None):
""" Push a document to pingen.com """Push a document to pingen.com
:param Pingen pingen: optional pingen object to reuse session :param Pingen pingen: optional pingen object to reuse session
""" """
decoded_document = self.attachment_id._decoded_content() decoded_document = self.attachment_id._decoded_content()
@@ -83,36 +91,41 @@ class PingenDocument(models.Model):
StringIO(decoded_document), StringIO(decoded_document),
self.pingen_send, self.pingen_send,
self.pingen_speed, self.pingen_speed,
self.pingen_color) self.pingen_color,
)
except ConnectionError: except ConnectionError:
_logger.exception( _logger.exception(
'Connection Error when pushing Pingen Document %s to %s.' % "Connection Error when pushing Pingen Document %s to %s."
(self.id, pingen.url)) % (self.id, pingen.url)
)
raise raise
except APIError: except APIError:
_logger.error( _logger.error(
'API Error when pushing Pingen Document %s to %s.' % "API Error when pushing Pingen Document %s to %s."
(self.id, pingen.url)) % (self.id, pingen.url)
)
raise raise
error = False error = False
state = 'pushed' state = "pushed"
if post_id: if post_id:
state = 'sendcenter' state = "sendcenter"
elif infos['requirement_failure']: elif infos["requirement_failure"]:
state = 'pingen_error' state = "pingen_error"
error = _('The document does not meet the Pingen requirements.') error = _("The document does not meet the Pingen requirements.")
push_date = pingen_datetime_to_utc(infos['date']) push_date = pingen_datetime_to_utc(infos["date"])
self.write( self.write(
{'last_error_message': error, {
'state': state, "last_error_message": error,
'push_date': fields.Datetime.to_string(push_date), "state": state,
'pingen_id': doc_id, "push_date": fields.Datetime.to_string(push_date),
'post_id': post_id},) "pingen_id": doc_id,
_logger.info( "post_id": post_id,
'Pingen Document %s: pushed to %s' % (self.id, pingen.url)) },
)
_logger.info("Pingen Document %s: pushed to %s" % (self.id, pingen.url))
def push_to_pingen(self): def push_to_pingen(self):
""" Push a document to pingen.com """Push a document to pingen.com
Convert errors to osv.except_osv to be handled by the client. Convert errors to osv.except_osv to be handled by the client.
Wrapper method for multiple ids (when triggered from button for Wrapper method for multiple ids (when triggered from button for
instance) for public interface. instance) for public interface.
@@ -123,117 +136,126 @@ class PingenDocument(models.Model):
try: try:
session = self._get_pingen_session() session = self._get_pingen_session()
self._push_to_pingen(pingen=session) self._push_to_pingen(pingen=session)
except ConnectionError as e: except ConnectionError:
state = 'error' state = "error"
error_msg = _('Connection Error when asking for ' error_msg = (
'sending the document %s to Pingen') % self.name _(
"Connection Error when asking for "
"sending the document %s to Pingen"
)
% self.name
)
except APIError as e: except APIError as e:
state = 'pingen_error' state = "pingen_error"
error_msg = _('Error when asking Pingen to send the document %s: ' error_msg = _(
'\n%s') % (self.name, e) "Error when asking Pingen to send the document %s: " "\n%s"
except Exception as e: ) % (self.name, e)
except Exception:
_logger.exception( _logger.exception(
'Unexpected Error when updating the status of pingen.document ' "Unexpected Error when updating the status of pingen.document "
'%s: ' % self.id) "%s: " % self.id
error_msg = _('Unexpected Error when updating the ' )
'status of Document %s') % self.name error_msg = (
_("Unexpected Error when updating the " "status of Document %s")
% self.name
)
finally: finally:
if error_msg: if error_msg:
vals = {'last_error_message': error_msg} vals = {"last_error_message": error_msg}
if state: if state:
vals.update({'state': state}) vals.update({"state": state})
with odoo.registry(self.env.cr.dbname).cursor() as new_cr: with odoo.registry(self.env.cr.dbname).cursor() as new_cr:
new_env = odoo.api.Environment( new_env = odoo.api.Environment(
new_cr, self.env.uid, self.env.context) new_cr, self.env.uid, self.env.context
)
self.with_env(new_env).write(vals) self.with_env(new_env).write(vals)
raise UserError(error_msg) raise UserError(error_msg)
return True return True
def _push_and_send_to_pingen_cron(self): def _push_and_send_to_pingen_cron(self):
""" Push a document to pingen.com """Push a document to pingen.com
Intended to be used in a cron. Intended to be used in a cron.
Commit after each record Commit after each record
Instead of raising, store the error in the pingen.document Instead of raising, store the error in the pingen.document
""" """
with odoo.api.Environment.manage(): with odoo.api.Environment.manage():
with odoo.registry(self.env.cr.dbname).cursor() as new_cr: with odoo.registry(self.env.cr.dbname).cursor() as new_cr:
new_env = odoo.api.Environment( new_env = odoo.api.Environment(new_cr, self.env.uid, self.env.context)
new_cr, self.env.uid, self.env.context)
# Instead of raising, store the error in the pingen.document # Instead of raising, store the error in the pingen.document
self = self.with_env(new_env) self = self.with_env(new_env)
not_sent_docs = self.search([('state', '!=', 'sent')]) not_sent_docs = self.search([("state", "!=", "sent")])
for document in not_sent_docs: for document in not_sent_docs:
session = document._get_pingen_session() session = document._get_pingen_session()
if document.state == 'error': if document.state == "error":
document._resolve_error() document._resolve_error()
document.refresh() document.refresh()
try: try:
if document.state == 'pending': if document.state == "pending":
document._push_to_pingen(pingen=session) document._push_to_pingen(pingen=session)
elif document.state == 'pushed': elif document.state == "pushed":
document._ask_pingen_send(pingen=session) document._ask_pingen_send(pingen=session)
except ConnectionError as e: except ConnectionError as e:
document.write({'last_error_message': e, document.write({"last_error_message": e, "state": "error"})
'state': 'error'})
except APIError as e: except APIError as e:
document.write({'last_error_message': e, document.write(
'state': 'pingen_error'}) {"last_error_message": e, "state": "pingen_error"}
except BaseException as e: )
_logger.error('Unexpected error in pingen cron') except BaseException:
_logger.error("Unexpected error in pingen cron")
return True return True
def _resolve_error(self): def _resolve_error(self):
""" A document as resolved, put in the correct state """ """A document as resolved, put in the correct state"""
if self.post_id: if self.post_id:
state = 'sendcenter' state = "sendcenter"
elif self.pingen_id: elif self.pingen_id:
state = 'pushed' state = "pushed"
else: else:
state = 'pending' state = "pending"
self.write({'state': state}) self.write({"state": state})
def resolve_error(self): def resolve_error(self):
""" A document as resolved, put in the correct state """ """A document as resolved, put in the correct state"""
for document in self: for document in self:
document._resolve_error() document._resolve_error()
return True return True
def _ask_pingen_send(self, pingen): def _ask_pingen_send(self, pingen):
""" For a document already pushed to pingen, ask to send it. """For a document already pushed to pingen, ask to send it.
:param Pingen pingen: pingen object to reuse :param Pingen pingen: pingen object to reuse
""" """
# sending has been explicitely asked so we change the option # sending has been explicitely asked so we change the option
# for consistency # for consistency
if not self.pingen_send: if not self.pingen_send:
self.write({'pingen_send': True}) self.write({"pingen_send": True})
try: try:
post_id = pingen.send_document( post_id = pingen.send_document(
self.pingen_id, self.pingen_id, self.pingen_speed, self.pingen_color
self.pingen_speed, )
self.pingen_color)
except ConnectionError: except ConnectionError:
_logger.exception( _logger.exception(
'Connection Error when asking for sending Pingen Document %s ' "Connection Error when asking for sending Pingen Document %s "
'to %s.' % (self.id, pingen.url)) "to %s." % (self.id, pingen.url)
)
raise raise
except APIError: except APIError:
_logger.exception( _logger.exception(
'API Error when asking for sending Pingen Document %s to %s.' % "API Error when asking for sending Pingen Document %s to %s."
(self.id, pingen.url)) % (self.id, pingen.url)
)
raise raise
self.write( self.write(
{'last_error_message': False, {"last_error_message": False, "state": "sendcenter", "post_id": post_id}
'state': 'sendcenter', )
'post_id': post_id})
_logger.info( _logger.info(
'Pingen Document %s: asked for sending to %s' % ( "Pingen Document %s: asked for sending to %s" % (self.id, pingen.url)
self.id, pingen.url)) )
return True return True
def ask_pingen_send(self): def ask_pingen_send(self):
""" For a document already pushed to pingen, ask to send it. """For a document already pushed to pingen, ask to send it.
Wrapper method for multiple ids (when triggered from button for Wrapper method for multiple ids (when triggered from button for
instance) for public interface. instance) for public interface.
""" """
@@ -241,27 +263,34 @@ class PingenDocument(models.Model):
try: try:
session = self._get_pingen_session() session = self._get_pingen_session()
self._ask_pingen_send(pingen=session) self._ask_pingen_send(pingen=session)
except ConnectionError as e: except ConnectionError:
raise UserError( raise UserError(
_('Connection Error when asking for ' _(
'sending the document %s to Pingen') % self.name) "Connection Error when asking for "
"sending the document %s to Pingen"
)
% self.name
)
except APIError as e: except APIError as e:
raise UserError( raise UserError(
_('Error when asking Pingen to send the document %s: ' _("Error when asking Pingen to send the document %s: " "\n%s")
'\n%s') % (self.name, e)) % (self.name, e)
)
except BaseException as e: except BaseException:
_logger.exception( _logger.exception(
'Unexpected Error when updating the status ' "Unexpected Error when updating the status "
'of pingen.document %s: ' % self.id) "of pingen.document %s: " % self.id
)
raise UserError( raise UserError(
_('Unexpected Error when updating the status ' _("Unexpected Error when updating the status " "of Document %s")
'of Document %s') % self.name) % self.name
)
return True return True
def _update_post_infos(self, pingen): def _update_post_infos(self, pingen):
""" Update the informations from """Update the informations from
pingen of a document in the Sendcenter pingen of a document in the Sendcenter
:param Pingen pingen: pingen object to reuse :param Pingen pingen: pingen object to reuse
""" """
@@ -271,44 +300,43 @@ class PingenDocument(models.Model):
post_infos = pingen.post_infos(self.pingen_id) post_infos = pingen.post_infos(self.pingen_id)
except ConnectionError: except ConnectionError:
_logger.exception( _logger.exception(
'Connection Error when asking for ' "Connection Error when asking for "
'sending Pingen Document %s to %s.' % "sending Pingen Document %s to %s." % (self.id, pingen.url)
(self.id, pingen.url)) )
raise raise
except APIError: except APIError:
_logger.exception( _logger.exception(
'API Error when asking for sending Pingen Document %s to %s.' % "API Error when asking for sending Pingen Document %s to %s."
(self.id, pingen.url)) % (self.id, pingen.url)
)
raise raise
country = self.env['res.country'].search( country = self.env["res.country"].search([("code", "=", post_infos["country"])])
[('code', '=', post_infos['country'])]) send_date = pingen_datetime_to_utc(post_infos["date"])
send_date = pingen_datetime_to_utc(post_infos['date'])
vals = { vals = {
'post_status': POST_SENDING_STATUS[post_infos['status']], "post_status": POST_SENDING_STATUS[post_infos["status"]],
'parsed_address': post_infos['address'], "parsed_address": post_infos["address"],
'country_id': country.id, "country_id": country.id,
'send_date': fields.Datetime.to_string(send_date), "send_date": fields.Datetime.to_string(send_date),
'pages': post_infos['pages'], "pages": post_infos["pages"],
'last_error_message': False, "last_error_message": False,
} }
if pingen.is_posted(post_infos): if pingen.is_posted(post_infos):
vals['state'] = 'sent' vals["state"] = "sent"
self.write(vals) self.write(vals)
_logger.info('Pingen Document %s: status updated' % self.id) _logger.info("Pingen Document %s: status updated" % self.id)
def _update_post_infos_cron(self): def _update_post_infos_cron(self):
""" Update the informations from pingen of a """Update the informations from pingen of a
document in the Sendcenter document in the Sendcenter
Intended to be used in a cron. Intended to be used in a cron.
Commit after each record Commit after each record
Do not raise errors, only skip the update of the record.""" Do not raise errors, only skip the update of the record."""
with odoo.api.Environment.manage(): with odoo.api.Environment.manage():
with odoo.registry(self.env.cr.dbname).cursor() as new_cr: with odoo.registry(self.env.cr.dbname).cursor() as new_cr:
new_env = odoo.api.Environment( new_env = odoo.api.Environment(new_cr, self.env.uid, self.env.context)
new_cr, self.env.uid, self.env.context)
# Instead of raising, store the error in the pingen.document # Instead of raising, store the error in the pingen.document
self = self.with_env(new_env) self = self.with_env(new_env)
pushed_docs = self.search([('state', '!=', 'sent')]) pushed_docs = self.search([("state", "!=", "sent")])
for document in pushed_docs: for document in pushed_docs:
session = document._get_pingen_session() session = document._get_pingen_session()
try: try:
@@ -319,12 +347,12 @@ class PingenDocument(models.Model):
# logged by _update_post_infos # logged by _update_post_infos
pass pass
except BaseException as e: except BaseException as e:
_logger.error('Unexcepted error in pingen cron: %', e) _logger.error("Unexcepted error in pingen cron: %", e)
raise raise
return True return True
def update_post_infos(self): def update_post_infos(self):
""" Update the informations from pingen of a document in the Sendcenter """Update the informations from pingen of a document in the Sendcenter
Wrapper method for multiple ids (when triggered from button for Wrapper method for multiple ids (when triggered from button for
instance) for public interface. instance) for public interface.
""" """
@@ -332,19 +360,26 @@ class PingenDocument(models.Model):
try: try:
session = self._get_pingen_session() session = self._get_pingen_session()
self._update_post_infos(pingen=session) self._update_post_infos(pingen=session)
except ConnectionError as e: except ConnectionError:
raise UserError( raise UserError(
_('Connection Error when updating the status ' _(
'of Document %s from Pingen') % self.name) "Connection Error when updating the status "
"of Document %s from Pingen"
)
% self.name
)
except APIError as e: except APIError as e:
raise UserError( raise UserError(
_('Error when updating the status of Document %s from ' _("Error when updating the status of Document %s from " "Pingen: \n%s")
'Pingen: \n%s') % (self.name, e)) % (self.name, e)
except BaseException as e: )
except BaseException:
_logger.exception( _logger.exception(
'Unexpected Error when updating the status ' "Unexpected Error when updating the status "
'of pingen.document %s: ' % self.id) "of pingen.document %s: " % self.id
)
raise UserError( raise UserError(
_('Unexpected Error when updating the status ' _("Unexpected Error when updating the status " "of Document %s")
'of Document %s') % self.name) % self.name
)
return True return True

View File

@@ -1,20 +1,20 @@
# -*- coding: utf-8 -*-
# Author: Guewen Baconnier # Author: Guewen Baconnier
# Copyright 2012-2017 Camptocamp SA # Copyright 2012-2017 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import models, fields from odoo import fields, models
from .pingen import Pingen from .pingen import Pingen
class ResCompany(models.Model): class ResCompany(models.Model):
_inherit = 'res.company' _inherit = "res.company"
pingen_token = fields.Char('Pingen Token', size=32) pingen_token = fields.Char("Pingen Token", size=32)
pingen_staging = fields.Boolean('Pingen Staging') pingen_staging = fields.Boolean("Pingen Staging")
def _pingen(self): def _pingen(self):
""" Return a Pingen instance to work on """ """Return a Pingen instance to work on"""
self.ensure_one() self.ensure_one()
return Pingen(self.pingen_token, staging=self.pingen_staging) return Pingen(self.pingen_token, staging=self.pingen_staging)

View File

@@ -1,14 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<record id="base_config_settings_inherit" model="ir.ui.view"> <record id="base_config_settings_inherit" model="ir.ui.view">
<field name="name">base.config.settings.inherit</field> <field name="name">base.config.settings.inherit</field>
<field name="inherit_id" ref="base_setup.view_general_configuration"/> <field name="inherit_id" ref="base_setup.view_general_configuration" />
<field name="model">base.config.settings</field> <field name="model">base.config.settings</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<group name="report" position="before"> <group name="report" position="before">
<group string="Pingen Integration"> <group string="Pingen Integration">
<field name="pingen_token" groups="base.group_system"/> <field name="pingen_token" groups="base.group_system" />
<field name="pingen_staging" groups="base.group_system"/> <field name="pingen_staging" groups="base.group_system" />
</group> </group>
</group> </group>
</field> </field>

View File

@@ -1,16 +1,22 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<record id="view_attachment_form" model="ir.ui.view"> <record id="view_attachment_form" model="ir.ui.view">
<field name="name">ir.attachment.pingen.view</field> <field name="name">ir.attachment.pingen.view</field>
<field name="model">ir.attachment</field> <field name="model">ir.attachment</field>
<field name="type">form</field> <field name="type">form</field>
<field name="inherit_id" ref="base.view_attachment_form"/> <field name="inherit_id" ref="base.view_attachment_form" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//group/group[2]" position="after"> <xpath expr="//group/group[2]" position="after">
<group string="Pingen info" groups="base.group_no_one"> <group string="Pingen info" groups="base.group_no_one">
<field name="send_to_pingen"/> <field name="send_to_pingen" />
<field name="pingen_send" attrs="{'required': [('send_to_pingen', '=', True)]}"/> <field
<field name="pingen_speed" attrs="{'required': [('pingen_send', '=', True)]}"/> name="pingen_send"
attrs="{'required': [('send_to_pingen', '=', True)]}"
/>
<field
name="pingen_speed"
attrs="{'required': [('pingen_send', '=', True)]}"
/>
<field name="pingen_color" /> <field name="pingen_color" />
</group> </group>
</xpath> </xpath>
@@ -18,10 +24,11 @@
</record> </record>
<act_window <act_window
context="{'search_default_attachment_id': [active_id], 'default_attachment_id': active_id}" context="{'search_default_attachment_id': [active_id], 'default_attachment_id': active_id}"
id="act_attachment_to_pingen_document" id="act_attachment_to_pingen_document"
name="Pingen Document" name="Pingen Document"
groups="" groups=""
res_model="pingen.document" res_model="pingen.document"
src_model="ir.attachment"/> src_model="ir.attachment"
/>
</odoo> </odoo>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<record id="view_pingen_document_tree" model="ir.ui.view"> <record id="view_pingen_document_tree" model="ir.ui.view">
@@ -7,12 +7,12 @@
<field name="type">tree</field> <field name="type">tree</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree string="Pingen Document"> <tree string="Pingen Document">
<field name="name"/> <field name="name" />
<field name="datas_fname"/> <field name="datas_fname" />
<field name="pingen_send"/> <field name="pingen_send" />
<field name="pingen_speed"/> <field name="pingen_speed" />
<field name="pingen_color"/> <field name="pingen_color" />
<field name="state"/> <field name="state" />
</tree> </tree>
</field> </field>
</record> </record>
@@ -24,88 +24,127 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Pingen Document"> <form string="Pingen Document">
<header> <header>
<field name="state" widget="statusbar" <field
statusbar_visible="pending,pushed,sent" name="state"
statusbar_colors='{"error":"red","pingen_error":"red","canceled":"grey","pushed":"blue","sent":"green"}'/> widget="statusbar"
statusbar_visible="pending,pushed,sent"
statusbar_colors='{"error":"red","pingen_error":"red","canceled":"grey","pushed":"blue","sent":"green"}'
/>
</header> </header>
<group colspan="4" col="6"> <group colspan="4" col="6">
<field name="name" readonly="True"/> <field name="name" readonly="True" />
<field name="type" readonly="True"/> <field name="type" readonly="True" />
<field name="company_id" readonly="True" groups="base.group_multi_company" widget="selection"/> <field
name="company_id"
readonly="True"
groups="base.group_multi_company"
widget="selection"
/>
</group> </group>
<notebook colspan="4"> <notebook colspan="4">
<page string="Pingen.com"> <page string="Pingen.com">
<separator string="Options" colspan="4"/> <separator string="Options" colspan="4" />
<newline /> <newline />
<group col="2" colspan="2"> <group col="2" colspan="2">
<field name="pingen_send" attrs="{'readonly': [('state', 'in', ['sendcenter', 'sent'])]}"/> <field
<field name="pingen_speed" attrs="{'readonly': [('state', 'in', ['sendcenter', 'sent'])], 'required': [('pingen_send', '=', True)]}"/> name="pingen_send"
<field name="pingen_color" attrs="{'readonly': [('state', 'in', ['sendcenter', 'sent'])]}"/> attrs="{'readonly': [('state', 'in', ['sendcenter', 'sent'])]}"
/>
<field
name="pingen_speed"
attrs="{'readonly': [('state', 'in', ['sendcenter', 'sent'])], 'required': [('pingen_send', '=', True)]}"
/>
<field
name="pingen_color"
attrs="{'readonly': [('state', 'in', ['sendcenter', 'sent'])]}"
/>
</group> </group>
<separator string="Dates" colspan="4"/> <separator string="Dates" colspan="4" />
<newline /> <newline />
<group col="2" colspan="2"> <group col="2" colspan="2">
<field name="push_date"/> <field name="push_date" />
</group> </group>
<group colspan="4" attrs="{'invisible': [('last_error_message', '=', False)]}"> <group
<separator string="Errors" colspan="4"/> colspan="4"
attrs="{'invisible': [('last_error_message', '=', False)]}"
>
<separator string="Errors" colspan="4" />
<newline /> <newline />
<group col="2" colspan="2"> <group col="2" colspan="2">
<field nolabel="1" name="last_error_message"/> <field nolabel="1" name="last_error_message" />
</group> </group>
</group> </group>
<group colspan="4" attrs="{'invisible': [('state', 'not in', ['sendcenter', 'sent'])]}"> <group
<separator string="Sendcenter" colspan="4"/> colspan="4"
attrs="{'invisible': [('state', 'not in', ['sendcenter', 'sent'])]}"
>
<separator string="Sendcenter" colspan="4" />
<newline /> <newline />
<group col="4" colspan="2"> <group col="4" colspan="2">
<field colspan="4" name="post_status"/> <field colspan="4" name="post_status" />
<group col="3" colspan="2"> <group col="3" colspan="2">
<field name="cost"/> <field name="cost" />
<field colspan="1" nolabel="1" name="currency_id"/> <field colspan="1" nolabel="1" name="currency_id" />
</group> </group>
<newline/> <newline />
<field name="parsed_address"/> <field name="parsed_address" />
<field name="country_id"/> <field name="country_id" />
<field name="send_date"/> <field name="send_date" />
<field name="pages"/> <field name="pages" />
</group> </group>
</group> </group>
<separator string="Actions" colspan="4"/> <separator string="Actions" colspan="4" />
<newline /> <newline />
<group col="2" colspan="2"> <group col="2" colspan="2">
<button name="push_to_pingen" type="object" <button
states="pending" name="push_to_pingen"
string="Push to pingen.com" icon="fa-upload"/> type="object"
<button name="ask_pingen_send" type="object" states="pending"
states="pushed" string="Push to pingen.com"
string="Ask pingen.com to send the document" icon="fa-share"/> icon="fa-upload"
<button name="resolve_error" type="object" />
states="error,pingen_error" <button
string="Errors resolved" icon="fa-repeat"/> name="ask_pingen_send"
<button name="update_post_infos" type="object" type="object"
states="sendcenter" states="pushed"
string="Update the letter's informations" icon="fa-refresh"/> string="Ask pingen.com to send the document"
icon="fa-share"
/>
<button
name="resolve_error"
type="object"
states="error,pingen_error"
string="Errors resolved"
icon="fa-repeat"
/>
<button
name="update_post_infos"
type="object"
states="sendcenter"
string="Update the letter's informations"
icon="fa-refresh"
/>
</group> </group>
</page> </page>
<page string="Attachment"> <page string="Attachment">
<group col="4" colspan="4"> <group col="4" colspan="4">
<separator string="Data" colspan="4"/> <separator string="Data" colspan="4" />
<newline /> <newline />
<group col="2" colspan="4" attrs="{'invisible':[('type','=','url')]}"> <group col="2" colspan="4" attrs="{'invisible':[('type','=','url')]}">
<field name="datas" filename="datas_fname" readonly="True"/> <field name="datas" filename="datas_fname" readonly="True" />
<field name="datas_fname" select="1" readonly="True"/> <field name="datas_fname" select="1" readonly="True" />
</group> </group>
<group col="2" colspan="4" attrs="{'invisible':[('type','=','binary')]}"> <group col="2" colspan="4" attrs="{'invisible':[('type','=','binary')]}">
<field name="url" widget="url" readonly="True"/> <field name="url" widget="url" readonly="True" />
</group> </group>
</group> </group>
<group col="2" colspan="4"> <group col="2" colspan="4">
<separator string="Attached To" colspan="2"/> <separator string="Attached To" colspan="2" />
<field name="attachment_id"/> <field name="attachment_id" />
</group> </group>
</page> </page>
</notebook> </notebook>
@@ -119,28 +158,30 @@
<field name="type">search</field> <field name="type">search</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<search string="Pingen Document"> <search string="Pingen Document">
<filter icon="terp-project" <filter
string="Pending" icon="terp-project"
domain="[('state','=','pending')]"/> string="Pending"
<filter icon="terp-stage" domain="[('state','=','pending')]"
string="Pushed" />
domain="[('state','=','pushed')]"/> <filter icon="terp-stage" string="Pushed" domain="[('state','=','pushed')]" />
<filter icon="gtk-print" <filter
string="In Sendcenter" icon="gtk-print"
domain="[('state','=','sendcenter')]"/> string="In Sendcenter"
<filter icon="kanban-apply" domain="[('state','=','sendcenter')]"
string="Sent" />
domain="[('state','=','sent')]"/> <filter icon="kanban-apply" string="Sent" domain="[('state','=','sent')]" />
<filter icon="kanban-stop" <filter icon="kanban-stop" string="Error" domain="[('state','=','error')]" />
string="Error" <filter
domain="[('state','=','error')]"/> icon="STOCK_NO"
<filter icon="STOCK_NO" string="Pingen Error"
string="Pingen Error" domain="[('state','=','pingen_error')]"
domain="[('state','=','pingen_error')]"/> />
<filter icon="terp-dialog-close" <filter
string="Canceled" icon="terp-dialog-close"
domain="[('state','=','canceled')]"/> string="Canceled"
<separator orientation="vertical"/> domain="[('state','=','canceled')]"
/>
<separator orientation="vertical" />
<field name="attachment_id" /> <field name="attachment_id" />
</search> </search>
</field> </field>
@@ -152,9 +193,9 @@
<field name="res_model">pingen.document</field> <field name="res_model">pingen.document</field>
<field name="view_type">form</field> <field name="view_type">form</field>
<field name="view_mode">tree,form</field> <field name="view_mode">tree,form</field>
<field name="search_view_id" ref="view_pingen_document_search"/> <field name="search_view_id" ref="view_pingen_document_search" />
</record> </record>
<menuitem action="action_pingen_document" id="menu_pingen_document"/> <menuitem action="action_pingen_document" id="menu_pingen_document" />
</odoo> </odoo>

View File

@@ -1,2 +1,3 @@
# generated from manifests external_dependencies # generated from manifests external_dependencies
pycups pycups
requests

View File

@@ -0,0 +1 @@
__import__('pkg_resources').declare_namespace(__name__)

View File

@@ -0,0 +1 @@
__import__('pkg_resources').declare_namespace(__name__)

View File

@@ -0,0 +1 @@
../../../../pingen

6
setup/pingen/setup.py Normal file
View File

@@ -0,0 +1,6 @@
import setuptools
setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)