Skip to content

[OU-ADD] calendar, resource: migration scripts from 19.0 to 20.0 - #3

Open
adekock11 wants to merge 1 commit into
20.0from
cursor/resource-calendar-20-e794
Open

adekock11 wants to merge 1 commit into
20.0from
cursor/resource-calendar-20-e794

Conversation

@adekock11

Copy link
Copy Markdown
Owner

Summary

Migration scripts for the 19.0 → 20.0 working-time and meetings stack:

  • resource (real data migration)
  • resource_mail (field moved, no script)
  • calendar (real data migration)
  • calendar_sms (one new view, no script)

calendar events used to hang off the organiser with no calendar record. 20.0 introduces calendar.calendar and calendar.user, and stores the default privacy on the calendar instead of res.users.settings. resource replaces schedule_type / flexible_hours / two_weeks_calendar with calendar_type (fixed, variable, undefined), drops per-calendar timezones and lunch rows, and makes resource.resource.calendar_id required.

Nothing from OCA#6068 is cherry-picked onto this branch. The scripts assume that PR's base migration and openupgrade_framework are present when the upgrade runs. upgrade_analysis.txt, noupdate_changes.xml, and the module table in docsource/modules190-200.rst are copied from hbrunn/OpenUpgrade 20.0-base at 2d9eb6bab7e7e579953e38d7ad3b96aad6fcc44d (the OCA#6068 head on 30 Oct 2026). The only edits on that table are the status cells for these four modules. After OCA#6068 is merged, rebasing this branch should leave a four-line docsource diff.

What is not in this PR

These showed up next to calendar/resource in the 19→20 analysis and should be their own PRs:

  • google_calendar. google.calendar.sync is replaced by google.event.sync / google.sync, and res.users.settings.google_calendar_cal_id moves to calendar.calendar.google_id. Odoo 20 does not drop removed columns, so that id is still readable for a follow-up. This PR does not touch it.
  • microsoft_calendar. New microsoft_account_email and views. No shared data with the scripts here.
  • hr_calendar, hr_homeworking_calendar, hr_calendar_google. hr_homeworking_calendar is merged into hr_calendar (homework.location.wizard moves). That is an HR merge, not part of the resource/calendar model change.

Dependency on OCA#6068

ir.model.access and ir.rule are gone; both modules' "DEL ir.model.access / DEL ir.rule / NEW ir.access" lines are the base merge into ir.access. OCA#6068 renames the old xmlids and keeps manual rules. This module's XML recreates the standard ones. These scripts do not repeat that work.

Runtime also needs openupgrade_framework on the addons path (server-wide) and --upgrade-path pointing at openupgrade_scripts/scripts.

openupgradelib 3.13.7 (current PyPI) calls odoo.tools.convert_xml_import. Odoo 20 no longer re-exports that name (odoo/tools/__init__.py only exports convert_file; the function is odoo.tools.convert.convert_xml_import). openupgrade.load_data therefore crashes on 20.0, including the base post-migration in OCA#6068. The test below patched the installed library to call odoo.tools.convert.convert_xml_import directly. Upstream openupgradelib needs that fix before this series runs unmodified.

Calendar

Odoo change: odoo/odoo@c03c350 ([IMP] calendar: introduce multiple calendars per user, task 5485938). Structure is res.users → calendar.user → calendar.calendar → calendar.event. Each internal user has exactly one primary calendar, created by res.users.create and by a post_init_hook that runs on install only, not on upgrade. calendar_default_privacy moves from res.users.settings onto calendar.calendar and gains members_only. calendar.filters.partner_checked is removed; the view uses active for other people and calendar.user.is_filter_checked for the user's own calendar. calendar.event.access_token becomes unique.

Without a script, existing events have no calendar, internal users who never organise a meeting have no primary calendar, per-user privacy falls back to the system parameter (the compute uses calendar.default_privacy or public, not the settings row), and duplicate invitation tokens abort the upgrade on the new unique constraint. Unchecked attendee filters would start showing those people's events.

pre-migration

