Repository navigation
[19.0][MIG] ni_patient: Migration to 19.0 - #155
Merged
Merged
Conversation
wasud
force-pushed
the
19.0-mig-p1-patient
branch
2 times, most recently
from
October 14, 2026 01:35
f6156e1 to
5fbca29
Compare
4 tasks done
wasud
force-pushed
the
19.0-mig-p1-patient
branch
from
October 14, 2026 01:39
5fbca29 to
77f38d1
Compare
This was referenced Oct 14, 2026
wasud
force-pushed
the
19.0-mig-p1-patient
branch
2 times, most recently
from
October 15, 2026 10:29
cd55453 to
e430962
Compare
This was referenced Oct 16, 2026
This was referenced Oct 19, 2026
piruin
reviewed
Oct 19, 2026
piruin
left a comment
Contributor
There was a problem hiding this comment.
Review
Blocking (mechanical): the branch conflicts with 19.0.
upgrade_path/README.mdis added on both sides.- The branch still carries #149's commits from before that PR was merged, so GitHub shows about 226 changed files instead of about 40.
- A rebase onto
origin/19.0fixes both.
Non-blocking: a bug carried over from 16.0 in ni_encounter.check_origin_date.
raise self.env._("...")raises a plain string, which fails with aTypeError.rec.origin_date > rec.period_startcompares a Date with a Datetime, which also raises aTypeError.- Per the epic, fix it on 16.0 under P0-4 (#135) and forward-port it with
oca-port, rather than inside the[MIG]commit.
Checked, OK
post-nirun_partner_mobile.pyagainst OpenUpgrade 19's_migrate_partner_mobile:- OpenUpgrade copies
phoneto the legacy column, then setsphonetophone // mobile, or to just the mobile number when the phone is empty. - The
WHERE COALESCE(mobile, '') != '' AND phone IS DISTINCT FROM legacycondition undoes exactly those rows. An originally empty phone ends up empty again. - Rows where phone equalled mobile are skipped by OpenUpgrade, and the script leaves them alone too.
- The
mobilecolumn is never dropped, soni_partner_mobilepicks it up.
- OpenUpgrade copies
- The privilege setup (
res.groups.privilegeplus groupsequence),user_ids,bypass_search_access,models.Constraintandself.env._(...)with arguments are all used correctly. - The list view reads the selection with
model.root.getResIds(true). - CI is green.
[FIX] ni_patient: lazy error [IMP] ni_patient: add 'user_id' for encounter_participant [IMP] ni_patient: Add chief complaint [IMP] ni_patient: Remove participant page [IMP] ni_patient: No duplicate encounter [IMP] ni_patient: use `color_picker` [IMP] ni_patient: lock state for CC [IMP] ni_patient: better CC
[IMP] ni_patient: rename `ni.encounter.cls` to `ni.encounter.class` [IMP] ni_patient: check datetime instead of date on every 'ni.patient.res' [IMP] ni_patient: priority value as char and show as badge on list view [MIG] ni_patient: cascade on delete Encounter [FIX] ni_patient: migration error [IMP] ni_patient_birthdate_est: `Medical` category
[IMP] ni_patient: implement create in batch [IMP] ni_patient: fix type
…d model -fix typo of priority's routine -discharge wizard call action_close() on encounter
[IMP] ni_patient: set name attribute for oe_chatter (xml) [IMP] ni_patient: location history as server action
- Fix _safe_get_parent() returning partner_id instead of parent_id - Fix action_abort() using in operator on string instead of = match - Fix action_revoked() filtering wrong state (in-progress doesn't exist on requests); now correctly targets draft/active records - Add state field with color decorations to event and request tree views - Add Timeline menu section under Encounter app (Events + Requests) - Add tests for workflow event/request creation, replacement tracking, action_resource, and garbage_collect behavior Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Took 20 minutes
Took 23 minutes
Add ni.workflow.line (_auto=False SQL UNION view) merging ni.workflow.event and ni.workflow.request into a single patient timeline. Add kanban + tree + search views for all three models with consistent badge/avatar styling and a shared Thai translation update. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Took 1 hour 23 minutes Took 18 minutes Took 6 minutes
…ife#97) - detect-secrets hook scans every commit for passwords, tokens, API keys - .secrets.baseline captures known false positives (CI postgres/test creds) - CLAUDE.md: explicit rule — no hardcoded credentials, use env vars
…ght cutoff (nirun-life#103) cron_auto_close() computed the "today" cutoff from fields.Datetime.now(), which is UTC-naive, so the midnight-offset day boundary was always UTC midnight regardless of the encounter's company. For companies running ahead of UTC (e.g. Thailand, UTC+7), an encounter that had already been open for a full local day could stay open for up to 7 more extra hours, then close abruptly whenever the UTC date happened to roll over - explaining reports that the cron "sometimes works, sometimes doesn't". Derive the day boundary from each encounter's company timezone instead (res.company.resource_calendar_id.tz, falling back to UTC), grouping candidates per company since a class isn't necessarily scoped to one. Fixes nirun-life#102. Co-authored-by: Nutcha <nutcha.noonmomo@gmail.com>
…'s event/request id WorkflowMixin._to_workflow() wrote the parent record's own id into ni.workflow.event.parent_id / ni.workflow.request.parent_id, but those fields reference other ni.workflow.event/request rows, not the source model's own id. This either raised a foreign key violation or silently linked to an unrelated workflow row, depending on whether the id happened to collide with an existing workflow row. Surfaced via ni_observation: an observation type with a computed parent (e.g. Systolic/Diastolic Blood Pressure -> Blood Pressure) hit this as soon as both child values were saved, since ni.observation.sheet links the children to the newly computed parent via child_ids, which re-writes each child's parent_id and re-triggers the workflow mirror write. Fixes nirun-life#109. Also fixes ni_device's test_requires_supported_observation_type, which tried to clear a device's observation_type_ids via its definition + recompute, but _compute_from_definition only ever fills a device's observation_type_ids from a non-empty definition and never clears it.
…irun-life#123) ## Why The departure wizard was reachable from exactly one view: the community care "simp" patient form, the only one carrying `js_class="ni_patient_form"`. Archive from anywhere else — the base form, any patient list — wrote `active=False` directly and lost the reason, date and note the wizard exists to collect. ## What - `js_class="ni_patient_form"` on the base patient form, `js_class="ni_patient_list"` on the base tree - New `PatientListController` so a multi-selection archives through the wizard too - Wizard takes `patient_ids` instead of `patient_id` and writes one reason set to every selected patient - One patient still renders as the big heading; several render as tags on a normal field label - `toggle_active` / `no_wizard` removed — nothing ever read `no_wizard`, and `write(active=False)` does the same work ## Deploy order Ships with nirun-life/nirun-dop#13, which imports `PatientListController` from this branch. **This repo must go out first** or the community care patient list fails to register its view type. ## Testing - `ni_patient/tests/test_ni_patient_archive_wizard.py` covers both the single and multi cases - Full `ni_patient` suite on a scratch DB: `0 failed, 0 error(s) of 20 tests` - Verified server-side: rendered arch carries both `js_class` values, `default_patient_ids` normalises to `Command.SET`, and the Thai field label resolves to `ผู้รับบริการ` - Not yet clicked through in a browser — worth one pass on the list and the form --------- Co-authored-by: Nutcha <nutcha.noonmomo@gmail.com>
…e#124) `ni.location._name_search` matched on `display_name`, which is neither stored nor given a search method. The ORM logs `Non-stored field ... cannot be searched` and quietly turns that leaf into `TRUE`, so every location autocomplete — and every `('<m2o to a location>', 'ilike', ...)` leaf — returned the whole table. Typing a community name into the house map's search bar brought back all five communities and, through them, every house in the database. ### What changes The override goes away. `_rec_names_search = ["name", "alias"]` states the same thing as a class attribute, and core's `_name_search` (`odoo/models.py:1633-1666`) does the rest — the same aggregator on `NEGATIVE_TERM_OPERATORS`, the same leaf shape, plus dotted paths, `convert_to_write` with a `ValueError` skip, and a warning rather than a `[None]` leaf when `_rec_name` is missing. Net -25 lines. ### Scope Nothing in the codebase calls `name_search` directly, so the change is confined to autocompletes and `ilike` leaves, which now filter instead of returning everything. Opening a dropdown without typing still lists all locations — core's `not (name == '' and operator in ('like', 'ilike'))` guard skips the domain entirely. No model inheriting `ni.location` overrides `_name_search`, so nothing resolves `super()` through the deleted method. `ni.location.commu` declares its own `_rec_names_search = ["name", "code"]`; it needs `"alias"` added there if community aliases should stay matchable. ### Tests `ni_patient/tests/test_ni_location_name_search.py`, two cases: `ilike` filters instead of matching everything, and `alias` is still matched. Both scope their `name_search` to the records they create — unscoped, the pre-fix `TRUE` leaf returns an arbitrary 100 rows that need not include the others, and the assertion would pass against the bug it guards. Verified they fail against the original bug — `_rec_names_search = ["display_name", "alias"]` reproduces it: ``` FAIL: test_ilike_filters_instead_of_matching_everything AssertionError: ni.location(22, 23, 24) != ni.location(22,) FAIL: test_alias_is_still_matched AssertionError: ni.location(22, 23, 24) != ni.location(23,) ``` `ni_patient` suite: 20 tests, 0 failed. --------- Co-authored-by: Nutcha <nutcha.noonmomo@gmail.com>
ni.encounter previously only received references from other resources' workflow events, never producing its own. Since its state selection is incompatible with ni.workflow.event.mixin, write to ni.workflow.event directly instead of inheriting the mixin, so start/discharge/cancel transitions show up in the patient timeline. Took 12 minutes
Encounter form gains a "Workflow Timeline" action-menu entry, alongside Location/Participant History, opening ni.workflow.line filtered to that encounter. Also default-groups the generic timeline by date, adds a company filter to its searchpanel, and drops the unused static/src/views/*.xml asset glob (no such templates exist). th.po gains the reference line the new action's name field needs to pick up the existing translation, and fills the previously blank encounter start-date validation message. Took 17 minutes
`default=fields.Datetime.now()` (and, in ni_reception, the stdlib `fields.datetime.now()`) is evaluated once when the model class is defined, freezing every record's default to that one moment instead of the actual creation time. Pass the bare callable so it evaluates per record. Affected: ni_observation (observation wizard), ni_document_ref, ni_medication (suggest wizard), ni_reception, and ni_patient's ni.workflow.event.mixin (shared by every clinical event model: procedures, immunizations, medication dispenses, etc).
Odoo's view validator warns on every <i class="fa ..."> and icon button without a title, since screen readers have nothing to announce. The titles name what the icon stands for (Occurrence, Patient, Encounter, Location, ...) so they double as tooltips. Took 4 minutes
Odoo 19 removed the partner mobile from base. OCA partner_mobile brings it back, but under AGPL-3; OPL-1 modules sit on top of ni_patient, so they cannot depend on AGPL code. This module brings back the mobile field, its formatting and the partner form and list fields as they were in Odoo 18.0 (LGPL-3). phone_validation picks it up as a number field for the phone search and SMS.
piruin
reviewed
Oct 19, 2026
piruin
left a comment
Contributor
There was a problem hiding this comment.
Follow-up review
Two more items, found while reviewing #158:
- Fix: copyright headers were lost. The 18→19 step removed the
<!-- Copyright … NSTDA -->header fromviews/ni_encounter_location_views.xml,views/ni_encounter_participant_views.xml,views/ni_location_views.xmlandviews/ni_patient_views.xml. The cause is probably the migrator rewriting the search-view<group>tags with lxml. Please restore the headers. - Minor: there is no
[IMP] ni_patient: pre-commit auto fixescommit before[MIG] ni_patient: Migration to 19.0, which the epic's commit order asks for.
wasud
force-pushed
the
19.0-mig-p1-patient
branch
from
October 19, 2026 05:09
e430962 to
b3f621e
Compare
Contributor
Author
|
Thanks. All fixed:
A fresh 19.0 install with ni_coding and ni_patient tests: 32/32 pass. #158 and #161 were rebased on top and only gain the 4 headers. |
2 tasks done
5 tasks done
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.
Decisions to review
ni_patientdepends on the new Nirun moduleni_partner_mobile(LGPL-3, adapted from Odoo 18.0baseandphone_validation), not on OCApartner_mobile(AGPL-3), for the same reason [19.0][MIG] ni_period, ni_identifier, ni_coding, partner_age, partner_gender, partner_religion, uom_alias: Migration to 19.0 #149 replacedpartner_titlewithni_partner_title. It keeps the patient mobile number as its own field (FHIRContactPoint.use = mobile) instead of Odoo 19's merged phone.upgrade_path/base/19.0.1.3/post-nirun_partner_mobile.pyputs back the originalphonethat OpenUpgrade 19 merged with the mobile ("phone // mobile"), using OpenUpgrade's legacy copy. It runs only whenni_patientis installed, and needsupgrade_path/as a second--upgrade-path(see [19.0][MIG] ni_period, ni_identifier, ni_coding, partner_age, partner_gender, partner_religion, uom_alias: Migration to 19.0 #149).res.groups.privilege"Patients" (res_groups_privilege_patient) in the existing Medical > Patients category;users→user_ids. OpenUpgrade leaves privileges to modules; memberships survive (checked).What changed
ni_patientreplayed from the 18.0 PR branch, then[MIG] ni_patient: Migration to 19.0.models.Constraint,bypass_search_access,env.cr/env.context,group_ids.title→title_id(onchange, demo, test);ni_partner_mobiledependency (new LGPL module, commit before the[MIG]);self.env._(...)with arguments.name_search(domain=),fields.Date/fields.Datetime; they don't rely on demo data.th.po.Why
Closes #137 (P1-2, epic #131).
Migration notes
name_search(args=)is gone in 19.How tested
upgrade_path/) on an 18 DB with ni_patient and no demo:ni_patient.group_user;migration_snapshot.pypatient/encounter hashes are identical before and after.pre-commit run -aclean.Documentation
README.mdupdatedNotes for reviewer
odoo-module-migrate17 rewrote Thai text in XML (comments,string,placeholder) as&#NNNN;entities, and prettier then split one of them with spaces. The Thai text is back as UTF-8.invisible="1"on a list field only hides the cells, so the column header still shows. Hidden list fields now usecolumn_invisible="1". Found in the browser check ofni_observation([MIG 19.0][P1-4] ni_observation #139).ni_partner_title): demo and tests useni_partner_title.res_partner_title_*.ni_partner_mobile(2026-10-15). Without OCA partner-contact on the addons path: 144 tests pass on the top of the stack ([19.0][MIG] ni_questionnaire: Migration to 19.0 #161). OpenUpgrade 18→19 of the [MIG 19.0][P1-6] Data migration pilot: OpenUpgrade 16→17→18→19 on Phase 1 modules #141 pilot DB: 0 errors, and its snapshot is identical to the pilot's 19 snapshot made with the OCA modules (7 mobiles, 7 titles, no merged phones)./my/homeand/my/patients.ListController.getSelectedResIds(), so the list now reads the selection withmodel.root.getResIds(true).ni_partner_mobile's ownth.po(มือถือ), so the earlier gap with OCApartner_mobile(no Thai translation) is gone.