Initial commit: Report packages

This commit is contained in:
Ernad Husremovic 2025-08-29 15:20:51 +02:00
commit bc5e1e9efa
604 changed files with 474102 additions and 0 deletions

View file

@ -0,0 +1,4 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_currency
from . import test_currency_rate

View file

@ -0,0 +1,85 @@
from odoo.tests.common import TransactionCase
class TestCurrencyRates(TransactionCase):
@classmethod
def setUpClass(cls):
super(TestCurrencyRates, cls).setUpClass()
cls.env["res.currency"].create(
[
{
"name": "MC1",
"symbol": ":D",
"rounding": 0.001,
},
{
"name": "MC2",
"symbol": "§",
},
]
)
eur_company = cls.env["res.company"].create(
{"name": "Company with EUR", "currency_id": cls.env.ref("base.EUR").id}
)
usd_company = cls.env["res.company"].create(
{"name": "Company with USD", "currency_id": cls.env.ref("base.USD").id}
)
cls.env.user.company_ids |= eur_company
cls.env.user.company_ids |= usd_company
cls.env.user.company_id = eur_company
cls.usd_company_id = usd_company.id
def test_get_currencies_for_spreadsheet(self):
self.assertEqual(
self.env["res.currency"].get_currencies_for_spreadsheet(["MC1", "MC2"]),
[
{
"code": "MC1",
"symbol": ":D",
"decimalPlaces": 3,
"position": "after",
},
{
"code": "MC2",
"symbol": "§",
"decimalPlaces": 2,
"position": "after",
},
],
)
self.assertEqual(
self.env["res.currency"].get_currencies_for_spreadsheet(["ProbablyNotACurrencyName?", "MC2"]),
[
None,
{
"code": "MC2",
"symbol": "§",
"decimalPlaces": 2,
"position": "after",
},
],
)
def test_get_company_currency_for_spreadsheet(self):
self.assertEqual(
self.env["res.currency"].get_company_currency_for_spreadsheet(),
{
"code": "EUR",
"symbol": "",
"decimalPlaces": 2,
"position": "after",
}
)
self.assertEqual(
self.env["res.currency"].get_company_currency_for_spreadsheet(self.usd_company_id),
{
"code": "USD",
"symbol": "$",
"decimalPlaces": 2,
"position": "before",
}
)
self.assertEqual(
self.env["res.currency"].get_company_currency_for_spreadsheet(123456),
False
)

View file

