Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docsource/modules190-200.rst
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ Module coverage 19.0 -> 20.0
+---------------------------------------------------+----------------------+-------------------------------------------------+
| event_sms | | |
+---------------------------------------------------+----------------------+-------------------------------------------------+
| fleet | | |
| fleet |Done | |
+---------------------------------------------------+----------------------+-------------------------------------------------+
| |new| fleet_maintenance | | |
+---------------------------------------------------+----------------------+-------------------------------------------------+
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<record id="mail_act_fleet_contract_to_renew" model="mail.activity.type">
<field name="icon">directions_car</field>
</record>
</odoo>
9 changes: 9 additions & 0 deletions openupgrade_scripts/scripts/fleet/20.0.0.1/post-migration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copyright 2026 Don Kendall
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from openupgradelib import openupgrade


@openupgrade.migrate()
def migrate(env, version):
openupgrade.load_data(env, "fleet", "20.0.0.1/noupdate_changes.xml")
52 changes: 52 additions & 0 deletions openupgrade_scripts/scripts/fleet/20.0.0.1/pre-migration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Copyright 2026 Don Kendall
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from openupgradelib import openupgrade


def _rename_the_service_date(env):
"""A service log's `date` becomes `date_from`, and nothing says so.

20.0 gives a service record a span instead of a point: `date` is dropped,
`date_from` and `date_to` are added, and a CHECK enforces the order::

CHECK((date_to IS NULL) OR (date_from IS NOT NULL AND date_to >= date_from))

The analysis reports a DEL and two NEWs, which reads as three unrelated
changes. The field definitions say otherwise -- 20.0's `date_from` carries
19.0's `date` help text **unchanged**, "Date when the cost has been
executed", and the same `default=fields.Date.context_today`:

19.0 date = fields.Date(help='Date when the cost has been executed', ...)
20.0 date_from = fields.Date(string='Start of Service',
help='Date when the cost has been executed', ...)

and 20.0's own `_get_odometer`-adjacent code passes `date_from` wherever it
needs the service's date.

Because `date_from` carries that default, nothing about the upgrade looks
wrong without this: the column is created, every existing row is filled with
**the day the upgrade ran**, and every service in the vehicle's history
silently moves to today. The seed's six service logs hold six different
dates; all six come out identical.

Renaming in pre-migration carries the values and leaves `date_to` null,
which the CHECK allows.
"""
if openupgrade.column_exists(env.cr, "fleet_vehicle_log_services", "date"):
openupgrade.rename_fields(
env,
[
(
"fleet.vehicle.log.services",
"fleet_vehicle_log_services",
"date",
"date_from",
)
],
)


@openupgrade.migrate()
def migrate(env, version):
_rename_the_service_date(env)
105 changes: 105 additions & 0 deletions openupgrade_scripts/scripts/fleet/20.0.0.1/upgrade_analysis.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
---Models in module 'fleet'---
---Fields in module 'fleet'---
fleet / fleet.service.type / company_id (many2one) : NEW relation: res.company, hasdefault: default
fleet / fleet.vehicle / contract_state (selection) : selection_keys added: [done] (most likely nothing to do)
fleet / fleet.vehicle / horsepower_tax (float) : DEL
fleet / fleet.vehicle / plan_to_change_bike (boolean) : DEL
fleet / fleet.vehicle / plan_to_change_car (boolean) : DEL
fleet / fleet.vehicle / plan_to_change_vehicle (boolean): NEW
fleet / fleet.vehicle / transmission (selection) : selection_keys added: [semi_automatic] (most likely nothing to do)
fleet / fleet.vehicle.log.contract / state (selection) : selection_keys added: [done] (most likely nothing to do)
fleet / fleet.vehicle.log.services / date (date) : DEL
fleet / fleet.vehicle.log.services / date_from (date) : NEW hasdefault: default
fleet / fleet.vehicle.log.services / date_to (date) : NEW
fleet / fleet.vehicle.model / color (char) : DEL
fleet / fleet.vehicle.model / company_id (many2one) : NEW relation: res.company
fleet / fleet.vehicle.model / horsepower_tax (float) : DEL
fleet / fleet.vehicle.model / trailer_hook (boolean) : DEL
fleet / fleet.vehicle.model / transmission (selection) : selection_keys added: [semi_automatic] (most likely nothing to do)
fleet / fleet.vehicle.model / vehicle_type (selection) : selection_keys added: [other] (most likely nothing to do)
---XML records in module 'fleet'---
fleet.service.type: fleet.type_contract_leasing (noupdate switched)
fleet.service.type: fleet.type_contract_omnium (noupdate switched)
fleet.vehicle.state: fleet.fleet_vehicle_state_downgraded (noupdate switched)
fleet.vehicle.state: fleet.fleet_vehicle_state_new_request (noupdate switched)
fleet.vehicle.state: fleet.fleet_vehicle_state_registered (noupdate switched)
fleet.vehicle.state: fleet.fleet_vehicle_state_to_order (noupdate switched)
NEW ir.access: fleet.access_fleet_report_manager
NEW ir.access: fleet.access_fleet_vehicle_assignation_log_fleet_group_user
NEW ir.access: fleet.access_fleet_vehicle_odometer_report_manager
NEW ir.access: fleet.access_fleet_vehicle_send_mail
NEW ir.access: fleet.access_mail_activity_type_fleet_manager
NEW ir.access: fleet.fleet_rule_service_visibility_manager
NEW ir.access: fleet.fleet_rule_vehicle_visibility_user
NEW ir.access: fleet.fleet_service_type_access_right
NEW ir.access: fleet.fleet_service_type_access_right_user
NEW ir.access: fleet.fleet_vehicle_log_contract_access_right_user
NEW ir.access: fleet.fleet_vehicle_log_services_access_right_user
NEW ir.access: fleet.fleet_vehicle_model_access_right
NEW ir.access: fleet.fleet_vehicle_model_access_right_user
NEW ir.access: fleet.fleet_vehicle_model_brand_access_right
NEW ir.access: fleet.fleet_vehicle_model_brand_access_right_user
NEW ir.access: fleet.fleet_vehicle_model_brand_category_right_user
NEW ir.access: fleet.fleet_vehicle_model_category_access_right
NEW ir.access: fleet.fleet_vehicle_odometer_access_right
NEW ir.access: fleet.fleet_vehicle_state_access_right
NEW ir.access: fleet.fleet_vehicle_state_access_right_user
NEW ir.access: fleet.fleet_vehicle_tag_access_right
NEW ir.access: fleet.fleet_vehicle_tag_access_right_user
NEW ir.access: fleet.ir_rule_fleet_log_services
NEW ir.access: fleet.ir_rule_fleet_odometer
NEW ir.access: fleet.ir_rule_fleet_report
NEW ir.access: fleet.ir_rule_fleet_service_type
NEW ir.access: fleet.ir_rule_fleet_vehicle
NEW ir.access: fleet.ir_rule_fleet_vehicle_log_contract
NEW ir.access: fleet.ir_rule_fleet_vehicle_model
NEW ir.actions.act_window: fleet.fleet_vehicle_contract_types_action
ir.actions.act_window: fleet.fleet_vehicle_odometer_reporting_action (deleted domain)
ir.actions.act_window: fleet.mail_activity_type_action_config_fleet (deleted domain)
ir.cron: fleet.ir_cron_contract_costs_generator (noupdate switched)
DEL ir.model.access: fleet.access_fleet_report_manager
DEL ir.model.access: fleet.access_fleet_vehicle_assignation_log_fleet_group_user
DEL ir.model.access: fleet.access_fleet_vehicle_odometer_report_manager
DEL ir.model.access: fleet.access_fleet_vehicle_send_mail
DEL ir.model.access: fleet.access_mail_activity_type_fleet_manager
DEL ir.model.access: fleet.fleet_service_type_access_right
DEL ir.model.access: fleet.fleet_service_type_access_right_user
DEL ir.model.access: fleet.fleet_vehicle_access_right
DEL ir.model.access: fleet.fleet_vehicle_access_right_user
DEL ir.model.access: fleet.fleet_vehicle_log_contract_access_right
DEL ir.model.access: fleet.fleet_vehicle_log_contract_access_right_user
DEL ir.model.access: fleet.fleet_vehicle_log_services_access_right
DEL ir.model.access: fleet.fleet_vehicle_log_services_access_right_user
DEL ir.model.access: fleet.fleet_vehicle_model_access_right
DEL ir.model.access: fleet.fleet_vehicle_model_access_right_user
DEL ir.model.access: fleet.fleet_vehicle_model_brand_access_right
DEL ir.model.access: fleet.fleet_vehicle_model_brand_access_right_user
DEL ir.model.access: fleet.fleet_vehicle_model_brand_category_right_user
DEL ir.model.access: fleet.fleet_vehicle_model_category_access_right
DEL ir.model.access: fleet.fleet_vehicle_odometer_access_right
DEL ir.model.access: fleet.fleet_vehicle_state_access_right
DEL ir.model.access: fleet.fleet_vehicle_state_access_right_user
DEL ir.model.access: fleet.fleet_vehicle_tag_access_right
DEL ir.model.access: fleet.fleet_vehicle_tag_access_right_user
NEW ir.model.constraint: fleet.constraint_fleet_vehicle_log_services_check_service_dates
ir.model.constraint: fleet.constraint_fleet_vehicle_model_category_name_uniq (changed definition: is now 'False' ('UNIQUE (name)'))
ir.model.constraint: fleet.constraint_fleet_vehicle_state_fleet_state_name_unique (changed definition: is now 'False' ('unique(name)'))
ir.model.constraint: fleet.constraint_fleet_vehicle_tag_name_uniq (changed definition: is now 'False' ('unique (name)'))
NEW ir.model.constraint: fleet.constraint_fleet_vehicle_unique_license_plate
DEL ir.rule: fleet.fleet_rule_contract_visibility_manager (noupdate)
DEL ir.rule: fleet.fleet_rule_odometer_visibility_manager (noupdate)
DEL ir.rule: fleet.fleet_rule_service_visibility_manager (noupdate)
DEL ir.rule: fleet.fleet_rule_vehicle_visibility_manager (noupdate)
DEL ir.rule: fleet.ir_rule_fleet_log_services (noupdate)
DEL ir.rule: fleet.ir_rule_fleet_odometer (noupdate)
DEL ir.rule: fleet.ir_rule_fleet_report (noupdate)
DEL ir.rule: fleet.ir_rule_fleet_vehicle (noupdate)
DEL ir.rule: fleet.ir_rule_fleet_vehicle_log_contract (noupdate)
NEW ir.ui.menu: fleet.fleet_contracts_configuration
NEW ir.ui.menu: fleet.fleet_vehicle_contract_types_menu
NEW ir.ui.view: fleet.fleet_service_type_view_form_shared
NEW ir.ui.view: fleet.fleet_vehicle_odometer_report_view_tree
NEW ir.ui.view: fleet.view_partner_form
DEL ir.ui.view: fleet.fleet_vehicle_odometer_view_graph
mail.activity.type: fleet.mail_act_fleet_contract_to_renew (noupdate switched)
mail.message.subtype: fleet.mt_fleet_driver_updated (noupdate switched)
46 changes: 46 additions & 0 deletions openupgrade_scripts/scripts/fleet/tests/test_fleet_migration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from odoo.tests import TransactionCase