Runs before the unique constraint and before partner_checked stops being a field (the column is kept — 20.0 only drops NOT NULL on removed fields — but the pre script is where the flag still means "shown").

  • calendar.filters.active is set false where partner_checked is false. v20 has no "listed but unchecked" state, so that distinction is dropped on purpose: hidden partners stay hidden.
  • Empty access_token values become NULL (multiple NULLs are allowed). Duplicate non-null tokens are regenerated with gen_random_uuid(), except the lowest id, so that event's existing links keep working.

post-migration

Runs after calendar.calendar / calendar.user exist. The event calendar_id compute may already have created a primary calendar for organisers while the new column was initialised (_find_or_create_primary_calendar). The post script fills the rest.

  • Every non-share user, including archived ones, gets a primary owner membership if they do not have one, via _generate_primary_calendar. Portal and public users are left alone, matching the install hook.
  • primary_calendar_id is backfilled from that membership when the stored compute is still NULL.
  • calendar_default_privacy is copied from res_users_settings when the value is public, private, or confidential. Users with no settings row keep the value the generator stored.
  • Events with an organiser and an empty calendar_id point at that primary calendar. Events with no organiser stay without one; the field is not required. calendar.recurrence.calendar_id is a non-stored related and is not in the analysis.
  • If the user's own partner filter was unchecked, calendar.user.is_filter_checked is set false.
  • noupdate_changes.xml reloads the meeting mail templates and the subtype_invitation label. Translations of those records are deleted so a stale translation does not hide the new English body.

NOTHING TO DO

  • Transient calendar.popover.delete.wizard and calendar.provider.config: no business data.
  • New archive wizards, is_draft (existing rows are not drafts; NULL passes CHECK(NOT (is_draft AND recurrency))), meeting_activity_ids (inverse of the existing mail.activity.calendar_event_id).
  • privacy / effective_privacy gain members_only. effective_privacy is not stored. Old keys are unchanged.
  • res.users.activity_calendar_event_id: non-stored compute on mail.activity.mixin in both versions (the analysis flags "now a function"; there is no column). The compute body changed from next(iter(...)) to activities[:1].
  • calendar_ids is not stored. calendar_user_ids is the one2many of the membership rows above.
  • res.users.settings.calendar_show_activities: new boolean, ORM default True.
  • New alarm, menus, views, actions, and the new mt_calendar_event_updated subtype: loaded by the module update.
  • ir.access / ir.model.access / ir.rule: base migration, see above.
  • The single-primary and unique-membership constraints hold because the post script inserts one owner row per user, after the ORM has created the indexes.

Resource

Odoo changes, in the order they landed and then got revised:

  • odoo/odoo@5cb102546 resource calendar rework (schedule_type flexible / fully fixed). That is the 19.0 shape.
  • odoo/odoo@2dff65ab refactor: remove two-week mode, lunch, attendance name, duration_days, and the calendar timezone. The commit message says flexible calendars disappear and an empty calendar_id means flexible. That intermediate state is not what 20.0 ships.
  • odoo/odoo@31b77987 variable calendars: dated attendances with a recurrence, explicitly replacing two-week mode.
  • Current 20.0 (this is what the scripts target): calendar_type is required and is fixed (weekly lines), variable (dated, optionally recurring), or undefined (no slots, optional hour targets). resource.resource.calendar_id is required. res.company.tz is a new stored field. resource.calendar.tz is removed. time_type (leave/other) becomes count_as (absence/working_time). odoo/odoo@8084b882 leaves reference_calendar_id empty by default.

Without a script the upgrade either fails the new duration_hours > 0 check (lunch and section rows are 0) or silently mis-classifies data: every calendar would default to fixed, the new computes would rebuild hours_per_week / hours_per_day from attendances, and flexible hour targets would be lost. Resources with no calendar (v19: fully flexible, the whole interval counts as work) would be filled with the env company's fixed calendar. Company timezone would be recomputed from the country (a US company would not keep America/Los_Angeles). time_type = other (training, counted as work) would become the default absence.

pre-migration

