diff --git a/setup/web_widget_date_more_filters/odoo/addons/web_widget_date_more_filters b/setup/web_widget_date_more_filters/odoo/addons/web_widget_date_more_filters new file mode 120000 index 000000000000..92ec45e2a870 --- /dev/null +++ b/setup/web_widget_date_more_filters/odoo/addons/web_widget_date_more_filters @@ -0,0 +1 @@ +../../../../web_widget_date_more_filters \ No newline at end of file diff --git a/setup/web_widget_date_more_filters/setup.py b/setup/web_widget_date_more_filters/setup.py new file mode 100644 index 000000000000..28c57bb64031 --- /dev/null +++ b/setup/web_widget_date_more_filters/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/web_widget_date_more_filters/__init__.py b/web_widget_date_more_filters/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/web_widget_date_more_filters/__manifest__.py b/web_widget_date_more_filters/__manifest__.py new file mode 100644 index 000000000000..02e11348683d --- /dev/null +++ b/web_widget_date_more_filters/__manifest__.py @@ -0,0 +1,18 @@ +# Copyright OpenStudio 2024 +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +{ + "name": "Web date widget more filters", + "category": "web", + "version": "16.0.1.0.0", + "website": "https://github.com/OCA/web", + "author": "Elise Gigot ", + "depends": ["web", "hr_timesheet"], + "summary": "Add some filters to the date filters wdidget", + "license": "AGPL-3", + "installable": True, + "assets": { + "web.assets_backend": [ + "web_widget_date_more_filters/static/src/js/web_widget_date_more_filters.js", + ], + }, +} diff --git a/web_widget_date_more_filters/i18n/fr.po b/web_widget_date_more_filters/i18n/fr.po new file mode 100644 index 000000000000..30b777c9f984 --- /dev/null +++ b/web_widget_date_more_filters/i18n/fr.po @@ -0,0 +1,66 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * hr_timesheet_custom_date_filter + +#. module: hr_timesheet_custom_date_filter +#. odoo-javascript +#: code:addons/static/src/js/hr_timesheet_custom_date_filter.js:0 +#, python-format +msgid "Today" +msgstr "Aujourd'hui" + +#. module: hr_timesheet_custom_date_filter +#. odoo-javascript +#: code:addons/static/src/js/hr_timesheet_custom_date_filter.js:0 +#, python-format +msgid "Yesterday" +msgstr "Hier" + +#. module: hr_timesheet_custom_date_filter +#. odoo-javascript +#: code:addons/static/src/js/hr_timesheet_custom_date_filter.js:0 +#, python-format +msgid "This week" +msgstr "Cette semaine" + +#. module: hr_timesheet_custom_date_filter +#. odoo-javascript +#: code:addons/static/src/js/hr_timesheet_custom_date_filter.js:0 +#, python-format +msgid "Last 7 days" +msgstr "7 derniers jours" + +#. module: hr_timesheet_custom_date_filter +#. odoo-javascript +#: code:addons/static/src/js/hr_timesheet_custom_date_filter.js:0 +#, python-format +msgid "Last 30 days" +msgstr "30 derniers jours" + +#. module: hr_timesheet_custom_date_filter +#. odoo-javascript +#: code:addons/static/src/js/hr_timesheet_custom_date_filter.js:0 +#, python-format +msgid "Last 365 days" +msgstr "365 derniers jours" + +#. module: hr_timesheet_custom_date_filter +#. odoo-javascript +#: code:addons/static/src/js/hr_timesheet_custom_date_filter.js:0 +#, python-format +msgid "From Date" +msgstr "À partir du" + +#. module: hr_timesheet_custom_date_filter +#. odoo-javascript +#: code:addons/static/src/js/hr_timesheet_custom_date_filter.js:0 +#, python-format +msgid "Until" +msgstr "Jusqu'au" + +#. module: hr_timesheet_custom_date_filter +#. odoo-javascript +#: code:addons/static/src/js/hr_timesheet_custom_date_filter.js:0 +#, python-format +msgid "Week" +msgstr "Semaine" \ No newline at end of file diff --git a/web_widget_date_more_filters/static/src/js/web_widget_date_more_filters.js b/web_widget_date_more_filters/static/src/js/web_widget_date_more_filters.js new file mode 100644 index 000000000000..9115e5dead3f --- /dev/null +++ b/web_widget_date_more_filters/static/src/js/web_widget_date_more_filters.js @@ -0,0 +1,390 @@ +/** @odoo-module **/ + +import { + QUARTERS, + COMPARISON_OPTIONS, + sortPeriodOptions, + MONTH_OPTIONS, + QUARTER_OPTIONS, + YEAR_OPTIONS, + PER_YEAR, +} from "@web/search/utils/dates"; +import * as dates from "@web/search/utils/dates"; +import {Domain} from "@web/core/domain"; +import {serializeDate, serializeDateTime} from "@web/core/l10n/dates"; +import {localization} from "@web/core/l10n/localization"; +import {_lt} from "@web/core/l10n/translation"; + +const {DateTime} = luxon; + +export const referenceMoment = DateTime.local(); + +const LAST_DAYS = { + 7: {description: _lt("Last 7 days")}, + 30: {description: _lt("Last 30 days")}, + 365: {description: _lt("Last 365 days")}, +}; + +export const NEW_OPTIONS = { + this_day: { + id: "this_day", + groupNumber: 3, + description: _lt("Today"), + granularity: "day", + plusParam: {}, + }, + this_yesterday: { + id: "this_yesterday", + groupNumber: 3, + description: _lt("Yesterday"), + granularity: "day", + plusParam: {days: -1}, + }, + this_week: { + id: "this_week", + groupNumber: 3, + description: _lt("This week"), + granularity: "week", + plusParam: {}, + }, + last_7_days: { + id: "last_7_days", + groupNumber: 4, + description: LAST_DAYS[7].description, + granularity: "day", + plusParam: {}, + dateParams: { + minus: {days: 7}, + }, + }, + last_30_days: { + id: "last_30_days", + groupNumber: 4, + description: LAST_DAYS[30].description, + granularity: "day", + plusParam: {}, + dateParams: { + minus: {days: 30}, + }, + }, + last_365_days: { + id: "last_365_days", + groupNumber: 4, + description: LAST_DAYS[365].description, + granularity: "day", + plusParam: {}, + dateParams: { + minus: {days: 365}, + }, + }, +}; + +export const CUSTOM_PERIOD_OPTIONS = Object.assign( + {}, + MONTH_OPTIONS, + QUARTER_OPTIONS, + YEAR_OPTIONS, + NEW_OPTIONS +); + +export const CUSTOM_PER_YEAR = { + ...PER_YEAR, + day: 365, + week: 52, +}; + +export function getDateParams(selectedOptionIds) { + const selectedOptions = []; + for (const optionId of selectedOptionIds) { + const option = CUSTOM_PERIOD_OPTIONS[optionId]; + if (option?.dateParams) { + selectedOptions.push(option.dateParams); + } + } + return selectedOptions; +} + +export function customConstructDateDomain( + referenceMoment, + fieldName, + fieldType, + selectedOptionIds, + comparisonOptionId +) { + let plusParam; + let selectedOptions; + if (comparisonOptionId) { + [plusParam, selectedOptions] = customGetComparisonParams( + referenceMoment, + selectedOptionIds, + comparisonOptionId + ); + } else { + selectedOptions = customGetSelectedOptions(referenceMoment, selectedOptionIds); + } + const dateParams = getDateParams(selectedOptionIds); + const yearOptions = selectedOptions.year; + const otherOptions = [ + ...(selectedOptions.quarter || []), + ...(selectedOptions.month || []), + ...(selectedOptions.day || []), + ...(selectedOptions.week || []), + ]; + sortPeriodOptions(yearOptions); + sortPeriodOptions(otherOptions); + const ranges = []; + for (const yearOption of yearOptions) { + const constructRangeParams = { + referenceMoment, + fieldName, + fieldType, + plusParam, + dateParams, + comparisonOptionId, + }; + if (otherOptions.length) { + for (const option of otherOptions) { + const setParam = Object.assign( + {}, + yearOption.setParam, + option ? option.setParam : {} + ); + const {granularity} = option; + const range = customConstructDateRange( + Object.assign({granularity, setParam}, constructRangeParams) + ); + ranges.push(range); + } + } else { + const {granularity, setParam} = yearOption; + const range = customConstructDateRange( + Object.assign({granularity, 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}; +} + +export function customConstructDateRange(params) { + const { + referenceMoment, + fieldName, + fieldType, + granularity, + setParam, + plusParam, + dateParams, + comparisonOptionId, + } = params; + if ("quarter" in setParam) { + // Luxon does not consider quarter key in setParam (like moment did) + setParam.month = QUARTERS[setParam.quarter].coveredMonths[0]; + delete setParam.quarter; + } + + const date = referenceMoment.set(setParam).plus(plusParam || {}); + // compute domain + const leftDate = date.minus(dateParams?.[0]?.minus || {}).startOf(granularity); + const rightDate = date.endOf(granularity); + 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 + const descriptions = + granularity !== "day" && granularity !== "week" ? [date.toFormat("yyyy")] : []; + const method = localization.direction === "rtl" ? "push" : "unshift"; + + if (granularity === "day") { + let description; + const minusDays = dateParams?.[0]?.minus?.days; + const isToday = referenceMoment.day === date.day; + const isYesterday = referenceMoment.day - 1 === date.day; + + if (comparisonOptionId && minusDays) { + description = `${_lt("From Date")} ${leftDate.toFormat( + "dd LLL yyyy" + )} ${_lt("Until")} ${rightDate.toFormat("dd LLL yyyy")}`; + } else if (minusDays) { + description = LAST_DAYS[dateParams[0].minus.days].description; + } else if (!comparisonOptionId && (isToday || isYesterday)) { + description = isToday ? _lt("Today") : _lt("Yesterday"); + } else { + description = date.toFormat("dd LLL yyyy"); + } + + descriptions[method](description); + } + if (granularity === "week") { + const comparisonDescription = `${_lt("Week")}: ${ + date.weekNumber + } - ${date.toFormat("yyyy")}`; + const description = comparisonOptionId + ? comparisonDescription + : _lt("This week"); + descriptions[method](description); + } + if (granularity === "month") { + descriptions[method](date.toFormat("MMMM")); + } else if (granularity === "quarter") { + const quarter = date.quarter; + descriptions[method](QUARTERS[quarter].description.toString()); + } + const description = descriptions.join(" "); + return {domain, description}; +} + +export function customGetPeriodOptions(referenceMoment) { + // adapt when solution for moment is found... + const options = []; + const originalOptions = Object.values(CUSTOM_PERIOD_OPTIONS); + for (const option of originalOptions) { + const {id, groupNumber} = option; + let description; + let defaultYear; + switch (option.granularity) { + case "quarter": + case "week": + case "day": + description = option.description.toString(); + defaultYear = referenceMoment.set(option.setParam).year; + break; + case "month": + case "year": { + const date = referenceMoment.plus(option.plusParam); + description = date.toFormat(option.format); + defaultYear = date.year; + break; + } + } + const setParam = customGetSetParam(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; +} + +export function customGetSelectedOptions(referenceMoment, selectedOptionIds) { + const selectedOptions = {year: []}; + for (const optionId of selectedOptionIds) { + const option = CUSTOM_PERIOD_OPTIONS[optionId]; + const setParam = customGetSetParam(option, referenceMoment); + const granularity = option.granularity; + if (!selectedOptions[granularity]) { + selectedOptions[granularity] = []; + } + selectedOptions[granularity].push({granularity, setParam}); + } + return selectedOptions; +} + +export function customGetSetParam(periodOption, referenceMoment) { + if (periodOption.granularity === "quarter") { + return periodOption.setParam; + } + const date = referenceMoment.plus(periodOption.plusParam); + const granularity = periodOption.granularity; + const setParam = {[granularity]: date[granularity]}; + return setParam; +} + +export function customGetComparisonParams( + referenceMoment, + selectedOptionIds, + comparisonOptionId +) { + const comparisonOption = COMPARISON_OPTIONS[comparisonOptionId]; + const selectedOptions = customGetSelectedOptions( + referenceMoment, + selectedOptionIds + ); + if (comparisonOption.plusParam) { + return [comparisonOption.plusParam, selectedOptions]; + } + const plusParam = {}; + const granularities = ["month", "quarter", "day", "week"]; + let globalGranularity = + granularities.find((granularity) => selectedOptions[granularity]) || "year"; + const granularityFactor = CUSTOM_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 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); + } + const num = -1 + granularityFactor * (yearMin - yearMax) + optionMin - optionMax; + const granularityMapping = { + year: "years", + month: "months", + week: "weeks", + day: "days", + quarter: "quarters", + }; + const key = granularityMapping[globalGranularity] || "quarters"; + plusParam[key] = num; + return [plusParam, selectedOptions]; +} + +dates.constructDateDomain = customConstructDateDomain; +dates.constructDateRange = customConstructDateRange; +dates.getPeriodOptions = customGetPeriodOptions; +dates.getSelectedOptions = customGetSelectedOptions; +dates.getSetParam = customGetSetParam; +dates.getComparisonParams = customGetComparisonParams; +dates.PERIOD_OPTIONS = CUSTOM_PERIOD_OPTIONS; +dates.PER_YEAR = CUSTOM_PER_YEAR;