from odoo.addons.openupgrade_framework import openupgrade_test


@openupgrade_test
class TestFleetMigration(TransactionCase):
"""Assertions on a service log's date surviving its rename to date_from.

There is no 19.0 fixture here and there does not need to be: the seed
already holds the discriminating rows, six service logs on six different
dates. What makes the failure invisible without a test is that `date_from`
carries a default of today, so the wrong answer is a full column rather than
an empty one.
"""

def test_a_service_log_kept_the_date_it_was_executed_on(self):
self.env.cr.execute(
"SELECT count(*), count(DISTINCT date_from) FROM fleet_vehicle_log_services"
)
total, distinct = self.env.cr.fetchone()
self.assertTrue(total, "the seed has no service logs to check")
self.assertGreater(
distinct,
1,
"every service log carries the same start date, so the history was "
"overwritten with the day the upgrade ran",
)

def test_every_service_log_still_has_a_start_date(self):
"""A rename must not leave a row without the date it used to carry."""
self.env.cr.execute(
"SELECT count(*) FROM fleet_vehicle_log_services WHERE date_from IS NULL"
)
self.assertEqual(
self.env.cr.fetchone()[0], 0, "a service log came out with no start date"
)

def test_the_old_column_is_gone_rather_than_left_beside_the_new_one(self):
self.env.cr.execute(
"""
SELECT count(*) FROM information_schema.columns
WHERE table_name = 'fleet_vehicle_log_services' AND column_name = 'date'
"""
)
self.assertEqual(self.env.cr.fetchone()[0], 0, "date was copied, not renamed")
Loading