Has to run before constraints and before the ORM fills new stored computes. New columns are created and filled here so init sees no NULLs and does not recompute.

  • Obsolete pytz names on resource_calendar.tz and resource_resource.tz are mapped the same way base maps res.partner.tz: Europe/Kiev → Europe/Kyiv, Portugal → Europe/Lisbon, Poland → Europe/Warsaw.
  • Lunch rows and display_type = line_section rows are deleted. Lunch is no longer a period; v20 treats the gap between slots as the break. Sections only labelled the two weeks.
  • Attendances with clock hours 0/0 and a positive duration_hours are given noon-centred hours (12 ± duration/2), which is what the v19 inverse already did and what v20 does for duration-based slots. v20's new stored duration_based is "both hours are 0". If those rows stayed at 0/0, the duration_hours compute (it only assigns when duration_based is false, and an unassigned computed value is written as 0) would zero them and fail CHECK(duration_hours > 0 AND duration_hours <= 24). The analysis line "duration_hours is not a function anymore" does not match current 20.0, where the field is still computed and stored. The pre script makes either shape safe.
  • Remaining rows with a non-positive or > 24 duration are deleted or rebuilt from hour_to - hour_from.
  • Resources with calendar_id NULL get one shared Fully Flexible calendar per company (schedule_type = flexible, 0 hours/day and hours/week). v20 then sees calendar_type = undefined and _is_fully_flexible() is true, which is the v19 "no calendar" behaviour. Doing this before init stops the ORM default from assigning the company calendar.
  • calendar_type: flexible → undefined, two-week → variable, otherwise fixed.
  • days_per_week is filled so hours_per_day (computed as hours_per_week / days_per_week, and that compute does not skip non-fixed calendars) is not rewritten. Fixed: distinct weekdays. Variable: the v19 two-week average (count of week-type 0 days plus week-type 1 days, divided by 2). Undefined: hours_per_week / hours_per_day, or 0 when there is no daily figure.
  • res_company.tz is added and set from the partner timezone (v19 company.tz was a non-stored related of partner_id.tz), else the company's default calendar tz, else UTC. A filled column skips the new country-based compute.
  • resource.calendar.leaves.time_type is renamed to count_as and mapped leave → absence, other → working_time. NULL or anything else becomes absence.

post-migration

  • Variable attendances are dated. v19 week_type is floor((date.toordinal() - 1) / 7) % 2, so a weekday keeps its week type every 14 days. Each line is anchored on the matching weekday in the fortnight starting Monday 2024-01-01 (verified: 2024-01-01 is week type 1, 2024-01-08 is week type 0) and repeats every 2 weeks until 9999-12-31. A line with no week type repeats every week. Fixed calendars stay undated; their new recurrence columns stay at the ORM defaults (recurrency null), which the checks allow. The recurrence checks are added at init, while recurrency is still null; the post script sets interval, end date, and date in one UPDATE.
  • Attendances of undefined calendars are deleted. v19 ignored them while the calendar was flexible, and the new resource.ir_cron_calendar_clean_up cron would delete them on its first run.
  • Calendars whose old tz differs from the company tz are logged. That tz is not representable anymore. resource.resource.tz is unchanged apart from the obsolete-name map, and that is the timezone v20 uses for a resource.
  • noupdate_changes.xml renames resource.resource_calendar_std from "Standard 40 hours/week" to "40 hours/week". The record is noupdate, so a normal module update would not touch it.

NOTHING TO DO

  • reference_calendar_id: optional, and 20.0 has no default. full_time_required_hours already exists, so the changed compute is not run for every row.
  • work_time_rate: v19 did not store it, and scaled it by 100. There is no column to divide. The ORM writes the new ratio (hours_per_week / full_time_required_hours) into the new column.
  • Attendance _order, name, and duration_days: display / dropped fields. Odoo 20 leaves the orphan columns in place (it no longer drops them).
  • resource.resource.color moved from resource_mail: the column stays. The ORM retargets ir.model.fields when resource is reflected, which is before resource_mail because of the dependency. im_status was a non-stored related.
  • New views, the material-resource action, and the cleanup cron: loaded by the module update.
  • ir.access / ir.model.access / ir.rule: base migration, same as calendar.

calendar_sms and resource_mail

calendar_sms only adds calendar_alarm_view_kanban. The module update loads it. The v19 rename of sms_notify_responsible already lives in the 19.0 calendar scripts.

resource_mail only loses color to resource, handled above.

Testing