@ -0,0 +1,136 @@
from freezegun import freeze_time
from odoo.tests.common import TransactionCase
CURRENT_USD = 1.5
CURRENT_EUR = 1
CURRENT_CAD = 1.2
USD_11 = 1.8
CAD_11 = 1.9
CAD_UTC = 1.3
CAD_AUS = 2
fake_now_utc = "2020-01-01 21:00:00"
class TestCurrencyRates(TransactionCase):
@classmethod
def setUpClass(cls):
super(TestCurrencyRates, cls).setUpClass()
usd = cls.env.ref("base.USD")
eur = cls.env.ref("base.EUR")
cad = cls.env.ref("base.CAD")
new_company = cls.env["res.company"].create(
{"name": "Test Currency Company", "currency_id": eur.id}
)
cls.env.user.company_ids |= new_company
cls.env.user.company_id = new_company
cls.env["res.currency.rate"].create(
[
{
"currency_id": usd.id,
"rate": CURRENT_USD,
},
{
"currency_id": cad.id,
"rate": CURRENT_CAD,
},
{
"name": "2021-11-11",
"currency_id": usd.id,
"rate": USD_11,
},
{
"name": "2021-11-11",
"currency_id": cad.id,
"rate": CAD_11,
},
]
)
def test_currency_without_date(self):
self.assertEqual(
self.env["res.currency.rate"]._get_rate_for_spreadsheet("USD", "EUR"),
CURRENT_EUR / CURRENT_USD,
)
self.assertEqual(
self.env["res.currency.rate"]._get_rate_for_spreadsheet("EUR", "USD"),
CURRENT_USD,
)
self.assertEqual(
self.env["res.currency.rate"]._get_rate_for_spreadsheet("USD", "CAD"),
CURRENT_CAD / CURRENT_USD,
)
def test_currency_with_date(self):
self.assertEqual(
self.env["res.currency.rate"]._get_rate_for_spreadsheet(
"USD", "EUR", "2021-11-11"
),
CURRENT_EUR / USD_11,
)
self.assertEqual(
self.env["res.currency.rate"]._get_rate_for_spreadsheet(
"EUR", "USD", "2021-11-11"
),
USD_11,
)
self.assertEqual(
self.env["res.currency.rate"]._get_rate_for_spreadsheet(
"USD", "CAD", "2021-11-11"
),
CAD_11 / USD_11,
)
def test_currency_invalid_args(self):
self.assertEqual(
self.env["res.currency.rate"]._get_rate_for_spreadsheet("INVALID", "EUR"),
False,
)
self.assertEqual(
self.env["res.currency.rate"]._get_rate_for_spreadsheet("EUR", "INVALID"),
False,
)
self.assertEqual(
self.env["res.currency.rate"]._get_rate_for_spreadsheet("INVALID", "USD"),
False,
)
self.assertEqual(
self.env["res.currency.rate"]._get_rate_for_spreadsheet("USD", "INVALID"),
False,
)
self.assertEqual(
self.env["res.currency.rate"]._get_rate_for_spreadsheet(False, "EUR"), False
)
self.assertEqual(
self.env["res.currency.rate"]._get_rate_for_spreadsheet("EUR", False), False
)
@freeze_time(fake_now_utc)
def test_rate_by_tz(self):
cad = self.env.ref("base.CAD")
self.env.user.tz = "UTC"
self.env["res.currency.rate"].create(
{
"currency_id": cad.id,
"rate": CAD_UTC,
}
)
self.env.user.tz = "Australia/Sydney"
self.env["res.currency.rate"].create(
{
"currency_id": cad.id,
"rate": CAD_AUS,
}
)
self.assertEqual(
self.env["res.currency.rate"]._get_rate_for_spreadsheet("CAD", "EUR"),
CURRENT_EUR / CAD_AUS,
)
self.assertEqual(
self.env["res.currency.rate"]
.with_context(tz="UTC")
._get_rate_for_spreadsheet("CAD", "EUR"),
CURRENT_EUR / CAD_UTC,
)

View file

