[MIG] web_time_range_menu_custom: Migration to 15.0

pull/2471/head
Carlos Roca 2023-03-22 16:54:19 +01:00
parent a2f20904b8
commit ab4dd9a465
19 changed files with 605 additions and 702 deletions

View File

@ -14,18 +14,19 @@ Web Time Range Menu Custom
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3 :alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github
:target: https://github.com/OCA/web/tree/13.0/web_time_range_menu_custom :target: https://github.com/OCA/web/tree/15.0/web_time_range_menu_custom
:alt: OCA/web :alt: OCA/web
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/web-13-0/web-13-0-web_time_range_menu_custom :target: https://translation.odoo-community.org/projects/web-15-0/web-15-0-web_time_range_menu_custom
:alt: Translate me on Weblate :alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png .. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runbot.odoo-community.org/runbot/162/13.0 :target: https://runboat.odoo-community.org/webui/builds.html?repo=OCA/web&target_branch=15.0
:alt: Try me on Runbot :alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5| |badge1| |badge2| |badge3| |badge4| |badge5|
Extend period and comparison period options for the time range menu adding a new option called "Custom Period". Extend period and comparison period options for the date and datetime fields filted menu
adding a new option called "Custom Period".
**Table of contents** **Table of contents**
@ -38,7 +39,7 @@ Bug Tracker
Bugs are tracked on `GitHub Issues <https://github.com/OCA/web/issues>`_. Bugs are tracked on `GitHub Issues <https://github.com/OCA/web/issues>`_.
In case of trouble, please check there if your issue has already been reported. In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed If you spotted it first, help us smashing it by providing a detailed and welcomed
`feedback <https://github.com/OCA/web/issues/new?body=module:%20web_time_range_menu_custom%0Aversion:%2013.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_. `feedback <https://github.com/OCA/web/issues/new?body=module:%20web_time_range_menu_custom%0Aversion:%2015.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues. Do not contact contributors directly about support or help with technical issues.
@ -56,6 +57,7 @@ Contributors
* `Tecnativa <https://www.tecnativa.com>`__: * `Tecnativa <https://www.tecnativa.com>`__:
* Alexandre D. Díaz * Alexandre D. Díaz
* Carlos Roca
Maintainers Maintainers
~~~~~~~~~~~ ~~~~~~~~~~~
@ -70,6 +72,6 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and mission is to support the collaborative development of Odoo features and
promote its widespread use. promote its widespread use.
This module is part of the `OCA/web <https://github.com/OCA/web/tree/13.0/web_time_range_menu_custom>`_ project on GitHub. This module is part of the `OCA/web <https://github.com/OCA/web/tree/15.0/web_time_range_menu_custom>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@ -2,13 +2,20 @@
{ {
"name": "Web Time Range Menu Custom", "name": "Web Time Range Menu Custom",
"version": "13.0.1.0.1", "version": "15.0.1.0.0",
"author": "Tecnativa, Odoo Community Association (OCA)", "author": "Tecnativa, Odoo Community Association (OCA)",
"license": "AGPL-3", "license": "AGPL-3",
"website": "https://github.com/OCA/web", "website": "https://github.com/OCA/web",
"data": ["templates/assets.xml"],
"depends": ["web"], "depends": ["web"],
"qweb": ["static/src/xml/time_range_menu.xml"],
"installable": True, "installable": True,
"auto_install": False, "auto_install": False,
"assets": {
"web.assets_backend": [
"/web_time_range_menu_custom/static/src/js/*.esm.js",
"/web_time_range_menu_custom/static/src/scss/*.scss",
],
"web.assets_qweb": [
"/web_time_range_menu_custom/static/src/xml/*.xml",
],
},
} }

View File

@ -1,3 +1,4 @@
* `Tecnativa <https://www.tecnativa.com>`__: * `Tecnativa <https://www.tecnativa.com>`__:
* Alexandre D. Díaz * Alexandre D. Díaz
* Carlos Roca

View File

@ -1 +1,2 @@
Extend period and comparison period options for the time range menu adding a new option called "Custom Period". Extend period and comparison period options for the date and datetime fields filted menu
adding a new option called "Custom Period".

View File

