Merge PR #2505 into 16.0

Signed-off-by thomaspaulb
pull/2657/head
OCA-git-bot 2023-04-18 14:55:50 +00:00
commit 58475f0239
91 changed files with 10366 additions and 0 deletions

View File

@ -0,0 +1,195 @@
==========================
Excel Import/Export/Report
==========================
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--tools-lightgray.png?logo=github
:target: https://github.com/OCA/server-tools/tree/16.0/excel_import_export
:alt: OCA/server-tools
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/server-tools-16-0/server-tools-16-0-excel_import_export
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
:target: https://runbot.odoo-community.org/runbot/149/16.0
:alt: Try me on Runbot
|badge1| |badge2| |badge3| |badge4| |badge5|
The module provide pre-built functions and wizards for developer to build excel import / export / report with ease.
Without having to code to create excel file, developer do,
- Create menu, action, wizard, model, view a normal Odoo development.
- Design excel template using standard Excel application, e.g., colors, fonts, formulas, etc.
- Instruct how the data will be located in Excel with simple dictionary instruction or from Odoo UI.
- Odoo will combine instruction with excel template, and result in final excel file.
**Table of contents**
.. contents::
:local:
Installation
============
To install this module, you need to install following python library, **xlrd, xlwt, openpyxl**.
Then, simply install **excel_import_export**.
For demo, install **excel_import_export_demo**
Usage
=====
Concepts
~~~~~~~~
This module contain pre-defined function and wizards to make exporting, importing and reporting easy.
At the heart of this module, there are 2 `main methods`
- ``self.env['xlsx.export'].export_xlsx(...)``
- ``self.env['xlsx.import'].import_xlsx(...)``
For reporting, also call `export_xlsx(...)` but through following method
- ``self.env['xslx.report'].report_xlsx(...)``
After install this module, go to Settings > Excel Import/Export > XLSX Templates, this is where the key component located.
As this module provide tools, it is best to explain as use cases. For example use cases, please install **excel_import_export_demo**
Use Cases
~~~~~~~~~
**Use Case 1:** Export/Import Excel on existing document
This add export/import action menus in existing document (example - excel_import_export_demo/import_export_sale_order)
1. Create export action menu on document, <act_window> with res_model="export.xlsx.wizard" and src_model="<document_model>", and context['template_domain'] to locate the right template -- actions.xml
2. Create import action menu on document, <act_window> with res_model="import.xlsx.wizard" and src_model="<document_model>", and context['template_domain'] to locate the right template -- action.xml
3. Create/Design Excel Template File (.xlsx), in the template, name the underlining tab used for export/import -- <file>.xlsx
4. Create instruction dictionary for export/import in xlsx.template model -- templates.xml
**Use Case 2:** Import Excel Files
With menu wizard to create new documents (example - excel_import_export_demo/import_sale_orders)
1. Create report menu with search wizard, res_model="import.xlsx.wizard" and context['template_domain'] to locate the right template -- menu_action.xml
2. Create Excel Template File (.xlsx), in the template, name the underlining tab used for import -- <import file>.xlsx
3. Create instruction dictionary for import in xlsx.template model -- templates.xml
**Use Case 3:** Create Excel Report
This create report menu with criteria wizard. (example - excel_import_export_demo/report_sale_order)
1. Create report's menu, action, and add context['template_domain'] to locate the right template for this report -- <report>.xml
2. Create report's wizard for search criteria. The view inherits ``excel_import_export.xlsx_report_view`` and mode="primary". In this view, you only need to add criteria fields, the rest will reuse from interited view -- <report.xml>
3. Create report model as models.Transient, then define search criteria fields, and get reporing data into ``results`` field -- <report>.py
4. Create/Design Excel Template File (.xlsx), in the template, name the underlining tab used for report results -- <report_file>.xlsx
5. Create instruction dictionary for report in xlsx.template model -- templates.xml
**Note:**
Another option for reporting is to use report action (report_type='excel'), I.e.,
.. code-block:: xml
<report id='action_report_saleorder_excel'
string='Quotation / Order (.xlsx)'
model='sale.order'
name='sale_order.xlsx'
file='sale_order'
report_type='excel'
/>
By using report action, Odoo will find template using combination of model and name, then do the export for the underlining record.
Please see example in excel_import_export_demo/report_action, which shows,
1. Print excel from an active sale.order
2. Run partner list report based on search criteria.
Easy Reporting Option
~~~~~~~~~~~~~~~~~~~~~
Technically, this option is the same as "Create Excel Report" use case. But instead of having to write XML / Python code like normally do,
this option allow user to create a report based on a model or view, all by configuration only.
1. Goto > Technical> Excel Import/Export > XLSX Templates, and create a new template for a report.
2. On the new template, select "Easy Reporting" option, then select followings
- Report Model, this can be data model or data view we want to get the results from.
- Click upload your file and add the excel template (.xlsx)
- Click Save, system will create sample export line, user can add more fields according to results model.
3. Click Add Report Menu, the report menu will be created, user can change its location. Now the report is ready to use.
.. figure:: https://raw.githubusercontent.com/OCA/server-tools/16.0/excel_import_export/static/description/xlsx_template.png
:width: 800 px
Note: Using easy reporting mode, system will used a common criteria wizard.
.. figure:: https://raw.githubusercontent.com/OCA/server-tools/16.0/excel_import_export/static/description/common_wizard.png
:width: 800 px
Known issues / Roadmap
======================
- Module extension e.g., excel_import_export_async, that add ability to execute as async process.
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-tools/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
`feedback <https://github.com/OCA/server-tools/issues/new?body=module:%20excel_import_export%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
~~~~~~~
* Ecosoft
Contributors
~~~~~~~~~~~~
* Kitti Upariphutthiphong. <kittiu@gmail.com> (http://ecosoft.co.th)
* Saran Lim. <saranl@ecosoft.co.th> (http://ecosoft.co.th)
Maintainers
~~~~~~~~~~~
This module is maintained by the OCA.
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
.. |maintainer-kittiu| image:: https://github.com/kittiu.png?size=40px
:target: https://github.com/kittiu
:alt: kittiu
Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:
|maintainer-kittiu|
This module is part of the `OCA/server-tools <https://github.com/OCA/server-tools/tree/16.0/excel_import_export>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@ -0,0 +1,6 @@
# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
from . import wizard
from . import models
from . import controllers

View File

@ -0,0 +1,30 @@
# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
{
"name": "Excel Import/Export/Report",
"summary": "Base module for developing Excel import/export/report",
"version": "16.0.1.0.0",
"author": "Ecosoft,Odoo Community Association (OCA)",
"license": "AGPL-3",
"website": "https://github.com/OCA/server-tools",
"category": "Tools",
"depends": ["mail"],
"external_dependencies": {"python": ["openpyxl"]},
"data": [
"security/ir.model.access.csv",
"wizard/export_xlsx_wizard.xml",
"wizard/import_xlsx_wizard.xml",
"wizard/report_xlsx_wizard.xml",
"views/xlsx_template_view.xml",
"views/xlsx_report.xml",
],
"installable": True,
"development_status": "Beta",
"maintainers": ["kittiu"],
"assets": {
"web.assets_backend": [
"/excel_import_export/static/src/js/report/action_manager_report.esm.js"
]
},
}

View File

@ -0,0 +1,4 @@
# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
from . import report

View File

