mirror of
https://github.com/bringout/oca-ocb-report.git
synced 2026-04-19 18:02:09 +02:00
19.0 vanilla
This commit is contained in:
parent
62d197ac8b
commit
184bb0e321
667 changed files with 691406 additions and 239886 deletions
|
|
@ -10,38 +10,16 @@ pip install odoo-bringout-oca-ocb-spreadsheet
|
|||
|
||||
## Dependencies
|
||||
|
||||
This addon depends on:
|
||||
- bus
|
||||
- web
|
||||
|
||||
## Manifest Information
|
||||
|
||||
- **Name**: Spreadsheet
|
||||
- **Version**: 1.0
|
||||
- **Category**: Hidden
|
||||
- **License**: LGPL-3
|
||||
- **Installable**: True
|
||||
- portal
|
||||
|
||||
## Source
|
||||
|
||||
Based on [OCA/OCB](https://github.com/OCA/OCB) branch 16.0, addon `spreadsheet`.
|
||||
- Repository: https://github.com/OCA/OCB
|
||||
- Branch: 19.0
|
||||
- Path: addons/spreadsheet
|
||||
|
||||
## License
|
||||
|
||||
This package maintains the original LGPL-3 license from the upstream Odoo project.
|
||||
|
||||
## Documentation
|
||||
|
||||
- Overview: doc/OVERVIEW.md
|
||||
- Architecture: doc/ARCHITECTURE.md
|
||||
- Models: doc/MODELS.md
|
||||
- Controllers: doc/CONTROLLERS.md
|
||||
- Wizards: doc/WIZARDS.md
|
||||
- Reports: doc/REPORTS.md
|
||||
- Security: doc/SECURITY.md
|
||||
- Install: doc/INSTALL.md
|
||||
- Usage: doc/USAGE.md
|
||||
- Configuration: doc/CONFIGURATION.md
|
||||
- Dependencies: doc/DEPENDENCIES.md
|
||||
- Troubleshooting: doc/TROUBLESHOOTING.md
|
||||
- FAQ: doc/FAQ.md
|
||||
This package preserves the original LGPL-3 license.
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
[project]
|
||||
name = "odoo-bringout-oca-ocb-spreadsheet"
|
||||
version = "16.0.0"
|
||||
description = "Spreadsheet - Spreadsheet"
|
||||
description = "Spreadsheet -
|
||||
Spreadsheet
|
||||
"
|
||||
authors = [
|
||||
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
|
||||
]
|
||||
dependencies = [
|
||||
"odoo-bringout-oca-ocb-bus>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-web>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-bus>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-web>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-portal>=19.0.0",
|
||||
"requests>=2.25.1"
|
||||
]
|
||||
readme = "README.md"
|
||||
|
|
@ -17,7 +20,7 @@ classifiers = [
|
|||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Topic :: Office/Business",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
from . import controllers
|
||||
|
|
|
|||
|
|
@ -3,35 +3,106 @@
|
|||
{
|
||||
'name': "Spreadsheet",
|
||||
'version': '1.0',
|
||||
'category': 'Hidden',
|
||||
'category': 'Productivity/Dashboard',
|
||||
'summary': 'Spreadsheet',
|
||||
'description': 'Spreadsheet',
|
||||
'depends': ['bus', 'web'],
|
||||
'data': [],
|
||||
'demo': [],
|
||||
'depends': ['bus', 'web', 'portal'],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
'author': 'Odoo S.A.',
|
||||
'license': 'LGPL-3',
|
||||
'data': [
|
||||
'views/public_readonly_spreadsheet_templates.xml',
|
||||
],
|
||||
'assets': {
|
||||
'web.chartjs_lib' : [
|
||||
'spreadsheet/static/lib/chartjs-chart-geo/chartjs-chart-geo.js',
|
||||
'spreadsheet/static/lib/chart_js_treemap.js',
|
||||
],
|
||||
'spreadsheet.o_spreadsheet': [
|
||||
'web/static/src/views/graph/graph_model.js',
|
||||
'web/static/src/views/pivot/pivot_model.js',
|
||||
'web/static/src/polyfills/clipboard.js',
|
||||
('include', 'web.chartjs_lib'),
|
||||
'spreadsheet/static/src/o_spreadsheet/o_spreadsheet.js',
|
||||
'spreadsheet/static/src/**/*.js',
|
||||
# Load all o_spreadsheet templates first to allow to inherit them
|
||||
'spreadsheet/static/src/o_spreadsheet/o_spreadsheet.xml',
|
||||
'spreadsheet/static/src/**/*.xml',
|
||||
('remove', 'spreadsheet/static/src/assets_backend/**/*')
|
||||
('remove', 'spreadsheet/static/src/assets_backend/**/*'),
|
||||
('remove', 'spreadsheet/static/src/public_readonly_app/**/*'),
|
||||
],
|
||||
'spreadsheet.assets_print': [
|
||||
'spreadsheet/static/src/print_assets/**/*',
|
||||
],
|
||||
'spreadsheet.public_spreadsheet': [
|
||||
('include', 'web.assets_frontend_minimal'),
|
||||
('include', 'web._assets_helpers'), # bootstrap variables
|
||||
'web/static/src/scss/bootstrap_overridden.scss',
|
||||
'web/static/src/scss/pre_variables.scss',
|
||||
'web/static/lib/bootstrap/scss/_variables.scss',
|
||||
'web/static/lib/bootstrap/scss/_variables-dark.scss',
|
||||
'web/static/lib/bootstrap/scss/_maps.scss',
|
||||
('include', 'web._assets_bootstrap'),
|
||||
'web/static/lib/popper/popper.js',
|
||||
'web/static/lib/bootstrap/js/dist/util/index.js',
|
||||
'web/static/lib/bootstrap/js/dist/dom/data.js',
|
||||
'web/static/lib/bootstrap/js/dist/dom/event-handler.js',
|
||||
'web/static/lib/bootstrap/js/dist/dom/manipulator.js',
|
||||
'web/static/lib/bootstrap/js/dist/dom/selector-engine.js',
|
||||
'web/static/lib/bootstrap/js/dist/util/config.js',
|
||||
'web/static/lib/bootstrap/js/dist/base-component.js',
|
||||
'web/static/lib/bootstrap/js/dist/collapse.js',
|
||||
'web/static/lib/bootstrap/js/dist/dropdown.js',
|
||||
'web/static/src/libs/fontawesome/css/font-awesome.css',
|
||||
'web/static/lib/owl/owl.js',
|
||||
'web/static/lib/luxon/luxon.js',
|
||||
'web/static/lib/owl/odoo_module.js',
|
||||
'web/static/src/core/utils/**/*.js',
|
||||
'web/static/src/core/browser/browser.js',
|
||||
'web/static/src/core/browser/feature_detection.js',
|
||||
'web/static/src/core/registry.js',
|
||||
'web/static/src/core/assets.js',
|
||||
'web/static/src/core/templates.js',
|
||||
'web/static/src/core/template_inheritance.js',
|
||||
'web/static/src/session.js',
|
||||
'web/static/src/env.js',
|
||||
'web/static/src/core/**/*.js',
|
||||
('remove', 'web/static/src/core/emoji_picker/emoji_data.js'),
|
||||
('include', 'web.chartjs_lib'),
|
||||
'spreadsheet/static/src/o_spreadsheet/o_spreadsheet.js',
|
||||
'spreadsheet/static/src/o_spreadsheet/o_spreadsheet.xml',
|
||||
'spreadsheet/static/src/o_spreadsheet/o_spreadsheet_variables.scss',
|
||||
'spreadsheet/static/src/o_spreadsheet/o_spreadsheet.scss',
|
||||
'spreadsheet/static/src/o_spreadsheet/icons.xml',
|
||||
'spreadsheet/static/src/o_spreadsheet/o_spreadsheet_extended.scss',
|
||||
'spreadsheet/static/src/o_spreadsheet/migration.js',
|
||||
'spreadsheet/static/src/helpers/odoo_functions_helpers.js',
|
||||
'spreadsheet/static/src/pivot/pivot_helpers.js',
|
||||
'spreadsheet/static/src/o_spreadsheet/odoo_module.js',
|
||||
'spreadsheet/static/src/helpers/helpers.js',
|
||||
'spreadsheet/static/src/public_readonly_app/**/*.xml',
|
||||
'spreadsheet/static/src/public_readonly_app/**/*.scss',
|
||||
'spreadsheet/static/src/public_readonly_app/**/*',
|
||||
'spreadsheet/static/src/hooks.js',
|
||||
'spreadsheet/static/src/plugins.js',
|
||||
],
|
||||
'web.assets_backend': [
|
||||
'spreadsheet/static/src/o_spreadsheet/o_spreadsheet_variables.scss',
|
||||
'spreadsheet/static/src/**/*.scss',
|
||||
'spreadsheet/static/src/assets_backend/**/*',
|
||||
('remove', 'spreadsheet/static/src/public_readonly_app/**/*.scss'),
|
||||
('remove', 'spreadsheet/static/src/**/*.dark.scss'),
|
||||
('remove', 'spreadsheet/static/src/print_assets/**/*'),
|
||||
],
|
||||
"web.dark_mode_assets_backend": [
|
||||
"web.assets_web_dark": [
|
||||
'spreadsheet/static/src/**/*.dark.scss',
|
||||
],
|
||||
'web.qunit_suite_tests': [
|
||||
'web.assets_unit_tests': [
|
||||
'spreadsheet/static/tests/**/*',
|
||||
('include', 'spreadsheet.o_spreadsheet')
|
||||
]
|
||||
('include', 'spreadsheet.o_spreadsheet'),
|
||||
'spreadsheet/static/src/public_readonly_app/**/*.xml',
|
||||
'spreadsheet/static/src/public_readonly_app/**/*.js',
|
||||
('remove', 'spreadsheet/static/src/public_readonly_app/main.js'),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import main
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
import logging
|
||||
|
||||
|
||||
from odoo import http
|
||||
from odoo.http import request, Controller
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SpreadsheetController(Controller):
|
||||
|
||||
@http.route("/spreadsheet/log", type="jsonrpc", auth="user", methods=["POST"])
|
||||
def log_action(self, action_type, datasources, **kw):
|
||||
if datasources:
|
||||
self._log_spreadsheet_export(action_type, request.env.uid, datasources)
|
||||
|
||||
def _log_spreadsheet_export(self, action_type, user_id, datasources):
|
||||
if action_type not in ["download", "copy", "freeze", "print"]:
|
||||
return
|
||||
data = [src for datasource in datasources if (src := self._stringify_source(datasource))]
|
||||
if not data:
|
||||
return
|
||||
logger.info(
|
||||
"User %d exported (%s) spreadsheet data (%s) from %s",
|
||||
user_id, action_type, "), (".join(data), request.httprequest.environ["REMOTE_ADDR"]
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _stringify_source(source):
|
||||
res_model = source.get("resModel")
|
||||
if not res_model or res_model not in request.env:
|
||||
return
|
||||
|
||||
if not (fields := source.get("fields", [])):
|
||||
return
|
||||
|
||||
string = f"model: {res_model} with fields: [{','.join(fields)}]"
|
||||
|
||||
if groupby := source.get("groupby"):
|
||||
string += f" grouped by [{','.join(groupby)}]"
|
||||
|
||||
if domain := source.get("domain"):
|
||||
string += f" with domain {domain}"
|
||||
|
||||
return string
|
||||
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
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
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
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
13479
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/el.po
Normal file
13479
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/el.po
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
14541
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/es_419.po
Normal file
14541
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/es_419.po
Normal file
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
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
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
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
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
File diff suppressed because it is too large
Load diff
13467
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/kab.po
Normal file
13467
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/kab.po
Normal file
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
13469
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/ku.po
Normal file
13469
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/ku.po
Normal file
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
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
File diff suppressed because it is too large
Load diff
13471
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/my.po
Normal file
13471
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/my.po
Normal file
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
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
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
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
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
13470
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/sr@latin.po
Normal file
13470
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/sr@latin.po
Normal file
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
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
File diff suppressed because it is too large
Load diff
16226
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/uz.po
Normal file
16226
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/i18n/uz.po
Normal file
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
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,8 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import ir_model
|
||||
from . import ir_http
|
||||
from . import res_currency
|
||||
from . import res_currency_rate
|
||||
from . import res_lang
|
||||
from . import spreadsheet_mixin
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models
|
||||
|
||||
|
||||
class IrHttp(models.AbstractModel):
|
||||
_inherit = 'ir.http'
|
||||
|
||||
def session_info(self):
|
||||
"""
|
||||
Override this method to enable the 'Insert in spreadsheet' button in the
|
||||
web client.
|
||||
"""
|
||||
res = super().session_info()
|
||||
res["can_insert_in_spreadsheet"] = False
|
||||
return res
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models, api
|
||||
|
||||
|
||||
class IrModel(models.Model):
|
||||
_inherit = "ir.model"
|
||||
|
||||
@api.readonly
|
||||
@api.model
|
||||
def has_searchable_parent_relation(self, model_names):
|
||||
result = {}
|
||||
for model_name in model_names:
|
||||
model = self.env.get(model_name)
|
||||
if model is None or not model.has_access("read"):
|
||||
result[model_name] = False
|
||||
else:
|
||||
# we consider only stored parent relationships were meant to
|
||||
# be searched
|
||||
result[model_name] = model._parent_store and model._parent_name in model._fields
|
||||
return result
|
||||
|
|
@ -4,34 +4,7 @@ from odoo import api, models
|
|||
class ResCurrency(models.Model):
|
||||
_inherit = "res.currency"
|
||||
|
||||
@api.model
|
||||
def get_currencies_for_spreadsheet(self, currency_names):
|
||||
"""
|
||||
Returns the currency structure of provided currency names.
|
||||
This function is meant to be called by the spreadsheet js lib,
|
||||
hence the formatting of the result.
|
||||
|
||||
:currency_names list(str): list of currency names (e.g. ["EUR", "USD", "CAD"])
|
||||
:return: list of dicts of the form `{ "code": str, "symbol": str, "decimalPlaces": int, "position":str }`
|
||||
"""
|
||||
currencies = self.with_context(active_test=False).search(
|
||||
[("name", "in", currency_names)],
|
||||
)
|
||||
result = []
|
||||
for currency_name in currency_names:
|
||||
currency = next(filter(lambda curr: curr.name == currency_name, currencies), None)
|
||||
if currency:
|
||||
currency_data = {
|
||||
"code": currency.name,
|
||||
"symbol": currency.symbol,
|
||||
"decimalPlaces": currency.decimal_places,
|
||||
"position": currency.position,
|
||||
}
|
||||
else:
|
||||
currency_data = None
|
||||
result.append(currency_data)
|
||||
return result
|
||||
|
||||
@api.readonly
|
||||
@api.model
|
||||
def get_company_currency_for_spreadsheet(self, company_id=None):
|
||||
"""
|
||||
|
|
@ -39,7 +12,7 @@ class ResCurrency(models.Model):
|
|||
This function is meant to be called by the spreadsheet js lib,
|
||||
hence the formatting of the result.
|
||||
|
||||
:company_id int: Id of the company
|
||||
:param int company_id: Id of the company
|
||||
:return: dict of the form `{ "code": str, "symbol": str, "decimalPlaces": int, "position":str }`
|
||||
"""
|
||||
company = self.env["res.company"].browse(company_id) if company_id else self.env.company
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ class ResCurrencyRate(models.Model):
|
|||
_inherit = "res.currency.rate"
|
||||
|
||||
@api.model
|
||||
def _get_rate_for_spreadsheet(self, currency_from_code, currency_to_code, date=None):
|
||||
def _get_rate_for_spreadsheet(self, currency_from_code, currency_to_code, date=None, company_id=None):
|
||||
if not currency_from_code or not currency_to_code:
|
||||
return False
|
||||
Currency = self.env["res.currency"].with_context({"active_test": False})
|
||||
|
|
@ -13,17 +13,25 @@ class ResCurrencyRate(models.Model):
|
|||
currency_to = Currency.search([("name", "=", currency_to_code)])
|
||||
if not currency_from or not currency_to:
|
||||
return False
|
||||
company = self.env.company
|
||||
company = self.env["res.company"].browse(company_id) if company_id else self.env.company
|
||||
date = fields.Date.from_string(date) if date else fields.Date.context_today(self)
|
||||
return Currency._get_conversion_rate(currency_from, currency_to, company, date)
|
||||
|
||||
@api.readonly
|
||||
@api.model
|
||||
def get_rates_for_spreadsheet(self, requests):
|
||||
result = []
|
||||
for request in requests:
|
||||
record = request.copy()
|
||||
record.update({
|
||||
"rate": self._get_rate_for_spreadsheet(request["from"], request["to"], request.get("date")),
|
||||
})
|
||||
record.update(
|
||||
{
|
||||
"rate": self._get_rate_for_spreadsheet(
|
||||
request["from"],
|
||||
request["to"],
|
||||
request.get("date"),
|
||||
request.get("company_id"),
|
||||
),
|
||||
}
|
||||
)
|
||||
result.append(record)
|
||||
return result
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, models
|
||||
|
||||
from odoo.addons.spreadsheet.utils.formatting import (
|
||||
strftime_format_to_spreadsheet_date_format,
|
||||
strftime_format_to_spreadsheet_time_format,
|
||||
)
|
||||
|
||||
|
||||
class ResLang(models.Model):
|
||||
_inherit = "res.lang"
|
||||
|
||||
@api.readonly
|
||||
@api.model
|
||||
def get_locales_for_spreadsheet(self):
|
||||
"""Return the list of locales available for a spreadsheet."""
|
||||
langs = self.with_context(active_test=False).search([])
|
||||
|
||||
spreadsheet_locales = [lang._odoo_lang_to_spreadsheet_locale() for lang in langs]
|
||||
return spreadsheet_locales
|
||||
|
||||
@api.model
|
||||
def _get_user_spreadsheet_locale(self):
|
||||
"""Convert the odoo lang to a spreadsheet locale."""
|
||||
lang = self._lang_get(self.env.user.lang or 'en_US')
|
||||
return lang._odoo_lang_to_spreadsheet_locale()
|
||||
|
||||
def _odoo_lang_to_spreadsheet_locale(self):
|
||||
"""Convert an odoo lang to a spreadsheet locale."""
|
||||
return {
|
||||
"name": self.name,
|
||||
"code": self.code,
|
||||
"thousandsSeparator": self.thousands_sep,
|
||||
"decimalSeparator": self.decimal_point,
|
||||
"dateFormat": strftime_format_to_spreadsheet_date_format(self.date_format),
|
||||
"timeFormat": strftime_format_to_spreadsheet_time_format(self.time_format),
|
||||
"formulaArgSeparator": ";" if self.decimal_point == "," else ",",
|
||||
"weekStart": int(self.week_start),
|
||||
}
|
||||
|
|
@ -0,0 +1,175 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import io
|
||||
import zipfile
|
||||
import base64
|
||||
import json
|
||||
import re
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
from odoo import api, fields, models, _, tools
|
||||
from odoo.exceptions import ValidationError, MissingError
|
||||
|
||||
from odoo.addons.spreadsheet.utils.validate_data import fields_in_spreadsheet, menus_xml_ids_in_spreadsheet
|
||||
|
||||
|
||||
class SpreadsheetMixin(models.AbstractModel):
|
||||
_name = 'spreadsheet.mixin'
|
||||
_description = "Spreadsheet mixin"
|
||||
_auto = False
|
||||
|
||||
spreadsheet_binary_data = fields.Binary(
|
||||
string="Spreadsheet file",
|
||||
default=lambda self: self._empty_spreadsheet_data_base64(),
|
||||
)
|
||||
spreadsheet_data = fields.Text(compute='_compute_spreadsheet_data', inverse='_inverse_spreadsheet_data')
|
||||
spreadsheet_file_name = fields.Char(compute='_compute_spreadsheet_file_name')
|
||||
thumbnail = fields.Binary()
|
||||
|
||||
@api.constrains("spreadsheet_binary_data")
|
||||
def _check_spreadsheet_data(self):
|
||||
for spreadsheet in self.filtered("spreadsheet_binary_data"):
|
||||
try:
|
||||
data = json.loads(base64.b64decode(spreadsheet.spreadsheet_binary_data).decode())
|
||||
except (json.JSONDecodeError, UnicodeDecodeError):
|
||||
raise ValidationError(_("Uh-oh! Looks like the spreadsheet file contains invalid data."))
|
||||
if not (tools.config['test_enable'] or tools.config['test_file']):
|
||||
continue
|
||||
if data.get("[Content_Types].xml"):
|
||||
# this is a xlsx file
|
||||
continue
|
||||
display_name = spreadsheet.display_name
|
||||
errors = []
|
||||
for model, field_chains in fields_in_spreadsheet(data).items():
|
||||
if model not in self.env:
|
||||
errors.append(f"- model '{model}' used in '{display_name}' does not exist")
|
||||
continue
|
||||
for field_chain in field_chains:
|
||||
field_model = model
|
||||
for fname in field_chain.split("."): # field chain 'product_id.channel_ids'
|
||||
if fname not in self.env[field_model]._fields:
|
||||
errors.append(f"- field '{fname}' used in spreadsheet '{display_name}' does not exist on model '{field_model}'")
|
||||
continue
|
||||
field = self.env[field_model]._fields[fname]
|
||||
if field.relational:
|
||||
field_model = field.comodel_name
|
||||
|
||||
for xml_id in menus_xml_ids_in_spreadsheet(data):
|
||||
record = self.env.ref(xml_id, raise_if_not_found=False)
|
||||
if not record:
|
||||
errors.append(f"- xml id '{xml_id}' used in spreadsheet '{display_name}' does not exist")
|
||||
continue
|
||||
# check that the menu has an action. Root menus always have an action.
|
||||
if not record.action and record.parent_id.id:
|
||||
errors.append(f"- menu with xml id '{xml_id}' used in spreadsheet '{display_name}' does not have an action")
|
||||
|
||||
if errors:
|
||||
raise ValidationError(
|
||||
_(
|
||||
"Uh-oh! Looks like the spreadsheet file contains invalid data.\n\n%(errors)s",
|
||||
errors="\n".join(errors),
|
||||
),
|
||||
)
|
||||
|
||||
@api.depends("spreadsheet_binary_data")
|
||||
def _compute_spreadsheet_data(self):
|
||||
attachments = self.env['ir.attachment'].with_context(bin_size=False).search([
|
||||
('res_model', '=', self._name),
|
||||
('res_field', '=', 'spreadsheet_binary_data'),
|
||||
('res_id', 'in', self.ids),
|
||||
])
|
||||
data = {
|
||||
attachment.res_id: attachment.raw
|
||||
for attachment in attachments
|
||||
}
|
||||
for spreadsheet in self:
|
||||
spreadsheet.spreadsheet_data = data.get(spreadsheet.id, False)
|
||||
|
||||
def _inverse_spreadsheet_data(self):
|
||||
for spreadsheet in self:
|
||||
if not spreadsheet.spreadsheet_data:
|
||||
spreadsheet.spreadsheet_binary_data = False
|
||||
else:
|
||||
spreadsheet.spreadsheet_binary_data = base64.b64encode(spreadsheet.spreadsheet_data.encode())
|
||||
|
||||
@api.depends('display_name')
|
||||
def _compute_spreadsheet_file_name(self):
|
||||
for spreadsheet in self:
|
||||
spreadsheet.spreadsheet_file_name = f"{spreadsheet.display_name}.osheet.json"
|
||||
|
||||
@api.onchange('spreadsheet_binary_data')
|
||||
def _onchange_data_(self):
|
||||
self._check_spreadsheet_data()
|
||||
|
||||
@api.readonly
|
||||
@api.model
|
||||
def get_display_names_for_spreadsheet(self, args):
|
||||
ids_per_model = defaultdict(list)
|
||||
for arg in args:
|
||||
ids_per_model[arg["model"]].append(arg["id"])
|
||||
display_names = defaultdict(dict)
|
||||
for model, ids in ids_per_model.items():
|
||||
records = self.env[model].with_context(active_test=False).search([("id", "in", ids)])
|
||||
for record in records:
|
||||
display_names[model][record.id] = record.display_name
|
||||
|
||||
# return the display names in the same order as the input
|
||||
return [
|
||||
display_names[arg["model"]].get(arg["id"])
|
||||
for arg in args
|
||||
]
|
||||
|
||||
def _empty_spreadsheet_data_base64(self):
|
||||
"""Create an empty spreadsheet workbook.
|
||||
Encoded as base64
|
||||
"""
|
||||
data = json.dumps(self._empty_spreadsheet_data())
|
||||
return base64.b64encode(data.encode())
|
||||
|
||||
def _empty_spreadsheet_data(self):
|
||||
"""Create an empty spreadsheet workbook.
|
||||
The sheet name should be the same for all users to allow consistent references
|
||||
in formulas. It is translated for the user creating the spreadsheet.
|
||||
"""
|
||||
lang = self.env["res.lang"]._lang_get(self.env.user.lang)
|
||||
locale = lang._odoo_lang_to_spreadsheet_locale()
|
||||
return {
|
||||
"sheets": [
|
||||
{
|
||||
"id": "sheet1",
|
||||
"name": _("Sheet1"),
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"locale": locale,
|
||||
},
|
||||
"revisionId": "START_REVISION",
|
||||
}
|
||||
|
||||
def _zip_xslx_files(self, files):
|
||||
stream = io.BytesIO()
|
||||
with zipfile.ZipFile(stream, 'w', compression=zipfile.ZIP_DEFLATED) as doc_zip:
|
||||
for f in files:
|
||||
# to reduce networking load, only the image path is sent.
|
||||
# It's replaced by the image content here.
|
||||
if 'imageSrc' in f:
|
||||
try:
|
||||
content = self._get_file_content(f['imageSrc'])
|
||||
doc_zip.writestr(f['path'], content)
|
||||
except MissingError:
|
||||
pass
|
||||
else:
|
||||
doc_zip.writestr(f['path'], f['content'])
|
||||
|
||||
return stream.getvalue()
|
||||
|
||||
def _get_file_content(self, file_path):
|
||||
if file_path.startswith('data:image/png;base64,'):
|
||||
return base64.b64decode(file_path.split(',')[1])
|
||||
match = re.match(r'/web/image/(\d+)', file_path)
|
||||
file_record = self.env['ir.binary']._find_record(
|
||||
res_model='ir.attachment',
|
||||
res_id=int(match.group(1)),
|
||||
)
|
||||
return self.env['ir.binary']._get_stream_from(file_record).read()
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 557 B |
|
|
@ -0,0 +1 @@
|
|||
<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M46 42C46 44.2091 44.2091 46 42 46L4 46L4 8C4 5.79086 5.79086 4 8 4L46 4L46 42Z" fill="#1A6F66"/><path d="M18 18L46 18L46 42C46 44.2091 44.2091 46 42 46L18 46L18 18Z" fill="#1AD3BB"/><path d="M18 18L4 18L4 8C4 5.79086 5.79086 4 8 4L18 4L18 18Z" fill="#1AD3BB"/></svg>
|
||||
|
After Width: | Height: | Size: 372 B |
Binary file not shown.
|
After Width: | Height: | Size: 3 KiB |
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,363 +0,0 @@
|
|||
/*!
|
||||
* chartjs-gauge.js v0.3.0
|
||||
* https://github.com/haiiaaa/chartjs-gauge/
|
||||
* (c) 2021 chartjs-gauge.js Contributors
|
||||
* Released under the MIT License
|
||||
*/
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('chart.js')) :
|
||||
typeof define === 'function' && define.amd ? define(['chart.js'], factory) :
|
||||
(global = global || self, global.Gauge = factory(global.Chart));
|
||||
}(this, (function (Chart) { 'use strict';
|
||||
|
||||
Chart = Chart && Object.prototype.hasOwnProperty.call(Chart, 'default') ? Chart['default'] : Chart;
|
||||
|
||||
function _defineProperty(obj, key, value) {
|
||||
if (key in obj) {
|
||||
Object.defineProperty(obj, key, {
|
||||
value: value,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: true
|
||||
});
|
||||
} else {
|
||||
obj[key] = value;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
function ownKeys(object, enumerableOnly) {
|
||||
var keys = Object.keys(object);
|
||||
|
||||
if (Object.getOwnPropertySymbols) {
|
||||
var symbols = Object.getOwnPropertySymbols(object);
|
||||
if (enumerableOnly) symbols = symbols.filter(function (sym) {
|
||||
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
|
||||
});
|
||||
keys.push.apply(keys, symbols);
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
function _objectSpread2(target) {
|
||||
for (var i = 1; i < arguments.length; i++) {
|
||||
var source = arguments[i] != null ? arguments[i] : {};
|
||||
|
||||
if (i % 2) {
|
||||
ownKeys(Object(source), true).forEach(function (key) {
|
||||
_defineProperty(target, key, source[key]);
|
||||
});
|
||||
} else if (Object.getOwnPropertyDescriptors) {
|
||||
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
|
||||
} else {
|
||||
ownKeys(Object(source)).forEach(function (key) {
|
||||
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
Chart.defaults._set('gauge', {
|
||||
needle: {
|
||||
// Needle circle radius as the percentage of the chart area width
|
||||
radiusPercentage: 2,
|
||||
// Needle width as the percentage of the chart area width
|
||||
widthPercentage: 3.2,
|
||||
// Needle length as the percentage of the interval between inner radius (0%) and outer radius (100%) of the arc
|
||||
lengthPercentage: 80,
|
||||
// The color of the needle
|
||||
color: 'rgba(0, 0, 0, 1)'
|
||||
},
|
||||
valueLabel: {
|
||||
// fontSize: undefined
|
||||
display: true,
|
||||
formatter: null,
|
||||
color: 'rgba(255, 255, 255, 1)',
|
||||
backgroundColor: 'rgba(0, 0, 0, 1)',
|
||||
borderRadius: 5,
|
||||
padding: {
|
||||
top: 5,
|
||||
right: 5,
|
||||
bottom: 5,
|
||||
left: 5
|
||||
},
|
||||
bottomMarginPercentage: 5
|
||||
},
|
||||
animation: {
|
||||
duration: 1000,
|
||||
animateRotate: true,
|
||||
animateScale: false
|
||||
},
|
||||
// The percentage of the chart that we cut out of the middle.
|
||||
cutoutPercentage: 50,
|
||||
// The rotation of the chart, where the first data arc begins.
|
||||
rotation: -Math.PI,
|
||||
// The total circumference of the chart.
|
||||
circumference: Math.PI,
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
tooltips: {
|
||||
enabled: false
|
||||
}
|
||||
});
|
||||
|
||||
var GaugeController = Chart.controllers.doughnut.extend({
|
||||
getValuePercent: function getValuePercent(_ref, value) {
|
||||
var minValue = _ref.minValue,
|
||||
data = _ref.data;
|
||||
var min = minValue || 0;
|
||||
var max = [undefined, null].includes(data[data.length - 1]) ? 1 : data[data.length - 1];
|
||||
var length = max - min;
|
||||
var percent = (value - min) / length;
|
||||
return percent;
|
||||
},
|
||||
getWidth: function getWidth(chart) {
|
||||
return chart.chartArea.right - chart.chartArea.left;
|
||||
},
|
||||
getTranslation: function getTranslation(chart) {
|
||||
var chartArea = chart.chartArea,
|
||||
offsetX = chart.offsetX,
|
||||
offsetY = chart.offsetY;
|
||||
var centerX = (chartArea.left + chartArea.right) / 2;
|
||||
var centerY = (chartArea.top + chartArea.bottom) / 2;
|
||||
var dx = centerX + offsetX;
|
||||
var dy = centerY + offsetY;
|
||||
return {
|
||||
dx: dx,
|
||||
dy: dy
|
||||
};
|
||||
},
|
||||
getAngle: function getAngle(_ref2) {
|
||||
var chart = _ref2.chart,
|
||||
valuePercent = _ref2.valuePercent;
|
||||
var _chart$options = chart.options,
|
||||
rotation = _chart$options.rotation,
|
||||
circumference = _chart$options.circumference;
|
||||
return rotation + circumference * valuePercent;
|
||||
},
|
||||
|
||||
/* TODO set min padding, not applied until chart.update() (also chartArea must have been set)
|
||||
setBottomPadding(chart) {
|
||||
const needleRadius = this.getNeedleRadius(chart);
|
||||
const padding = this.chart.config.options.layout.padding;
|
||||
if (needleRadius > padding.bottom) {
|
||||
padding.bottom = needleRadius;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
*/
|
||||
drawNeedle: function drawNeedle(ease) {
|
||||
if (!this.chart.animating) {
|
||||
// triggered when hovering
|
||||
ease = 1;
|
||||
}
|
||||
|
||||
var _this$chart = this.chart,
|
||||
ctx = _this$chart.ctx,
|
||||
config = _this$chart.config,
|
||||
innerRadius = _this$chart.innerRadius,
|
||||
outerRadius = _this$chart.outerRadius;
|
||||
var dataset = config.data.datasets[this.index];
|
||||
|
||||
var _this$getMeta = this.getMeta(),
|
||||
previous = _this$getMeta.previous;
|
||||
|
||||
var _config$options$needl = config.options.needle,
|
||||
radiusPercentage = _config$options$needl.radiusPercentage,
|
||||
widthPercentage = _config$options$needl.widthPercentage,
|
||||
lengthPercentage = _config$options$needl.lengthPercentage,
|
||||
color = _config$options$needl.color;
|
||||
var width = this.getWidth(this.chart);
|
||||
var needleRadius = radiusPercentage / 100 * width;
|
||||
var needleWidth = widthPercentage / 100 * width;
|
||||
var needleLength = lengthPercentage / 100 * (outerRadius - innerRadius) + innerRadius; // center
|
||||
|
||||
var _this$getTranslation = this.getTranslation(this.chart),
|
||||
dx = _this$getTranslation.dx,
|
||||
dy = _this$getTranslation.dy; // interpolate
|
||||
|
||||
|
||||
var origin = this.getAngle({
|
||||
chart: this.chart,
|
||||
valuePercent: previous.valuePercent
|
||||
}); // TODO valuePercent is in current.valuePercent also
|
||||
|
||||
var target = this.getAngle({
|
||||
chart: this.chart,
|
||||
valuePercent: this.getValuePercent(dataset, dataset.value)
|
||||
});
|
||||
var angle = origin + (target - origin) * ease; // draw
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(dx, dy);
|
||||
ctx.rotate(angle);
|
||||
ctx.fillStyle = color; // draw circle
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(0, 0, needleRadius, needleRadius, 0, 0, 2 * Math.PI);
|
||||
ctx.fill(); // draw needle
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, needleWidth / 2);
|
||||
ctx.lineTo(needleLength, 0);
|
||||
ctx.lineTo(0, -needleWidth / 2);
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
},
|
||||
drawValueLabel: function drawValueLabel(ease) {
|
||||
// eslint-disable-line no-unused-vars
|
||||
if (!this.chart.config.options.valueLabel.display) {
|
||||
return;
|
||||
}
|
||||
|
||||
var _this$chart2 = this.chart,
|
||||
ctx = _this$chart2.ctx,
|
||||
config = _this$chart2.config;
|
||||
var defaultFontFamily = config.options.defaultFontFamily;
|
||||
var dataset = config.data.datasets[this.index];
|
||||
var _config$options$value = config.options.valueLabel,
|
||||
formatter = _config$options$value.formatter,
|
||||
fontSize = _config$options$value.fontSize,
|
||||
color = _config$options$value.color,
|
||||
backgroundColor = _config$options$value.backgroundColor,
|
||||
borderRadius = _config$options$value.borderRadius,
|
||||
padding = _config$options$value.padding,
|
||||
bottomMarginPercentage = _config$options$value.bottomMarginPercentage;
|
||||
var width = this.getWidth(this.chart);
|
||||
var bottomMargin = bottomMarginPercentage / 100 * width;
|
||||
|
||||
var fmt = formatter || function (value) {
|
||||
return value;
|
||||
};
|
||||
|
||||
var valueText = fmt(dataset.value).toString();
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.textAlign = 'center';
|
||||
|
||||
if (fontSize) {
|
||||
ctx.font = "".concat(fontSize, "px ").concat(defaultFontFamily);
|
||||
} // const { width: textWidth, actualBoundingBoxAscent, actualBoundingBoxDescent } = ctx.measureText(valueText);
|
||||
// const textHeight = actualBoundingBoxAscent + actualBoundingBoxDescent;
|
||||
|
||||
|
||||
var _ctx$measureText = ctx.measureText(valueText),
|
||||
textWidth = _ctx$measureText.width; // approximate height until browsers support advanced TextMetrics
|
||||
|
||||
|
||||
var textHeight = Math.max(ctx.measureText('m').width, ctx.measureText("\uFF37").width);
|
||||
var x = -(padding.left + textWidth / 2);
|
||||
var y = -(padding.top + textHeight / 2);
|
||||
var w = padding.left + textWidth + padding.right;
|
||||
var h = padding.top + textHeight + padding.bottom; // center
|
||||
|
||||
var _this$getTranslation2 = this.getTranslation(this.chart),
|
||||
dx = _this$getTranslation2.dx,
|
||||
dy = _this$getTranslation2.dy; // add rotation
|
||||
|
||||
|
||||
var rotation = this.chart.options.rotation % (Math.PI * 2.0);
|
||||
dx += bottomMargin * Math.cos(rotation + Math.PI / 2);
|
||||
dy += bottomMargin * Math.sin(rotation + Math.PI / 2); // draw
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(dx, dy); // draw background
|
||||
|
||||
ctx.beginPath();
|
||||
Chart.helpers.canvas.roundedRect(ctx, x, y, w, h, borderRadius);
|
||||
ctx.fillStyle = backgroundColor;
|
||||
ctx.fill(); // draw value text
|
||||
|
||||
ctx.fillStyle = color || config.options.defaultFontColor;
|
||||
var magicNumber = 0.075; // manual testing
|
||||
|
||||
ctx.fillText(valueText, 0, textHeight * magicNumber);
|
||||
ctx.restore();
|
||||
},
|
||||
// overrides
|
||||
update: function update(reset) {
|
||||
var dataset = this.chart.config.data.datasets[this.index];
|
||||
dataset.minValue = dataset.minValue || 0;
|
||||
var meta = this.getMeta();
|
||||
var initialValue = {
|
||||
valuePercent: 0
|
||||
}; // animations on will call update(reset) before update()
|
||||
|
||||
if (reset) {
|
||||
meta.previous = null;
|
||||
meta.current = initialValue;
|
||||
} else {
|
||||
dataset.data.sort(function (a, b) {
|
||||
return a - b;
|
||||
});
|
||||
meta.previous = meta.current || initialValue;
|
||||
meta.current = {
|
||||
valuePercent: this.getValuePercent(dataset, dataset.value)
|
||||
};
|
||||
}
|
||||
|
||||
Chart.controllers.doughnut.prototype.update.call(this, reset);
|
||||
},
|
||||
updateElement: function updateElement(arc, index, reset) {
|
||||
// TODO handle reset and options.animation
|
||||
Chart.controllers.doughnut.prototype.updateElement.call(this, arc, index, reset);
|
||||
var dataset = this.getDataset();
|
||||
var data = dataset.data; // const { options } = this.chart.config;
|
||||
// scale data
|
||||
|
||||
var previousValue = index === 0 ? dataset.minValue : data[index - 1];
|
||||
var value = data[index];
|
||||
var startAngle = this.getAngle({
|
||||
chart: this.chart,
|
||||
valuePercent: this.getValuePercent(dataset, previousValue)
|
||||
});
|
||||
var endAngle = this.getAngle({
|
||||
chart: this.chart,
|
||||
valuePercent: this.getValuePercent(dataset, value)
|
||||
});
|
||||
var circumference = endAngle - startAngle;
|
||||
arc._model = _objectSpread2({}, arc._model, {
|
||||
startAngle: startAngle,
|
||||
endAngle: endAngle,
|
||||
circumference: circumference
|
||||
});
|
||||
},
|
||||
draw: function draw(ease) {
|
||||
Chart.controllers.doughnut.prototype.draw.call(this, ease);
|
||||
this.drawNeedle(ease);
|
||||
this.drawValueLabel(ease);
|
||||
}
|
||||
});
|
||||
|
||||
/* eslint-disable max-len, func-names */
|
||||
var polyfill = function polyfill() {
|
||||
if (CanvasRenderingContext2D.prototype.ellipse === undefined) {
|
||||
CanvasRenderingContext2D.prototype.ellipse = function (x, y, radiusX, radiusY, rotation, startAngle, endAngle, antiClockwise) {
|
||||
this.save();
|
||||
this.translate(x, y);
|
||||
this.rotate(rotation);
|
||||
this.scale(radiusX, radiusY);
|
||||
this.arc(0, 0, 1, startAngle, endAngle, antiClockwise);
|
||||
this.restore();
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
polyfill();
|
||||
Chart.controllers.gauge = GaugeController;
|
||||
|
||||
Chart.Gauge = function (context, config) {
|
||||
config.type = 'gauge';
|
||||
return new Chart(context, config);
|
||||
};
|
||||
|
||||
var index = Chart.Gauge;
|
||||
|
||||
return index;
|
||||
|
||||
})));
|
||||
157
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/static/src/@types/commands.d.ts
vendored
Normal file
157
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/static/src/@types/commands.d.ts
vendored
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
import { FieldMatching } from "./global_filter.d";
|
||||
import {
|
||||
CorePlugin,
|
||||
UIPlugin,
|
||||
DispatchResult,
|
||||
CommandResult,
|
||||
AddPivotCommand,
|
||||
UpdatePivotCommand,
|
||||
CancelledReason,
|
||||
} from "@odoo/o-spreadsheet";
|
||||
import * as OdooCancelledReason from "@spreadsheet/o_spreadsheet/cancelled_reason";
|
||||
|
||||
type CoreDispatch = CorePlugin["dispatch"];
|
||||
type UIDispatch = UIPlugin["dispatch"];
|
||||
type CoreCommand = Parameters<CorePlugin["allowDispatch"]>[0];
|
||||
type Command = Parameters<UIPlugin["allowDispatch"]>[0];
|
||||
|
||||
// TODO look for a way to remove this and use the real import * as OdooCancelledReason
|
||||
type OdooCancelledReason = string;
|
||||
|
||||
declare module "@spreadsheet" {
|
||||
interface OdooCommandDispatcher {
|
||||
dispatch<T extends OdooCommandTypes, C extends Extract<OdooCommand, { type: T }>>(
|
||||
type: {} extends Omit<C, "type"> ? T : never
|
||||
): OdooDispatchResult;
|
||||
dispatch<T extends OdooCommandTypes, C extends Extract<OdooCommand, { type: T }>>(
|
||||
type: T,
|
||||
r: Omit<C, "type">
|
||||
): OdooDispatchResult;
|
||||
}
|
||||
|
||||
interface OdooCoreCommandDispatcher {
|
||||
dispatch<T extends OdooCoreCommandTypes, C extends Extract<OdooCoreCommand, { type: T }>>(
|
||||
type: {} extends Omit<C, "type"> ? T : never
|
||||
): OdooDispatchResult;
|
||||
dispatch<T extends OdooCoreCommandTypes, C extends Extract<OdooCoreCommand, { type: T }>>(
|
||||
type: T,
|
||||
r: Omit<C, "type">
|
||||
): OdooDispatchResult;
|
||||
}
|
||||
|
||||
interface OdooDispatchResult extends DispatchResult {
|
||||
readonly reasons: (CancelledReason | OdooCancelledReason)[];
|
||||
isCancelledBecause(reason: CancelledReason | OdooCancelledReason): boolean;
|
||||
}
|
||||
|
||||
type OdooCommandTypes = OdooCommand["type"];
|
||||
type OdooCoreCommandTypes = OdooCoreCommand["type"];
|
||||
|
||||
type OdooDispatch = UIDispatch & OdooCommandDispatcher["dispatch"];
|
||||
type OdooCoreDispatch = CoreDispatch & OdooCoreCommandDispatcher["dispatch"];
|
||||
|
||||
// CORE
|
||||
|
||||
export interface ExtendedAddPivotCommand extends AddPivotCommand {
|
||||
pivot: ExtendedPivotCoreDefinition;
|
||||
}
|
||||
|
||||
export interface ExtendedUpdatePivotCommand extends UpdatePivotCommand {
|
||||
pivot: ExtendedPivotCoreDefinition;
|
||||
}
|
||||
|
||||
export interface AddThreadCommand {
|
||||
type: "ADD_COMMENT_THREAD";
|
||||
threadId: number;
|
||||
sheetId: string;
|
||||
col: number;
|
||||
row: number;
|
||||
}
|
||||
|
||||
export interface EditThreadCommand {
|
||||
type: "EDIT_COMMENT_THREAD";
|
||||
threadId: number;
|
||||
sheetId: string;
|
||||
col: number;
|
||||
row: number;
|
||||
isResolved: boolean;
|
||||
}
|
||||
|
||||
export interface DeleteThreadCommand {
|
||||
type: "DELETE_COMMENT_THREAD";
|
||||
threadId: number;
|
||||
sheetId: string;
|
||||
col: number;
|
||||
row: number;
|
||||
}
|
||||
|
||||
// this command is deprecated. use UPDATE_PIVOT instead
|
||||
export interface UpdatePivotDomainCommand {
|
||||
type: "UPDATE_ODOO_PIVOT_DOMAIN";
|
||||
pivotId: string;
|
||||
domain: Array;
|
||||
}
|
||||
|
||||
export interface AddGlobalFilterCommand {
|
||||
type: "ADD_GLOBAL_FILTER";
|
||||
filter: CmdGlobalFilter;
|
||||
[string]: any; // Fields matching
|
||||
}
|
||||
|
||||
export interface EditGlobalFilterCommand {
|
||||
type: "EDIT_GLOBAL_FILTER";
|
||||
filter: CmdGlobalFilter;
|
||||
[string]: any; // Fields matching
|
||||
}
|
||||
|
||||
export interface RemoveGlobalFilterCommand {
|
||||
type: "REMOVE_GLOBAL_FILTER";
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface MoveGlobalFilterCommand {
|
||||
type: "MOVE_GLOBAL_FILTER";
|
||||
id: string;
|
||||
delta: number;
|
||||
}
|
||||
|
||||
// UI
|
||||
|
||||
export interface RefreshAllDataSourcesCommand {
|
||||
type: "REFRESH_ALL_DATA_SOURCES";
|
||||
}
|
||||
|
||||
export interface SetGlobalFilterValueCommand {
|
||||
type: "SET_GLOBAL_FILTER_VALUE";
|
||||
id: string;
|
||||
value: any;
|
||||
}
|
||||
|
||||
export interface SetManyGlobalFilterValueCommand {
|
||||
type: "SET_MANY_GLOBAL_FILTER_VALUE";
|
||||
filters: { filterId: string; value: any }[];
|
||||
}
|
||||
|
||||
type OdooCoreCommand =
|
||||
| ExtendedAddPivotCommand
|
||||
| ExtendedUpdatePivotCommand
|
||||
| UpdatePivotDomainCommand
|
||||
| AddThreadCommand
|
||||
| DeleteThreadCommand
|
||||
| EditThreadCommand
|
||||
| AddGlobalFilterCommand
|
||||
| EditGlobalFilterCommand
|
||||
| RemoveGlobalFilterCommand
|
||||
| MoveGlobalFilterCommand;
|
||||
|
||||
export type AllCoreCommand = OdooCoreCommand | CoreCommand;
|
||||
|
||||
type OdooLocalCommand =
|
||||
| RefreshAllDataSourcesCommand
|
||||
| SetGlobalFilterValueCommand
|
||||
| SetManyGlobalFilterValueCommand;
|
||||
|
||||
type OdooCommand = OdooCoreCommand | OdooLocalCommand;
|
||||
|
||||
export type AllCommand = OdooCommand | Command;
|
||||
}
|
||||
11
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/static/src/@types/env.d.ts
vendored
Normal file
11
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/static/src/@types/env.d.ts
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { SpreadsheetChildEnv as SSChildEnv } from "@odoo/o-spreadsheet";
|
||||
import { Services } from "services";
|
||||
|
||||
declare module "@spreadsheet" {
|
||||
import { Model } from "@odoo/o-spreadsheet";
|
||||
|
||||
export interface SpreadsheetChildEnv extends SSChildEnv {
|
||||
model: OdooSpreadsheetModel;
|
||||
services: Services;
|
||||
}
|
||||
}
|
||||
11
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/static/src/@types/functions.d.ts
vendored
Normal file
11
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/static/src/@types/functions.d.ts
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
declare module "@spreadsheet" {
|
||||
import { AddFunctionDescription, Arg, EvalContext } from "@odoo/o-spreadsheet";
|
||||
|
||||
export interface CustomFunctionDescription extends AddFunctionDescription {
|
||||
compute: (this: ExtendedEvalContext, ...args: Arg[]) => any;
|
||||
}
|
||||
|
||||
interface ExtendedEvalContext extends EvalContext {
|
||||
getters: OdooGetters;
|
||||
}
|
||||
}
|
||||
74
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/static/src/@types/getters.d.ts
vendored
Normal file
74
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/static/src/@types/getters.d.ts
vendored
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import { CorePlugin, Model, UID } from "@odoo/o-spreadsheet";
|
||||
import { ChartOdooMenuPlugin, OdooChartCorePlugin, OdooChartCoreViewPlugin } from "@spreadsheet/chart";
|
||||
import { CurrencyPlugin } from "@spreadsheet/currency/plugins/currency";
|
||||
import { AccountingPlugin } from "addons/spreadsheet_account/static/src/plugins/accounting_plugin";
|
||||
import { GlobalFiltersCorePlugin, GlobalFiltersCoreViewPlugin } from "@spreadsheet/global_filters";
|
||||
import { ListCorePlugin, ListCoreViewPlugin } from "@spreadsheet/list";
|
||||
import { IrMenuPlugin } from "@spreadsheet/ir_ui_menu/ir_ui_menu_plugin";
|
||||
import { PivotOdooCorePlugin } from "@spreadsheet/pivot";
|
||||
import { PivotCoreGlobalFilterPlugin } from "@spreadsheet/pivot/plugins/pivot_core_global_filter_plugin";
|
||||
|
||||
type Getters = Model["getters"];
|
||||
type CoreGetters = CorePlugin["getters"];
|
||||
|
||||
/**
|
||||
* Union of all getter names of a plugin.
|
||||
*
|
||||
* e.g. With the following plugin
|
||||
* @example
|
||||
* class MyPlugin {
|
||||
* static getters = [
|
||||
* "getCell",
|
||||
* "getCellValue",
|
||||
* ] as const;
|
||||
* getCell() { ... }
|
||||
* getCellValue() { ... }
|
||||
* }
|
||||
* type Names = GetterNames<typeof MyPlugin>
|
||||
* // is equivalent to "getCell" | "getCellValue"
|
||||
*/
|
||||
type GetterNames<Plugin extends { getters: readonly string[] }> = Plugin["getters"][number];
|
||||
|
||||
/**
|
||||
* Extract getter methods from a plugin, based on its `getters` static array.
|
||||
* @example
|
||||
* class MyPlugin {
|
||||
* static getters = [
|
||||
* "getCell",
|
||||
* "getCellValue",
|
||||
* ] as const;
|
||||
* getCell() { ... }
|
||||
* getCellValue() { ... }
|
||||
* }
|
||||
* type MyPluginGetters = PluginGetters<typeof MyPlugin>;
|
||||
* // MyPluginGetters is equivalent to:
|
||||
* // {
|
||||
* // getCell: () => ...,
|
||||
* // getCellValue: () => ...,
|
||||
* // }
|
||||
*/
|
||||
type PluginGetters<Plugin extends { new (...args: unknown[]): any; getters: readonly string[] }> =
|
||||
Pick<InstanceType<Plugin>, GetterNames<Plugin>>;
|
||||
|
||||
declare module "@spreadsheet" {
|
||||
/**
|
||||
* Add getters from custom plugins defined in odoo
|
||||
*/
|
||||
|
||||
interface OdooCoreGetters extends CoreGetters {}
|
||||
interface OdooCoreGetters extends PluginGetters<typeof GlobalFiltersCorePlugin> {}
|
||||
interface OdooCoreGetters extends PluginGetters<typeof ListCorePlugin> {}
|
||||
interface OdooCoreGetters extends PluginGetters<typeof OdooChartCorePlugin> {}
|
||||
interface OdooCoreGetters extends PluginGetters<typeof ChartOdooMenuPlugin> {}
|
||||
interface OdooCoreGetters extends PluginGetters<typeof IrMenuPlugin> {}
|
||||
interface OdooCoreGetters extends PluginGetters<typeof PivotOdooCorePlugin> {}
|
||||
interface OdooCoreGetters extends PluginGetters<typeof PivotCoreGlobalFilterPlugin> {}
|
||||
|
||||
interface OdooGetters extends Getters {}
|
||||
interface OdooGetters extends OdooCoreGetters {}
|
||||
interface OdooGetters extends PluginGetters<typeof GlobalFiltersCoreViewPlugin> {}
|
||||
interface OdooGetters extends PluginGetters<typeof ListCoreViewPlugin> {}
|
||||
interface OdooGetters extends PluginGetters<typeof OdooChartCoreViewPlugin> {}
|
||||
interface OdooGetters extends PluginGetters<typeof CurrencyPlugin> {}
|
||||
interface OdooGetters extends PluginGetters<typeof AccountingPlugin> {}
|
||||
}
|
||||
175
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/static/src/@types/global_filter.d.ts
vendored
Normal file
175
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/static/src/@types/global_filter.d.ts
vendored
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
import { Range, RangeData } from "@odoo/o-spreadsheet";
|
||||
import { DomainListRepr } from "@web/core/domain";
|
||||
|
||||
declare module "@spreadsheet" {
|
||||
export type DateDefaultValue =
|
||||
| "today"
|
||||
| "yesterday"
|
||||
| "last_7_days"
|
||||
| "last_30_days"
|
||||
| "last_90_days"
|
||||
| "month_to_date"
|
||||
| "last_month"
|
||||
| "this_month"
|
||||
| "this_quarter"
|
||||
| "last_12_months"
|
||||
| "this_year"
|
||||
| "year_to_date";
|
||||
|
||||
export interface MonthDateValue {
|
||||
type: "month";
|
||||
year: number;
|
||||
month: number; // 1-12
|
||||
}
|
||||
|
||||
export interface QuarterDateValue {
|
||||
type: "quarter";
|
||||
year: number;
|
||||
quarter: number; // 1-4
|
||||
}
|
||||
|
||||
export interface YearDateValue {
|
||||
type: "year";
|
||||
year: number;
|
||||
}
|
||||
|
||||
export interface RelativeDateValue {
|
||||
type: "relative";
|
||||
period:
|
||||
| "today"
|
||||
| "yesterday"
|
||||
| "last_7_days"
|
||||
| "last_30_days"
|
||||
| "last_90_days"
|
||||
| "month_to_date"
|
||||
| "last_month"
|
||||
| "last_12_months"
|
||||
| "year_to_date";
|
||||
}
|
||||
|
||||
export interface DateRangeValue {
|
||||
type: "range";
|
||||
from?: string;
|
||||
to?: string;
|
||||
}
|
||||
|
||||
export type DateValue =
|
||||
| MonthDateValue
|
||||
| QuarterDateValue
|
||||
| YearDateValue
|
||||
| RelativeDateValue
|
||||
| DateRangeValue;
|
||||
|
||||
interface SetValue {
|
||||
operator: "set" | "not set";
|
||||
}
|
||||
|
||||
interface RelationIdsValue {
|
||||
operator: "in" | "not in" | "child_of";
|
||||
ids: number[];
|
||||
}
|
||||
|
||||
interface RelationContainsValue {
|
||||
operator: "ilike" | "not ilike" | "starts with";
|
||||
strings: string[];
|
||||
}
|
||||
|
||||
interface CurrentUser {
|
||||
operator: "in" | "not in";
|
||||
ids: "current_user";
|
||||
}
|
||||
|
||||
export type RelationValue = RelationIdsValue | SetValue | RelationContainsValue;
|
||||
type RelationDefaultValue = RelationValue | CurrentUser;
|
||||
|
||||
interface NumericUnaryValue {
|
||||
operator: "=" | "!=" | ">" | "<";
|
||||
targetValue: number;
|
||||
}
|
||||
|
||||
interface NumericRangeValue {
|
||||
operator: "between";
|
||||
minimumValue: number;
|
||||
maximumValue: number;
|
||||
}
|
||||
|
||||
export type NumericValue = NumericUnaryValue | NumericRangeValue;
|
||||
|
||||
interface TextInValue {
|
||||
operator: "in" | "not in";
|
||||
strings: string[];
|
||||
}
|
||||
|
||||
interface TextContainsValue {
|
||||
operator: "ilike" | "not ilike" | "starts with";
|
||||
text: string;
|
||||
}
|
||||
|
||||
export type TextValue = TextInValue | TextContainsValue | SetValue;
|
||||
|
||||
interface SelectionInValue {
|
||||
operator: "in" | "not in";
|
||||
selectionValues: string[];
|
||||
}
|
||||
|
||||
export interface FieldMatching {
|
||||
chain: string;
|
||||
type: string;
|
||||
offset?: number;
|
||||
}
|
||||
|
||||
export interface TextGlobalFilter {
|
||||
type: "text";
|
||||
id: string;
|
||||
label: string;
|
||||
rangesOfAllowedValues?: Range[];
|
||||
defaultValue?: TextValue;
|
||||
}
|
||||
|
||||
export interface SelectionGlobalFilter {
|
||||
type: "selection";
|
||||
id: string;
|
||||
label: string;
|
||||
resModel: string;
|
||||
selectionField: string;
|
||||
defaultValue?: SelectionInValue;
|
||||
}
|
||||
|
||||
export interface CmdTextGlobalFilter extends TextGlobalFilter {
|
||||
rangesOfAllowedValues?: RangeData[];
|
||||
}
|
||||
|
||||
export interface DateGlobalFilter {
|
||||
type: "date";
|
||||
id: string;
|
||||
label: string;
|
||||
defaultValue?: DateDefaultValue;
|
||||
}
|
||||
|
||||
export interface RelationalGlobalFilter {
|
||||
type: "relation";
|
||||
id: string;
|
||||
label: string;
|
||||
modelName: string;
|
||||
includeChildren: boolean;
|
||||
defaultValue?: RelationDefaultValue;
|
||||
domainOfAllowedValues?: DomainListRepr | string;
|
||||
}
|
||||
|
||||
export interface NumericGlobalFilter {
|
||||
type: "numeric";
|
||||
id: string;
|
||||
label: string;
|
||||
defaultValue?: NumericValue;
|
||||
}
|
||||
|
||||
export interface BooleanGlobalFilter {
|
||||
type: "boolean";
|
||||
id: string;
|
||||
label: string;
|
||||
defaultValue?: SetValue;
|
||||
}
|
||||
|
||||
export type GlobalFilter = TextGlobalFilter | DateGlobalFilter | RelationalGlobalFilter | BooleanGlobalFilter | SelectionGlobalFilter | NumericGlobalFilter;
|
||||
export type CmdGlobalFilter = CmdTextGlobalFilter | DateGlobalFilter | RelationalGlobalFilter | BooleanGlobalFilter | SelectionGlobalFilter | NumericGlobalFilter;
|
||||
}
|
||||
10
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/static/src/@types/misc.d.ts
vendored
Normal file
10
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/static/src/@types/misc.d.ts
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
declare module "@spreadsheet" {
|
||||
export interface Zone {
|
||||
bottom: number;
|
||||
left: number;
|
||||
right: number;
|
||||
top: number;
|
||||
}
|
||||
|
||||
export interface LazyTranslatedString extends String {}
|
||||
}
|
||||
16
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/static/src/@types/models.d.ts
vendored
Normal file
16
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/static/src/@types/models.d.ts
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
declare module "@spreadsheet" {
|
||||
import { Model } from "@odoo/o-spreadsheet";
|
||||
|
||||
export interface OdooSpreadsheetModel extends Model {
|
||||
getters: OdooGetters;
|
||||
dispatch: OdooDispatch;
|
||||
}
|
||||
|
||||
export interface OdooSpreadsheetModelConstructor {
|
||||
new (
|
||||
data: object,
|
||||
config: Partial<Model["config"]>,
|
||||
revisions: object[]
|
||||
): OdooSpreadsheetModel;
|
||||
}
|
||||
}
|
||||
75
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/static/src/@types/pivot.d.ts
vendored
Normal file
75
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/static/src/@types/pivot.d.ts
vendored
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import { OdooPivotRuntimeDefinition } from "@spreadsheet/pivot/pivot_runtime";
|
||||
import { ORM } from "@web/core/orm_service";
|
||||
import { PivotMeasure } from "@spreadsheet/pivot/pivot_runtime";
|
||||
import { ServerData } from "@spreadsheet/data_sources/server_data";
|
||||
import { Pivot, CommonPivotCoreDefinition, PivotCoreDefinition } from "@odoo/o-spreadsheet";
|
||||
|
||||
declare module "@spreadsheet" {
|
||||
export interface OdooPivotCoreDefinition extends CommonPivotCoreDefinition {
|
||||
type: "ODOO";
|
||||
model: string;
|
||||
domain: Array;
|
||||
context: Object;
|
||||
actionXmlId: string;
|
||||
}
|
||||
|
||||
export type ExtendedPivotCoreDefinition = PivotCoreDefinition | OdooPivotCoreDefinition;
|
||||
|
||||
interface OdooPivot<T> extends Pivot<T> {
|
||||
type: ExtendedPivotCoreDefinition["type"];
|
||||
}
|
||||
export interface GFLocalPivot {
|
||||
id: string;
|
||||
fieldMatching: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface OdooField {
|
||||
name: string;
|
||||
type: string;
|
||||
string: string;
|
||||
relation?: string;
|
||||
searchable?: boolean;
|
||||
aggregator?: string;
|
||||
store?: boolean;
|
||||
}
|
||||
|
||||
export type OdooFields = Record<string, Field | undefined>;
|
||||
|
||||
export interface PivotMetaData {
|
||||
colGroupBys: string[];
|
||||
rowGroupBys: string[];
|
||||
activeMeasures: string[];
|
||||
resModel: string;
|
||||
fields?: Record<string, Field | undefined>;
|
||||
modelLabel?: string;
|
||||
fieldAttrs: any;
|
||||
}
|
||||
|
||||
export interface PivotSearchParams {
|
||||
groupBy: string[];
|
||||
orderBy: string[];
|
||||
domain: Array;
|
||||
context: Object;
|
||||
}
|
||||
|
||||
/* Params used for the odoo pivot model */
|
||||
export interface WebPivotModelParams {
|
||||
metaData: PivotMetaData;
|
||||
searchParams: PivotSearchParams;
|
||||
}
|
||||
|
||||
export interface OdooPivotModelParams {
|
||||
fields: OdooFields;
|
||||
definition: OdooPivotRuntimeDefinition;
|
||||
searchParams: {
|
||||
domain: Array;
|
||||
context: Object;
|
||||
};
|
||||
}
|
||||
|
||||
export interface PivotModelServices {
|
||||
serverData: ServerData;
|
||||
orm: ORM;
|
||||
getters: OdooGetters;
|
||||
}
|
||||
}
|
||||
29
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/static/src/@types/plugins.d.ts
vendored
Normal file
29
odoo-bringout-oca-ocb-spreadsheet/spreadsheet/static/src/@types/plugins.d.ts
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
declare module "@spreadsheet" {
|
||||
import { CommandResult, CorePlugin, UIPlugin } from "@odoo/o-spreadsheet";
|
||||
import { CommandResult as CR } from "@spreadsheet/o_spreadsheet/cancelled_reason";
|
||||
type OdooCommandResult = CommandResult | typeof CR;
|
||||
|
||||
export interface OdooCorePlugin extends CorePlugin {
|
||||
getters: OdooCoreGetters;
|
||||
dispatch: OdooCoreDispatch;
|
||||
allowDispatch(command: AllCoreCommand): string | string[];
|
||||
beforeHandle(command: AllCoreCommand): void;
|
||||
handle(command: AllCoreCommand): void;
|
||||
}
|
||||
|
||||
export interface OdooCorePluginConstructor {
|
||||
new (config: unknown): OdooCorePlugin;
|
||||
}
|
||||
|
||||
export interface OdooUIPlugin extends UIPlugin {
|
||||
getters: OdooGetters;
|
||||
dispatch: OdooDispatch;
|
||||
allowDispatch(command: AllCommand): string | string[];
|
||||
beforeHandle(command: AllCommand): void;
|
||||
handle(command: AllCommand): void;
|
||||
}
|
||||
|
||||
export interface OdooUIPluginConstructor {
|
||||
new (config: unknown): OdooUIPlugin;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* @typedef {import("@web/webclient/actions/action_service").ActionOptions} ActionOptions
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {*} env
|
||||
* @param {string} actionXmlId
|
||||
* @param {Object} actionDescription
|
||||
* @param {ActionOptions} options
|
||||
*/
|
||||
export async function navigateTo(env, actionXmlId, actionDescription, options) {
|
||||
const actionService = env.services.action;
|
||||
let navigateActionDescription;
|
||||
const { views, view_mode, domain, context, name, res_model, res_id } = actionDescription;
|
||||
try {
|
||||
navigateActionDescription = await actionService.loadAction(actionXmlId, context);
|
||||
const filteredViews = views.map(
|
||||
([v, viewType]) =>
|
||||
navigateActionDescription.views.find(([, type]) => viewType === type) || [
|
||||
v,
|
||||
viewType,
|
||||
]
|
||||
);
|
||||
|
||||
navigateActionDescription = {
|
||||
...navigateActionDescription,
|
||||
context,
|
||||
domain,
|
||||
name,
|
||||
res_model,
|
||||
res_id,
|
||||
view_mode,
|
||||
target: "current",
|
||||
views: filteredViews,
|
||||
};
|
||||
} catch {
|
||||
navigateActionDescription = {
|
||||
type: "ir.actions.act_window",
|
||||
name,
|
||||
res_model,
|
||||
res_id,
|
||||
views,
|
||||
target: "current",
|
||||
domain,
|
||||
context,
|
||||
view_mode,
|
||||
};
|
||||
} finally {
|
||||
await actionService.doAction(
|
||||
// clear empty keys
|
||||
JSON.parse(JSON.stringify(navigateActionDescription)),
|
||||
options
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { useSpreadsheetNotificationStore } from "@spreadsheet/hooks";
|
||||
import { Spreadsheet, Model } from "@odoo/o-spreadsheet";
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
/**
|
||||
* Component wrapping the <Spreadsheet> component from o-spreadsheet
|
||||
* to add user interactions extensions from odoo such as notifications,
|
||||
* error dialogs, etc.
|
||||
*/
|
||||
export class SpreadsheetComponent extends Component {
|
||||
static template = "spreadsheet.SpreadsheetComponent";
|
||||
static components = { Spreadsheet };
|
||||
static props = {
|
||||
model: Model,
|
||||
};
|
||||
|
||||
get model() {
|
||||
return this.props.model;
|
||||
}
|
||||
setup() {
|
||||
useSpreadsheetNotificationStore();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
.o_spreadsheet_container {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<templates>
|
||||
<div t-name="spreadsheet.SpreadsheetComponent" class="o_spreadsheet_container o_field_highlight">
|
||||
<Spreadsheet model="model"/>
|
||||
</div>
|
||||
</templates>
|
||||
|
|
@ -1,66 +1,46 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import { DataSources } from "@spreadsheet/data_sources/data_sources";
|
||||
import { migrate } from "@spreadsheet/o_spreadsheet/migration";
|
||||
import { download } from "@web/core/network/download";
|
||||
import { registry } from "@web/core/registry";
|
||||
import spreadsheet from "../o_spreadsheet/o_spreadsheet_extended";
|
||||
import { createSpreadsheetModel, waitForDataLoaded } from "@spreadsheet/helpers/model";
|
||||
import { user } from "@web/core/user";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
|
||||
const { Model } = spreadsheet;
|
||||
|
||||
/**
|
||||
* @param {import("@web/env").OdooEnv} env
|
||||
* @param {object} action
|
||||
*/
|
||||
async function downloadSpreadsheet(env, action) {
|
||||
let { orm, name, data, stateUpdateMessages, xlsxData } = action.params;
|
||||
const canExport = await user.hasGroup("base.group_allow_export");
|
||||
if (!canExport) {
|
||||
env.services.notification.add(
|
||||
_t("You don't have the rights to export data. Please contact an Administrator."),
|
||||
{
|
||||
title: _t("Access Error"),
|
||||
type: "danger",
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
let { name, data, sources, stateUpdateMessages, xlsxData } = action.params;
|
||||
if (!xlsxData) {
|
||||
const dataSources = new DataSources(orm);
|
||||
const model = new Model(migrate(data), { dataSources }, stateUpdateMessages);
|
||||
const model = await createSpreadsheetModel({ env, data, revisions: stateUpdateMessages });
|
||||
await waitForDataLoaded(model);
|
||||
sources = model.getters.getLoadedDataSources();
|
||||
xlsxData = model.exportXLSX();
|
||||
}
|
||||
await download({
|
||||
url: "/spreadsheet/xlsx",
|
||||
data: {
|
||||
zip_name: `${name}.xlsx`,
|
||||
files: new Blob([JSON.stringify(xlsxData.files)], { type: "application/json" }),
|
||||
files: new Blob([JSON.stringify(xlsxData.files)], {
|
||||
type: "application/json",
|
||||
}),
|
||||
datasources: new Blob([JSON.stringify(sources)], {
|
||||
type: "application/json",
|
||||
}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the spreadsheet does not contains cells that are in loading state
|
||||
* @param {Model} model
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function waitForDataLoaded(model) {
|
||||
const dataSources = model.config.dataSources;
|
||||
return new Promise((resolve, reject) => {
|
||||
function check() {
|
||||
model.dispatch("EVALUATE_CELLS");
|
||||
if (isLoaded(model)) {
|
||||
dataSources.removeEventListener("data-source-updated", check);
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
dataSources.addEventListener("data-source-updated", check);
|
||||
check();
|
||||
});
|
||||
}
|
||||
|
||||
function isLoaded(model) {
|
||||
for (const sheetId of model.getters.getSheetIds()) {
|
||||
for (const cell of Object.values(model.getters.getCells(sheetId))) {
|
||||
if (
|
||||
cell.evaluated &&
|
||||
cell.evaluated.type === "error" &&
|
||||
cell.evaluated.error.message === _t("Data is loading")
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
registry
|
||||
.category("actions")
|
||||
.add("action_download_spreadsheet", downloadSpreadsheet, { force: true });
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import { _lt } from "@web/core/l10n/translation";
|
||||
|
||||
export const FILTER_DATE_OPTION = {
|
||||
quarter: ["first_quarter", "second_quarter", "third_quarter", "fourth_quarter"],
|
||||
year: ["this_year", "last_year", "antepenultimate_year"],
|
||||
};
|
||||
|
||||
// TODO Remove this mapping, We should only need number > description to avoid multiple conversions
|
||||
// This would require a migration though
|
||||
export const monthsOptions = [
|
||||
{ id: "january", description: _lt("January") },
|
||||
{ id: "february", description: _lt("February") },
|
||||
{ id: "march", description: _lt("March") },
|
||||
{ id: "april", description: _lt("April") },
|
||||
{ id: "may", description: _lt("May") },
|
||||
{ id: "june", description: _lt("June") },
|
||||
{ id: "july", description: _lt("July") },
|
||||
{ id: "august", description: _lt("August") },
|
||||
{ id: "september", description: _lt("September") },
|
||||
{ id: "october", description: _lt("October") },
|
||||
{ id: "november", description: _lt("November") },
|
||||
{ id: "december", description: _lt("December") },
|
||||
];
|
||||
|
|
@ -1,48 +1,49 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { getBundle, loadBundle } from "@web/core/assets";
|
||||
import { sprintf } from "@web/core/utils/strings";
|
||||
import { loadBundle } from "@web/core/assets";
|
||||
|
||||
const actionRegistry = registry.category("actions");
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} env
|
||||
* Add a new function client action which loads the spreadsheet bundle, then
|
||||
* launch the actual action.
|
||||
* The action should be redefine in the bundle with `{ force: true }`
|
||||
* and the actual action component or function
|
||||
* @param {string} actionName
|
||||
* @param {function} actionLazyLoader
|
||||
* @param {string} [path]
|
||||
* @param {string} [displayName]
|
||||
*/
|
||||
export async function loadSpreadsheetAction(env, actionName, actionLazyLoader) {
|
||||
const desc = await getBundle("spreadsheet.o_spreadsheet");
|
||||
await loadBundle(desc);
|
||||
export function addSpreadsheetActionLazyLoader(actionName, path, displayName) {
|
||||
const actionLazyLoader = async (env, action) => {
|
||||
// load the bundle which should redefine the action in the registry
|
||||
await loadBundle("spreadsheet.o_spreadsheet");
|
||||
|
||||
if (actionRegistry.get(actionName) === actionLazyLoader) {
|
||||
// At this point, the real spreadsheet client action should be loaded and have
|
||||
// replaced this function in the action registry. If it's not the case,
|
||||
// it probably means that there was a crash in the bundle (e.g. syntax
|
||||
// error). In this case, this action will remain in the registry, which
|
||||
// will lead to an infinite loop. To prevent that, we push another action
|
||||
// in the registry.
|
||||
actionRegistry.add(
|
||||
actionName,
|
||||
() => {
|
||||
const msg = sprintf(env._t("%s couldn't be loaded"), actionName);
|
||||
env.services.notification.add(msg, { type: "danger" });
|
||||
},
|
||||
{ force: true }
|
||||
);
|
||||
if (actionRegistry.get(actionName) === actionLazyLoader) {
|
||||
// At this point, the real spreadsheet client action should be loaded and have
|
||||
// replaced this function in the action registry. If it's not the case,
|
||||
// it probably means that there was a crash in the bundle (e.g. syntax
|
||||
// error). In this case, this action will remain in the registry, which
|
||||
// will lead to an infinite loop. To prevent that, we push another action
|
||||
// in the registry.
|
||||
actionRegistry.add(
|
||||
actionName,
|
||||
() => {
|
||||
const msg = _t("%s couldn't be loaded", actionName);
|
||||
env.services.notification.add(msg, { type: "danger" });
|
||||
},
|
||||
{ force: true }
|
||||
);
|
||||
}
|
||||
// then do the action again, with the actual definition registered
|
||||
return action;
|
||||
};
|
||||
if (path) {
|
||||
actionLazyLoader.path = path;
|
||||
}
|
||||
if (displayName) {
|
||||
actionLazyLoader.displayName = displayName;
|
||||
}
|
||||
actionRegistry.add(actionName, actionLazyLoader);
|
||||
}
|
||||
|
||||
const loadSpreadsheetDownloadAction = async (env, context) => {
|
||||
await loadSpreadsheetAction(env, "action_download_spreadsheet", loadSpreadsheetDownloadAction);
|
||||
|
||||
return {
|
||||
...context,
|
||||
target: "current",
|
||||
tag: "action_download_spreadsheet",
|
||||
type: "ir.actions.client",
|
||||
};
|
||||
};
|
||||
|
||||
actionRegistry.add("action_download_spreadsheet", loadSpreadsheetDownloadAction);
|
||||
addSpreadsheetActionLazyLoader("action_download_spreadsheet");
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
import { registry } from "@web/core/registry";
|
||||
import { BinaryField, binaryField } from "@web/views/fields/binary/binary_field";
|
||||
|
||||
export class SpreadsheetBinaryField extends BinaryField {
|
||||
static template = "spreadsheet.SpreadsheetBinaryField";
|
||||
|
||||
setup() {
|
||||
super.setup();
|
||||
}
|
||||
|
||||
async onFileDownload() {}
|
||||
}
|
||||
|
||||
export const spreadsheetBinaryField = {
|
||||
...binaryField,
|
||||
component: SpreadsheetBinaryField,
|
||||
};
|
||||
|
||||
registry.category("fields").add("binary_spreadsheet", spreadsheetBinaryField);
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue