Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[16.0][IMP] spreadsheet_*_oca: Add possibility to make columns dynamic #44

Merged
merged 1 commit into from
Sep 4, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -26,6 +26,8 @@
import_data["new"] = 1
if self.dynamic:
import_data["dyn_number_of_rows"] = self.number_of_rows
if self.dynamic_cols:
import_data["dyn_number_of_cols"] = self.number_of_cols

Check warning on line 30 in spreadsheet_dashboard_oca/wizards/spreadsheet_spreadsheet_import.py

Codecov / codecov/patch

spreadsheet_dashboard_oca/wizards/spreadsheet_spreadsheet_import.py#L30

Added line #L30 was not covered by tests
return {
"type": "ir.actions.client",
"tag": "action_spreadsheet_oca",
@@ -42,6 +44,8 @@
import_data["new_sheet"] = new_sheet
if self.dynamic:
import_data["dyn_number_of_rows"] = self.number_of_rows
if self.dynamic_cols:
import_data["dyn_number_of_cols"] = self.number_of_cols

Check warning on line 48 in spreadsheet_dashboard_oca/wizards/spreadsheet_spreadsheet_import.py

Codecov / codecov/patch

spreadsheet_dashboard_oca/wizards/spreadsheet_spreadsheet_import.py#L48

Added line #L48 was not covered by tests
return {
"type": "ir.actions.client",
"tag": "action_spreadsheet_oca",
6 changes: 3 additions & 3 deletions spreadsheet_oca/README.rst
Original file line number Diff line number Diff line change
@@ -77,12 +77,12 @@ Usage
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Go to any pivot
* Press on insert button
* Select the dynamic option and set a number of rows
* Select the dynamic rows or dynamic columns option and set a number of rows/columns

A new table that will be updated with the actual or filtered values will be added.

* Note: When a pivot has multiple levels of aggrupations in the rows, the number of rows
selected will be transfered to each level.
* Note: When a pivot has multiple levels of aggrupations in the rows or the columns,
the number of rows/columns selected will be transfered to each level.

Example:
number of groups -> 2
3 changes: 2 additions & 1 deletion spreadsheet_oca/__manifest__.py
Original file line number Diff line number Diff line change
@@ -39,7 +39,8 @@
"spreadsheet_oca/static/src/spreadsheet/bundle/odoo_panels.esm.js",
"spreadsheet_oca/static/src/spreadsheet/bundle/chart_panels.esm.js",
"spreadsheet_oca/static/src/spreadsheet/bundle/chart_panel.esm.js",
"spreadsheet_oca/static/src/spreadsheet/utils/dynamic_row_generator.esm.js",
"spreadsheet_oca/static/src/spreadsheet/utils/dynamic_generators.esm.js",
"spreadsheet_oca/static/src/pivot/pivot_table.esm.js",
],
},
}
46 changes: 38 additions & 8 deletions spreadsheet_oca/i18n/es.po
Original file line number Diff line number Diff line change
@@ -6,8 +6,8 @@ msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-07-18 13:08+0000\n"
"PO-Revision-Date: 2024-07-18 15:09+0200\n"
"POT-Creation-Date: 2024-09-03 11:51+0000\n"
"PO-Revision-Date: 2024-09-03 13:54+0200\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: es\n"
@@ -213,9 +213,20 @@ msgid "Duplicated groupbys in pivot are not supprted"
msgstr "Las agrupaciones duplicadas en la vista pivot no están soportadas"

#. module: spreadsheet_oca
#: model:ir.model.fields,field_description:spreadsheet_oca.field_spreadsheet_select_row_number__dynamic_cols
msgid "Dynamic Cols"
msgstr "Columnas Dinámicas"

#. module: spreadsheet_oca
#: model:ir.model.fields,field_description:spreadsheet_oca.field_spreadsheet_spreadsheet_import__dynamic_cols
msgid "Dynamic Columns"
msgstr "Columnas Dinámicas"

