Skip to content

feat: decouple spp_programs from core API / Studio / audit / source-tracking (#1080)#231

Open
emjay0921 wants to merge 8 commits into
19.0from
feat/1080-decouple-spp-programs
Open

feat: decouple spp_programs from core API / Studio / audit / source-tracking (#1080)#231
emjay0921 wants to merge 8 commits into
19.0from
feat/1080-decouple-spp-programs

Conversation

@emjay0921

Copy link
Copy Markdown
Contributor

Why is this change needed?

Several core modules hard-depended on spp_programs, so any starter bundle that included them installed the full Programs/Cycles/Entitlements stack — even registry-only deployments (Social Registry, Disability Registry) that don't use programs. This implements the #1080 umbrella: split the program-specific parts into thin *_programs companions so the base modules no longer depend on spp_programs.

Parent: #1080

How was the change implemented?

Each split follows the same pattern: a new *_programs companion that auto-installs when its base module + spp_programs are both present (so program functionality is preserved with zero loss), while the base module drops its spp_programs dependency.

  • #1081 spp_api_v2spp_api_v2_programs: program/program_membership routers, services, schemas, filter configs, api.path records, and the program filter routers (registered into the shared RESOURCE_SERVICES).
  • #1082 spp_api_v2_data: no code change — it only pulled spp_programs transitively via spp_api_v2; resolved by #1081 + #1084.
  • #1083 spp_studiospp_studio_programs: the program_ids link on spp.studio.mixin and the pack-install wizard's program_id (routed via a _get_pack_program_id() hook).
  • #1084 spp_source_trackingspp_source_tracking_programs: the spp.program.membership source-tracking extension; the 1.0 migration's program UPDATE is guarded by a table-existence check.
  • #1085 spp_auditspp_audit_programs: the program/cycle audit rules (base keeps Registry + Service Point rules).

New unit tests

Each companion ships tests for its program feature; base tests that used program models as incidental vehicles were made program-agnostic (e.g. spp_audit self-protection tests now use res.users) or skip gracefully when spp.program is absent.

Unit tests executed by the author

Per-module installs, all green:

  • spp_api_v2 + spp_api_v2_programs: 0 failed / 0 errors of 716
  • spp_studio + spp_studio_programs: 0 / 228
  • spp_source_tracking (+ companion): 0 / 46 alone, 0 / 47 with companion
  • spp_audit alone: 0 / 34; spp_audit_programs: 0 / 1

Decoupling verified — each base module installs alone with spp_programs = uninstalled:

  • spp_source_tracking alone → spp_programs uninstalled
  • spp_studio alone → spp_programs uninstalled (spp_audit installed without it)
  • Dependency-closure check confirms spp_api_v2, spp_api_v2_data, spp_source_tracking, spp_studio, spp_audit pull no spp_programs.

How to test manually

Install any base module alone (e.g. spp_studio) and confirm spp_programs is not installed. Install it alongside spp_programs and confirm the *_programs companion auto-installs and program features work.

Related links

#1080, #1081, #1082, #1083, #1084, #1085

emjay0921 added 4 commits June 9, 2026 12:56
…, #1082)

Extract the Program and ProgramMembership REST API out of spp_api_v2 into a
new spp_api_v2_programs companion (auto-installs when spp_api_v2 + spp_programs
are both present), so spp_api_v2 no longer DIRECTLY depends on spp_programs.

Moved into the companion: program / program_membership routers, services,
schemas, filter configs, api.path records, the program scope-enforcement test,
and the program filter routers (registered into the shared RESOURCE_SERVICES).
Base spp_api_v2 drops the program imports, the spp_programs dependency, and the
program data records.

#1082: spp_api_v2_data has no program code of its own; it only pulled
spp_programs transitively via spp_api_v2. The remaining transitive path
(spp_api_v2 -> spp_source_tracking -> spp_programs) closes in #1084.
Remove the two hard spp.program references from spp_studio so it no longer
DIRECTLY depends on spp_programs:
- program_ids (Many2many spp.program) on spp.studio.mixin
- program_id (Many2one spp.program) on the pack-install wizard

Both move to a new spp_studio_programs companion (auto-installs when
spp_studio + spp_programs are both present). The base wizard now resolves the
program via a _get_pack_program_id() hook (None in base; the companion returns
the selected program).

Note: spp_studio still pulls spp_programs transitively via spp_audit, which
also depends on spp_programs and is not yet split.
…rce_tracking_programs (#1084)

Move the spp.program.membership source-tracking extension out of
spp_source_tracking into a new spp_source_tracking_programs companion
(auto-installs when spp_source_tracking + spp_programs are both present), so
spp_source_tracking no longer depends on spp_programs.

- Base res_partner merge logic already guards program_membership_ids via
  hasattr, so it stays in the base module.
- Guarded the 1.0 migration's spp_program_membership UPDATE behind a
  table-existence check.
- Base source-tracking tests skip the program-membership cases when
  spp.program is absent; the companion affirms the extension.

With #1081 + #1084, spp_api_v2, spp_api_v2_data and spp_source_tracking now
install with no spp_programs (verified). spp_studio still pulls it via
spp_audit (tracked in #1085).
Move the program/cycle audit rules (Program, Cycle, and the program manager
rules) out of spp_audit into a new spp_audit_programs companion (auto-installs
when spp_audit + spp_programs are both present), so spp_audit no longer depends
on spp_programs. The base keeps only the Registry and Service Point rules.

Also point the base self-protection / mail-thread audit tests at res.users
instead of program models, so they pass without spp_programs.

With this, spp_audit, spp_studio, spp_api_v2, spp_api_v2_data and
spp_source_tracking all install with no spp_programs (verified: each installs
alone with spp_programs uninstalled). Completes the #1080 decoupling.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the codebase to decouple the base modules (spp_api_v2, spp_audit, spp_source_tracking, and spp_studio) from the spp_programs dependency by splitting program-specific features, configurations, and endpoints into new companion modules (spp_api_v2_programs, spp_audit_programs, spp_source_tracking_programs, and spp_studio_programs). These companion modules are configured to auto-install when both their base module and spp_programs are present, allowing registry-only deployments to run without the programs stack. Feedback on the changes highlights a critical migration risk in spp_source_tracking where executing an update query on spp_program_membership before the companion module's columns are created could trigger an UndefinedColumn database error; checking for the existence of the source_system column before running the migration is recommended.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +50 to +62
cr.execute(
"""
UPDATE spp_program_membership
SET source_system = 'v1-migration',
collection_method = 'migration',
collection_date = create_date
WHERE source_system IS NULL
"""
)
membership_count = cr.rowcount
_logger.info(
"Updated %s existing program memberships with migration source tracking",
membership_count,
"SELECT 1 FROM information_schema.tables WHERE table_name = 'spp_program_membership'"
)
if cr.fetchone():
cr.execute(
"""
UPDATE spp_program_membership
SET source_system = 'v1-migration',
collection_method = 'migration',
collection_date = create_date
WHERE source_system IS NULL
"""
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Critical Migration Failure Risk

If spp_programs is installed, the table spp_program_membership will exist. However, during the migration phase of spp_source_tracking, the companion module spp_source_tracking_programs (which adds the source tracking columns to spp_program_membership) might not be installed or fully loaded yet.

Running the UPDATE query when the columns source_system, collection_method, and collection_date do not yet exist on spp_program_membership will raise an UndefinedColumn database error, causing the entire Odoo upgrade/startup process to fail.

To prevent this, we should defensively check if the columns actually exist in the table before executing the UPDATE query.

Suggested change
cr.execute(
"""
UPDATE spp_program_membership
SET source_system = 'v1-migration',
collection_method = 'migration',
collection_date = create_date
WHERE source_system IS NULL
"""
)
membership_count = cr.rowcount
_logger.info(
"Updated %s existing program memberships with migration source tracking",
membership_count,
"SELECT 1 FROM information_schema.tables WHERE table_name = 'spp_program_membership'"
)
if cr.fetchone():
cr.execute(
"""
UPDATE spp_program_membership
SET source_system = 'v1-migration',
collection_method = 'migration',
collection_date = create_date
WHERE source_system IS NULL
"""
)
cr.execute(
"""
SELECT 1
FROM information_schema.columns
WHERE table_name = 'spp_program_membership'
AND column_name = 'source_system'
AND table_schema = current_schema()
"""
)
if cr.fetchone():
cr.execute(
"""
UPDATE spp_program_membership
SET source_system = 'v1-migration',
collection_method = 'migration',
collection_date = create_date
WHERE source_system IS NULL
"""
)

@codecov

codecov Bot commented Jun 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.04110% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.10%. Comparing base (74516d3) to head (5241067).
⚠️ Report is 2 commits behind head on 19.0.

Files with missing lines Patch % Lines
spp_studio/wizard/pack_install_wizard.py 33.33% 2 Missing ⚠️
spp_api_v2_programs/__manifest__.py 0.00% 1 Missing ⚠️
spp_audit_programs/__manifest__.py 0.00% 1 Missing ⚠️
spp_source_tracking_programs/__manifest__.py 0.00% 1 Missing ⚠️
...pp_studio_change_requests_programs/__manifest__.py 0.00% 1 Missing ⚠️
spp_studio_events_programs/__manifest__.py 0.00% 1 Missing ⚠️
spp_studio_programs/__manifest__.py 0.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             19.0     #231      +/-   ##
==========================================
+ Coverage   73.06%   73.10%   +0.03%     
==========================================
  Files        1069     1106      +37     
  Lines       62120    63245    +1125     
==========================================
+ Hits        45390    46233     +843     
- Misses      16730    17012     +282     
Flag Coverage Δ
spp_api_v2 79.78% <ø> (-0.55%) ⬇️
spp_api_v2_change_request 66.85% <ø> (ø)
spp_api_v2_cycles 71.12% <ø> (ø)
spp_api_v2_data 64.41% <ø> (ø)
spp_api_v2_entitlements 70.19% <ø> (ø)
spp_api_v2_programs 92.03% <97.95%> (?)
spp_audit 71.39% <ø> (-1.22%) ⬇️
spp_audit_programs 0.00% <0.00%> (?)
spp_base_common 90.26% <ø> (ø)
spp_programs 64.84% <ø> (ø)
spp_registry 86.83% <ø> (ø)
spp_security 66.66% <ø> (ø)
spp_source_tracking 80.00% <ø> (?)
spp_source_tracking_programs 85.71% <66.66%> (?)
spp_studio 62.90% <33.33%> (-0.05%) ⬇️
spp_studio_change_requests 84.67% <ø> (ø)
spp_studio_change_requests_programs 0.00% <0.00%> (?)
spp_studio_events 71.20% <ø> (?)
spp_studio_events_programs 0.00% <0.00%> (?)
spp_studio_programs 93.33% <93.33%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
spp_api_v2/__manifest__.py 0.00% <ø> (ø)
spp_api_v2/models/fastapi_endpoint_registry.py 93.93% <ø> (-0.35%) ⬇️
spp_api_v2/routers/__init__.py 100.00% <ø> (ø)
spp_api_v2/routers/filter.py 24.77% <ø> (-5.15%) ⬇️
spp_api_v2/schemas/__init__.py 100.00% <ø> (ø)
spp_api_v2/services/__init__.py 100.00% <ø> (ø)
spp_api_v2_programs/__init__.py 100.00% <100.00%> (ø)
spp_api_v2_programs/models/__init__.py 100.00% <100.00%> (ø)
...pi_v2_programs/models/fastapi_endpoint_programs.py 100.00% <100.00%> (ø)
spp_api_v2_programs/routers/__init__.py 100.00% <100.00%> (ø)
... and 29 more

... and 16 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

emjay0921 added 4 commits June 9, 2026 15:20
The #1083 decoupling exposed two latent couplings that crashed when
spp_studio is installed without spp_programs (or when spp_programs is
installed afterwards):

1. program_ids view references: spp_studio_change_requests and
   spp_studio_events displayed the shared spp.studio.mixin program_ids
   field, which now lives in the spp_studio_programs companion. Their
   base views failed to load without it. Dropped program_ids from both
   base views and added two auto-install companions
   (spp_studio_change_requests_programs, spp_studio_events_programs)
   that re-inject the field/page when programs are present.

2. spp.cel.expression.state selection: spp_studio replaced the shared
   selection, dropping the base 'active'/'inactive' values. This only
   worked when spp_programs loaded first (forced by the old dependency).
   Switched to selection_add so the field is the union of both state
   sets, making it load-order independent. spp_programs eligibility
   templates (state='active') now load regardless of install order.

Also guarded TestDeferredResolutionProgramOverrides to skip when
spp_programs is not installed (it accessed spp.cel.program.parameter
in setUpClass).
…panion (#1081)

CI runs each module's tests in isolation. TestScopeIsolation.setUp in
spp_api_v2 created a program + membership for all its tests, which fails
with KeyError: 'spp.program' when spp_api_v2 is tested without
spp_programs. Only one test (program-membership scope isolation) needs
programs, and it targets the /ProgramMembership endpoint that moved to
spp_api_v2_programs in #1081.

- Trim TestScopeIsolation.setUp to the resource types the base module
  serves (individual, group); the other 6 isolation tests need no program
- Move test_individual_scope_does_not_grant_program_membership_access to
  spp_api_v2_programs (TestScopeEnforcementProgramMembership)

Also apply pre-commit auto-format fixes that drifted on files from earlier
commits in this branch (import order, line length, blank lines).
…#1081)

Group first-party odoo.addons.spp_api_v2.* imports ahead of relative
imports in the files moved from spp_api_v2 in #1081. Import-order only,
no behavior change; fixes the pre-commit ruff check.
…#1081, #1083)

The program-membership PUT update tests used url_open (which cannot issue
a PUT) and only asserted [200, 405], so the update endpoint body was never
exercised. Rewrite them to use url_put and add the missing paths:
- update success (status change persisted)
- 409 on stale If-Match, 404 on unknown membership, 400 on bad identifier
- 422 create/update error paths (unknown program reference)
- previous-page link on non-zero _offset

Add a test for the pack-install wizard's _get_pack_program_id hook
(selected program and the None branch).

spp_api_v2_programs 86% -> 92%, spp_studio_programs 87% -> 93%. Test-only.
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.

1 participant