[IMP] spreadsheet: add date from_to global filter

Adds in date type global filters a new category
"From / To" allowing to define a domain between
two dates.

closes odoo/odoo#138507

Task: 3516362
Related: odoo/enterprise#48855
Signed-off-by: Lucas Lefèvre (lul) <lul@odoo.com>
This commit is contained in:
Alexis Lacroix
2023-10-24 12:38:25 +00:00
committed by Lucas Lefèvre (lul)
parent 005b76462a
commit c0bc8d9ea9
9 changed files with 240 additions and 16 deletions
@@ -0,0 +1,40 @@
/** @odoo-module */
import { Component } from "@odoo/owl";
import { _t } from "@web/core/l10n/translation";
import { DateTimeInput } from "@web/core/datetime/datetime_input";
import { serializeDate, deserializeDate } from "@web/core/l10n/dates";
export class DateFromToValue extends Component {
static template = "spreadsheet.DateFromToValue";
static components = { DateTimeInput };
static props = {
onFromToChanged: Function,
from: { type: String, optional: true },
to: { type: String, optional: true },
};
fromPlaceholder = _t("Date from...");
toPlaceholder = _t("Date to...");
onDateFromChanged(dateFrom) {
this.props.onFromToChanged({
from: dateFrom && serializeDate(dateFrom.startOf("day")),
to: this.props.to,
});
}
onDateToChanged(dateTo) {
this.props.onFromToChanged({
from: this.props.from,
to: dateTo && serializeDate(dateTo.endOf("day")),
});
}
get dateFrom() {
return this.props.from && deserializeDate(this.props.from);
}
get dateTo() {
return this.props.to && deserializeDate(this.props.to);
}
}
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<templates>
<div t-name="spreadsheet.DateFromToValue" class="date_filter_values">
<DateTimeInput
value="dateFrom"
type="'date'"
placeholder="fromPlaceholder"
onChange.bind="onDateFromChanged"
/>
<i class="oi oi-arrow-right"></i>
<DateTimeInput
value="dateTo"
type="'date'"
placeholder="toPlaceholder"
onChange.bind="onDateToChanged"
/>
</div>
</templates>
@@ -3,6 +3,7 @@
import { MultiRecordSelector } from "@web/core/record_selectors/multi_record_selector";
import { RELATIVE_DATE_RANGE_TYPES } from "@spreadsheet/helpers/constants";
import { DateFilterValue } from "../filter_date_value/filter_date_value";
import { DateFromToValue } from "../filter_date_from_to_value/filter_date_from_to_value";
import { Component } from "@odoo/owl";
import { _t } from "@web/core/l10n/translation";
@@ -48,7 +49,7 @@ export class FilterValue extends Component {
}
}
FilterValue.template = "spreadsheet_edition.FilterValue";
FilterValue.components = { DateFilterValue, MultiRecordSelector };
FilterValue.components = { DateFilterValue, DateFromToValue, MultiRecordSelector };
FilterValue.props = {
filter: Object,
model: Object,
@@ -7,4 +7,9 @@
.o_datepicker_input {
color: $o-main-text-color;
}
.date_filter_values {
display: flex;
gap: map-get($spacers, 2);
align-items: baseline;
}
}
@@ -2,9 +2,10 @@
<templates>
<t t-name="spreadsheet_edition.FilterValue"
>
<div class="o-filter-value d-flex align-items-start w-100">
<t t-set="filter"
t-value="props.filter"/>
<t t-set="filter"
t-value="props.filter"/>
<div class="o-filter-value d-flex align-items-start w-100"
t-att-class="filter.rangeType === 'from_to' ? 'o-filter-value-double-size':''">
<t t-set="filterValue"
t-value="getters.getGlobalFilterValue(filter.id) || []"/>
<div t-if="filter.type === 'text'" class="w-100">
@@ -22,10 +23,6 @@
update="(resIds) => this.onTagSelected(filter.id, resIds)" />
</span>
<div t-if="filter.type === 'date'" class="w-100">
<DateFilterValue t-if="filter.rangeType !== 'relative'"
period="filterValue &amp;&amp; filterValue.period"
yearOffset="filterValue &amp;&amp; filterValue.yearOffset"
onTimeRangeChanged="(value) => this.onDateInput(filter.id, value)" />
<select t-if="filter.rangeType === 'relative'"
t-on-change="(e) => this.onDateInput(filter.id, e.target.value || undefined)"
class="date_filter_values o_input me-3"
@@ -40,6 +37,15 @@
</option>
</t>
</select>
<DateFromToValue t-elif="filter.rangeType === 'from_to'"
from="filterValue?.from"
to="filterValue?.to"
onFromToChanged="(value) => this.onDateInput(filter.id, value)"/>
<DateFilterValue t-else=""
period="filterValue?.period"
yearOffset="filterValue?.yearOffset"
onTimeRangeChanged="(value) => this.onDateInput(filter.id, value)"/>
</div>
<i t-if="getters.isGlobalFilterActive(filter.id)"
class="fa fa-times btn btn-link text-muted o_side_panel_filter_icon ms-1"
@@ -1,7 +1,7 @@
/** @odoo-module */
/**
* @typedef {"fixedPeriod"|"relative"} RangeType
* @typedef {"fixedPeriod"|"relative"|"from_to"} RangeType
*
* @typedef {"last_month" | "last_week" | "last_year" | "last_three_years" | "this_month" | "this_quarter" | "this_year"} RelativePeriod
*
@@ -163,6 +163,9 @@ export class GlobalFiltersUIPlugin extends spreadsheet.UIPlugin {
const value = filterId in this.values ? this.values[filterId].value : undefined;
const preventAutomaticValue = this.values[filterId]?.value?.preventAutomaticValue;
if (filter.type === "date" && filter.rangeType === "from_to") {
return value || { from: undefined, to: undefined };
}
const defaultValue = (!preventAutomaticValue && filter.defaultValue) || undefined;
if (filter.type === "date" && preventAutomaticValue) {
return undefined;
@@ -193,7 +196,11 @@ export class GlobalFiltersUIPlugin extends spreadsheet.UIPlugin {
case "date":
return (
value &&
(typeof value === "string" || value.yearOffset !== undefined || value.period)
(typeof value === "string" ||
value.yearOffset !== undefined ||
value.period ||
value.from ||
value.to)
);
case "relation":
return value && value.length;
@@ -315,7 +322,7 @@ export class GlobalFiltersUIPlugin extends spreadsheet.UIPlugin {
value = "";
break;
case "date":
value = { yearOffset: undefined, preventAutomaticValue: true };
value = { preventAutomaticValue: true };
break;
case "relation":
value = { preventAutomaticValue: true };
@@ -349,6 +356,19 @@ export class GlobalFiltersUIPlugin extends spreadsheet.UIPlugin {
const offset = fieldMatching.offset || 0;
const now = DateTime.local();
if (filter.rangeType === "from_to") {
if (value.from && value.to) {
return new Domain(["&", [field, ">=", value.from], [field, "<=", value.to]]);
}
if (value.from) {
return new Domain([[field, ">=", value.from]]);
}
if (value.to) {
return new Domain([[field, "<=", value.to]]);
}
return new Domain();
}
if (filter.rangeType === "relative") {
return getRelativeDateDomain(now, offset, value, field, type);
}
@@ -1173,6 +1173,24 @@ QUnit.module("spreadsheet > Global filters model", {}, () => {
});
});
QUnit.test(
"Date filter automatic undefined values for from_to filter",
async function (assert) {
const label = "From to";
const { model } = await createSpreadsheetWithPivot();
await addGlobalFilter(model, {
id: "1",
type: "date",
label,
rangeType: "from_to",
});
assert.deepEqual(model.getters.getGlobalFilterValue("1"), {
from: undefined,
to: undefined,
});
}
);
QUnit.test("Date filter automatic default value at model loading", async function (assert) {
const label = "This year";
const model = new Model({
@@ -1317,6 +1335,122 @@ QUnit.module("spreadsheet > Global filters model", {}, () => {
assertDateDomainEqual(assert, "date", "2016-05-18", "2019-05-17", computedDomain);
});
QUnit.test("from_to date filter at model loading", async function (assert) {
const model = new Model({
globalFilters: [
{ type: "date", rangeType: "from_to", label: "From To", fields: {}, id: "1" },
],
});
assert.deepEqual(model.getters.getGlobalFilterValue("1"), {
from: undefined,
to: undefined,
});
});
QUnit.test("from_to date filter domain value", async function (assert) {
const { model } = await createSpreadsheetWithPivot();
/**@type GlobalFilter */
const filter = {
id: "42",
type: "date",
label: "From To",
rangeType: "from_to",
};
const value = {
from: "2022-01-01",
to: "2022-05-16",
};
await addGlobalFilter(model, filter, { pivot: { 1: { chain: "date", type: "date" } } });
await setGlobalFilterValue(model, { id: "42", value });
const computedDomain = model.getters.getPivotComputedDomain("1");
assertDateDomainEqual(assert, "date", "2022-01-01", "2022-05-16", computedDomain);
});
QUnit.test(
"set 'from_to' date filter domain value from specific date --> to specific date",
async function (assert) {
const { model } = await createSpreadsheetWithPivot();
/**@type GlobalFilter */
const filter = {
id: "42",
type: "date",
label: "From To",
rangeType: "from_to",
};
const value = {
from: "2022-01-01",
to: "2022-05-16",
};
await addGlobalFilter(model, filter, { pivot: { 1: { chain: "date", type: "date" } } });
await setGlobalFilterValue(model, { id: "42", value });
const computedDomain = model.getters.getPivotComputedDomain("1");
assertDateDomainEqual(assert, "date", "2022-01-01", "2022-05-16", computedDomain);
}
);
QUnit.test(
"set 'from_to' date filter domain value from specific date",
async function (assert) {
const { model } = await createSpreadsheetWithPivot();
/**@type GlobalFilter */
const filter = {
id: "42",
type: "date",
label: "From To",
rangeType: "from_to",
};
const value = {
from: "2022-01-01",
to: undefined,
};
await addGlobalFilter(model, filter, { pivot: { 1: { chain: "date", type: "date" } } });
await setGlobalFilterValue(model, { id: "42", value });
const computedDomain = model.getters.getPivotComputedDomain("1");
assert.deepEqual(computedDomain, [["date", ">=", "2022-01-01"]]);
}
);
QUnit.test("set 'from_to' date filter domain value to specific date", async function (assert) {
const { model } = await createSpreadsheetWithPivot();
/**@type GlobalFilter */
const filter = {
id: "42",
type: "date",
label: "From To",
rangeType: "from_to",
};
const value = {
from: undefined,
to: "2022-05-16",
};
await addGlobalFilter(model, filter, { pivot: { 1: { chain: "date", type: "date" } } });
await setGlobalFilterValue(model, { id: "42", value });
const computedDomain = model.getters.getPivotComputedDomain("1");
assert.deepEqual(computedDomain, [["date", "<=", "2022-05-16"]]);
});
QUnit.test("can clear 'from_to' date filter values", async function (assert) {
const { model } = await createSpreadsheetWithPivot();
await addGlobalFilter(model, {
id: "42",
type: "date",
label: "From To",
rangeType: "from_to",
});
const [filter] = model.getters.getGlobalFilters();
const value = {
from: "2022-01-01",
to: "2022-05-16",
};
await setGlobalFilterValue(model, { id: "42", value });
model.dispatch("CLEAR_GLOBAL_FILTER_VALUE", { id: filter.id });
assert.deepEqual(
model.getters.getGlobalFilterValue(filter.id),
{ preventAutomaticValue: true },
"can clear 'from_to' date filter values"
);
});
QUnit.test(
"Can set a value to a relation filter from the SET_MANY_GLOBAL_FILTER_VALUE command",
async function (assert) {
@@ -67,16 +67,16 @@
.o-filter-value {
min-height: 25px;
min-width: 100px;
flex-basis: 100px;
flex-grow: 1;
.o_multi_record_selector {
width: 100%;
}
}
.date_filter_values {
display: flex;
gap: map-get($spacers, 2);
align-items: baseline;
}
.o-filter-value-double-size {
flex-basis: 200px;
}
}
}