[15.0][MIG] excel_import_export, excel_import_export_demo: Migration
parent
fa45e3289a
commit
f94ba4fc5c
|
@ -4,7 +4,7 @@
|
||||||
{
|
{
|
||||||
"name": "Excel Import/Export/Report",
|
"name": "Excel Import/Export/Report",
|
||||||
"summary": "Base module for developing Excel import/export/report",
|
"summary": "Base module for developing Excel import/export/report",
|
||||||
"version": "14.0.1.1.0",
|
"version": "15.0.1.0.0",
|
||||||
"author": "Ecosoft,Odoo Community Association (OCA)",
|
"author": "Ecosoft,Odoo Community Association (OCA)",
|
||||||
"license": "AGPL-3",
|
"license": "AGPL-3",
|
||||||
"website": "https://github.com/OCA/server-tools",
|
"website": "https://github.com/OCA/server-tools",
|
||||||
|
@ -18,9 +18,13 @@
|
||||||
"wizard/report_xlsx_wizard.xml",
|
"wizard/report_xlsx_wizard.xml",
|
||||||
"views/xlsx_template_view.xml",
|
"views/xlsx_template_view.xml",
|
||||||
"views/xlsx_report.xml",
|
"views/xlsx_report.xml",
|
||||||
"views/webclient_templates.xml",
|
|
||||||
],
|
],
|
||||||
"installable": True,
|
"installable": True,
|
||||||
"development_status": "Beta",
|
"development_status": "Beta",
|
||||||
"maintainers": ["kittiu"],
|
"maintainers": ["kittiu"],
|
||||||
|
"assets": {
|
||||||
|
"web.assets_backend": [
|
||||||
|
"/excel_import_export/static/src/js/report/action_manager_report.esm.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,24 @@
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
from odoo.http import content_disposition, request, route
|
from werkzeug.urls import url_decode
|
||||||
from odoo.tools.safe_eval import safe_eval
|
|
||||||
|
from odoo import http
|
||||||
|
from odoo.http import (
|
||||||
|
content_disposition,
|
||||||
|
request,
|
||||||
|
route,
|
||||||
|
serialize_exception as _serialize_exception,
|
||||||
|
)
|
||||||
|
from odoo.tools import html_escape
|
||||||
|
from odoo.tools.safe_eval import safe_eval, time
|
||||||
|
|
||||||
from odoo.addons.web.controllers import main as report
|
from odoo.addons.web.controllers import main as report
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ReportController(report.ReportController):
|
class ReportController(report.ReportController):
|
||||||
@route()
|
@route()
|
||||||
|
@ -28,15 +40,20 @@ class ReportController(report.ReportController):
|
||||||
if data["context"].get("lang"):
|
if data["context"].get("lang"):
|
||||||
del data["context"]["lang"]
|
del data["context"]["lang"]
|
||||||
context.update(data["context"])
|
context.update(data["context"])
|
||||||
excel, report_name = report.with_context(context).render_excel(
|
|
||||||
|
excel, report_name = report.with_context(**context)._render_excel(
|
||||||
docids, data=data
|
docids, data=data
|
||||||
)
|
)
|
||||||
excel = base64.decodestring(excel)
|
excel = base64.decodestring(excel)
|
||||||
if report.print_report_name and not len(docids) > 1:
|
if docids:
|
||||||
obj = request.env[report.model].browse(docids[0])
|
records = request.env[report.model].browse(docids)
|
||||||
file_ext = report_name.split(".")[-1:].pop()
|
if report.print_report_name and not len(records) > 1:
|
||||||
report_name = safe_eval(report.print_report_name, {"object": obj})
|
# this is a bad idea, this sou ld only be .xlsx
|
||||||
report_name = "{}.{}".format(report_name, file_ext)
|
extension = report_name.split(".")[-1:].pop()
|
||||||
|
report_name = safe_eval(
|
||||||
|
report.print_report_name, {"object": records, "time": time}
|
||||||
|
)
|
||||||
|
report_name = f"{report_name}.{extension}"
|
||||||
excelhttpheaders = [
|
excelhttpheaders = [
|
||||||
(
|
(
|
||||||
"Content-Type",
|
"Content-Type",
|
||||||
|
@ -47,6 +64,37 @@ class ReportController(report.ReportController):
|
||||||
("Content-Disposition", content_disposition(report_name)),
|
("Content-Disposition", content_disposition(report_name)),
|
||||||
]
|
]
|
||||||
return request.make_response(excel, headers=excelhttpheaders)
|
return request.make_response(excel, headers=excelhttpheaders)
|
||||||
return super(ReportController, self).report_routes(
|
return super().report_routes(reportname, docids, converter, **data)
|
||||||
reportname, docids, converter, **data
|
|
||||||
|
@http.route()
|
||||||
|
def report_download(self, data, context=None):
|
||||||
|
requestcontent = json.loads(data)
|
||||||
|
url, report_type = requestcontent[0], requestcontent[1]
|
||||||
|
if report_type != "excel":
|
||||||
|
return super().report_download(data, context)
|
||||||
|
reportname = "???"
|
||||||
|
try:
|
||||||
|
pattern = "/report/excel/"
|
||||||
|
reportname = url.split(pattern)[1].split("?")[0]
|
||||||
|
docids = None
|
||||||
|
if "/" in reportname:
|
||||||
|
reportname, docids = reportname.split("/")
|
||||||
|
_logger.warning(reportname)
|
||||||
|
if docids:
|
||||||
|
return self.report_routes(
|
||||||
|
reportname, docids=docids, converter="excel", context=context
|
||||||
)
|
)
|
||||||
|
data = dict(url_decode(url.split("?")[1]).items())
|
||||||
|
if "context" in data:
|
||||||
|
context, data_context = json.loads(context or "{}"), json.loads(
|
||||||
|
data.pop("context")
|
||||||
|
)
|
||||||
|
context = json.dumps({**context, **data_context})
|
||||||
|
return self.report_routes(
|
||||||
|
reportname, converter="excel", context=context, **data
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
_logger.exception("Error while generating report %s", reportname)
|
||||||
|
se = _serialize_exception(e)
|
||||||
|
error = {"code": 200, "message": "Odoo Server Error", "data": se}
|
||||||
|
return request.make_response(html_escape(json.dumps(error)))
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -6,8 +6,8 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Odoo Server 14.0\n"
|
"Project-Id-Version: Odoo Server 14.0\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2022-05-13 10:05+0000\n"
|
"PO-Revision-Date: 2022-03-16 13:17+0000\n"
|
||||||
"Last-Translator: Alessandro Fiorino <alessandro.fiorino@digitaldomus.it>\n"
|
"Last-Translator: Francesco Foresti <francesco.foresti@ooops404.com>\n"
|
||||||
"Language-Team: none\n"
|
"Language-Team: none\n"
|
||||||
"Language: it\n"
|
"Language: it\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
|
@ -242,7 +242,7 @@ msgstr "<code>date, datetime, time</code>: alcune utili classi python"
|
||||||
#. module: excel_import_export
|
#. module: excel_import_export
|
||||||
#: model_terms:ir.ui.view,arch_db:excel_import_export.view_xlsx_template_form
|
#: model_terms:ir.ui.view,arch_db:excel_import_export.view_xlsx_template_form
|
||||||
msgid "<code>model</code>: active model, e.g., self.env['my.model']"
|
msgid "<code>model</code>: active model, e.g., self.env['my.model']"
|
||||||
msgstr "<code>model</code>: modello attivo, ad es. self.env['my.model']"
|
msgstr "<code>modello</code>: modello attivo, ad es. self.env['my.model']"
|
||||||
|
|
||||||
#. module: excel_import_export
|
#. module: excel_import_export
|
||||||
#: model_terms:ir.ui.view,arch_db:excel_import_export.view_xlsx_template_form
|
#: model_terms:ir.ui.view,arch_db:excel_import_export.view_xlsx_template_form
|
||||||
|
@ -250,18 +250,18 @@ msgid ""
|
||||||
"<code>object</code>: record object or line object depends on <b>Row "
|
"<code>object</code>: record object or line object depends on <b>Row "
|
||||||
"Field</b>"
|
"Field</b>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"<code>object</code>: oggetto del record o oggetto della riga che dipende dal "
|
"<code>oggetto</code>: oggetto del record o oggetto della riga che dipende "
|
||||||
"<b>Campo Riga</b>"
|
"dal <b>Campo Riga</b>"
|
||||||
|
|
||||||
#. module: excel_import_export
|
#. module: excel_import_export
|
||||||
#: model_terms:ir.ui.view,arch_db:excel_import_export.view_xlsx_template_form
|
#: model_terms:ir.ui.view,arch_db:excel_import_export.view_xlsx_template_form
|
||||||
msgid "<code>value</code>: value from <b>Cell</b>"
|
msgid "<code>value</code>: value from <b>Cell</b>"
|
||||||
msgstr "<code>value</code>: valore dalla <b>Cella</b>"
|
msgstr "<code>valore</code>: valore dalla <b>Cella</b>"
|
||||||
|
|
||||||
#. module: excel_import_export
|
#. module: excel_import_export
|
||||||
#: model_terms:ir.ui.view,arch_db:excel_import_export.view_xlsx_template_form
|
#: model_terms:ir.ui.view,arch_db:excel_import_export.view_xlsx_template_form
|
||||||
msgid "<code>value</code>: value from <b>Field</b>"
|
msgid "<code>value</code>: value from <b>Field</b>"
|
||||||
msgstr "<code>value</code>: valore dal <b>Campo</b>"
|
msgstr "<code>valore</code>: valore dal <b>Campo</b>"
|
||||||
|
|
||||||
#. module: excel_import_export
|
#. module: excel_import_export
|
||||||
#. openerp-web
|
#. openerp-web
|
||||||
|
|
|
@ -105,10 +105,11 @@ def fill_cell_style(field, field_style, styles):
|
||||||
for f in field_styles:
|
for f in field_styles:
|
||||||
(key, value) = f.split("=")
|
(key, value) = f.split("=")
|
||||||
if key not in styles.keys():
|
if key not in styles.keys():
|
||||||
raise ValidationError(_("Invalid style type %s" % key))
|
raise ValidationError(_("Invalid style type %s") % key)
|
||||||
if value.lower() not in styles[key].keys():
|
if value.lower() not in styles[key].keys():
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
_("Invalid value {} for style type {}".format(value, key))
|
_("Invalid value %(value)s for style type %(key)s")
|
||||||
|
% {"value": value, "key": key}
|
||||||
)
|
)
|
||||||
cell_style = styles[key][value]
|
cell_style = styles[key][value]
|
||||||
if key == "font":
|
if key == "font":
|
||||||
|
@ -177,8 +178,8 @@ def xlrd_get_sheet_by_name(book, name):
|
||||||
sheet = book.sheet_by_index(idx)
|
sheet = book.sheet_by_index(idx)
|
||||||
if sheet.name == name:
|
if sheet.name == name:
|
||||||
return sheet
|
return sheet
|
||||||
except IndexError:
|
except IndexError as exc:
|
||||||
raise ValidationError(_("'%s' sheet not found") % (name,))
|
raise ValidationError(_("'%s' sheet not found") % (name,)) from exc
|
||||||
|
|
||||||
|
|
||||||
def isfloat(input_val):
|
def isfloat(input_val):
|
||||||
|
|
|
@ -13,7 +13,7 @@ class ReportAction(models.Model):
|
||||||
)
|
)
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def render_excel(self, docids, data):
|
def _render_excel(self, docids, data):
|
||||||
if len(docids) != 1:
|
if len(docids) != 1:
|
||||||
raise UserError(_("Only one id is allowed for excel_import_export"))
|
raise UserError(_("Only one id is allowed for excel_import_export"))
|
||||||
xlsx_template = self.env["xlsx.template"].search(
|
xlsx_template = self.env["xlsx.template"].search(
|
||||||
|
@ -21,10 +21,8 @@ class ReportAction(models.Model):
|
||||||
)
|
)
|
||||||
if not xlsx_template or len(xlsx_template) != 1:
|
if not xlsx_template or len(xlsx_template) != 1:
|
||||||
raise UserError(
|
raise UserError(
|
||||||
_(
|
_("Template %(report_name)s on model %(model)s is not unique!")
|
||||||
"Template %s on model %s is not unique!"
|
% {"report_name": self.report_name, "model": self.model}
|
||||||
% (self.report_name, self.model)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
Export = self.env["xlsx.export"]
|
Export = self.env["xlsx.export"]
|
||||||
return Export.export_xlsx(xlsx_template, self.model, docids[0])
|
return Export.export_xlsx(xlsx_template, self.model, docids[0])
|
||||||
|
@ -40,5 +38,4 @@ class ReportAction(models.Model):
|
||||||
("report_type", "in", qwebtypes),
|
("report_type", "in", qwebtypes),
|
||||||
("report_name", "=", report_name),
|
("report_name", "=", report_name),
|
||||||
]
|
]
|
||||||
context = self.env["res.users"].context_get()
|
return report_obj.search(conditions, limit=1)
|
||||||
return report_obj.with_context(context).search(conditions, limit=1)
|
|
||||||
|
|
|
@ -129,7 +129,7 @@ class XLSXExport(models.AbstractModel):
|
||||||
self._fill_head(ws, st, record)
|
self._fill_head(ws, st, record)
|
||||||
self._fill_lines(ws, st, record)
|
self._fill_lines(ws, st, record)
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
raise ValidationError(_("Key Error\n%s") % e)
|
raise ValidationError(_("Key Error\n%s") % e) from e
|
||||||
except IllegalCharacterError as e:
|
except IllegalCharacterError as e:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
_(
|
_(
|
||||||
|
@ -137,9 +137,11 @@ class XLSXExport(models.AbstractModel):
|
||||||
"Some exporting data contain special character\n%s"
|
"Some exporting data contain special character\n%s"
|
||||||
)
|
)
|
||||||
% e
|
% e
|
||||||
)
|
) from e
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValidationError(_("Error filling data into Excel sheets\n%s") % e)
|
raise ValidationError(
|
||||||
|
_("Error filling data into Excel sheets\n%s") % e
|
||||||
|
) from e
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _get_field_data(self, _field, _line):
|
def _get_field_data(self, _field, _line):
|
||||||
|
@ -235,7 +237,7 @@ class XLSXExport(models.AbstractModel):
|
||||||
out_file = template.datas
|
out_file = template.datas
|
||||||
return (out_file, out_name)
|
return (out_file, out_name)
|
||||||
# Prepare temp file (from now, only xlsx file works for openpyxl)
|
# Prepare temp file (from now, only xlsx file works for openpyxl)
|
||||||
decoded_data = base64.decodestring(template.datas)
|
decoded_data = base64.decodebytes(template.datas)
|
||||||
ConfParam = self.env["ir.config_parameter"].sudo()
|
ConfParam = self.env["ir.config_parameter"].sudo()
|
||||||
ptemp = ConfParam.get_param("path_temp_file") or "/tmp"
|
ptemp = ConfParam.get_param("path_temp_file") or "/tmp"
|
||||||
stamp = dt.utcnow().strftime("%H%M%S%f")[:-3]
|
stamp = dt.utcnow().strftime("%H%M%S%f")[:-3]
|
||||||
|
|
|
@ -50,7 +50,7 @@ class XLSXImport(models.AbstractModel):
|
||||||
ModelData.create(
|
ModelData.create(
|
||||||
{
|
{
|
||||||
"name": "{}_{}".format(record._table, record.id),
|
"name": "{}_{}".format(record._table, record.id),
|
||||||
"module": "excel_import_export",
|
"module": "__excel_import_export__",
|
||||||
"model": record._name,
|
"model": record._name,
|
||||||
"res_id": record.id,
|
"res_id": record.id,
|
||||||
}
|
}
|
||||||
|
@ -68,10 +68,10 @@ class XLSXImport(models.AbstractModel):
|
||||||
record = record[f]
|
record = record[f]
|
||||||
else:
|
else:
|
||||||
return field_type
|
return field_type
|
||||||
except Exception:
|
except Exception as exc:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
_("Invalid declaration, %s has no valid field type") % field
|
_("Invalid declaration, %s has no valid field type") % field
|
||||||
)
|
) from exc
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _delete_record_data(self, record, data_dict):
|
def _delete_record_data(self, record, data_dict):
|
||||||
|
@ -93,7 +93,7 @@ class XLSXImport(models.AbstractModel):
|
||||||
new_fv = data_dict[s].pop(f)
|
new_fv = data_dict[s].pop(f)
|
||||||
data_dict[s][f.replace("_NODEL_", "")] = new_fv
|
data_dict[s][f.replace("_NODEL_", "")] = new_fv
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValidationError(_("Error deleting data\n%s") % e)
|
raise ValidationError(_("Error deleting data\n%s") % e) from e
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _get_end_row(self, st, worksheet, line_field):
|
def _get_end_row(self, st, worksheet, line_field):
|
||||||
|
@ -161,12 +161,11 @@ class XLSXImport(models.AbstractModel):
|
||||||
rc, key_eval_cond = co.get_field_condition(rc)
|
rc, key_eval_cond = co.get_field_condition(rc)
|
||||||
field, val_eval_cond = co.get_field_condition(field)
|
field, val_eval_cond = co.get_field_condition(field)
|
||||||
field_type = self._get_field_type(model, field)
|
field_type = self._get_field_type(model, field)
|
||||||
value = False
|
|
||||||
try:
|
try:
|
||||||
row, col = co.pos2idx(rc)
|
row, col = co.pos2idx(rc)
|
||||||
value = co._get_cell_value(st.cell(row, col), field_type=field_type)
|
value = co._get_cell_value(st.cell(row, col), field_type=field_type)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
value = False
|
||||||
eval_context = self.get_eval_context(model=model, value=value)
|
eval_context = self.get_eval_context(model=model, value=value)
|
||||||
if key_eval_cond:
|
if key_eval_cond:
|
||||||
value = str(safe_eval(key_eval_cond, eval_context))
|
value = str(safe_eval(key_eval_cond, eval_context))
|
||||||
|
@ -206,7 +205,7 @@ class XLSXImport(models.AbstractModel):
|
||||||
xml_id = (
|
xml_id = (
|
||||||
record
|
record
|
||||||
and self.get_external_id(record)
|
and self.get_external_id(record)
|
||||||
or "{}.{}".format("xls", uuid.uuid4())
|
or "{}.{}".format("__excel_import_export__", uuid.uuid4())
|
||||||
)
|
)
|
||||||
out_st.write(0, 0, "id") # id and xml_id on first column
|
out_st.write(0, 0, "id") # id and xml_id on first column
|
||||||
out_st.write(1, 0, xml_id)
|
out_st.write(1, 0, xml_id)
|
||||||
|
@ -228,11 +227,11 @@ class XLSXImport(models.AbstractModel):
|
||||||
"file_name": "temp.xls",
|
"file_name": "temp.xls",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
errors = imp.do(
|
errors = imp.execute_import(
|
||||||
header_fields,
|
header_fields,
|
||||||
header_fields,
|
header_fields,
|
||||||
{
|
{
|
||||||
"headers": True,
|
"has_headers": True,
|
||||||
"advanced": True,
|
"advanced": True,
|
||||||
"keep_matches": False,
|
"keep_matches": False,
|
||||||
"encoding": "",
|
"encoding": "",
|
||||||
|
@ -254,10 +253,10 @@ class XLSXImport(models.AbstractModel):
|
||||||
message = ", ".join([x["message"] for x in messages])
|
message = ", ".join([x["message"] for x in messages])
|
||||||
raise ValidationError(message.encode("utf-8"))
|
raise ValidationError(message.encode("utf-8"))
|
||||||
return self.env.ref(xml_id)
|
return self.env.ref(xml_id)
|
||||||
except xlrd.XLRDError:
|
except xlrd.XLRDError as exc:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
_("Invalid file style, only .xls or .xlsx file allowed")
|
_("Invalid file style, only .xls or .xlsx file allowed")
|
||||||
)
|
) from exc
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
@ -272,7 +271,7 @@ class XLSXImport(models.AbstractModel):
|
||||||
eval_context = {"object": record}
|
eval_context = {"object": record}
|
||||||
safe_eval(code, eval_context)
|
safe_eval(code, eval_context)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValidationError(_("Post import operation error\n%s") % e)
|
raise ValidationError(_("Post import operation error\n%s") % e) from e
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def import_xlsx(self, import_file, template, res_model=False, res_id=False):
|
def import_xlsx(self, import_file, template, res_model=False, res_id=False):
|
||||||
|
|
|
@ -35,13 +35,12 @@ class XLSXTemplate(models.Model):
|
||||||
help="Multiple template of same model, can belong to same group,\n"
|
help="Multiple template of same model, can belong to same group,\n"
|
||||||
"result in multiple template selection",
|
"result in multiple template selection",
|
||||||
)
|
)
|
||||||
description = fields.Char(string="Description")
|
description = fields.Char()
|
||||||
input_instruction = fields.Text(
|
input_instruction = fields.Text(
|
||||||
string="Instruction (Input)",
|
string="Instruction (Input)",
|
||||||
help="This is used to construct instruction in tab Import/Export",
|
help="This is used to construct instruction in tab Import/Export",
|
||||||
)
|
)
|
||||||
instruction = fields.Text(
|
instruction = fields.Text(
|
||||||
string="Instruction",
|
|
||||||
compute="_compute_output_instruction",
|
compute="_compute_output_instruction",
|
||||||
help="Instruction on how to import/export, prepared by system.",
|
help="Instruction on how to import/export, prepared by system.",
|
||||||
)
|
)
|
||||||
|
@ -538,18 +537,14 @@ class XLSXTemplateImport(models.Model):
|
||||||
ondelete="cascade",
|
ondelete="cascade",
|
||||||
readonly=True,
|
readonly=True,
|
||||||
)
|
)
|
||||||
sequence = fields.Integer(string="Sequence", default=10)
|
sequence = fields.Integer(default=10)
|
||||||
sheet = fields.Char(string="Sheet")
|
sheet = fields.Char()
|
||||||
section_type = fields.Selection(
|
section_type = fields.Selection(
|
||||||
[("sheet", "Sheet"), ("head", "Head"), ("row", "Row"), ("data", "Data")],
|
[("sheet", "Sheet"), ("head", "Head"), ("row", "Row"), ("data", "Data")],
|
||||||
string="Section Type",
|
|
||||||
required=True,
|
required=True,
|
||||||
)
|
)
|
||||||
row_field = fields.Char(
|
row_field = fields.Char(help="If section type is row, this field is required")
|
||||||
string="Row Field", help="If section type is row, this field is required"
|
|
||||||
)
|
|
||||||
no_delete = fields.Boolean(
|
no_delete = fields.Boolean(
|
||||||
string="No Delete",
|
|
||||||
default=False,
|
default=False,
|
||||||
help="By default, all rows will be deleted before import.\n"
|
help="By default, all rows will be deleted before import.\n"
|
||||||
"Select No Delete, otherwise",
|
"Select No Delete, otherwise",
|
||||||
|
@ -584,16 +579,13 @@ class XLSXTemplateExport(models.Model):
|
||||||
ondelete="cascade",
|
ondelete="cascade",
|
||||||
readonly=True,
|
readonly=True,
|
||||||
)
|
)
|
||||||
sequence = fields.Integer(string="Sequence", default=10)
|
sequence = fields.Integer(default=10)
|
||||||
sheet = fields.Char(string="Sheet")
|
sheet = fields.Char()
|
||||||
section_type = fields.Selection(
|
section_type = fields.Selection(
|
||||||
[("sheet", "Sheet"), ("head", "Head"), ("row", "Row"), ("data", "Data")],
|
[("sheet", "Sheet"), ("head", "Head"), ("row", "Row"), ("data", "Data")],
|
||||||
string="Section Type",
|
|
||||||
required=True,
|
required=True,
|
||||||
)
|
)
|
||||||
row_field = fields.Char(
|
row_field = fields.Char(help="If section type is row, this field is required")
|
||||||
string="Row Field", help="If section type is row, this field is required"
|
|
||||||
)
|
|
||||||
is_cont = fields.Boolean(
|
is_cont = fields.Boolean(
|
||||||
string="Continue", default=False, help="Continue data rows after last data row"
|
string="Continue", default=False, help="Continue data rows after last data row"
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
/** @odoo-module **/
|
||||||
|
|
||||||
|
import {download} from "@web/core/network/download";
|
||||||
|
import {registry} from "@web/core/registry";
|
||||||
|
|
||||||
|
function getReportUrl({report_name, context, data}, env) {
|
||||||
|
// Rough copy of action_service.js _getReportUrl method.
|
||||||
|
let url = `/report/excel/${report_name}`;
|
||||||
|
const actionContext = context || {};
|
||||||
|
if (data && JSON.stringify(data) !== "{}") {
|
||||||
|
const encodedOptions = encodeURIComponent(JSON.stringify(data));
|
||||||
|
const encodedContext = encodeURIComponent(JSON.stringify(actionContext));
|
||||||
|
return `${url}?options=${encodedOptions}&context=${encodedContext}`;
|
||||||
|
}
|
||||||
|
if (actionContext.active_ids) {
|
||||||
|
url += `/${actionContext.active_ids.join(",")}`;
|
||||||
|
}
|
||||||
|
const userContext = encodeURIComponent(JSON.stringify(env.services.user.context));
|
||||||
|
return `${url}?context=${userContext}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function triggerDownload(action, {onClose}, env) {
|
||||||
|
// Rough copy of action_service.js _triggerDownload method.
|
||||||
|
env.services.ui.block();
|
||||||
|
try {
|
||||||
|
await download({
|
||||||
|
url: "/report/download",
|
||||||
|
data: {
|
||||||
|
data: JSON.stringify([getReportUrl(action, env), "excel"]),
|
||||||
|
context: JSON.stringify(env.services.user.context),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
env.services.ui.unblock();
|
||||||
|
}
|
||||||
|
if (action.close_on_report_download) {
|
||||||
|
return env.services.action.doAction(
|
||||||
|
{type: "ir.actions.act_window_close"},
|
||||||
|
{onClose}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (onClose) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registry
|
||||||
|
.category("ir.actions.report handlers")
|
||||||
|
.add("excel_handler", async function (action, options, env) {
|
||||||
|
if (action.report_type === "excel") {
|
||||||
|
await triggerDownload(action, options, env);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
|
@ -1,98 +0,0 @@
|
||||||
// © 2017 Creu Blanca
|
|
||||||
// License AGPL-3.0 or later (https://www.gnuorg/licenses/agpl.html).
|
|
||||||
odoo.define("excel_import_export.report", function (require) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
var core = require("web.core");
|
|
||||||
var ActionManager = require("web.ActionManager");
|
|
||||||
var framework = require("web.framework");
|
|
||||||
var session = require("web.session");
|
|
||||||
var _t = core._t;
|
|
||||||
|
|
||||||
ActionManager.include({
|
|
||||||
_downloadReportExcel: function (url, actions) {
|
|
||||||
var self = this;
|
|
||||||
framework.blockUI();
|
|
||||||
var type = "excel";
|
|
||||||
var cloned_action = _.clone(actions);
|
|
||||||
var new_url = url;
|
|
||||||
|
|
||||||
if (
|
|
||||||
_.isUndefined(cloned_action.data) ||
|
|
||||||
_.isNull(cloned_action.data) ||
|
|
||||||
(_.isObject(cloned_action.data) && _.isEmpty(cloned_action.data))
|
|
||||||
) {
|
|
||||||
if (cloned_action.context.active_ids) {
|
|
||||||
new_url += "/" + cloned_action.context.active_ids.join(",");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
new_url +=
|
|
||||||
"?options=" +
|
|
||||||
encodeURIComponent(JSON.stringify(cloned_action.data));
|
|
||||||
new_url +=
|
|
||||||
"&context=" +
|
|
||||||
encodeURIComponent(JSON.stringify(cloned_action.context));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise(function (resolve, reject) {
|
|
||||||
var blocked = !session.get_file({
|
|
||||||
url: new_url,
|
|
||||||
data: {
|
|
||||||
data: JSON.stringify([new_url, type]),
|
|
||||||
},
|
|
||||||
success: resolve,
|
|
||||||
error: (error) => {
|
|
||||||
self.call("crash_manager", "rpc_error", error);
|
|
||||||
reject();
|
|
||||||
},
|
|
||||||
complete: framework.unblockUI,
|
|
||||||
});
|
|
||||||
if (blocked) {
|
|
||||||
// AAB: this check should be done in get_file service directly,
|
|
||||||
// should not be the concern of the caller (and that way, get_file
|
|
||||||
// could return a deferred)
|
|
||||||
var message = _t(
|
|
||||||
"A popup window with your report was blocked. You " +
|
|
||||||
"may need to change your browser settings to allow " +
|
|
||||||
"popup windows for this page."
|
|
||||||
);
|
|
||||||
this.do_warn(_t("Warning"), message, true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
_triggerDownload: function (action, options, type) {
|
|
||||||
var self = this;
|
|
||||||
var reportUrls = this._makeReportUrls(action);
|
|
||||||
if (type === "excel") {
|
|
||||||
return this._downloadReportExcel(reportUrls[type], action).then(
|
|
||||||
function () {
|
|
||||||
if (action.close_on_report_download) {
|
|
||||||
var closeAction = {type: "ir.actions.act_window_close"};
|
|
||||||
return self.doAction(
|
|
||||||
closeAction,
|
|
||||||
_.pick(options, "on_close")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return options.on_close();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return this._super.apply(this, arguments);
|
|
||||||
},
|
|
||||||
|
|
||||||
_makeReportUrls: function (action) {
|
|
||||||
var reportUrls = this._super.apply(this, arguments);
|
|
||||||
reportUrls.excel = "/report/excel/" + action.report_name;
|
|
||||||
return reportUrls;
|
|
||||||
},
|
|
||||||
|
|
||||||
_executeReportAction: function (action, options) {
|
|
||||||
var self = this;
|
|
||||||
if (action.report_type === "excel") {
|
|
||||||
return self._triggerDownload(action, options, "excel");
|
|
||||||
}
|
|
||||||
return this._super.apply(this, arguments);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,14 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
|
||||||
<!--
|
|
||||||
Copyright 2019 Ecosoft Co., Ltd.
|
|
||||||
License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).-->
|
|
||||||
<odoo>
|
|
||||||
<template id="assets_backend" inherit_id="web.assets_backend">
|
|
||||||
<xpath expr="." position="inside">
|
|
||||||
<script
|
|
||||||
type="text/javascript"
|
|
||||||
src="/excel_import_export/static/src/js/report/action_manager_report.js"
|
|
||||||
/>
|
|
||||||
</xpath>
|
|
||||||
</template>
|
|
||||||
</odoo>
|
|
|
@ -6,7 +6,7 @@
|
||||||
<record id="view_xlsx_template_tree" model="ir.ui.view">
|
<record id="view_xlsx_template_tree" model="ir.ui.view">
|
||||||
<field name="model">xlsx.template</field>
|
<field name="model">xlsx.template</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<tree string="XLSX Template">
|
<tree>
|
||||||
<field name="name" />
|
<field name="name" />
|
||||||
</tree>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
|
|
|
@ -98,8 +98,11 @@ class ImportXLSXWizard(models.TransientModel):
|
||||||
if not template.datas:
|
if not template.datas:
|
||||||
act = self.env.ref("excel_import_export.action_xlsx_template")
|
act = self.env.ref("excel_import_export.action_xlsx_template")
|
||||||
raise RedirectWarning(
|
raise RedirectWarning(
|
||||||
_('File "%s" not found in template, %s.')
|
_(
|
||||||
% (template.fname, template.name),
|
'File "%(fname)s" not found in template, %(name)s.',
|
||||||
|
fname=template.fname,
|
||||||
|
name=template.name,
|
||||||
|
),
|
||||||
act.id,
|
act.id,
|
||||||
_("Set Templates"),
|
_("Set Templates"),
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "Excel Import/Export/Report Demo",
|
"name": "Excel Import/Export/Report Demo",
|
||||||
"version": "14.0.1.0.0",
|
"version": "15.0.1.0.0",
|
||||||
"author": "Ecosoft,Odoo Community Association (OCA)",
|
"author": "Ecosoft,Odoo Community Association (OCA)",
|
||||||
"license": "AGPL-3",
|
"license": "AGPL-3",
|
||||||
"website": "https://github.com/OCA/server-tools",
|
"website": "https://github.com/OCA/server-tools",
|
||||||
|
|
|
@ -11,7 +11,6 @@ class ReportPartnerList(models.TransientModel):
|
||||||
partner_ids = fields.Many2many(comodel_name="res.partner")
|
partner_ids = fields.Many2many(comodel_name="res.partner")
|
||||||
results = fields.Many2many(
|
results = fields.Many2many(
|
||||||
"res.partner",
|
"res.partner",
|
||||||
string="Results",
|
|
||||||
compute="_compute_results",
|
compute="_compute_results",
|
||||||
help="Use compute fields, so there is nothing store in database",
|
help="Use compute fields, so there is nothing store in database",
|
||||||
)
|
)
|
||||||
|
|
|
@ -7,9 +7,10 @@
|
||||||
<field name="report_type">excel</field>
|
<field name="report_type">excel</field>
|
||||||
<field name="report_name">sale_order_form.xlsx</field>
|
<field name="report_name">sale_order_form.xlsx</field>
|
||||||
<field name="report_file">sale_order</field>
|
<field name="report_file">sale_order</field>
|
||||||
|
<field name="binding_model_id" ref="sale.model_sale_order" />
|
||||||
|
<field name="binding_type">report</field>
|
||||||
<field
|
<field
|
||||||
name="print_report_name"
|
name="print_report_name"
|
||||||
>(object.state in ('draft', 'sent') and 'Quotation - %s' % (object.name)) or 'Order - %s' % (object.name)</field>
|
>(object.state in ('draft', 'sent') and 'Quotation - %s' % (object.name)) or 'Order - %s' % (object.name)</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|
|
@ -39,7 +39,7 @@ class ReportCRMLead(models.TransientModel):
|
||||||
domain += [("team_id", "=", self.team_id.id)]
|
domain += [("team_id", "=", self.team_id.id)]
|
||||||
results = self.env["crm.lead"].read_group(
|
results = self.env["crm.lead"].read_group(
|
||||||
domain,
|
domain,
|
||||||
["country_id", "planned_revenue"],
|
["country_id", "expected_revenue"],
|
||||||
["country_id"],
|
["country_id"],
|
||||||
orderby="country_id",
|
orderby="country_id",
|
||||||
)
|
)
|
||||||
|
@ -47,7 +47,7 @@ class ReportCRMLead(models.TransientModel):
|
||||||
self.revenue_by_country += self.env["crm.lead"].new(
|
self.revenue_by_country += self.env["crm.lead"].new(
|
||||||
{
|
{
|
||||||
"country_id": row["country_id"],
|
"country_id": row["country_id"],
|
||||||
"planned_revenue": row["planned_revenue"],
|
"expected_revenue": row["expected_revenue"],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -57,9 +57,9 @@ class ReportCRMLead(models.TransientModel):
|
||||||
if self.team_id:
|
if self.team_id:
|
||||||
domain += [("team_id", "=", self.team_id.id)]
|
domain += [("team_id", "=", self.team_id.id)]
|
||||||
results = self.env["crm.lead"].read_group(
|
results = self.env["crm.lead"].read_group(
|
||||||
domain, ["team_id", "planned_revenue"], ["team_id"], orderby="team_id"
|
domain, ["team_id", "expected_revenue"], ["team_id"], orderby="team_id"
|
||||||
)
|
)
|
||||||
for row in results:
|
for row in results:
|
||||||
self.revenue_by_team += self.env["crm.lead"].new(
|
self.revenue_by_team += self.env["crm.lead"].new(
|
||||||
{"team_id": row["team_id"], "planned_revenue": row["planned_revenue"]}
|
{"team_id": row["team_id"], "expected_revenue": row["expected_revenue"]}
|
||||||
)
|
)
|
||||||
|
|
|
@ -19,20 +19,20 @@
|
||||||
'D4': 'activity_date_deadline${value or ""}#{style=date}',
|
'D4': 'activity_date_deadline${value or ""}#{style=date}',
|
||||||
'E4': 'activity_summary',
|
'E4': 'activity_summary',
|
||||||
'F4': 'stage_id.name',
|
'F4': 'stage_id.name',
|
||||||
'G4': 'planned_revenue${value or 0}#{style=number}',
|
'G4': 'expected_revenue${value or 0}#{style=number}',
|
||||||
'H4': 'team_id.name',
|
'H4': 'team_id.name',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'revenue_by_country': {
|
'revenue_by_country': {
|
||||||
'revenue_by_country': {
|
'revenue_by_country': {
|
||||||
'A3': 'country_id.name',
|
'A3': 'country_id.name',
|
||||||
'B3': 'planned_revenue${value or 0}#{style=number}'
|
'B3': 'expected_revenue${value or 0}#{style=number}'
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'revenue_by_team': {
|
'revenue_by_team': {
|
||||||
'revenue_by_team': {
|
'revenue_by_team': {
|
||||||
'A3': 'team_id.name',
|
'A3': 'team_id.name',
|
||||||
'B3': 'planned_revenue${value or 0}#{style=number}'
|
'B3': 'expected_revenue${value or 0}#{style=number}'
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -14,7 +14,6 @@ class ReportSaleOrder(models.TransientModel):
|
||||||
# Report Result, sale.order
|
# Report Result, sale.order
|
||||||
results = fields.Many2many(
|
results = fields.Many2many(
|
||||||
"sale.order",
|
"sale.order",
|
||||||
string="Results",
|
|
||||||
compute="_compute_results",
|
compute="_compute_results",
|
||||||
help="Use compute fields, so there is nothing stored in database",
|
help="Use compute fields, so there is nothing stored in database",
|
||||||
)
|
)
|
||||||
|
|
|
@ -24,7 +24,7 @@ class TestXLSXImportExport(TestExcelImportExport):
|
||||||
("gname", "=", False),
|
("gname", "=", False),
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
f = Form(self.env["export.xlsx.wizard"].with_context(ctx))
|
f = Form(self.env["export.xlsx.wizard"].with_context(**ctx))
|
||||||
export_wizard = f.save()
|
export_wizard = f.save()
|
||||||
# Test whether it loads correct template
|
# Test whether it loads correct template
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
@ -47,7 +47,7 @@ class TestXLSXImportExport(TestExcelImportExport):
|
||||||
],
|
],
|
||||||
"template_context": {"state": "draft"},
|
"template_context": {"state": "draft"},
|
||||||
}
|
}
|
||||||
with Form(self.env["import.xlsx.wizard"].with_context(ctx)) as f:
|
with Form(self.env["import.xlsx.wizard"].with_context(**ctx)) as f:
|
||||||
f.import_file = self.export_file
|
f.import_file = self.export_file
|
||||||
import_wizard = f.save()
|
import_wizard = f.save()
|
||||||
# Test sample template
|
# Test sample template
|
||||||
|
|
|
@ -21,7 +21,7 @@ class TestXLSXReport(TestExcelImportExport):
|
||||||
("gname", "=", False),
|
("gname", "=", False),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
with Form(self.env["report.sale.order"].with_context(ctx)) as f:
|
with Form(self.env["report.sale.order"].with_context(**ctx)) as f:
|
||||||
f.partner_id = self.partner
|
f.partner_id = self.partner
|
||||||
report_wizard = f.save()
|
report_wizard = f.save()
|
||||||
# Test whether it loads correct template
|
# Test whether it loads correct template
|
||||||
|
|
|
@ -84,7 +84,7 @@ class TestXLSXTemplate(TestExcelImportExport):
|
||||||
self.assertTrue(template.report_menu_id)
|
self.assertTrue(template.report_menu_id)
|
||||||
res = template.report_menu_id.action.read()[0]
|
res = template.report_menu_id.action.read()[0]
|
||||||
ctx = literal_eval(res["context"])
|
ctx = literal_eval(res["context"])
|
||||||
f = Form(self.env[res["res_model"]].with_context(ctx))
|
f = Form(self.env[res["res_model"]].with_context(**ctx))
|
||||||
report_wizard = f.save()
|
report_wizard = f.save()
|
||||||
res = report_wizard.action_report()
|
res = report_wizard.action_report()
|
||||||
# Finally reture the report action
|
# Finally reture the report action
|
||||||
|
|
Loading…
Reference in New Issue