Files
report-print-send/pingen/models/pingen_document.py
2023-09-25 08:57:04 +02:00

443 lines
16 KiB
Python

# Author: Guewen Baconnier
# Copyright 2012-2017 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import logging
from itertools import groupby
from cStringIO import StringIO
from oauthlib.oauth2.rfc6749.errors import OAuth2Error
import odoo
from odoo import _, api, fields, models
from odoo.exceptions import UserError
from .pingen import APIError, pingen_datetime_to_utc
_logger = logging.getLogger(__name__)
class PingenDocument(models.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"}
_order = "push_date desc, id desc"
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_undeliverable", "Undeliverable"),
("error", "Connection Error"),
("pingen_error", "Pingen Error"),
("canceled", "Canceled"),
],
string="State",
readonly=True,
required=True,
default="pending",
)
auto_send = fields.Boolean(
"Auto Send",
help="Defines if a document is merely uploaded or also sent",
default=True,
)
delivery_product = fields.Selection(
[
("fast", "fast"),
("cheap", "cheap"),
("bulk", "bulk"),
("track", "track"),
("sign", "sign"),
("atpost_economy", "atpost_economy"),
("atpost_priority", "atpost_priority"),
("postag_a", "postag_a"),
("postag_b", "postag_b"),
("postag_b2", "postag_b2"),
("postag_registered", "postag_registered"),
("postag_aplus", "postag_aplus"),
("dpag_standard", "dpag_standard"),
("dpag_economy", "dpag_economy"),
("indpost_mail", "indpost_mail"),
("indpost_speedmail", "indpost_speedmail"),
("nlpost_priority", "nlpost_priority"),
("dhl_priority", "dhl_priority"),
],
"Delivery product",
default="cheap",
)
print_spectrum = fields.Selection(
[("grayscale", "Grayscale"), ("color", "Color")],
"Print Spectrum",
default="grayscale",
)
print_mode = fields.Selection(
[("simplex", "Simplex"), ("duplex", "Duplex")], "Print mode", default="simplex"
)
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 API v2 fields
pingen_uuid = fields.Char(readonly=True)
pingen_status = fields.Char(readonly=True)
# sendcenter infos
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)
company_id = fields.Many2one(related="attachment_id.company_id")
_sql_constraints = [
(
"pingen_document_attachment_uniq",
"unique (attachment_id)",
"Only one Pingen document is allowed per attachment.",
),
]
def _push_to_pingen(self, pingen=None):
"""Push a document to pingen.com
:param Pingen pingen: optional pingen object to reuse session
"""
decoded_document = self.attachment_id._decoded_content()
if pingen is None:
pingen = self.company_id._get_pingen_client()
try:
doc_id, post_id, infos = pingen.push_document(
self.name,
StringIO(decoded_document),
self.attachment_id.mimetype,
self.auto_send,
self.delivery_product,
self.print_spectrum,
self.print_mode,
)
except OAuth2Error as e:
_logger.exception(
"Connection Error when pushing Pingen Document with ID %s to %s: %s"
% (self.id, pingen.api_url, e.description)
)
raise
except APIError:
_logger.error(
"API Error when pushing Pingen Document %s to %s."
% (self.id, pingen.api_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.')
push_date = pingen_datetime_to_utc(infos.get("created_at"))
self.write(
{
"last_error_message": error,
"state": state,
"push_date": fields.Datetime.to_string(push_date),
"pingen_uuid": doc_id,
"pingen_status": infos.get("status"),
}
)
_logger.info("Pingen Document %s: pushed to %s" % (self.id, pingen.api_url))
def push_to_pingen(self):
"""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.
"""
self.ensure_one()
state = False
error_msg = False
try:
session = self.company_id._get_pingen_client()
self._push_to_pingen(pingen=session)
except OAuth2Error:
state = "error"
error_msg = (
_("Connection Error when pushing document %s to Pingen") % self.name
)
except APIError as e:
state = "pingen_error"
error_msg = _("Error when pushing the document %s to Pingen:\n%s") % (
self.name,
e,
)
except Exception as e:
error_msg = _(
"Unexpected Error when pushing the document %s to Pingen:\n%s"
) % (self.name, e)
_logger.exception(error_msg)
finally:
if error_msg:
vals = {"last_error_message": error_msg}
if state:
vals.update({"state": state})
with odoo.registry(self.env.cr.dbname).cursor() as new_cr:
new_env = odoo.api.Environment(
new_cr, self.env.uid, self.env.context
)
self.with_env(new_env).write(vals)
raise UserError(error_msg)
return True
def _push_and_send_to_pingen_cron(self):
"""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
"""
with odoo.api.Environment.manage():
with odoo.registry(self.env.cr.dbname).cursor() as new_cr:
new_env = odoo.api.Environment(new_cr, self.env.uid, self.env.context)
# Instead of raising, store the error in the pingen.document
self = self.with_env(new_env)
not_sent_docs = self.search(
[("state", "!=", "sent")], order="company_id"
)
for company, documents in groupby(
not_sent_docs, lambda d: d.company_id
):
session = company._get_pingen_client()
for document in documents:
if document.state == "error":
document._resolve_error()
document.refresh()
try:
if document.state == "pending":
document._push_to_pingen(pingen=session)
elif document.state == "pushed" and not document.auto_send:
document._ask_pingen_send(pingen=session)
except OAuth2Error as e:
document.write({"last_error_message": e, "state": "error"})
except APIError as e:
document.write(
{"last_error_message": e, "state": "pingen_error"}
)
except BaseException:
_logger.error("Unexpected error in pingen cron")
return True
def _resolve_error(self):
"""A document as resolved, put in the correct state"""
if self.send_date:
state = "sent"
elif self.pingen_uuid:
state = "pushed"
else:
state = "pending"
self.write({"state": state})
def resolve_error(self):
"""A document as resolved, put in the correct state"""
for document in self:
document._resolve_error()
return True
def _ask_pingen_send(self, pingen):
"""For a document already pushed to pingen, ask to send it.
:param Pingen pingen: pingen object to reuse
"""
try:
infos = pingen.send_document(
self.pingen_uuid,
self.delivery_product,
self.print_spectrum,
self.print_mode,
)
except OAuth2Error:
_logger.exception(
"Connection Error when asking for sending Pingen Document %s "
"to %s." % (self.id, pingen.api_url)
)
raise
except APIError:
_logger.exception(
"API Error when asking for sending Pingen Document %s to %s."
% (self.id, pingen.api_url)
)
raise
self.write(
{
"last_error_message": False,
"state": "sendcenter",
"pingen_status": infos.get("status"),
}
)
_logger.info(
"Pingen Document %s: asked for sending to %s" % (self.id, pingen.api_url)
)
return True
def ask_pingen_send(self):
"""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.
"""
self.ensure_one()
try:
session = self.company_id._get_pingen_client()
self._ask_pingen_send(pingen=session)
except OAuth2Error:
raise UserError(
_(
"Connection Error when asking for "
"sending the document %s to Pingen"
)
% self.name
)
except APIError as e:
raise UserError(
_("Error when asking Pingen to send the document %s: " "\n%s")
% (self.name, e)
)
except BaseException:
_logger.exception(
"Unexpected Error when updating the status "
"of pingen.document %s: " % self.id
)
raise UserError(
_("Unexpected Error when updating the status " "of Document %s")
% self.name
)
return True
def _get_and_update_post_infos(self, pingen):
"""Update the informations from
pingen of a document in the Sendcenter
:param Pingen pingen: pingen object to reuse
"""
post_infos = self._get_post_infos(pingen)
self._update_post_infos(post_infos)
def _get_post_infos(self, pingen):
if not self.pingen_uuid:
return
try:
post_infos = pingen.post_infos(self.pingen_uuid)
except OAuth2Error:
_logger.exception(
"Connection Error when asking for "
"sending Pingen Document %s to %s." % (self.id, pingen.api_url)
)
raise
except APIError:
_logger.exception(
"API Error when asking for sending Pingen Document %s to %s."
% (self.id, pingen.api_url)
)
raise
return post_infos
@api.model
def _prepare_values_from_post_infos(self, post_infos):
country = self.env["res.country"].search(
[("code", "=", post_infos.get("country"))]
)
currency = self.env["res.currency"].search(
[("name", "=", post_infos.get("price_currency"))]
)
vals = {
"pingen_status": post_infos.get("status"),
"parsed_address": post_infos.get("address"),
"country_id": country.id,
"pages": post_infos.get("file_pages"),
"last_error_message": False,
"cost": post_infos.get("price_value"),
"currency_id": currency.id,
}
is_posted = post_infos.get("status") == "sent"
if is_posted:
post_date = post_infos.get("submitted_at")
send_date = fields.Datetime.to_string(pingen_datetime_to_utc(post_date))
vals["state"] = "sent"
else:
send_date = False
vals["send_date"] = send_date
return vals
def _update_post_infos(self, post_infos):
self.ensure_one()
values = self._prepare_values_from_post_infos(post_infos)
self.write(values)
_logger.info("Pingen Document %s: status updated" % self.id)
def _update_post_infos_cron(self):
"""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."""
with odoo.api.Environment.manage():
with odoo.registry(self.env.cr.dbname).cursor() as new_cr:
new_env = odoo.api.Environment(new_cr, self.env.uid, self.env.context)
# Instead of raising, store the error in the pingen.document
self = self.with_env(new_env)
pushed_docs = self.search([("state", "!=", "sent")])
for document in pushed_docs:
session = document.company_id._get_pingen_client()
try:
document._get_and_update_post_infos(pingen=session)
except (OAuth2Error, APIError):
# will be retried the next time
# In any case, the error has been
# logged by _update_post_infos
pass
except BaseException as e:
_logger.error("Unexcepted error in pingen cron: %", e)
raise
return True
def update_post_infos(self):
"""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.
"""
self.ensure_one()
try:
session = self.company_id._get_pingen_client()
self._get_and_update_post_infos(pingen=session)
except OAuth2Error:
raise UserError(
_(
"Connection Error when updating the status "
"of Document %s from Pingen"
)
% self.name
)
except APIError as e:
raise UserError(
_("Error when updating the status of Document %s from " "Pingen: \n%s")
% (self.name, e)
)
except BaseException:
_logger.exception(
"Unexpected Error when updating the status "
"of pingen.document %s: " % self.id
)
raise UserError(
_("Unexpected Error when updating the status " "of Document %s")
% self.name
)
return True