From 9732d078fae5d0fc080d1f9f2e1592fa25bde992 Mon Sep 17 00:00:00 2001 From: Florian Mounier Date: Tue, 15 Mar 2022 17:42:49 +0100 Subject: [PATCH 01/14] [FIX] fieldservice_recurring: Use timezone aware rrule and normalize recurring dates to avoid hour jumps during daylight saving time changes --- .../models/fsm_frequency.py | 46 +++++++++++++++---- .../tests/test_fsm_recurring.py | 12 ++--- 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/fieldservice_recurring/models/fsm_frequency.py b/fieldservice_recurring/models/fsm_frequency.py index 1be5cc2177..3a7a2f8e1c 100644 --- a/fieldservice_recurring/models/fsm_frequency.py +++ b/fieldservice_recurring/models/fsm_frequency.py @@ -1,6 +1,7 @@ # Copyright (C) 2019 Brian McMaster, Open Source Integrators # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import pytz from dateutil.rrule import ( DAILY, FR, @@ -117,15 +118,42 @@ def _check_month_day(self): def _get_rrule(self, dtstart=None, until=None): self.ensure_one() freq = FREQUENCIES[self.interval_type] - return rrule( - freq, - interval=self.interval, - dtstart=dtstart, - until=until, - byweekday=self._byweekday(), - bymonth=self._bymonth(), - bymonthday=self._bymonthday(), - bysetpos=self._bysetpos(), + # localize dtstart and until to user timezone + tz = pytz.timezone(self._context.get("tz", self.env.user.tz or "UTC")) + + if dtstart: + dtstart = pytz.timezone("UTC").localize(dtstart).astimezone(tz) + if until: + until = pytz.timezone("UTC").localize(until).astimezone(tz) + # We force until in the starting timezone to avoid incoherent results + until = tz.normalize(until.replace(tzinfo=dtstart.tzinfo)) + + return ( + # Replace original timezone with current date timezone + # without changing the time and force it back to UTC, + # this will keep the same final time even in case of + # daylight saving time change + # + # for instance recurring weekly + # from 2022-03-21 15:00:00+01:00 to 2022-04-11 15:30:00+02:00 + # will give: + # + # utc naive -> datetime timezone aware + # 2022-03-21 14:00:00 -> 2022-03-21 15:00:00+01:00 + # 2022-03-28 13:00:00 -> 2022-03-28 15:00:00+02:00 + date.replace(tzinfo=tz.normalize(date).tzinfo) + .astimezone(pytz.UTC) + .replace(tzinfo=None) + for date in rrule( + freq, + interval=self.interval, + dtstart=dtstart, + until=until, + byweekday=self._byweekday(), + bymonth=self._bymonth(), + bymonthday=self._bymonthday(), + bysetpos=self._bysetpos(), + ) ) def _byweekday(self): diff --git a/fieldservice_recurring/tests/test_fsm_recurring.py b/fieldservice_recurring/tests/test_fsm_recurring.py index 0aafb3ce17..7b1f83dbad 100644 --- a/fieldservice_recurring/tests/test_fsm_recurring.py +++ b/fieldservice_recurring/tests/test_fsm_recurring.py @@ -99,7 +99,7 @@ def test_cron_generate_orders_rule1(self): { "fsm_frequency_set_id": fr_set.id, "location_id": self.test_location.id, - "start_date": fields.Datetime.today(), + "start_date": fields.Datetime.now().replace(hour=12), } ) test_recurring = self.Recurring.create( @@ -177,7 +177,7 @@ def test_cron_generate_orders_rule2(self): { "fsm_frequency_set_id": fr_set.id, "location_id": self.test_location.id, - "start_date": fields.Datetime.today(), + "start_date": fields.Datetime.now().replace(hour=12), "end_date": expire_date1, } ) @@ -185,7 +185,7 @@ def test_cron_generate_orders_rule2(self): { "fsm_frequency_set_id": fr_set.id, "location_id": self.test_location.id, - "start_date": fields.Datetime.today(), + "start_date": fields.Datetime.now().replace(hour=12), "max_orders": 1, } ) @@ -193,7 +193,7 @@ def test_cron_generate_orders_rule2(self): { "fsm_frequency_set_id": fr_set.id, "location_id": self.test_location.id, - "start_date": fields.Datetime.today(), + "start_date": fields.Datetime.now().replace(hour=12), "max_orders": 1, } ) @@ -210,7 +210,7 @@ def test_cron_generate_orders_rule2(self): x = False for d in all_dates: if x: - diff_days = (d - x).days + diff_days = (d.date() - x.date()).days self.assertEqual(diff_days, 21) x = d @@ -252,7 +252,7 @@ def test_cron_generate_orders_rule3(self): { "fsm_frequency_set_id": fr_set.id, "location_id": self.test_location.id, - "start_date": fields.Datetime.today(), + "start_date": fields.Datetime.now().replace(hour=12), } ) recurring.action_start() From 5029ff3b2ab5271e3d39d9587eb0a6dcdb4eed5c Mon Sep 17 00:00:00 2001 From: hparfr Date: Fri, 8 Apr 2022 21:38:39 +0200 Subject: [PATCH 02/14] Orders are created from a frequency It should not be allowed to select orders to be added to recurring --- fieldservice_recurring/views/fsm_recurring.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fieldservice_recurring/views/fsm_recurring.xml b/fieldservice_recurring/views/fsm_recurring.xml index ef3824081a..b49238250c 100644 --- a/fieldservice_recurring/views/fsm_recurring.xml +++ b/fieldservice_recurring/views/fsm_recurring.xml @@ -86,8 +86,8 @@ - - + + From 265c7ae949227fdeca034214437f85e2e373913a Mon Sep 17 00:00:00 2001 From: Florian Mounier Date: Thu, 2 Jun 2022 10:12:08 +0200 Subject: [PATCH 03/14] [FIX] fieldservice_recurring: Fix missing timezone when context->tz is False --- fieldservice_recurring/models/fsm_frequency.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fieldservice_recurring/models/fsm_frequency.py b/fieldservice_recurring/models/fsm_frequency.py index 3a7a2f8e1c..954e1fe462 100644 --- a/fieldservice_recurring/models/fsm_frequency.py +++ b/fieldservice_recurring/models/fsm_frequency.py @@ -119,7 +119,9 @@ def _get_rrule(self, dtstart=None, until=None): self.ensure_one() freq = FREQUENCIES[self.interval_type] # localize dtstart and until to user timezone - tz = pytz.timezone(self._context.get("tz", self.env.user.tz or "UTC")) + tz = ( + pytz.timezone(self._context.get("tz", None) or self.env.user.tz) or pytz.UTC + ) if dtstart: dtstart = pytz.timezone("UTC").localize(dtstart).astimezone(tz) From eb88f49a5558303b0c6cf0b5c77472ac7f2aee3b Mon Sep 17 00:00:00 2001 From: hparfr Date: Fri, 17 Jun 2022 16:40:05 +0200 Subject: [PATCH 04/14] fsm_recurring: add team_id in tree / filter --- fieldservice_recurring/views/fsm_recurring.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fieldservice_recurring/views/fsm_recurring.xml b/fieldservice_recurring/views/fsm_recurring.xml index b49238250c..506bee61e3 100644 --- a/fieldservice_recurring/views/fsm_recurring.xml +++ b/fieldservice_recurring/views/fsm_recurring.xml @@ -14,6 +14,7 @@ + @@ -187,6 +188,12 @@ domain="" context="{'group_by':'state'}" /> + From 6820cf9e9817bdba59c87a0c552664003becbce2 Mon Sep 17 00:00:00 2001 From: Brian McMaster Date: Fri, 17 Jun 2022 14:01:11 -0400 Subject: [PATCH 05/14] [FIX] fieldservice_recurring: adapt access to ir.actions.* records --- fieldservice_recurring/models/fsm_order.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fieldservice_recurring/models/fsm_order.py b/fieldservice_recurring/models/fsm_order.py index 03a2a0aabe..867d753b7a 100644 --- a/fieldservice_recurring/models/fsm_order.py +++ b/fieldservice_recurring/models/fsm_order.py @@ -30,7 +30,9 @@ def create(self, vals_list): return super().create(vals_list) def action_view_fsm_recurring(self): - action = self.env.ref("fieldservice_recurring.action_fsm_recurring").read()[0] + action = self.env["ir.actions.act_window"]._for_xml_id( + "fieldservice_recurring.action_fsm_recurring" + ) action["views"] = [ (self.env.ref("fieldservice_recurring.fsm_recurring_form_view").id, "form") ] From e377321ab1af8932c272497965e36ffff898f5ce Mon Sep 17 00:00:00 2001 From: Florian Mounier Date: Wed, 21 Sep 2022 10:26:25 +0200 Subject: [PATCH 06/14] [FIX] fieldservice_recurring: Default company Use current company as default instead of user company. --- fieldservice_recurring/models/fsm_recurring.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fieldservice_recurring/models/fsm_recurring.py b/fieldservice_recurring/models/fsm_recurring.py index 1a78b2a178..3eca3794a7 100644 --- a/fieldservice_recurring/models/fsm_recurring.py +++ b/fieldservice_recurring/models/fsm_recurring.py @@ -62,7 +62,7 @@ def _default_team_id(self): help="This is the order template that will be recurring", ) company_id = fields.Many2one( - "res.company", "Company", default=lambda self: self.env.user.company_id + "res.company", "Company", default=lambda self: self.env.company ) fsm_order_ids = fields.One2many( "fsm.order", "fsm_recurring_id", string="Orders", copy=False From 5cd348fac37c925732c418982f6bead347b8c2ca Mon Sep 17 00:00:00 2001 From: Florian Mounier Date: Mon, 3 Oct 2022 17:38:11 +0200 Subject: [PATCH 07/14] [IMP] fieldservice_recurring: Use location_id tz Use location_id timezone prioritarily if set when generating fsm orders for a location --- fieldservice_recurring/models/fsm_frequency.py | 6 +++--- fieldservice_recurring/models/fsm_frequency_set.py | 6 +++--- fieldservice_recurring/models/fsm_recurring.py | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/fieldservice_recurring/models/fsm_frequency.py b/fieldservice_recurring/models/fsm_frequency.py index 954e1fe462..9e03cd7cf7 100644 --- a/fieldservice_recurring/models/fsm_frequency.py +++ b/fieldservice_recurring/models/fsm_frequency.py @@ -115,12 +115,12 @@ def _check_month_day(self): if not (1 <= rec.month_day <= 31): raise UserError(_("'Day of Month must be between 1 and 31")) - def _get_rrule(self, dtstart=None, until=None): + def _get_rrule(self, dtstart=None, until=None, tz=None): self.ensure_one() freq = FREQUENCIES[self.interval_type] # localize dtstart and until to user timezone - tz = ( - pytz.timezone(self._context.get("tz", None) or self.env.user.tz) or pytz.UTC + tz = pytz.timezone( + tz or self._context.get("tz", None) or self.env.user.tz or "UTC" ) if dtstart: diff --git a/fieldservice_recurring/models/fsm_frequency_set.py b/fieldservice_recurring/models/fsm_frequency_set.py index dfa8425f20..9b9e159356 100644 --- a/fieldservice_recurring/models/fsm_frequency_set.py +++ b/fieldservice_recurring/models/fsm_frequency_set.py @@ -36,12 +36,12 @@ class FSMFrequencySet(models.Model): that an event can be done""", ) - def _get_rruleset(self, dtstart=None, until=None): + def _get_rruleset(self, dtstart=None, until=None, tz=None): self.ensure_one() rset = rruleset() for rule in self.fsm_frequency_ids: if not rule.is_exclusive: - rset.rrule(rule._get_rrule(dtstart, until)) + rset.rrule(rule._get_rrule(dtstart, until, tz)) else: - rset.exrule(rule._get_rrule(dtstart)) + rset.exrule(rule._get_rrule(dtstart, tz=tz)) return rset diff --git a/fieldservice_recurring/models/fsm_recurring.py b/fieldservice_recurring/models/fsm_recurring.py index 3eca3794a7..564a6d34ad 100644 --- a/fieldservice_recurring/models/fsm_recurring.py +++ b/fieldservice_recurring/models/fsm_recurring.py @@ -167,7 +167,7 @@ def _get_rruleset(self): thru_date = request_thru_date # use variables to calulate and return the rruleset object ruleset = self.fsm_frequency_set_id._get_rruleset( - dtstart=next_date, until=thru_date + dtstart=next_date, until=thru_date, tz=self.location_id.tz ) return ruleset @@ -265,7 +265,7 @@ def _cron_manage_expiration(self): if rec.max_orders > 0: orders_in_30 = rec.fsm_order_count orders_in_30 += rec.fsm_frequency_set_id._get_rruleset( - until=expire_date + until=expire_date, tz=rec.location_id.tz ).count() if orders_in_30 >= rec.max_orders: to_renew += rec From 2a2d20d05480474e2902bea7c6249ba853fe040d Mon Sep 17 00:00:00 2001 From: hparfr Date: Fri, 17 Jun 2022 16:42:48 +0200 Subject: [PATCH 08/14] fieldservice_recurring: add recurring in team's dashboard --- fieldservice_recurring/__manifest__.py | 1 + fieldservice_recurring/models/__init__.py | 1 + fieldservice_recurring/models/fsm_team.py | 25 +++++++++++ fieldservice_recurring/views/fsm_team.xml | 55 +++++++++++++++++++++++ 4 files changed, 82 insertions(+) create mode 100644 fieldservice_recurring/models/fsm_team.py create mode 100644 fieldservice_recurring/views/fsm_team.xml diff --git a/fieldservice_recurring/__manifest__.py b/fieldservice_recurring/__manifest__.py index 6982ef81ee..8474cc3eb9 100644 --- a/fieldservice_recurring/__manifest__.py +++ b/fieldservice_recurring/__manifest__.py @@ -21,6 +21,7 @@ "views/fsm_order.xml", "views/fsm_recurring_template.xml", "views/fsm_recurring.xml", + "views/fsm_team.xml", "data/recurring_cron.xml", ], "demo": [ diff --git a/fieldservice_recurring/models/__init__.py b/fieldservice_recurring/models/__init__.py index 9d2cd7b25b..35c9f9fbaf 100644 --- a/fieldservice_recurring/models/__init__.py +++ b/fieldservice_recurring/models/__init__.py @@ -7,4 +7,5 @@ fsm_frequency, fsm_recurring_template, fsm_recurring, + fsm_team, ) diff --git a/fieldservice_recurring/models/fsm_team.py b/fieldservice_recurring/models/fsm_team.py new file mode 100644 index 0000000000..988db6cffa --- /dev/null +++ b/fieldservice_recurring/models/fsm_team.py @@ -0,0 +1,25 @@ +# Copyright (C) 2022 Raphaël Reverdy (Akretion) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class FSMTeam(models.Model): + _inherit = "fsm.team" + + def _compute_recurring_draft_count(self): + order_data = self.env["fsm.recurring"].read_group( + [ + ("team_id", "in", self.ids), + ("state", "=", "draft"), + ], + ["team_id"], + ["team_id"], + ) + result = {data["team_id"][0]: int(data["team_id_count"]) for data in order_data} + for team in self: + team.recurring_draft_count = result.get(team.id, 0) + + recurring_draft_count = fields.Integer( + compute="_compute_recurring_draft_count", string="Recurring in draft" + ) diff --git a/fieldservice_recurring/views/fsm_team.xml b/fieldservice_recurring/views/fsm_team.xml new file mode 100644 index 0000000000..50105c1350 --- /dev/null +++ b/fieldservice_recurring/views/fsm_team.xml @@ -0,0 +1,55 @@ + + + Recurring Orders + fsm.recurring + {} + tree,form + [('team_id', '=', active_id)] + + + fsm.team + + + + + + +
+ + +
+
+ +
+ +
+
+
+
+
+
+
+
From cb5f881841c51ae8cf7b1135ef28d032f83a4f5d Mon Sep 17 00:00:00 2001 From: BojanOD Date: Fri, 25 Nov 2022 16:44:48 +0100 Subject: [PATCH 09/14] [14.0][ADD] Equipment field to the `fsm.recurring` model --- fieldservice_recurring/models/fsm_recurring.py | 2 ++ fieldservice_recurring/tests/test_fsm_recurring.py | 3 +++ fieldservice_recurring/views/fsm_recurring.xml | 1 + 3 files changed, 6 insertions(+) diff --git a/fieldservice_recurring/models/fsm_recurring.py b/fieldservice_recurring/models/fsm_recurring.py index 564a6d34ad..f84d15fa28 100644 --- a/fieldservice_recurring/models/fsm_recurring.py +++ b/fieldservice_recurring/models/fsm_recurring.py @@ -79,6 +79,7 @@ def _default_team_id(self): person_id = fields.Many2one( "fsm.person", string="Assigned To", index=True, tracking=True ) + equipment_ids = fields.Many2many("fsm.equipment") @api.depends("fsm_order_ids") def _compute_order_count(self): @@ -191,6 +192,7 @@ def _prepare_order_values(self, date=None): "category_ids": [(6, False, self.fsm_order_template_id.category_ids.ids)], "company_id": self.company_id.id, "person_id": self.person_id.id, + "equipment_ids": [(6, 0, self.equipment_ids.ids)], } def _create_order(self, date): diff --git a/fieldservice_recurring/tests/test_fsm_recurring.py b/fieldservice_recurring/tests/test_fsm_recurring.py index 7b1f83dbad..89a8211bca 100644 --- a/fieldservice_recurring/tests/test_fsm_recurring.py +++ b/fieldservice_recurring/tests/test_fsm_recurring.py @@ -15,6 +15,7 @@ class FSMRecurringCase(TransactionCase): @classmethod def setUpClass(cls): super(FSMRecurringCase, cls).setUpClass() + cls.Equipment = cls.env["fsm.equipment"] cls.Recurring = cls.env["fsm.recurring"] cls.Frequency = cls.env["fsm.frequency"] cls.FrequencySet = cls.env["fsm.frequency.set"] @@ -54,6 +55,7 @@ def setUpClass(cls): cls.fsm_recurring_template = cls.env["fsm.recurring.template"].create( {"name": "Test Template"} ) + cls.test_equipment = cls.Equipment.create({"name": "Equipment"}) def test_cron_generate_orders_rule1(self): """Test recurring order with following rule, @@ -100,6 +102,7 @@ def test_cron_generate_orders_rule1(self): "fsm_frequency_set_id": fr_set.id, "location_id": self.test_location.id, "start_date": fields.Datetime.now().replace(hour=12), + "equipment_ids": [(6, 0, [self.test_equipment.id])], } ) test_recurring = self.Recurring.create( diff --git a/fieldservice_recurring/views/fsm_recurring.xml b/fieldservice_recurring/views/fsm_recurring.xml index 506bee61e3..77e4928606 100644 --- a/fieldservice_recurring/views/fsm_recurring.xml +++ b/fieldservice_recurring/views/fsm_recurring.xml @@ -79,6 +79,7 @@ groups="fieldservice.group_fsm_team" /> + Date: Wed, 4 Jan 2023 15:58:10 +0100 Subject: [PATCH 10/14] [14.0][FIX]fieldservice_recurring: fix security for group_fsm_user_own --- fieldservice_recurring/views/fsm_order.xml | 4 ++++ fieldservice_recurring/views/fsm_team.xml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/fieldservice_recurring/views/fsm_order.xml b/fieldservice_recurring/views/fsm_order.xml index 468c73dd46..91b01fd797 100644 --- a/fieldservice_recurring/views/fsm_order.xml +++ b/fieldservice_recurring/views/fsm_order.xml @@ -2,6 +2,10 @@ fsm.order +