Repository navigation
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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)calendarevents used to hang off the organiser with no calendar record. 20.0 introducescalendar.calendarandcalendar.user, and stores the default privacy on the calendar instead ofres.users.settings.resourcereplacesschedule_type/flexible_hours/two_weeks_calendarwithcalendar_type(fixed,variable,undefined), drops per-calendar timezones and lunch rows, and makesresource.resource.calendar_idrequired.Nothing from OCA#6068 is cherry-picked onto this branch. The scripts assume that PR's
basemigration andopenupgrade_frameworkare present when the upgrade runs.upgrade_analysis.txt,noupdate_changes.xml, and the module table indocsource/modules190-200.rstare copied fromhbrunn/OpenUpgrade20.0-baseat2d9eb6bab7e7e579953e38d7ad3b96aad6fcc44d(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.syncis replaced bygoogle.event.sync/google.sync, andres.users.settings.google_calendar_cal_idmoves tocalendar.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_account_emailand views. No shared data with the scripts here.hr_homeworking_calendaris merged intohr_calendar(homework.location.wizardmoves). That is an HR merge, not part of the resource/calendar model change.Dependency on OCA#6068
ir.model.accessandir.ruleare gone; both modules' "DEL ir.model.access / DEL ir.rule / NEW ir.access" lines are the base merge intoir.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_frameworkon the addons path (server-wide) and--upgrade-pathpointing atopenupgrade_scripts/scripts.openupgradelib3.13.7 (current PyPI) callsodoo.tools.convert_xml_import. Odoo 20 no longer re-exports that name (odoo/tools/__init__.pyonly exportsconvert_file; the function isodoo.tools.convert.convert_xml_import).openupgrade.load_datatherefore crashes on 20.0, including thebasepost-migration in OCA#6068. The test below patched the installed library to callodoo.tools.convert.convert_xml_importdirectly. 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 isres.users→calendar.user→calendar.calendar→calendar.event. Each internal user has exactly one primary calendar, created byres.users.createand by apost_init_hookthat runs on install only, not on upgrade.calendar_default_privacymoves fromres.users.settingsontocalendar.calendarand gainsmembers_only.calendar.filters.partner_checkedis removed; the view usesactivefor other people andcalendar.user.is_filter_checkedfor the user's own calendar.calendar.event.access_tokenbecomes 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_privacyorpublic, 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_checkedstops 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.activeis set false wherepartner_checkedis false. v20 has no "listed but unchecked" state, so that distinction is dropped on purpose: hidden partners stay hidden.access_tokenvalues become NULL (multiple NULLs are allowed). Duplicate non-null tokens are regenerated withgen_random_uuid(), except the lowest id, so that event's existing links keep working.post-migration
Runs after
calendar.calendar/calendar.userexist. The eventcalendar_idcompute 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._generate_primary_calendar. Portal and public users are left alone, matching the install hook.primary_calendar_idis backfilled from that membership when the stored compute is still NULL.calendar_default_privacyis copied fromres_users_settingswhen the value ispublic,private, orconfidential. Users with no settings row keep the value the generator stored.calendar_idpoint at that primary calendar. Events with no organiser stay without one; the field is not required.calendar.recurrence.calendar_idis a non-stored related and is not in the analysis.calendar.user.is_filter_checkedis set false.noupdate_changes.xmlreloads the meeting mail templates and thesubtype_invitationlabel. Translations of those records are deleted so a stale translation does not hide the new English body.NOTHING TO DO
calendar.popover.delete.wizardandcalendar.provider.config: no business data.is_draft(existing rows are not drafts; NULL passesCHECK(NOT (is_draft AND recurrency))),meeting_activity_ids(inverse of the existingmail.activity.calendar_event_id).privacy/effective_privacygainmembers_only.effective_privacyis not stored. Old keys are unchanged.res.users.activity_calendar_event_id: non-stored compute onmail.activity.mixinin both versions (the analysis flags "now a function"; there is no column). The compute body changed fromnext(iter(...))toactivities[:1].calendar_idsis not stored.calendar_user_idsis the one2many of the membership rows above.res.users.settings.calendar_show_activities: new boolean, ORM default True.mt_calendar_event_updatedsubtype: loaded by the module update.ir.access/ir.model.access/ir.rule: base migration, see above.Resource
Odoo changes, in the order they landed and then got revised:
schedule_typeflexible / fully fixed). That is the 19.0 shape.duration_days, and the calendar timezone. The commit message says flexible calendars disappear and an emptycalendar_idmeans flexible. That intermediate state is not what 20.0 ships.calendar_typeis required and isfixed(weekly lines),variable(dated, optionally recurring), orundefined(no slots, optional hour targets).resource.resource.calendar_idis required.res.company.tzis a new stored field.resource.calendar.tzis removed.time_type(leave/other) becomescount_as(absence/working_time). odoo/odoo@8084b882 leavesreference_calendar_idempty by default.Without a script the upgrade either fails the new
duration_hours > 0check (lunch and section rows are 0) or silently mis-classifies data: every calendar would default tofixed, the new computes would rebuildhours_per_week/hours_per_dayfrom 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 keepAmerica/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.
resource_calendar.tzandresource_resource.tzare mapped the same way base mapsres.partner.tz:Europe/Kiev→Europe/Kyiv,Portugal→Europe/Lisbon,Poland→Europe/Warsaw.display_type = line_sectionrows are deleted. Lunch is no longer a period; v20 treats the gap between slots as the break. Sections only labelled the two weeks.duration_hoursare 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 storedduration_basedis "both hours are 0". If those rows stayed at 0/0, theduration_hourscompute (it only assigns whenduration_basedis false, and an unassigned computed value is written as 0) would zero them and failCHECK(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.hour_to - hour_from.calendar_idNULL get one sharedFully Flexiblecalendar per company (schedule_type = flexible, 0 hours/day and hours/week). v20 then seescalendar_type = undefinedand_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, otherwisefixed.days_per_weekis filled sohours_per_day(computed ashours_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.tzis added and set from the partner timezone (v19company.tzwas a non-stored related ofpartner_id.tz), else the company's default calendar tz, else UTC. A filled column skips the new country-based compute.resource.calendar.leaves.time_typeis renamed tocount_asand mappedleave→absence,other→working_time. NULL or anything else becomesabsence.post-migration
week_typeisfloor((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 (recurrencynull), which the checks allow. The recurrence checks are added at init, whilerecurrencyis still null; the post script sets interval, end date, anddatein one UPDATE.undefinedcalendars are deleted. v19 ignored them while the calendar was flexible, and the newresource.ir_cron_calendar_clean_upcron would delete them on its first run.resource.resource.tzis unchanged apart from the obsolete-name map, and that is the timezone v20 uses for a resource.noupdate_changes.xmlrenamesresource.resource_calendar_stdfrom "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_hoursalready 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._order,name, andduration_days: display / dropped fields. Odoo 20 leaves the orphan columns in place (it no longer drops them).resource.resource.colormoved fromresource_mail: the column stays. The ORM retargetsir.model.fieldswhenresourceis reflected, which is beforeresource_mailbecause of the dependency.im_statuswas a non-stored related.ir.access/ir.model.access/ir.rule: base migration, same as calendar.calendar_sms and resource_mail
calendar_smsonly addscalendar_alarm_view_kanban. The module update loads it. The v19 rename ofsms_notify_responsiblealready lives in the 19.0 calendar scripts.resource_mailonly losescolortoresource, handled above.Testing
Fresh Odoo 19.0 and 20.0 from github.com/odoo/odoo, local PostgreSQL 16 only.
--with-demoforresource,calendar, andcalendar_sms(resource_mailauto-installed). Demo gave 7 events, the standard / 35h / 38h / flexible calendars, and 60 attendances including lunch rows.America/New_York(country compute would not keep that), admincalendar_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/Kievon the 38h calendar and on a resource, a resource with no calendar, and leaves withtime_typeleaveandother.-u all,openupgrade_frameworkand the [20.0][MIG] base OCA/OpenUpgrade#6068basescripts 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:
America/New_York. The four calendars still onEurope/Brusselswere logged as differing from the company.fixed, 5 days, 8 h/day, 40 h/week. Lunch rows gone.fixedat 7 h/day and 35 h/week. The duration line became 09:00–15:00 (full_day, duration 6).Europe/KievbecameEurope/Kyiv.undefined, attendances removed, 8 h/day and 40 h/week kept,days_per_week5.undefinedcalendar with 0 hours. Its own tz isEurope/Kyiv.recurrency_until9999-12-31. Sections and the lunch row are gone. No attendance hasduration_hoursoutside (0, 24].absence, training →working_time.__system__, admin, and demo each have one primary owner calendar. Admin privacy isprivate; the others arepublic. Portal and public users have none.duplicate-token; the other copy was regenerated; the empty token is NULL.The OpenUpgrade framework logged, and continued past, one foreign-key failure while deleting obsolete xmlid
base.res_partner_1: that partner is still referenced bycalendar_filters. The filter is real data, so keeping the partner is the right outcome. Not caused by these scripts.Known gaps
resource.resource.tz. Company-level math usesres.company.tz. The post script logs the mismatches.week_typeon a two-week calendar becomes weekly, which matches v19 treating it as both weeks only where the old code did;_get_days_per_weekignored those lines and so doesdays_per_weekhere.google_calendar_cal_idis intentionally left for the Google sync PR. The column remains.openupgradelib3.13.7 cannot load noupdate XML on Odoo 20 untilconvert_xml_importis imported fromodoo.tools.convert. See above.ir.accessand for these scripts to be the only migration those modules need. This branch does not contain the framework or the base scripts.