#. module: spreadsheet_oca
#: model:ir.model.fields,field_description:spreadsheet_oca.field_spreadsheet_select_row_number__dynamic_rows
#: model:ir.model.fields,field_description:spreadsheet_oca.field_spreadsheet_spreadsheet_import__dynamic
msgid "Dynamic"
msgstr "Dinámico"
msgid "Dynamic Rows"
msgstr "Filas Dinámicas"

#. module: spreadsheet_oca
#: model_terms:ir.ui.view,arch_db:spreadsheet_oca.spreadsheet_spreadsheet_form_view
@@ -410,12 +421,22 @@ msgstr "Siguiente"
msgid "Next Revision"
msgstr "Siguiente revisión"

#. module: spreadsheet_oca
#: model:ir.model.fields,field_description:spreadsheet_oca.field_spreadsheet_select_row_number__number_of_cols
msgid "Number Of Cols"
msgstr "Número de columnas"

#. module: spreadsheet_oca
#: model:ir.model.fields,field_description:spreadsheet_oca.field_spreadsheet_select_row_number__number_of_rows
#: model:ir.model.fields,field_description:spreadsheet_oca.field_spreadsheet_spreadsheet_import__number_of_rows
msgid "Number Of Rows"
msgstr "Número de filas"

#. module: spreadsheet_oca
#: model:ir.model.fields,field_description:spreadsheet_oca.field_spreadsheet_spreadsheet_import__number_of_cols
msgid "Number of Columns"
msgstr "Número de columnas"

#. module: spreadsheet_oca
#: model_terms:ir.ui.view,arch_db:spreadsheet_oca.spreadsheet_spreadsheet_import_form_view
msgid "OK"
@@ -564,14 +585,23 @@ msgstr "Hojas de cálculo"
msgid "Stacked"
msgstr "Apilados"

#. module: spreadsheet_oca
#: model:ir.model.fields,help:spreadsheet_oca.field_spreadsheet_spreadsheet_import__dynamic_cols
msgid ""
"This field allows you to generate tables that its cols are updated with the "
"filters set in the spreadsheets."
msgstr ""
"Este campo permite generar tablas en las que se actualizan sus columnas con "
"los filtros establecidos en las hojas de cálculo."

#. module: spreadsheet_oca
#: model:ir.model.fields,help:spreadsheet_oca.field_spreadsheet_spreadsheet_import__dynamic
msgid ""
"This field allows you to generate tables that are updated with the filters "
"set in the spreadsheets."
"This field allows you to generate tables that its rows are updated with the "
"filters set in the spreadsheets."
msgstr ""
"Este campo permite generar tablas que se actualizan con los filtros "
"establecidos en las hojas de cálculo."
"Este campo permite generar tablas en las que se actualizan sus filas con "
"los filtros establecidos en las hojas de cálculo."

#. module: spreadsheet_oca
#. odoo-javascript
36 changes: 33 additions & 3 deletions spreadsheet_oca/i18n/spreadsheet_oca.pot
Original file line number Diff line number Diff line change
@@ -6,6 +6,8 @@ msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-03 11:51+0000\n"
"PO-Revision-Date: 2024-09-03 11:51+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
@@ -214,8 +216,19 @@ msgid "Duplicated groupbys in pivot are not supprted"
msgstr ""

#. module: spreadsheet_oca
#: model:ir.model.fields,field_description:spreadsheet_oca.field_spreadsheet_select_row_number__dynamic_cols
msgid "Dynamic Cols"
msgstr ""

#. module: spreadsheet_oca
#: model:ir.model.fields,field_description:spreadsheet_oca.field_spreadsheet_spreadsheet_import__dynamic_cols
msgid "Dynamic Columns"
msgstr ""

#. module: spreadsheet_oca
#: model:ir.model.fields,field_description:spreadsheet_oca.field_spreadsheet_select_row_number__dynamic_rows
#: model:ir.model.fields,field_description:spreadsheet_oca.field_spreadsheet_spreadsheet_import__dynamic
msgid "Dynamic"
msgid "Dynamic Rows"
msgstr ""