@ -3,7 +3,7 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.15.1: http://docutils.sourceforge.net/" /> <meta name="generator" content="Docutils: http://docutils.sourceforge.net/" />
<title>Web Time Range Menu Custom</title> <title>Web Time Range Menu Custom</title>
<style type="text/css"> <style type="text/css">
@ -367,8 +367,9 @@ ul.auto-toc {
!! This file is generated by oca-gen-addon-readme !! !! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !! !! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/web/tree/13.0/web_time_range_menu_custom"><img alt="OCA/web" src="https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/web-13-0/web-13-0-web_time_range_menu_custom"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runbot.odoo-community.org/runbot/162/13.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p> <p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/web/tree/15.0/web_time_range_menu_custom"><img alt="OCA/web" src="https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/web-15-0/web-15-0-web_time_range_menu_custom"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runboat.odoo-community.org/webui/builds.html?repo=OCA/web&amp;target_branch=15.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>Extend period and comparison period options for the time range menu adding a new option called “Custom Period”.</p> <p>Extend period and comparison period options for the date and datetime fields filted menu
adding a new option called “Custom Period”.</p>
<p><strong>Table of contents</strong></p> <p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents"> <div class="contents local topic" id="contents">
<ul class="simple"> <ul class="simple">
@ -386,7 +387,7 @@ ul.auto-toc {
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/web/issues">GitHub Issues</a>. <p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/web/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported. In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed If you spotted it first, help us smashing it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/web/issues/new?body=module:%20web_time_range_menu_custom%0Aversion:%2013.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p> <a class="reference external" href="https://github.com/OCA/web/issues/new?body=module:%20web_time_range_menu_custom%0Aversion:%2015.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p> <p>Do not contact contributors directly about support or help with technical issues.</p>
</div> </div>
<div class="section" id="credits"> <div class="section" id="credits">
@ -402,6 +403,7 @@ If you spotted it first, help us smashing it by providing a detailed and welcome
<ul class="simple"> <ul class="simple">
<li><a class="reference external" href="https://www.tecnativa.com">Tecnativa</a>:<ul> <li><a class="reference external" href="https://www.tecnativa.com">Tecnativa</a>:<ul>
<li>Alexandre D. Díaz</li> <li>Alexandre D. Díaz</li>
<li>Carlos Roca</li>
</ul> </ul>
</li> </li>
</ul> </ul>
@ -413,7 +415,7 @@ If you spotted it first, help us smashing it by providing a detailed and welcome
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose <p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and mission is to support the collaborative development of Odoo features and
promote its widespread use.</p> promote its widespread use.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/web/tree/13.0/web_time_range_menu_custom">OCA/web</a> project on GitHub.</p> <p>This module is part of the <a class="reference external" href="https://github.com/OCA/web/tree/15.0/web_time_range_menu_custom">OCA/web</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p> <p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div> </div>
</div> </div>

View File

@ -1,26 +0,0 @@
odoo.define("web_time_range_menu_custom.ControlPanelController", function (require) {
"use strict";
const ControlPanelController = require("web.ControlPanelController");
ControlPanelController.include({
custom_events: _.extend({}, ControlPanelController.prototype.custom_events, {
activate_custom_time_range: "_onActivateCustomTimeRange",
}),
/**
* @override
*/
_onActivateCustomTimeRange: function (ev) {
ev.stopPropagation();
this.model.activateTimeRangeCustom(
ev.data.id,
ev.data.timeRangeId,
ev.data.comparisonTimeRangeId,
ev.data.timeRangeCustom,
ev.data.comparisonTimeRangeCustom
);
this._reportNewQueryAndRender();
},
});
});

View File

@ -1,104 +0,0 @@
/* Copyright 2021 Tecnativa - Alexandre D. Díaz
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
odoo.define("web_time_range_menu_custom.ControlPanelModel", function (require) {
"use strict";
const core = require("web.core");
const Domain = require("web.Domain");
const ControlPanelModel = require("web.ControlPanelModel");
const _t = core._t;
ControlPanelModel.include({
activateTimeRangeCustom: function (
filterId,
timeRangeId,
comparisonTimeRangeId,
timeRangeCustom,
comparisonTimeRangeCustom
) {
var filter = this.filters[filterId];
filter.timeRangeCustom = timeRangeCustom;
filter.comparisonTimeRangeCustom = comparisonTimeRangeCustom;
this.activateTimeRange(filterId, timeRangeId, comparisonTimeRangeId);
},
/**
* @override
*/
_getTimeRangeMenuData: function (evaluation) {
const context = this._super.apply(this, arguments);
// GroupOfTimeRanges can be undefined in case with withSearchBar is false
var groupOfTimeRanges = this.groups[this._getGroupIdOfType("timeRange")];
if (groupOfTimeRanges && groupOfTimeRanges.activeFilterIds.length) {
var filter = this.filters[groupOfTimeRanges.activeFilterIds[0][0]];
if (filter.timeRangeId === "custom_period") {
context.timeRangeMenuData.timeRange =
Domain.prototype.constructCustomDomain(
filter.fieldName,
filter.timeRangeId,
filter.fieldType,
undefined,
filter.timeRangeCustom
);
context.timeRangeMenuData.timeRangeDescription =
_t("Last ") +
`${filter.timeRangeCustom.value} ${filter.timeRangeCustom.type}`;
context.timeRangeMenuData.timeRangeCustomValue =
filter.timeRangeCustom.value;
context.timeRangeMenuData.timeRangeCustomType =
filter.timeRangeCustom.type;
if (evaluation) {
context.timeRangeMenuData.timeRange =
Domain.prototype.stringToArray(
context.timeRangeMenuData.timeRange
);
}
if (filter.comparisonTimeRangeId !== "custom_comparison_period") {
context.timeRangeMenuData.comparisonTimeRange =
Domain.prototype.constructCustomDomain(
filter.fieldName,
filter.timeRangeId,
filter.fieldType,
filter.comparisonTimeRangeId,
filter.timeRangeCustom,
filter.comparisonTimeRangeCustom
);
if (evaluation) {
context.timeRangeMenuData.comparisonTimeRange =
Domain.prototype.stringToArray(
context.timeRangeMenuData.comparisonTimeRange
);
}
}
}
if (filter.comparisonTimeRangeId === "custom_comparison_period") {
context.timeRangeMenuData.comparisonTimeRange =
Domain.prototype.constructCustomDomain(
filter.fieldName,
filter.timeRangeId,
filter.fieldType,
filter.comparisonTimeRangeId,
filter.timeRangeCustom,
filter.comparisonTimeRangeCustom
);
context.timeRangeMenuData.comparisonTimeRangeDescription =
_t("Previous ") +
`${filter.comparisonTimeRangeCustom.value} ${filter.comparisonTimeRangeCustom.type}`;
context.timeRangeMenuData.comparisonTimeRangeCustomValue =
filter.comparisonTimeRangeCustom.value;
context.timeRangeMenuData.comparisonTimeRangeCustomType =
filter.comparisonTimeRangeCustom.type;
if (evaluation) {
context.timeRangeMenuData.comparisonTimeRange =
Domain.prototype.stringToArray(
context.timeRangeMenuData.comparisonTimeRange
);
}
}
}
return context;
},
});
});

