diff --git a/connector_extension/models/backend/backend.py b/connector_extension/models/backend/backend.py index 03b25354a..d5ff6f3a9 100644 --- a/connector_extension/models/backend/backend.py +++ b/connector_extension/models/backend/backend.py @@ -3,6 +3,7 @@ # Copyright 2025 NuoBiT Solutions SL - Deniz Gallo # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html) import logging +from contextlib import contextmanager import pytz @@ -45,6 +46,13 @@ def _select_state(self): default=lambda self: self.env.company, string="Company", ) + user_id = fields.Many2one( + comodel_name="res.users", + string="User", + help="User used as the default responsible for records created by " + "this backend. If set, all operations (imports, exports, jobs) " + "will run under this user context.", + ) lang_ids = fields.Many2many( comodel_name="res.lang", @@ -70,7 +78,21 @@ def _select_state(self): string="Timezone", required=True, default=lambda self: self._context.get("tz") or self.env.user.tz or "UTC", - help="This field is used to define in which timezone the backend will work.", + help="The timezone of the business data in the external system. " + "All imported datetime values are assumed to be in this timezone " + "and will be converted to UTC for storage in Odoo. " + "On export, Odoo UTC values are converted back to this timezone.", + ) + server_tz = fields.Selection( + _tz_get, + string="Server Timezone", + required=True, + default=lambda self: self._context.get("tz") or self.env.user.tz or "UTC", + help="The timezone of the external system's server. " + "Used for technical timestamps managed by the server itself " + "(e.g. modification dates, audit trails) which may differ from " + "the business data timezone when the server is hosted in a " + "different region.", ) chunk_size = fields.Integer( @@ -113,18 +135,35 @@ def button_reset_to_draft(self): self.ensure_one() self.write({"state": "draft", "version": False}) + @staticmethod + def _convert_tz(dt_naive, from_tz, to_tz): + dt = pytz.timezone(from_tz).localize(dt_naive) + dt = dt.astimezone(pytz.timezone(to_tz)) + return dt.replace(tzinfo=None) + def tz_to_utc(self, datetime_local_naive): - datetime_local = pytz.timezone(self.tz).localize(datetime_local_naive) - datetime_utc = datetime_local.astimezone(pytz.utc) - datetime_utc_naive = datetime_utc.replace(tzinfo=None) - return datetime_utc_naive + return self._convert_tz(datetime_local_naive, self.tz, "UTC") + + def utc_to_local(self, datetime_utc_naive): + return self._convert_tz(datetime_utc_naive, "UTC", self.tz) + # Deprecated: use utc_to_local instead def tz_to_local(self, datetime_utc_naive): - local_tz = pytz.timezone(self.tz) - datetime_utc = pytz.utc.localize(datetime_utc_naive) - datetime_local = datetime_utc.astimezone(local_tz) - datetime_local_naive = datetime_local.replace(tzinfo=None) - return datetime_local_naive + return self.utc_to_local(datetime_utc_naive) + + def server_tz_to_utc(self, datetime_server_naive): + return self._convert_tz(datetime_server_naive, self.server_tz, "UTC") + + def utc_to_server_tz(self, datetime_utc_naive): + return self._convert_tz(datetime_utc_naive, "UTC", self.server_tz) + + @contextmanager + def work_on(self, model_name, **kwargs): + backend = self + if self.user_id: + backend = self.with_user(self.user_id) + with super(ConnectorBackend, backend).work_on(model_name, **kwargs) as work: + yield work # Scheduler methods @api.model