commit
58475f0239
|
@ -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.
|
|
@ -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
|
|
@ -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"
|
||||
]
|
||||
},
|
||||
}
|
|
@ -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
|
|
@ -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
|
@ -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)
|
|
@ -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
|
|
@ -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)
|
|
@ -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%",
|
||||
},
|
||||
}
|
|
@ -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)
|
|
@ -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
|
|
@ -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",
|
||||
}
|
|
@ -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
|
|
@ -0,0 +1,2 @@
|
|||
* Kitti Upariphutthiphong. <kittiu@gmail.com> (http://ecosoft.co.th)
|
||||
* Saran Lim. <saranl@ecosoft.co.th> (http://ecosoft.co.th)
|
|
@ -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.
|
|
@ -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**
|
|
@ -0,0 +1 @@
|
|||
- Module extension e.g., excel_import_export_async, that add ability to execute as async process.
|
|
@ -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
|
|
@ -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
|
|
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
After Width: | Height: | Size: 9.2 KiB |
|
@ -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 > Excel Import/Export > 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, <act_window> with res_model=”export.xlsx.wizard” and src_model=”<document_model>”, and context[‘template_domain’] to locate the right template – actions.xml</li>
|
||||
<li>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</li>
|
||||
<li>Create/Design Excel Template File (.xlsx), in the template, name the underlining tab used for export/import – <file>.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 – <import file>.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 report’s menu, action, and add context[‘template_domain’] to locate the right template for this report – <report>.xml</li>
|
||||
<li>Create report’s 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 – <report.xml></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 – <report>.py</li>
|
||||
<li>Create/Design Excel Template File (.xlsx), in the template, name the underlining tab used for report results – <report_file>.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"><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">/></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 > Technical> Excel Import/Export > 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. <<a class="reference external" href="mailto:kittiu@gmail.com">kittiu@gmail.com</a>> (<a class="reference external" href="http://ecosoft.co.th">http://ecosoft.co.th</a>)</li>
|
||||
<li>Saran Lim. <<a class="reference external" href="mailto:saranl@ecosoft.co.th">saranl@ecosoft.co.th</a>> (<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 |
|
@ -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;
|
||||
});
|
|
@ -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>
|
|
@ -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>
|
|
@ -0,0 +1,3 @@
|
|||
from . import export_xlsx_wizard
|
||||
from . import import_xlsx_wizard
|
||||
from . import report_xlsx_wizard
|
|
@ -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",
|
||||
}
|
|
@ -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>
|
|
@ -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",
|
||||
}
|
|
@ -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>
|
|
@ -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 "[]")
|
|
@ -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>
|
|
@ -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.
|
|
@ -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
|
|
@ -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"],
|
||||
}
|
|
@ -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 ""
|
|
@ -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 ""
|
|
@ -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 "获取"
|
|
@ -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>
|
Binary file not shown.
|
@ -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>
|
|
@ -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>
|
Binary file not shown.
|
@ -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>
|
Binary file not shown.
|
@ -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>
|
|
@ -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>
|
|
@ -0,0 +1 @@
|
|||
* Kitti Upariphutthiphong. <kittiu@gmail.com> (http://ecosoft.co.th)
|
|
@ -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)
|
|
@ -0,0 +1,3 @@
|
|||
To install this module, you need to install **excel_import_export**
|
||||
|
||||
Then, simply install **excel_import_export_demo**.
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
Binary file not shown.
|
@ -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>
|
|
@ -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")
|
|
@ -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>
|
|
@ -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
|
|
|
@ -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>
|
|
@ -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>
|
Binary file not shown.
|
@ -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>
|
|
@ -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
|
|
@ -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"]}
|
||||
)
|
Binary file not shown.
|
@ -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>
|
|
@ -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
|
|
|
@ -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>
|
|
@ -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
|
|
@ -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)
|
Binary file not shown.
|
@ -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>
|
|
@ -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
|
|
|
@ -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 |
|
@ -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 > Excel Import/Export > Sample Import Sales Order</p>
|
||||
<p><strong>Example 3:</strong> Create Excel Report</p>
|
||||
<p>To test this use case, go to Settings > Excel Import/Export > 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 > Reporting > 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. <<a class="reference external" href="mailto:kittiu@gmail.com">kittiu@gmail.com</a>> (<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>
|
|
@ -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.
|
@ -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,
|
||||
}
|
||||
)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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")
|
|
@ -3,5 +3,6 @@ astor
|
|||
dataclasses
|
||||
mako
|
||||
odoorpc
|
||||
openpyxl
|
||||
openupgradelib
|
||||
sentry_sdk>=1.17.0
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
../../../../excel_import_export
|
|
@ -0,0 +1,6 @@
|
|||
import setuptools
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['setuptools-odoo'],
|
||||
odoo_addon=True,
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
../../../../excel_import_export_demo
|
|
@ -0,0 +1,6 @@
|
|||
import setuptools
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['setuptools-odoo'],
|
||||
odoo_addon=True,
|
||||
)
|
Loading…
Reference in New Issue