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:
Ernad Husremovic 2026-03-31 17:24:17 +02:00
commit b59a9dc6bb
347 changed files with 16699 additions and 0 deletions

View file

@ -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>{{&quot;isPrint&quot;: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.")

View file

@ -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()

View file

@ -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();