@ -0,0 +1,218 @@
from collections import defaultdict
from itertools import chain
import json
import re
from odoo.tests.common import TransactionCase
markdown_link_regex = r"^\[([^\[]+)\]\((.+)\)$"
xml_id_url_prefix = "odoo://ir_menu_xml_id/"
odoo_view_link_prefix = "odoo://view/"
def odoo_charts(data):
"""return all odoo chart definition in the spreadsheet"""
figures = []
for sheet in data["sheets"]:
figures += [
dict(figure["data"], id=figure["id"])
for figure in sheet["figures"]
if figure["tag"] == "chart" and figure["data"]["type"].startswith("odoo_")
]
return figures
def links_urls(data):
"""return all markdown links in cells"""
urls = []
link_prefix = "odoo://view/"
for sheet in data["sheets"]:
for cell in sheet["cells"].values():
content = cell.get("content", "")
match = re.match(markdown_link_regex, content)
if match and match.group(2).startswith(link_prefix):
urls.append(match.group(2))
return urls
def odoo_view_links(data):
"""return all view definitions embedded in link cells.
urls looks like odoo://view/{... view data...}
"""
return [
json.loads(url[len(odoo_view_link_prefix):])
for url in links_urls(data)
if url.startswith(odoo_view_link_prefix)
]
def remove_group_operator(field_name):
"""remove the group operator
>>> remove_group_operator("amount:sum")
>>> "amount"
"""
return field_name.split(":")[0]
def domain_fields(domain):
"""return all field names used in the domain"""
fields = []
for leaf in domain:
if len(leaf) == 3:
fields.append(leaf[0])
return fields
def pivot_measure_fields(pivot):
return [
measure["field"]
for measure in pivot["measures"]
if measure["field"] != "__count"
]
def pivot_fields(pivot):
"""return all field names used in a pivot definition"""
model = pivot["model"]
fields = set(
pivot["colGroupBys"]
+ pivot["rowGroupBys"]
+ pivot_measure_fields(pivot)
+ domain_fields(pivot["domain"])
)
measure = pivot.get("sortedColumn") and pivot["sortedColumn"]["measure"]
if measure and measure != "__count":
fields.add(measure)
return model, fields
def list_order_fields(list_definition):
return [order["name"] for order in list_definition["orderBy"]]
def list_fields(list_definition):
"""return all field names used in a list definitions"""
model = list_definition["model"]
fields = set(
list_definition["columns"]
+ list_order_fields(list_definition)
+ domain_fields(list_definition["domain"])
)
return model, fields
def chart_fields(chart):
"""return all field names used in a chart definitions"""
model = chart["metaData"]["resModel"]
fields = set(
chart["metaData"]["groupBy"]
+ chart["searchParams"]["groupBy"]
+ domain_fields(chart["searchParams"]["domain"])
)
measure = chart["metaData"]["measure"]
if measure != "__count":
fields.add(measure)
return model, fields
def filter_fields(data):
"""return all field names used in global filter definitions"""
fields_by_model = defaultdict(set)
charts = odoo_charts(data)
odoo_version = data.get("odooVersion", 1)
if odoo_version < 5:
for filter_definition in data.get("globalFilters", []):
for pivot_id, matching in filter_definition.get("pivotFields", dict()).items():
model = data["pivots"][pivot_id]["model"]
fields_by_model[model].add(matching["field"])
for list_id, matching in filter_definition.get("listFields", dict()).items():
model = data["lists"][list_id]["model"]
fields_by_model[model].add(matching["field"])
for chart_id, matching in filter_definition.get("graphFields", dict()).items():
chart = next((chart for chart in charts if chart["id"] == chart_id), None)
model = chart["metaData"]["resModel"]
fields_by_model[model].add(matching["field"])
else:
for pivot in data["pivots"].values():
model = pivot.model
field = pivot.get("fieldMatching", {}).get("chain")
if field:
fields_by_model[model].add(field)
for _list in data["lists"].values():
model = _list.model
field = _list.get("fieldMatching", {}).get("chain")
if field:
fields_by_model[model].add(field)
for chart in charts:
model = chart["metaData"]["resModel"]
field = chart.get("fieldMatching", {}).get("chain")
if field:
fields_by_model[model].add(field)
return dict(fields_by_model)
def odoo_view_fields(view):
return view["action"]["modelName"], set(domain_fields(view["action"]["domain"]))
def extract_fields(extract_fn, items):
fields_by_model = defaultdict(set)
for item in items:
model, fields = extract_fn(item)
fields_by_model[model] |= {remove_group_operator(field) for field in fields}
return dict(fields_by_model)
def fields_in_spreadsheet(data):
"""return all fields, grouped by model, used in the spreadsheet"""
all_fields = chain(
extract_fields(list_fields, data.get("lists", dict()).values()).items(),
extract_fields(pivot_fields, data.get("pivots", dict()).values()).items(),
extract_fields(chart_fields, odoo_charts(data)).items(),
extract_fields(odoo_view_fields, odoo_view_links(data)).items(),
filter_fields(data).items(),
)
fields_by_model = defaultdict(set)
for model, fields in all_fields:
fields_by_model[model] |= fields
return dict(fields_by_model)
def xml_ids_in_spreadsheet(data):
return set(data.get("chartOdooMenusReferences", {}).values()) | {
url[len(xml_id_url_prefix):]
for url in links_urls(data)
if url.startswith(xml_id_url_prefix)
}
class ValidateSpreadsheetData(TransactionCase):
def validate_spreadsheet_data(self, stringified_data, spreadsheet_name):
data = json.loads(stringified_data)
for model, fields in fields_in_spreadsheet(data).items():
if model not in self.env:
raise AssertionError(
f"model '{model}' used in '{spreadsheet_name}' does not exist"
)
for field_chain in fields:
field_model = model
for fname in field_chain.split(
"."
): # field chain 'product_id.channel_ids'
if fname not in self.env[field_model]._fields:
raise AssertionError(
f"field '{fname}' used in spreadsheet '{spreadsheet_name}' does not exist on model '{field_model}'"
)
field = self.env[field_model]._fields[fname]
if field.relational:
field_model = field.comodel_name
for xml_id in xml_ids_in_spreadsheet(data):
record = self.env.ref(xml_id, raise_if_not_found=False)
if not record:
raise AssertionError(
f"xml id '{xml_id}' used in spreadsheet '{spreadsheet_name}' does not exist"
)