From 8234bb9e0d2981ac07cc96dad8cf56619d1a3fc7 Mon Sep 17 00:00:00 2001 From: "raimundops.jr" Date: Tue, 16 Sep 2025 15:43:54 +0000 Subject: [PATCH] [18.0][ADD]fleet_vehicle_assignation_log_datetime: add new module --- .../README.rst | 77 ++++ .../__init__.py | 4 + .../__manifest__.py | 19 + .../models/__init__.py | 5 + .../models/fleet_vehicle.py | 57 +++ .../models/fleet_vehicle_assignation_log.py | 94 ++++ .../pyproject.toml | 3 + .../readme/CONTRIBUTORS.md | 1 + .../readme/DESCRIPTION.md | 1 + .../static/description/index.html | 424 ++++++++++++++++++ .../tests/__init__.py | 4 + ..._fleet_vehicle_assignation_log_datetime.py | 165 +++++++ .../fleet_vehicle_assignation_log_views.xml | 53 +++ 13 files changed, 907 insertions(+) create mode 100644 fleet_vehicle_assignation_log_datetime/README.rst create mode 100644 fleet_vehicle_assignation_log_datetime/__init__.py create mode 100644 fleet_vehicle_assignation_log_datetime/__manifest__.py create mode 100644 fleet_vehicle_assignation_log_datetime/models/__init__.py create mode 100644 fleet_vehicle_assignation_log_datetime/models/fleet_vehicle.py create mode 100644 fleet_vehicle_assignation_log_datetime/models/fleet_vehicle_assignation_log.py create mode 100644 fleet_vehicle_assignation_log_datetime/pyproject.toml create mode 100644 fleet_vehicle_assignation_log_datetime/readme/CONTRIBUTORS.md create mode 100644 fleet_vehicle_assignation_log_datetime/readme/DESCRIPTION.md create mode 100644 fleet_vehicle_assignation_log_datetime/static/description/index.html create mode 100644 fleet_vehicle_assignation_log_datetime/tests/__init__.py create mode 100644 fleet_vehicle_assignation_log_datetime/tests/test_fleet_vehicle_assignation_log_datetime.py create mode 100644 fleet_vehicle_assignation_log_datetime/views/fleet_vehicle_assignation_log_views.xml diff --git a/fleet_vehicle_assignation_log_datetime/README.rst b/fleet_vehicle_assignation_log_datetime/README.rst new file mode 100644 index 00000000..8667c5c2 --- /dev/null +++ b/fleet_vehicle_assignation_log_datetime/README.rst @@ -0,0 +1,77 @@ +====================================== +Fleet Vehicle Assignation Log Datetime +====================================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:db5d90474e51304940aeb5b0e2bb4d457e3fe871d38183de4db8de22dd0a46f8 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Ffleet-lightgray.png?logo=github + :target: https://github.com/OCA/fleet/tree/18.0/fleet_vehicle_assignation_log_datetime + :alt: OCA/fleet +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/fleet-18-0/fleet-18-0-fleet_vehicle_assignation_log_datetime + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/fleet&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module extends the functionality of the fleet management by adding +a datetime field for driver history logs. + +**Table of contents** + +.. contents:: + :local: + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Raimundo Pereira da Silva Junior + +Contributors +------------ + +- Raimundo Pereira da Silva Junior raimundojunior.silva@gmail.com + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/fleet `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/fleet_vehicle_assignation_log_datetime/__init__.py b/fleet_vehicle_assignation_log_datetime/__init__.py new file mode 100644 index 00000000..9c6cc33a --- /dev/null +++ b/fleet_vehicle_assignation_log_datetime/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2025 Raimundo Pereira da Silva Junior, Odoo Community Association (OCA) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import models diff --git a/fleet_vehicle_assignation_log_datetime/__manifest__.py b/fleet_vehicle_assignation_log_datetime/__manifest__.py new file mode 100644 index 00000000..a2b37a11 --- /dev/null +++ b/fleet_vehicle_assignation_log_datetime/__manifest__.py @@ -0,0 +1,19 @@ +# Copyright 2025 Raimundo Pereira da Silva Junior, Odoo Community Association (OCA) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +{ + "name": "Fleet Vehicle Assignation Log Datetime", + "version": "18.0.1.0.0", + "category": "Fleet", + "summary": "Adds datetime precision to driver assignation logs.", + "author": "Raimundo Pereira da Silva Junior, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/fleet", + "license": "AGPL-3", + "depends": [ + "fleet", + "fleet_vehicle_history_date_end", + ], + "data": [ + "views/fleet_vehicle_assignation_log_views.xml", + ], + "installable": True, +} diff --git a/fleet_vehicle_assignation_log_datetime/models/__init__.py b/fleet_vehicle_assignation_log_datetime/models/__init__.py new file mode 100644 index 00000000..289a22e9 --- /dev/null +++ b/fleet_vehicle_assignation_log_datetime/models/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025 Raimundo Pereira da Silva Junior, Odoo Community Association (OCA) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import fleet_vehicle_assignation_log +from . import fleet_vehicle diff --git a/fleet_vehicle_assignation_log_datetime/models/fleet_vehicle.py b/fleet_vehicle_assignation_log_datetime/models/fleet_vehicle.py new file mode 100644 index 00000000..f5c3e0f6 --- /dev/null +++ b/fleet_vehicle_assignation_log_datetime/models/fleet_vehicle.py @@ -0,0 +1,57 @@ +# Copyright 2025 Raimundo Pereira da Silva Junior, Odoo Community Association (OCA) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class FleetVehicle(models.Model): + _inherit = "fleet.vehicle" + + def write(self, vals): + """ + Overrides write to capture the precise time of a driver change. + + 1. Sets the new assignment's start time to the exact moment of the change. + 2. Finds the previous assignment and sets its end time to match the new + start time. + """ + assignment_time = fields.Datetime.now() + + previous_logs = {} + if "driver_id" in vals and vals.get("driver_id"): + for vehicle in self: + previous_log = self.env["fleet.vehicle.assignation.log"].search( + [ + ("vehicle_id", "=", vehicle.id), + ("driver_id", "=", vehicle.driver_id.id), + ("date_end", "=", False), + ], + order="date_start desc, id desc", + limit=1, + ) + previous_logs[vehicle.id] = previous_log + + res = super().write(vals) + + if "driver_id" in vals and vals.get("driver_id"): + for vehicle in self: + new_log = self.env["fleet.vehicle.assignation.log"].search( + [ + ("vehicle_id", "=", vehicle.id), + ("driver_id", "=", vals["driver_id"]), + ("date_end", "=", False), + ], + order="date_start desc, id desc", + limit=1, + ) + + if new_log: + # Update the new log with the precise start time. + new_log.datetime_start = assignment_time + + # Update the previous log with the precise end time. + previous_log = previous_logs.get(vehicle.id) + if previous_log: + previous_log.datetime_end = assignment_time + + return res diff --git a/fleet_vehicle_assignation_log_datetime/models/fleet_vehicle_assignation_log.py b/fleet_vehicle_assignation_log_datetime/models/fleet_vehicle_assignation_log.py new file mode 100644 index 00000000..0c7df899 --- /dev/null +++ b/fleet_vehicle_assignation_log_datetime/models/fleet_vehicle_assignation_log.py @@ -0,0 +1,94 @@ +# Copyright 2025 Raimundo Pereira da Silva Junior, Odoo Community Association (OCA) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class FleetVehicleAssignationLog(models.Model): + # Inherit from mail.thread and mail.activity.mixin to enable tracking + _inherit = ["fleet.vehicle.assignation.log", "mail.thread", "mail.activity.mixin"] + _name = "fleet.vehicle.assignation.log" # Explicitly redeclare _name + + datetime_start = fields.Datetime( + string="Start Datetime", + help="Precise start date and time of the driver assignment.", + required=True, + default=fields.Datetime.now, + tracking=True, # Tracking is now valid + ) + datetime_end = fields.Datetime( + string="End Datetime", + help="Precise end date and time of the driver assignment.", + tracking=True, # Tracking is now valid + ) + + @api.model + def _synchronize_datetimes(self, vals): + """ + Helper method to synchronize date and datetime fields. + Ensures that if a date is provided without a datetime, the datetime + is populated accordingly. This is used in create() and write(). + """ + # Synchronize start date + if vals.get("date_start") and not vals.get("datetime_start"): + vals["datetime_start"] = fields.Datetime.to_datetime(vals["date_start"]) + + # Synchronize end date + if vals.get("date_end") and not vals.get("datetime_end"): + date_end = fields.Date.to_date(vals["date_end"]) + vals["datetime_end"] = fields.Datetime.to_datetime(date_end).replace( + hour=23, minute=59, second=59 + ) + return vals + + @api.model_create_multi + def create(self, vals_list): + """ + On create, ensure datetimes are synchronized if only dates are provided. + """ + for vals in vals_list: + vals = self._synchronize_datetimes(vals) + return super().create(vals_list) + + def write(self, vals): + """ + On write, ensure datetimes are synchronized. This is crucial for + compatibility with modules that set `date_end` programmatically. + """ + vals = self._synchronize_datetimes(vals) + return super().write(vals) + + # --- Onchange methods are still useful for UI responsiveness --- + + @api.onchange("datetime_start") + def _onchange_datetime_start(self): + """Synchronizes the date field from the datetime field.""" + if self.datetime_start: + self.date_start = self.datetime_start.date() + else: + self.date_start = False + + @api.onchange("datetime_end") + def _onchange_datetime_end(self): + """Synchronizes the date field from the datetime field.""" + if self.datetime_end: + self.date_end = self.datetime_end.date() + else: + self.date_end = False + + @api.onchange("date_start") + def _onchange_date_start(self): + """Synchronizes the datetime field from the date field in the UI.""" + if self.date_start and not self.datetime_start: + self.datetime_start = fields.Datetime.to_datetime(self.date_start) + + @api.onchange("date_end") + def _onchange_date_end(self): + """Synchronizes the datetime field from the date field in the UI.""" + if self.date_end: + if not self.datetime_end or self.datetime_end.date() != self.date_end: + self.datetime_end = fields.Datetime.to_datetime(self.date_end).replace( + hour=23, minute=59, second=59 + ) + else: + self.datetime_end = False diff --git a/fleet_vehicle_assignation_log_datetime/pyproject.toml b/fleet_vehicle_assignation_log_datetime/pyproject.toml new file mode 100644 index 00000000..4231d0cc --- /dev/null +++ b/fleet_vehicle_assignation_log_datetime/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/fleet_vehicle_assignation_log_datetime/readme/CONTRIBUTORS.md b/fleet_vehicle_assignation_log_datetime/readme/CONTRIBUTORS.md new file mode 100644 index 00000000..69301ea2 --- /dev/null +++ b/fleet_vehicle_assignation_log_datetime/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +* Raimundo Pereira da Silva Junior diff --git a/fleet_vehicle_assignation_log_datetime/readme/DESCRIPTION.md b/fleet_vehicle_assignation_log_datetime/readme/DESCRIPTION.md new file mode 100644 index 00000000..fd99b52d --- /dev/null +++ b/fleet_vehicle_assignation_log_datetime/readme/DESCRIPTION.md @@ -0,0 +1 @@ +This module extends the functionality of the fleet management by adding a datetime field for driver history logs. diff --git a/fleet_vehicle_assignation_log_datetime/static/description/index.html b/fleet_vehicle_assignation_log_datetime/static/description/index.html new file mode 100644 index 00000000..6b60a3ef --- /dev/null +++ b/fleet_vehicle_assignation_log_datetime/static/description/index.html @@ -0,0 +1,424 @@ + + + + + +Fleet Vehicle Assignation Log Datetime + + + +
+

Fleet Vehicle Assignation Log Datetime

+ + +

Beta License: AGPL-3 OCA/fleet Translate me on Weblate Try me on Runboat

+

This module extends the functionality of the fleet management by adding +a datetime field for driver history logs.

+

Table of contents

+ +
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Raimundo Pereira da Silva Junior
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/fleet project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/fleet_vehicle_assignation_log_datetime/tests/__init__.py b/fleet_vehicle_assignation_log_datetime/tests/__init__.py new file mode 100644 index 00000000..1c78898c --- /dev/null +++ b/fleet_vehicle_assignation_log_datetime/tests/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2025 Raimundo Pereira da Silva Junior, Odoo Community Association (OCA) +# License AG-PL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import test_fleet_vehicle_assignation_log_datetime diff --git a/fleet_vehicle_assignation_log_datetime/tests/test_fleet_vehicle_assignation_log_datetime.py b/fleet_vehicle_assignation_log_datetime/tests/test_fleet_vehicle_assignation_log_datetime.py new file mode 100644 index 00000000..66c68483 --- /dev/null +++ b/fleet_vehicle_assignation_log_datetime/tests/test_fleet_vehicle_assignation_log_datetime.py @@ -0,0 +1,165 @@ +# Copyright 2025 Raimundo Pereira da Silva Junior, Odoo Community Association (OCA) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from unittest.mock import patch + +from odoo import fields + +# Corrected import for Odoo 18 - Form is no longer needed here +from odoo.tests import TransactionCase + + +class TestFleetVehicleAssignationLogDatetime(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + brand = cls.env["fleet.vehicle.model.brand"].create({"name": "Test Brand"}) + model = cls.env["fleet.vehicle.model"].create( + {"brand_id": brand.id, "name": "Test Model"} + ) + cls.vehicle = cls.env["fleet.vehicle"].create( + { + "model_id": model.id, + "license_plate": "TEST-123", + } + ) + cls.driver1 = cls.env["res.partner"].create({"name": "Test Driver 1"}) + cls.driver2 = cls.env["res.partner"].create({"name": "Test Driver 2"}) + + # ... (os testes 01, 02, 03 e 04 permanecem exatamente os mesmos) ... + def test_01_driver_change_updates_datetimes(self): + """ + Test that changing a vehicle's driver correctly sets the start and end + datetimes on the assignation logs. + """ + self.vehicle.write({"driver_id": self.driver1.id}) + log1 = self.env["fleet.vehicle.assignation.log"].search( + [("vehicle_id", "=", self.vehicle.id), ("driver_id", "=", self.driver1.id)] + ) + self.assertEqual(len(log1), 1) + self.assertTrue(log1.datetime_start) + self.assertFalse(log1.datetime_end) + self.vehicle.write({"driver_id": self.driver2.id}) + log2 = self.env["fleet.vehicle.assignation.log"].search( + [("vehicle_id", "=", self.vehicle.id), ("driver_id", "=", self.driver2.id)] + ) + self.assertEqual(len(log2), 1) + self.assertTrue(log2.datetime_start) + self.assertFalse(log2.datetime_end) + log1_updated = self.env["fleet.vehicle.assignation.log"].browse(log1.id) + self.assertTrue(log1_updated.datetime_end) + self.assertEqual(log1_updated.datetime_end, log2.datetime_start) + + def test_02_create_and_write_log_directly(self): + """ + Test that creating/writing logs directly with date fields correctly + populates the datetime fields. + """ + log = self.env["fleet.vehicle.assignation.log"].create( + { + "vehicle_id": self.vehicle.id, + "driver_id": self.driver1.id, + "date_start": fields.Date.from_string("2025-01-15"), + } + ) + expected_datetime_start = fields.Datetime.from_string("2025-01-15 00:00:00") + self.assertEqual(log.datetime_start, expected_datetime_start) + log.write({"date_end": fields.Date.from_string("2025-01-20")}) + expected_datetime_end = fields.Datetime.from_string("2025-01-20 23:59:59") + self.assertEqual(log.datetime_end, expected_datetime_end) + + def test_03_onchange_and_sync_logic(self): + """ + Test all synchronization logic (onchange and create/write) in the + assignation log model to ensure full test coverage. + """ + log_model = self.env["fleet.vehicle.assignation.log"] + log = log_model.new({}) + log.datetime_start = fields.Datetime.from_string("2025-02-01 10:00:00") + log._onchange_datetime_start() + self.assertEqual(log.date_start, fields.Date.from_string("2025-02-01")) + log.datetime_start = False + log._onchange_datetime_start() + self.assertFalse(log.date_start) + log.datetime_end = fields.Datetime.from_string("2025-02-10 18:00:00") + log._onchange_datetime_end() + self.assertEqual(log.date_end, fields.Date.from_string("2025-02-10")) + log.datetime_end = False + log._onchange_datetime_end() + self.assertFalse(log.date_end) + log.datetime_start = False + log.date_start = fields.Date.from_string("2025-03-01") + log._onchange_date_start() + self.assertEqual( + log.datetime_start, fields.Datetime.from_string("2025-03-01 00:00:00") + ) + log.datetime_end = False + log.date_end = fields.Date.from_string("2025-03-15") + log._onchange_date_end() + self.assertEqual( + log.datetime_end, fields.Datetime.from_string("2025-03-15 23:59:59") + ) + specific_time = fields.Datetime.from_string("2025-03-15 11:00:00") + log.datetime_end = specific_time + log._onchange_date_end() + self.assertEqual(log.datetime_end, specific_time) + log.date_end = False + log._onchange_date_end() + self.assertFalse(log.datetime_end) + log_created = log_model.create( + { + "vehicle_id": self.vehicle.id, + "driver_id": self.driver2.id, + "date_start": fields.Date.from_string("2025-04-01"), + "date_end": fields.Date.from_string("2025-04-10"), + } + ) + self.assertEqual( + log_created.datetime_start, + fields.Datetime.from_string("2025-04-01 00:00:00"), + ) + self.assertEqual( + log_created.datetime_end, + fields.Datetime.from_string("2025-04-10 23:59:59"), + ) + + def test_04_coverage_edge_cases(self): + """ + Test edge cases specifically to increase test coverage on conditional + branches. + """ + self.vehicle.write({"driver_id": self.driver1.id}) + log_count_before = self.env["fleet.vehicle.assignation.log"].search_count( + [("vehicle_id", "=", self.vehicle.id)] + ) + self.vehicle.write({"license_plate": "NEW-PLATE-456"}) + log_count_after = self.env["fleet.vehicle.assignation.log"].search_count( + [("vehicle_id", "=", self.vehicle.id)] + ) + self.assertEqual(log_count_before, log_count_after) + log_model = self.env["fleet.vehicle.assignation.log"] + log = log_model.new({}) + log.datetime_start = False + log.date_start = fields.Date.from_string("2025-05-01") + log._onchange_date_start() + self.assertEqual( + log.datetime_start, + fields.Datetime.from_string("2025-05-01 00:00:00"), + ) + + def test_05_if_new_log_coverage(self): + """ + Test the `if new_log:` branch in `fleet.vehicle.py` by mocking + the log creation to simulate a failure. + """ + log_model = self.env["fleet.vehicle.assignation.log"] + # Use patch.object on the already loaded model class + with patch.object( + type(log_model), "create", return_value=log_model.browse([]) + ) as mock_create: + # This write will trigger our overridden method, but the mock + # will prevent the creation of the log. + self.vehicle.write({"driver_id": self.driver2.id}) + # The subsequent search for `new_log` will be empty, covering the + # 'else' path of the `if new_log:` condition. + mock_create.assert_called() diff --git a/fleet_vehicle_assignation_log_datetime/views/fleet_vehicle_assignation_log_views.xml b/fleet_vehicle_assignation_log_datetime/views/fleet_vehicle_assignation_log_views.xml new file mode 100644 index 00000000..14375d1d --- /dev/null +++ b/fleet_vehicle_assignation_log_datetime/views/fleet_vehicle_assignation_log_views.xml @@ -0,0 +1,53 @@ + + + + + + fleet.vehicle.assignation.log.form.datetime + fleet.vehicle.assignation.log + +
+ + + + + + + + + + + +
+ + + +
+
+
+
+ + + fleet.vehicle.assignation.log.list.inherit.datetime + fleet.vehicle.assignation.log + + + + + + + True + + + + + + + True + + + +