From 58b4025068c0e145a68d8263cbaed17da9dba62f Mon Sep 17 00:00:00 2001 From: QuiJoQuim Date: Mon, 21 Oct 2024 03:37:23 +0200 Subject: [PATCH 1/3] [FIX] Modify how employee attedance is computed --- project_forecast_line/models/forecast_line.py | 6 +++--- project_forecast_line/models/hr_employee.py | 8 +++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/project_forecast_line/models/forecast_line.py b/project_forecast_line/models/forecast_line.py index debaebe098..c5ca631640 100644 --- a/project_forecast_line/models/forecast_line.py +++ b/project_forecast_line/models/forecast_line.py @@ -307,7 +307,7 @@ def _prepare_forecast_lines( resource = self.env["resource.resource"] calendar = self.env.company.resource_calendar_id for updates in self._split_per_period( - date_from, date_to, forecast_hours, unit_cost, resource, calendar + date_from, date_to, forecast_hours, unit_cost, resource, calendar, force_granularity= (res_model=='hr.employee') ): values = common_value_dict.copy() values.update(updates) @@ -332,7 +332,7 @@ def _compute_horizon(self, date_from, date_to): return horiz_date_from, horiz_date_to, date_to def _split_per_period( - self, date_from, date_to, forecast_hours, unit_cost, resource, calendar + self, date_from, date_to, forecast_hours, unit_cost, resource, calendar, force_granularity=False ): company = self.env.company granularity = company.forecast_line_granularity @@ -344,7 +344,7 @@ def _split_per_period( if horiz_date_to <= horiz_date_from: return whole_period_forecast = self._number_of_hours( - horiz_date_from, horiz_date_to, resource, calendar + horiz_date_from, horiz_date_to, resource, calendar, force_granularity=force_granularity ) if whole_period_forecast == 0: # the resource if completely off during the period -> we cannot diff --git a/project_forecast_line/models/hr_employee.py b/project_forecast_line/models/hr_employee.py index 7518fabd69..7d76cab4ce 100644 --- a/project_forecast_line/models/hr_employee.py +++ b/project_forecast_line/models/hr_employee.py @@ -114,10 +114,9 @@ def _update_forecast_lines(self): [ ("res_id", "in", self.ids), ("res_model", "=", self._name), - ("date_from", "<", today), ] ).unlink() - horizon_end = ForecastLine._company_horizon_end() + horiz_date_from, horiz_date_to, date_to = (ForecastLine._compute_horizon(today,ForecastLine._company_horizon_end())) for rec in self: ForecastLine = ForecastLine.with_company(rec.company_id) if rec.date_end: @@ -126,11 +125,10 @@ def _update_forecast_lines(self): [ ("res_id", "=", rec.id), ("res_model", "=", self._name), - ("date_to", ">=", date_end), ] ).unlink() else: - date_end = horizon_end - relativedelta(days=1) + date_end = date_to if leave_date_to is not None: date_end = min(leave_date_to, date_end) date_start = max(rec.date_start, today) @@ -140,7 +138,7 @@ def _update_forecast_lines(self): calendar = resource.calendar_id forecast = ForecastLine._number_of_hours( - date_start, date_end, resource, calendar, force_granularity=True + date_start, date_end - relativedelta(days=1), resource, calendar, force_granularity=False ) forecast_lines = ForecastLine.search( [ From 0b4eced183cae4d5c879e8cacd66bad786231940 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20L=C3=B3pez=20Ram=C3=ADrez?= Date: Tue, 14 Oct 2025 13:13:51 +0200 Subject: [PATCH 2/3] Fixed first tests --- project_forecast_line/models/hr_employee.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/project_forecast_line/models/hr_employee.py b/project_forecast_line/models/hr_employee.py index 7d76cab4ce..68f11e57dd 100644 --- a/project_forecast_line/models/hr_employee.py +++ b/project_forecast_line/models/hr_employee.py @@ -91,10 +91,10 @@ def create(self, vals_list): def write(self, values): res = super().write(values) - self._update_forecast_lines() + self._update_forecast_lines(force_granularity=True) return res - def _update_forecast_lines(self): + def _update_forecast_lines(self, force_granularity=False): today = fields.Date.context_today(self) leave_date_start = self.env.context.get("date_start") leave_date_to = self.env.context.get("date_to") @@ -128,7 +128,7 @@ def _update_forecast_lines(self): ] ).unlink() else: - date_end = date_to + date_end = date_to if leave_date_to is not None: date_end = min(leave_date_to, date_end) date_start = max(rec.date_start, today) @@ -138,7 +138,7 @@ def _update_forecast_lines(self): calendar = resource.calendar_id forecast = ForecastLine._number_of_hours( - date_start, date_end - relativedelta(days=1), resource, calendar, force_granularity=False + date_start, date_end - relativedelta(days=1), resource, calendar, force_granularity=force_granularity ) forecast_lines = ForecastLine.search( [ From 17401b7cb9c79759d056e374118045a62fd40348 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20L=C3=B3pez=20Ram=C3=ADrez?= Date: Tue, 21 Oct 2025 12:19:57 +0200 Subject: [PATCH 3/3] Reverts commit 58b4025068c0e145a68d8263cbaed17da9dba62f and fix tests --- project_forecast_line/models/forecast_line.py | 6 ++-- project_forecast_line/models/hr_employee.py | 12 ++++---- project_forecast_line/models/project_task.py | 29 +++++++++++++++---- project_forecast_line/models/sale_order.py | 16 ++++++++++ .../tests/test_forecast_line.py | 16 +++++----- 5 files changed, 58 insertions(+), 21 deletions(-) diff --git a/project_forecast_line/models/forecast_line.py b/project_forecast_line/models/forecast_line.py index c5ca631640..debaebe098 100644 --- a/project_forecast_line/models/forecast_line.py +++ b/project_forecast_line/models/forecast_line.py @@ -307,7 +307,7 @@ def _prepare_forecast_lines( resource = self.env["resource.resource"] calendar = self.env.company.resource_calendar_id for updates in self._split_per_period( - date_from, date_to, forecast_hours, unit_cost, resource, calendar, force_granularity= (res_model=='hr.employee') + date_from, date_to, forecast_hours, unit_cost, resource, calendar ): values = common_value_dict.copy() values.update(updates) @@ -332,7 +332,7 @@ def _compute_horizon(self, date_from, date_to): return horiz_date_from, horiz_date_to, date_to def _split_per_period( - self, date_from, date_to, forecast_hours, unit_cost, resource, calendar, force_granularity=False + self, date_from, date_to, forecast_hours, unit_cost, resource, calendar ): company = self.env.company granularity = company.forecast_line_granularity @@ -344,7 +344,7 @@ def _split_per_period( if horiz_date_to <= horiz_date_from: return whole_period_forecast = self._number_of_hours( - horiz_date_from, horiz_date_to, resource, calendar, force_granularity=force_granularity + horiz_date_from, horiz_date_to, resource, calendar ) if whole_period_forecast == 0: # the resource if completely off during the period -> we cannot diff --git a/project_forecast_line/models/hr_employee.py b/project_forecast_line/models/hr_employee.py index 68f11e57dd..7518fabd69 100644 --- a/project_forecast_line/models/hr_employee.py +++ b/project_forecast_line/models/hr_employee.py @@ -91,10 +91,10 @@ def create(self, vals_list): def write(self, values): res = super().write(values) - self._update_forecast_lines(force_granularity=True) + self._update_forecast_lines() return res - def _update_forecast_lines(self, force_granularity=False): + def _update_forecast_lines(self): today = fields.Date.context_today(self) leave_date_start = self.env.context.get("date_start") leave_date_to = self.env.context.get("date_to") @@ -114,9 +114,10 @@ def _update_forecast_lines(self, force_granularity=False): [ ("res_id", "in", self.ids), ("res_model", "=", self._name), + ("date_from", "<", today), ] ).unlink() - horiz_date_from, horiz_date_to, date_to = (ForecastLine._compute_horizon(today,ForecastLine._company_horizon_end())) + horizon_end = ForecastLine._company_horizon_end() for rec in self: ForecastLine = ForecastLine.with_company(rec.company_id) if rec.date_end: @@ -125,10 +126,11 @@ def _update_forecast_lines(self, force_granularity=False): [ ("res_id", "=", rec.id), ("res_model", "=", self._name), + ("date_to", ">=", date_end), ] ).unlink() else: - date_end = date_to + date_end = horizon_end - relativedelta(days=1) if leave_date_to is not None: date_end = min(leave_date_to, date_end) date_start = max(rec.date_start, today) @@ -138,7 +140,7 @@ def _update_forecast_lines(self, force_granularity=False): calendar = resource.calendar_id forecast = ForecastLine._number_of_hours( - date_start, date_end - relativedelta(days=1), resource, calendar, force_granularity=force_granularity + date_start, date_end, resource, calendar, force_granularity=True ) forecast_lines = ForecastLine.search( [ diff --git a/project_forecast_line/models/project_task.py b/project_forecast_line/models/project_task.py index 16a9e7b05f..9cf7bb0276 100644 --- a/project_forecast_line/models/project_task.py +++ b/project_forecast_line/models/project_task.py @@ -30,7 +30,9 @@ def create(self, vals_list): if vals.get("planned_date_end"): vals["forecast_date_planned_end"] = vals["planned_date_end"] tasks = super().create(vals_list) - # tasks._update_forecast_lines() + for i,task in enumerate(tasks): + if set(vals_list[i]) & set(self._update_forecast_lines_trigger_fields()): + task._update_forecast_lines() return tasks def _update_forecast_lines_trigger_fields(self): @@ -59,15 +61,32 @@ def write(self, values): values["forecast_date_planned_start"] = values["planned_date_begin"] if "planned_date_end" in values: values["forecast_date_planned_end"] = values["planned_date_end"] - return super().write(values) - def _write(self, values): - res = super()._write(values) - if "forecast_recomputation_trigger" in values: + res = super().write(values) + if set(values.keys()) & { + # "sale_order_line_id", + "forecast_role_id", + "forecast_date_planned_start", + "forecast_date_planned_end", + # "remaining_hours", + "name", + # "planned_time", + "user_ids", + "project_id.stage_id", + "project_id.stage_id.forecast_line_type", + "planned_hours" + }: self._update_forecast_lines() elif "remaining_hours" in values: self._quick_update_forecast_lines() return res + def _write(self, values): + res = super()._write(values) + """if "forecast_recomputation_trigger" in values: + self._update_forecast_lines()""" + if "remaining_hours" in values: + self._quick_update_forecast_lines() + return res @api.onchange("user_ids") def onchange_user_ids(self): diff --git a/project_forecast_line/models/sale_order.py b/project_forecast_line/models/sale_order.py index 1ae499b33b..075e1c0fc2 100644 --- a/project_forecast_line/models/sale_order.py +++ b/project_forecast_line/models/sale_order.py @@ -30,4 +30,20 @@ def write(self, values): self.env["forecast.line"].sudo().search( [("sale_id", "in", self.ids)] ).write({"project_id": values["project_id"]}) + if self and "default_forecast_date_start" in values or "default_forecast_date_end" in values: + for sol in self.mapped("order_line"): + update_forecast = False + if not sol.forecast_date_start: + sol.write({ + "forecast_date_start": values.get( + "default_forecast_date_start" + )}) + update_forecast = True + if not sol.forecast_date_end: + sol.write({ + "forecast_date_end": values.get("default_forecast_date_end"), + }) + update_forecast = True + if update_forecast: + sol._update_forecast_lines() return res diff --git a/project_forecast_line/tests/test_forecast_line.py b/project_forecast_line/tests/test_forecast_line.py index 3e3e4fb1cc..69e4c74335 100644 --- a/project_forecast_line/tests/test_forecast_line.py +++ b/project_forecast_line/tests/test_forecast_line.py @@ -757,14 +757,14 @@ def test_task_forecast_lines_consolidated_forecast(self): @freeze_time("2022-01-01 12:00:00") def test_forecast_with_holidays(self): self.test_task_forecast_lines_consolidated_forecast() - with Form(self.env["hr.leave"]) as form: - form.employee_id = self.employee_consultant - form.holiday_status_id = self.env.ref("hr_holidays.holiday_status_unpaid") - form.request_date_from = "2022-02-14" - form.request_date_to = "2022-02-15" - form.request_hour_from = "8" - form.request_hour_to = "18" - leave_request = form.save() + leave_request = self.env["hr.leave"].create( + { + "employee_id": self.employee_consultant.id, + "holiday_status_id": self.env.ref("hr_holidays.holiday_status_unpaid").id, + "date_from": "2022-02-14 07:00:00", + "date_to": "2022-02-15 17:00:00", + } + ) # validating the leave request will recompute the forecast lines for # the employee capactities (actually delete the existing ones and # create new ones -> we check that the project task lines are