View File

@ -1,32 +0,0 @@
/* Copyright 2021 Tecnativa - Alexandre D. Díaz
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
odoo.define(
"web_time_range_menu_custom.controlPanelViewParameters",
function (require) {
"use strict";
const controlPanelViewParameters = require("web.controlPanelViewParameters");
const core = require("web.core");
const _lt = core._lt;
controlPanelViewParameters.PERIOD_OPTIONS =
controlPanelViewParameters.PERIOD_OPTIONS.concat([
{
description: _lt("Custom Period"),
optionId: "custom_period",
groupId: 4,
},
]);
controlPanelViewParameters.TIME_RANGE_OPTIONS =
controlPanelViewParameters.PERIOD_OPTIONS;
controlPanelViewParameters.COMPARISON_TIME_RANGE_OPTIONS =
controlPanelViewParameters.COMPARISON_TIME_RANGE_OPTIONS.concat([
{
description: _lt("Custom Period"),
optionId: "custom_comparison_period",
},
]);
}
);

View File

@ -0,0 +1,123 @@
/** @odoo-module **/
/* Copyright 2022 Tecnativa - Carlos Roca
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) */
import {useBus} from "@web/core/utils/hooks";
const {Component, QWeb} = owl;
const {useState} = owl.hooks;
import * as dates from "@web/search/utils/dates";
const {DateTime} = luxon; // eslint-disable-line no-undef
var ID_CUSTOM_DATE = 0;
/**
* @extends Component
*/
export class DropdownItemCustomPeriod extends Component {
setup() {
this.isOpen = useState({value: false});
this.type = useState({value: "day"});
this.quantity = useState({value: 0});
this.referenceMoment = DateTime.local();
var fieldsSelected = new Set();
for (var selected in this.props.comparisonItems) {
fieldsSelected.add(this.props.comparisonItems[selected].dateFilterId);
}
this.fields_selected = Array.from(fieldsSelected);
this.field = useState({
value: (this.props.field && this.props.field.id) || this.fields_selected[0],
});
useBus(this.env.searchModel, "update", this.render);
}
onClickCustomPeriod() {
this.isOpen.value = !this.isOpen.value;
}
onClickAdd() {
if (this.props.type === "filter") {
this.addRange();
} else if (this.props.type === "comparison") {
this.addComparison();
}
}
addRange() {
const field_id = this.field.value;
const key = "custom_" + ID_CUSTOM_DATE++;
const plusParamID = this.type.value + "s";
var formatSearch = "";
switch (this.type.value) {
case "day":
formatSearch = "dd MMMM yyyy";
break;
case "week":
formatSearch = "'W'WW yyyy";
break;
case "month":
formatSearch = "MMMM";
break;
case "year":
formatSearch = "yyyy";
break;
}
const periodSearch = {
[key]: {
id: key,
groupNumber: 3,
description:
"Last " + this.quantity.value + " " + this.type.value + "s",
format: formatSearch,
plusParam: {[plusParamID]: -this.quantity.value},
granularity: this.type.value,
custom_period: {
is_custom_period: true,
last_period: this.quantity.value,
},
},
};
Object.assign(dates.PERIOD_OPTIONS, periodSearch);
this.env.searchModel.optionGenerators = dates.getPeriodOptions(
this.referenceMoment
);
this.env.searchModel.toggleDateFilter(field_id, key);
}
addComparison() {
const key = "custom_" + ID_CUSTOM_DATE++;
const plusParamID = this.type.value + "s";
Object.assign(dates.COMPARISON_OPTIONS, {
[key]: {
description:
"Previous " + this.quantity.value + " " + this.type.value + "s",
id: key,
plusParam: {[plusParamID]: -this.quantity.value},
},
});
const comparisonId = this.env.searchModel.nextId;
this.env.searchModel.searchItems[comparisonId] = {
comparisonOptionId: key,
dateFilterId: this.field.value,
description:
this.env.searchModel.searchItems[this.field.value].description +
": Previous " +
this.quantity.value +
" " +
this.type.value +
"s",
groupId: 14,
id: comparisonId,
type: "comparison",
};
this.env.searchModel.nextId++;
this.env.searchModel.toggleSearchItem(comparisonId);
}
}
DropdownItemCustomPeriod.template =
"web_time_range_menu_custom.DropdownItemCustomPeriod";
DropdownItemCustomPeriod.props = {
type: {type: String},
field: {type: Object, optional: true},
comparisonItems: {type: Object, optional: true},
};
QWeb.registerComponent("DropdownItemCustomPeriod", DropdownItemCustomPeriod);

View File