Fresh Odoo 19.0 and 20.0 from github.com/odoo/odoo, local PostgreSQL 16 only.

  1. Installed 19.0 with --with-demo for resource, calendar, and calendar_sms (resource_mail auto-installed). Demo gave 7 events, the standard / 35h / 38h / flexible calendars, and 60 attendances including lunch rows.
  2. Seeded, still on 19.0: company partner tz America/New_York (country compute would not keep that), admin calendar_default_privacy = private, one attendee filter unchecked, duplicate and empty access tokens, a noon-centred duration line (hours 0/0, duration 6) on the 35h calendar, a two-week calendar with section rows, a lunch row, week-type 0 and 1 Monday slots and a week-type-less Tuesday, Europe/Kiev on the 38h calendar and on a resource, a resource with no calendar, and leaves with time_type leave and other.
  3. Upgraded that database with Odoo 20.0, -u all, openupgrade_framework and the [20.0][MIG] base OCA/OpenUpgrade#6068 base scripts on the upgrade path, plus these scripts. The openupgradelib patch described above was applied in the test virtualenv only.

Result: upgrade finished, all five modules at their 20.0 versions. Checked afterwards:

  • Company tz stayed America/New_York. The four calendars still on Europe/Brussels were logged as differing from the company.
  • Standard calendar renamed to "40 hours/week", fixed, 5 days, 8 h/day, 40 h/week. Lunch rows gone.
  • 35h stayed fixed at 7 h/day and 35 h/week. The duration line became 09:00–15:00 (full_day, duration 6).
  • 38h tz Europe/Kiev became Europe/Kyiv.
  • Flexible 40h is undefined, attendances removed, 8 h/day and 40 h/week kept, days_per_week 5.
  • The empty-calendar resource points at a shared "Fully Flexible" undefined calendar with 0 hours. Its own tz is Europe/Kyiv.
  • Two-week lines: week type 1 → 2024-01-01 repeating every 2 weeks, week type 0 → 2024-01-08 every 2 weeks, the typeless Tuesday → 2024-01-09 every week, recurrency_until 9999-12-31. Sections and the lunch row are gone. No attendance has duration_hours outside (0, 24].
  • Leaves: holiday → absence, training → working_time.
  • __system__, admin, and demo each have one primary owner calendar. Admin privacy is private; the others are public. Portal and public users have none.
  • Events sit on the organiser's primary calendar. The event with no organiser has no calendar. The lowest id kept duplicate-token; the other copy was regenerated; the empty token is NULL.
  • The unchecked attendee filter is inactive.

The OpenUpgrade framework logged, and continued past, one foreign-key failure while deleting obsolete xmlid base.res_partner_1: that partner is still referenced by calendar_filters. The filter is real data, so keeping the partner is the right outcome. Not caused by these scripts.

Known gaps

  • A calendar timezone that is not the company timezone and not the resource timezone cannot be stored. Scheduling for a resource still uses resource.resource.tz. Company-level math uses res.company.tz. The post script logs the mismatches.
  • Two-week → variable uses a 14-day recurrence forever. Excluded dates and "this week / other week" labels are not carried (the section rows had no working time). A line with no week_type on a two-week calendar becomes weekly, which matches v19 treating it as both weeks only where the old code did; _get_days_per_week ignored those lines and so does days_per_week here.
  • "Listed but unchecked" attendee filters become inactive. There is nowhere to store the listed-but-hidden state.
  • Duplicate access tokens: one of the duplicates gets a new token, so old links to that copy stop working. The oldest row keeps its token.
  • google_calendar_cal_id is intentionally left for the Google sync PR. The column remains.
  • openupgradelib 3.13.7 cannot load noupdate XML on Odoo 20 until convert_xml_import is imported from odoo.tools.convert. See above.
  • [20.0][MIG] base OCA/OpenUpgrade#6068 must be applied for ir.access and for these scripts to be the only migration those modules need. This branch does not contain the framework or the base scripts.
Open in Web Open in Cursor 

Create primary calendars and preserve per-user privacy, and rewrite
resource schedules onto calendar_type (fixed, variable, undefined).
calendar_sms and resource_mail have no data migration.

Co-authored-by: André de Kock <adekock11@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants