diff --git a/docsource/modules160-170.rst b/docsource/modules160-170.rst
index 2e389348d8ef..fc3b300c0d95 100644
--- a/docsource/modules160-170.rst
+++ b/docsource/modules160-170.rst
@@ -156,7 +156,7 @@ Module coverage 16.0 -> 17.0
+---------------------------------------------------+----------------------+-------------------------------------------------+
| google_recaptcha | Nothing to do | |
+---------------------------------------------------+----------------------+-------------------------------------------------+
-| hr | | |
+| hr | Done | |
+---------------------------------------------------+----------------------+-------------------------------------------------+
| hr_attendance | | |
+---------------------------------------------------+----------------------+-------------------------------------------------+
diff --git a/openupgrade_scripts/scripts/hr/17.0.1.1/noupdate_changes.xml b/openupgrade_scripts/scripts/hr/17.0.1.1/noupdate_changes.xml
index 52f80799ebd0..63c00d7cd9a4 100644
--- a/openupgrade_scripts/scripts/hr/17.0.1.1/noupdate_changes.xml
+++ b/openupgrade_scripts/scripts/hr/17.0.1.1/noupdate_changes.xml
@@ -26,7 +26,7 @@
manager
20
-
+
diff --git a/openupgrade_scripts/scripts/hr/17.0.1.1/post-migration.py b/openupgrade_scripts/scripts/hr/17.0.1.1/post-migration.py
new file mode 100644
index 000000000000..25ae3c589547
--- /dev/null
+++ b/openupgrade_scripts/scripts/hr/17.0.1.1/post-migration.py
@@ -0,0 +1,57 @@
+# Copyright 2024 Viindoo Technology Joint Stock Company (Viindoo)
+# Copyright 2025 Tecnativa - Pedro M. Baeza
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+
+from openupgradelib import openupgrade
+
+_deleted_xml_records = [
+ "hr.dep_sales",
+ "hr.hr_plan_activity_type_company_rule",
+ "hr.hr_plan_company_rule",
+ "hr.res_partner_admin_private_address",
+]
+
+
+def _transfer_employee_private_data(env):
+ """On v17, there's no more private res.partner records, so we should transfer the
+ information to the dedicated employee fields, and then anonymize the remaining data
+ in the res.partner record.
+ """
+ openupgrade.logged_query(
+ env.cr,
+ """
+ UPDATE hr_employee he
+ SET lang = rp.lang,
+ private_city = rp.city,
+ private_country_id = rp.country_id,
+ private_email = rp.email,
+ private_phone = rp.phone,
+ private_state_id = rp.state_id,
+ private_street = rp.street,
+ private_street2 = rp.street2,
+ private_zip = rp.zip
+ FROM res_partner rp
+ WHERE he.address_home_id = rp.id""",
+ )
+ openupgrade.logged_query(
+ env.cr,
+ """
+ UPDATE res_partner rp
+ SET city = '***',
+ country_id = NULL,
+ email = '***',
+ phone = '***',
+ state_id = NULL,
+ street = '***',
+ street2 = '***',
+ zip = '***'
+ FROM hr_employee he
+ WHERE he.address_home_id = rp.id""",
+ )
+
+
+@openupgrade.migrate()
+def migrate(env, version):
+ _transfer_employee_private_data(env)
+ openupgrade.load_data(env, "hr", "17.0.1.1/noupdate_changes.xml")
+ openupgrade.delete_records_safely_by_xml_id(env, _deleted_xml_records)
diff --git a/openupgrade_scripts/scripts/hr/17.0.1.1/pre-migration.py b/openupgrade_scripts/scripts/hr/17.0.1.1/pre-migration.py
new file mode 100644
index 000000000000..01580441475a
--- /dev/null
+++ b/openupgrade_scripts/scripts/hr/17.0.1.1/pre-migration.py
@@ -0,0 +1,119 @@
+# Copyright 2024 Viindoo Technology Joint Stock Company (Viindoo)
+# Copyright 2025 Tecnativa - Pedro M. Baeza
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+from openupgradelib import openupgrade
+
+
+def _rename_subscription_department_ids_m2m(env):
+ openupgrade.logged_query(
+ env.cr,
+ """ALTER TABLE hr_department_mail_channel_rel
+ RENAME TO discuss_channel_hr_department_rel""",
+ )
+ openupgrade.logged_query(
+ env.cr,
+ """ALTER TABLE discuss_channel_hr_department_rel
+ RENAME mail_channel_id TO discuss_channel_id""",
+ )
+
+
+def _fill_hr_contract_type_code(env):
+ openupgrade.logged_query(
+ env.cr, "ALTER TABLE hr_contract_type ADD COLUMN IF NOT EXISTS code VARCHAR"
+ )
+ openupgrade.logged_query(
+ env.cr, "UPDATE hr_contract_type SET code = name WHERE code IS NULL"
+ )
+
+
+def _hr_plan_sync_to_mail_activity_plan(env):
+ employee_model = env["ir.model"].search([("model", "=", "hr.employee")])
+ # sync hr.plan to mail.activity.plan
+ openupgrade.logged_query(
+ env.cr,
+ """
+ ALTER TABLE mail_activity_plan ADD COLUMN IF NOT EXISTS department_id INTEGER,
+ ADD COLUMN IF NOT EXISTS hr_plan_legacy_id INTEGER""",
+ )
+ hr_plan_query = f"""
+ INSERT INTO mail_activity_plan (
+ company_id, res_model_id, create_uid, write_uid, name, res_model,
+ active, create_date, write_date, department_id, hr_plan_legacy_id
+ )
+ SELECT
+ company_id, {employee_model.id}, create_uid, write_uid, name, 'hr.employee',
+ active, create_date, write_date, department_id, id
+ FROM hr_plan
+ """
+ openupgrade.logged_query(env.cr, hr_plan_query)
+ # sync hr.plan.activitype.type to mail.activity.plan.template
+ openupgrade.logged_query(
+ env.cr,
+ """
+ ALTER TABLE mail_activity_plan_template
+ ADD COLUMN hr_plan_activity_type_legacy_id INTEGER""",
+ )
+ hr_plan_activity_type_query = """
+ INSERT INTO mail_activity_plan_template (
+ activity_type_id, responsible_id, plan_id, responsible_type,
+ summary, note, create_uid, write_uid, create_date,
+ write_date, hr_plan_activity_type_legacy_id
+ )
+ SELECT
+ hpat.activity_type_id, hpat.responsible_id, map.id, hpat.responsible,
+ hpat.summary, hpat.note, hpat.create_uid, hpat.write_uid, hpat.create_date,
+ hpat.write_date, hpat.id
+ FROM hr_plan_activity_type hpat
+ JOIN mail_activity_plan map ON hpat.plan_id = map.hr_plan_legacy_id
+ """
+ openupgrade.logged_query(env.cr, hr_plan_activity_type_query)
+ # Reassign standard data XML-IDs for pointing to the new records
+ openupgrade.logged_query(
+ env.cr,
+ """
+ UPDATE ir_model_data imd
+ SET model = 'mail.activity.plan', res_id = map.id
+ FROM ir_model_data imd2
+ JOIN mail_activity_plan map ON
+ map.hr_plan_legacy_id = imd2.res_id
+ AND imd2.name IN ('onboarding_plan', 'offboarding_plan')
+ AND imd2.module = 'hr'
+ WHERE imd.id = imd2.id""",
+ )
+ openupgrade.logged_query(
+ env.cr,
+ """
+ UPDATE ir_model_data imd
+ SET model = 'mail.activity.plan.template', res_id = mapt.id
+ FROM ir_model_data imd2
+ JOIN mail_activity_plan_template mapt ON
+ mapt.hr_plan_activity_type_legacy_id = imd2.res_id
+ AND imd2.name IN (
+ 'onboarding_setup_it_materials',
+ 'onboarding_plan_training',
+ 'onboarding_training',
+ 'offboarding_setup_compute_out_delais',
+ 'offboarding_take_back_hr_materials'
+ )
+ AND imd2.module = 'hr'
+ WHERE imd.id = imd2.id""",
+ )
+
+
+def _hr_work_location_fill_location_type(env):
+ openupgrade.logged_query(
+ env.cr,
+ """
+ ALTER TABLE hr_work_location ADD COLUMN IF NOT EXISTS location_type VARCHAR;
+ UPDATE hr_work_location
+ SET location_type = 'office'
+ """,
+ )
+
+
+@openupgrade.migrate()
+def migrate(env, version):
+ _rename_subscription_department_ids_m2m(env)
+ _fill_hr_contract_type_code(env)
+ _hr_plan_sync_to_mail_activity_plan(env)
+ _hr_work_location_fill_location_type(env)
diff --git a/openupgrade_scripts/scripts/hr/17.0.1.1/upgrade_analysis_work.txt b/openupgrade_scripts/scripts/hr/17.0.1.1/upgrade_analysis_work.txt
new file mode 100644
index 000000000000..8ac9ae5cf6dd
--- /dev/null
+++ b/openupgrade_scripts/scripts/hr/17.0.1.1/upgrade_analysis_work.txt
@@ -0,0 +1,154 @@
+---Models in module 'hr'---
+obsolete model hr.plan
+obsolete model hr.plan.activity.type
+# DONE pre-migration: move data into mail.activity.plan
+
+obsolete model hr.plan.wizard [transient]
+# NOTHING TO DO
+
+---Fields in module 'hr'---
+hr / mail.channel / subscription_department_ids (many2many): column1 is now 'discuss_channel_id' ('mail_channel_id') [hr_department_mail_channel_rel]
+hr / mail.channel / subscription_department_ids (many2many): table is now 'discuss_channel_hr_department_rel' ('hr_department_mail_channel_rel')
+# DONE: pre-migration: Rename table and column
+
+hr / hr.contract.type / code (char) : NEW hasdefault: compute
+# DONE: pre-migration: Column created and filled with the name. It wouldn't be too much burden to let the ORM compute, being few records, but let's do it by SQL anyway.
+
+hr / hr.contract.type / country_id (many2one) : NEW relation: res.country
+# NOTHING TO DO: new feature
+
+hr / hr.department / message_main_attachment_id (many2one): DEL relation: ir.attachment
+# NOTHING TO DO
+
+hr / hr.department / plan_ids (one2many) : relation is now 'mail.activity.plan' ('hr.plan') [nothing to do]
+hr / hr.plan / active (boolean) : DEL
+hr / hr.plan / company_id (many2one) : DEL relation: res.company
+hr / hr.plan / department_id (many2one) : DEL relation: hr.department
+hr / hr.plan / name (char) : DEL required
+hr / hr.plan / plan_activity_type_ids (one2many): DEL relation: hr.plan.activity.type
+hr / hr.plan.activity.type / activity_type_id (many2one) : DEL relation: mail.activity.type
+hr / hr.plan.activity.type / company_id (many2one) : DEL relation: res.company
+hr / hr.plan.activity.type / note (html) : DEL
+hr / hr.plan.activity.type / plan_id (many2one) : DEL relation: hr.plan
+hr / hr.plan.activity.type / responsible (selection) : DEL required, selection_keys: ['coach', 'employee', 'manager', 'other']
+hr / hr.plan.activity.type / responsible_id (many2one) : DEL relation: res.users
+hr / hr.plan.activity.type / summary (char) : DEL
+hr / mail.activity.plan / department_id (many2one) : NEW relation: hr.department, hasdefault: compute
+hr / mail.activity.plan.template / responsible_type (False) : NEW selection_keys: ['coach', 'employee', 'manager', 'on_demand', 'other'], mode: modify
+# DONE: pre-migration: move data into mail.activity.plan
+
+hr / hr.department / rating_ids (one2many) : NEW relation: rating.rating
+# NOTHING TO DO
+
+hr / hr.departure.reason / reason_code (integer) : NEW
+# DONE post-migration: load noupdate
+
+hr / hr.employee / activity_user_id (many2one) : not related anymore
+hr / hr.employee / activity_user_id (many2one) : now a function
+# NOTHING TO DO: no store field
+
+hr / hr.employee / employee_properties (properties): NEW hasdefault: compute
+hr / res.company / employee_properties_definition (properties_definition): NEW
+# NOTHING TO DO: new feature for assigning properties
+
+hr / hr.employee / private_car_plate (char) : NEW
+# NOTHING TO DO: new feature
+
+hr / hr.employee / address_home_id (many2one) : DEL relation: res.partner
+hr / hr.employee / lang (selection) : is now stored
+hr / hr.employee / lang (selection) : not related anymore
+hr / hr.employee / private_city (char) : NEW
+hr / hr.employee / private_country_id (many2one) : NEW relation: res.country
+hr / hr.employee / private_email (char) : is now stored
+hr / hr.employee / private_email (char) : not related anymore
+hr / hr.employee / private_phone (char) : NEW
+hr / hr.employee / private_state_id (many2one) : NEW relation: res.country.state
+hr / hr.employee / private_street (char) : NEW
+hr / hr.employee / private_street2 (char) : NEW
+hr / hr.employee / private_zip (char) : NEW
+# DONE: post-migration: fill values from address_home_id to the employee fields, and anonymize the information on the res.partner record
+
+hr / hr.employee / rating_ids (one2many) : NEW relation: rating.rating
+hr / hr.job / message_main_attachment_id (many2one): DEL relation: ir.attachment
+hr / hr.job / rating_ids (one2many) : NEW relation: rating.rating
+# NOTHING TO DO: New rating inheritance
+
+hr / hr.work.location / location_type (selection) : NEW required, selection_keys: ['home', 'office', 'other'], hasdefault: default
+# DONE: pre-migration: Pre-created and filled with the value 'office', which is fine according previous version behavior.
+
+---XML records in module 'hr'---
+NEW hr.contract.type: hr.contract_type_full_time (noupdate)
+NEW hr.contract.type: hr.contract_type_part_time (noupdate)
+NEW hr.contract.type: hr.contract_type_permanent (noupdate)
+NEW hr.contract.type: hr.contract_type_seasonal (noupdate)
+NEW hr.contract.type: hr.contract_type_temporary (noupdate)
+# NOTHING TO DO
+
+DEL hr.department: hr.dep_sales (noupdate)
+DEL ir.rule: hr.hr_plan_activity_type_company_rule (noupdate)
+DEL ir.rule: hr.hr_plan_company_rule (noupdate)
+DEL res.partner: hr.res_partner_admin_private_address (noupdate)
+# DONE: post-migration: removed safely
+
+DEL hr.plan: hr.offboarding_plan (noupdate)
+DEL hr.plan: hr.onboarding_plan (noupdate)
+DEL hr.plan.activity.type: hr.offboarding_setup_compute_out_delais (noupdate)
+DEL hr.plan.activity.type: hr.offboarding_take_back_hr_materials (noupdate)
+DEL hr.plan.activity.type: hr.onboarding_plan_training (noupdate)
+DEL hr.plan.activity.type: hr.onboarding_setup_it_materials (noupdate)
+DEL hr.plan.activity.type: hr.onboarding_training (noupdate)
+NEW mail.activity.plan: hr.offboarding_plan (noupdate)
+NEW mail.activity.plan: hr.onboarding_plan (noupdate)
+NEW mail.activity.plan.template: hr.offboarding_setup_compute_out_delais (noupdate)
+NEW mail.activity.plan.template: hr.offboarding_take_back_hr_materials (noupdate)
+NEW mail.activity.plan.template: hr.onboarding_plan_training (noupdate)
+NEW mail.activity.plan.template: hr.onboarding_setup_it_materials (noupdate)
+NEW mail.activity.plan.template: hr.onboarding_training (noupdate)
+# DONE: pre-migration: Reassign XML-IDs to the new mail.activity.plan* records
+
+NEW hr.work.location: hr.home_work_location (noupdate)
+NEW hr.work.location: hr.home_work_office (noupdate)
+NEW hr.work.location: hr.home_work_other (noupdate)
+NEW ir.actions.act_window: hr.mail_activity_plan_action
+DEL ir.actions.act_window: hr.hr_employee_action_from_user
+DEL ir.actions.act_window: hr.hr_plan_action
+DEL ir.actions.act_window: hr.hr_plan_activity_type_action
+NEW ir.actions.act_window.view: hr.mail_activity_plan_action_employee_view_form
+NEW ir.actions.act_window.view: hr.mail_activity_plan_action_employee_view_tree
+NEW ir.model.access: hr.access_mail_activity_plan_hr_manager
+NEW ir.model.access: hr.access_mail_activity_plan_template_hr_manager
+DEL ir.model.access: hr.access_hr_plan_activity_type_employee
+DEL ir.model.access: hr.access_hr_plan_activity_type_hr_user
+DEL ir.model.access: hr.access_hr_plan_employee
+DEL ir.model.access: hr.access_hr_plan_hr_user
+DEL ir.model.access: hr.access_hr_plan_wizard
+DEL ir.model.constraint: hr.constraint_hr_employee_barcode_uniq
+DEL ir.model.constraint: hr.constraint_hr_employee_category_name_uniq
+DEL ir.model.constraint: hr.constraint_hr_employee_user_uniq
+DEL ir.model.constraint: hr.constraint_hr_job_name_company_uniq
+DEL ir.model.constraint: hr.constraint_hr_job_no_of_recruitment_positive
+NEW ir.rule: hr.ir_rule_hr_contract_type_multi_company (noupdate)
+NEW ir.rule: hr.mail_plan_rule_group_hr_manager (noupdate)
+NEW ir.rule: hr.mail_plan_templates_rule_group_hr_manager (noupdate)
+# NOTHING TO DO
+
+NEW ir.ui.menu: hr.menu_resource_calendar_view
+NEW ir.ui.view: hr.discuss_channel_view_form
+NEW ir.ui.view: hr.hr_employee_plan_activity_summary
+NEW ir.ui.view: hr.hr_employee_view_graph
+NEW ir.ui.view: hr.hr_employee_view_pivot
+NEW ir.ui.view: hr.mail_activity_plan_template_view_form
+NEW ir.ui.view: hr.mail_activity_plan_view_form
+NEW ir.ui.view: hr.mail_activity_plan_view_form_hr_employee
+NEW ir.ui.view: hr.mail_activity_plan_view_tree
+NEW ir.ui.view: hr.mail_activity_schedule_view_form
+DEL ir.ui.view: hr.hr_plan_activity_type_view_form
+DEL ir.ui.view: hr.hr_plan_activity_type_view_tree
+DEL ir.ui.view: hr.hr_plan_view_form
+DEL ir.ui.view: hr.hr_plan_view_search
+DEL ir.ui.view: hr.hr_plan_view_tree
+DEL ir.ui.view: hr.mail_channel_view_form_
+DEL ir.ui.view: hr.plan_wizard
+DEL ir.ui.view: hr.view_employee_form_smartbutton
+DEL ir.ui.view: hr.view_partner_tree2
+# NOTHING TO DO