#. module: spreadsheet_oca
@@ -419,12 +432,22 @@ msgstr ""
msgid "Next Revision"
msgstr ""

#. module: spreadsheet_oca
#: model:ir.model.fields,field_description:spreadsheet_oca.field_spreadsheet_select_row_number__number_of_cols
msgid "Number Of Cols"
msgstr ""

#. module: spreadsheet_oca
#: model:ir.model.fields,field_description:spreadsheet_oca.field_spreadsheet_select_row_number__number_of_rows
#: model:ir.model.fields,field_description:spreadsheet_oca.field_spreadsheet_spreadsheet_import__number_of_rows
msgid "Number Of Rows"
msgstr ""

#. module: spreadsheet_oca
#: model:ir.model.fields,field_description:spreadsheet_oca.field_spreadsheet_spreadsheet_import__number_of_cols
msgid "Number of Columns"
msgstr ""

#. module: spreadsheet_oca
#: model_terms:ir.ui.view,arch_db:spreadsheet_oca.spreadsheet_spreadsheet_import_form_view
msgid "OK"
@@ -573,11 +596,18 @@ msgstr ""
msgid "Stacked"
msgstr ""

#. module: spreadsheet_oca
#: model:ir.model.fields,help:spreadsheet_oca.field_spreadsheet_spreadsheet_import__dynamic_cols
msgid ""
"This field allows you to generate tables that its cols are updated with the "
"filters set in the spreadsheets."
msgstr ""

#. module: spreadsheet_oca
#: model:ir.model.fields,help:spreadsheet_oca.field_spreadsheet_spreadsheet_import__dynamic
msgid ""
"This field allows you to generate tables that are updated with the filters "
"set in the spreadsheets."
"This field allows you to generate tables that its rows are updated with the "
"filters set in the spreadsheets."
msgstr ""

#. module: spreadsheet_oca
6 changes: 3 additions & 3 deletions spreadsheet_oca/readme/USAGE.rst
Original file line number Diff line number Diff line change
@@ -35,12 +35,12 @@
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Go to any pivot
* Press on insert button
* Select the dynamic option and set a number of rows
* Select the dynamic rows or dynamic columns option and set a number of rows/columns

A new table that will be updated with the actual or filtered values will be added.

* Note: When a pivot has multiple levels of aggrupations in the rows, the number of rows
selected will be transfered to each level.
* Note: When a pivot has multiple levels of aggrupations in the rows or the columns,
the number of rows/columns selected will be transfered to each level.

Example:
number of groups -> 2
6 changes: 3 additions & 3 deletions spreadsheet_oca/static/description/index.html
Original file line number Diff line number Diff line change
@@ -445,12 +445,12 @@ <h2><a class="toc-backref" href="#toc-entry-3"><strong>Create a new dynamic spre
<ul class="simple">
<li>Go to any pivot</li>
<li>Press on insert button</li>
<li>Select the dynamic option and set a number of rows</li>
<li>Select the dynamic rows or dynamic columns option and set a number of rows/columns</li>
</ul>
<p>A new table that will be updated with the actual or filtered values will be added.</p>
<ul>
<li><p class="first">Note: When a pivot has multiple levels of aggrupations in the rows, the number of rows
selected will be transfered to each level.</p>
<li><p class="first">Note: When a pivot has multiple levels of aggrupations in the rows or the columns,
the number of rows/columns selected will be transfered to each level.</p>
<p>Example:
number of groups -&gt; 2
number of rows -&gt; 3</p>
67 changes: 67 additions & 0 deletions spreadsheet_oca/static/src/pivot/pivot_table.esm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/** @odoo-module */
/* Copyright 2024 Tecnativa - Carlos Roca
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
import {SpreadsheetPivotTable} from "@spreadsheet/pivot/pivot_table";
import {patch} from "@web/core/utils/patch";

patch(SpreadsheetPivotTable.prototype, "spreadsheet_oca.SpreadsheetPivotTable", {
get _dynamic_cols() {
return this._cols[this._cols.length - 1] === "dynamic_cols";
},
getNumberOfMeasures() {
if (this._dynamic_cols) {
// We have disabled the computation of the last column to prevent showing
// the totalization of the columns when they are of a dynamic type. If
// shown, it would display the total of all records corresponding to the
// row, rather than what is being displayed on the screen.
return 1;
}
return this._super(...arguments);
},
getColHeaders() {
if (this._dynamic_cols) {
// We return a copy of this._cols without the last entry of the array,
// which indicates that these are dynamic columns.
var cols = [...this._cols];
cols.pop();
return cols;
}
return this._super(...arguments);
},
getMeasureHeaders() {
if (this._dynamic_cols) {
return this._cols[this._cols.length - 2];
}
return this._super(...arguments);
},
getColWidth() {
if (this._dynamic_cols) {
return this._cols[this._cols.length - 2].length;
}
return this._super(...arguments);
},
getColHeight() {
if (this._dynamic_cols) {
return this._cols.length - 1;
}
return this._super(...arguments);
},
getColMeasureIndex(values) {
if (this._dynamic_cols) {
var cols = [...this._cols];
cols.pop();
const vals = JSON.stringify(values);
const maxLength = Math.max(...cols.map((col) => col.length));
for (let i = 0; i < maxLength; i++) {
const cellValues = cols.map((col) =>
JSON.stringify((col[i] || {}).values)
);
if (cellValues.includes(vals)) {
return i;
}
}
return -1;
}
return this._super(...arguments);
},
});
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ import spreadsheet from "@spreadsheet/o_spreadsheet/o_spreadsheet_extended";
import {time_to_str} from "web.time";
import {useService} from "@web/core/utils/hooks";
import {FormViewDialog} from "@web/views/view_dialogs/form_view_dialog";
import {makeDynamicRows} from "../utils/dynamic_row_generator.esm";
import {makeDynamicCols, makeDynamicRows} from "../utils/dynamic_generators.esm";

const {sidePanelRegistry, topbarMenuRegistry} = spreadsheet.registries;
const {createFullMenuItem} = spreadsheet.helpers;
@@ -127,30 +127,45 @@ export class PivotPanelDisplay extends Component {
.getPivotDataSource(this.props.pivotId)
.copyModelWithOriginalDomain();
var {cols, rows, measures} = datasourceModel.getTableStructure().export();
const number_of_rows = await new Promise((resolve) => {
this.dialog.add(
FormViewDialog,
{
title: this.env._t("Select the quantity of rows"),
resModel: "spreadsheet.select.row.number",
onRecordSaved: async (record) => {
resolve(record.data.number_of_rows);
const {dynamic_rows, number_of_rows, dynamic_cols, number_of_cols} =
await new Promise((resolve) => {
this.dialog.add(
FormViewDialog,
{
title: this.env._t("Select the quantity of rows"),
resModel: "spreadsheet.select.row.number",
onRecordSaved: async (record) => {
resolve({
dynamic_rows: record.data.dynamic_rows,
number_of_rows: record.data.number_of_rows,
dynamic_cols: record.data.dynamic_cols,
number_of_cols: record.data.number_of_cols,
});
},
},
},
{onClose: () => resolve(false)}
);
});
if (!number_of_rows) {
{onClose: () => resolve(false)}
);
});
if (!dynamic_rows && !dynamic_cols) {
return;
}
const indentations = rows.map((r) => r.indent);
const max_indentation = Math.max(...indentations);
rows = makeDynamicRows(
this.props.pivotDefinition.rowGroupBys,
number_of_rows,
1,
max_indentation
);
if (dynamic_rows) {
const indentations = rows.map((r) => r.indent);
const max_indentation = Math.max(...indentations);
rows = makeDynamicRows(
this.props.pivotDefinition.rowGroupBys,
number_of_rows,
1,
max_indentation
);
}
if (dynamic_cols) {
cols = makeDynamicCols(
this.props.pivotDefinition.colGroupBys,
number_of_cols,
this.props.pivotDefinition.measures
);
}
const table = {
cols,
rows,
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ import {SpreadsheetRenderer} from "./spreadsheet_renderer.esm";
import {registry} from "@web/core/registry";
import spreadsheet from "@spreadsheet/o_spreadsheet/o_spreadsheet_extended";
import {useService} from "@web/core/utils/hooks";
import {makeDynamicRows} from "../utils/dynamic_row_generator.esm";
import {makeDynamicCols, makeDynamicRows} from "../utils/dynamic_generators.esm";

const uuidGenerator = new spreadsheet.helpers.UuidGenerator();
const actionRegistry = registry.category("actions");
@@ -207,6 +207,13 @@ export class ActionSpreadsheetOca extends Component {
max_indentation
);
}
if (this.import_data.dyn_number_of_cols) {
cols = makeDynamicCols(
colGroupBys,
this.import_data.dyn_number_of_cols,
this.import_data.metaData.activeMeasures
);
}
const table = {
cols,
rows,
124 changes: 124 additions & 0 deletions spreadsheet_oca/static/src/spreadsheet/utils/dynamic_generators.esm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/** @odoo-module **/
/* Copyright 2024 Tecnativa - Carlos Roca
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */

/**
* Calculates the rows to calculate the dynamic values. This can be used to make
* a top of data.
*
* When the rows has just a indentation of 1, the number of rows will be the
* final result.
*
* Example:
* number_of_rows = 2
* max_indentation = 1
* - first element
* - second element
*
* The pivot allows to increase the indentation, when it happens, the number of
* rows will be propagated to each indentation.
*
* Example:
* number_of_rows = 2
* max_indentation = 2
* - first element
* - first subelement
* - second subelement
* - second element
* - first subelement
* - second subelement
*
* @param {Array} fields
* @param {Number} number_of_rows
* @param {Number} indent
* @param {Number} max_indentation
* @param {Array} parent_indexes
* @returns {Array}
*/
export function makeDynamicRows(
fields,
number_of_rows,
indent,
max_indentation,
parent_indexes = []
) {
var rows = [];
for (var index = 1; index <= number_of_rows; index++) {
rows.push({
fields: fields.slice(0, indent).map((f) => "#" + f),
indent,
values: [...parent_indexes, index],
});
if (indent < max_indentation) {
rows = rows.concat(
makeDynamicRows(fields, number_of_rows, indent + 1, max_indentation, [
...parent_indexes,
index,
])
);
}
}
return rows;
}

function _incrementArray(arr, base) {
let carry = 1;
for (let i = arr.length - 1; i >= 0; i--) {
const sum = arr[i] + carry;
arr[i] = sum % base ? sum % base : 1;
carry = Math.floor(sum / base);
if (carry === 0) break;
}

return arr;
}

function _getColLevelInfo(fields, number_of_cols, width) {
const col_info = [];
var values = Array.from({length: fields.length}, () => 1);
for (var f = Math.pow(number_of_cols, fields.length - 1) - 1; f >= 0; f--) {
for (var c = 1; c <= number_of_cols; c++) {
col_info.push({
fields,
values,
width,
});
values = _incrementArray([...values], number_of_cols + 1);
}
}
return col_info;
}

/**
*
* @param {Array} fields
* @param {Number} number_of_cols
* @param {Number} indent
* @param {Number} max_indentation
* @param {Array} parent_indexes
* @returns {Array}
*/
export function makeDynamicCols(fields, number_of_cols, measures) {
var cols = [];
const max_width =
(Math.pow(number_of_cols, fields.length) * measures.length) / number_of_cols;
for (var index = 0; index < fields.length; index++) {
const width = max_width / Math.pow(number_of_cols, index);
const newFields = fields.slice(0, index + 1).map((f) => "#" + f);
cols.push(_getColLevelInfo(newFields, number_of_cols, width));
}
const measuresCols = [];
const lastCols = cols[cols.length - 1];
for (var col of lastCols) {
for (var measure of measures) {
measuresCols.push({
fields: [...col.fields, "measure"],
values: [...col.values, measure],
width: 1,
});
}
}
cols.push(measuresCols);
cols.push("dynamic_cols");
return cols;
}

