mirror of
https://github.com/bringout/euro-office.git
synced 2026-04-18 04:22:01 +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 @@
|
|||
from . import controllers
|
||||
|
|
@ -0,0 +1,529 @@
|
|||
#
|
||||
# (c) Copyright Ascensio System SIA 2024
|
||||
#
|
||||
import base64
|
||||
import codecs
|
||||
import io
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
import zipfile
|
||||
from datetime import datetime
|
||||
from urllib.parse import quote
|
||||
|
||||
from odoo import http
|
||||
from odoo.http import request
|
||||
from odoo.tools import (
|
||||
DEFAULT_SERVER_DATE_FORMAT,
|
||||
DEFAULT_SERVER_DATETIME_FORMAT,
|
||||
file_open,
|
||||
get_lang,
|
||||
)
|
||||
|
||||
from odoo.addons.eurooffice_odoo.controllers.controllers import Eurooffice_Connector, 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 config_utils as templates_config_utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Eurooffice_Inherited_Connector(Eurooffice_Connector):
|
||||
@http.route("/eurooffice/template/template_content/<string:path>", auth="public")
|
||||
def get_template_content(self, path):
|
||||
try:
|
||||
file_content = request.env["eurooffice.odoo.demo.templates"].get_template_content(path.replace("_", "/"))
|
||||
|
||||
return request.make_response(
|
||||
file_content,
|
||||
headers=[
|
||||
("Content-Type", "application/pdf"),
|
||||
("Content-Disposition", 'inline; filename="preview.pdf"'),
|
||||
],
|
||||
)
|
||||
except Exception as e:
|
||||
return request.not_found(f"Error: {str(e)}")
|
||||
|
||||
@http.route("/eurooffice/template/editor", auth="user", methods=["POST"], type="json", csrf=False)
|
||||
def override_render_editor(self, attachment_id, access_token=None):
|
||||
attachment = self.get_attachment(attachment_id)
|
||||
if not attachment:
|
||||
return request.not_found()
|
||||
|
||||
attachment.validate_access(access_token)
|
||||
|
||||
data = attachment.read(["id", "checksum", "public", "name", "access_token"])[0]
|
||||
filename = data["name"]
|
||||
|
||||
can_read = attachment.check_access_rights("read", raise_exception=False) and file_utils.can_view(filename)
|
||||
hasAccess = http.request.env.user.has_group("eurooffice_odoo_templates.group_eurooffice_odoo_templates_admin")
|
||||
can_write = (
|
||||
hasAccess
|
||||
and attachment.check_access_rights("write", raise_exception=False)
|
||||
and file_utils.can_edit(filename)
|
||||
)
|
||||
|
||||
if not can_read:
|
||||
raise Exception("cant read")
|
||||
|
||||
prepare_editor_values = self.prepare_editor_values(attachment, access_token, can_write)
|
||||
return prepare_editor_values
|
||||
|
||||
|
||||
class EuroofficeTemplate_Connector(http.Controller):
|
||||
@http.route("/eurooffice/template/fill", auth="user", type="http")
|
||||
def main(self, template_id, record_ids):
|
||||
logger.info("GET /eurooffice/template/fill - template: %s, records: %s", template_id, record_ids)
|
||||
internal_jwt_secret = config_utils.get_internal_jwt_secret(request.env)
|
||||
oo_security_token = jwt_utils.encode_payload(request.env, {"id": request.env.user.id}, internal_jwt_secret)
|
||||
|
||||
try:
|
||||
templates = self.fill_template(oo_security_token, record_ids, template_id)
|
||||
if len(templates) == 1:
|
||||
url = next(iter(templates.values()))
|
||||
filename = next(iter(templates))
|
||||
filename = filename.encode("ascii", "ignore").decode("ascii")
|
||||
if not filename:
|
||||
filename = "document.pdf"
|
||||
response = eurooffice_request(url=quote(url, safe="/:?=&"), method="get")
|
||||
if response.status_code == 200:
|
||||
headers = [
|
||||
("Content-Type", "application/pdf"),
|
||||
("X-Content-Type-Options", "nosniff"),
|
||||
("Content-Length", str(len(response.content))),
|
||||
("Content-Disposition", f'attachment; filename="{filename}"'),
|
||||
]
|
||||
logger.info("GET /eurooffice/template/fill - returning single PDF: %s", filename)
|
||||
return request.make_response(response.content, headers)
|
||||
else:
|
||||
e = f"error while downloading the document file, status = {response.status_code}"
|
||||
logger.warning(e)
|
||||
return request.not_found()
|
||||
elif len(templates) > 1:
|
||||
logger.info("GET /eurooffice/template/fill - creating ZIP with %s files", len(templates))
|
||||
stream = io.BytesIO()
|
||||
with zipfile.ZipFile(stream, "w", zipfile.ZIP_DEFLATED) as archive:
|
||||
for filename, url in templates.items():
|
||||
response = eurooffice_request(url=url, method="get")
|
||||
if response.status_code == 200:
|
||||
archive.writestr(filename, response.content)
|
||||
else:
|
||||
e = f"error while downloading the document file to be generated zip, status = {response.status_code}" # noqa: E501
|
||||
logger.warning(e)
|
||||
return request.not_found()
|
||||
stream.seek(0)
|
||||
content = stream.read()
|
||||
stream.flush()
|
||||
|
||||
filename = f"eurooffice-templates-{datetime.now().strftime('%Y_%m_%d_%H_%M')}.zip"
|
||||
headers = [
|
||||
("Content-Type", "application/zip"),
|
||||
("X-Content-Type-Options", "nosniff"),
|
||||
("Content-Length", str(len(response.content))),
|
||||
("Content-Disposition", f'attachment; filename="{filename}"'),
|
||||
]
|
||||
logger.info("GET /eurooffice/template/fill - returning ZIP: %s", filename)
|
||||
return request.make_response(content, headers)
|
||||
else:
|
||||
logger.warning("no templates found")
|
||||
logger.debug(templates)
|
||||
return request.not_found()
|
||||
except Exception as e:
|
||||
logger.warning(e)
|
||||
return request.not_found()
|
||||
|
||||
return request.not_found()
|
||||
|
||||
def fill_template(self, oo_security_token, record_ids, template_id):
|
||||
logger.info("fill_template - template: %s, records: %s", template_id, record_ids)
|
||||
docserver_url = config_utils.get_doc_server_public_url(request.env)
|
||||
docserver_url = url_utils.replace_public_url_to_internal(request.env, docserver_url)
|
||||
docbuilder_url = f"{docserver_url}docbuilder"
|
||||
jwt_header = config_utils.get_jwt_header(request.env)
|
||||
jwt_secret = config_utils.get_jwt_secret(request.env)
|
||||
odoo_url = config_utils.get_base_or_odoo_url(request.env)
|
||||
|
||||
docbuilder_headers = {"Content-Type": "application/json", "Accept": "application/json"}
|
||||
docbuilder_callback_url = f"{odoo_url}eurooffice/template/callback/docbuilder/fill_template?oo_security_token={oo_security_token}&record_ids={record_ids}&template_id={template_id}" # noqa: E501
|
||||
docbuilder_payload = {"async": False, "url": docbuilder_callback_url}
|
||||
|
||||
logger.info("fill_template - docserver_url: %s", docserver_url)
|
||||
logger.info("fill_template - jwt_enabled: %s", bool(jwt_secret))
|
||||
|
||||
if jwt_secret:
|
||||
docbuilder_payload["token"] = jwt_utils.encode_payload(request.env, docbuilder_payload, jwt_secret)
|
||||
docbuilder_headers[jwt_header] = "Bearer " + jwt_utils.encode_payload(
|
||||
request.env, {"payload": docbuilder_payload}, jwt_secret
|
||||
)
|
||||
|
||||
try:
|
||||
if jwt_secret:
|
||||
docbuilder_response = eurooffice_request(
|
||||
url=docbuilder_url,
|
||||
method="post",
|
||||
opts={
|
||||
"json": docbuilder_payload,
|
||||
"headers": docbuilder_headers,
|
||||
},
|
||||
)
|
||||
else:
|
||||
docbuilder_response = eurooffice_request(
|
||||
url=docbuilder_url,
|
||||
method="post",
|
||||
opts={
|
||||
"json": docbuilder_payload,
|
||||
},
|
||||
)
|
||||
docbuilder_json = docbuilder_response.json()
|
||||
if docbuilder_json.get("error"):
|
||||
e = self.get_docbuilder_error(docbuilder_json.get("error"))
|
||||
logger.warning("fill_template - docbuilder error: %s", e)
|
||||
raise Exception(e)
|
||||
|
||||
urls = docbuilder_json.get("urls")
|
||||
logger.info("fill_template - success, got %s URLs", len(urls) if urls else 0)
|
||||
return urls
|
||||
except Exception as e:
|
||||
logger.warning("fill_template - error: %s", str(e))
|
||||
raise
|
||||
|
||||
@http.route("/eurooffice/template/callback/docbuilder/fill_template", auth="public")
|
||||
def docbuilder_fill_template(self, oo_security_token, record_ids, template_id):
|
||||
logger.info(
|
||||
"GET /eurooffice/template/callback/docbuilder/fill_template - template: %s, records: %s",
|
||||
template_id,
|
||||
record_ids,
|
||||
)
|
||||
if not oo_security_token or not record_ids or not template_id:
|
||||
logger.warning("oo_security_token or record_ids or template_id not found")
|
||||
return request.not_found()
|
||||
|
||||
user = self.get_user_from_token(oo_security_token)
|
||||
if not user:
|
||||
logger.warning("user not found")
|
||||
return request.not_found()
|
||||
|
||||
template = self.get_record("eurooffice.odoo.templates", template_id, user)
|
||||
if not template:
|
||||
logger.warning("template not found: %s", template_id)
|
||||
return request.not_found()
|
||||
|
||||
attachment_id = template.attachment_id.id
|
||||
if not attachment_id:
|
||||
logger.warning("attachment_id of the template was not found")
|
||||
return request.not_found()
|
||||
|
||||
model = template.template_model_model
|
||||
if not model:
|
||||
logger.warning("model of the template was not found")
|
||||
return request.not_found()
|
||||
|
||||
try:
|
||||
record_ids = [int(x) for x in record_ids.split(",")]
|
||||
logger.info(
|
||||
"GET /eurooffice/template/callback/docbuilder/fill_template - processing %s records", len(record_ids)
|
||||
)
|
||||
url = f"{config_utils.get_base_or_odoo_url(http.request.env)}eurooffice/template/download/{attachment_id}?oo_security_token={oo_security_token}" # noqa: E501
|
||||
|
||||
docbuilder_content = ""
|
||||
docbuilder_script_content = ""
|
||||
with file_open("eurooffice_odoo_templates/controllers/fill_template.docbuilder", "r") as f:
|
||||
docbuilder_script_content = f.read()
|
||||
|
||||
keys = self.get_keys(attachment_id, oo_security_token)
|
||||
logger.info(
|
||||
"GET /eurooffice/template/callback/docbuilder/fill_template - got %s keys", len(keys) if keys else 0
|
||||
)
|
||||
for record_id in record_ids:
|
||||
fields = self.get_fields(keys, model, record_id, user)
|
||||
fields = json.dumps(fields, ensure_ascii=False)
|
||||
|
||||
docbuilder_content += f"""
|
||||
builder.OpenFile("{url}");
|
||||
var fields = {fields};
|
||||
"""
|
||||
docbuilder_content += docbuilder_script_content
|
||||
|
||||
record = self.get_record(model, record_id, user)
|
||||
record_name = getattr(record, "display_name", getattr(record, "name", str(record_id)))
|
||||
template_name = getattr(template, "display_name", getattr(template, "name", "Filled Template"))
|
||||
filename = re.sub(r"[<>:'/\\|?*\x00-\x1f]", " ", f"{template_name} - {record_name}")
|
||||
|
||||
editable_form_fields = templates_config_utils.get_editable_form_fields(http.request.env)
|
||||
if editable_form_fields:
|
||||
docbuilder_content += f"""
|
||||
builder.SaveFile("pdf", "{filename}.pdf", "<m_sJsonParams>{{"isPrint":true}}</m_sJsonParams>")
|
||||
builder.CloseFile();
|
||||
""" # noqa: E501
|
||||
else:
|
||||
docbuilder_content += f"""
|
||||
builder.SaveFile("pdf", "{filename}.pdf");
|
||||
builder.CloseFile();
|
||||
"""
|
||||
|
||||
headers = {
|
||||
"Content-Disposition": "attachment; filename='fill_template.docbuilder'",
|
||||
"Content-Type": "text/plain",
|
||||
}
|
||||
|
||||
logger.info("GET /eurooffice/template/callback/docbuilder/fill_template - success")
|
||||
return request.make_response(docbuilder_content, headers)
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(e)
|
||||
return request.not_found()
|
||||
|
||||
def get_keys(self, attachment_id, oo_security_token):
|
||||
logger.info("get_keys - attachment: %s", attachment_id)
|
||||
docserver_url = config_utils.get_doc_server_public_url(request.env)
|
||||
docserver_url = url_utils.replace_public_url_to_internal(request.env, docserver_url)
|
||||
docbuilder_url = f"{docserver_url}docbuilder"
|
||||
jwt_header = config_utils.get_jwt_header(request.env)
|
||||
jwt_secret = config_utils.get_jwt_secret(request.env)
|
||||
odoo_url = config_utils.get_base_or_odoo_url(request.env)
|
||||
|
||||
docbuilder_headers = {"Content-Type": "application/json", "Accept": "application/json"}
|
||||
docbuilder_callback_url = f"{odoo_url}eurooffice/template/callback/docbuilder/get_keys?attachment_id={attachment_id}&oo_security_token={oo_security_token}" # noqa: E501
|
||||
docbuilder_payload = {"async": False, "url": docbuilder_callback_url}
|
||||
|
||||
if jwt_secret:
|
||||
docbuilder_payload["token"] = jwt_utils.encode_payload(request.env, docbuilder_payload, jwt_secret)
|
||||
docbuilder_headers[jwt_header] = "Bearer " + jwt_utils.encode_payload(
|
||||
request.env, {"payload": docbuilder_payload}, jwt_secret
|
||||
)
|
||||
|
||||
try:
|
||||
if jwt_secret:
|
||||
docbuilder_response = eurooffice_request(
|
||||
url=docbuilder_url,
|
||||
method="post",
|
||||
opts={
|
||||
"json": docbuilder_payload,
|
||||
"headers": docbuilder_headers,
|
||||
},
|
||||
)
|
||||
else:
|
||||
docbuilder_response = eurooffice_request(
|
||||
url=docbuilder_url,
|
||||
method="post",
|
||||
opts={
|
||||
"json": docbuilder_payload,
|
||||
},
|
||||
)
|
||||
docbuilder_json = docbuilder_response.json()
|
||||
if docbuilder_json.get("error"):
|
||||
e = self.get_docbuilder_error(docbuilder_json.get("error"))
|
||||
raise Exception(e)
|
||||
|
||||
urls = docbuilder_json.get("urls")
|
||||
keys_url = urls.get("keys.txt")
|
||||
keys_response = eurooffice_request(
|
||||
url=keys_url,
|
||||
method="get",
|
||||
)
|
||||
response_content = codecs.decode(keys_response.content, "utf-8-sig")
|
||||
|
||||
logger.info("get_keys - success")
|
||||
return json.loads(response_content)
|
||||
except Exception as e:
|
||||
logger.warning("get_keys - error: %s", str(e))
|
||||
raise
|
||||
|
||||
@http.route("/eurooffice/template/callback/docbuilder/get_keys", auth="public")
|
||||
def docbuilder_get_keys(self, attachment_id, oo_security_token):
|
||||
logger.info("GET /eurooffice/template/callback/docbuilder/get_keys - attachment: %s", attachment_id)
|
||||
if not attachment_id or not oo_security_token:
|
||||
logger.warning("attachment_id or oo_security_token not found")
|
||||
return request.not_found()
|
||||
|
||||
url = f"{config_utils.get_base_or_odoo_url(http.request.env)}eurooffice/template/download/{attachment_id}?oo_security_token={oo_security_token}" # noqa: E501
|
||||
docbuilder_content = f"""
|
||||
builder.OpenFile("{url}");
|
||||
"""
|
||||
|
||||
with file_open("eurooffice_odoo_templates/controllers/get_keys.docbuilder", "r") as f:
|
||||
docbuilder_content = docbuilder_content + f.read()
|
||||
|
||||
headers = {
|
||||
"Content-Disposition": "attachment; filename='get_keys.docbuilder'",
|
||||
"Content-Type": "text/plain",
|
||||
}
|
||||
|
||||
logger.info("GET /eurooffice/template/callback/docbuilder/get_keys - success")
|
||||
return request.make_response(docbuilder_content, headers)
|
||||
|
||||
@http.route("/eurooffice/template/download/<int:attachment_id>", auth="public")
|
||||
def download(self, attachment_id, oo_security_token):
|
||||
logger.info("GET /eurooffice/template/download - attachment: %s", attachment_id)
|
||||
if not attachment_id or not oo_security_token:
|
||||
logger.warning("attachment_id or oo_security_token not found")
|
||||
return request.not_found()
|
||||
|
||||
attachment = self.get_record("ir.attachment", attachment_id, self.get_user_from_token(oo_security_token))
|
||||
if attachment:
|
||||
content = base64.b64decode(attachment.datas)
|
||||
headers = {
|
||||
"Content-Type": "application/pdf",
|
||||
"Content-Disposition": "attachment; filename=template.pdf",
|
||||
}
|
||||
logger.info("GET /eurooffice/template/download - success")
|
||||
return request.make_response(content, headers)
|
||||
else:
|
||||
logger.warning("attachment not found: %s", attachment_id)
|
||||
return request.not_found()
|
||||
|
||||
def get_fields(self, keys, model, record_id, user): # noqa: C901
|
||||
logger.info("get_fields - model: %s, record: %s", model, record_id)
|
||||
|
||||
def convert_keys(input_list):
|
||||
output_dict = {}
|
||||
for item in input_list:
|
||||
if " " in item:
|
||||
keys = item.split(" ")
|
||||
current_dict = output_dict
|
||||
for key in keys[:-1]:
|
||||
current_dict = current_dict.setdefault(key, {})
|
||||
current_dict[keys[-1]] = None
|
||||
else:
|
||||
output_dict[item] = None
|
||||
|
||||
def dict_to_list(input_dict):
|
||||
output_list = []
|
||||
for key, value in input_dict.items():
|
||||
if isinstance(value, dict):
|
||||
output_list.append({key: dict_to_list(value)})
|
||||
else:
|
||||
output_list.append(key)
|
||||
return output_list
|
||||
|
||||
return dict_to_list(output_dict)
|
||||
|
||||
def get_related_field(keys, model, record_id): # noqa: C901
|
||||
result = {}
|
||||
record = self.get_record(model, record_id, user)
|
||||
if not record:
|
||||
logger.warning("Record not found")
|
||||
return
|
||||
for field in keys:
|
||||
try:
|
||||
if isinstance(field, dict):
|
||||
related_field = list(field.keys())[0]
|
||||
if related_field not in record._fields:
|
||||
continue
|
||||
field_type = record._fields[related_field].type
|
||||
related_keys = field[related_field]
|
||||
if field_type in ["one2many", "many2many", "many2one"]:
|
||||
related_model = record._fields[related_field].comodel_name
|
||||
related_record_ids = record.read([related_field])[0][related_field]
|
||||
if not related_record_ids:
|
||||
continue
|
||||
if field_type == "many2one" and isinstance(related_record_ids, tuple):
|
||||
related_data = get_related_field(related_keys, related_model, related_record_ids[0])
|
||||
else:
|
||||
related_data = []
|
||||
for record_id in related_record_ids:
|
||||
related_data_temp = get_related_field(related_keys, related_model, record_id)
|
||||
if related_data_temp:
|
||||
related_data.append(related_data_temp)
|
||||
if related_data:
|
||||
result[related_field] = related_data
|
||||
else:
|
||||
if field not in record._fields:
|
||||
continue
|
||||
field_type = record._fields[field].type
|
||||
data = record.read([field])[0][field]
|
||||
if field_type in ["html", "json"]:
|
||||
continue # TODO
|
||||
elif field_type == "boolean":
|
||||
result[field] = str(data).lower()
|
||||
elif isinstance(data, tuple):
|
||||
result[field] = str(data[1])
|
||||
elif field_type == "binary" and isinstance(data, bytes):
|
||||
img = re.search(r"'(.*?)'", str(data))
|
||||
if img:
|
||||
result[field] = img.group(1)
|
||||
elif data:
|
||||
if field_type in ["float", "integer", "char", "text"]:
|
||||
result[field] = str(data)
|
||||
elif field_type == "monetary":
|
||||
data = f"{float(data):,.2f}"
|
||||
currency_field_name = record._fields[field].currency_field
|
||||
if currency_field_name:
|
||||
currency = getattr(record, currency_field_name).name
|
||||
result[field] = f"{data} {currency}" if currency else str(data)
|
||||
else:
|
||||
result[field] = str(data)
|
||||
elif field_type == "date":
|
||||
date_format = None
|
||||
lang = request.env["res.lang"].search([("code", "=", user.lang)], limit=1)
|
||||
user_date_format = lang.date_format
|
||||
if user_date_format:
|
||||
date_format = user_date_format
|
||||
else:
|
||||
date_format = get_lang(request.env).date_format
|
||||
format_to_use = date_format or DEFAULT_SERVER_DATE_FORMAT
|
||||
result[field] = str(data.strftime(format_to_use))
|
||||
elif field_type == "datetime":
|
||||
date_format = None
|
||||
time_format = None
|
||||
lang = request.env["res.lang"].search([("code", "=", user.lang)], limit=1)
|
||||
user_date_format = lang.date_format
|
||||
user_time_format = lang.time_format
|
||||
if user_date_format and user_time_format:
|
||||
date_format = user_date_format
|
||||
time_format = user_time_format
|
||||
else:
|
||||
date_format = get_lang(request.env).date_format
|
||||
time_format = get_lang(request.env).time_format
|
||||
if date_format and time_format:
|
||||
format_to_use = f"{date_format} {time_format}"
|
||||
else:
|
||||
format_to_use = DEFAULT_SERVER_DATETIME_FORMAT
|
||||
result[field] = str(data.strftime(format_to_use))
|
||||
elif field_type == "selection":
|
||||
selection = record._fields[field].selection
|
||||
if isinstance(selection, list):
|
||||
result[field] = str(dict(selection).get(data))
|
||||
else:
|
||||
result[field] = str(data)
|
||||
except Exception as e:
|
||||
logger.warning(e)
|
||||
continue
|
||||
return result
|
||||
|
||||
keys = convert_keys(keys)
|
||||
return get_related_field(keys, model, record_id)
|
||||
|
||||
def get_record(self, model, record_id, user=None):
|
||||
logger.info("get_record - model: %s, record: %s", model, record_id)
|
||||
if not isinstance(record_id, list):
|
||||
record_id = [int(record_id)]
|
||||
model = request.env[model].sudo()
|
||||
context = {"lang": request.env.context.get("lang", "en_US")}
|
||||
if user:
|
||||
model = model.with_user(user)
|
||||
context["lang"] = user.lang
|
||||
context["uid"] = user.id
|
||||
try:
|
||||
return model.with_context(**context).browse(record_id).exists() # TODO: Add .sudo()
|
||||
except Exception as e:
|
||||
logger.warning(e)
|
||||
raise
|
||||
|
||||
def get_user_from_token(self, token):
|
||||
if not token:
|
||||
raise Exception("missing security token")
|
||||
user_id = jwt_utils.decode_token(request.env, token, config_utils.get_internal_jwt_secret(request.env))["id"]
|
||||
user = request.env["res.users"].sudo().browse(user_id).exists().ensure_one()
|
||||
logger.info("get_user_from_token - user: %s", user.name)
|
||||
return user
|
||||
|
||||
def get_docbuilder_error(self, error_code):
|
||||
docbuilder_messages = {
|
||||
-1: "Unknown error.",
|
||||
-2: "Generation timeout error.",
|
||||
-3: "Document generation error.",
|
||||
-4: "Error while downloading the document file to be generated.",
|
||||
-6: "Error while accessing the document generation result database.",
|
||||
-8: "Invalid token.",
|
||||
}
|
||||
return docbuilder_messages.get(error_code, "Error code not recognized.")
|
||||
|
|
@ -0,0 +1,370 @@
|
|||
var oDocument = Api.GetDocument()
|
||||
// As new elements appear when filling tables and lists, the total number of elements in the document increases,
|
||||
// so we need to store references to elements, or increase the loop counter when traversing document elements.
|
||||
var oDocumentElementsCount = oDocument.GetElementsCount()
|
||||
var oDocumentElements = []
|
||||
for (var i = 0; i < oDocumentElementsCount; i++) {
|
||||
var oElement = oDocument.GetElement(i)
|
||||
oDocumentElements.push(oElement)
|
||||
}
|
||||
|
||||
oDocumentElements.forEach((oElement) => {
|
||||
fillElementByType(oElement)
|
||||
})
|
||||
|
||||
// From docs:
|
||||
// 💡 Please note that the current paragraph must be in the document (not in the footer/header).
|
||||
// And if the current paragraph is placed in a shape, then a caption is added after (or before) the parent shape.
|
||||
// So, numbering in shapes and footer/header is not supported.
|
||||
|
||||
// fill header and footer
|
||||
var oSection = oDocument.GetFinalSection()
|
||||
var oHeader = oSection.GetHeader("default", true)
|
||||
var oFooter = oSection.GetFooter("default", true)
|
||||
var oHeaderElementsCount = oHeader.GetElementsCount()
|
||||
var oFooterElementsCount = oFooter.GetElementsCount()
|
||||
for (var i = 0; i < oHeaderElementsCount; i++) {
|
||||
var oElement = oHeader.GetElement(i)
|
||||
var oElementClassType = oElement.GetClassType()
|
||||
if (oElementClassType === "paragraph" && oElement.GetNumbering()) {
|
||||
continue
|
||||
} else {
|
||||
fillElementByType(oElement)
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < oFooterElementsCount; i++) {
|
||||
var oElement = oFooter.GetElement(i)
|
||||
var oElementClassType = oElement.GetClassType()
|
||||
if (oElementClassType === "paragraph" && oElement.GetNumbering()) {
|
||||
continue
|
||||
} else {
|
||||
fillElementByType(oElement)
|
||||
}
|
||||
}
|
||||
|
||||
// fill form in shapes object
|
||||
var oShapes = oDocument.GetAllShapes()
|
||||
oShapes.forEach((oShape) => {
|
||||
var oShapesClassType = oShape.GetClassType()
|
||||
if (oShapesClassType === "shape") {
|
||||
var oShapeContent = oShape.GetDocContent()
|
||||
try {
|
||||
var oShapeContentClassType = oShapeContent.GetClassType()
|
||||
if (oShapeContentClassType === "documentContent") {
|
||||
var oShapeElementsCount = oShapeContent.GetElementsCount()
|
||||
for (var i = 0; i < oShapeElementsCount; i++) {
|
||||
var oElement = oShapeContent.GetElement(i)
|
||||
var oElementClassType = oElement.GetClassType()
|
||||
if (oElementClassType === "paragraph" && oElement.GetNumbering()) {
|
||||
continue
|
||||
} else {
|
||||
fillElementByType(oElement)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (_e) {}
|
||||
}
|
||||
})
|
||||
|
||||
function getData(keyPath, index = 0, obj = fields) {
|
||||
const keys = keyPath.split(" ")
|
||||
|
||||
// Recursive function to traverse the object and get the desired value(s)
|
||||
function traverse(obj, keys) {
|
||||
if (keys.length === 0) return obj
|
||||
|
||||
let key = keys[0]
|
||||
let remainingKeys = keys.slice(1)
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map((item) => traverse(item, [key, ...remainingKeys]))
|
||||
} else {
|
||||
if (typeof obj[key] === "undefined") {
|
||||
return ""
|
||||
}
|
||||
return traverse(obj[key], remainingKeys)
|
||||
}
|
||||
}
|
||||
|
||||
// Get the value(s) from the object based on the keys
|
||||
let values = traverse(obj, keys)
|
||||
|
||||
if (typeof values[index] === "undefined") {
|
||||
return ""
|
||||
}
|
||||
|
||||
if (Array.isArray(values[0])) {
|
||||
// If the first element is an array, return a concatenated string of all values by key
|
||||
return values[index].flat().join(" ")
|
||||
} else if (Array.isArray(values) && typeof values[index] !== "object") {
|
||||
// If values are an array, return the value at the specified index
|
||||
return values[index]
|
||||
} else {
|
||||
// If the value is not an array, return it as is
|
||||
return values
|
||||
}
|
||||
}
|
||||
|
||||
function getDataArrayLength(keyPath, obj = fields) {
|
||||
const keys = keyPath.split(" ")
|
||||
let currentObj = obj
|
||||
for (let key of keys) {
|
||||
if (typeof currentObj === "undefined") {
|
||||
return 0
|
||||
}
|
||||
if (Array.isArray(currentObj)) {
|
||||
return currentObj.length
|
||||
}
|
||||
currentObj = currentObj[key]
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
function fillElementByType(oElement) {
|
||||
var oElementClassType = oElement.GetClassType()
|
||||
|
||||
if (oElementClassType === "paragraph") {
|
||||
if (oElement.GetNumbering()) {
|
||||
fillNumbering(oElement)
|
||||
} else {
|
||||
fillParagraph(oElement)
|
||||
}
|
||||
}
|
||||
if (oElementClassType === "form" || oElementClassType === "textForm") {
|
||||
fillForm(oElement)
|
||||
}
|
||||
if (oElementClassType === "run") {
|
||||
}
|
||||
if (oElementClassType === "table") {
|
||||
fillTable(oElement)
|
||||
}
|
||||
if (oElementClassType === "pictureForm") {
|
||||
fillForm(oElement)
|
||||
}
|
||||
}
|
||||
|
||||
function fillParagraph(oParagraph) {
|
||||
var oParagraphElementsCount = oParagraph.GetElementsCount()
|
||||
for (var i = 0; i < oParagraphElementsCount; i++) {
|
||||
var oElement = oParagraph.GetElement(i)
|
||||
fillElementByType(oElement)
|
||||
}
|
||||
}
|
||||
|
||||
function fillNumbering(oParagraph) {
|
||||
var requiredLevel = 0
|
||||
|
||||
var oParagraphElementsCount = oParagraph.GetElementsCount()
|
||||
for (var i = 0; i < oParagraphElementsCount; i++) {
|
||||
var oElement = oParagraph.GetElement(i)
|
||||
var oElementClassType = oElement.GetClassType()
|
||||
|
||||
if (oElementClassType === "form" || oElementClassType === "textForm" || oElementClassType === "pictureForm") {
|
||||
var oForm = oElement
|
||||
var oFormKey = oForm.GetFormKey()
|
||||
|
||||
var data = getData(oFormKey, 0)
|
||||
var length = getDataArrayLength(oFormKey)
|
||||
requiredLevel = Math.max(requiredLevel, length)
|
||||
fillForm(oForm, data)
|
||||
}
|
||||
}
|
||||
|
||||
if (requiredLevel > 1) {
|
||||
var oNumberingLevel = oParagraph.GetNumbering()
|
||||
var oCurrentParagraph = oParagraph
|
||||
for (var newLevel = 1; newLevel < requiredLevel; newLevel++) {
|
||||
var nPos = oCurrentParagraph.GetPosInParent()
|
||||
|
||||
var oNewParagraph = oParagraph.Copy()
|
||||
oNewParagraph.SetNumbering(oNumberingLevel)
|
||||
|
||||
var oParagraphElementsCount = oNewParagraph.GetElementsCount()
|
||||
for (var element = 0; element < oParagraphElementsCount; element++) {
|
||||
var oElement = oNewParagraph.GetElement(element)
|
||||
var oElementClassType = oElement.GetClassType()
|
||||
|
||||
if (oElementClassType === "form" || oElementClassType === "textForm" || oElementClassType === "pictureForm") {
|
||||
var oForm = oElement
|
||||
var oFormKey = oForm.GetFormKey()
|
||||
|
||||
oForm.SetFormKey(oFormKey + newLevel)
|
||||
var data = getData(oFormKey, newLevel)
|
||||
fillForm(oForm, data)
|
||||
}
|
||||
}
|
||||
oDocument.AddElement(nPos + 1, oNewParagraph)
|
||||
oCurrentParagraph = oNewParagraph
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function fillTable(oTable) {
|
||||
var rows = oTable.GetRowsCount()
|
||||
for (var row = 0; row < rows; row++) {
|
||||
var oRow = oTable.GetRow(row)
|
||||
var cols = oRow.GetCellsCount()
|
||||
|
||||
// If there is a form in the cell, then write the length of the array using the form key.
|
||||
// Maximum length - the number of lines that must be inserted after the current row.
|
||||
var requiredRows = 0
|
||||
var tableData = {}
|
||||
|
||||
for (var col = 0; col < cols; col++) {
|
||||
var oCell = oTable.GetCell(row, col)
|
||||
var oCellContent = oCell.GetContent()
|
||||
|
||||
// Enum of paragraphs inside the cell
|
||||
var oCellElementsCount = oCellContent.GetElementsCount()
|
||||
for (var cellElement = 0; cellElement < oCellElementsCount; cellElement++) {
|
||||
var oCellElement = oCellContent.GetElement(cellElement)
|
||||
|
||||
if (oCellElement.GetClassType() !== "paragraph") {
|
||||
fillElementByType(oCellElement)
|
||||
} else {
|
||||
// Enum paragraph elements inside a cell
|
||||
var oParagraphElementsCount = oCellElement.GetElementsCount()
|
||||
for (var paragraphElement = 0; paragraphElement < oParagraphElementsCount; paragraphElement++) {
|
||||
var oParagraphElement = oCellElement.GetElement(paragraphElement)
|
||||
|
||||
if (
|
||||
oParagraphElement.GetClassType() !== "form" &&
|
||||
oParagraphElement.GetClassType() !== "textForm" &&
|
||||
oParagraphElement.GetClassType() !== "pictureForm"
|
||||
) {
|
||||
fillElementByType(oParagraphElement)
|
||||
} else {
|
||||
// Fill the first element and count the number of required rows
|
||||
var oForm = oParagraphElement
|
||||
var oFormKey = oForm.GetFormKey()
|
||||
|
||||
var data = getData(oFormKey, 0)
|
||||
var length = getDataArrayLength(oFormKey)
|
||||
requiredRows = Math.max(requiredRows, length)
|
||||
fillForm(oForm, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (requiredRows > 1) {
|
||||
// Add new rows, the first row is already there and filled
|
||||
// In each cell of the column we copy the previous cell to save the structure.
|
||||
oTable.AddRows(oTable.GetCell(row, 0), requiredRows - 1, false)
|
||||
for (var newRow = 1; newRow < requiredRows; newRow++) {
|
||||
for (var col = 0; col < cols; col++) {
|
||||
var oNewCell = oTable.GetCell(row + newRow, col)
|
||||
var oCell = oTable.GetCell(row, col)
|
||||
var oCellContent = oCell.GetContent()
|
||||
|
||||
var oCellElementsCount = oCellContent.GetElementsCount()
|
||||
for (var i = 0; i < oCellElementsCount; i++) {
|
||||
// Get the contents of the cell for further copying to a new cell
|
||||
var oCellElement = oCellContent.GetElement(i)
|
||||
// Copy the contents of a cell to paste into a new cell
|
||||
var oNewCellElement = oCellElement.Copy()
|
||||
// If this is not a paragraph, paste into a new cell unchanged
|
||||
if (oCellElement.GetClassType() !== "paragraph") {
|
||||
oNewCell.AddElement(i, oNewCellElement)
|
||||
} else {
|
||||
// If it is a paragraph, process the elements of the paragraph to fill the forms inside
|
||||
var oParagraphElementsCount = oNewCellElement.GetElementsCount()
|
||||
for (var paragraphElement = 0; paragraphElement < oParagraphElementsCount; paragraphElement++) {
|
||||
var oNewParagraphElement = oNewCellElement.GetElement(paragraphElement)
|
||||
if (
|
||||
oNewParagraphElement.GetClassType() === "form" ||
|
||||
oNewParagraphElement.GetClassType() === "textForm" ||
|
||||
oNewParagraphElement.GetClassType() === "pictureForm"
|
||||
) {
|
||||
var oForm = oNewParagraphElement
|
||||
var oFormKey = oForm.GetFormKey()
|
||||
|
||||
var data = getData(oFormKey, newRow)
|
||||
fillForm(oForm, data)
|
||||
}
|
||||
}
|
||||
oNewCell.AddElement(i, oNewCellElement)
|
||||
}
|
||||
}
|
||||
// After creating a new cell, there is an empty paragraph inside. Remove it
|
||||
var oNewCellContent = oNewCell.GetContent()
|
||||
var oNewCellElementsCount = oNewCellContent.GetElementsCount()
|
||||
oNewCellContent.RemoveElement(oNewCellElementsCount - 1)
|
||||
}
|
||||
}
|
||||
rows += requiredRows - 1
|
||||
row += requiredRows - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// In odoo, if there is no data in the field, the value will be false
|
||||
function fillForm(oForm, data = null) {
|
||||
var oFormFormType = oForm.GetFormType()
|
||||
|
||||
if (oFormFormType === "form" || oFormFormType === "textForm") {
|
||||
function fillTextForm(data) {
|
||||
data = String(data)
|
||||
if (data === "false" || data === "undefined") {
|
||||
oForm.SetText(" ")
|
||||
} else {
|
||||
oForm.SetText(data)
|
||||
}
|
||||
}
|
||||
|
||||
if (data === null) {
|
||||
var oFormKey = oForm.GetFormKey()
|
||||
data = getData(oFormKey)
|
||||
fillTextForm(data)
|
||||
} else {
|
||||
fillTextForm(data)
|
||||
}
|
||||
}
|
||||
|
||||
if (oFormFormType === "comboBoxForm") {
|
||||
}
|
||||
|
||||
if (oFormFormType === "dropDownForm") {
|
||||
}
|
||||
|
||||
if (oFormFormType === "checkBoxForm") {
|
||||
function fillCheckBoxForm(data) {
|
||||
try {
|
||||
data = JSON.parse(data)
|
||||
oForm.SetChecked(data)
|
||||
} catch (_e) {
|
||||
// TODO: set checked BoxForm in case of error
|
||||
}
|
||||
}
|
||||
|
||||
if (data === null) {
|
||||
var oFormKey = oForm.GetFormKey()
|
||||
var data = getData(oFormKey)
|
||||
fillCheckBoxForm(data)
|
||||
} else {
|
||||
fillCheckBoxForm(data)
|
||||
}
|
||||
}
|
||||
|
||||
if (oFormFormType === "radioButtonForm") {
|
||||
}
|
||||
|
||||
if (oFormFormType === "pictureForm") {
|
||||
function fillPictureForm(data) {
|
||||
if (typeof data === "string") {
|
||||
oForm.SetImage(`data:image/png;base64, ${data}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (data === null) {
|
||||
var oFormKey = oForm.GetFormKey()
|
||||
data = getData(oFormKey)
|
||||
fillPictureForm(data)
|
||||
} else {
|
||||
fillPictureForm(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Api.Save()
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
var oDocument = Api.GetDocument();
|
||||
var aForms = oDocument.GetAllForms();
|
||||
var sKeys = [];
|
||||
aForms.forEach(aForm => {
|
||||
sKeys.push(aForm.GetFormKey());
|
||||
});
|
||||
var json = JSON.stringify(sKeys);
|
||||
GlobalVariable["json"] = json;
|
||||
builder.CloseFile();
|
||||
|
||||
builder.CreateFile("docx");
|
||||
var json = GlobalVariable["json"];
|
||||
var oDocument = Api.GetDocument();
|
||||
var oParagraph = oDocument.GetElement(0);
|
||||
oParagraph.AddText(json);
|
||||
oDocument.Push(oParagraph);
|
||||
Api.Save();
|
||||
builder.SaveFile("txt", "keys.txt");
|
||||
builder.CloseFile();
|
||||
Loading…
Add table
Add a link
Reference in a new issue