@ -0,0 +1,347 @@
/** @odoo-module **/
/* eslint-disable init-declarations */
/* Copyright 2022 Tecnativa - Carlos Roca
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) */
import {patch} from "@web/core/utils/patch";
import {Domain} from "@web/core/domain";
import {serializeDate, serializeDateTime} from "@web/core/l10n/dates";
import {localization} from "@web/core/l10n/localization";
/* Redefine some methods of dates.js from web.static.src.utils to
add support to days and weeks periods */
import * as dates from "@web/search/utils/dates";
/**
* Method used to calculate the quantity of days and weeks of the
* actual year.
*/
function _getQtyOfCurrentYear(option) {
const now = new Date();
const startOfYear = new Date(now.getFullYear(), 0, 1);
const endOfYear = new Date(now.getFullYear(), 11, 31);
let millisecondsPerWeek = 0;
if (option === "week") {
millisecondsPerWeek = 604800000;
} else if (option === "day") {
millisecondsPerWeek = 86400000;
} else {
return;
}
const weeks = Math.ceil((endOfYear - startOfYear) / millisecondsPerWeek);
return weeks;
}
Object.assign(dates.PER_YEAR, {
week: _getQtyOfCurrentYear("week"),
day: _getQtyOfCurrentYear("day"),
});
// This is needed to call the super functions on @web/search/utils/dates
const _getSetParam = dates.getSetParam;
// Patch of functions defined before
patch(dates, "patch dates", {
/*
* Redefine function to avoid the exclusion of days and weeks.
*/
constructDateDomain(
referenceMoment,
fieldName,
fieldType,
selectedOptionIds,
comparisonOptionId
) {
let plusParam;
let selectedOptions;
if (comparisonOptionId) {
[plusParam, selectedOptions] = dates.getComparisonParams(
referenceMoment,
selectedOptionIds,
comparisonOptionId
);
} else {
selectedOptions = dates.getSelectedOptions(
referenceMoment,
selectedOptionIds
);
}
const yearOptions = selectedOptions.year;
const otherOptions = [
...(selectedOptions.quarter || []),
...(selectedOptions.month || []),
...(selectedOptions.day || []),
...(selectedOptions.week || []),
];
dates.sortPeriodOptions(yearOptions);
dates.sortPeriodOptions(otherOptions);
const ranges = [];
for (const yearOption of yearOptions) {
const constructRangeParams = {
referenceMoment,
fieldName,
fieldType,
plusParam,
};
if (otherOptions.length) {
for (const option of otherOptions) {
const setParam = Object.assign(
{},
yearOption.setParam,
option ? option.setParam : {}
);
const {granularity, custom_period} = option;
if (comparisonOptionId && custom_period.is_custom_period) {
custom_period.is_comparison = true;
}
const range = dates.constructDateRange(
Object.assign(
{granularity, custom_period, setParam},
constructRangeParams
)
);
ranges.push(range);
}
} else {
const {granularity, custom_period, setParam} = yearOption;
if (comparisonOptionId && custom_period.is_custom_period) {
custom_period.is_comparison = true;
}
const range = dates.constructDateRange(
Object.assign(
{granularity, custom_period, setParam},
constructRangeParams
)
);
ranges.push(range);
}
}
const domain = Domain.combine(
ranges.map((range) => range.domain),
"OR"
);
const description = ranges.map((range) => range.description).join("/");
return {domain, description};
},
constructDateRange(params) {
const {
referenceMoment,
fieldName,
fieldType,
granularity,
setParam,
plusParam,
custom_period,
} = params;
if ("quarter" in setParam) {
// Luxon does not consider quarter key in setParam (like moment did)
setParam.month = dates.QUARTERS[setParam.quarter].coveredMonths[0];
delete setParam.quarter;
}
const plusParamReferenceMoment = referenceMoment.plus(plusParam || {});
const date = referenceMoment.set(setParam).plus(plusParam || {});
// Compute domain
const leftDate = date.startOf(granularity);
var rightDate = date.endOf(granularity);
if (custom_period.is_custom_period) {
rightDate = plusParamReferenceMoment;
}
let leftBound;
let rightBound;
if (fieldType === "date") {
leftBound = serializeDate(leftDate);
rightBound = serializeDate(rightDate);
} else {
leftBound = serializeDateTime(leftDate);
rightBound = serializeDateTime(rightDate);
}
const domain = new Domain([
"&",
[fieldName, ">=", leftBound],
[fieldName, "<=", rightBound],
]);
// Compute description
var description = "";
if (custom_period.is_custom_period) {
if (plusParam) {
const key = Object.keys(plusParam)[0];
description = "Previous " + Math.abs(plusParam[key]) + " " + key;
} else {
description =
"Last " + custom_period.last_period + " " + granularity + "s";
}
} else {
var descriptions = [date.toFormat("yyyy")];
const method = localization.direction === "rtl" ? "push" : "unshift";
if (granularity === "month") {
descriptions[method](date.toFormat("MMMM"));
} else if (granularity === "quarter") {
const quarter = date.quarter;
descriptions[method](dates.QUARTERS[quarter].description.toString());
} else if (granularity === "day") {
descriptions[method](date.toFormat("dd MMMM"));
} else if (granularity === "week") {
descriptions[method](date.toFormat("'W'WW"));
}
description = descriptions.join(" ");
}
return {domain, description};
},
/*
* Redefine function to allow process days and weeks.
*/
getPeriodOptions(referenceMoment) {
const options = [];
const originalOptions = Object.values(dates.PERIOD_OPTIONS);
for (const option of originalOptions) {
const {id, groupNumber} = option;
let description;
let defaultYear;
switch (option.granularity) {
case "quarter":
description = option.description.toString();
defaultYear = referenceMoment.set(option.setParam).year;
break;
case "day":
case "week":
case "month":
case "year":
const date = referenceMoment.plus(option.plusParam);
description = option.description || date.toFormat(option.format);
defaultYear = date.year;
break;
}
const setParam = dates.getSetParam(option, referenceMoment);
options.push({id, groupNumber, description, defaultYear, setParam});
}
const periodOptions = [];
for (const option of options) {
const {id, groupNumber, description, defaultYear} = option;
const yearOption = options.find(
(o) => o.setParam && o.setParam.year === defaultYear
);
periodOptions.push({
id,
groupNumber,
description,
defaultYearId: yearOption.id,
});
}
return periodOptions;
},
/*
* Add selection of month when week or day selected.
*
* @override
*/
getSetParam(periodOption, referenceMoment) {
if (periodOption.granularity === "day") {
const date = referenceMoment.plus(periodOption.plusParam);
return {
day: date.day,
month: date.month,
};
} else if (periodOption.granularity === "week") {
const date = referenceMoment.plus(periodOption.plusParam);
return {
weekNumber: date.weekNumber,
};
}
return _getSetParam(...arguments);
},
getSelectedOptions(referenceMoment, selectedOptionIds) {
const selectedOptions = {year: []};
for (const optionId of selectedOptionIds) {
const option = dates.PERIOD_OPTIONS[optionId];
const setParam = dates.getSetParam(option, referenceMoment);
const custom_period = option.custom_period || {};
const granularity = option.granularity;
if (!selectedOptions[granularity]) {
selectedOptions[granularity] = [];
}
selectedOptions[granularity].push({granularity, setParam, custom_period});
}
return selectedOptions;
},
/*
* Add support to day and week options.
*
*/
getComparisonParams(referenceMoment, selectedOptionIds, comparisonOptionId) {
const comparisonOption = dates.COMPARISON_OPTIONS[comparisonOptionId];
const selectedOptions = dates.getSelectedOptions(
referenceMoment,
selectedOptionIds
);
if (comparisonOption.plusParam) {
return [comparisonOption.plusParam, selectedOptions];
}
const plusParam = {};
let globalGranularity = "year";
if (selectedOptions.day) {
globalGranularity = "day";
} else if (selectedOptions.week) {
globalGranularity = "week";
} else if (selectedOptions.month) {
globalGranularity = "month";
} else if (selectedOptions.quarter) {
globalGranularity = "quarter";
}
const granularityFactor = dates.PER_YEAR[globalGranularity];
const years = selectedOptions.year.map((o) => o.setParam.year);
const yearMin = Math.min(...years);
const yearMax = Math.max(...years);
let optionMin = 0;
let optionMax = 0;
if (selectedOptions.quarter) {
const quarters = selectedOptions.quarter.map((o) => o.setParam.quarter);
if (globalGranularity === "month") {
delete selectedOptions.quarter;
for (const quarter of quarters) {
for (const month of dates.QUARTERS[quarter].coveredMonths) {
const monthOption = selectedOptions.month.find(
(o) => o.setParam.month === month
);
if (!monthOption) {
selectedOptions.month.push({
setParam: {month},
granularity: "month",
});
}
}
}
} else {
optionMin = Math.min(...quarters);
optionMax = Math.max(...quarters);
}
}
if (selectedOptions.month) {
const months = selectedOptions.month.map((o) => o.setParam.month);
optionMin = Math.min(...months);
optionMax = Math.max(...months);
}
if (selectedOptions.week) {
const weeks = selectedOptions.week.map((o) => o.setParam.weekNumber);
optionMin = Math.min(...weeks);
optionMax = Math.max(...weeks);
}
if (selectedOptions.day) {
const days = selectedOptions.day.map((o) => o.setParam.day);
optionMin = Math.min(...days);
optionMax = Math.max(...days);
}
const num =
-1 + granularityFactor * (yearMin - yearMax) + optionMin - optionMax;
const key =
globalGranularity === "year"
? "years"
: globalGranularity === "month"
? "months"
: globalGranularity === "week"
? "weeks"
: globalGranularity === "day"
? "days"
: "quarters";
plusParam[key] = num;
return [plusParam, selectedOptions];
},
});

