mirror of
https://github.com/bringout/oca-ocb-report.git
synced 2026-04-22 07:02:06 +02:00
19.0 vanilla
This commit is contained in:
parent
62d197ac8b
commit
184bb0e321
667 changed files with 691406 additions and 239886 deletions
|
|
@ -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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue