mirror of
https://github.com/bringout/oca-ocb-report.git
synced 2026-04-18 21:02:06 +02:00
Initial commit: Report packages
This commit is contained in:
commit
bc5e1e9efa
604 changed files with 474102 additions and 0 deletions
|
|
@ -0,0 +1,73 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import { nextTick } from "@web/../tests/helpers/utils";
|
||||
import spreadsheet from "@spreadsheet/o_spreadsheet/o_spreadsheet_extended";
|
||||
import { createModelWithDataSource } from "./model";
|
||||
const uuidGenerator = new spreadsheet.helpers.UuidGenerator();
|
||||
|
||||
/** @typedef {import("@spreadsheet/o_spreadsheet/o_spreadsheet").Model} Model */
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Model} model
|
||||
*/
|
||||
export function insertChartInSpreadsheet(model, type = "odoo_bar") {
|
||||
const definition = getChartDefinition(type);
|
||||
model.dispatch("CREATE_CHART", {
|
||||
sheetId: model.getters.getActiveSheetId(),
|
||||
id: definition.id,
|
||||
position: {
|
||||
x: 10,
|
||||
y: 10,
|
||||
},
|
||||
definition,
|
||||
});
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {Object} params
|
||||
* @param {function} [params.mockRPC]
|
||||
* @param {object} [params.serverData]
|
||||
* @param {string} [params.type]
|
||||
*
|
||||
* @returns { Promise<{ model: Model, env: Object }>}
|
||||
*/
|
||||
export async function createSpreadsheetWithChart(params = {}) {
|
||||
const model = await createModelWithDataSource({
|
||||
mockRPC: params.mockRPC,
|
||||
serverData: params.serverData,
|
||||
});
|
||||
|
||||
insertChartInSpreadsheet(model, params.type);
|
||||
|
||||
const env = model.config.evalContext.env;
|
||||
env.model = model;
|
||||
await nextTick();
|
||||
return { model, env };
|
||||
}
|
||||
|
||||
function getChartDefinition(type) {
|
||||
return {
|
||||
metaData: {
|
||||
groupBy: ["foo", "bar"],
|
||||
measure: "__count",
|
||||
order: null,
|
||||
resModel: "partner",
|
||||
},
|
||||
searchParams: {
|
||||
comparison: null,
|
||||
context: {},
|
||||
domain: [],
|
||||
groupBy: [],
|
||||
orderBy: [],
|
||||
},
|
||||
stacked: true,
|
||||
title: "Partners",
|
||||
background: "#FFFFFF",
|
||||
legendPosition: "top",
|
||||
verticalAxisPosition: "left",
|
||||
dataSourceId: uuidGenerator.uuidv4(),
|
||||
id: uuidGenerator.uuidv4(),
|
||||
type,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import spreadsheet from "@spreadsheet/o_spreadsheet/o_spreadsheet_extended";
|
||||
import { waitForDataSourcesLoaded } from "@spreadsheet/../tests/utils/model";
|
||||
|
||||
const { toCartesian, toZone } = spreadsheet.helpers;
|
||||
|
||||
/**
|
||||
* @typedef {import("@spreadsheet/global_filters/plugins/global_filters_core_plugin").GlobalFilter} GlobalFilter
|
||||
*/
|
||||
|
||||
/**
|
||||
* Select a cell
|
||||
*/
|
||||
export function selectCell(model, xc) {
|
||||
const { col, row } = toCartesian(xc);
|
||||
return model.selection.selectCell(col, row);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a global filter and ensure the data sources are completely reloaded
|
||||
* @param {Model} model
|
||||
* @param {{filter: GlobalFilter}} filter
|
||||
*/
|
||||
export async function addGlobalFilter(model, filter, fieldMatchings = {}) {
|
||||
const result = model.dispatch("ADD_GLOBAL_FILTER", { ...filter, ...fieldMatchings });
|
||||
await waitForDataSourcesLoaded(model);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a global filter and ensure the data sources are completely reloaded
|
||||
*/
|
||||
export async function removeGlobalFilter(model, id) {
|
||||
const result = model.dispatch("REMOVE_GLOBAL_FILTER", { id });
|
||||
await waitForDataSourcesLoaded(model);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit a global filter and ensure the data sources are completely reloaded
|
||||
*/
|
||||
export async function editGlobalFilter(model, filter) {
|
||||
const result = model.dispatch("EDIT_GLOBAL_FILTER", filter);
|
||||
await waitForDataSourcesLoaded(model);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of a global filter and ensure the data sources are completely
|
||||
* reloaded
|
||||
*/
|
||||
export async function setGlobalFilterValue(model, payload) {
|
||||
const result = model.dispatch("SET_GLOBAL_FILTER_VALUE", payload);
|
||||
await waitForDataSourcesLoaded(model);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the selection
|
||||
*/
|
||||
export function setSelection(model, xc) {
|
||||
const zone = toZone(xc);
|
||||
model.selection.selectZone({ cell: { col: zone.left, row: zone.top }, zone });
|
||||
}
|
||||
|
||||
/**
|
||||
* Autofill from a zone to a cell
|
||||
*/
|
||||
export function autofill(model, from, to) {
|
||||
setSelection(model, from);
|
||||
model.dispatch("AUTOFILL_SELECT", toCartesian(to));
|
||||
model.dispatch("AUTOFILL");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the content of a cell
|
||||
*/
|
||||
export function setCellContent(model, xc, content, sheetId = model.getters.getActiveSheetId()) {
|
||||
model.dispatch("UPDATE_CELL", { ...toCartesian(xc), sheetId, content });
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the format of a cell
|
||||
*/
|
||||
export function setCellFormat(model, xc, format, sheetId = model.getters.getActiveSheetId()) {
|
||||
model.dispatch("UPDATE_CELL", { ...toCartesian(xc), sheetId, format });
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the style of a cell
|
||||
*/
|
||||
export function setCellStyle(model, xc, style, sheetId = model.getters.getActiveSheetId()) {
|
||||
model.dispatch("UPDATE_CELL", { ...toCartesian(xc), sheetId, style });
|
||||
}
|
||||
|
||||
/** Create a test chart in the active sheet*/
|
||||
export function createBasicChart(model, chartId, sheetId = model.getters.getActiveSheetId()) {
|
||||
model.dispatch("CREATE_CHART", {
|
||||
id: chartId,
|
||||
position: { x: 0, y: 0 },
|
||||
sheetId: sheetId,
|
||||
definition: {
|
||||
title: "test",
|
||||
dataSets: ["A1"],
|
||||
type: "bar",
|
||||
background: "#fff",
|
||||
verticalAxisPosition: "left",
|
||||
legendPosition: "top",
|
||||
stackedBar: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** Create a test scorecard chart in the active sheet*/
|
||||
export function createScorecardChart(model, chartId, sheetId = model.getters.getActiveSheetId()) {
|
||||
model.dispatch("CREATE_CHART", {
|
||||
id: chartId,
|
||||
position: { x: 0, y: 0 },
|
||||
sheetId: sheetId,
|
||||
definition: {
|
||||
title: "test",
|
||||
keyValue: "A1",
|
||||
type: "scorecard",
|
||||
background: "#fff",
|
||||
baselineColorDown: "#DC6965",
|
||||
baselineColorUp: "#00A04A",
|
||||
baselineMode: "absolute",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** Create a test scorecard chart in the active sheet*/
|
||||
export function createGaugeChart(model, chartId, sheetId = model.getters.getActiveSheetId()) {
|
||||
model.dispatch("CREATE_CHART", {
|
||||
id: chartId,
|
||||
position: { x: 0, y: 0 },
|
||||
sheetId: sheetId,
|
||||
definition: {
|
||||
title: "test",
|
||||
type: "gauge",
|
||||
background: "#fff",
|
||||
dataRange: "A1",
|
||||
sectionRule: {
|
||||
rangeMin: "0",
|
||||
rangeMax: "100",
|
||||
colors: {
|
||||
lowerColor: "#112233",
|
||||
middleColor: "#445566",
|
||||
upperColor: "#778899",
|
||||
},
|
||||
lowerInflectionPoint: {
|
||||
type: "number",
|
||||
value: "25",
|
||||
},
|
||||
upperInflectionPoint: {
|
||||
type: "number",
|
||||
value: "85",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,445 @@
|
|||
/** @odoo-module */
|
||||
|
||||
/**
|
||||
* @typedef {object} ServerData
|
||||
* @property {object} models
|
||||
* @property {object} views
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get a basic arch for a pivot, which is compatible with the data given by
|
||||
* getBasicData().
|
||||
*
|
||||
* Here is the pivot created:
|
||||
* A B C D E F
|
||||
* 1 1 2 12 17 Total
|
||||
* 2 Proba Proba Proba Proba Proba
|
||||
* 3 false 15 15
|
||||
* 4 true 11 10 95 116
|
||||
* 5 Total 11 15 10 95 131
|
||||
*/
|
||||
export function getBasicPivotArch() {
|
||||
return /* xml */ `
|
||||
<pivot string="Partners">
|
||||
<field name="foo" type="col"/>
|
||||
<field name="bar" type="row"/>
|
||||
<field name="probability" type="measure"/>
|
||||
</pivot>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a basic arch for a list, which is compatible with the data given by
|
||||
* getBasicData().
|
||||
*
|
||||
* Here is the list created:
|
||||
* A B C D
|
||||
* 1 Foo bar Date Product
|
||||
* 2 12 True 2016-04-14 xphone
|
||||
* 3 1 True 2016-10-26 xpad
|
||||
* 4 17 True 2016-12-15 xpad
|
||||
* 5 2 False 2016-12-11 xpad
|
||||
*/
|
||||
export function getBasicListArch() {
|
||||
return /* xml */ `
|
||||
<tree string="Partners">
|
||||
<field name="foo"/>
|
||||
<field name="bar"/>
|
||||
<field name="date"/>
|
||||
<field name="product_id"/>
|
||||
</tree>
|
||||
`;
|
||||
}
|
||||
|
||||
export function getBasicGraphArch() {
|
||||
return /* xml */ `
|
||||
<graph>
|
||||
<field name="bar" />
|
||||
</graph>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {ServerData}
|
||||
*/
|
||||
export function getBasicServerData() {
|
||||
return {
|
||||
models: getBasicData(),
|
||||
views: {
|
||||
"partner,false,list": getBasicListArch(),
|
||||
"partner,false,pivot": getBasicPivotArch(),
|
||||
"partner,false,graph": getBasicGraphArch(),
|
||||
"partner,false,form": /* xml */ `<Form/>`,
|
||||
"partner,false,search": /* xml */ `<search/>`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} model
|
||||
* @param {Array<string>} columns
|
||||
* @param {Object} data
|
||||
*
|
||||
* @returns { {definition: Object, columns: Array<Object>}}
|
||||
*/
|
||||
export function generateListDefinition(model, columns, data = getBasicData()) {
|
||||
const cols = [];
|
||||
for (const name of columns) {
|
||||
cols.push({
|
||||
name,
|
||||
type: data[model].fields[name].type,
|
||||
});
|
||||
}
|
||||
return {
|
||||
definition: {
|
||||
metaData: {
|
||||
resModel: model,
|
||||
columns,
|
||||
},
|
||||
searchParams: {
|
||||
domain: [],
|
||||
context: {},
|
||||
orderBy: [],
|
||||
},
|
||||
name: "List",
|
||||
},
|
||||
columns: cols,
|
||||
};
|
||||
}
|
||||
|
||||
export function getBasicListArchs() {
|
||||
return {
|
||||
"partner,false,list": getBasicListArch(),
|
||||
"partner,false,search": /* xml */ `<search/>`,
|
||||
"partner,false,form": /* xml */ `<form/>`,
|
||||
};
|
||||
}
|
||||
|
||||
export function getBasicData() {
|
||||
return {
|
||||
"documents.document": {
|
||||
fields: {
|
||||
name: { string: "Name", type: "char" },
|
||||
raw: { string: "Data", type: "text" },
|
||||
thumbnail: { string: "Thumbnail", type: "text" },
|
||||
display_thumbnail: { string: "Thumbnail", type: "text" },
|
||||
favorited_ids: { string: "Name", type: "many2many" },
|
||||
is_favorited: { string: "Name", type: "boolean" },
|
||||
mimetype: { string: "Mimetype", type: "char" },
|
||||
partner_id: { string: "Related partner", type: "many2one", relation: "partner" },
|
||||
owner_id: { string: "Owner", type: "many2one", relation: "partner" },
|
||||
handler: {
|
||||
string: "Handler",
|
||||
type: "selection",
|
||||
selection: [["spreadsheet", "Spreadsheet"]],
|
||||
},
|
||||
previous_attachment_ids: {
|
||||
string: "History",
|
||||
type: "many2many",
|
||||
relation: "ir.attachment",
|
||||
},
|
||||
tag_ids: { string: "Tags", type: "many2many", relation: "documents.tag" },
|
||||
folder_id: { string: "Workspaces", type: "many2one", relation: "documents.folder" },
|
||||
res_model: { string: "Model (technical)", type: "char" },
|
||||
available_rule_ids: {
|
||||
string: "Rules",
|
||||
type: "many2many",
|
||||
relation: "documents.workflow.rule",
|
||||
},
|
||||
},
|
||||
records: [
|
||||
{
|
||||
id: 1,
|
||||
name: "My spreadsheet",
|
||||
raw: "{}",
|
||||
is_favorited: false,
|
||||
folder_id: 1,
|
||||
handler: "spreadsheet",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "",
|
||||
raw: "{}",
|
||||
is_favorited: true,
|
||||
folder_id: 1,
|
||||
handler: "spreadsheet",
|
||||
},
|
||||
],
|
||||
},
|
||||
"ir.model": {
|
||||
fields: {
|
||||
name: { string: "Model Name", type: "char" },
|
||||
model: { string: "Model", type: "char" },
|
||||
},
|
||||
records: [
|
||||
{
|
||||
id: 37,
|
||||
name: "Product",
|
||||
model: "product",
|
||||
},
|
||||
{
|
||||
id: 40,
|
||||
name: "Partner",
|
||||
model: "partner",
|
||||
},
|
||||
],
|
||||
},
|
||||
"documents.folder": {
|
||||
fields: {
|
||||
name: { string: "Name", type: "char" },
|
||||
parent_folder_id: {
|
||||
string: "Parent Workspace",
|
||||
type: "many2one",
|
||||
relation: "documents.folder",
|
||||
},
|
||||
description: { string: "Description", type: "text" },
|
||||
},
|
||||
records: [
|
||||
{
|
||||
id: 1,
|
||||
name: "Workspace1",
|
||||
description: "Workspace",
|
||||
parent_folder_id: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
"documents.tag": {
|
||||
fields: {},
|
||||
records: [],
|
||||
get_tags: () => [],
|
||||
},
|
||||
"documents.workflow.rule": {
|
||||
fields: {},
|
||||
records: [],
|
||||
},
|
||||
"documents.share": {
|
||||
fields: {},
|
||||
records: [],
|
||||
},
|
||||
"spreadsheet.template": {
|
||||
fields: {
|
||||
name: { string: "Name", type: "char" },
|
||||
data: { string: "Data", type: "binary" },
|
||||
thumbnail: { string: "Thumbnail", type: "binary" },
|
||||
display_thumbnail: { string: "Thumbnail", type: "text" },
|
||||
},
|
||||
records: [
|
||||
{ id: 1, name: "Template 1", data: btoa("{}") },
|
||||
{ id: 2, name: "Template 2", data: btoa("{}") },
|
||||
],
|
||||
},
|
||||
"res.currency": {
|
||||
fields: {
|
||||
name: { string: "Code", type: "char" },
|
||||
symbol: { string: "Symbol", type: "char" },
|
||||
position: {
|
||||
string: "Position",
|
||||
type: "selection",
|
||||
selection: [
|
||||
["after", "A"],
|
||||
["before", "B"],
|
||||
],
|
||||
},
|
||||
decimal_places: { string: "decimal", type: "integer" },
|
||||
},
|
||||
records: [
|
||||
{
|
||||
id: 1,
|
||||
name: "EUR",
|
||||
symbol: "€",
|
||||
position: "after",
|
||||
decimal_places: 2,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "USD",
|
||||
symbol: "$",
|
||||
position: "before",
|
||||
decimal_places: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
partner: {
|
||||
fields: {
|
||||
foo: {
|
||||
string: "Foo",
|
||||
type: "integer",
|
||||
store: true,
|
||||
searchable: true,
|
||||
group_operator: "sum",
|
||||
},
|
||||
bar: {
|
||||
string: "Bar",
|
||||
type: "boolean",
|
||||
store: true,
|
||||
sortable: true,
|
||||
searchable: true,
|
||||
},
|
||||
name: {
|
||||
string: "name",
|
||||
type: "char",
|
||||
store: true,
|
||||
sortable: true,
|
||||
searchable: true,
|
||||
},
|
||||
date: {
|
||||
string: "Date",
|
||||
type: "date",
|
||||
store: true,
|
||||
sortable: true,
|
||||
searchable: true,
|
||||
},
|
||||
create_date: {
|
||||
string: "Creation Date",
|
||||
type: "datetime",
|
||||
store: true,
|
||||
sortable: true,
|
||||
},
|
||||
active: { string: "Active", type: "bool", default: true, searchable: true },
|
||||
product_id: {
|
||||
string: "Product",
|
||||
type: "many2one",
|
||||
relation: "product",
|
||||
store: true,
|
||||
sortable: true,
|
||||
searchable: true,
|
||||
},
|
||||
tag_ids: {
|
||||
string: "Tags",
|
||||
type: "many2many",
|
||||
relation: "tag",
|
||||
store: true,
|
||||
sortable: true,
|
||||
searchable: true,
|
||||
},
|
||||
probability: {
|
||||
string: "Probability",
|
||||
type: "float",
|
||||
searchable: true,
|
||||
store: true,
|
||||
group_operator: "avg",
|
||||
},
|
||||
field_with_array_agg: {
|
||||
string: "field_with_array_agg",
|
||||
type: "integer",
|
||||
searchable: true,
|
||||
group_operator: "array_agg",
|
||||
},
|
||||
currency_id: {
|
||||
string: "Currency",
|
||||
type: "many2one",
|
||||
relation: "res.currency",
|
||||
store: true,
|
||||
sortable: true,
|
||||
searchable: true,
|
||||
},
|
||||
pognon: {
|
||||
string: "Money!",
|
||||
type: "monetary",
|
||||
currency_field: "currency_id",
|
||||
store: true,
|
||||
sortable: true,
|
||||
group_operator: "avg",
|
||||
searchable: true,
|
||||
},
|
||||
partner_properties: {
|
||||
string: "Properties",
|
||||
type: "properties",
|
||||
store: true,
|
||||
sortable: true,
|
||||
searchable: true,
|
||||
},
|
||||
jsonField: {
|
||||
string: "Json Field",
|
||||
type: "json",
|
||||
store: true,
|
||||
},
|
||||
},
|
||||
records: [
|
||||
{
|
||||
id: 1,
|
||||
foo: 12,
|
||||
bar: true,
|
||||
date: "2016-04-14",
|
||||
create_date: "2016-04-03 00:00:00",
|
||||
product_id: 37,
|
||||
probability: 10,
|
||||
field_with_array_agg: 1,
|
||||
tag_ids: [42, 67],
|
||||
currency_id: 1,
|
||||
pognon: 74.4,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
foo: 1,
|
||||
bar: true,
|
||||
date: "2016-10-26",
|
||||
create_date: "2014-04-03 00:05:32",
|
||||
product_id: 41,
|
||||
probability: 11,
|
||||
field_with_array_agg: 2,
|
||||
tag_ids: [42, 67],
|
||||
currency_id: 2,
|
||||
pognon: 74.8,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
foo: 17,
|
||||
bar: true,
|
||||
date: "2016-12-15",
|
||||
create_date: "2006-01-03 11:30:50",
|
||||
product_id: 41,
|
||||
probability: 95,
|
||||
field_with_array_agg: 3,
|
||||
tag_ids: [],
|
||||
currency_id: 1,
|
||||
pognon: 4,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
foo: 2,
|
||||
bar: false,
|
||||
date: "2016-12-11",
|
||||
create_date: "2016-12-10 21:59:59",
|
||||
product_id: 41,
|
||||
probability: 15,
|
||||
field_with_array_agg: 4,
|
||||
tag_ids: [42],
|
||||
currency_id: 2,
|
||||
pognon: 1000,
|
||||
},
|
||||
],
|
||||
},
|
||||
product: {
|
||||
fields: {
|
||||
name: { string: "Product Name", type: "char" },
|
||||
active: { string: "Active", type: "bool", default: true },
|
||||
},
|
||||
records: [
|
||||
{
|
||||
id: 37,
|
||||
display_name: "xphone",
|
||||
},
|
||||
{
|
||||
id: 41,
|
||||
display_name: "xpad",
|
||||
},
|
||||
],
|
||||
},
|
||||
tag: {
|
||||
fields: {
|
||||
name: { string: "Tag Name", type: "char" },
|
||||
},
|
||||
records: [
|
||||
{
|
||||
id: 42,
|
||||
display_name: "isCool",
|
||||
},
|
||||
{
|
||||
id: 67,
|
||||
display_name: "Growing",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
/** @odoo-module */
|
||||
|
||||
const { DateTime } = luxon;
|
||||
import { Domain } from "@web/core/domain";
|
||||
|
||||
function getDateDomainBounds(domain) {
|
||||
const startDateStr = domain[1][2];
|
||||
const endDateStr = domain[2][2];
|
||||
|
||||
const isDateTime = startDateStr.includes(":");
|
||||
|
||||
if (isDateTime) {
|
||||
const dateTimeFormat = "yyyy-MM-dd HH:mm:ss";
|
||||
const start = DateTime.fromFormat(startDateStr, dateTimeFormat);
|
||||
const end = DateTime.fromFormat(endDateStr, dateTimeFormat);
|
||||
return { start, end };
|
||||
}
|
||||
|
||||
const start = DateTime.fromISO(startDateStr);
|
||||
const end = DateTime.fromISO(endDateStr);
|
||||
const startIsIncluded = domain[1][1] === ">=";
|
||||
const endIsIncluded = domain[2][1] === "<=";
|
||||
return {
|
||||
start: startIsIncluded ? start.startOf("day") : start.endOf("day"),
|
||||
end: endIsIncluded ? end.endOf("day") : end.startOf("day"),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} assert
|
||||
* @param {string} field
|
||||
* @param {string} start
|
||||
* @param {string} end
|
||||
* @param {import("@web/core/domain").DomainRepr} domain
|
||||
*/
|
||||
export function assertDateDomainEqual(assert, field, start, end, domain) {
|
||||
domain = new Domain(domain).toList();
|
||||
assert.deepEqual(domain[0], "&");
|
||||
assert.deepEqual(domain[1], [field, ">=", start]);
|
||||
assert.deepEqual(domain[2], [field, "<=", end]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("@web/core/domain").DomainRepr} domain
|
||||
* @returns {number}
|
||||
*/
|
||||
export function getDateDomainDurationInDays(domain) {
|
||||
domain = new Domain(domain).toList();
|
||||
const bounds = getDateDomainBounds(domain);
|
||||
const diff = bounds.end.diff(bounds.start, ["days"]);
|
||||
return Math.round(diff.days);
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import spreadsheet from "@spreadsheet/o_spreadsheet/o_spreadsheet_extended";
|
||||
|
||||
const { toCartesian } = spreadsheet.helpers;
|
||||
|
||||
/**
|
||||
* Get the value of the given cell
|
||||
*/
|
||||
export function getCellValue(model, xc, sheetId = model.getters.getActiveSheetId()) {
|
||||
const { col, row } = toCartesian(xc);
|
||||
const cell = model.getters.getCell(sheetId, col, row);
|
||||
if (!cell) {
|
||||
return undefined;
|
||||
}
|
||||
return cell.evaluated.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cell of the given xc
|
||||
*/
|
||||
export function getCell(model, xc, sheetId = model.getters.getActiveSheetId()) {
|
||||
const { col, row } = toCartesian(xc);
|
||||
return model.getters.getCell(sheetId, col, row);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cells of the given sheet (or active sheet if not provided)
|
||||
*/
|
||||
export function getCells(model, sheetId = model.getters.getActiveSheetId()) {
|
||||
return model.getters.getCells(sheetId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the formula of the given xc
|
||||
*/
|
||||
export function getCellFormula(model, xc, sheetId = model.getters.getActiveSheetId()) {
|
||||
const cell = getCell(model, xc, sheetId);
|
||||
return cell && cell.isFormula() ? model.getters.getFormulaCellContent(sheetId, cell) : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the content of the given xc
|
||||
*/
|
||||
export function getCellContent(model, xc, sheetId = model.getters.getActiveSheetId()) {
|
||||
const cell = getCell(model, xc, sheetId);
|
||||
return cell ? model.getters.getCellText(cell, true) : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of the merges (["A1:A2"]) of the sheet
|
||||
*/
|
||||
export function getMerges(model, sheetId = model.getters.getActiveSheetId()) {
|
||||
return model.exportData().sheets.find((sheet) => sheet.id === sheetId).merges;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the formatted value of the given xc
|
||||
*/
|
||||
export function getCellFormattedValue(model, xc, sheetId = model.getters.getActiveSheetId()) {
|
||||
const cell = getCell(model, xc, sheetId);
|
||||
return cell ? model.getters.getCellText(cell, false) : "";
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import spreadsheet from "@spreadsheet/o_spreadsheet/o_spreadsheet_extended";
|
||||
import { generateListDefinition } from "./data";
|
||||
import { createModelWithDataSource, waitForDataSourcesLoaded } from "./model";
|
||||
|
||||
const uuidGenerator = new spreadsheet.helpers.UuidGenerator();
|
||||
|
||||
/** @typedef {import("@spreadsheet/o_spreadsheet/o_spreadsheet").Model} Model */
|
||||
|
||||
/**
|
||||
* Insert a list in a spreadsheet model.
|
||||
*
|
||||
* @param {Model} model
|
||||
* @param {Object} params
|
||||
* @param {string} params.model
|
||||
* @param {Array<string>} params.columns
|
||||
* @param {number} [params.linesNumber]
|
||||
* @param {[number, number]} [params.position]
|
||||
* @param {string} [params.sheetId]
|
||||
*/
|
||||
export function insertListInSpreadsheet(model, params) {
|
||||
const { definition, columns } = generateListDefinition(params.model, params.columns);
|
||||
const [col, row] = params.position || [0, 0];
|
||||
|
||||
model.dispatch("INSERT_ODOO_LIST", {
|
||||
sheetId: params.sheetId || model.getters.getActiveSheetId(),
|
||||
definition,
|
||||
linesNumber: params.linesNumber || 10,
|
||||
columns,
|
||||
id: model.getters.getNextListId(),
|
||||
col,
|
||||
row,
|
||||
dataSourceId: uuidGenerator.uuidv4(),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} params
|
||||
* @param {string} [params.model]
|
||||
* @param {Array<string>} [params.columns]
|
||||
* @param {Object} [params.serverData]
|
||||
* @param {function} [params.mockRPC]
|
||||
* @param {number} [params.linesNumber]
|
||||
* @param {[number, number]} [params.position]
|
||||
* @param {string} [params.sheetId]
|
||||
*
|
||||
* @returns { Promise<{ model: Model, env: Object }>}
|
||||
*/
|
||||
export async function createSpreadsheetWithList(params = {}) {
|
||||
const model = await createModelWithDataSource({
|
||||
mockRPC: params.mockRPC,
|
||||
serverData: params.serverData,
|
||||
});
|
||||
|
||||
insertListInSpreadsheet(model, {
|
||||
columns: params.columns || ["foo", "bar", "date", "product_id"],
|
||||
model: params.model || "partner",
|
||||
linesNumber: params.linesNumber,
|
||||
position: params.position,
|
||||
sheetId: params.sheetId,
|
||||
});
|
||||
|
||||
const env = model.config.evalContext.env;
|
||||
env.model = model;
|
||||
await waitForDataSourcesLoaded(model);
|
||||
return { model, env };
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
registry
|
||||
.category("mock_server")
|
||||
.add("res.currency/get_currencies_for_spreadsheet", function (route, args) {
|
||||
const currencyNames = args.args[0];
|
||||
const result = [];
|
||||
for (let currencyName of currencyNames) {
|
||||
const curr = this.models["res.currency"].records.find(
|
||||
(curr) => curr.name === currencyName
|
||||
);
|
||||
|
||||
result.push({
|
||||
code: curr.name,
|
||||
symbol: curr.symbol,
|
||||
decimalPlaces: curr.decimal_places || 2,
|
||||
position: curr.position || "after",
|
||||
});
|
||||
}
|
||||
return result;
|
||||
})
|
||||
.add("res.currency/get_company_currency_for_spreadsheet", function (route, args) {
|
||||
return {
|
||||
code: "EUR",
|
||||
symbol: "€",
|
||||
position: "after",
|
||||
decimalPlaces: 2,
|
||||
};
|
||||
});
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import { ormService } from "@web/core/orm_service";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { makeFakeLocalizationService } from "@web/../tests/helpers/mock_services";
|
||||
import { makeTestEnv } from "@web/../tests/helpers/mock_env";
|
||||
import { nextTick } from "@web/../tests/helpers/utils";
|
||||
|
||||
import spreadsheet from "@spreadsheet/o_spreadsheet/o_spreadsheet_extended";
|
||||
import { DataSources } from "@spreadsheet/data_sources/data_sources";
|
||||
import { getBasicServerData } from "./data";
|
||||
|
||||
const { Model } = spreadsheet;
|
||||
|
||||
/**
|
||||
* @typedef {import("@spreadsheet/../tests/utils/data").ServerData} ServerData
|
||||
*/
|
||||
|
||||
export function setupDataSourceEvaluation(model) {
|
||||
model.config.dataSources.addEventListener("data-source-updated", () => {
|
||||
const sheetId = model.getters.getActiveSheetId();
|
||||
model.dispatch("EVALUATE_CELLS", { sheetId });
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a spreadsheet model with a mocked server environnement
|
||||
*
|
||||
* @param {object} params
|
||||
* @param {object} [params.spreadsheetData] Spreadsheet data to import
|
||||
* @param {ServerData} [params.serverData] Data to be injected in the mock server
|
||||
* @param {function} [params.mockRPC] Mock rpc function
|
||||
*/
|
||||
export async function createModelWithDataSource(params = {}) {
|
||||
registry.category("services").add("orm", ormService, { force: true });
|
||||
registry.category("services").add("localization", makeFakeLocalizationService(), { force: true });
|
||||
const env = await makeTestEnv({
|
||||
serverData: params.serverData || getBasicServerData(),
|
||||
mockRPC: params.mockRPC,
|
||||
});
|
||||
const model = new Model(params.spreadsheetData, {
|
||||
evalContext: { env },
|
||||
//@ts-ignore
|
||||
dataSources: new DataSources(env.services.orm),
|
||||
});
|
||||
setupDataSourceEvaluation(model);
|
||||
await nextTick(); // initial async formulas loading
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Model} model
|
||||
*/
|
||||
export async function waitForDataSourcesLoaded(model) {
|
||||
function readAllCellsValue() {
|
||||
for (const sheetId of model.getters.getSheetIds()) {
|
||||
const cells = model.getters.getCells(sheetId);
|
||||
for (const cellId in cells) {
|
||||
cells[cellId].evaluated.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Read a first time in order to trigger the RPC
|
||||
readAllCellsValue();
|
||||
//@ts-ignore
|
||||
await model.config.dataSources.waitForAllLoaded();
|
||||
await nextTick();
|
||||
// Read a second time to trigger the compute format (which could trigger a RPC for currency, in list)
|
||||
readAllCellsValue();
|
||||
await nextTick();
|
||||
// Read a third time to trigger the RPC to get the correct currency
|
||||
readAllCellsValue();
|
||||
await nextTick();
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import { PivotArchParser } from "@web/views/pivot/pivot_arch_parser";
|
||||
import { nextTick } from "@web/../tests/helpers/utils";
|
||||
|
||||
import PivotDataSource from "@spreadsheet/pivot/pivot_data_source";
|
||||
import { getBasicServerData } from "./data";
|
||||
import { createModelWithDataSource, waitForDataSourcesLoaded } from "./model";
|
||||
|
||||
/** @typedef {import("@spreadsheet/o_spreadsheet/o_spreadsheet").Model} Model */
|
||||
|
||||
/**
|
||||
* @param {Model} model
|
||||
* @param {object} params
|
||||
* @param {string} params.arch
|
||||
* @param {[number, number]} [params.anchor]
|
||||
*/
|
||||
export async function insertPivotInSpreadsheet(model, params) {
|
||||
const archInfo = new PivotArchParser().parse(params.arch);
|
||||
const definition = {
|
||||
metaData: {
|
||||
colGroupBys: archInfo.colGroupBys,
|
||||
rowGroupBys: archInfo.rowGroupBys,
|
||||
activeMeasures: archInfo.activeMeasures,
|
||||
resModel: params.resModel || "partner",
|
||||
},
|
||||
searchParams: {
|
||||
domain: [],
|
||||
context: {},
|
||||
groupBy: [],
|
||||
orderBy: [],
|
||||
},
|
||||
name: "Partner Pivot",
|
||||
};
|
||||
const dataSource = model.config.dataSources.create(PivotDataSource, definition);
|
||||
await dataSource.load();
|
||||
const { cols, rows, measures } = dataSource.getTableStructure().export();
|
||||
const table = {
|
||||
cols,
|
||||
rows,
|
||||
measures,
|
||||
};
|
||||
const [col, row] = params.anchor || [0, 0];
|
||||
model.dispatch("INSERT_PIVOT", {
|
||||
id: model.getters.getNextPivotId(),
|
||||
sheetId: model.getters.getActiveSheetId(),
|
||||
col,
|
||||
row,
|
||||
table,
|
||||
dataSourceId: "pivotData1",
|
||||
definition,
|
||||
});
|
||||
await nextTick();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} params
|
||||
* @param {string} [params.arch]
|
||||
* @param {object} [params.serverData]
|
||||
* @param {function} [params.mockRPC]
|
||||
* @returns {Promise<{ model: Model, env: object}>}
|
||||
*/
|
||||
export async function createSpreadsheetWithPivot(params = {}) {
|
||||
const serverData = params.serverData || getBasicServerData();
|
||||
const model = await createModelWithDataSource({
|
||||
mockRPC: params.mockRPC,
|
||||
serverData: params.serverData,
|
||||
});
|
||||
const arch = params.arch || serverData.views["partner,false,pivot"];
|
||||
await insertPivotInSpreadsheet(model, { arch });
|
||||
const env = model.config.evalContext.env;
|
||||
env.model = model;
|
||||
await waitForDataSourcesLoaded(model);
|
||||
return { model, env };
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import { nextTick } from "@web/../tests/helpers/utils";
|
||||
import { createSpreadsheetWithPivot } from "./pivot";
|
||||
import { insertListInSpreadsheet } from "./list";
|
||||
|
||||
export async function createSpreadsheetWithPivotAndList() {
|
||||
const { model, env } = await createSpreadsheetWithPivot();
|
||||
insertListInSpreadsheet(model, {
|
||||
model: "partner",
|
||||
columns: ["foo", "bar", "date", "product_id"],
|
||||
});
|
||||
await nextTick();
|
||||
return { env, model };
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import spreadsheet from "@spreadsheet/o_spreadsheet/o_spreadsheet_extended";
|
||||
import { registerCleanup } from "@web/../tests/helpers/cleanup";
|
||||
import { getFixture } from "@web/../tests/helpers/utils";
|
||||
import { loadJS, templates } from "@web/core/assets";
|
||||
|
||||
const { App } = owl;
|
||||
const { Spreadsheet } = spreadsheet;
|
||||
const { getMenuChildren } = spreadsheet.helpers;
|
||||
|
||||
/** @typedef {import("@spreadsheet/o_spreadsheet/o_spreadsheet").Model} Model */
|
||||
|
||||
/**
|
||||
* Mount o-spreadsheet component with the given spreadsheet model
|
||||
* @param {Model} model
|
||||
* @returns {Promise<HTMLElement>}
|
||||
*/
|
||||
export async function mountSpreadsheet(model) {
|
||||
await loadJS("/web/static/lib/Chart/Chart.js");
|
||||
const app = new App(Spreadsheet, {
|
||||
props: { model },
|
||||
templates: templates,
|
||||
env: model.config.evalContext.env,
|
||||
test: true,
|
||||
});
|
||||
registerCleanup(() => app.destroy());
|
||||
const fixture = getFixture();
|
||||
await app.mount(fixture);
|
||||
return fixture;
|
||||
}
|
||||
|
||||
export async function doMenuAction(registry, path, env) {
|
||||
const root = path[0];
|
||||
let node = registry.get(root);
|
||||
for (const p of path.slice(1)) {
|
||||
const children = getMenuChildren(node, env);
|
||||
node = children.find((child) => child.id === p);
|
||||
}
|
||||
if (!node) {
|
||||
throw new Error(`Cannot find menu with path "${path.join("/")}"`);
|
||||
}
|
||||
await node.action(env);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue