mirror of
https://github.com/bringout/euro-office.git
synced 2026-04-27 02:02:04 +02:00
init: Euro-Office Odoo 16.0 modules
Based on onlyoffice_odoo by Ascensio System SIA (ONLYOFFICE, LGPL-3). Rebranded and adapted for Euro-Office by bring.out d.o.o. Modules: - eurooffice_odoo: base integration - eurooffice_odoo_templates: document templates - eurooffice_odoo_oca_dms: OCA DMS integration (replaces Enterprise documents) All references renamed: onlyoffice -> eurooffice, ONLYOFFICE -> Euro-Office. Original copyright notices preserved.
This commit is contained in:
commit
b59a9dc6bb
347 changed files with 16699 additions and 0 deletions
|
|
@ -0,0 +1,3 @@
|
|||
from . import eurooffice_odoo_templates
|
||||
from . import eurooffice_odoo_demo_templates
|
||||
from . import res_config_settings
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
import base64
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.modules import get_module_path
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EuroOfficeDemoTemplate(models.Model):
|
||||
_name = "eurooffice.odoo.demo.templates"
|
||||
_description = "Euro-Office Demo Templates"
|
||||
|
||||
selected_templates = fields.Text(string="Selected Templates")
|
||||
|
||||
def _get_template_structure(self):
|
||||
templates_dir = self._get_templates_dir()
|
||||
structure = {}
|
||||
|
||||
for root, _dirs, files in os.walk(templates_dir):
|
||||
if files:
|
||||
model = os.path.basename(root)
|
||||
|
||||
model_exists = bool(self.env["ir.model"].search([("model", "=", model)], limit=1))
|
||||
if not model_exists:
|
||||
continue
|
||||
|
||||
name = self._get_model_name(model)
|
||||
|
||||
rel_path = os.path.relpath(root, templates_dir)
|
||||
|
||||
structure[model] = {
|
||||
"model": model,
|
||||
"name": name,
|
||||
"files": [
|
||||
{
|
||||
"name": f,
|
||||
"path": os.path.join(rel_path, f) if rel_path != "." else f,
|
||||
}
|
||||
for f in files
|
||||
],
|
||||
}
|
||||
|
||||
return structure
|
||||
|
||||
def _get_model_name(self, model_name):
|
||||
model = self.env["ir.model"].search([("model", "=", model_name)], limit=1)
|
||||
return model.name if model else model_name
|
||||
|
||||
def _get_templates_dir(self):
|
||||
module_path = get_module_path(self._module)
|
||||
return os.path.join(module_path, "data", "templates")
|
||||
|
||||
@api.model
|
||||
def get_template_data(self):
|
||||
structure = self._get_template_structure()
|
||||
selected = json.loads(self.selected_templates or "[]")
|
||||
|
||||
return {"structure": structure, "selected": selected}
|
||||
|
||||
def action_save(self):
|
||||
selected_templates = json.loads(self.selected_templates or "[]")
|
||||
if len(selected_templates) == 0:
|
||||
return
|
||||
templates_dir = self._get_templates_dir()
|
||||
template_model = self.env["eurooffice.odoo.templates"]
|
||||
|
||||
for template_path in selected_templates:
|
||||
model_name, filename = template_path.split("/")
|
||||
full_path = os.path.join(templates_dir, template_path)
|
||||
|
||||
try:
|
||||
with open(full_path, "rb") as f:
|
||||
template_data = base64.b64encode(f.read())
|
||||
|
||||
model = self.env["ir.model"].search([("model", "=", model_name)], limit=1)
|
||||
if not model:
|
||||
continue
|
||||
|
||||
template_model.create(
|
||||
{
|
||||
"name": os.path.splitext(filename)[0],
|
||||
"template_model_id": model.id,
|
||||
"file": template_data,
|
||||
"mimetype": "application/pdf",
|
||||
}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
_logger.error("Failed to process template %s: %s", template_path, str(e))
|
||||
continue
|
||||
|
||||
return {
|
||||
"type": "ir.actions.client",
|
||||
"tag": "soft_reload",
|
||||
}
|
||||
|
||||
def get_template_content(self, template_path):
|
||||
templates_dir = self._get_templates_dir()
|
||||
file_path = os.path.join(templates_dir, template_path)
|
||||
|
||||
with open(file_path, "rb") as f:
|
||||
return f.read()
|
||||
|
|
@ -0,0 +1,344 @@
|
|||
import base64
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
|
||||
from odoo import _, api, fields, models, tools
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.modules import get_module_path
|
||||
|
||||
from odoo.addons.eurooffice_odoo.controllers.controllers import eurooffice_request
|
||||
from odoo.addons.eurooffice_odoo.utils import config_utils, file_utils, jwt_utils, url_utils
|
||||
from odoo.addons.eurooffice_odoo_templates.utils import pdf_utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EuroOfficeTemplate(models.Model):
|
||||
_name = "eurooffice.odoo.templates"
|
||||
_description = "Euro-Office Templates"
|
||||
|
||||
name = fields.Char(required=True, string="Template Name")
|
||||
template_model_id = fields.Many2one("ir.model", string="Select Model")
|
||||
template_model_name = fields.Char(string="Model Description", compute="_compute_template_model_fields", store=True)
|
||||
template_model_related_name = fields.Char("Model Description", related="template_model_id.name")
|
||||
template_model_model = fields.Char(string=" ", compute="_compute_template_model_fields", store=True)
|
||||
file = fields.Binary(string="Upload an existing template")
|
||||
hide_file_field = fields.Boolean(string="Hide File Field", default=False)
|
||||
attachment_id = fields.Many2one("ir.attachment", readonly=True)
|
||||
mimetype = fields.Char(default="application/pdf")
|
||||
|
||||
@api.onchange("name")
|
||||
def _onchange_name(self):
|
||||
if self.attachment_id:
|
||||
self.attachment_id.name = self.name + ".pdf"
|
||||
self.attachment_id.display_name = self.name
|
||||
|
||||
@api.depends("template_model_id")
|
||||
def _compute_template_model_fields(self):
|
||||
for record in self:
|
||||
if record.template_model_id:
|
||||
record.template_model_name = record.template_model_id.name
|
||||
record.template_model_model = record.template_model_id.model
|
||||
else:
|
||||
record.template_model_name = False
|
||||
record.template_model_model = False
|
||||
|
||||
@api.onchange("file")
|
||||
def _onchange_file(self):
|
||||
if self.file and self.create_date: # if file exist
|
||||
decode_file = base64.b64decode(self.file)
|
||||
is_pdf_form = pdf_utils.is_pdf_form(decode_file)
|
||||
old_datas = self.attachment_id.datas
|
||||
self.attachment_id.write({"datas": self.file})
|
||||
self.file = False
|
||||
|
||||
if not is_pdf_form:
|
||||
self.env.cr.commit()
|
||||
converted_result = self._convert_to_form(self.attachment_id)
|
||||
if converted_result.get("error"):
|
||||
self.attachment_id.write({"datas": old_datas})
|
||||
self.env.cr.commit()
|
||||
raise UserError(converted_result.get("message"))
|
||||
if converted_result.get("fileUrl"):
|
||||
try:
|
||||
response = eurooffice_request(
|
||||
url=converted_result["fileUrl"],
|
||||
method="get",
|
||||
)
|
||||
new_datas = base64.b64encode(response.content)
|
||||
self.attachment_id.write({"datas": new_datas})
|
||||
self.env.cr.commit()
|
||||
except Exception as e:
|
||||
logger.error("Failed to download and update PDF form: %s", str(e))
|
||||
self.attachment_id.write({"datas": old_datas})
|
||||
self.env.cr.commit()
|
||||
raise UserError(_("Failed to download converted PDF form")) from e
|
||||
|
||||
@api.model
|
||||
def _create_demo_data(self):
|
||||
module_path = get_module_path(self._module)
|
||||
templates_dir = os.path.join(module_path, "data", "templates")
|
||||
if not os.path.exists(templates_dir):
|
||||
return
|
||||
|
||||
model_folders = [name for name in os.listdir(templates_dir) if os.path.isdir(os.path.join(templates_dir, name))]
|
||||
|
||||
installed_models = self.env["ir.model"].search([])
|
||||
installed_models_list = [(model.model, model.name) for model in installed_models]
|
||||
|
||||
for model_name in model_folders:
|
||||
if any(model_name == model[0] for model in installed_models_list):
|
||||
templates_path = os.path.join(templates_dir, model_name)
|
||||
templates_name = [
|
||||
name
|
||||
for name in os.listdir(templates_path)
|
||||
if os.path.isfile(os.path.join(templates_path, name)) and name.lower().endswith(".pdf")
|
||||
]
|
||||
for template_name in templates_name:
|
||||
template_path = os.path.join(templates_path, template_name)
|
||||
template = open(template_path, "rb")
|
||||
try:
|
||||
template_data = template.read()
|
||||
template_data = base64.encodebytes(template_data)
|
||||
model = self.env["ir.model"].search([("model", "=", model_name)], limit=1)
|
||||
name = template_name.rstrip(".pdf")
|
||||
self.create(
|
||||
{
|
||||
"name": name,
|
||||
"template_model_id": model.id,
|
||||
"file": template_data,
|
||||
}
|
||||
)
|
||||
finally:
|
||||
template.close()
|
||||
return
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
url = self._context.get("url", None)
|
||||
if isinstance(url, str) and url.startswith(("http://", "https://")) and url.endswith(".pdf"):
|
||||
try:
|
||||
response = eurooffice_request(
|
||||
url=url,
|
||||
method="get",
|
||||
)
|
||||
|
||||
file_content = response.content
|
||||
vals["file"] = base64.b64encode(file_content)
|
||||
except Exception as e:
|
||||
raise UserError(_("Failed to download form")) from e
|
||||
|
||||
is_pdf_form = None
|
||||
if "file" in vals and vals["file"]:
|
||||
try:
|
||||
decode_file = base64.b64decode(vals["file"])
|
||||
is_pdf_form = pdf_utils.is_pdf_form(decode_file)
|
||||
except Exception as e:
|
||||
raise UserError(_("Invalid file format.")) from e
|
||||
else:
|
||||
vals["file"] = base64.encodebytes(file_utils.get_default_file_template(self.env.user.lang, "pdf"))
|
||||
is_pdf_form = True
|
||||
|
||||
model = self.env["ir.model"].search([("id", "=", vals["template_model_id"])], limit=1)
|
||||
vals["template_model_name"] = model.name
|
||||
vals["template_model_model"] = model.model
|
||||
vals["mimetype"] = file_utils.get_mime_by_ext("pdf")
|
||||
|
||||
datas = vals.pop("file")
|
||||
vals.pop("hide_file_field", None)
|
||||
vals.pop("datas", None)
|
||||
|
||||
record = super().create(
|
||||
{
|
||||
"name": vals.get("name", "New Template"),
|
||||
"template_model_id": vals.get("template_model_id"),
|
||||
"mimetype": vals.get("mimetype", "application/pdf"),
|
||||
"template_model_name": vals.get("template_model_name", ""),
|
||||
"template_model_model": vals.get("template_model_model", ""),
|
||||
}
|
||||
)
|
||||
|
||||
attachment = self.env["ir.attachment"].create(
|
||||
{
|
||||
"name": vals.get("name", record.name) + ".pdf",
|
||||
"display_name": vals.get("name", record.name),
|
||||
"mimetype": vals.get("mimetype"),
|
||||
"datas": datas,
|
||||
"res_model": self._name,
|
||||
"res_id": record.id,
|
||||
}
|
||||
)
|
||||
record.attachment_id = attachment.id
|
||||
|
||||
if not is_pdf_form:
|
||||
self.env.cr.commit()
|
||||
converted_result = self._convert_to_form(attachment)
|
||||
if converted_result.get("error"):
|
||||
attachment.unlink()
|
||||
record.unlink()
|
||||
super().unlink()
|
||||
self.env.cr.commit()
|
||||
raise UserError(converted_result.get("message"))
|
||||
if converted_result.get("fileUrl"):
|
||||
try:
|
||||
response = eurooffice_request(
|
||||
url=converted_result["fileUrl"],
|
||||
method="get",
|
||||
)
|
||||
new_datas = base64.b64encode(response.content)
|
||||
attachment.write({"datas": new_datas, "mimetype": vals.get("mimetype")})
|
||||
self.env.cr.commit()
|
||||
except Exception as e:
|
||||
logger.error("Failed to download and update PDF form: %s", str(e))
|
||||
attachment.unlink()
|
||||
record.unlink()
|
||||
super().unlink()
|
||||
self.env.cr.commit()
|
||||
raise UserError(_("Failed to download converted PDF form")) from e
|
||||
return record
|
||||
|
||||
@api.model
|
||||
def _convert_to_form(self, attachment):
|
||||
jwt_header = config_utils.get_jwt_header(self.env)
|
||||
jwt_secret = config_utils.get_jwt_secret(self.env)
|
||||
docserver_url = config_utils.get_doc_server_public_url(self.env)
|
||||
docserver_url = url_utils.replace_public_url_to_internal(self.env, docserver_url)
|
||||
|
||||
odoo_url = config_utils.get_base_or_odoo_url(self.env)
|
||||
internal_jwt_secret = config_utils.get_internal_jwt_secret(self.env)
|
||||
|
||||
oo_security_token = jwt_utils.encode_payload(self.env, {"id": self.env.user.id}, internal_jwt_secret)
|
||||
oo_security_token = (
|
||||
oo_security_token.decode("utf-8") if isinstance(oo_security_token, bytes) else oo_security_token
|
||||
)
|
||||
|
||||
conversion_url = os.path.join(docserver_url, "ConvertService.ashx")
|
||||
|
||||
payload = {
|
||||
"url": f"{odoo_url}eurooffice/template/download/{attachment.id}?oo_security_token={oo_security_token}",
|
||||
"key": int(time.time()),
|
||||
"filetype": "pdf",
|
||||
"outputtype": "pdf",
|
||||
"pdf": {
|
||||
"form": True,
|
||||
},
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
}
|
||||
|
||||
if bool(jwt_secret):
|
||||
payload = {"payload": payload}
|
||||
token = jwt_utils.encode_payload(self.env, payload, jwt_secret)
|
||||
headers[jwt_header] = "Bearer " + token
|
||||
payload["token"] = token
|
||||
|
||||
try:
|
||||
response = eurooffice_request(
|
||||
url=conversion_url,
|
||||
method="get",
|
||||
opts={
|
||||
"data": json.dumps(payload),
|
||||
"headers": headers,
|
||||
},
|
||||
)
|
||||
if response.status_code == 200:
|
||||
response_json = response.json()
|
||||
if "error" in response_json:
|
||||
return {
|
||||
"error": response_json.get("error"),
|
||||
"message": self._get_conversion_error_message(response_json.get("error")),
|
||||
}
|
||||
else:
|
||||
return response_json
|
||||
else:
|
||||
return {
|
||||
"error": response.status_code,
|
||||
"message": f"Document conversion service returned status {response.status_code}",
|
||||
}
|
||||
except Exception:
|
||||
return {
|
||||
"error": 1,
|
||||
"message": "Document conversion service cannot be reached",
|
||||
}
|
||||
|
||||
def _get_conversion_error_message(self, error_code):
|
||||
error_dictionary = {
|
||||
-1: "Unknown error",
|
||||
-2: "Conversion timeout error",
|
||||
-3: "Conversion error",
|
||||
-4: "Error while downloading the document file to be converted",
|
||||
-5: "Incorrect password",
|
||||
-6: "Error while accessing the conversion result database",
|
||||
-7: "Input error",
|
||||
-8: "Invalid token",
|
||||
}
|
||||
try:
|
||||
return error_dictionary[error_code]
|
||||
except Exception:
|
||||
return "Undefined error code"
|
||||
|
||||
@api.model
|
||||
def get_fields_for_model(self, model, prefix="", parent_name="", exclude=None):
|
||||
try:
|
||||
m = self.env[model]
|
||||
fields = m.fields_get()
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
fields = sorted(fields.items(), key=lambda field: tools.ustr(field[1].get("string", "").lower()))
|
||||
records = []
|
||||
for field_name, field in fields:
|
||||
if exclude and field_name in exclude:
|
||||
continue
|
||||
if field.get("type") in ("properties", "properties_definition", "html", "json"):
|
||||
continue
|
||||
if not field.get("exportable", True):
|
||||
continue
|
||||
|
||||
ident = prefix + ("/" if prefix else "") + field_name
|
||||
val = ident
|
||||
name = parent_name + (parent_name and "/" or "") + field["string"]
|
||||
record = {
|
||||
"id": ident,
|
||||
"string": name,
|
||||
"value": val,
|
||||
"children": False,
|
||||
"field_type": field.get("type"),
|
||||
"required": field.get("required"),
|
||||
"relation_field": field.get("relation_field"),
|
||||
}
|
||||
records.append(record)
|
||||
|
||||
if len(ident.split("/")) < 4 and "relation" in field:
|
||||
ref = field.pop("relation")
|
||||
record["value"] += "/id"
|
||||
record["params"] = {"model": ref, "prefix": ident, "name": name}
|
||||
record["children"] = True
|
||||
|
||||
return records
|
||||
|
||||
@api.model
|
||||
def update_relationship(self, template_model_id, model):
|
||||
"""
|
||||
If the module was uninstalled and reinstalled, its model id may have changed.
|
||||
Update the model id in the template record
|
||||
"""
|
||||
if not template_model_id or not model:
|
||||
return
|
||||
|
||||
model_id = self.sudo().env["ir.model"].search([("model", "=", model)]).id
|
||||
if not model_id:
|
||||
return
|
||||
|
||||
record = self.sudo().env["eurooffice.odoo.templates"].browse(template_model_id)
|
||||
if not record:
|
||||
return
|
||||
|
||||
if record.template_model_id != model_id:
|
||||
record.template_model_id = model_id
|
||||
return
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
from odoo import fields, models
|
||||
|
||||
from odoo.addons.eurooffice_odoo_templates.utils import config_utils
|
||||
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = "res.config.settings"
|
||||
editable_form_fields = fields.Boolean("Disable form fields after printing PDF form")
|
||||
|
||||
def set_values(self):
|
||||
res = super().set_values()
|
||||
previous_disable_form_fields = config_utils.get_editable_form_fields(self.env)
|
||||
if previous_disable_form_fields != self.editable_form_fields:
|
||||
config_utils.set_editable_form_fields(self.env, self.editable_form_fields)
|
||||
return res
|
||||
|
||||
def get_values(self):
|
||||
res = super().get_values()
|
||||
editable_form_fields = config_utils.get_editable_form_fields(self.env)
|
||||
res.update(editable_form_fields=editable_form_fields)
|
||||
return res
|
||||
Loading…
Add table
Add a link
Reference in a new issue