diff --git a/docsource/modules190-200.rst b/docsource/modules190-200.rst index 8da46d62bbc..ec34d06fc31 100644 --- a/docsource/modules190-200.rst +++ b/docsource/modules190-200.rst @@ -176,7 +176,7 @@ Module coverage 19.0 -> 20.0 +---------------------------------------------------+----------------------+-------------------------------------------------+ | hr | | | +---------------------------------------------------+----------------------+-------------------------------------------------+ -| hr_attendance | | | +| hr_attendance |Done | | +---------------------------------------------------+----------------------+-------------------------------------------------+ | hr_calendar | | | +---------------------------------------------------+----------------------+-------------------------------------------------+ diff --git a/openupgrade_scripts/scripts/hr_attendance/20.0.2.0/post-migration.py b/openupgrade_scripts/scripts/hr_attendance/20.0.2.0/post-migration.py new file mode 100644 index 00000000000..09424cf5528 --- /dev/null +++ b/openupgrade_scripts/scripts/hr_attendance/20.0.2.0/post-migration.py @@ -0,0 +1,32 @@ +# Copyright 2026 Don Kendall +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from openupgradelib import openupgrade + +_legacy_validation = openupgrade.get_legacy_name("attendance_overtime_validation") + + +@openupgrade.migrate() +def migrate(env, version): + """Carry whether extra hours need approving into 20.0's wider question. + + 19.0 asked no_validation or by_manager. 20.0 asks no_validation, + manual_validation or tolerance_validation, under a new name and so in a new + column that takes the default. A company that required a manager to approve + extra hours therefore comes out approving them automatically, and nothing + says so: the setting is not shown as empty, it is shown as the other answer. + + Only by_manager is written. no_validation is already what the default gives, + and tolerance_validation is 20.0's new middle ground, which no 19.0 database + can have chosen. + """ + if not openupgrade.column_exists(env.cr, "res_company", _legacy_validation): + return + openupgrade.logged_query( + env.cr, + f""" + UPDATE res_company + SET attendance_validation = 'manual_validation' + WHERE {_legacy_validation} = 'by_manager' + """, + ) diff --git a/openupgrade_scripts/scripts/hr_attendance/20.0.2.0/pre-migration.py b/openupgrade_scripts/scripts/hr_attendance/20.0.2.0/pre-migration.py new file mode 100644 index 00000000000..1439820f0ab --- /dev/null +++ b/openupgrade_scripts/scripts/hr_attendance/20.0.2.0/pre-migration.py @@ -0,0 +1,19 @@ +# 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): + # 19.0's attendance_overtime_validation said whether extra hours needed a + # manager's approval. 20.0 asks the same question under a different name, + # attendance_validation, with a third answer added. Being a different + # column, it takes its own default and the answer is not carried, so + # post-migration reads this one back. + if openupgrade.column_exists( + env.cr, "res_company", "attendance_overtime_validation" + ): + openupgrade.rename_columns( + env.cr, {"res_company": [("attendance_overtime_validation", None)]} + ) diff --git a/openupgrade_scripts/scripts/hr_attendance/20.0.2.0/upgrade_analysis.txt b/openupgrade_scripts/scripts/hr_attendance/20.0.2.0/upgrade_analysis.txt new file mode 100644 index 00000000000..b0bc107655d --- /dev/null +++ b/openupgrade_scripts/scripts/hr_attendance/20.0.2.0/upgrade_analysis.txt @@ -0,0 +1,138 @@ +---Models in module 'hr_attendance'--- +obsolete model hr.attendance.overtime.line +obsolete model hr.attendance.overtime.rule +obsolete model hr.attendance.overtime.ruleset +---Fields in module 'hr_attendance'--- +hr_attendance / hr.attendance / break_duration (float) : NEW +hr_attendance / hr.attendance / day_of_date (selection) : NEW selection_keys: ['0', '1', '2', '3', '4', '5', '6'], isfunction: function, stored +hr_attendance / hr.attendance / expected_hours (float) : DEL +hr_attendance / hr.attendance / in_image (binary) : NEW attachment: True +hr_attendance / hr.attendance / linked_overtime_ids (many2many): DEL relation: hr.attendance.overtime.line, stored: False +hr_attendance / hr.attendance / overtime_attendance_ids (one2many): NEW relation: hr.attendance +hr_attendance / hr.attendance / overtime_hours (float) : DEL +hr_attendance / hr.attendance / overtime_status (selection) : DEL selection_keys: ['approved', 'refused', 'to_approve'] +hr_attendance / hr.attendance / source_attendance_id (many2one): NEW relation: hr.attendance +hr_attendance / hr.attendance / source_stale (boolean) : NEW hasdefault: default +hr_attendance / hr.attendance / state (selection) : NEW required, selection_keys: ['draft', 'refused', 'validated'] +hr_attendance / hr.attendance / time_rule_id (many2one) : NEW relation: hr.time.rule +hr_attendance / hr.attendance / validated_overtime_hours (float): DEL +hr_attendance / hr.attendance / website_message_ids (one2many): DEL relation: mail.message +hr_attendance / hr.attendance / work_entry_type_id (many2one) : NEW relation: hr.work.entry.type, required, hasdefault: default +hr_attendance / hr.attendance.overtime.line / amount_rate (float) : DEL required +hr_attendance / hr.attendance.overtime.line / date (date) : DEL required +hr_attendance / hr.attendance.overtime.line / duration (float) : DEL required +hr_attendance / hr.attendance.overtime.line / employee_id (many2one) : DEL relation: hr.employee, required +hr_attendance / hr.attendance.overtime.line / manual_duration (float) : DEL +hr_attendance / hr.attendance.overtime.line / rule_ids (many2many) : DEL relation: hr.attendance.overtime.rule +hr_attendance / hr.attendance.overtime.line / status (selection) : DEL required, selection_keys: ['approved', 'refused', 'to_approve'] +hr_attendance / hr.attendance.overtime.line / time_start (datetime) : DEL +hr_attendance / hr.attendance.overtime.line / time_stop (datetime) : DEL +hr_attendance / hr.attendance.overtime.rule / amount_rate (float) : DEL +hr_attendance / hr.attendance.overtime.rule / base_off (selection) : DEL required, selection_keys: ['quantity', 'timing'] +hr_attendance / hr.attendance.overtime.rule / description (html) : DEL +hr_attendance / hr.attendance.overtime.rule / employee_tolerance (float) : DEL +hr_attendance / hr.attendance.overtime.rule / employer_tolerance (float) : DEL +hr_attendance / hr.attendance.overtime.rule / expected_hours (float) : DEL +hr_attendance / hr.attendance.overtime.rule / expected_hours_from_contract (boolean): DEL +hr_attendance / hr.attendance.overtime.rule / name (char) : DEL required +hr_attendance / hr.attendance.overtime.rule / paid (boolean) : DEL +hr_attendance / hr.attendance.overtime.rule / quantity_period (selection) : DEL selection_keys: ['day', 'week'] +hr_attendance / hr.attendance.overtime.rule / resource_calendar_id (many2one): DEL relation: resource.calendar +hr_attendance / hr.attendance.overtime.rule / ruleset_id (many2one) : DEL relation: hr.attendance.overtime.ruleset, required +hr_attendance / hr.attendance.overtime.rule / sequence (integer) : DEL +hr_attendance / hr.attendance.overtime.rule / timing_start (float) : DEL +hr_attendance / hr.attendance.overtime.rule / timing_stop (float) : DEL +hr_attendance / hr.attendance.overtime.rule / timing_type (selection) : DEL selection_keys: ['leave', 'non_work_days', 'schedule', 'work_days'] +hr_attendance / hr.attendance.overtime.ruleset / active (boolean) : DEL +hr_attendance / hr.attendance.overtime.ruleset / company_id (many2one) : DEL relation: res.company +hr_attendance / hr.attendance.overtime.ruleset / country_id (many2one) : DEL relation: res.country +hr_attendance / hr.attendance.overtime.ruleset / description (html) : DEL +hr_attendance / hr.attendance.overtime.ruleset / name (char) : DEL required +hr_attendance / hr.attendance.overtime.ruleset / rate_combination_mode (selection): DEL required, selection_keys: ['max', 'sum'] +hr_attendance / hr.attendance.overtime.ruleset / rule_ids (one2many) : DEL relation: hr.attendance.overtime.rule +hr_attendance / hr.employee / overtime_ids (one2many) : DEL relation: hr.attendance.overtime.line +hr_attendance / hr.time.rule / condition_work_entry_type_ids (False): NEW mode: modify, hasdefault: default +hr_attendance / hr.version / attendance_based (boolean) : NEW hasdefault: default +hr_attendance / hr.version / ruleset_id (many2one) : DEL relation: hr.attendance.overtime.ruleset +hr_attendance / res.company / attendance_based (boolean) : NEW hasdefault: default +hr_attendance / res.company / attendance_break_management (boolean): NEW +hr_attendance / res.company / attendance_capture_check_in (boolean): NEW hasdefault: default +hr_attendance / res.company / attendance_overtime_validation (selection): DEL selection_keys: ['by_manager', 'no_validation'] +hr_attendance / res.company / attendance_validation (selection): NEW selection_keys: ['manual_validation', 'no_validation', 'tolerance_validation'], hasdefault: default +hr_attendance / res.company / attendance_validation_tolerance (float): NEW hasdefault: default +hr_attendance / res.company / attendance_work_entry_type_id (many2one): NEW relation: hr.work.entry.type, hasdefault: compute +hr_attendance / res.company / auto_check_out_mode (selection): NEW selection_keys: ['specific_time', 'tolerance'], hasdefault: default +hr_attendance / res.company / auto_check_out_specific_time (float): NEW hasdefault: default +hr_attendance / res.company / hr_attendance_display_overtime (boolean): DEL +hr_attendance / res.company / overtime_company_threshold (integer): DEL +hr_attendance / res.company / overtime_employee_threshold (integer): DEL +hr_attendance / res.company / single_check_in (boolean) : NEW +---XML records in module 'hr_attendance'--- +DEL hr.attendance.overtime.rule: hr_attendance.hr_attendance_overtime_employee_schedule_rule (noupdate) +DEL hr.attendance.overtime.rule: hr_attendance.hr_attendance_overtime_non_working_days_rule (noupdate) +DEL hr.attendance.overtime.rule: hr_attendance.hr_attendance_overtime_rule_1_ae (noupdate) +DEL hr.attendance.overtime.rule: hr_attendance.hr_attendance_overtime_rule_2_ae (noupdate) +DEL hr.attendance.overtime.rule: hr_attendance.hr_attendance_overtime_rule_3_ae (noupdate) +DEL hr.attendance.overtime.rule: hr_attendance.hr_attendance_overtime_rule_4_ae (noupdate) +DEL hr.attendance.overtime.ruleset: hr_attendance.hr_attendance_default_ruleset (noupdate) +DEL hr.attendance.overtime.ruleset: hr_attendance.l10n_ae_hr_attendance_overtime_ruleset (noupdate) +NEW ir.access: hr_attendance.access_hr_time_rule_attendance_admin +NEW ir.access: hr_attendance.hr_attendance_rule_attendance_admin +NEW ir.access: hr_attendance.hr_attendance_rule_attendance_manager_restrict +NEW ir.access: hr_attendance.hr_attendance_rule_attendance_own +NEW ir.access: hr_attendance.hr_attendance_rule_attendance_simple_user +NEW ir.access: hr_attendance.hr_attendance_rule_employee_company +ir.actions.act_window: hr_attendance.hr_attendance_management_action (deleted domain) +NEW ir.actions.act_window: hr_attendance.hr_attendance_my_action +DEL ir.actions.act_window: hr_attendance.hr_attendance_overtime_rule_action +DEL ir.actions.act_window: hr_attendance.hr_attendance_overtime_ruleset_action +NEW ir.actions.server: hr_attendance.action_attendance_refuse +NEW ir.actions.server: hr_attendance.action_attendance_validate +NEW ir.cron: hr_attendance.hr_attendance_day_undertime_rules_cron +NEW ir.cron: hr_attendance.hr_attendance_week_time_rules_cron +DEL ir.model.access: hr_attendance.access_hr_attendance_admin +DEL ir.model.access: hr_attendance.access_hr_attendance_officer +DEL ir.model.access: hr_attendance.access_hr_attendance_overtime_line_officer +DEL ir.model.access: hr_attendance.access_hr_attendance_overtime_line_own_reader +DEL ir.model.access: hr_attendance.access_hr_attendance_overtime_rule_admin +DEL ir.model.access: hr_attendance.access_hr_attendance_overtime_rule_officer +DEL ir.model.access: hr_attendance.access_hr_attendance_overtime_rule_user +DEL ir.model.access: hr_attendance.access_hr_attendance_overtime_ruleset_admin +DEL ir.model.access: hr_attendance.access_hr_attendance_overtime_ruleset_hr_manager +DEL ir.model.access: hr_attendance.access_hr_attendance_own_reader +DEL ir.model.access: hr_attendance.access_hr_attendance_user +NEW ir.model.constraint: hr_attendance.constraint_res_company_check_auto_check_out_specific_time_range +DEL ir.model.constraint: hr_attendance.constraint_hr_attendance_overtime_line_overtime_start_before_end +DEL ir.model.constraint: hr_attendance.constraint_hr_attendance_overtime_rule_timing_start_is_hour +DEL ir.model.constraint: hr_attendance.constraint_hr_attendance_overtime_rule_timing_stop_is_hour +DEL ir.rule: hr_attendance.hr_attendance_overtime_line_rule_admin (noupdate) +DEL ir.rule: hr_attendance.hr_attendance_overtime_line_rule_employee_company (noupdate) +DEL ir.rule: hr_attendance.hr_attendance_overtime_line_rule_officer (noupdate) +DEL ir.rule: hr_attendance.hr_attendance_overtime_line_rule_own_reader (noupdate) +DEL ir.rule: hr_attendance.hr_attendance_overtime_ruleset_rule (noupdate) +DEL ir.rule: hr_attendance.hr_attendance_overtime_ruleset_rule_group_hr_attendance_manager (noupdate) +DEL ir.rule: hr_attendance.hr_attendance_rule_attendance_admin (noupdate) +DEL ir.rule: hr_attendance.hr_attendance_rule_attendance_manager_restrict (noupdate) +DEL ir.rule: hr_attendance.hr_attendance_rule_attendance_simple_user (noupdate) +DEL ir.rule: hr_attendance.hr_attendance_rule_employee_company (noupdate) +NEW ir.ui.menu: hr_attendance.menu_hr_attendance_management_list +NEW ir.ui.menu: hr_attendance.menu_hr_attendance_my_attendances +NEW ir.ui.menu: hr_attendance.menu_hr_attendance_time_rules +NEW ir.ui.menu: hr_attendance.menu_hr_attendance_work_entry_type +DEL ir.ui.menu: hr_attendance.menu_hr_attendance_overtime_rulesets +DEL ir.ui.menu: hr_attendance.menu_hr_attendance_overview +NEW ir.ui.view: hr_attendance.hr_attendance_create_view_form +NEW ir.ui.view: hr_attendance.hr_attendance_employee_calendar_view +NEW ir.ui.view: hr_attendance.hr_attendance_my_calendar_view +NEW ir.ui.view: hr_attendance.hr_attendance_my_form_view +NEW ir.ui.view: hr_attendance.hr_contract_template_view_form +NEW ir.ui.view: hr_attendance.hr_version_list_view_enhanced_hr_attendance_base +NEW ir.ui.view: hr_attendance.hr_version_search_view_enhanced_hr_attendance +DEL ir.ui.view: hr_attendance.hr_attendance_employee_simple_tree_view +DEL ir.ui.view: hr_attendance.hr_attendance_overtime_rule_view_form +DEL ir.ui.view: hr_attendance.hr_attendance_overtime_rule_view_list +DEL ir.ui.view: hr_attendance.hr_attendance_overtime_ruleset_view_filter +DEL ir.ui.view: hr_attendance.hr_attendance_overtime_ruleset_view_form +DEL ir.ui.view: hr_attendance.hr_attendance_overtime_ruleset_view_list +res.groups: base.group_user (noupdate switched) +NEW res.groups: hr_attendance.group_hr_attendance_own diff --git a/openupgrade_scripts/scripts/hr_attendance/20.0.2.0/upgrade_analysis_work.txt b/openupgrade_scripts/scripts/hr_attendance/20.0.2.0/upgrade_analysis_work.txt new file mode 100644 index 00000000000..b678236b902 --- /dev/null +++ b/openupgrade_scripts/scripts/hr_attendance/20.0.2.0/upgrade_analysis_work.txt @@ -0,0 +1,230 @@ +---Models in module 'hr_attendance'--- +obsolete model hr.attendance.overtime.line +obsolete model hr.attendance.overtime.rule +obsolete model hr.attendance.overtime.ruleset +# NOTHING TO DO: the model is gone from 20.0; OpenUpgrade leaves its table +# in place for database_cleanup rather than dropping it during an upgrade +---Fields in module 'hr_attendance'--- +hr_attendance / hr.attendance / break_duration (float) : NEW +# NOTHING TO DO: empty is the right value for existing records +hr_attendance / hr.attendance / day_of_date (selection) : NEW selection_keys: ['0', '1', '2', '3', '4', '5', '6'], isfunction: function, stored +# NOTHING TO DO: a new stored computed field, the ORM fills it in +hr_attendance / hr.attendance / expected_hours (float) : DEL +# NOTHING TO DO: the column is left in place for database_cleanup +hr_attendance / hr.attendance / in_image (binary) : NEW attachment: True +# NOTHING TO DO: a new binary field, stored as an attachment rather than in a +# column, and empty is the right value for existing records +hr_attendance / hr.attendance / linked_overtime_ids (many2many): DEL relation: hr.attendance.overtime.line, stored: False +# NOTHING TO DO: a one2many has no column, so nothing is left behind +hr_attendance / hr.attendance / overtime_attendance_ids (one2many): NEW relation: hr.attendance +# NOTHING TO DO: a one2many has no column of its own +hr_attendance / hr.attendance / overtime_hours (float) : DEL +# NOTHING TO DO: the column is left in place for database_cleanup +hr_attendance / hr.attendance / overtime_status (selection) : DEL selection_keys: ['approved', 'refused', 'to_approve'] +# NOTHING TO DO: the keys changed but no row holds one 20.0 does not declare -- +# the selection gate reads ir_model_fields_selection and reports the field clean +hr_attendance / hr.attendance / source_attendance_id (many2one): NEW relation: hr.attendance +# NOTHING TO DO: empty is the right value for existing records +hr_attendance / hr.attendance / source_stale (boolean) : NEW hasdefault: default +# NOTHING TO DO: a new field whose default is falsy -- False, or an empty +# list -- so a NULL left on an existing row already means what the default +# would have written. The one flagged field whose default names a record, +# pos.config.default_product_id, has a script instead +hr_attendance / hr.attendance / state (selection) : NEW required, selection_keys: ['draft', 'refused', 'validated'] +# NOTHING TO DO: the NOT NULL applied cleanly on the seed -- the constraint gate +# reports none outstanding -- so the model is either new or the rows carry a value +hr_attendance / hr.attendance / time_rule_id (many2one) : NEW relation: hr.time.rule +# NOTHING TO DO: empty is the right value for existing records +hr_attendance / hr.attendance / validated_overtime_hours (float): DEL +# NOTHING TO DO: the column is left in place for database_cleanup +hr_attendance / hr.attendance / website_message_ids (one2many): DEL relation: mail.message +# NOTHING TO DO: a one2many has no column, so nothing is left behind +hr_attendance / hr.attendance / work_entry_type_id (many2one) : NEW relation: hr.work.entry.type, required, hasdefault: default +# NOTHING TO DO: the NOT NULL applied cleanly on the seed -- the constraint gate +# reports none outstanding -- so the model is either new or the rows carry a value +hr_attendance / hr.attendance.overtime.line / amount_rate (float) : DEL required +hr_attendance / hr.attendance.overtime.line / date (date) : DEL required +hr_attendance / hr.attendance.overtime.line / duration (float) : DEL required +# NOTHING TO DO: a dropped field. The column is left in place for +# database_cleanup, and its NOT NULL goes with the field +hr_attendance / hr.attendance.overtime.line / employee_id (many2one) : DEL relation: hr.employee, required +hr_attendance / hr.attendance.overtime.line / manual_duration (float) : DEL +hr_attendance / hr.attendance.overtime.line / rule_ids (many2many) : DEL relation: hr.attendance.overtime.rule +# NOTHING TO DO: the column is left in place for database_cleanup +hr_attendance / hr.attendance.overtime.line / status (selection) : DEL required, selection_keys: ['approved', 'refused', 'to_approve'] +# NOTHING TO DO: a dropped field. The column is left in place for +# database_cleanup, and its NOT NULL goes with the field +hr_attendance / hr.attendance.overtime.line / time_start (datetime) : DEL +hr_attendance / hr.attendance.overtime.line / time_stop (datetime) : DEL +hr_attendance / hr.attendance.overtime.rule / amount_rate (float) : DEL +# NOTHING TO DO: the column is left in place for database_cleanup +hr_attendance / hr.attendance.overtime.rule / base_off (selection) : DEL required, selection_keys: ['quantity', 'timing'] +# NOTHING TO DO: a dropped field. The column is left in place for +# database_cleanup, and its NOT NULL goes with the field +hr_attendance / hr.attendance.overtime.rule / description (html) : DEL +hr_attendance / hr.attendance.overtime.rule / employee_tolerance (float) : DEL +hr_attendance / hr.attendance.overtime.rule / employer_tolerance (float) : DEL +hr_attendance / hr.attendance.overtime.rule / expected_hours (float) : DEL +hr_attendance / hr.attendance.overtime.rule / expected_hours_from_contract (boolean): DEL +# NOTHING TO DO: the column is left in place for database_cleanup +hr_attendance / hr.attendance.overtime.rule / name (char) : DEL required +# NOTHING TO DO: a dropped field. The column is left in place for +# database_cleanup, and its NOT NULL goes with the field +hr_attendance / hr.attendance.overtime.rule / paid (boolean) : DEL +# NOTHING TO DO: the column is left in place for database_cleanup +hr_attendance / hr.attendance.overtime.rule / quantity_period (selection) : DEL selection_keys: ['day', 'week'] +# NOTHING TO DO: the keys changed but no row holds one 20.0 does not declare -- +# the selection gate reads ir_model_fields_selection and reports the field clean +hr_attendance / hr.attendance.overtime.rule / resource_calendar_id (many2one): DEL relation: resource.calendar +hr_attendance / hr.attendance.overtime.rule / ruleset_id (many2one) : DEL relation: hr.attendance.overtime.ruleset, required +hr_attendance / hr.attendance.overtime.rule / sequence (integer) : DEL +hr_attendance / hr.attendance.overtime.rule / timing_start (float) : DEL +hr_attendance / hr.attendance.overtime.rule / timing_stop (float) : DEL +# NOTHING TO DO: the column is left in place for database_cleanup +hr_attendance / hr.attendance.overtime.rule / timing_type (selection) : DEL selection_keys: ['leave', 'non_work_days', 'schedule', 'work_days'] +# NOTHING TO DO: the keys changed but no row holds one 20.0 does not declare -- +# the selection gate reads ir_model_fields_selection and reports the field clean +hr_attendance / hr.attendance.overtime.ruleset / active (boolean) : DEL +hr_attendance / hr.attendance.overtime.ruleset / company_id (many2one) : DEL relation: res.company +hr_attendance / hr.attendance.overtime.ruleset / country_id (many2one) : DEL relation: res.country +hr_attendance / hr.attendance.overtime.ruleset / description (html) : DEL +# NOTHING TO DO: the column is left in place for database_cleanup +hr_attendance / hr.attendance.overtime.ruleset / name (char) : DEL required +hr_attendance / hr.attendance.overtime.ruleset / rate_combination_mode (selection): DEL required, selection_keys: ['max', 'sum'] +# NOTHING TO DO: a dropped field. The column is left in place for +# database_cleanup, and its NOT NULL goes with the field +hr_attendance / hr.attendance.overtime.ruleset / rule_ids (one2many) : DEL relation: hr.attendance.overtime.rule +hr_attendance / hr.employee / overtime_ids (one2many) : DEL relation: hr.attendance.overtime.line +# NOTHING TO DO: a one2many has no column, so nothing is left behind +hr_attendance / hr.time.rule / condition_work_entry_type_ids (False): NEW mode: modify, hasdefault: default +hr_attendance / hr.version / attendance_based (boolean) : NEW hasdefault: default +# NOTHING TO DO: a new field whose default is falsy -- False, or an empty +# list -- so a NULL left on an existing row already means what the default +# would have written. The one flagged field whose default names a record, +# pos.config.default_product_id, has a script instead +hr_attendance / hr.version / ruleset_id (many2one) : DEL relation: hr.attendance.overtime.ruleset +# NOTHING TO DO: the column is left in place for database_cleanup +hr_attendance / res.company / attendance_based (boolean) : NEW hasdefault: default +# NOTHING TO DO: a new field whose default is falsy -- False, or an empty +# list -- so a NULL left on an existing row already means what the default +# would have written. The one flagged field whose default names a record, +# pos.config.default_product_id, has a script instead +hr_attendance / res.company / attendance_break_management (boolean): NEW +# NOTHING TO DO: empty is the right value for existing records +hr_attendance / res.company / attendance_capture_check_in (boolean): NEW hasdefault: default +# NOTHING TO DO: a new field whose default is falsy -- False, or an empty +# list -- so a NULL left on an existing row already means what the default +# would have written. The one flagged field whose default names a record, +# pos.config.default_product_id, has a script instead +hr_attendance / res.company / attendance_overtime_validation (selection): DEL selection_keys: ['by_manager', 'no_validation'] +hr_attendance / res.company / attendance_validation (selection): NEW selection_keys: ['manual_validation', 'no_validation', 'tolerance_validation'], hasdefault: default +# DONE: pre-migration keeps the 19.0 answer and post-migration writes +# by_manager across as manual_validation. Not a selection whose keys changed but +# a field renamed: the new column takes its own default, so a company that +# required a manager to approve extra hours comes out approving them +# automatically -- shown as the other answer rather than as empty, which is why +# neither the selection gate nor a filled-versus-empty check can see it +hr_attendance / res.company / attendance_validation_tolerance (float): NEW hasdefault: default +hr_attendance / res.company / attendance_work_entry_type_id (many2one): NEW relation: hr.work.entry.type, hasdefault: compute +hr_attendance / res.company / auto_check_out_mode (selection): NEW selection_keys: ['specific_time', 'tolerance'], hasdefault: default +hr_attendance / res.company / auto_check_out_specific_time (float): NEW hasdefault: default +# NOTHING TO DO: a new field whose default is falsy -- False, or an empty +# list -- so a NULL left on an existing row already means what the default +# would have written. The one flagged field whose default names a record, +# pos.config.default_product_id, has a script instead +hr_attendance / res.company / hr_attendance_display_overtime (boolean): DEL +hr_attendance / res.company / overtime_company_threshold (integer): DEL +hr_attendance / res.company / overtime_employee_threshold (integer): DEL +# NOTHING TO DO: the column is left in place for database_cleanup +hr_attendance / res.company / single_check_in (boolean) : NEW +# NOTHING TO DO: empty is the right value for existing records +---XML records in module 'hr_attendance'--- +DEL hr.attendance.overtime.rule: hr_attendance.hr_attendance_overtime_employee_schedule_rule (noupdate) +DEL hr.attendance.overtime.rule: hr_attendance.hr_attendance_overtime_non_working_days_rule (noupdate) +DEL hr.attendance.overtime.rule: hr_attendance.hr_attendance_overtime_rule_1_ae (noupdate) +DEL hr.attendance.overtime.rule: hr_attendance.hr_attendance_overtime_rule_2_ae (noupdate) +DEL hr.attendance.overtime.rule: hr_attendance.hr_attendance_overtime_rule_3_ae (noupdate) +DEL hr.attendance.overtime.rule: hr_attendance.hr_attendance_overtime_rule_4_ae (noupdate) +DEL hr.attendance.overtime.ruleset: hr_attendance.hr_attendance_default_ruleset (noupdate) +DEL hr.attendance.overtime.ruleset: hr_attendance.l10n_ae_hr_attendance_overtime_ruleset (noupdate) +# NOTHING TO DO: a noupdate record the module stops shipping. _process_end +# only removes stale data rows whose noupdate is false, so it survives -- +# 583 of the 753 such records on this seed do. Writing an orphan cleanup +# is the anti-pattern upstream rejected in #5630-5632: OpenUpgrade +# preserves information and database_cleanup removes residue later, once +# an administrator can see what would go +NEW ir.access: hr_attendance.access_hr_time_rule_attendance_admin +NEW ir.access: hr_attendance.hr_attendance_rule_attendance_admin +NEW ir.access: hr_attendance.hr_attendance_rule_attendance_manager_restrict +NEW ir.access: hr_attendance.hr_attendance_rule_attendance_own +NEW ir.access: hr_attendance.hr_attendance_rule_attendance_simple_user +NEW ir.access: hr_attendance.hr_attendance_rule_employee_company +# NOTHING TO DO: base folds ir.model.access and ir.rule into ir.access, keeping the xml_ids +ir.actions.act_window: hr_attendance.hr_attendance_management_action (deleted domain) +# NOTHING TO DO: the record is not noupdate, so the module update rewrites the domain +NEW ir.actions.act_window: hr_attendance.hr_attendance_my_action +# NOTHING TO DO: the record is not noupdate, so the module update creates it +DEL ir.actions.act_window: hr_attendance.hr_attendance_overtime_rule_action +DEL ir.actions.act_window: hr_attendance.hr_attendance_overtime_ruleset_action +# NOTHING TO DO: the record is not noupdate, so the module update removes it +NEW ir.actions.server: hr_attendance.action_attendance_refuse +NEW ir.actions.server: hr_attendance.action_attendance_validate +NEW ir.cron: hr_attendance.hr_attendance_day_undertime_rules_cron +NEW ir.cron: hr_attendance.hr_attendance_week_time_rules_cron +# NOTHING TO DO: the record is not noupdate, so the module update creates it +DEL ir.model.access: hr_attendance.access_hr_attendance_admin +DEL ir.model.access: hr_attendance.access_hr_attendance_officer +DEL ir.model.access: hr_attendance.access_hr_attendance_overtime_line_officer +DEL ir.model.access: hr_attendance.access_hr_attendance_overtime_line_own_reader +DEL ir.model.access: hr_attendance.access_hr_attendance_overtime_rule_admin +DEL ir.model.access: hr_attendance.access_hr_attendance_overtime_rule_officer +DEL ir.model.access: hr_attendance.access_hr_attendance_overtime_rule_user +DEL ir.model.access: hr_attendance.access_hr_attendance_overtime_ruleset_admin +DEL ir.model.access: hr_attendance.access_hr_attendance_overtime_ruleset_hr_manager +DEL ir.model.access: hr_attendance.access_hr_attendance_own_reader +DEL ir.model.access: hr_attendance.access_hr_attendance_user +# NOTHING TO DO: base folds ir.model.access and ir.rule into ir.access, keeping the xml_ids +NEW ir.model.constraint: hr_attendance.constraint_res_company_check_auto_check_out_specific_time_range +# NOTHING TO DO: the record is not noupdate, so the module update creates it +DEL ir.model.constraint: hr_attendance.constraint_hr_attendance_overtime_line_overtime_start_before_end +DEL ir.model.constraint: hr_attendance.constraint_hr_attendance_overtime_rule_timing_start_is_hour +DEL ir.model.constraint: hr_attendance.constraint_hr_attendance_overtime_rule_timing_stop_is_hour +# NOTHING TO DO: the record is not noupdate, so the module update removes it +DEL ir.rule: hr_attendance.hr_attendance_overtime_line_rule_admin (noupdate) +DEL ir.rule: hr_attendance.hr_attendance_overtime_line_rule_employee_company (noupdate) +DEL ir.rule: hr_attendance.hr_attendance_overtime_line_rule_officer (noupdate) +DEL ir.rule: hr_attendance.hr_attendance_overtime_line_rule_own_reader (noupdate) +DEL ir.rule: hr_attendance.hr_attendance_overtime_ruleset_rule (noupdate) +DEL ir.rule: hr_attendance.hr_attendance_overtime_ruleset_rule_group_hr_attendance_manager (noupdate) +DEL ir.rule: hr_attendance.hr_attendance_rule_attendance_admin (noupdate) +DEL ir.rule: hr_attendance.hr_attendance_rule_attendance_manager_restrict (noupdate) +DEL ir.rule: hr_attendance.hr_attendance_rule_attendance_simple_user (noupdate) +DEL ir.rule: hr_attendance.hr_attendance_rule_employee_company (noupdate) +# NOTHING TO DO: base folds ir.model.access and ir.rule into ir.access, keeping the xml_ids +NEW ir.ui.menu: hr_attendance.menu_hr_attendance_management_list +NEW ir.ui.menu: hr_attendance.menu_hr_attendance_my_attendances +NEW ir.ui.menu: hr_attendance.menu_hr_attendance_time_rules +NEW ir.ui.menu: hr_attendance.menu_hr_attendance_work_entry_type +# NOTHING TO DO: the record is not noupdate, so the module update creates it +DEL ir.ui.menu: hr_attendance.menu_hr_attendance_overtime_rulesets +DEL ir.ui.menu: hr_attendance.menu_hr_attendance_overview +# NOTHING TO DO: the record is not noupdate, so the module update removes it +NEW ir.ui.view: hr_attendance.hr_attendance_create_view_form +NEW ir.ui.view: hr_attendance.hr_attendance_employee_calendar_view +NEW ir.ui.view: hr_attendance.hr_attendance_my_calendar_view +NEW ir.ui.view: hr_attendance.hr_attendance_my_form_view +NEW ir.ui.view: hr_attendance.hr_contract_template_view_form +NEW ir.ui.view: hr_attendance.hr_version_list_view_enhanced_hr_attendance_base +NEW ir.ui.view: hr_attendance.hr_version_search_view_enhanced_hr_attendance +# NOTHING TO DO: the record is not noupdate, so the module update creates it +DEL ir.ui.view: hr_attendance.hr_attendance_employee_simple_tree_view +DEL ir.ui.view: hr_attendance.hr_attendance_overtime_rule_view_form +DEL ir.ui.view: hr_attendance.hr_attendance_overtime_rule_view_list +DEL ir.ui.view: hr_attendance.hr_attendance_overtime_ruleset_view_filter +DEL ir.ui.view: hr_attendance.hr_attendance_overtime_ruleset_view_form +DEL ir.ui.view: hr_attendance.hr_attendance_overtime_ruleset_view_list +# NOTHING TO DO: the record is not noupdate, so the module update removes it +res.groups: base.group_user (noupdate switched) +# NOTHING TO DO: only the noupdate flag changed, the definition is the same +NEW res.groups: hr_attendance.group_hr_attendance_own +# NOTHING TO DO: the record is not noupdate, so the module update creates it diff --git a/openupgrade_scripts/scripts/hr_attendance/tests/data_hr_attendance_migration.py b/openupgrade_scripts/scripts/hr_attendance/tests/data_hr_attendance_migration.py new file mode 100644 index 00000000000..fc569b62265 --- /dev/null +++ b/openupgrade_scripts/scripts/hr_attendance/tests/data_hr_attendance_migration.py @@ -0,0 +1,23 @@ +env = locals().get("env") + +# 19.0 asked whether extra hours needed a manager's approval through +# res.company.attendance_overtime_validation. 20.0 asks the same question as +# attendance_validation, a different column, which therefore takes its own +# default -- so by_manager is not carried and the company silently starts +# approving extra hours automatically. +# +# Every one of the seed's 117 companies is on no_validation, which is also the +# 20.0 default, so the seed agrees with the broken behaviour and the fix would +# be asserted by a database that cannot disagree. by_manager has to be planted. +company = env["res.company"].search([], order="id", limit=1) +assert company, "the seed needs a company" +company.attendance_overtime_validation = "by_manager" + +# One, deliberately: the test asserts the others were left alone, which is what +# says the carry wrote only where 19.0 had an answer to carry. +others = env["res.company"].search_count( + [("id", "!=", company.id), ("attendance_overtime_validation", "=", "by_manager")] +) +assert not others, "another company is already on by_manager" + +env.cr.commit() diff --git a/openupgrade_scripts/scripts/hr_attendance/tests/test_hr_attendance_migration.py b/openupgrade_scripts/scripts/hr_attendance/tests/test_hr_attendance_migration.py new file mode 100644 index 00000000000..7f40b25a412 --- /dev/null +++ b/openupgrade_scripts/scripts/hr_attendance/tests/test_hr_attendance_migration.py @@ -0,0 +1,83 @@ +from openupgradelib import openupgrade + +from odoo.tests import TransactionCase + +from odoo.addons.openupgrade_framework import openupgrade_test + + +@openupgrade_test +class TestHrAttendanceMigration(TransactionCase): + """Assertions on extra-hours approval surviving its rename. + + Every method opens by asserting the record exists. The data snippet runs in + a separate process through the 19.0 shell, and if its commit were lost it + would exit 0 having written nothing -- an assertion whose empty case is a + pass would then report success for a migration that never happened. + """ + + def setUp(self): + super().setUp() + self.legacy = openupgrade.get_legacy_name("attendance_overtime_validation") + self.env.cr.execute( + """ + SELECT count(*) FROM information_schema.columns + WHERE table_name = 'res_company' AND column_name = %s + """, + (self.legacy,), + ) + self.assertTrue( + self.env.cr.fetchone()[0], + f"res_company.{self.legacy} is gone, so the 19.0 answer was not kept", + ) + + def _companies_on(self, value): + self.env.cr.execute( + f"SELECT id FROM res_company WHERE {self.legacy} = %s ORDER BY id", (value,) + ) + return [row[0] for row in self.env.cr.fetchall()] + + def test_a_company_that_required_approval_still_requires_it(self): + """by_manager is 20.0's manual_validation under a new name.""" + ids = self._companies_on("by_manager") + self.assertEqual( + len(ids), 1, "the fixture company is missing from the migrated database" + ) + company = self.env["res.company"].browse(ids[0]) + self.assertEqual( + company.attendance_validation, + "manual_validation", + "the company stopped requiring approval for extra hours", + ) + + def test_a_company_that_did_not_is_left_where_the_default_puts_it(self): + """no_validation is what 20.0 defaults to, so nothing is written for it. + + This is the half that says the carry is targeted. A blanket write would + pass the test above and still be wrong. + """ + ids = self._companies_on("no_validation") + self.assertTrue(ids, "the seed has no company on no_validation to check") + self.env.cr.execute( + """ + SELECT count(*) FROM res_company + WHERE id = ANY(%s) AND attendance_validation != 'no_validation' + """, + (ids,), + ) + self.assertEqual( + self.env.cr.fetchone()[0], + 0, + "a company that approved extra hours automatically was changed", + ) + + def test_nothing_was_written_where_19_0_had_no_answer(self): + """tolerance_validation is new in 20.0 and cannot be carried into.""" + self.env.cr.execute( + "SELECT count(*) FROM res_company WHERE attendance_validation = %s", + ("tolerance_validation",), + ) + self.assertEqual( + self.env.cr.fetchone()[0], + 0, + "a 20.0-only answer was invented for a 19.0 company", + )