@ -0,0 +1,94 @@
# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
import base64
import json
import logging
from werkzeug.urls import url_decode
from odoo import http
from odoo.http import content_disposition, request, route, serialize_exception
from odoo.tools import html_escape
from odoo.tools.safe_eval import safe_eval, time
from odoo.addons.web.controllers import report
_logger = logging.getLogger(__name__)
class ReportController(report.ReportController):
@route()
def report_routes(self, reportname, docids=None, converter=None, **data):
if converter == "excel":
report = request.env["ir.actions.report"]._get_report_from_name(reportname)
context = dict(request.env.context)
if docids:
docids = [int(i) for i in docids.split(",")]
if data.get("options"):
data.update(json.loads(data.pop("options")))
if data.get("context"):
# Ignore 'lang' here, because the context in data is the one
# from the webclient *but* if the user explicitely wants to
# change the lang, this mechanism overwrites it.
data["context"] = json.loads(data["context"])
if data["context"].get("lang"):
del data["context"]["lang"]
context.update(data["context"])
excel, report_name = report.with_context(**context)._render_excel(
docids, data=data
)
excel = base64.decodebytes(excel)
if docids:
records = request.env[report.model].browse(docids)
if report.print_report_name and not len(records) > 1:
# this is a bad idea, this should only be .xlsx
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 = [
(
"Content-Type",
"application/vnd.openxmlformats-"
"officedocument.spreadsheetml.sheet",
),
("Content-Length", len(excel)),
("Content-Disposition", content_disposition(report_name)),
]
return request.make_response(excel, headers=excelhttpheaders)
return super().report_routes(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("/")
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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
from . import styles
from . import common
from . import xlsx_export
from . import xlsx_import
from . import xlsx_template
from . import xlsx_report
from . import ir_report
#
#
# INSERT INTO "purchase_order_line" (
# "id", "create_uid", "create_date",
# "write_uid", "write_date", "date_planned",
# "display_type", "name", "order_id",
# "price_unit", "product_qty", "product_uom",
# "sequence") VALUES (
# nextval('purchase_order_line_id_seq'), 2, (now() at time zone 'UTC'),
# 2, (now() at time zone 'UTC'), '2020-10-05 09:39:28',
# NULL, '[FURN_0269] Office Chair Black', 8,
# '11111.00', '5.000', 1,
# 10)

View File

@ -0,0 +1,335 @@
# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
import csv
import itertools
import re
import string
import uuid
from ast import literal_eval
from datetime import datetime as dt
from io import StringIO
import xlrd
from dateutil.parser import parse
from odoo import _
from odoo.exceptions import ValidationError
def adjust_cell_formula(value, k):
"""Cell formula, i.e., if i=5, val=?(A11)+?(B12) -> val=A16+B17"""
if isinstance(value, str):
for i in range(value.count("?(")):
if value and "?(" in value and ")" in value:
i = value.index("?(")
j = value.index(")", i)
val = value[i + 2 : j]
col, row = split_row_col(val)
new_val = "{}{}".format(col, row + k)
value = value.replace("?(%s)" % val, new_val)
return value
def get_field_aggregation(field):
"""i..e, 'field@{sum}'"""
if field and "@{" in field and "}" in field:
i = field.index("@{")
j = field.index("}", i)
cond = field[i + 2 : j]
try:
if cond or cond == "":
return (field[:i], cond)
except Exception:
return (field.replace("@{%s}" % cond, ""), False)
return (field, False)
def get_field_condition(field):
"""i..e, 'field${value > 0 and value or False}'"""
if field and "${" in field and "}" in field:
i = field.index("${")
j = field.index("}", i)
cond = field[i + 2 : j]
try:
if cond or cond == "":
return (field.replace("${%s}" % cond, ""), cond)
except Exception:
return (field, False)
return (field, False)
def get_field_style(field):
"""
Available styles
- font = bold, bold_red
- fill = red, blue, yellow, green, grey
- align = left, center, right
- number = true, false
i.e., 'field#{font=bold;fill=red;align=center;style=number}'
"""
if field and "#{" in field and "}" in field:
i = field.index("#{")
j = field.index("}", i)
cond = field[i + 2 : j]
try:
if cond or cond == "":
return (field.replace("#{%s}" % cond, ""), cond)
except Exception:
return (field, False)
return (field, False)
def get_field_style_cond(field):
"""i..e, 'field#?object.partner_id and #{font=bold} or #{}?'"""
if field and "#?" in field and "?" in field:
i = field.index("#?")
j = field.index("?", i + 2)
cond = field[i + 2 : j]
try:
if cond or cond == "":
return (field.replace("#?%s?" % cond, ""), cond)
except Exception:
return (field, False)
return (field, False)
def fill_cell_style(field, field_style, styles):
field_styles = field_style.split(";") if field_style else []
for f in field_styles:
(key, value) = f.split("=")
if key not in styles.keys():
raise ValidationError(_("Invalid style type %s") % key)
if value.lower() not in styles[key].keys():
raise ValidationError(
_("Invalid value %(value)s for style type %(key)s")
% {"value": value, "key": key}
)
cell_style = styles[key][value]
if key == "font":
field.font = cell_style
if key == "fill":
field.fill = cell_style
if key == "align":
field.alignment = cell_style
if key == "style":
if value == "text":
try:
# In case value can't be encoded as utf, we do normal str()
field.value = field.value.encode("utf-8")
except Exception:
field.value = str(field.value)
field.number_format = cell_style
def get_line_max(line_field):
"""i.e., line_field = line_ids[100], max = 100 else 0"""
if line_field and "[" in line_field and "]" in line_field:
i = line_field.index("[")
j = line_field.index("]")
max_str = line_field[i + 1 : j]
try:
if len(max_str) > 0:
return (line_field[:i], int(max_str))
else:
return (line_field, False)
except Exception:
return (line_field, False)
return (line_field, False)
def get_groupby(line_field):
"""i.e., line_field = line_ids["a_id, b_id"], groupby = ["a_id", "b_id"]"""
if line_field and "[" in line_field and "]" in line_field:
i = line_field.index("[")
j = line_field.index("]")
groupby = literal_eval(line_field[i : j + 1])
return groupby
return False
def split_row_col(pos):
match = re.match(r"([a-z]+)([0-9]+)", pos, re.I)
if not match:
raise ValidationError(_("Position %s is not valid") % pos)
col, row = match.groups()
return col, int(row)
def openpyxl_get_sheet_by_name(book, name):
"""Get sheet by name for openpyxl"""
i = 0
for sheetname in book.sheetnames:
if sheetname == name:
return book.worksheets[i]
i += 1
raise ValidationError(_("'%s' sheet not found") % (name,))
def xlrd_get_sheet_by_name(book, name):
try:
for idx in itertools.count():
sheet = book.sheet_by_index(idx)
if sheet.name == name:
return sheet
except IndexError as exc:
raise ValidationError(_("'%s' sheet not found") % (name,)) from exc
def isfloat(input_val):
try:
float(input_val)
return True
except ValueError:
return False
def isinteger(input_val):
try:
int(input_val)
return True
except ValueError:
return False
def isdatetime(input_val):
try:
if len(input_val) == 10:
dt.strptime(input_val, "%Y-%m-%d")
elif len(input_val) == 19:
dt.strptime(input_val, "%Y-%m-%d %H:%M:%S")
else:
return False
return True
except ValueError:
return False
def str_to_number(input_val):
if isinstance(input_val, str):
if " " not in input_val:
if isdatetime(input_val):
return parse(input_val)
elif isinteger(input_val):
if not (len(input_val) > 1 and input_val[:1] == "0"):
return int(input_val)
elif isfloat(input_val):
if not (input_val.find(".") > 2 and input_val[:1] == "0"):
return float(input_val)
return input_val
def csv_from_excel(excel_content, delimiter, quote):
wb = xlrd.open_workbook(file_contents=excel_content)
sh = wb.sheet_by_index(0)
content = StringIO()
quoting = csv.QUOTE_ALL
if not quote:
quoting = csv.QUOTE_NONE
if delimiter == " " and quoting == csv.QUOTE_NONE:
quoting = csv.QUOTE_MINIMAL
wr = csv.writer(content, delimiter=delimiter, quoting=quoting)
for rownum in range(sh.nrows):
row = []
for x in sh.row_values(rownum):
if quoting == csv.QUOTE_NONE and delimiter in x:
raise ValidationError(
_(
"Template with CSV Quoting = False, data must not "
'contain the same char as delimiter -> "%s"'
)
% delimiter
)
row.append(x)
wr.writerow(row)
content.seek(0) # Set index to 0, and start reading
out_file = content.getvalue().encode("utf-8")
return out_file
def pos2idx(pos):
match = re.match(r"([a-z]+)([0-9]+)", pos, re.I)
if not match:
raise ValidationError(_("Position %s is not valid") % (pos,))
col, row = match.groups()
col_num = 0
for c in col:
if c in string.ascii_letters:
col_num = col_num * 26 + (ord(c.upper()) - ord("A")) + 1
return (int(row) - 1, col_num - 1)
def _get_cell_value(cell, field_type=False):
"""If Odoo's field type is known, convert to valid string for import,
if not know, just get value as is"""
value = False
datemode = 0 # From book.datemode, but we fix it for simplicity
if field_type in ["date", "datetime"]:
ctype = xlrd.sheet.ctype_text.get(cell.ctype, "unknown type")
if ctype in ("xldate", "number"):
is_datetime = cell.value % 1 != 0.0
time_tuple = xlrd.xldate_as_tuple(cell.value, datemode)
date = dt(*time_tuple)
value = (
date.strftime("%Y-%m-%d %H:%M:%S")
if is_datetime
else date.strftime("%Y-%m-%d")
)
else:
value = cell.value
elif field_type in ["integer", "float"]:
value_str = str(cell.value).strip().replace(",", "")
if len(value_str) == 0:
value = ""
elif value_str.replace(".", "", 1).isdigit(): # Is number
if field_type == "integer":
value = int(float(value_str))
elif field_type == "float":
value = float(value_str)
else: # Is string, no conversion
value = value_str
elif field_type in ["many2one"]:
# If number, change to string
if isinstance(cell.value, (int, float, complex)):
value = str(cell.value)
else:
value = cell.value
else: # text, char
value = cell.value
# If string, cleanup
if isinstance(value, str):
if value[-2:] == ".0":
value = value[:-2]
# Except boolean, when no value, we should return as ''
if field_type not in ["boolean"]:
if not value:
value = ""
return value
def _add_column(column_name, column_value, file_txt):
i = 0
txt_lines = []
for line in file_txt.split("\n"):
if line and i == 0:
line = '"' + str(column_name) + '",' + line
elif line:
line = '"' + str(column_value) + '",' + line
txt_lines.append(line)
i += 1
file_txt = "\n".join(txt_lines)
return file_txt
def _add_id_column(file_txt):
i = 0
txt_lines = []
for line in file_txt.split("\n"):
if line and i == 0:
line = '"id",' + line
elif line:
line = f"__excel_import_export__.{uuid.uuid4()},{line}"
txt_lines.append(line)
i += 1
file_txt = "\n".join(txt_lines)
return file_txt

View File

@ -0,0 +1,42 @@
# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
from odoo import _, api, fields, models
from odoo.exceptions import UserError
class ReportAction(models.Model):
_inherit = "ir.actions.report"
report_type = fields.Selection(
selection_add=[("excel", "Excel")], ondelete={"excel": "cascade"}
)
@api.model
def _render_excel(self, docids, data):
if len(docids) != 1:
raise UserError(_("Only one id is allowed for excel_import_export"))
xlsx_template = self.env["xlsx.template"].search(
[("fname", "=", self.report_name), ("res_model", "=", self.model)]
)
if not xlsx_template or len(xlsx_template) != 1:
raise UserError(
_("Template %(report_name)s on model %(model)s is not unique!")
% {"report_name": self.report_name, "model": self.model}
)
Export = self.env["xlsx.export"]
return Export.export_xlsx(xlsx_template, self.model, docids[0])
@api.model
def _get_report_from_name(self, report_name):
res = super(ReportAction, self)._get_report_from_name(report_name)
if res:
return res
report_obj = self.env["ir.actions.report"]
qwebtypes = ["excel"]
conditions = [
("report_type", "in", qwebtypes),
("report_name", "=", report_name),
]
context = self.env["res.users"].context_get()
return report_obj.with_context(**context).search(conditions, limit=1)

View File

@ -0,0 +1,47 @@
# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
import logging
from odoo import api, models
_logger = logging.getLogger(__name__)
try:
from openpyxl.styles import Alignment, Font, PatternFill
except ImportError:
_logger.debug('Cannot import "openpyxl". Please make sure it is installed.')
class XLSXStyles(models.AbstractModel):
_name = "xlsx.styles"
_description = "Available styles for excel"
@api.model
def get_openpyxl_styles(self):
"""List all syles that can be used with styleing directive #{...}"""
return {
"font": {
"bold": Font(name="Arial", size=10, bold=True),
"bold_red": Font(name="Arial", size=10, color="FF0000", bold=True),
},
"fill": {
"red": PatternFill("solid", fgColor="FF0000"),
"grey": PatternFill("solid", fgColor="DDDDDD"),
"yellow": PatternFill("solid", fgColor="FFFCB7"),
"blue": PatternFill("solid", fgColor="9BF3FF"),
"green": PatternFill("solid", fgColor="B0FF99"),
},
"align": {
"left": Alignment(horizontal="left"),
"center": Alignment(horizontal="center"),
"right": Alignment(horizontal="right"),
},
"style": {
"number": "#,##0.00",
"date": "dd/mm/yyyy",
"datestamp": "yyyy-mm-dd",
"text": "@",
"percent": "0.00%",
},
}

View File

@ -0,0 +1,291 @@
# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
import base64
import logging
import os
import zipfile
from datetime import date, datetime as dt
from io import BytesIO
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
from odoo.tools.float_utils import float_compare
from odoo.tools.safe_eval import safe_eval
from . import common as co
_logger = logging.getLogger(__name__)
try:
from openpyxl import load_workbook
from openpyxl.utils.exceptions import IllegalCharacterError
except ImportError:
_logger.debug('Cannot import "openpyxl". Please make sure it is installed.')
class XLSXExport(models.AbstractModel):
_name = "xlsx.export"
_description = "Excel Export AbstractModel"
@api.model
def get_eval_context(self, model, record, value):
eval_context = {
"float_compare": float_compare,
"datetime": dt,
"date": date,
"value": value,
"object": record,
"model": self.env[model],
"env": self.env,
"context": self._context,
}
return eval_context
@api.model
def _get_line_vals(self, record, line_field, fields):
"""Get values of this field from record set and return as dict of vals
- record: main object
- line_field: rows object, i.e., line_ids
- fields: fields in line_ids, i.e., partner_id.display_name
"""
line_field, max_row = co.get_line_max(line_field)
line_field = line_field.replace("_CONT_", "") # Remove _CONT_ if any
line_field = line_field.replace("_EXTEND_", "") # Remove _EXTEND_ if any
lines = record[line_field]
if max_row > 0 and len(lines) > max_row:
raise Exception(_("Records in %s exceed max records allowed") % line_field)
vals = {field: [] for field in fields} # value and do_style
# Get field condition & aggre function
field_cond_dict = {}
aggre_func_dict = {}
field_style_dict = {}
style_cond_dict = {}
pair_fields = [] # I.e., ('debit${value and . or .}@{sum}', 'debit')
for field in fields:
temp_field, eval_cond = co.get_field_condition(field)
eval_cond = eval_cond or 'value or ""'
temp_field, field_style = co.get_field_style(temp_field)
temp_field, style_cond = co.get_field_style_cond(temp_field)
raw_field, aggre_func = co.get_field_aggregation(temp_field)
# Dict of all special conditions
field_cond_dict.update({field: eval_cond})
aggre_func_dict.update({field: aggre_func})
field_style_dict.update({field: field_style})
style_cond_dict.update({field: style_cond})
# --
pair_fields.append((field, raw_field))
for line in lines:
for field in pair_fields: # (field, raw_field)
value = self._get_field_data(field[1], line)
eval_cond = field_cond_dict[field[0]]
eval_context = self.get_eval_context(line._name, line, value)
if eval_cond:
value = safe_eval(eval_cond, eval_context)
# style w/Cond takes priority
style_cond = style_cond_dict[field[0]]
style = self._eval_style_cond(line._name, line, value, style_cond)
if style is None:
style = False # No style
elif style is False:
style = field_style_dict[field[0]] # Use default style
vals[field[0]].append((value, style))
return (vals, aggre_func_dict)
@api.model
def _eval_style_cond(self, model, record, value, style_cond):
eval_context = self.get_eval_context(model, record, value)
field = style_cond = style_cond or "#??"
styles = {}
for i in range(style_cond.count("#{")):
i += 1
field, style = co.get_field_style(field)
styles.update({i: style})
style_cond = style_cond.replace("#{%s}" % style, str(i))
if not styles:
return False
res = safe_eval(style_cond, eval_context)
if res is None or res is False:
return res
return styles[res]
@api.model
def _fill_workbook_data(self, workbook, record, data_dict):
"""Fill data from record with style in data_dict to workbook"""
if not record or not data_dict:
return
try:
for sheet_name in data_dict:
ws = data_dict[sheet_name]
st = False
if isinstance(sheet_name, str):
st = co.openpyxl_get_sheet_by_name(workbook, sheet_name)
elif isinstance(sheet_name, int):
if sheet_name > len(workbook.worksheets):
raise Exception(_("Not enough worksheets"))
st = workbook.worksheets[sheet_name - 1]
if not st:
raise ValidationError(_("Sheet %s not found") % sheet_name)
# Fill data, header and rows
self._fill_head(ws, st, record)
self._fill_lines(ws, st, record)
except KeyError as e:
raise ValidationError(_("Key Error\n%s") % e) from e
except IllegalCharacterError as e:
raise ValidationError(
_(
"IllegalCharacterError\n"
"Some exporting data contain special character\n%s"
)
% e
) from e
except Exception as e:
raise ValidationError(
_("Error filling data into Excel sheets\n%s") % e
) from e
@api.model
def _get_field_data(self, _field, _line):
"""Get field data, and convert data type if needed"""
if not _field:
return None
line_copy = _line
for f in _field.split("."):
line_copy = line_copy[f]
if isinstance(line_copy, str):
line_copy = line_copy.encode("utf-8")
return line_copy
@api.model
def _fill_head(self, ws, st, record):
for rc, field in ws.get("_HEAD_", {}).items():
tmp_field, eval_cond = co.get_field_condition(field)
eval_cond = eval_cond or 'value or ""'
tmp_field, field_style = co.get_field_style(tmp_field)
tmp_field, style_cond = co.get_field_style_cond(tmp_field)
value = tmp_field and self._get_field_data(tmp_field, record)
# Eval
eval_context = self.get_eval_context(record._name, record, value)
if eval_cond:
value = safe_eval(eval_cond, eval_context)
if value is not None:
st[rc] = value
fc = not style_cond and True or safe_eval(style_cond, eval_context)
if field_style and fc: # has style and pass style_cond
styles = self.env["xlsx.styles"].get_openpyxl_styles()
co.fill_cell_style(st[rc], field_style, styles)
@api.model
def _fill_lines(self, ws, st, record):
line_fields = list(ws)
if "_HEAD_" in line_fields:
line_fields.remove("_HEAD_")
cont_row = 0 # last data row to continue
for line_field in line_fields:
fields = ws.get(line_field, {}).values()
vals, func = self._get_line_vals(record, line_field, fields)
is_cont = "_CONT_" in line_field and True or False # continue row
is_extend = "_EXTEND_" in line_field and True or False # extend row
cont_set = 0
rows_inserted = False # flag to insert row
for rc, field in ws.get(line_field, {}).items():
col, row = co.split_row_col(rc) # starting point
# Case continue, start from the last data row
if is_cont and not cont_set: # only once per line_field
cont_set = cont_row + 1
if is_cont:
row = cont_set
rc = "{}{}".format(col, cont_set)
i = 0
new_row = 0
new_rc = False
row_count = len(vals[field])
# Insert rows to preserve total line
if is_extend and not rows_inserted:
rows_inserted = True
st.insert_rows(row + 1, row_count - 1)
# --
for (row_val, style) in vals[field]:
new_row = row + i
new_rc = "{}{}".format(col, new_row)
row_val = co.adjust_cell_formula(row_val, i)
if row_val not in ("None", None):
st[new_rc] = co.str_to_number(row_val)
if style:
styles = self.env["xlsx.styles"].get_openpyxl_styles()
co.fill_cell_style(st[new_rc], style, styles)
i += 1
# Add footer line if at least one field have sum
f = func.get(field, False)
if f and new_row > 0:
new_row += 1
f_rc = "{}{}".format(col, new_row)
st[f_rc] = "={}({}:{})".format(f, rc, new_rc)
styles = self.env["xlsx.styles"].get_openpyxl_styles()
co.fill_cell_style(st[f_rc], style, styles)
cont_row = cont_row < new_row and new_row or cont_row
return
@api.model
def export_xlsx(self, template, res_model, res_ids):
if template.res_model != res_model:
raise ValidationError(_("Template's model mismatch"))
data_dict = co.literal_eval(template.instruction.strip())
export_dict = data_dict.get("__EXPORT__", False)
out_name = template.name
if not export_dict: # If there is not __EXPORT__ formula, just export
out_name = template.fname
out_file = template.datas
return (out_file, out_name)
# Prepare temp file (from now, only xlsx file works for openpyxl)
decoded_data = base64.decodebytes(template.datas)
ConfParam = self.env["ir.config_parameter"].sudo()
ptemp = ConfParam.get_param("path_temp_file") or "/tmp"
stamp = dt.utcnow().strftime("%H%M%S%f")[:-3]
ftemp = "{}/temp{}.xlsx".format(ptemp, stamp)
# Start working with workbook
records = res_model and self.env[res_model].browse(res_ids) or False
outputs = []
for record in records:
f = open(ftemp, "wb")
f.write(decoded_data)
f.seek(0)
f.close()
# Workbook created, temp file removed
wb = load_workbook(ftemp)
os.remove(ftemp)
self._fill_workbook_data(wb, record, export_dict)
# Return file as .xlsx
content = BytesIO()
wb.save(content)
content.seek(0) # Set index to 0, and start reading
out_file = content.read()
if record and "name" in record and record.name:
out_name = record.name.replace(" ", "").replace("/", "")
else:
fname = out_name.replace(" ", "").replace("/", "")
ts = fields.Datetime.context_timestamp(self, dt.now())
out_name = "{}_{}".format(fname, ts.strftime("%Y%m%d_%H%M%S"))
if not out_name or len(out_name) == 0:
out_name = "noname"
out_ext = "xlsx"
# CSV (convert only on 1st sheet)
if template.to_csv:
delimiter = template.csv_delimiter
out_file = co.csv_from_excel(out_file, delimiter, template.csv_quote)
out_ext = template.csv_extension
outputs.append((out_file, "{}.{}".format(out_name, out_ext)))
# If outputs > 1 files, zip it
if len(outputs) > 1:
zip_buffer = BytesIO()
with zipfile.ZipFile(
zip_buffer, "a", zipfile.ZIP_DEFLATED, False
) as zip_file:
for data, file_name in outputs:
zip_file.writestr(file_name, data)
zip_buffer.seek(0)
out_file = base64.encodebytes(zip_buffer.read())
out_name = "files.zip"
return (out_file, out_name)
else:
(out_file, out_name) = outputs[0]
return (base64.encodebytes(out_file), out_name)

View File

@ -0,0 +1,300 @@
# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
import base64
import uuid
from ast import literal_eval
from datetime import date, datetime as dt
from io import BytesIO
import xlrd
import xlwt
from odoo import _, api, models
from odoo.exceptions import ValidationError
from odoo.tools.float_utils import float_compare
from odoo.tools.safe_eval import safe_eval
from . import common as co
class XLSXImport(models.AbstractModel):
_name = "xlsx.import"
_description = "Excel Import AbstractModel"
@api.model
def get_eval_context(self, model=False, value=False):
eval_context = {
"float_compare": float_compare,
"datetime": dt,
"date": date,
"env": self.env,
"context": self._context,
"value": False,
"model": False,
}
if model:
eval_context.update({"model": self.env[model]})
if value:
if isinstance(value, str): # Remove non Ord 128 character
value = "".join([i if ord(i) < 128 else " " for i in value])
eval_context.update({"value": value})
return eval_context
@api.model
def get_external_id(self, record):
"""Get external ID of the record, if not already exists create one"""
ModelData = self.env["ir.model.data"]
xml_id = record.get_external_id()
if not xml_id or (record.id in xml_id and xml_id[record.id] == ""):
ModelData.create(
{
"name": "{}_{}".format(record._table, record.id),
"module": "__excel_import_export__",
"model": record._name,
"res_id": record.id,
}
)
xml_id = record.get_external_id()
return xml_id[record.id]
@api.model
def _get_field_type(self, model, field):
try:
record = self.env[model].new()
for f in field.split("/"):
field_type = record._fields[f].type
if field_type in ("one2many", "many2many"):
record = record[f]
else:
return field_type
except Exception as exc:
raise ValidationError(
_("Invalid declaration, %s has no valid field type") % field
) from exc
@api.model
def _delete_record_data(self, record, data_dict):
"""If no _NODEL_, delete existing lines before importing"""
if not record or not data_dict:
return
try:
for sheet_name in data_dict:
worksheet = data_dict[sheet_name]
line_fields = filter(lambda x: x != "_HEAD_", worksheet)
for line_field in line_fields:
if "_NODEL_" not in line_field:
if line_field in record and record[line_field]:
record[line_field].unlink()
# Remove _NODEL_ from dict
for s, _sv in data_dict.copy().items():
for f, _fv in data_dict[s].copy().items():
if "_NODEL_" in f:
new_fv = data_dict[s].pop(f)
data_dict[s][f.replace("_NODEL_", "")] = new_fv
except Exception as e:
raise ValidationError(_("Error deleting data\n%s") % e) from e
@api.model
def _get_end_row(self, st, worksheet, line_field):
"""Get max row or next empty row as the ending row"""
_x, max_row = co.get_line_max(line_field)
test_rows = {}
max_end_row = 0
for rc, _col in worksheet.get(line_field, {}).items():
rc, key_eval_cond = co.get_field_condition(rc)
row, col = co.pos2idx(rc)
# Use max_row, i.e., order_line[5], use it. Otherwise, use st.nrows
max_end_row = st.nrows if max_row is False else (row + max_row)
for idx in range(row, max_row and max_end_row or st.nrows):
cell_type = st.cell_type(idx, col) # empty type = 0
r_types = test_rows.get(idx, [])
r_types.append(cell_type)
test_rows[idx] = r_types
empty_list = filter(lambda y: all(i == 0 for i in y[1]), test_rows.items())
empty_rows = list(map(lambda z: z[0], empty_list))
next_empty_row = empty_rows and min(empty_rows) or max_end_row
return next_empty_row
@api.model
def _get_line_vals(self, st, worksheet, model, line_field):
"""Get values of this field from excel sheet"""
vals = {}
end_row = self._get_end_row(st, worksheet, line_field)
for rc, columns in worksheet.get(line_field, {}).items():
if not isinstance(columns, list):
columns = [columns]
for field in columns:
rc, key_eval_cond = co.get_field_condition(rc)
x_field, val_eval_cond = co.get_field_condition(field)
row, col = co.pos2idx(rc)
new_line_field, _x = co.get_line_max(line_field)
out_field = "{}/{}".format(new_line_field, x_field)
field_type = self._get_field_type(model, out_field)
vals.update({out_field: []})
for idx in range(row, end_row):
value = co._get_cell_value(st.cell(idx, col), field_type=field_type)
eval_context = self.get_eval_context(model=model, value=value)
if key_eval_cond:
value = safe_eval(key_eval_cond, eval_context)
if val_eval_cond:
value = safe_eval(val_eval_cond, eval_context)
vals[out_field].append(value)
if not filter(lambda x: x != "", vals[out_field]):
vals.pop(out_field)
return vals
@api.model
def _process_worksheet(self, wb, out_wb, out_st, model, data_dict, header_fields):
col_idx = 1
for sheet_name in data_dict: # For each Sheet
worksheet = data_dict[sheet_name]
st = False
if isinstance(sheet_name, str):
st = co.xlrd_get_sheet_by_name(wb, sheet_name)
elif isinstance(sheet_name, int):
st = wb.sheet_by_index(sheet_name - 1)
if not st:
raise ValidationError(_("Sheet %s not found") % sheet_name)
# HEAD updates
for rc, field in worksheet.get("_HEAD_", {}).items():
rc, key_eval_cond = co.get_field_condition(rc)
field, val_eval_cond = co.get_field_condition(field)
field_type = self._get_field_type(model, field)
try:
row, col = co.pos2idx(rc)
value = co._get_cell_value(st.cell(row, col), field_type=field_type)
except Exception:
value = False
eval_context = self.get_eval_context(model=model, value=value)
if key_eval_cond:
value = str(safe_eval(key_eval_cond, eval_context))
if val_eval_cond:
value = str(safe_eval(val_eval_cond, eval_context))
out_st.write(0, col_idx, field) # Next Column
out_st.write(1, col_idx, value) # Next Value
header_fields.append(field)
col_idx += 1
# Line Items
line_fields = filter(lambda x: x != "_HEAD_", worksheet)
for line_field in line_fields:
vals = self._get_line_vals(st, worksheet, model, line_field)
for field in vals:
# Columns, i.e., line_ids/field_id
out_st.write(0, col_idx, field)
header_fields.append(field)
# Data
i = 1
for value in vals[field]:
out_st.write(i, col_idx, value)
i += 1
col_idx += 1
@api.model
def _import_record_data(self, import_file, record, data_dict):
"""From complex excel, create temp simple excel and do import"""
if not data_dict:
return
try:
header_fields = []
model = record._name
decoded_data = base64.decodebytes(import_file)
wb = xlrd.open_workbook(file_contents=decoded_data)
out_wb = xlwt.Workbook()
out_st = out_wb.add_sheet("Sheet 1")
xml_id = (
record
and self.get_external_id(record)
or "{}.{}".format("__excel_import_export__", uuid.uuid4())
)
out_st.write(0, 0, "id") # id and xml_id on first column
out_st.write(1, 0, xml_id)
header_fields.append("id")
# Process on all worksheets
self._process_worksheet(wb, out_wb, out_st, model, data_dict, header_fields)
# --
content = BytesIO()
out_wb.save(content)
content.seek(0) # Set index to 0, and start reading
xls_file = content.read()
# Do the import
Import = self.env["base_import.import"]
imp = Import.create(
{
"res_model": model,
"file": xls_file,
"file_type": "application/vnd.ms-excel",
"file_name": "temp.xls",
}
)
errors = imp.execute_import(
header_fields,
header_fields,
{
"has_headers": True,
"advanced": True,
"keep_matches": False,
"encoding": "",
"separator": "",
"quoting": '"',
"date_format": "%Y-%m-%d",
"datetime_format": "%Y-%m-%d %H:%M:%S",
"float_thousand_separator": ",",
"float_decimal_separator": ".",
"fields": [],
},
)
if errors.get("messages"):
message = _("Error importing data")
messages = errors["messages"]
if isinstance(messages, dict):
message = messages["message"]
if isinstance(messages, list):
message = ", ".join([x["message"] for x in messages])
raise ValidationError(message.encode("utf-8"))
return self.env.ref(xml_id)
except xlrd.XLRDError as exc:
raise ValidationError(
_("Invalid file style, only .xls or .xlsx file allowed")
) from exc
except Exception as e:
raise e
@api.model
def _post_import_operation(self, record, operation):
"""Run python code after import"""
if not record or not operation:
return
try:
if "${" in operation:
code = (operation.split("${"))[1].split("}")[0]
eval_context = {"object": record}
safe_eval(code, eval_context)
except Exception as e:
raise ValidationError(_("Post import operation error\n%s") % e) from e
@api.model
def import_xlsx(self, import_file, template, res_model=False, res_id=False):
"""
- If res_id = False, we want to create new document first
- Delete fields' data according to data_dict['__IMPORT__']
- Import data from excel according to data_dict['__IMPORT__']
"""
self = self.sudo()
if res_model and template.res_model != res_model:
raise ValidationError(_("Template's model mismatch"))
record = self.env[template.res_model].browse(res_id)
data_dict = literal_eval(template.instruction.strip())
if not data_dict.get("__IMPORT__"):
raise ValidationError(
_("No data_dict['__IMPORT__'] in template %s") % template.name
)
if record:
# Delete existing data first
self._delete_record_data(record, data_dict["__IMPORT__"])
# Fill up record with data from excel sheets
record = self._import_record_data(import_file, record, data_dict["__IMPORT__"])
# Post Import Operation, i.e., cleanup some data
if data_dict.get("__POST_IMPORT__", False):
self._post_import_operation(record, data_dict["__POST_IMPORT__"])
return record

View File

@ -0,0 +1,56 @@
# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
class XLSXReport(models.AbstractModel):
"""Common class for xlsx reporting wizard"""
_name = "xlsx.report"
_description = "Excel Report AbstractModel"
name = fields.Char(string="File Name", readonly=True, size=500)
data = fields.Binary(string="File", readonly=True)
template_id = fields.Many2one(
"xlsx.template",
string="Template",
required=True,
ondelete="cascade",
domain=lambda self: self._context.get("template_domain", []),
)
choose_template = fields.Boolean(string="Allow Choose Template", default=False)
state = fields.Selection(
[("choose", "Choose"), ("get", "Get")],
default="choose",
help="* Choose: wizard show in user selection mode"
"\n* Get: wizard show results from user action",
)
@api.model
def default_get(self, fields):
template_domain = self._context.get("template_domain", [])
templates = self.env["xlsx.template"].search(template_domain)
if not templates:
raise ValidationError(_("No template found"))
defaults = super(XLSXReport, self).default_get(fields)
for template in templates:
if not template.datas:
raise ValidationError(_("No file in %s") % (template.name,))
defaults["template_id"] = len(templates) == 1 and templates.id or False
return defaults
def report_xlsx(self):
self.ensure_one()
Export = self.env["xlsx.export"]
out_file, out_name = Export.export_xlsx(self.template_id, self._name, self.id)
self.write({"state": "get", "data": out_file, "name": out_name})
return {
"type": "ir.actions.act_window",
"res_model": self._name,
"view_mode": "form",
"res_id": self.id,
"views": [(False, "form")],
"target": "new",
}

View File

@ -0,0 +1,629 @@
# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
import base64
import os
from ast import literal_eval
from os.path import join as opj
from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError
from odoo.modules.module import get_module_path
from . import common as co
class XLSXTemplate(models.Model):
"""Master Data for XLSX Templates
- Excel Template
- Import/Export Meta Data (dict text)
- Default values, etc.
"""
_name = "xlsx.template"
_description = "Excel template file and instruction"
_order = "name"
name = fields.Char(string="Template Name", required=True)
res_model = fields.Char(
string="Resource Model",
help="The database object this attachment will be attached to.",
)
fname = fields.Char(string="File Name")
gname = fields.Char(
string="Group Name",
help="Multiple template of same model, can belong to same group,\n"
"result in multiple template selection",
)
description = fields.Char()
input_instruction = fields.Text(
string="Instruction (Input)",
help="This is used to construct instruction in tab Import/Export",
)
instruction = fields.Text(
compute="_compute_output_instruction",
help="Instruction on how to import/export, prepared by system.",
)
datas = fields.Binary(string="File Content")
to_csv = fields.Boolean(
string="Convert to CSV?",
default=False,
help="Convert file into CSV format on export",
)
csv_delimiter = fields.Char(
string="CSV Delimiter",
size=1,
default=",",
required=True,
help="Optional for CSV, default is comma.",
)
csv_extension = fields.Char(
string="CSV File Extension",
size=5,
default="csv",
required=True,
help="Optional for CSV, default is .csv",
)
csv_quote = fields.Boolean(
string="CSV Quoting",
default=True,
help="Optional for CSV, default is full quoting.",
)
export_ids = fields.One2many(
comodel_name="xlsx.template.export", inverse_name="template_id"
)
import_ids = fields.One2many(
comodel_name="xlsx.template.import", inverse_name="template_id"
)
post_import_hook = fields.Char(
string="Post Import Function Hook",
help="Call a function after successful import, i.e.,\n"
"${object.post_import_do_something()}",
)
show_instruction = fields.Boolean(
string="Show Output",
default=False,
help="This is the computed instruction based on tab Import/Export,\n"
"to be used by xlsx import/export engine",
)
redirect_action = fields.Many2one(
comodel_name="ir.actions.act_window",
string="Return Action",
domain=[("type", "=", "ir.actions.act_window")],
help="Optional action, redirection after finish import operation",
)
# Utilities
export_action_id = fields.Many2one(
comodel_name="ir.actions.act_window",
ondelete="set null",
)
import_action_id = fields.Many2one(
comodel_name="ir.actions.act_window",
ondelete="set null",
)
use_report_wizard = fields.Boolean(
string="Easy Reporting",
help="Use common report wizard model, instead of create specific model",
)
result_model_id = fields.Many2one(
comodel_name="ir.model",
string="Report Model",
help="When use commone wizard, choose the result model",
)
result_field = fields.Char(
compute="_compute_result_field",
)
report_menu_id = fields.Many2one(
comodel_name="ir.ui.menu",
string="Report Menu",
readonly=True,
)
report_action_id = fields.Many2one(
comodel_name="ir.actions.report",
string="Report Action",
)
def _compute_result_field(self):
for rec in self:
rec.result_field = (
("x_%s_results" % rec.id) if rec.result_model_id else False
)
@api.constrains("redirect_action", "res_model")
def _check_action_model(self):
for rec in self:
if (
rec.res_model
and rec.redirect_action
and rec.res_model != rec.redirect_action.res_model
):
raise ValidationError(
_("The selected redirect action is " "not for model %s")
% rec.res_model
)
@api.model
def load_xlsx_template(self, template_ids, addon=False):
for template in self.browse(template_ids):
if not addon:
addon = list(template.get_external_id().values())[0].split(".")[0]
addon_path = get_module_path(addon)
file_path = False
for root, _dirs, files in os.walk(addon_path):
for name in files:
if name == template.fname:
file_path = os.path.abspath(opj(root, name))
if file_path:
template.datas = base64.b64encode(open(file_path, "rb").read())
return True
@api.model_create_multi
def create(self, vals_list):
res = super().create(vals_list)
for rec in res:
if rec.input_instruction:
rec._compute_input_export_instruction()
rec._compute_input_import_instruction()
rec._compute_input_post_import_hook()
if rec.result_model_id:
rec._update_result_field_common_wizard()
rec._update_result_export_ids()
return res
def write(self, vals):
res = super().write(vals)
if vals.get("input_instruction"):
for rec in self:
rec._compute_input_export_instruction()
rec._compute_input_import_instruction()
rec._compute_input_post_import_hook()
if vals.get("result_model_id"):
for rec in self:
rec._update_result_field_common_wizard()
rec._update_result_export_ids()
return res
def unlink(self):
self.env["ir.model.fields"].search(
[
("model", "=", "report.xlsx.wizard"),
("name", "=", self.mapped("result_field")),
]
).unlink()
return super().unlink()
def _update_result_field_common_wizard(self):
self.ensure_one()
_model = self.env["ir.model"].search([("model", "=", "report.xlsx.wizard")])
_model.ensure_one()
_field = self.env["ir.model.fields"].search(
[("model", "=", "report.xlsx.wizard"), ("name", "=", self.result_field)]
)
if not _field:
_field = self.env["ir.model.fields"].create(
{
"model_id": _model.id,
"name": self.result_field,
"field_description": "Results",
"ttype": "many2many",
"relation": self.result_model_id.model,
"store": False,
"depends": "res_model",
}
)
else:
_field.ensure_one()
_field.write({"relation": self.result_model_id.model})
_field.compute = """
self['{}'] = self.env['{}'].search(self.safe_domain(self.domain))
""".format(
self.result_field,
self.result_model_id.model,
)
def _update_result_export_ids(self):
self.ensure_one()
results = self.env["xlsx.template.export"].search(
[("template_id", "=", self.id), ("row_field", "=", self.result_field)]
)
if not results:
self.export_ids.unlink()
self.write(
{
"export_ids": [
(0, 0, {"sequence": 10, "section_type": "sheet", "sheet": 1}),
(
0,
0,
{
"sequence": 20,
"section_type": "row",
"row_field": self.result_field,
},
),
(
0,
0,
{
"sequence": 30,
"section_type": "data",
"excel_cell": "A1",
"field_name": "id",
},
),
],
}
)
@api.onchange("use_report_wizard")
def _onchange_use_report_wizard(self):
self.res_model = "report.xlsx.wizard" if self.use_report_wizard else False
self.redirect_action = False
def _compute_input_export_instruction(self):
self = self.with_context(compute_from_input=True)
for rec in self:
# Export Instruction
input_dict = literal_eval(rec.input_instruction.strip())
rec.export_ids.unlink()
export_dict = input_dict.get("__EXPORT__")
if not export_dict:
continue
export_lines = []
sequence = 0
# Sheet
for sheet, rows in export_dict.items():
sequence += 1
vals = {
"sequence": sequence,
"section_type": "sheet",
"sheet": str(sheet),
}
export_lines.append((0, 0, vals))
# Rows
for row_field, lines in rows.items():
sequence += 1
is_cont = False
if "_CONT_" in row_field:
is_cont = True
row_field = row_field.replace("_CONT_", "")
is_extend = False
if "_EXTEND_" in row_field:
is_extend = True
row_field = row_field.replace("_EXTEND_", "")
vals = {
"sequence": sequence,
"section_type": (row_field == "_HEAD_" and "head" or "row"),
"row_field": row_field,
"is_cont": is_cont,
"is_extend": is_extend,
}
export_lines.append((0, 0, vals))
for excel_cell, field_name in lines.items():
sequence += 1
vals = {
"sequence": sequence,
"section_type": "data",
"excel_cell": excel_cell,
"field_name": field_name,
}
export_lines.append((0, 0, vals))
rec.write({"export_ids": export_lines})
def _compute_input_import_instruction(self):
self = self.with_context(compute_from_input=True)
for rec in self:
# Import Instruction
input_dict = literal_eval(rec.input_instruction.strip())
rec.import_ids.unlink()
import_dict = input_dict.get("__IMPORT__")
if not import_dict:
continue
import_lines = []
sequence = 0
# Sheet
for sheet, rows in import_dict.items():
sequence += 1
vals = {
"sequence": sequence,
"section_type": "sheet",
"sheet": str(sheet),
}
import_lines.append((0, 0, vals))
# Rows
for row_field, lines in rows.items():
sequence += 1
no_delete = False
if "_NODEL_" in row_field:
no_delete = True
row_field = row_field.replace("_NODEL_", "")
vals = {
"sequence": sequence,
"section_type": (row_field == "_HEAD_" and "head" or "row"),
"row_field": row_field,
"no_delete": no_delete,
}
import_lines.append((0, 0, vals))
for excel_cell, field_name in lines.items():
sequence += 1
vals = {
"sequence": sequence,
"section_type": "data",
"excel_cell": excel_cell,
"field_name": field_name,
}
import_lines.append((0, 0, vals))
rec.write({"import_ids": import_lines})
def _compute_input_post_import_hook(self):
self = self.with_context(compute_from_input=True)
for rec in self:
# Import Instruction
input_dict = literal_eval(rec.input_instruction.strip())
rec.post_import_hook = input_dict.get("__POST_IMPORT__")
def _compute_output_instruction(self):
"""From database, compute back to dictionary"""
for rec in self:
inst_dict = {}
prev_sheet = False
prev_row = False
# Export Instruction
itype = "__EXPORT__"
inst_dict[itype] = {}
for line in rec.export_ids:
if line.section_type == "sheet":
sheet = co.isinteger(line.sheet) and int(line.sheet) or line.sheet
sheet_dict = {sheet: {}}
inst_dict[itype].update(sheet_dict)
prev_sheet = sheet
continue
if line.section_type in ("head", "row"):
row_field = line.row_field
if line.section_type == "row" and line.is_cont:
row_field = "_CONT_%s" % row_field
if line.section_type == "row" and line.is_extend:
row_field = "_EXTEND_%s" % row_field
row_dict = {row_field: {}}
inst_dict[itype][prev_sheet].update(row_dict)
prev_row = row_field
continue
if line.section_type == "data":
excel_cell = line.excel_cell
field_name = line.field_name or ""
field_name += line.field_cond or ""
field_name += line.style or ""
field_name += line.style_cond or ""
if line.is_sum:
field_name += "@{sum}"
cell_dict = {excel_cell: field_name}
inst_dict[itype][prev_sheet][prev_row].update(cell_dict)
continue
# Import Instruction
itype = "__IMPORT__"
inst_dict[itype] = {}
for line in rec.import_ids:
if line.section_type == "sheet":
sheet = co.isinteger(line.sheet) and int(line.sheet) or line.sheet
sheet_dict = {sheet: {}}
inst_dict[itype].update(sheet_dict)
prev_sheet = sheet
continue
if line.section_type in ("head", "row"):
row_field = line.row_field
if line.section_type == "row" and line.no_delete:
row_field = "_NODEL_%s" % row_field
row_dict = {row_field: {}}
inst_dict[itype][prev_sheet].update(row_dict)
prev_row = row_field
continue
if line.section_type == "data":
excel_cell = line.excel_cell
field_name = line.field_name or ""
field_name += line.field_cond or ""
cell_dict = {excel_cell: field_name}
inst_dict[itype][prev_sheet][prev_row].update(cell_dict)
continue
itype = "__POST_IMPORT__"
inst_dict[itype] = False
if rec.post_import_hook:
inst_dict[itype] = rec.post_import_hook
rec.instruction = inst_dict
def add_export_action(self):
self.ensure_one()
vals = {
"name": "Export Excel",
"res_model": "export.xlsx.wizard",
"binding_model_id": self.env["ir.model"]
.search([("model", "=", self.res_model)])
.id,
"binding_type": "action",
"target": "new",
"view_mode": "form",
"context": """
{'template_domain': [('res_model', '=', '%s'),
('fname', '=', '%s'),
('gname', '=', False)]}
"""
% (self.res_model, self.fname),
}
action = self.env["ir.actions.act_window"].create(vals)
self.export_action_id = action
def remove_export_action(self):
self.ensure_one()
if self.export_action_id:
self.export_action_id.unlink()
def add_import_action(self):
self.ensure_one()
vals = {
"name": "Import Excel",
"res_model": "import.xlsx.wizard",
"binding_model_id": self.env["ir.model"]
.search([("model", "=", self.res_model)])
.id,
"binding_type": "action",
"target": "new",
"view_mode": "form",
"context": """
{'template_domain': [('res_model', '=', '%s'),
('fname', '=', '%s'),
('gname', '=', False)]}
"""
% (self.res_model, self.fname),
}
action = self.env["ir.actions.act_window"].create(vals)
self.import_action_id = action
def remove_import_action(self):
self.ensure_one()
if self.import_action_id:
self.import_action_id.unlink()
def add_report_menu(self):
self.ensure_one()
if not self.fname:
raise UserError(_("No file content!"))
# Create report action
vals = {
"name": self.name,
"report_type": "excel",
"model": "report.xlsx.wizard",
"report_name": self.fname,
"report_file": self.fname,
}
report_action = self.env["ir.actions.report"].create(vals)
self.report_action_id = report_action
# Create window action
vals = {
"name": self.name,
"res_model": "report.xlsx.wizard",
"binding_type": "action",
"target": "new",
"view_mode": "form",
"context": {
"report_action_id": report_action.id,
"default_res_model": self.result_model_id.model,
},
}
action = self.env["ir.actions.act_window"].create(vals)
# Create menu
vals = {
"name": self.name,
"action": "{},{}".format(action._name, action.id),
}
menu = self.env["ir.ui.menu"].create(vals)
self.report_menu_id = menu
def remove_report_menu(self):
self.ensure_one()
if self.report_action_id:
self.report_action_id.unlink()
if self.report_menu_id:
self.report_menu_id.action.unlink()
self.report_menu_id.unlink()
class XLSXTemplateImport(models.Model):
_name = "xlsx.template.import"
_description = "Detailed of how excel data will be imported"
_order = "sequence"
template_id = fields.Many2one(
comodel_name="xlsx.template",
string="XLSX Template",
index=True,
ondelete="cascade",
readonly=True,
)
sequence = fields.Integer(default=10)
sheet = fields.Char()
section_type = fields.Selection(
[("sheet", "Sheet"), ("head", "Head"), ("row", "Row"), ("data", "Data")],
required=True,
)
row_field = fields.Char(help="If section type is row, this field is required")
no_delete = fields.Boolean(
default=False,
help="By default, all rows will be deleted before import.\n"
"Select No Delete, otherwise",
)
excel_cell = fields.Char(string="Cell")
field_name = fields.Char(string="Field")
field_cond = fields.Char(string="Field Cond.")
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
vals = self._extract_field_name(vals)
return super().create(vals_list)
@api.model
def _extract_field_name(self, vals):
if self._context.get("compute_from_input") and vals.get("field_name"):
field_name, field_cond = co.get_field_condition(vals["field_name"])
field_cond = field_cond and "${%s}" % (field_cond or "") or False
vals.update({"field_name": field_name, "field_cond": field_cond})
return vals
class XLSXTemplateExport(models.Model):
_name = "xlsx.template.export"
_description = "Detailed of how excel data will be exported"
_order = "sequence"
template_id = fields.Many2one(
comodel_name="xlsx.template",
string="XLSX Template",
index=True,
ondelete="cascade",
readonly=True,
)
sequence = fields.Integer(default=10)
sheet = fields.Char()
section_type = fields.Selection(
[("sheet", "Sheet"), ("head", "Head"), ("row", "Row"), ("data", "Data")],
required=True,
)
row_field = fields.Char(help="If section type is row, this field is required")
is_cont = fields.Boolean(
string="Continue", default=False, help="Continue data rows after last data row"
)
is_extend = fields.Boolean(
string="Extend",
default=False,
help="Extend a blank row after filling each record, to extend the footer",
)
excel_cell = fields.Char(string="Cell")
field_name = fields.Char(string="Field")
field_cond = fields.Char(string="Field Cond.")
is_sum = fields.Boolean(string="Sum", default=False)
style = fields.Char(string="Default Style")
style_cond = fields.Char(string="Style w/Cond.")
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
vals = self._extract_field_name(vals)
return super().create(vals_list)
@api.model
def _extract_field_name(self, vals):
if self._context.get("compute_from_input") and vals.get("field_name"):
field_name, field_cond = co.get_field_condition(vals["field_name"])
field_cond = field_cond or 'value or ""'
field_name, style = co.get_field_style(field_name)
field_name, style_cond = co.get_field_style_cond(field_name)
field_name, func = co.get_field_aggregation(field_name)
vals.update(
{
"field_name": field_name,
"field_cond": "${%s}" % (field_cond or ""),
"style": "#{%s}" % (style or ""),
"style_cond": "#?%s?" % (style_cond or ""),
"is_sum": func == "sum" and True or False,
}
)
return vals

View File

@ -0,0 +1,2 @@
* Kitti Upariphutthiphong. <kittiu@gmail.com> (http://ecosoft.co.th)
* Saran Lim. <saranl@ecosoft.co.th> (http://ecosoft.co.th)

View File

@ -0,0 +1,8 @@
The module provide pre-built functions and wizards for developer to build excel import / export / report with ease.
Without having to code to create excel file, developer do,
- Create menu, action, wizard, model, view a normal Odoo development.
- Design excel template using standard Excel application, e.g., colors, fonts, formulas, etc.
- Instruct how the data will be located in Excel with simple dictionary instruction or from Odoo UI.
- Odoo will combine instruction with excel template, and result in final excel file.

View File

@ -0,0 +1,5 @@
To install this module, you need to install following python library, **xlrd, xlwt, openpyxl**.
Then, simply install **excel_import_export**.
For demo, install **excel_import_export_demo**

View File

@ -0,0 +1 @@
- Module extension e.g., excel_import_export_async, that add ability to execute as async process.

View File

@ -0,0 +1,88 @@
Concepts
~~~~~~~~
This module contain pre-defined function and wizards to make exporting, importing and reporting easy.
At the heart of this module, there are 2 `main methods`
- ``self.env['xlsx.export'].export_xlsx(...)``
- ``self.env['xlsx.import'].import_xlsx(...)``
For reporting, also call `export_xlsx(...)` but through following method
- ``self.env['xslx.report'].report_xlsx(...)``
After install this module, go to Settings > Excel Import/Export > XLSX Templates, this is where the key component located.
As this module provide tools, it is best to explain as use cases. For example use cases, please install **excel_import_export_demo**
Use Cases
~~~~~~~~~
**Use Case 1:** Export/Import Excel on existing document
This add export/import action menus in existing document (example - excel_import_export_demo/import_export_sale_order)
1. Create export action menu on document, <act_window> with res_model="export.xlsx.wizard" and src_model="<document_model>", and context['template_domain'] to locate the right template -- actions.xml
2. Create import action menu on document, <act_window> with res_model="import.xlsx.wizard" and src_model="<document_model>", and context['template_domain'] to locate the right template -- action.xml
3. Create/Design Excel Template File (.xlsx), in the template, name the underlining tab used for export/import -- <file>.xlsx
4. Create instruction dictionary for export/import in xlsx.template model -- templates.xml
**Use Case 2:** Import Excel Files
With menu wizard to create new documents (example - excel_import_export_demo/import_sale_orders)
1. Create report menu with search wizard, res_model="import.xlsx.wizard" and context['template_domain'] to locate the right template -- menu_action.xml
2. Create Excel Template File (.xlsx), in the template, name the underlining tab used for import -- <import file>.xlsx
3. Create instruction dictionary for import in xlsx.template model -- templates.xml
**Use Case 3:** Create Excel Report
This create report menu with criteria wizard. (example - excel_import_export_demo/report_sale_order)
1. Create report's menu, action, and add context['template_domain'] to locate the right template for this report -- <report>.xml
2. Create report's wizard for search criteria. The view inherits ``excel_import_export.xlsx_report_view`` and mode="primary". In this view, you only need to add criteria fields, the rest will reuse from interited view -- <report.xml>
3. Create report model as models.Transient, then define search criteria fields, and get reporing data into ``results`` field -- <report>.py
4. Create/Design Excel Template File (.xlsx), in the template, name the underlining tab used for report results -- <report_file>.xlsx
5. Create instruction dictionary for report in xlsx.template model -- templates.xml
**Note:**
Another option for reporting is to use report action (report_type='excel'), I.e.,
.. code-block:: xml
<report id='action_report_saleorder_excel'
string='Quotation / Order (.xlsx)'
model='sale.order'
name='sale_order.xlsx'
file='sale_order'
report_type='excel'
/>
By using report action, Odoo will find template using combination of model and name, then do the export for the underlining record.
Please see example in excel_import_export_demo/report_action, which shows,
1. Print excel from an active sale.order
2. Run partner list report based on search criteria.
Easy Reporting Option
~~~~~~~~~~~~~~~~~~~~~
Technically, this option is the same as "Create Excel Report" use case. But instead of having to write XML / Python code like normally do,
this option allow user to create a report based on a model or view, all by configuration only.
1. Goto > Technical> Excel Import/Export > XLSX Templates, and create a new template for a report.
2. On the new template, select "Easy Reporting" option, then select followings
- Report Model, this can be data model or data view we want to get the results from.
- Click upload your file and add the excel template (.xlsx)
- Click Save, system will create sample export line, user can add more fields according to results model.
3. Click Add Report Menu, the report menu will be created, user can change its location. Now the report is ready to use.
.. figure:: ../static/description/xlsx_template.png
:width: 800 px
Note: Using easy reporting mode, system will used a common criteria wizard.
.. figure:: ../static/description/common_wizard.png
:width: 800 px

View File

@ -0,0 +1,7 @@
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
xlsx_template_user,xlsx_template_user,model_xlsx_template,,1,1,1,1
xlsx_template_export_user,xlsx_template_export_user,model_xlsx_template_export,,1,1,1,1
xlsx_template_import_user,xlsx_template_import_user,model_xlsx_template_import,,1,1,1,1
access_export_xlsx_wizard,access_export_xlsx_wizard,model_export_xlsx_wizard,base.group_user,1,1,1,1
access_import_xlsx_wizard,access_import_xlsx_wizard,model_import_xlsx_wizard,base.group_user,1,1,1,1
access_report_xlsx_wizard,access_report_xlsx_wizard,model_report_xlsx_wizard,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 xlsx_template_user xlsx_template_user model_xlsx_template 1 1 1 1
3 xlsx_template_export_user xlsx_template_export_user model_xlsx_template_export 1 1 1 1
4 xlsx_template_import_user xlsx_template_import_user model_xlsx_template_import 1 1 1 1
5 access_export_xlsx_wizard access_export_xlsx_wizard model_export_xlsx_wizard base.group_user 1 1 1 1
6 access_import_xlsx_wizard access_import_xlsx_wizard model_import_xlsx_wizard base.group_user 1 1 1 1
7 access_report_xlsx_wizard access_report_xlsx_wizard model_report_xlsx_wizard base.group_user 1 1 1 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@ -0,0 +1,535 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: http://docutils.sourceforge.net/" />
<title>Excel Import/Export/Report</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="excel-import-export-report">
<h1 class="title">Excel Import/Export/Report</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/server-tools/tree/16.0/excel_import_export"><img alt="OCA/server-tools" src="https://img.shields.io/badge/github-OCA%2Fserver--tools-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/server-tools-16-0/server-tools-16-0-excel_import_export"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runbot.odoo-community.org/runbot/149/16.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p>
<p>The module provide pre-built functions and wizards for developer to build excel import / export / report with ease.</p>
<p>Without having to code to create excel file, developer do,</p>
<ul class="simple">
<li>Create menu, action, wizard, model, view a normal Odoo development.</li>
<li>Design excel template using standard Excel application, e.g., colors, fonts, formulas, etc.</li>
<li>Instruct how the data will be located in Excel with simple dictionary instruction or from Odoo UI.</li>
<li>Odoo will combine instruction with excel template, and result in final excel file.</li>
</ul>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#installation" id="id1">Installation</a></li>
<li><a class="reference internal" href="#usage" id="id2">Usage</a><ul>
<li><a class="reference internal" href="#concepts" id="id3">Concepts</a></li>
<li><a class="reference internal" href="#use-cases" id="id4">Use Cases</a></li>
<li><a class="reference internal" href="#easy-reporting-option" id="id5">Easy Reporting Option</a></li>
</ul>
</li>
<li><a class="reference internal" href="#known-issues-roadmap" id="id6">Known issues / Roadmap</a></li>
<li><a class="reference internal" href="#bug-tracker" id="id7">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id8">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id9">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id10">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="id11">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="installation">
<h1><a class="toc-backref" href="#id1">Installation</a></h1>
<p>To install this module, you need to install following python library, <strong>xlrd, xlwt, openpyxl</strong>.</p>
<p>Then, simply install <strong>excel_import_export</strong>.</p>
<p>For demo, install <strong>excel_import_export_demo</strong></p>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#id2">Usage</a></h1>
<div class="section" id="concepts">
<h2><a class="toc-backref" href="#id3">Concepts</a></h2>
<p>This module contain pre-defined function and wizards to make exporting, importing and reporting easy.</p>
<p>At the heart of this module, there are 2 <cite>main methods</cite></p>
<ul class="simple">
<li><tt class="docutils literal"><span class="pre">self.env['xlsx.export'].export_xlsx(...)</span></tt></li>
<li><tt class="docutils literal"><span class="pre">self.env['xlsx.import'].import_xlsx(...)</span></tt></li>
</ul>
<p>For reporting, also call <cite>export_xlsx(…)</cite> but through following method</p>
<ul class="simple">
<li><tt class="docutils literal"><span class="pre">self.env['xslx.report'].report_xlsx(...)</span></tt></li>
</ul>
<p>After install this module, go to Settings &gt; Excel Import/Export &gt; XLSX Templates, this is where the key component located.</p>
<p>As this module provide tools, it is best to explain as use cases. For example use cases, please install <strong>excel_import_export_demo</strong></p>
</div>
<div class="section" id="use-cases">
<h2><a class="toc-backref" href="#id4">Use Cases</a></h2>
<p><strong>Use Case 1:</strong> Export/Import Excel on existing document</p>
<p>This add export/import action menus in existing document (example - excel_import_export_demo/import_export_sale_order)</p>
<ol class="arabic simple">
<li>Create export action menu on document, &lt;act_window&gt; with res_model=”export.xlsx.wizard” and src_model=”&lt;document_model&gt;”, and context[template_domain] to locate the right template actions.xml</li>
<li>Create import action menu on document, &lt;act_window&gt; with res_model=”import.xlsx.wizard” and src_model=”&lt;document_model&gt;”, and context[template_domain] to locate the right template action.xml</li>
<li>Create/Design Excel Template File (.xlsx), in the template, name the underlining tab used for export/import &lt;file&gt;.xlsx</li>
<li>Create instruction dictionary for export/import in xlsx.template model templates.xml</li>
</ol>
<p><strong>Use Case 2:</strong> Import Excel Files</p>
<p>With menu wizard to create new documents (example - excel_import_export_demo/import_sale_orders)</p>
<ol class="arabic simple">
<li>Create report menu with search wizard, res_model=”import.xlsx.wizard” and context[template_domain] to locate the right template menu_action.xml</li>
<li>Create Excel Template File (.xlsx), in the template, name the underlining tab used for import &lt;import file&gt;.xlsx</li>
<li>Create instruction dictionary for import in xlsx.template model templates.xml</li>
</ol>
<p><strong>Use Case 3:</strong> Create Excel Report</p>
<p>This create report menu with criteria wizard. (example - excel_import_export_demo/report_sale_order)</p>
<ol class="arabic simple">
<li>Create reports menu, action, and add context[template_domain] to locate the right template for this report &lt;report&gt;.xml</li>
<li>Create reports wizard for search criteria. The view inherits <tt class="docutils literal">excel_import_export.xlsx_report_view</tt> and mode=”primary”. In this view, you only need to add criteria fields, the rest will reuse from interited view &lt;report.xml&gt;</li>
<li>Create report model as models.Transient, then define search criteria fields, and get reporing data into <tt class="docutils literal">results</tt> field &lt;report&gt;.py</li>
<li>Create/Design Excel Template File (.xlsx), in the template, name the underlining tab used for report results &lt;report_file&gt;.xlsx</li>
<li>Create instruction dictionary for report in xlsx.template model templates.xml</li>
</ol>
<p><strong>Note:</strong></p>
<p>Another option for reporting is to use report action (report_type=excel), I.e.,</p>
<pre class="code xml literal-block">
<span class="nt">&lt;report</span> <span class="na">id=</span><span class="s">'action_report_saleorder_excel'</span>
<span class="na">string=</span><span class="s">'Quotation / Order (.xlsx)'</span>
<span class="na">model=</span><span class="s">'sale.order'</span>
<span class="na">name=</span><span class="s">'sale_order.xlsx'</span>
<span class="na">file=</span><span class="s">'sale_order'</span>
<span class="na">report_type=</span><span class="s">'excel'</span>
<span class="nt">/&gt;</span>
</pre>
<p>By using report action, Odoo will find template using combination of model and name, then do the export for the underlining record.
Please see example in excel_import_export_demo/report_action, which shows,</p>
<ol class="arabic simple">
<li>Print excel from an active sale.order</li>
<li>Run partner list report based on search criteria.</li>
</ol>
</div>
<div class="section" id="easy-reporting-option">
<h2><a class="toc-backref" href="#id5">Easy Reporting Option</a></h2>
<p>Technically, this option is the same as “Create Excel Report” use case. But instead of having to write XML / Python code like normally do,
this option allow user to create a report based on a model or view, all by configuration only.</p>
<ol class="arabic simple">
<li>Goto &gt; Technical&gt; Excel Import/Export &gt; XLSX Templates, and create a new template for a report.</li>
<li>On the new template, select “Easy Reporting” option, then select followings
- Report Model, this can be data model or data view we want to get the results from.
- Click upload your file and add the excel template (.xlsx)
- Click Save, system will create sample export line, user can add more fields according to results model.</li>
<li>Click Add Report Menu, the report menu will be created, user can change its location. Now the report is ready to use.</li>
</ol>
<blockquote>
<div class="figure">
<img alt="https://raw.githubusercontent.com/OCA/server-tools/16.0/excel_import_export/static/description/xlsx_template.png" src="https://raw.githubusercontent.com/OCA/server-tools/16.0/excel_import_export/static/description/xlsx_template.png" style="width: 800px;" />
</div>
</blockquote>
<p>Note: Using easy reporting mode, system will used a common criteria wizard.</p>
<blockquote>
<div class="figure">
<img alt="https://raw.githubusercontent.com/OCA/server-tools/16.0/excel_import_export/static/description/common_wizard.png" src="https://raw.githubusercontent.com/OCA/server-tools/16.0/excel_import_export/static/description/common_wizard.png" style="width: 800px;" />
</div>
</blockquote>
</div>
</div>
<div class="section" id="known-issues-roadmap">
<h1><a class="toc-backref" href="#id6">Known issues / Roadmap</a></h1>
<ul class="simple">
<li>Module extension e.g., excel_import_export_async, that add ability to execute as async process.</li>
</ul>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#id7">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/server-tools/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/server-tools/issues/new?body=module:%20excel_import_export%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#id8">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#id9">Authors</a></h2>
<ul class="simple">
<li>Ecosoft</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#id10">Contributors</a></h2>
<ul class="simple">
<li>Kitti Upariphutthiphong. &lt;<a class="reference external" href="mailto:kittiu&#64;gmail.com">kittiu&#64;gmail.com</a>&gt; (<a class="reference external" href="http://ecosoft.co.th">http://ecosoft.co.th</a>)</li>
<li>Saran Lim. &lt;<a class="reference external" href="mailto:saranl&#64;ecosoft.co.th">saranl&#64;ecosoft.co.th</a>&gt; (<a class="reference external" href="http://ecosoft.co.th">http://ecosoft.co.th</a>)</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#id11">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
<p>Current <a class="reference external" href="https://odoo-community.org/page/maintainer-role">maintainer</a>:</p>
<p><a class="reference external" href="https://github.com/kittiu"><img alt="kittiu" src="https://github.com/kittiu.png?size=40px" /></a></p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/server-tools/tree/16.0/excel_import_export">OCA/server-tools</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

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

@ -0,0 +1,61 @@
<?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>
<record id="xlsx_report_view" model="ir.ui.view">
<field name="name">xlsx.report.view</field>
<field name="model">xlsx.report</field>
<field name="arch" type="xml">
<form string="Excel Report">
<!-- search criteria -->
<group name="criteria" states="choose">
</group>
<!-- xlsx.report common field -->
<div name="xlsx.report">
<field name="state" invisible="1" />
<field name="name" invisible="1" />
<field name="choose_template" invisible="1" />
<div states="choose">
<label
string="Choose Template: "
for="template_id"
attrs="{'invisible': [('choose_template', '=', False)]}"
/>
<field
name="template_id"
attrs="{'invisible': [('choose_template', '=', False)]}"
/>
</div>
<div states="get">
<h2>
Complete Prepare Report (.xlsx)
</h2>
<p colspan="4">
Here is the report file:
<field name="data" filename="name" class="oe_inline" />
</p>
</div>
<footer states="choose">
<button
name="report_xlsx"
string="Execute Report"
type="object"
class="oe_highlight"
/>
or
<button
special="cancel"
string="Cancel"
type="object"
class="oe_link"
/>
</footer>
<footer states="get">
<button special="cancel" string="Close" type="object" />
</footer>
</div>
</form>
</field>
</record>
</odoo>

View File

@ -0,0 +1,439 @@
<?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>
<record id="view_xlsx_template_tree" model="ir.ui.view">
<field name="model">xlsx.template</field>
<field name="arch" type="xml">
<tree>
<field name="name" />
</tree>
</field>
</record>
<record id="view_xlsx_template_form" model="ir.ui.view">
<field name="model">xlsx.template</field>
<field name="arch" type="xml">
<form string="XLSX Template">
<sheet>
<div class="oe_button_box" name="button_box">
<button
name="add_report_menu"
string="Add Report Menu"
type="object"
attrs="{'invisible': ['|', ('use_report_wizard', '=', False), ('report_menu_id', '!=', False)]}"
icon="fa-plus-square"
help="Add new report menu at root level"
class="oe_stat_button"
/>
<button
name="remove_report_menu"
string="Remove Report Menu"
type="object"
attrs="{'invisible': [('report_menu_id', '=', False)]}"
icon="fa-minus-square"
class="oe_stat_button"
/>
</div>
<h1>
<field name="name" colspan="3" />
</h1>
<group>
<group>
<field name="description" />
<field name="to_csv" />
<field
name="csv_delimiter"
attrs="{'invisible': [('to_csv', '=', False)]}"
/>
<field
name="csv_extension"
attrs="{'invisible': [('to_csv', '=', False)]}"
/>
<field
name="csv_quote"
attrs="{'invisible': [('to_csv', '=', False)]}"
/>
<field name="use_report_wizard" />
<field
name="report_menu_id"
attrs="{'invisible': [('report_menu_id', '=', False)]}"
/>
<field
name="result_model_id"
attrs="{'invisible': [('use_report_wizard', '=', False)],
'required': [('use_report_wizard', '=', True)]}"
/>
<field
name="result_field"
attrs="{'invisible': [('use_report_wizard', '=', False)]}"
/>
</group>
<group>
<field name="fname" invisible="1" />
<field name="datas" filename="fname" />
<field name="gname" />
<field name="res_model" />
<field name="redirect_action" />
</group>
</group>
<notebook>
<page string="Export">
<div
name="export_actions"
attrs="{'invisible': [('use_report_wizard', '=', True)]}"
>
<button
name="add_export_action"
class="oe_highlight"
type="object"
string="Add Export Action"
attrs="{'invisible': [('export_action_id', '!=', False)]}"
/>
<button
name="remove_export_action"
type="object"
string="Remove Export Action"
attrs="{'invisible': [('export_action_id', '=', False)]}"
/>
<field name="export_action_id" invisible="1" />
</div>
<separator />
<field name="export_ids">
<tree name="export_instruction" editable="bottom">
<control>
<create
string="Add sheet section"
context="{'default_section_type': 'sheet'}"
/>
<create
string="Add header section"
context="{'default_section_type': 'head', 'default_row_field': '_HEAD_'}"
/>
<create
string="Add row section"
context="{'default_section_type': 'row'}"
/>
<create
string="Add data column"
context="{'default_section_type': 'data'}"
/>
</control>
<field name="sequence" widget="handle" />
<field name="section_type" invisible="1" />
<field
name="sheet"
attrs="{'required': [('section_type', '=', 'sheet')],
'invisible': [('section_type', '!=', 'sheet')]}"
/>
<field
name="row_field"
attrs="{'required': [('section_type', 'in', ('head', 'row'))],
'invisible': [('section_type', 'not in', ('head', 'row'))]}"
/>
<field
name="is_cont"
attrs="{'required': [('section_type', 'in', ('head', 'row'))],
'invisible': [('section_type', 'not in', ('head', 'row'))]}"
/>
<field
name="is_extend"
attrs="{'required': [('section_type', 'in', ('head', 'row'))],
'invisible': [('section_type', 'not in', ('head', 'row'))]}"
/>
<field
name="excel_cell"
attrs="{'required': [('section_type', '=', 'data')],
'invisible': [('section_type', '!=', 'data')]}"
/>
<field
name="field_name"
attrs="{'invisible': [('section_type', '!=', 'data')]}"
/>
<field
name="field_cond"
attrs="{'invisible': [('section_type', '!=', 'data')]}"
/>
<field
name="is_sum"
attrs="{'invisible': [('section_type', '!=', 'data')]}"
/>
<field
name="style"
attrs="{'invisible': [('section_type', '!=', 'data')]}"
/>
<field
name="style_cond"
attrs="{'invisible': [('section_type', '!=', 'data')]}"
/>
</tree>
</field>
<div style="margin-top: 4px;">
<h3>Help with Export Instruction</h3>
<p>
Export Instruction is how to write data from an active data record to specified cells in excel sheet.
For example, an active record can be a sale order that user want to export.
The record itself will be mapped to the header part of excel sheet. The record can contain multiple one2many fields, which will be written as data lines.
You can look at following instruction as Excel Sheet(s), each with 1 header section (_HEAD_) and multiple row sections (one2many fields).
</p>
<ul>
<li
>In header section part, map data fields (e.g., number, partner_id.name) into cells (e.g., B1, B2).</li>
<li
>In row section, data list will be rolled out from one2many row field (e.g., order_line), and map data field (i.e., product_id.name, uom_id.name, qty) into the first row cells to start rolling (e.g., A6, B6, C6).</li>
</ul>
<p>Following are more explaination on each column:</p>
<ul>
<li><b
>Sheet</b>: Name (e.g., Sheet 1) or index (e.g., 1) of excel sheet to export data to</li>
<li><b
>Row Field</b>: Use _HEAD_ for the record itself, and one2many field (e.g., line_ids) for row data</li>
<li><b
>Continue</b>: If not selected, start rolling with specified first row cells. If selected, continue from previous one2many field</li>
<li><b
>Extend</b>: If selected, extend one row after one data row in order to preserve the sum line</li>
<li><b
>Cell</b>: Location of data in excel sheet (e.g., A1, B1, ...)</li>
<li><b
>Field</b>: Field of the record, e.g., product_id.uom_id.name. They are orm compliant.</li>
<li><b>Field Cond.</b>: Python code in <code
>$</code><code
>{...}</code> to manipulate field value, e.g., if field = product_id, <code
>value</code> will represent product object, e.g., <code
>$</code><code
>{value and value.uom_id.name or ""}</code></li>
<li><b>Sum</b>: Add sum value on last row, <code
>@{sum}</code></li>
<li><b>Style</b>: Default style in <code
>#{...}</code> that apply to each cell, e.g., <code
>#{align=left;style=text}</code>. See module's <b
>style.py</b> for available styles.</li>
<li><b
>Style w/Cond.</b>: Conditional style by python code in <code
>#?...?</code>, e.g., apply style for specific product, <code
>#?value.name == "ABC" and #{font=bold;fill=red} or None?</code></li>
</ul>
<p>
<b>Note:</b>
</p>
For code block <code>$</code><code
>{...}</code> and <code
>#?...?</code>, following object are available,
<ul>
<li><code>value</code>: value from <b>Field</b></li>
<li><code
>object</code>: record object or line object depends on <b
>Row Field</b></li>
<li><code
>model</code>: active model, e.g., self.env['my.model']</li>
<li><code
>date, datetime, time</code>: some useful python classes</li>
</ul>
</div>
</page>
<page
string="Import"
attrs="{'invisible': [('use_report_wizard', '=', True)]}"
>
<div name="import_actions">
<button
name="add_import_action"
class="oe_highlight"
type="object"
string="Add Import Action"
attrs="{'invisible': [('import_action_id', '!=', False)]}"
/>
<button
name="remove_import_action"
type="object"
string="Remove Import Action"
attrs="{'invisible': [('import_action_id', '=', False)]}"
/>
<field name="import_action_id" invisible="1" />
</div>
<separator />
<field name="import_ids">
<tree name="import_instruction" editable="bottom">
<control>
<create
string="Add sheet section"
context="{'default_section_type': 'sheet'}"
/>
<create
string="Add header section"
context="{'default_section_type': 'head', 'default_row_field': '_HEAD_'}"
/>
<create
string="Add row section"
context="{'default_section_type': 'row'}"
/>
<create
string="Add data column"
context="{'default_section_type': 'data'}"
/>
</control>
<field name="sequence" widget="handle" />
<field name="section_type" invisible="1" />
<field
name="sheet"
attrs="{'required': [('section_type', '=', 'sheet')],
'invisible': [('section_type', '!=', 'sheet')]}"
/>
<field
name="row_field"
attrs="{'required': [('section_type', 'in', ('head', 'row'))],
'invisible': [('section_type', 'not in', ('head', 'row'))]}"
/>
<field
name="no_delete"
attrs="{'invisible': [('section_type', '!=', 'row')]}"
/>
<field
name="excel_cell"
attrs="{'required': [('section_type', '=', 'data')],
'invisible': [('section_type', '!=', 'data')]}"
/>
<field
name="field_name"
attrs="{'invisible': [('section_type', '!=', 'data')]}"
/>
<field
name="field_cond"
attrs="{'invisible': [('section_type', '!=', 'data')]}"
/>
</tree>
</field>
<group string="Post Import Hook">
<field
name="post_import_hook"
placeholder="${object.post_import_do_something()}"
/>
</group>
<hr />
<div style="margin-top: 4px;">
<h3>Help with Import Instruction</h3>
<p>
Import Instruction is how to get data from excel sheet and write them to an active record.
For example, user create a sales order document, and want to import order lines from excel.
In reverse direction to exporting, data from excel's cells will be mapped to record fields during import.
Cells can be mapped to record in header section (_HEAD_) and data table can be mapped to row section (one2many field, begins from specifed cells.
</p>
<ul>
<li
>In header section, map cells (e.g., B1, B2) into data fields (e.g., number, partner_id).</li>
<li
>In row section, data table from excel can be imported to one2many row field (e.g., order_line) by mapping cells on first row onwards (e.g., A6, B6, C6) to fields (e.g., product_id, uom_id, qty) </li>
</ul>
<p>Following are more explaination on each column:</p>
<ul>
<li><b
>Sheet</b>: Name (e.g., Sheet 1) or index (e.g., 1) of excel sheet</li>
<li><b
>Row Field</b>: Use _HEAD_ for the record itself, and one2many field for row data, e.g., order_line, line_ids<code
>[max_row]</code> where <code
>[max_row]</code> is optional number of rows to import</li>
<li><b
>No Delete</b>: By default, all one2many lines will be deleted before import. Select this, to avoid deletion</li>
<li><b
>Cell</b>: Location of data in excel sheet (e.g., A1, B1, ...)</li>
<li><b
>Field</b>: Field of the record to be imported to, e.g., product_id</li>
<li><b>Field Cond.</b>: Python code in <code
>$</code><code
>{...}</code> value will represent data from excel cell, e.g., if A1 = 'ABC', <code
>value</code> will represent 'ABC', e.g., <code
>$</code><code
>{value == "ABC" and "X" or "Y"}</code> thus can change from cell value to other value for import.</li>
</ul>
<p>
<b>Note:</b>
</p>
For code block <code>$</code><code
>{...}</code>, following object are available,
<ul>
<li><code>value</code>: value from <b>Cell</b></li>
<li><code
>model</code>: active model, e.g., self.env['my.model']</li>
<li><code
>date, datetime, time</code>: some useful python classes</li>
</ul>
</div>
</page>
<page string="Input Instruction (Dict.)">
<field name="input_instruction" />
<field name="show_instruction" />
<label for="show_instruction" />
<field
name="instruction"
attrs="{'invisible': [('show_instruction', '=', False)]}"
/>
<hr />
<div style="margin-top: 4px;">
<h3>Sample Input Instruction as Dictionary</h3>
<p>
Following show very simple example of the dictionary construct.
Normally, this will be within templates.xml file within addons.
</p>
<pre>
<code class="oe_grey">
{
'__EXPORT__': {
'sale_order': { # sheet can be name (string) or index (integer)
'_HEAD_': {
'B2': 'partner_id.display_name<span
>$</span>{value or ""}#{align=left;style=text}',
'B3': 'name<span>$</span>{value or ""}#{align=left;style=text}',
},
'line_ids': { # prefix with _CONT_ to continue rows from previous row field
'A6': 'product_id.display_name<span>$</span>{value or ""}#{style=text}',
'C6': 'product_uom_qty<span>$</span>{value or 0}#{style=number}',
'E6': 'price_unit<span>$</span>{value or 0}#{style=number}',
'G6': 'price_subtotal<span>$</span>{value or 0}#{style=number}',
},
},
},
'__IMPORT__': {
'sale_order': { # sheet can be name (string) or index (integer)
'order_line': { # prefix with _NODEL_ to not delete rows before import
'A6': 'product_id',
'C6': 'product_uom_qty',
'E6': 'price_unit<span>$</span>{value > 0 and value or 0}',
},
},
},
'__POST_IMPORT__': '<span>$</span>{object.post_import_do_something()}',
}
</code>
</pre>
</div>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="action_xlsx_template" model="ir.actions.act_window">
<field name="name">XLSX Templates</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">xlsx.template</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create a XLSX Template Object.
</p>
</field>
</record>
<menuitem
id="menu_excel_import_export"
name="Excel Import/Export"
parent="base.menu_custom"
sequence="130"
/>
<menuitem
id="menu_xlsx_template"
parent="menu_excel_import_export"
action="action_xlsx_template"
sequence="10"
/>
</odoo>

View File

@ -0,0 +1,3 @@
from . import export_xlsx_wizard
from . import import_xlsx_wizard
from . import report_xlsx_wizard

View File

@ -0,0 +1,67 @@
# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
from odoo.tools.safe_eval import safe_eval
class ExportXLSXWizard(models.TransientModel):
"""This wizard is used with the template (xlsx.template) to export
xlsx template filled with data form the active record"""
_name = "export.xlsx.wizard"
_description = "Wizard for exporting excel"
name = fields.Char(string="File Name", readonly=True, size=500)
data = fields.Binary(string="File", readonly=True)
template_id = fields.Many2one(
"xlsx.template",
string="Template",
required=True,
ondelete="cascade",
domain=lambda self: self._context.get("template_domain", []),
)
res_ids = fields.Char(string="Resource IDs", readonly=True, required=True)
res_model = fields.Char(
string="Resource Model", readonly=True, required=True, size=500
)
state = fields.Selection(
[("choose", "Choose"), ("get", "Get")],
default="choose",
help="* Choose: wizard show in user selection mode"
"\n* Get: wizard show results from user action",
)
@api.model
def default_get(self, fields):
res_model = self._context.get("active_model", False)
res_ids = self._context.get("active_ids", False)
template_domain = self._context.get("template_domain", [])
templates = self.env["xlsx.template"].search(template_domain)
if not templates:
raise ValidationError(_("No template found"))
defaults = super(ExportXLSXWizard, self).default_get(fields)
for template in templates:
if not template.datas:
raise ValidationError(_("No file in %s") % (template.name,))
defaults["template_id"] = len(templates) == 1 and templates.id or False
defaults["res_ids"] = ",".join([str(x) for x in res_ids])
defaults["res_model"] = res_model
return defaults
def action_export(self):
self.ensure_one()
Export = self.env["xlsx.export"]
out_file, out_name = Export.export_xlsx(
self.template_id, self.res_model, safe_eval(self.res_ids)
)
self.write({"state": "get", "data": out_file, "name": out_name})
return {
"type": "ir.actions.act_window",
"res_model": "export.xlsx.wizard",
"view_mode": "form",
"res_id": self.id,
"views": [(False, "form")],
"target": "new",
}

View File

@ -0,0 +1,50 @@
<?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>
<record id="export_xlsx_wizard" model="ir.ui.view">
<field name="name">export.xlsx.wizard</field>
<field name="model">export.xlsx.wizard</field>
<field name="arch" type="xml">
<form string="Get Import Template">
<field invisible="1" name="state" />
<field name="name" invisible="1" />
<group states="choose">
<group>
<field name="template_id" widget="selection" />
</group>
<group>
<field name="res_model" invisible="1" />
<field name="res_ids" invisible="1" />
</group>
</group>
<div states="get">
<h2>Complete Prepare File (.xlsx)</h2>
<p>Here is the exported file: <field
name="data"
readonly="1"
filename="name"
/></p>
</div>
<footer states="choose">
<button
name="action_export"
string="Export"
type="object"
class="oe_highlight"
/> or
<button
special="cancel"
string="Cancel"
type="object"
class="oe_link"
/>
</footer>
<footer states="get">
<button special="cancel" string="Close" type="object" />
</footer>
</form>
</field>
</record>
</odoo>

View File

@ -0,0 +1,156 @@
# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
from odoo import _, api, fields, models
from odoo.exceptions import RedirectWarning, ValidationError
class ImportXLSXWizard(models.TransientModel):
"""This wizard is used with the template (xlsx.template) to import
xlsx template back to active record"""
_name = "import.xlsx.wizard"
_description = "Wizard for importing excel"
import_file = fields.Binary(string="Import File (*.xlsx)")
template_id = fields.Many2one(
"xlsx.template",
string="Template",
required=True,
ondelete="cascade",
domain=lambda self: self._context.get("template_domain", []),
)
res_id = fields.Integer(string="Resource ID", readonly=True)
res_model = fields.Char(string="Resource Model", readonly=True, size=500)
datas = fields.Binary(string="Sample", related="template_id.datas", readonly=True)
fname = fields.Char(
string="Template Name", related="template_id.fname", readonly=True
)
attachment_ids = fields.Many2many(
"ir.attachment",
string="Import File(s) (*.xlsx)",
required=True,
help="You can select multiple files to import.",
)
state = fields.Selection(
[("choose", "Choose"), ("get", "Get")],
default="choose",
help="* Choose: wizard show in user selection mode"
"\n* Get: wizard show results from user action",
)
@api.model
def view_init(self, fields_list):
"""This template only works on some context of active record"""
res = super(ImportXLSXWizard, self).view_init(fields_list)
res_model = self._context.get("active_model", False)
res_id = self._context.get("active_id", False)
if not res_model or not res_id:
return res
record = self.env[res_model].browse(res_id)
messages = []
valid = True
# For all import, only allow import in draft state (for documents)
import_states = self._context.get("template_import_states", [])
if import_states: # states specified in context, test this
if "state" in record and record["state"] not in import_states:
messages.append(_("Document must be in %s states") % import_states)
valid = False
else: # no specific state specified, test with draft
if "state" in record and "draft" not in record["state"]: # not in
messages.append(_("Document must be in draft state"))
valid = False
# Context testing
if self._context.get("template_context", False):
template_context = self._context["template_context"]
for key, value in template_context.items():
if (
key not in record
or (
record._fields[key].type == "many2one"
and record[key].id
or record[key]
)
!= value
):
valid = False
messages.append(
_(
"This import action is not usable "
"in this document context"
)
)
break
if not valid:
raise ValidationError("\n".join(messages))
return res
@api.model
def default_get(self, fields):
res_model = self._context.get("active_model", False)
res_id = self._context.get("active_id", False)
template_domain = self._context.get("template_domain", [])
templates = self.env["xlsx.template"].search(template_domain)
if not templates:
raise ValidationError(_("No template found"))
defaults = super(ImportXLSXWizard, self).default_get(fields)
for template in templates:
if not template.datas:
act = self.env.ref("excel_import_export.action_xlsx_template")
raise RedirectWarning(
_(
'File "%(fname)s" not found in template, %(name)s.',
fname=template.fname,
name=template.name,
),
act.id,
_("Set Templates"),
)
defaults["template_id"] = len(templates) == 1 and template.id or False
defaults["res_id"] = res_id
defaults["res_model"] = res_model
return defaults
def get_import_sample(self):
self.ensure_one()
return {
"name": _("Import Excel"),
"type": "ir.actions.act_window",
"res_model": "import.xlsx.wizard",
"view_mode": "form",
"res_id": self.id,
"views": [(False, "form")],
"target": "new",
"context": self._context.copy(),
}
def action_import(self):
self.ensure_one()
Import = self.env["xlsx.import"]
res_ids = []
if self.import_file:
record = Import.import_xlsx(
self.import_file, self.template_id, self.res_model, self.res_id
)
res_ids = [record.id]
elif self.attachment_ids:
for attach in self.attachment_ids:
record = Import.import_xlsx(attach.datas, self.template_id)
res_ids.append(record.id)
else:
raise ValidationError(_("Please select Excel file to import"))
# If redirect_action is specified, do redirection
if self.template_id.redirect_action:
vals = self.template_id.redirect_action.read()[0]
vals["domain"] = [("id", "in", res_ids)]
return vals
self.write({"state": "get"})
return {
"type": "ir.actions.act_window",
"res_model": self._name,
"view_mode": "form",
"res_id": self.id,
"views": [(False, "form")],
"target": "new",
}

View File

@ -0,0 +1,68 @@
<?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>
<record id="import_xlsx_wizard" model="ir.ui.view">
<field name="name">import.xlsx.wizard</field>
<field name="model">import.xlsx.wizard</field>
<field name="arch" type="xml">
<form string="Import File Template">
<field name="id" invisible="1" />
<field name="state" invisible="1" />
<field name="fname" invisible="1" />
<field name="res_model" invisible="1" />
<field name="res_id" invisible="1" />
<group states="choose">
<group>
<field
name="import_file"
attrs="{'invisible': [('res_id', '=', False)]}"
/>
<field
name="attachment_ids"
widget="many2many_binary"
nolabel="1"
attrs="{'invisible': [('res_id', '!=', False)]}"
/>
</group>
<group>
<field name="template_id" widget="selection" />
<div colspan="2">
<button
name="get_import_sample"
string="⇒ Get Sample Import Template"
type="object"
class="oe_link"
attrs="{'invisible': [('id', '!=', False)]}"
/>
</div>
<field
name="datas"
filename="fname"
attrs="{'invisible': [('id', '=', False)]}"
/>
</group>
</group>
<group states="get">
<p>
Import Successful!
</p>
</group>
<footer states="choose">
<button
name="action_import"
string="Import"
type="object"
class="oe_highlight"
/>
or
<button string="Cancel" class="oe_link" special="cancel" />
</footer>
<footer states="get">
<button string="Close" class="oe_link" special="cancel" />
</footer>
</form>
</field>
</record>
</odoo>

View File

@ -0,0 +1,22 @@
# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
import ast
from odoo import fields, models
class ReportXLSXWizard(models.TransientModel):
_name = "report.xlsx.wizard"
_description = "Generic Report Wizard, used with template reporting option"
res_model = fields.Char()
domain = fields.Char(string="Search Criterias")
def action_report(self):
action_id = self._context.get("report_action_id")
action = self.env["ir.actions.report"].browse(action_id)
res = action.read()[0]
return res
def safe_domain(self, str_domain):
return ast.literal_eval(str_domain or "[]")

View File

@ -0,0 +1,31 @@
<?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>
<record id="report_xlsx_wizard" model="ir.ui.view">
<field name="name">report.xlsx.wizard</field>
<field name="model">report.xlsx.wizard</field>
<field name="arch" type="xml">
<form>
<group>
<field name="res_model" invisible="1" />
<field
name="domain"
widget="domain"
options="{'model': 'res_model', 'in_dialog': True}"
/>
</group>
<footer>
<button
name="action_report"
type="object"
string="Execute"
class="oe_highlight"
/>
<button special='cancel' string='Cancel' />
</footer>
</form>
</field>
</record>
</odoo>

View File

@ -0,0 +1,117 @@
===============================
Excel Import/Export/Report Demo
===============================
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--tools-lightgray.png?logo=github
:target: https://github.com/OCA/server-tools/tree/16.0/excel_import_export_demo
:alt: OCA/server-tools
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/server-tools-16-0/server-tools-16-0-excel_import_export_demo
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
:target: https://runbot.odoo-community.org/runbot/149/16.0
:alt: Try me on Runbot
|badge1| |badge2| |badge3| |badge4| |badge5|
This module provide some example use case for excel_import_export
1. Import/Export Sales Order (import_export_sale_order)
2. Import New Sales Orders (import_sale_orders)
3. Sales Orders Report (report_sale_order)
4. Print Quoation / Order (.xlsx) (report_action/sale_order)
5. Run Partner List Report (report_action/partner_list)
**Table of contents**
.. contents::
:local:
Installation
============
To install this module, you need to install **excel_import_export**
Then, simply install **excel_import_export_demo**.
Usage
=====
**Example 1:** Export/Import Excel on existing document
To test this use case, go to any Sales Order and use Export Excel or Import Excel in action menu.
**Example 2:** Import Excel Files
To test this use case, go to Settings > Excel Import/Export > Sample Import Sales Order
**Example 3:** Create Excel Report
To test this use case, go to Settings > Excel Import/Export > Sample Sales Report
**Example 4:** Printout Excel on existing document, using report action
To test this use case, go to any Sales Order and click print "Quotation / Order (.xlsx)".
**Example 5:** Run Partner List Report, using report action
To test this use case, go to menu Sales > Reporting > Partner List Report
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-tools/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
`feedback <https://github.com/OCA/server-tools/issues/new?body=module:%20excel_import_export_demo%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
~~~~~~~
* Ecosoft
Contributors
~~~~~~~~~~~~
* Kitti Upariphutthiphong. <kittiu@gmail.com> (http://ecosoft.co.th)
Maintainers
~~~~~~~~~~~
This module is maintained by the OCA.
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
.. |maintainer-kittiu| image:: https://github.com/kittiu.png?size=40px
:target: https://github.com/kittiu
:alt: kittiu
Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:
|maintainer-kittiu|
This module is part of the `OCA/server-tools <https://github.com/OCA/server-tools/tree/16.0/excel_import_export_demo>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@ -0,0 +1,7 @@
# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
from . import import_export_sale_order
from . import report_sale_order
from . import report_crm_lead
from . import report_action

View File

@ -0,0 +1,36 @@
# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
{
"name": "Excel Import/Export/Report Demo",
"version": "16.0.1.0.0",
"author": "Ecosoft,Odoo Community Association (OCA)",
"license": "AGPL-3",
"website": "https://github.com/OCA/server-tools",
"category": "Tools",
"depends": ["excel_import_export", "sale_management", "purchase", "crm"],
"data": [
"import_export_sale_order/actions.xml",
"import_export_sale_order/templates.xml",
"import_export_purchase_order/actions.xml",
"import_export_purchase_order/templates.xml",
"report_sale_order/report_sale_order.xml",
"report_sale_order/templates.xml",
"report_sale_order/security/ir.model.access.csv",
"report_crm_lead/report_crm_lead.xml",
"report_crm_lead/templates.xml",
"report_crm_lead/security/ir.model.access.csv",
"import_sale_orders/menu_action.xml",
"import_sale_orders/templates.xml",
# Use report action
"report_action/sale_order/report.xml",
"report_action/sale_order/templates.xml",
"report_action/partner_list/report.xml",
"report_action/partner_list/templates.xml",
"report_action/partner_list/report_partner_list.xml",
"report_action/partner_list/security/ir.model.access.csv",
],
"installable": True,
"development_status": "Beta",
"maintainers": ["kittiu"],
}

View File

@ -0,0 +1,267 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * excel_import_export_demo
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 14.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: ca\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
#. module: excel_import_export_demo
#: model:ir.actions.report,print_report_name:excel_import_export_demo.action_report_saleorder_excel
msgid ""
"(object.state in ('draft', 'sent') and 'Quotation - %s' % (object.name)) or "
"'Order - %s' % (object.name)"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,help:excel_import_export_demo.field_report_crm_lead__state
#: model:ir.model.fields,help:excel_import_export_demo.field_report_sale_order__state
msgid ""
"* Choose: wizard show in user selection mode\n"
"* Get: wizard show results from user action"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__choose_template
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__choose_template
msgid "Allow Choose Template"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__assigned_attachment_ids
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__assigned_attachment_ids
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__assigned_attachment_ids
msgid "Assigned Attachments"
msgstr ""
#. module: excel_import_export_demo
#: model_terms:ir.ui.view,arch_db:excel_import_export_demo.partner_list_wizard
msgid "Cancel"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__changeset_change_ids
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__changeset_change_ids
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__changeset_change_ids
msgid "Changeset Changes"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__changeset_ids
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__changeset_ids
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__changeset_ids
msgid "Changesets"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__count_pending_changeset_changes
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__count_pending_changeset_changes
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__count_pending_changeset_changes
msgid "Count Pending Changeset Changes"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__count_pending_changesets
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__count_pending_changesets
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__count_pending_changesets
msgid "Count Pending Changesets"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__create_uid
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__create_uid
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__create_uid
msgid "Created by"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__create_date
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__create_date
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__create_date
msgid "Created on"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__display_name
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__display_name
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__display_name
msgid "Display Name"
msgstr ""
#. module: excel_import_export_demo
#: model_terms:ir.ui.view,arch_db:excel_import_export_demo.partner_list_wizard
msgid "Execute"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.actions.act_window,name:excel_import_export_demo.action_purchase_order_export_xlsx
#: model:ir.actions.act_window,name:excel_import_export_demo.action_sale_oder_export_xlsx
msgid "Export Excel"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__data
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__data
msgid "File"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__name
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__name
msgid "File Name"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__id
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__id
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__id
msgid "ID"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.actions.act_window,name:excel_import_export_demo.action_purchase_order_import_xlsx
#: model:ir.actions.act_window,name:excel_import_export_demo.action_sale_oder_import_xlsx
msgid "Import Excel"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead____last_update
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list____last_update
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order____last_update
msgid "Last Modified on"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__write_uid
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__write_uid
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__write_uid
msgid "Last Updated by"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__write_date
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__write_date
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__write_date
msgid "Last Updated on"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__partner_ids
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__partner_id
msgid "Partner"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.actions.report,name:excel_import_export_demo.action_report_partner_excel
msgid "Partner List (.xlsx)"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.actions.act_window,name:excel_import_export_demo.action_report_partner_list
#: model:ir.ui.menu,name:excel_import_export_demo.menu_report_partner_list
msgid "Partner List Report"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.actions.report,name:excel_import_export_demo.action_report_saleorder_excel
msgid "Quotation / Order (.xlsx)"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__results
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__results
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__results
msgid "Results"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__revenue_by_country
msgid "Revenue By Country"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__revenue_by_team
msgid "Revenue By Team"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__team_id
msgid "Sales Team"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.actions.act_window,name:excel_import_export_demo.action_import_sale_order
#: model:ir.ui.menu,name:excel_import_export_demo.menu_import_sale_order
msgid "Sample Import Sale Order"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.actions.act_window,name:excel_import_export_demo.action_report_crm_lead
#: model:ir.ui.menu,name:excel_import_export_demo.menu_report_crm_lead
msgid "Sample Lead Report"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.actions.act_window,name:excel_import_export_demo.action_report_sale_order
#: model:ir.ui.menu,name:excel_import_export_demo.menu_report_sale_order
msgid "Sample Sales Report"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__smart_search
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__smart_search
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__smart_search
msgid "Smart Search"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__state
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__state
msgid "State"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__template_id
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__template_id
msgid "Template"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,help:excel_import_export_demo.field_report_partner_list__results
msgid "Use compute fields, so there is nothing store in database"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,help:excel_import_export_demo.field_report_sale_order__results
msgid "Use compute fields, so there is nothing stored in database"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__user_can_see_changeset
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__user_can_see_changeset
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__user_can_see_changeset
msgid "User Can See Changeset"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model,name:excel_import_export_demo.model_report_crm_lead
msgid "Wizard for report.crm.lead"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model,name:excel_import_export_demo.model_report_partner_list
msgid "Wizard for report.partner.list"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model,name:excel_import_export_demo.model_report_sale_order
msgid "Wizard for report.sale.order"
msgstr ""

View File

@ -0,0 +1,266 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * excel_import_export_demo
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 14.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: excel_import_export_demo
#: model:ir.actions.report,print_report_name:excel_import_export_demo.action_report_saleorder_excel
msgid ""
"(object.state in ('draft', 'sent') and 'Quotation - %s' % (object.name)) or "
"'Order - %s' % (object.name)"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,help:excel_import_export_demo.field_report_crm_lead__state
#: model:ir.model.fields,help:excel_import_export_demo.field_report_sale_order__state
msgid ""
"* Choose: wizard show in user selection mode\n"
"* Get: wizard show results from user action"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__choose_template
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__choose_template
msgid "Allow Choose Template"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__assigned_attachment_ids
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__assigned_attachment_ids
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__assigned_attachment_ids
msgid "Assigned Attachments"
msgstr ""
#. module: excel_import_export_demo
#: model_terms:ir.ui.view,arch_db:excel_import_export_demo.partner_list_wizard
msgid "Cancel"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__changeset_change_ids
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__changeset_change_ids
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__changeset_change_ids
msgid "Changeset Changes"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__changeset_ids
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__changeset_ids
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__changeset_ids
msgid "Changesets"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__count_pending_changeset_changes
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__count_pending_changeset_changes
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__count_pending_changeset_changes
msgid "Count Pending Changeset Changes"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__count_pending_changesets
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__count_pending_changesets
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__count_pending_changesets
msgid "Count Pending Changesets"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__create_uid
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__create_uid
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__create_uid
msgid "Created by"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__create_date
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__create_date
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__create_date
msgid "Created on"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__display_name
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__display_name
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__display_name
msgid "Display Name"
msgstr ""
#. module: excel_import_export_demo
#: model_terms:ir.ui.view,arch_db:excel_import_export_demo.partner_list_wizard
msgid "Execute"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.actions.act_window,name:excel_import_export_demo.action_purchase_order_export_xlsx
#: model:ir.actions.act_window,name:excel_import_export_demo.action_sale_oder_export_xlsx
msgid "Export Excel"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__data
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__data
msgid "File"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__name
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__name
msgid "File Name"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__id
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__id
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__id
msgid "ID"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.actions.act_window,name:excel_import_export_demo.action_purchase_order_import_xlsx
#: model:ir.actions.act_window,name:excel_import_export_demo.action_sale_oder_import_xlsx
msgid "Import Excel"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead____last_update
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list____last_update
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order____last_update
msgid "Last Modified on"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__write_uid
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__write_uid
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__write_uid
msgid "Last Updated by"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__write_date
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__write_date
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__write_date
msgid "Last Updated on"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__partner_ids
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__partner_id
msgid "Partner"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.actions.report,name:excel_import_export_demo.action_report_partner_excel
msgid "Partner List (.xlsx)"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.actions.act_window,name:excel_import_export_demo.action_report_partner_list
#: model:ir.ui.menu,name:excel_import_export_demo.menu_report_partner_list
msgid "Partner List Report"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.actions.report,name:excel_import_export_demo.action_report_saleorder_excel
msgid "Quotation / Order (.xlsx)"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__results
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__results
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__results
msgid "Results"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__revenue_by_country
msgid "Revenue By Country"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__revenue_by_team
msgid "Revenue By Team"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__team_id
msgid "Sales Team"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.actions.act_window,name:excel_import_export_demo.action_import_sale_order
#: model:ir.ui.menu,name:excel_import_export_demo.menu_import_sale_order
msgid "Sample Import Sale Order"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.actions.act_window,name:excel_import_export_demo.action_report_crm_lead
#: model:ir.ui.menu,name:excel_import_export_demo.menu_report_crm_lead
msgid "Sample Lead Report"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.actions.act_window,name:excel_import_export_demo.action_report_sale_order
#: model:ir.ui.menu,name:excel_import_export_demo.menu_report_sale_order
msgid "Sample Sales Report"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__smart_search
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__smart_search
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__smart_search
msgid "Smart Search"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__state
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__state
msgid "State"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__template_id
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__template_id
msgid "Template"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,help:excel_import_export_demo.field_report_partner_list__results
msgid "Use compute fields, so there is nothing store in database"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,help:excel_import_export_demo.field_report_sale_order__results
msgid "Use compute fields, so there is nothing stored in database"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__user_can_see_changeset
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__user_can_see_changeset
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__user_can_see_changeset
msgid "User Can See Changeset"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model,name:excel_import_export_demo.model_report_crm_lead
msgid "Wizard for report.crm.lead"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model,name:excel_import_export_demo.model_report_partner_list
msgid "Wizard for report.partner.list"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model,name:excel_import_export_demo.model_report_sale_order
msgid "Wizard for report.sale.order"
msgstr ""

View File

@ -0,0 +1,277 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * excel_import_export_demo
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2019-09-01 04:34+0000\n"
"Last-Translator: 黎伟杰 <674416404@qq.com>\n"
"Language-Team: none\n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 3.8\n"
#. module: excel_import_export_demo
#: model:ir.actions.report,print_report_name:excel_import_export_demo.action_report_saleorder_excel
msgid ""
"(object.state in ('draft', 'sent') and 'Quotation - %s' % (object.name)) or "
"'Order - %s' % (object.name)"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,help:excel_import_export_demo.field_report_crm_lead__state
#: model:ir.model.fields,help:excel_import_export_demo.field_report_sale_order__state
msgid ""
"* Choose: wizard show in user selection mode\n"
"* Get: wizard show results from user action"
msgstr ""
"* 选择:用户选择模式下的向导显示\n"
"* 获取:向导显示用户操作的结果"
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__choose_template
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__choose_template
msgid "Allow Choose Template"
msgstr "添加表单部分"
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__assigned_attachment_ids
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__assigned_attachment_ids
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__assigned_attachment_ids
msgid "Assigned Attachments"
msgstr ""
#. module: excel_import_export_demo
#: model_terms:ir.ui.view,arch_db:excel_import_export_demo.partner_list_wizard
msgid "Cancel"
msgstr "取消"
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__changeset_change_ids
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__changeset_change_ids
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__changeset_change_ids
msgid "Changeset Changes"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__changeset_ids
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__changeset_ids
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__changeset_ids
msgid "Changesets"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__count_pending_changeset_changes
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__count_pending_changeset_changes
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__count_pending_changeset_changes
msgid "Count Pending Changeset Changes"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__count_pending_changesets
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__count_pending_changesets
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__count_pending_changesets
msgid "Count Pending Changesets"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__create_uid
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__create_uid
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__create_uid
msgid "Created by"
msgstr "创建者"
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__create_date
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__create_date
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__create_date
msgid "Created on"
msgstr "创建时间"
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__display_name
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__display_name
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__display_name
msgid "Display Name"
msgstr "显示名称"
#. module: excel_import_export_demo
#: model_terms:ir.ui.view,arch_db:excel_import_export_demo.partner_list_wizard
msgid "Execute"
msgstr "执行"
#. module: excel_import_export_demo
#: model:ir.actions.act_window,name:excel_import_export_demo.action_purchase_order_export_xlsx
#: model:ir.actions.act_window,name:excel_import_export_demo.action_sale_oder_export_xlsx
msgid "Export Excel"
msgstr "导出Excel"
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__data
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__data
msgid "File"
msgstr "文件"
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__name
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__name
msgid "File Name"
msgstr "文件名"
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__id
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__id
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__id
msgid "ID"
msgstr "ID"
#. module: excel_import_export_demo
#: model:ir.actions.act_window,name:excel_import_export_demo.action_purchase_order_import_xlsx
#: model:ir.actions.act_window,name:excel_import_export_demo.action_sale_oder_import_xlsx
msgid "Import Excel"
msgstr "导入Excel"
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead____last_update
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list____last_update
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order____last_update
msgid "Last Modified on"
msgstr "最后修改时间"
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__write_uid
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__write_uid
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__write_uid
msgid "Last Updated by"
msgstr "最后更新者"
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__write_date
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__write_date
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__write_date
msgid "Last Updated on"
msgstr "最后更新时间"
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__partner_ids
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__partner_id
msgid "Partner"
msgstr "业务伙伴"
#. module: excel_import_export_demo
#: model:ir.actions.report,name:excel_import_export_demo.action_report_partner_excel
msgid "Partner List (.xlsx)"
msgstr "业务伙伴名单(.xlsx)"
#. module: excel_import_export_demo
#: model:ir.actions.act_window,name:excel_import_export_demo.action_report_partner_list
#: model:ir.ui.menu,name:excel_import_export_demo.menu_report_partner_list
msgid "Partner List Report"
msgstr "业务伙伴名单报告"
#. module: excel_import_export_demo
#: model:ir.actions.report,name:excel_import_export_demo.action_report_saleorder_excel
msgid "Quotation / Order (.xlsx)"
msgstr "报价单/订单 (.xlsx)"
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__results
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__results
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__results
msgid "Results"
msgstr "结果"
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__revenue_by_country
msgid "Revenue By Country"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__revenue_by_team
msgid "Revenue By Team"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__team_id
msgid "Sales Team"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.actions.act_window,name:excel_import_export_demo.action_import_sale_order
#: model:ir.ui.menu,name:excel_import_export_demo.menu_import_sale_order
msgid "Sample Import Sale Order"
msgstr "导入销售订单样本"
#. module: excel_import_export_demo
#: model:ir.actions.act_window,name:excel_import_export_demo.action_report_crm_lead
#: model:ir.ui.menu,name:excel_import_export_demo.menu_report_crm_lead
msgid "Sample Lead Report"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.actions.act_window,name:excel_import_export_demo.action_report_sale_order
#: model:ir.ui.menu,name:excel_import_export_demo.menu_report_sale_order
msgid "Sample Sales Report"
msgstr "销售报告样本"
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__smart_search
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__smart_search
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__smart_search
msgid "Smart Search"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__state
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__state
msgid "State"
msgstr "状态"
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__template_id
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__template_id
msgid "Template"
msgstr "模板"
#. module: excel_import_export_demo
#: model:ir.model.fields,help:excel_import_export_demo.field_report_partner_list__results
msgid "Use compute fields, so there is nothing store in database"
msgstr "使用计算字段,因此数据库中没有任何存储"
#. module: excel_import_export_demo
#: model:ir.model.fields,help:excel_import_export_demo.field_report_sale_order__results
msgid "Use compute fields, so there is nothing stored in database"
msgstr "使用计算字段,因此数据库中没有任何内容"
#. module: excel_import_export_demo
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_crm_lead__user_can_see_changeset
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_partner_list__user_can_see_changeset
#: model:ir.model.fields,field_description:excel_import_export_demo.field_report_sale_order__user_can_see_changeset
msgid "User Can See Changeset"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model,name:excel_import_export_demo.model_report_crm_lead
msgid "Wizard for report.crm.lead"
msgstr ""
#. module: excel_import_export_demo
#: model:ir.model,name:excel_import_export_demo.model_report_partner_list
msgid "Wizard for report.partner.list"
msgstr "业务伙伴名单报告向导"
#. module: excel_import_export_demo
#: model:ir.model,name:excel_import_export_demo.model_report_sale_order
msgid "Wizard for report.sale.order"
msgstr "销售订单报告向导"
#~ msgid "Choose"
#~ msgstr "选择"
#~ msgid "Get"
#~ msgstr "获取"

View File

@ -0,0 +1,41 @@
<?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>
<record id="action_purchase_order_export_xlsx" model="ir.actions.act_window">
<field name="name">Export Excel</field>
<field name="res_model">export.xlsx.wizard</field>
<field name="binding_view_types">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="context">
{
'template_domain': [('res_model', '=', 'purchase.order'),
('fname', '=', 'purchase_order.xlsx'),
('gname', '=', False)],
}
</field>
</record>
<record id="action_purchase_order_import_xlsx" model="ir.actions.act_window">
<field name="name">Import Excel</field>
<field name="res_model">import.xlsx.wizard</field>
<field name="binding_view_types">form</field>
<field name="binding_model_id" ref="purchase.model_purchase_order" />
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="context">
{
'template_domain': [('res_model', '=', 'purchase.order'),
('fname', '=', 'purchase_order.xlsx'),
('gname', '=', False)],
'template_context': {},
'template_import_states': [],
}
</field>
</record>
</odoo>

View File

@ -0,0 +1,70 @@
<?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>
<record id="purchase_order_xlsx_template" model="xlsx.template">
<field name="res_model">purchase.order</field>
<field name="fname">purchase_order.xlsx</field>
<field name="name">Purchase Order Template (import/export)</field>
<field name="description">Sample Purchase Order Template for testing</field>
<field
name="import_action_id"
eval="ref('action_purchase_order_import_xlsx')"
/>
<field
name="export_action_id"
eval="ref('action_purchase_order_export_xlsx')"
/>
<field name="input_instruction">
{
'__EXPORT__': {
'purchase': {
'_HEAD_': {
'B1': 'partner_id.contact_address',
'F4': 'display_name',
'H4': 'date_order',
'B8': 'user_id.display_name',
'B10': 'company_id.name',
'B12': '${"%s, %s, %s" % (object.company_id.street, object.company_id.city, object.company_id.state_id.name)}',
'B15': 'company_id.phone',
'B17': 'company_id.email',
'E8': 'partner_id.name',
'E10': 'partner_id.parent_id.name',
'E15': 'partner_id.phone',
'E17': 'partner_id.email',
'H20': 'date_planned${value or ""}#{style=date}',
'I37': 'amount_untaxed#{style=number}',
'O38': 'amount_tax#{style=number}',
'I39': 'amount_total#{style=number}',
},
'order_line': {
'B22': 'product_id.default_code',
'C22': 'name',
'E22': 'product_qty${value or 0}#{style=number}',
'F22': 'product_uom.name',
'G22': 'price_unit${value or 0}#{style=number}',
'H22': 'taxes_id.name',
}
}
},
'__IMPORT__': {
'purchase': {
'order_line': {
'B22': 'product_id',
'C22': 'name',
'E22': 'product_qty',
'F22': 'product_uom',
'G22': 'price_unit',
'H22': 'taxes_id',
'I22': 'date_planned${time.strftime("%Y-%m-%d %H:%M:%S")}',
}
}
},
}
</field>
</record>
<function model="xlsx.template" name="load_xlsx_template">
<value eval="[ref('purchase_order_xlsx_template')]" />
</function>
</odoo>

View File

@ -0,0 +1,41 @@
<?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>
<record id="action_sale_oder_export_xlsx" model="ir.actions.act_window">
<field name="name">Export Excel</field>
<field name="res_model">export.xlsx.wizard</field>
<field name="binding_view_types">list,form</field>
<field name="binding_model_id" ref="sale.model_sale_order" />
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="context">
{
'template_domain': [('res_model', '=', 'sale.order'),
('fname', '=', 'sale_order.xlsx'),
('gname', '=', False)],
}
</field>
</record>
<record id="action_sale_oder_import_xlsx" model="ir.actions.act_window">
<field name="name">Import Excel</field>
<field name="res_model">import.xlsx.wizard</field>
<field name="binding_view_types">form</field>
<field name="binding_model_id" ref="sale.model_sale_order" />
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="context">
{
'template_domain': [('res_model', '=', 'sale.order'),
('fname', '=', 'sale_order.xlsx'),
('gname', '=', False)],
'template_context': {},
'template_import_states': [],
}
</field>
</record>
</odoo>

View File

@ -0,0 +1,51 @@
<?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>
<record id="sale_order_xlsx_template" model="xlsx.template">
<field name="res_model">sale.order</field>
<field name="fname">sale_order.xlsx</field>
<field name="name">Sale Order Template (import/export)</field>
<field name="description">Sample Sales Order Template for testing</field>
<field name="import_action_id" eval="ref('action_sale_oder_export_xlsx')" />
<field name="export_action_id" eval="ref('action_sale_oder_import_xlsx')" />
<field name="input_instruction">
{
'__EXPORT__': {
'sale_order': {
'_HEAD_': {
'B2': 'partner_id.display_name${value or ""}#{align=left;style=text}',
'B3': 'name${value or ""}#{align=left;style=text}',
},
'order_line': {
'A6': 'product_id.display_name${value or ""}#{style=text}',
'B6': 'name${value or ""}#{style=text}',
'C6': 'product_uom_qty${value or 0}#{style=number}',
'D6': 'product_uom.name${value or ""}#{style=text}',
'E6': 'price_unit${value or 0}#{style=number}',
'F6': 'tax_id${value and ",".join([x.display_name for x in value]) or ""}',
'G6': 'price_subtotal${value or 0}#{style=number}',
}
}
},
'__IMPORT__': {
'sale_order': {
'_NODEL_order_line': {
'A6': 'product_id',
'B6': 'name',
'C6': 'product_uom_qty',
'D6': 'product_uom',
'E6': 'price_unit',
'F6': 'tax_id',
}
}
},
# '__POST_IMPORT__': '${object.post_import_do_something()}',
}
</field>
</record>
<function model="xlsx.template" name="load_xlsx_template">
<value eval="[ref('sale_order_xlsx_template')]" />
</function>
</odoo>

View File

@ -0,0 +1,24 @@
<?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>
<record id="action_import_sale_order" model="ir.actions.act_window">
<field name="name">Sample Import Sale Order</field>
<field name="res_model">import.xlsx.wizard</field>
<field name="binding_view_types">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="context">{
'template_domain': [('res_model', '=', 'sale.order'),
('fname', '=', 'import_sale_order.xlsx'),
('gname', '=', False)], }
</field>
</record>
<menuitem
id="menu_import_sale_order"
parent="excel_import_export.menu_excel_import_export"
action="action_import_sale_order"
sequence="30"
/>
</odoo>

View File

@ -0,0 +1,35 @@
<?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>
<record id="import_sale_order_xlsx_template" model="xlsx.template">
<field name="res_model">sale.order</field>
<field name="fname">import_sale_order.xlsx</field>
<field name="name">Import Sale Order Template</field>
<field name="description">Sample Import Sales Order Tempalte for testing</field>
<field name="redirect_action" ref="sale.action_orders" />
<field name="input_instruction">
{
'__IMPORT__': {
'sale_order': {
'_HEAD_': {
'B2': 'partner_id',
},
'order_line': {
'A6': 'product_id',
'B6': 'name',
'C6': 'product_uom_qty',
'D6': 'product_uom',
'E6': 'price_unit',
'F6': 'tax_id',
}
}
},
}
</field>
</record>
<function model="xlsx.template" name="load_xlsx_template">
<value eval="[ref('import_sale_order_xlsx_template')]" />
</function>
</odoo>

View File

@ -0,0 +1 @@
* Kitti Upariphutthiphong. <kittiu@gmail.com> (http://ecosoft.co.th)

View File

@ -0,0 +1,7 @@
This module provide some example use case for excel_import_export
1. Import/Export Sales Order (import_export_sale_order)
2. Import New Sales Orders (import_sale_orders)
3. Sales Orders Report (report_sale_order)
4. Print Quoation / Order (.xlsx) (report_action/sale_order)
5. Run Partner List Report (report_action/partner_list)

View File

@ -0,0 +1,3 @@
To install this module, you need to install **excel_import_export**
Then, simply install **excel_import_export_demo**.

View File

@ -0,0 +1,19 @@
**Example 1:** Export/Import Excel on existing document
To test this use case, go to any Sales Order and use Export Excel or Import Excel in action menu.
**Example 2:** Import Excel Files
To test this use case, go to Settings > Excel Import/Export > Sample Import Sales Order
**Example 3:** Create Excel Report
To test this use case, go to Settings > Excel Import/Export > Sample Sales Report
**Example 4:** Printout Excel on existing document, using report action
To test this use case, go to any Sales Order and click print "Quotation / Order (.xlsx)".
**Example 5:** Run Partner List Report, using report action
To test this use case, go to menu Sales > Reporting > Partner List Report

View File

@ -0,0 +1,4 @@
# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
from . import partner_list

View File

@ -0,0 +1,4 @@
# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
from . import report_partner_list

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="action_report_partner_excel" model="ir.actions.report">
<field name="name">Partner List (.xlsx)</field>
<field name="model">report.partner.list</field>
<field name="report_type">excel</field>
<field name="report_name">partner_list.xlsx</field>
<field name="report_file">partner_list</field>
</record>
</odoo>

View File

@ -0,0 +1,26 @@
# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
from odoo import fields, models
class ReportPartnerList(models.TransientModel):
_name = "report.partner.list"
_description = "Wizard for report.partner.list"
partner_ids = fields.Many2many(comodel_name="res.partner")
results = fields.Many2many(
"res.partner",
compute="_compute_results",
help="Use compute fields, so there is nothing store in database",
)
def _compute_results(self):
"""On the wizard, result will be computed and added to results line
before export to excel by report_excel action
"""
self.ensure_one()
domain = []
if self.partner_ids:
domain.append(("id", "in", self.partner_ids.ids))
self.results = self.env["res.partner"].search(domain, order="id")

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="partner_list_wizard" model="ir.ui.view">
<field name="name">partner.list.wizard</field>
<field name="model">report.partner.list</field>
<field name="arch" type="xml">
<form>
<group>
<group>
<field name="partner_ids" widget="many2many_tags" />
</group>
<group>
</group>
</group>
<footer>
<button
name='%(excel_import_export_demo.action_report_partner_excel)d'
type='action'
string='Execute'
class='oe_highlight'
/>
<button special='cancel' string='Cancel' />
</footer>
</form>
</field>
</record>
<record id='action_report_partner_list' model='ir.actions.act_window'>
<field name='name'>Partner List Report</field>
<field name='res_model'>report.partner.list</field>
<field name='binding_view_types'>form</field>
<field name='view_mode'>form</field>
<field name='target'>new</field>
</record>
<menuitem
id="menu_report_partner_list"
parent="sale.menu_sale_report"
action="action_report_partner_list"
name="Partner List Report"
/>
</odoo>

View File

@ -0,0 +1,2 @@
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
access_report_partner_list,access_report_partner_list,model_report_partner_list,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_report_partner_list access_report_partner_list model_report_partner_list base.group_user 1 1 1 1

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="partner_list_xlsx_template" model="xlsx.template">
<field name="res_model">report.partner.list</field>
<field name="fname">partner_list.xlsx</field>
<field name="name">Partner List Report Template</field>
<field
name="description"
>Sample Partner List Report Template for testing</field>
<field name="input_instruction">
{
'__EXPORT__': {
1: {
'results': {
'A4': 'id',
'B4': 'name${value or ""}#{style=text}',
'C4': 'phone${value or ""}#{style=text}',
'D4': 'email${value or ""}#{style=text}',
}
}
},
}
</field>
</record>
<function model="xlsx.template" name="load_xlsx_template">
<value eval="[ref('partner_list_xlsx_template')]" />
</function>
</odoo>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="action_report_saleorder_excel" model="ir.actions.report">
<field name="name">Quotation / Order (.xlsx)</field>
<field name="model">sale.order</field>
<field name="report_type">excel</field>
<field name="report_name">sale_order_form.xlsx</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
name="print_report_name"
>(object.state in ('draft', 'sent') and 'Quotation - %s' % (object.name)) or 'Order - %s' % (object.name)</field>
</record>
</odoo>

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="sale_order_excel_template" model="xlsx.template">
<field name="res_model">sale.order</field>
<field name="fname">sale_order_form.xlsx</field>
<field name="name">Sale Order Template</field>
<field name="description">Sample Sales Order Template for testing</field>
<field name="input_instruction">
{
'__EXPORT__': {
'sale_order': {
'_HEAD_': {
'B2': 'partner_id.display_name${value or ""}#{align=left;style=text}',
'B3': 'name${value or ""}#{align=left;style=text}',
},
'order_line': {
'A6': 'product_id.display_name${value or ""}#{style=text}',
'B6': 'name${value or ""}#{style=text}',
'C6': 'product_uom_qty${value or 0}#{style=number}',
'D6': 'product_uom.name${value or ""}#{style=text}',
'E6': 'price_unit${value or 0}#{style=number}',
'F6': 'tax_id${value and ",".join([x.display_name for x in value]) or ""}',
'G6': 'price_subtotal${value or 0}#{style=number}@{sum}',
}
}
},
}
</field>
</record>
<function model="xlsx.template" name="load_xlsx_template">
<value eval="[ref('sale_order_excel_template')]" />
</function>
</odoo>

View File

@ -0,0 +1,4 @@
# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
from . import report_crm_lead

View File

@ -0,0 +1,65 @@
# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
from odoo import fields, models
class ReportCRMLead(models.TransientModel):
_name = "report.crm.lead"
_description = "Wizard for report.crm.lead"
_inherit = "xlsx.report"
# Search Criteria
team_id = fields.Many2one("crm.team", string="Sales Team")
# Report Result, crm.lead
results = fields.Many2many(
"crm.lead",
compute="_compute_results",
)
revenue_by_country = fields.Many2many(
"crm.lead",
compute="_compute_revenue_by_country",
)
revenue_by_team = fields.Many2many(
"crm.lead",
compute="_compute_revenue_by_team",
)
def _compute_results(self):
self.ensure_one()
domain = []
if self.team_id:
domain += [("team_id", "=", self.team_id.id)]
self.results = self.env["crm.lead"].search(domain)
def _compute_revenue_by_country(self):
self.ensure_one()
domain = []
if self.team_id:
domain += [("team_id", "=", self.team_id.id)]
results = self.env["crm.lead"].read_group(
domain,
["country_id", "expected_revenue"],
["country_id"],
orderby="country_id",
)
for row in results:
self.revenue_by_country += self.env["crm.lead"].new(
{
"country_id": row["country_id"],
"expected_revenue": row["expected_revenue"],
}
)
def _compute_revenue_by_team(self):
self.ensure_one()
domain = []
if self.team_id:
domain += [("team_id", "=", self.team_id.id)]
results = self.env["crm.lead"].read_group(
domain, ["team_id", "expected_revenue"], ["team_id"], orderby="team_id"
)
for row in results:
self.revenue_by_team += self.env["crm.lead"].new(
{"team_id": row["team_id"], "expected_revenue": row["expected_revenue"]}
)

View File

@ -0,0 +1,39 @@
<?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>
<record id="report_crm_lead" model="ir.ui.view">
<field name="name">report.crm.lead</field>
<field name="model">report.crm.lead</field>
<field name="inherit_id" ref="excel_import_export.xlsx_report_view" />
<field name="mode">primary</field>
<field name="arch" type="xml">
<xpath expr="//group[@name='criteria']" position="inside">
<group>
<field name="team_id" />
</group>
<group>
</group>
</xpath>
</field>
</record>
<record id="action_report_crm_lead" model="ir.actions.act_window">
<field name="name">Sample Lead Report</field>
<field name="res_model">report.crm.lead</field>
<field name="binding_view_types">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="context">
{'template_domain': [('res_model', '=', 'report.crm.lead'),
('fname', '=', 'report_crm_lead.xlsx'),
('gname', '=', False)]}
</field>
</record>
<menuitem
id="menu_report_crm_lead"
parent="excel_import_export.menu_excel_import_export"
action="action_report_crm_lead"
sequence="20"
/>
</odoo>

View File

@ -0,0 +1,2 @@
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
access_report_crm_lead,access_report_crm_lead,model_report_crm_lead,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_report_crm_lead access_report_crm_lead model_report_crm_lead base.group_user 1 1 1 1

View File

@ -0,0 +1,45 @@
<?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>
<record id="report_crm_lead_template" model="xlsx.template">
<field name="res_model">report.crm.lead</field>
<field name="fname">report_crm_lead.xlsx</field>
<field name="name">Report CRM Lead Template</field>
<field name="description">Sample Report Sales Order Tempalte for testing</field>
<field name="input_instruction">
{
'__EXPORT__': {
'crm_lead': {
'results': {
'A4': 'name',
'B4': 'partner_id.name',
'C4': 'country_id.name',
'D4': 'activity_date_deadline${value or ""}#{style=date}',
'E4': 'activity_summary',
'F4': 'stage_id.name',
'G4': 'expected_revenue${value or 0}#{style=number}',
'H4': 'team_id.name',
},
},
'revenue_by_country': {
'revenue_by_country': {
'A3': 'country_id.name',
'B3': 'expected_revenue${value or 0}#{style=number}'
},
},
'revenue_by_team': {
'revenue_by_team': {
'A3': 'team_id.name',
'B3': 'expected_revenue${value or 0}#{style=number}'
},
},
},
}
</field>
</record>
<function model="xlsx.template" name="load_xlsx_template">
<value eval="[ref('report_crm_lead_template')]" />
</function>
</odoo>

View File

@ -0,0 +1,4 @@
# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
from . import report_sale_order

View File

@ -0,0 +1,30 @@
# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
from odoo import fields, models
class ReportSaleOrder(models.TransientModel):
_name = "report.sale.order"
_description = "Wizard for report.sale.order"
_inherit = "xlsx.report"
# Search Criteria
partner_id = fields.Many2one("res.partner", string="Partner")
# Report Result, sale.order
results = fields.Many2many(
"sale.order",
compute="_compute_results",
help="Use compute fields, so there is nothing stored in database",
)
def _compute_results(self):
"""On the wizard, result will be computed and added to results line
before export to excel, by using xlsx.export
"""
self.ensure_one()
Result = self.env["sale.order"]
domain = []
if self.partner_id:
domain += [("partner_id", "=", self.partner_id.id)]
self.results = Result.search(domain)

View File

@ -0,0 +1,39 @@
<?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>
<record id="report_sale_order" model="ir.ui.view">
<field name="name">report.sale.order</field>
<field name="model">report.sale.order</field>
<field name="inherit_id" ref="excel_import_export.xlsx_report_view" />
<field name="mode">primary</field>
<field name="arch" type="xml">
<xpath expr="//group[@name='criteria']" position="inside">
<group>
<field name="partner_id" />
</group>
<group>
</group>
</xpath>
</field>
</record>
<record id="action_report_sale_order" model="ir.actions.act_window">
<field name="name">Sample Sales Report</field>
<field name="res_model">report.sale.order</field>
<field name="binding_view_types">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="context">
{'template_domain': [('res_model', '=', 'report.sale.order'),
('fname', '=', 'report_sale_order.xlsx'),
('gname', '=', False)]}
</field>
</record>
<menuitem
id="menu_report_sale_order"
parent="excel_import_export.menu_excel_import_export"
action="action_report_sale_order"
sequence="20"
/>
</odoo>

View File

@ -0,0 +1,2 @@
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
access_report_sale_order,access_report_sale_order,model_report_sale_order,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_report_sale_order access_report_sale_order model_report_sale_order base.group_user 1 1 1 1

View File

@ -0,0 +1,33 @@
<?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>
<record id="report_sale_order_template" model="xlsx.template">
<field name="res_model">report.sale.order</field>
<field name="fname">report_sale_order.xlsx</field>
<field name="name">Report Sale Order Template</field>
<field name="description">Sample Report Sales Order Tempalte for testing</field>
<field name="input_instruction">
{
'__EXPORT__': {
1: {
'_HEAD_': {
'B2': 'partner_id.display_name${value or ""}#{align=left;style=text}',
},
'_EXTEND_results': {
'A5': 'name${value or ""}#{style=text}',
'B5': 'date_order${value or ""}#{style=date}',
'C5': 'amount_untaxed${value or 0}#{style=number}@{sum}',
'D5': 'amount_tax${value or 0}#{style=number}@{sum}',
'E5': 'amount_total${value or 0}#{style=number}@{sum}',
},
},
},
}
</field>
</record>
<function model="xlsx.template" name="load_xlsx_template">
<value eval="[ref('report_sale_order_template')]" />
</function>
</odoo>

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@ -0,0 +1,448 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: http://docutils.sourceforge.net/" />
<title>Excel Import/Export/Report Demo</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="excel-import-export-report-demo">
<h1 class="title">Excel Import/Export/Report Demo</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/server-tools/tree/16.0/excel_import_export_demo"><img alt="OCA/server-tools" src="https://img.shields.io/badge/github-OCA%2Fserver--tools-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/server-tools-16-0/server-tools-16-0-excel_import_export_demo"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runbot.odoo-community.org/runbot/149/16.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p>
<p>This module provide some example use case for excel_import_export</p>
<ol class="arabic simple">
<li>Import/Export Sales Order (import_export_sale_order)</li>
<li>Import New Sales Orders (import_sale_orders)</li>
<li>Sales Orders Report (report_sale_order)</li>
<li>Print Quoation / Order (.xlsx) (report_action/sale_order)</li>
<li>Run Partner List Report (report_action/partner_list)</li>
</ol>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#installation" id="id1">Installation</a></li>
<li><a class="reference internal" href="#usage" id="id2">Usage</a></li>
<li><a class="reference internal" href="#bug-tracker" id="id3">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id4">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id5">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id6">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="id7">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="installation">
<h1><a class="toc-backref" href="#id1">Installation</a></h1>
<p>To install this module, you need to install <strong>excel_import_export</strong></p>
<p>Then, simply install <strong>excel_import_export_demo</strong>.</p>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#id2">Usage</a></h1>
<p><strong>Example 1:</strong> Export/Import Excel on existing document</p>
<p>To test this use case, go to any Sales Order and use Export Excel or Import Excel in action menu.</p>
<p><strong>Example 2:</strong> Import Excel Files</p>
<p>To test this use case, go to Settings &gt; Excel Import/Export &gt; Sample Import Sales Order</p>
<p><strong>Example 3:</strong> Create Excel Report</p>
<p>To test this use case, go to Settings &gt; Excel Import/Export &gt; Sample Sales Report</p>
<p><strong>Example 4:</strong> Printout Excel on existing document, using report action</p>
<p>To test this use case, go to any Sales Order and click print “Quotation / Order (.xlsx)”.</p>
<p><strong>Example 5:</strong> Run Partner List Report, using report action</p>
<p>To test this use case, go to menu Sales &gt; Reporting &gt; Partner List Report</p>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#id3">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/server-tools/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/server-tools/issues/new?body=module:%20excel_import_export_demo%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#id4">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#id5">Authors</a></h2>
<ul class="simple">
<li>Ecosoft</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#id6">Contributors</a></h2>
<ul class="simple">
<li>Kitti Upariphutthiphong. &lt;<a class="reference external" href="mailto:kittiu&#64;gmail.com">kittiu&#64;gmail.com</a>&gt; (<a class="reference external" href="http://ecosoft.co.th">http://ecosoft.co.th</a>)</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#id7">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
<p>Current <a class="reference external" href="https://odoo-community.org/page/maintainer-role">maintainer</a>:</p>
<p><a class="reference external" href="https://github.com/kittiu"><img alt="kittiu" src="https://github.com/kittiu.png?size=40px" /></a></p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/server-tools/tree/16.0/excel_import_export_demo">OCA/server-tools</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,5 @@
# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
from . import test_xlsx_template
from . import test_xlsx_import_export
from . import test_xlsx_report

Binary file not shown.

View File

@ -0,0 +1,135 @@
# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
from odoo.tests.common import SingleTransactionCase
class TestExcelImportExport(SingleTransactionCase):
@classmethod
def setUpClass(cls):
super(TestExcelImportExport, cls).setUpClass()
@classmethod
def setUpXLSXTemplate(cls):
cls.template_obj = cls.env["xlsx.template"]
# Create xlsx.template using input_instruction
input_instruction = {
"__EXPORT__": {
"sale_order": {
"_HEAD_": {
"B2": 'partner_id.display_name${value or ""}'
"#{align=left;style=text}",
"B3": 'name${value or ""}#{align=left;style=text}',
},
"order_line": {
"A6": 'product_id.display_name${value or ""}' "#{style=text}",
"B6": 'name${value or ""}#{style=text}',
"C6": "product_uom_qty${value or 0}#{style=number}",
"D6": 'product_uom.name${value or ""}#{style=text}',
"E6": "price_unit${value or 0}#{style=number}",
"F6": 'tax_id${value and ","'
'.join([x.display_name for x in value]) or ""}',
"G6": "price_subtotal${value or 0}#{style=number}",
},
}
},
"__IMPORT__": {
"sale_order": {
"order_line": {
"A6": "product_id",
"B6": "name",
"C6": "product_uom_qty",
"D6": "product_uom",
"E6": "price_unit",
"F6": "tax_id",
}
}
},
# '__POST_IMPORT__': '${object.post_import_do_something()}',
}
vals = {
"res_model": "sale.order",
"fname": "sale_order.xlsx",
"name": "Sale Order Template",
"description": "Sample Sales Order Template for testing",
"input_instruction": str(input_instruction),
}
cls.sample_template = cls.template_obj.create(vals)
@classmethod
def setUpSaleOrder(cls):
cls.setUpPrepSaleOrder()
# Create a Sales Order
product_line = {
"name": cls.product_order.name,
"product_id": cls.product_order.id,
"product_uom_qty": 2,
"product_uom": cls.product_order.uom_id.id,
"price_unit": cls.product_order.list_price,
"tax_id": False,
}
cls.sale_order = cls.env["sale.order"].create(
{
"partner_id": cls.partner.id,
"order_line": [(0, 0, product_line), (0, 0, product_line)],
}
)
@classmethod
def setUpManySaleOrder(cls):
cls.setUpPrepSaleOrder()
# Create a Sales Order
product_line = {
"name": cls.product_order.name,
"product_id": cls.product_order.id,
"product_uom_qty": 2,
"product_uom": cls.product_order.uom_id.id,
"price_unit": cls.product_order.list_price,
"tax_id": False,
}
for _i in range(10):
cls.env["sale.order"].create(
{
"partner_id": cls.partner.id,
"order_line": [(0, 0, product_line), (0, 0, product_line)],
}
)
@classmethod
def setUpPrepSaleOrder(cls):
categ_ids = cls.env["res.partner.category"].search([]).ids
cls.partner = cls.env["res.partner"].create(
{"name": "Test Partner", "category_id": [(6, 0, categ_ids)]}
)
# Create a Product
cls.account_income_product = cls.env["account.account"].create(
{
"code": "PROD111",
"name": "Income - Test Account",
"account_type": "expense_direct_cost",
}
)
# Create category
cls.product_category = cls.env["product.category"].create(
{
"name": "Product Category with Income account",
"property_account_income_categ_id": cls.account_income_product.id,
}
)
# Products
uom_unit = cls.env.ref("uom.product_uom_unit")
cls.product_order = cls.env["product.product"].create(
{
"name": "Test Product",
"standard_price": 235.0,
"list_price": 280.0,
"type": "consu",
"uom_id": uom_unit.id,
"uom_po_id": uom_unit.id,
"invoice_policy": "order",
"expense_policy": "no",
"default_code": "PROD_ORDER",
"service_type": "manual",
"taxes_id": False,
"categ_id": cls.product_category.id,
}
)

View File

@ -0,0 +1,78 @@
# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
from odoo.tests.common import Form
from .test_common import TestExcelImportExport
class TestXLSXImportExport(TestExcelImportExport):
@classmethod
def setUpClass(cls):
super(TestExcelImportExport, cls).setUpClass()
def test_xlsx_export_import(self):
"""Test Export Excel from Sales Order"""
# Create Sales Order
self.setUpSaleOrder()
# ----------- EXPORT ---------------
ctx = {
"active_model": "sale.order",
"active_ids": [self.sale_order.id],
"template_domain": [
("res_model", "=", "sale.order"),
("fname", "=", "sale_order.xlsx"),
("gname", "=", False),
],
}
f = Form(self.env["export.xlsx.wizard"].with_context(**ctx))
export_wizard = f.save()
# Test whether it loads correct template
self.assertEqual(
export_wizard.template_id,
self.env.ref("excel_import_export_demo.sale_order_xlsx_template"),
)
# Export excel
export_wizard.action_export()
self.assertTrue(export_wizard.data)
self.export_file = export_wizard.data
# ----------- IMPORT ---------------
ctx = {
"active_model": "sale.order",
"active_id": self.sale_order.id,
"template_domain": [
("res_model", "=", "sale.order"),
("fname", "=", "sale_order.xlsx"),
("gname", "=", False),
],
"template_context": {"state": "draft"},
}
with Form(self.env["import.xlsx.wizard"].with_context(**ctx)) as f:
f.import_file = self.export_file
import_wizard = f.save()
# Test sample template
import_wizard.get_import_sample()
self.assertTrue(import_wizard.datas)
# Test whether it loads correct template
self.assertEqual(
import_wizard.template_id,
self.env.ref("excel_import_export_demo.sale_order_xlsx_template"),
)
# Import Excel
import_wizard.action_import()
def test_add_remove_export_import_action(self):
"""On the template itself, test add / remove action"""
template = self.env.ref("excel_import_export_demo.sale_order_xlsx_template")
self.assertTrue(template.import_action_id)
self.assertTrue(template.export_action_id)
# Remove actions
template.remove_export_action()
template.remove_import_action()
self.assertFalse(template.import_action_id)
self.assertFalse(template.export_action_id)
# Add actions back again
template.add_export_action()
template.add_import_action()
self.assertTrue(template.import_action_id)
self.assertTrue(template.export_action_id)

View File

@ -0,0 +1,34 @@
# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
from odoo.tests.common import Form
from .test_common import TestExcelImportExport
class TestXLSXReport(TestExcelImportExport):
@classmethod
def setUpClass(cls):
super(TestXLSXReport, cls).setUpClass()
def test_xlsx_report(self):
"""Test Report from Sales Order"""
# Create Many Sales Orders
self.setUpManySaleOrder()
ctx = {
"template_domain": [
("res_model", "=", "report.sale.order"),
("fname", "=", "report_sale_order.xlsx"),
("gname", "=", False),
]
}
with Form(self.env["report.sale.order"].with_context(**ctx)) as f:
f.partner_id = self.partner
report_wizard = f.save()
# Test whether it loads correct template
self.assertEqual(
report_wizard.template_id,
self.env.ref("excel_import_export_demo." "report_sale_order_template"),
)
# Report excel
report_wizard.report_xlsx()
self.assertTrue(report_wizard.data)

View File

@ -0,0 +1,91 @@
# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
from ast import literal_eval
from odoo.exceptions import UserError
from odoo.tests.common import Form
from .test_common import TestExcelImportExport
class TestXLSXTemplate(TestExcelImportExport):
@classmethod
def setUpClass(cls):
super(TestExcelImportExport, cls).setUpClass()
def test_xlsx_template(self):
"""Test XLSX Template input and output instruction"""
self.setUpXLSXTemplate()
instruction_dict = literal_eval(self.sample_template.instruction)
self.assertDictEqual(
instruction_dict,
{
"__EXPORT__": {
"sale_order": {
"_HEAD_": {
"B2": 'partner_id.display_name${value or ""}'
"#{align=left;style=text}#??",
"B3": 'name${value or ""}' "#{align=left;style=text}#??",
},
"order_line": {
"A6": 'product_id.display_name${value or ""}'
"#{style=text}#??",
"B6": 'name${value or ""}#{style=text}#??',
"C6": "product_uom_qty${value or 0}" "#{style=number}#??",
"D6": 'product_uom.name${value or ""}' "#{style=text}#??",
"E6": "price_unit${value or 0}#{style=number}#??",
"F6": 'tax_id${value and ",".join([x.display_name '
'for x in value]) or ""}#{}#??',
"G6": "price_subtotal${value or 0}" "#{style=number}#??",
},
}
},
"__IMPORT__": {
"sale_order": {
"order_line": {
"A6": "product_id",
"B6": "name",
"C6": "product_uom_qty",
"D6": "product_uom",
"E6": "price_unit",
"F6": "tax_id",
}
}
},
"__POST_IMPORT__": False,
},
)
# Finally load excel file into this new template
self.assertFalse(self.sample_template.datas) # Not yet loaded
self.template_obj.load_xlsx_template(
[self.sample_template.id], addon="excel_import_export_demo"
)
self.assertTrue(self.sample_template.datas) # Loaded successfully
def test_xlsx_template_easy_reporting(self):
"""Test XLSX template using easy reporting option"""
sale_model = self.env["ir.model"].search([("model", "=", "sale.order")])
# Create the template
with Form(self.env["xlsx.template"]) as f:
f.name = "Test Easy Reporting"
f.use_report_wizard = True
f.result_model_id = sale_model
template = f.save()
self.assertEqual(template.res_model, "report.xlsx.wizard")
self.assertFalse(template.redirect_action, False)
self.assertTrue(template.result_field)
self.assertFalse(template.report_menu_id)
self.assertEqual(len(template.export_ids), 3)
with self.assertRaises(UserError):
template.add_report_menu()
template.fname = "test.xlsx"
# Add the menu
template.add_report_menu()
self.assertTrue(template.report_menu_id)
res = template.report_menu_id.action.read()[0]
ctx = literal_eval(res["context"])
f = Form(self.env[res["res_model"]].with_context(**ctx))
report_wizard = f.save()
res = report_wizard.action_report()
# Finally reture the report action
self.assertEqual(res["type"], "ir.actions.report")

View File

@ -3,5 +3,6 @@ astor
dataclasses dataclasses
mako mako
odoorpc odoorpc
openpyxl
openupgradelib openupgradelib
sentry_sdk>=1.17.0 sentry_sdk>=1.17.0

View File

@ -0,0 +1 @@
../../../../excel_import_export

View File

@ -0,0 +1,6 @@
import setuptools
setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)

View File

@ -0,0 +1 @@
../../../../excel_import_export_demo

View File

@ -0,0 +1,6 @@
import setuptools
setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)