This file was deleted.

3 changes: 3 additions & 0 deletions spreadsheet_oca/wizards/spreadsheet_select_row_number.py
Original file line number Diff line number Diff line change
@@ -8,4 +8,7 @@ class SpreadsheetSelectRowNumber(models.TransientModel):
_name = "spreadsheet.select.row.number"
_description = "Select number of rows to duplicate row"

dynamic_rows = fields.Boolean()
number_of_rows = fields.Integer()
dynamic_cols = fields.Boolean()
number_of_cols = fields.Integer()
11 changes: 10 additions & 1 deletion spreadsheet_oca/wizards/spreadsheet_select_row_number.xml
Original file line number Diff line number Diff line change
@@ -8,7 +8,16 @@
<form>
<sheet>
<group>
<field name="number_of_rows" />
<field name="dynamic_rows" />
<field
name="number_of_rows"
attrs="{'invisible': [('dynamic_rows', '=', False)]}"
/>
<field name="dynamic_cols" />
<field
name="number_of_cols"
attrs="{'invisible': [('dynamic_cols', '=', False)]}"
/>
</group>
</sheet>
</form>
15 changes: 13 additions & 2 deletions spreadsheet_oca/wizards/spreadsheet_spreadsheet_import.py
Original file line number Diff line number Diff line change
@@ -26,10 +26,17 @@
can_be_dynamic = fields.Boolean()
is_tree = fields.Boolean()
dynamic = fields.Boolean(
help="This field allows you to generate tables that are updated with the "
"filters set in the spreadsheets."
"Dynamic Rows",
help="This field allows you to generate tables that its rows are updated with"
" the filters set in the spreadsheets.",
)
dynamic_cols = fields.Boolean(
"Dynamic Columns",
help="This field allows you to generate tables that its cols are updated with"
" the filters set in the spreadsheets.",
)
number_of_rows = fields.Integer()
number_of_cols = fields.Integer("Number of Columns")

def insert_pivot(self):
self.ensure_one()
@@ -47,6 +54,8 @@
import_data["new"] = 1
if self.dynamic:
import_data["dyn_number_of_rows"] = self.number_of_rows
if self.dynamic_cols:
import_data["dyn_number_of_cols"] = self.number_of_cols

Check warning on line 58 in spreadsheet_oca/wizards/spreadsheet_spreadsheet_import.py

Codecov / codecov/patch

spreadsheet_oca/wizards/spreadsheet_spreadsheet_import.py#L58

Added line #L58 was not covered by tests
return {
"type": "ir.actions.client",
"tag": "action_spreadsheet_oca",
@@ -63,6 +72,8 @@
import_data["new_sheet"] = new_sheet
if self.dynamic:
import_data["dyn_number_of_rows"] = self.number_of_rows
if self.dynamic_cols:
import_data["dyn_number_of_cols"] = self.number_of_cols

Check warning on line 76 in spreadsheet_oca/wizards/spreadsheet_spreadsheet_import.py

Codecov / codecov/patch

spreadsheet_oca/wizards/spreadsheet_spreadsheet_import.py#L76

Added line #L76 was not covered by tests
return {
"type": "ir.actions.client",
"tag": "action_spreadsheet_oca",
9 changes: 9 additions & 0 deletions spreadsheet_oca/wizards/spreadsheet_spreadsheet_import.xml
Original file line number Diff line number Diff line change
@@ -37,6 +37,15 @@
name="number_of_rows"
attrs="{'invisible': [('dynamic', '=', False)], 'required': [('dynamic', '=', True)]}"
/>
<field
name="dynamic_cols"
force_save="1"
attrs="{'invisible': ['|', ('can_be_dynamic', '=', False), ('is_tree', '=', True)]}"
/>
<field
name="number_of_cols"
attrs="{'invisible': [('dynamic_cols', '=', False)], 'required': [('dynamic_cols', '=', True)]}"
/>
</group>
</sheet>
<footer>