View File

@ -1,238 +0,0 @@
/* Copyright 2021 Tecnativa - Alexandre D. Díaz
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
odoo.define("web_time_range_menu_custom.Domain", function (require) {
"use strict";
const Domain = require("web.Domain");
Domain.include({
/**
* @override
*/
constructCustomDomain: function (
fieldName,
period,
type,
comparisonPeriod,
periodCustom,
comparisonPeriodCustom
) {
let leftBoundaryParams = {};
let rightBoundaryParams = {};
let offsetPeriodParams = 0;
// Cloned function from web.domain
// Necessary because inner function usage
// TODO: Check changes in new versions
function makeInterval() {
switch (comparisonPeriod) {
case "previous_period":
_.each(offsetPeriodParams, function (value, key) {
if (
!leftBoundaryParams[key] ||
_.isNumber(leftBoundaryParams[key])
) {
leftBoundaryParams[key] =
value + (leftBoundaryParams[key] || 0);
} else {
leftBoundaryParams[key] =
value + " + " + leftBoundaryParams[key];
}
if (
!rightBoundaryParams[key] ||
_.isNumber(rightBoundaryParams[key])
) {
rightBoundaryParams[key] =
value + (rightBoundaryParams[key] || 0);
} else {
rightBoundaryParams[key] =
value + " + " + rightBoundaryParams[key];
}
});
break;
case "previous_year":
leftBoundaryParams.years = leftBoundaryParams.years
? leftBoundaryParams.years - 1
: -1;
rightBoundaryParams.years = rightBoundaryParams.years
? rightBoundaryParams.years - 1
: -1;
break;
case "custom_comparison_period":
// This case is the addition for custom periods
leftBoundaryParams[comparisonPeriodCustom.type] =
leftBoundaryParams[comparisonPeriodCustom.type]
? leftBoundaryParams[comparisonPeriodCustom.type] -
comparisonPeriodCustom.value
: -comparisonPeriodCustom.value;
rightBoundaryParams[comparisonPeriodCustom.type] =
rightBoundaryParams[comparisonPeriodCustom.type]
? rightBoundaryParams[comparisonPeriodCustom.type] -
comparisonPeriodCustom.value
: -comparisonPeriodCustom.value;
break;
}
var stringifyParams = function (value, key) {
return key + "=" + value;
};
var leftBoundaryStringifyParams = _.map(
leftBoundaryParams,
stringifyParams
).join(", ");
var rightBoundaryStringifyParams = _.map(
rightBoundaryParams,
stringifyParams
).join(", ");
if (type === "date") {
return (
"['&'," +
"('" +
fieldName +
"', '>=', (context_today() + relativedelta(" +
leftBoundaryStringifyParams +
")).strftime('%Y-%m-%d'))," +
"('" +
fieldName +
"', '<', (context_today() + relativedelta(" +
rightBoundaryStringifyParams +
")).strftime('%Y-%m-%d'))" +
"]"
);
}
return (
"['&'," +
"('" +
fieldName +
"', '>=', " +
"(datetime.datetime.combine(context_today() + relativedelta(" +
leftBoundaryStringifyParams +
"), datetime.time(0,0,0)).to_utc()).strftime('%Y-%m-%d %H:%M:%S'))," +
"('" +
fieldName +
"', '<', " +
"(datetime.datetime.combine(context_today() + relativedelta(" +
rightBoundaryStringifyParams +
"), datetime.time(0,0,0)).to_utc()).strftime('%Y-%m-%d %H:%M:%S'))" +
"]"
);
}
function defineCustom() {
if (periodCustom.type === "years") {
leftBoundaryParams = {month: 1, day: 1, years: -periodCustom.value};
rightBoundaryParams = {month: 1, day: 1};
offsetPeriodParams = {years: -periodCustom.value};
} else if (periodCustom.type === "months") {
leftBoundaryParams = {day: 1, months: -periodCustom.value};
rightBoundaryParams = {day: 1};
offsetPeriodParams = {months: -periodCustom.value};
} else if (periodCustom.type === "weeks") {
leftBoundaryParams = {
days: 1,
weekday: 0,
weeks: -periodCustom.value,
};
rightBoundaryParams = {days: 1, weekday: 0};
offsetPeriodParams = {weeks: -periodCustom.value};
} else if (periodCustom.type === "days") {
leftBoundaryParams = {days: -periodCustom.value};
rightBoundaryParams = {};
offsetPeriodParams = {days: -periodCustom.value};
}
}
// Cloned from web.domain
// Necessary because can have customPeriod or/and comparisonPeriodCustom
// TODO: Check changes in new versions
switch (period) {
case "today":
leftBoundaryParams = {};
rightBoundaryParams = {days: 1};
offsetPeriodParams = {days: -1};
return makeInterval();
case "this_week":
leftBoundaryParams = {weeks: -1, days: 1, weekday: 0};
rightBoundaryParams = {days: 1, weekday: 0};
offsetPeriodParams = {weeks: -1};
return makeInterval();
case "this_month":
leftBoundaryParams = {day: 1};
rightBoundaryParams = {day: 1, months: 1};
offsetPeriodParams = {months: -1};
return makeInterval();
case "this_quarter":
leftBoundaryParams = {
months: "- (context_today().month - 1) % 3",
day: 1,
};
rightBoundaryParams = {
months: "3 - (context_today().month - 1) % 3",
day: 1,
};
offsetPeriodParams = {months: -3};
return makeInterval();
case "this_year":
leftBoundaryParams = {month: 1, day: 1};
rightBoundaryParams = {month: 1, day: 1, years: 1};
offsetPeriodParams = {years: -1};
return makeInterval();
case "yesterday":
leftBoundaryParams = {days: -1};
rightBoundaryParams = {};
offsetPeriodParams = {days: -1};
return makeInterval();
case "last_week":
leftBoundaryParams = {weeks: -2, days: 1, weekday: 0};
rightBoundaryParams = {weeks: -1, days: 1, weekday: 0};
offsetPeriodParams = {weeks: -1};
return makeInterval();
case "last_month":
leftBoundaryParams = {months: -1, day: 1};
rightBoundaryParams = {day: 1};
offsetPeriodParams = {months: -1};
return makeInterval();
case "last_quarter":
leftBoundaryParams = {
months: "- 3 - (context_today().month - 1) % 3",
day: 1,
};
rightBoundaryParams = {
months: "- (context_today().month - 1) % 3",
day: 1,
};
offsetPeriodParams = {months: -3};
return makeInterval();
case "last_year":
leftBoundaryParams = {month: 1, day: 1, years: -1};
rightBoundaryParams = {month: 1, day: 1};
offsetPeriodParams = {years: -1};
return makeInterval();
case "last_7_days":
leftBoundaryParams = {days: -7};
rightBoundaryParams = {};
offsetPeriodParams = {days: -7};
return makeInterval();
case "last_30_days":
leftBoundaryParams = {days: -30};
rightBoundaryParams = {};
offsetPeriodParams = {days: -30};
return makeInterval();
case "last_365_days":
leftBoundaryParams = {days: -365};
rightBoundaryParams = {};
offsetPeriodParams = {days: -365};
return makeInterval();
case "last_5_years":
leftBoundaryParams = {years: -5};
rightBoundaryParams = {};
offsetPeriodParams = {years: -5};
return makeInterval();
case "custom_period":
// This case is the addition for custom periods
defineCustom();
return makeInterval();
}
},
});
});

View File

@ -1,57 +0,0 @@
/* Copyright 2021 Tecnativa - Alexandre D. Díaz
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
odoo.define("web_time_range_menu_custom.SearchFacet", function (require) {
"use strict";
const core = require("web.core");
const SearchFacet = require("web.SearchFacet");
const _t = core._t;
SearchFacet.include({
/**
* @override
*/
_getFilterDescription: function (filter) {
if (
filter.type === "timeRange" &&
(filter.timeRangeId === "custom_period" ||
filter.comparisonTimeRangeId === "custom_comparison_period")
) {
let description = filter.description;
if (filter.timeRangeId === "custom_period") {
description +=
": " +
_t("Last ") +
`${filter.timeRangeCustom.value} ${filter.timeRangeCustom.type}`;
} else {
var timeRangeValue = _.findWhere(filter.timeRangeOptions, {
optionId: filter.timeRangeId,
});
description += ": " + timeRangeValue.description;
}
if (filter.comparisonTimeRangeId) {
if (filter.comparisonTimeRangeId === "custom_comparison_period") {
description +=
" / " +
_t("Previous ") +
`${filter.comparisonTimeRangeCustom.value} ${filter.comparisonTimeRangeCustom.type}`;
} else {
var comparisonTimeRangeValue = _.findWhere(
filter.comparisonTimeRangeOptions,
{
optionId: filter.comparisonTimeRangeId,
}
);
description += " / " + comparisonTimeRangeValue.description;
}
}
return description;
}
return this._super.apply(this, arguments);
},
});
});

View File

@ -1,140 +0,0 @@
/* Copyright 2021 Tecnativa - Alexandre D. Díaz
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
odoo.define("web_time_range_menu_custom.TimeRangeMenu", function (require) {
"use strict";
const TimeRangeMenu = require("web.TimeRangeMenu");
TimeRangeMenu.include({
events: _.extend({}, TimeRangeMenu.prototype.events, {
"change #time_range_selector": "_onChangeTimeRangeSelector",
"change .o_comparison_time_range_selector":
"_onChangeComparisonTimeRangeSelector",
}),
renderElement: function () {
this._super.apply(this, arguments);
this.$time_range_selector = this.$el.find("#time_range_selector");
this.$comparison_time_range_selector = this.$el.find(
".o_comparison_time_range_selector"
);
this.$selector_custom = this.$el.find("#time_range_selector_custom");
this.$selector_custom_field_value = this.$selector_custom.find(
"#date_field_selector_custom_value"
);
this.$selector_custom_field_type = this.$selector_custom.find(
"#date_field_selector_custom_type"
);
this.$selector_comparison_custom = this.$el.find(
"#comparison_time_range_selector_custom"
);
this.$selector_comparison_custom_field_value =
this.$selector_comparison_custom.find(
"#date_field_selector_comparison_custom_value"
);
this.$selector_comparison_custom_field_type =
this.$selector_comparison_custom.find(
"#date_field_selector_comparison_custom_type"
);
this.$selector_custom.toggleClass(
"d-none",
this.$time_range_selector.val() !== "custom_period"
);
this.$selector_comparison_custom.toggleClass(
"d-none",
this.$comparison_time_range_selector.val() !==
"custom_comparison_period"
);
if (!_.isEmpty(this.timeRangeCustom)) {
this.$selector_custom_field_value.val(this.timeRangeCustom.value);
this.$selector_custom_field_type.val(this.timeRangeCustom.type);
}
if (!_.isEmpty(this.comparisonTimeRangeCustom)) {
this.$selector_comparison_custom_field_value.val(
this.comparisonTimeRangeCustom.value
);
this.$selector_comparison_custom_field_type.val(
this.comparisonTimeRangeCustom.type
);
}
},
_onChangeTimeRangeSelector: function (ev) {
this.$selector_custom.toggleClass(
"d-none",
ev.target.value !== "custom_period"
);
},
_onChangeComparisonTimeRangeSelector: function (ev) {
this.$selector_comparison_custom.toggleClass(
"d-none",
ev.target.value !== "custom_comparison_period"
);
},
_onCheckBoxClick: function () {
this._super.apply(this, arguments);
const comparisonTimeRangeId = this.$(
".o_comparison_time_range_selector"
).val();
this.$selector_comparison_custom.toggleClass(
"d-none",
!this.configuration.comparisonIsSelected ||
comparisonTimeRangeId !== "custom_comparison_period"
);
},
_onApplyButtonClick: function () {
const id = this.$(".o_date_field_selector").val();
const timeRangeId = this.$(".o_time_range_selector").val();
let comparisonTimeRangeId = false;
if (this.configuration.comparisonIsSelected) {
comparisonTimeRangeId = this.$(
".o_comparison_time_range_selector"
).val();
}
if (
timeRangeId !== "custom_period" &&
comparisonTimeRangeId !== "custom_comparison_period"
) {
return this._super.apply(this, arguments);
}
const values = {
id: id,
timeRangeId: timeRangeId,
comparisonTimeRangeId: comparisonTimeRangeId,
};
this.timeRangeCustom = null;
if (timeRangeId === "custom_period") {
const value = this.$selector_custom_field_value.val();
const type = this.$selector_custom_field_type.val();
if (!value || !type) {
return false;
}
this.timeRangeCustom = {
value: value,
type: type,
};
values.timeRangeCustom = this.timeRangeCustom;
}
this.comparisonTimeRangeCustom = null;
if (comparisonTimeRangeId === "custom_comparison_period") {
const value = this.$selector_comparison_custom_field_value.val();
const type = this.$selector_comparison_custom_field_type.val();
if (!value || !type) {
return false;
}
this.comparisonTimeRangeCustom = {
value: value,
type: type,
};
values.comparisonTimeRangeCustom = this.comparisonTimeRangeCustom;
}
this.trigger_up("activate_custom_time_range", values);
},
});
});

View File

@ -0,0 +1,8 @@
#add_custom_period_wrapper {
padding: 0 20px;
white-space: nowrap;
.d-table-cell {
vertical-align: middle;
padding: 3px 0;
}
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-inherit="web.ComparisonMenu" t-inherit-mode="extension" owl="1">
<xpath expr="//t[@t-foreach='items']" position="after">
<DropdownItemCustomPeriod comparisonItems="items" type="'comparison'" />
</xpath>
</t>
</templates>

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="web_time_range_menu_custom.DropdownItemCustomPeriod" owl="1">
<div class="o_menu_item dropdown-item" data-id="__computed__">
<a href="#" role="menuitem" t-on-click="onClickCustomPeriod">
Custom period
<span class="o_submenu_switcher" data-id="__computed__">
<span
t-att-class="isOpen.value ? 'fa fa-caret-down' : 'fa fa-caret-right'"
/>
</span>
</a>
<t t-if="isOpen.value">
<div id="add_custom_period_wrapper" class="d-table">
<div class="d-table-row" t-if="props.type === 'comparison'">
<div class="d-table-cell">
<select
id="field_comparison"
class="o_input o_date_field_selector"
t-model="field.value"
>
<t
t-foreach="fields_selected"
t-as="field_item"
t-key="field_item"
>
<option t-att-value="field_item"><span
t-esc="env.searchModel.searchItems[field_item].description"
/></option>
</t>
</select>
</div>
</div>
<div class="d-table-row">
<div class="d-table-cell" style="width: 50%">
<input
type="number"
min="0"
id="period_quantity"
class="o_input"
t-model="quantity.value"
/>
</div>
<div class="d-table-cell">
<select
id="period_type"
class="o_input o_date_field_selector"
t-model="type.value"
>
<option value="day">Day</option>
<option value="week">Week</option>
<option value="month">Month</option>
<option value="year">Year</option>
</select>
</div>
</div>
<div class="d-table-row">
<div class="d-table-cell">
<button
class="btn btn-primary o_add_computed_measure"
type="button"
t-on-click="onClickAdd"
>Add</button>
</div>
</div>
</div>
</t>
</div>
</t>
</templates>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-inherit="web.FilterMenu" t-inherit-mode="extension" owl="1">
<xpath
expr="//t[@t-if='item.options']//t[@t-foreach='item.options']"
position="after"
>
<t t-if="item.type === 'dateFilter'">
<DropdownItemCustomPeriod field="item" type="'filter'" />
</t>
</xpath>
</t>
</templates>

View File

@ -1,53 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<template>
<t t-name="web_time_range_menu_custom.CustomZone">
<div class="d-flex">
<input type="number" min="1" t-att-id="field_value" class="o_input" />
<select t-att-id="field_type" class="o_input o_date_field_selector">
<option value="days">Day</option>
<option value="weeks">Week</option>
<option value="months">Month</option>
<option value="years">Year</option>
</select>
</div>
</t>
<t t-extend="web.TimeRangeMenu">
<t
t-jquery="div.dropdown-item-text:has(#time_range_selector)"
t-operation="after"
>
<div class="dropdown-item-text d-none" id="time_range_selector_custom">
<t t-call="web_time_range_menu_custom.CustomZone">
<t
t-set="field_value"
t-value="'date_field_selector_custom_value'"
/>
<t t-set="field_type" t-value="'date_field_selector_custom_type'" />
</t>
</div>
</t>
<t
t-jquery="div.dropdown-item-text:has(.o_comparison_checkbox)"
t-operation="after"
>
<div
class="dropdown-item-text d-none"
id="comparison_time_range_selector_custom"
>
<t t-call="web_time_range_menu_custom.CustomZone">
<t
t-set="field_value"
t-value="'date_field_selector_comparison_custom_value'"
/>
<t
t-set="field_type"
t-value="'date_field_selector_comparison_custom_type'"
/>
</t>
</div>
</t>
</t>
</template>

View File

@ -1,35 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<template
id="assets_backend"
name="Web Pivot Time Range - Assets Backend"
inherit_id="web.assets_backend"
>
<xpath expr="//script[1]" position="after">
<script
src="/web_time_range_menu_custom/static/src/js/domain.js"
type="text/javascript"
/>
<script
src="/web_time_range_menu_custom/static/src/js/control_panel_view_parameters.js"
type="text/javascript"
/>
<script
src="/web_time_range_menu_custom/static/src/js/control_panel_model.js"
type="text/javascript"
/>
<script
src="/web_time_range_menu_custom/static/src/js/control_panel_controller.js"
type="text/javascript"
/>
<script
src="/web_time_range_menu_custom/static/src/js/search_facet.js"
type="text/javascript"
/>
<script
src="/web_time_range_menu_custom/static/src/js/time_range_menu.js"
type="text/javascript"
/>
</xpath>
</template>
</odoo>