[15.0][MIG] excel_import_export, excel_import_export_demo: Migration

pull/2329/head
Mantux11 2022-12-29 12:01:01 +00:00
parent fa45e3289a
commit f94ba4fc5c
23 changed files with 180 additions and 1674 deletions

View File

@ -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"
]
},
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -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]

View File

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

View File

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

View File

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

View File

@ -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);
},
});
});

View File

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

View File

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

View File

@ -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"),
) )

View File

@ -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",

View File

@ -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",
) )

View File

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

View File

@ -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"]}
) )

View File

@ -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}'
}, },
}, },
}, },

View File

@ -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",
) )

View File

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

View File

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

View File

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