Skip to content

[16.0][REF] lighting: is_spare_part now category-derived (symmetric with is_accessory)#99

Merged
eantones merged 1 commit into
16.0from
16.0-ref-lighting-spare_part_categorical_field
May 14, 2026
Merged

[16.0][REF] lighting: is_spare_part now category-derived (symmetric with is_accessory)#99
eantones merged 1 commit into
16.0from
16.0-ref-lighting-spare_part_categorical_field

Conversation

@eantones
Copy link
Copy Markdown
Member

Summary

Refactor is_spare_part on lighting.product to follow the same pattern as is_accessory — derived from the category. Adds the missing categorical flag that accessories already have, bringing spare-parts to full symmetry: 1 categorical + 1 relational (versus accessories' 1 categorical + 2 relational, one per m2m kind).

Before this refactor

is_accessory                ← categorical (from category_id._get_is_accessory)
is_optional_accessory       ← relational  (m2m optional_ids inverse)
is_required_accessory       ← relational  (m2m required_ids inverse)
is_spare_part               ← relational  (m2m spare_part_ids inverse)
                              ↑ missing the categorical equivalent

[('is_spare_part', '=', True)] returned only items already linked somewhere as spare parts — never the broader universe of categorically-spare items. That made the "Spare parts" search filter on the product list useless for browsing the spare-parts universe.

After this refactor

is_accessory                ← categorical (from category)
is_optional_accessory       ← relational
is_required_accessory       ← relational
is_spare_part               ← categorical (from category — NEW)
is_used_as_spare_part       ← relational  (m2m spare_part_ids inverse — RENAMED from previous is_spare_part)

Both Booleans coexist, both useful. The existing search filter now returns the universe of categorically-spare products — the intended user-facing behavior.

Changes

lighting/models/product_category.py

  • NEW: stored Boolean is_spare_part next to the existing is_accessory (line 59).
  • NEW: recursive walker _get_is_spare_part(self) next to _get_is_accessory — same parent-chain semantics.

lighting/models/product.py

  • NEW: computed Boolean is_spare_part (categorical) right after _search_is_accessory (line 557). Compute via category_id._get_is_spare_part(). Direct mirror of is_accessory / _compute_is_accessory / _search_is_accessory.
  • RENAME: existing is_spare_part (m2m-derived) → is_used_as_spare_part. Compute/search logic unchanged, just the field/method names. Original semantics preserved under the more accurate name.

lighting/views/product_category_views.xml

  • Add <field name="is_spare_part" /> next to <field name="is_accessory" /> on both the form view (line 89) and the list view (line 158).

Translations

  • i18n/es.po, i18n/fr.po, i18n/pt.po: updated. Old Is spare part msgid replaced by the auto-derived Is Spare Part (now shared between the product and category fields) + new Is used as spare part for the renamed field.
  • i18n/ca.po: created (was previously missing — required per NuoBiT convention).

lighting/__manifest__.py

  • Version bumped 16.0.1.0.016.0.1.1.0 (minor).

Migration

No data migration script required.

  • The lighting_product_spare_part_rel m2m table is unchanged. The renamed is_used_as_spare_part field uses the same compute/search logic as before, reading the same rows.
  • The new is_spare_part Boolean on lighting.product.category is a stored column that Odoo auto-adds on -u lighting. Defaults to False — no observable change for existing customers unless they explicitly flag a category.
  • All other changes are computed fields with no DB column footprint.

Behavioral change for downstream consumers

  • A single in-codebase domain consumer exists: the search filter [('is_spare_part', '=', True)] in lighting/views/product_views.xml:825. After this refactor, the filter intentionally returns the categorical universe instead of the relational subset. This is the intended user-facing behavior.
  • No other modules in the addons tree reference is_spare_part.
  • External customers using is_spare_part in custom code/views will get the categorical semantics. The relational semantics are still available via the renamed is_used_as_spare_part field — a one-line search/replace migration path.

Test plan

  • -u lighting on a database that previously installed this module — verify the new column is added, no exceptions.
  • Create a lighting.product.category with is_spare_part=True.
  • Create a child category under it (no is_spare_part set) and a lighting.product whose category_id is the child.
  • Verify the product has is_spare_part=True (inherited via the parent-chain walker).
  • Verify the search filter domain="[('is_spare_part', '=', True)]" returns the product.
  • Manually link two products via spare_part_ids — verify is_used_as_spare_part=True on the linked product.
  • Verify the smart button parent_spare_part_product_count still works on the linked product (uses the m2m count directly, independent of the renamed flag).

@codecov
Copy link
Copy Markdown

codecov Bot commented May 14, 2026

Codecov Report

❌ Patch coverage is 36.36364% with 21 lines in your changes missing coverage. Please review.
✅ Project coverage is 43.52%. Comparing base (077dd16) to head (e19d40e).

Files with missing lines Patch % Lines
lighting/models/product.py 39.13% 13 Missing and 1 partial ⚠️
lighting/models/product_category.py 30.00% 7 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             16.0      #99      +/-   ##
==========================================
- Coverage   43.56%   43.52%   -0.05%     
==========================================
  Files         283      283              
  Lines        8130     8157      +27     
  Branches     1570     1579       +9     
==========================================
+ Hits         3542     3550       +8     
- Misses       4493     4511      +18     
- Partials       95       96       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@eantones eantones force-pushed the 16.0-ref-lighting-spare_part_categorical_field branch from b80de92 to 545c598 Compare May 14, 2026 13:22
…s superset of spare parts)

Refactors the spare-parts schema into its truthful abstraction: the
category-level flag captures *component* identity (parts that go INTO
a luminaire), while the spare-part role is a per-luminaire
relationship captured by the m2m spare_part_ids on lighting.product.
Spare parts are a relational subset of components, not a separate
categorical type.

Schema:

* lighting.product.category.is_component (NEW) — stored Boolean.
  Admin checks the box on root categories that group components.
  Mirrors the existing is_accessory pattern (accessories and
  components are both non-luminaire by-product categories).
* lighting.product.is_component (NEW) — computed Boolean, walks the
  category tree via _get_is_component() to derive the categorical
  state.
* lighting.product.is_used_as_spare_part — RENAMED from is_spare_part.
  m2m-derived Boolean that means "this product appears in some other
  luminaire's spare_part_ids". The new name matches the semantic;
  computed logic is unchanged. The m2m relation spare_part_ids
  (introduced in OCA/lighting-vertical task #2554) is untouched.

Picker domain on spare_part_ids: the field now declares
`domain="[('is_component', '=', True)]"` so the picker only proposes
products whose category is flagged as component. This prevents
selecting full luminaires or accessories as spare parts by mistake.
Aligns with the architectural rule "a spare part of a luminaire IS a
component of that luminaire by definition".

Validation and filter sites updated to treat components symmetrically
with accessories so they share the existing accessory-aware exemptions:

* lighting/models/product_source.py — _check_efficiency_lampholder
* lighting/models/product_source_line.py — _check_efficiency_lampholder
* lighting/models/product_category.py — _check_efficiency_lampholder
* lighting_reporting/models/product.py — related-products filter
* lighting_reporting_product/models/lighting_product.py — related-products filter

Silent regression fix in lighting/views/product_views.xml:825: the
search filter "Spare parts" had a domain pointing at the field by its
former name (which has since become the category-derived Boolean).
Domain restored to is_used_as_spare_part to match the semantic of its
sibling filters is_optional_accessory / is_required_accessory (both
m2m-derived).

Translations: ca.po created (was missing — mandatory per NuoBiT),
es.po / fr.po / pt.po extended with the new msgids "Is Component" and
"Is used as spare part".

No migration script needed:
* The renamed m2m-derived field had no DB column (compute-only),
  rename is a code-only operation.
* The m2m relation lighting_product_spare_part_rel is empty at deploy
  time (feature shipped earlier, never populated in production yet),
  so the rename is a no-data event.
* The new is_component Boolean on category is added with default
  False by Odoo's -u; admin checks the box on the "Components" root
  category post-deploy.
* The new domain on spare_part_ids is a UI restriction only; it does
  not invalidate any existing m2m rows (the rel table is empty), so
  no data backfill needed.
* Odoo 16 has no ir.translation table — translations are stored as
  JSONB on each translatable column, kept consistent by the framework.

Manifest bumps:
* lighting 16.0.1.0.0 -> 16.0.1.3.0
* lighting_reporting 16.0.1.0.0 -> 16.0.1.2.0
* lighting_reporting_product 16.0.1.0.0 -> 16.0.1.2.0

lighting_reporting/README.rst + static/description/index.html
regenerated from fragments by the pre-commit hook (template drift +
manifest version bump).
@eantones eantones force-pushed the 16.0-ref-lighting-spare_part_categorical_field branch from 545c598 to e19d40e Compare May 14, 2026 13:35
@eantones eantones merged commit d622fec into 16.0 May 14, 2026
4 of 6 checks passed
@eantones eantones deleted the 16.0-ref-lighting-spare_part_categorical_field branch May 14, 2026 13:58
eantones added a commit that referenced this pull request May 15, 2026
Follow-up to the [IMP] commit on the same PR, completing what's missing
for a clean deploy on top of nuobit/16.0 (which is currently at 16.0.1.3.0
after the PR #99 spare parts refactor).

Manifest bumped 16.0.1.1.0 -> 16.0.1.4.0 so the upgrade is monotonic
from the deployed version. The previous 16.0.1.1.0 happened because the
three revert commits in this PR brought the version back to 16.0.1.0.0
and only the new [IMP] bumped it by one.

ca.po is mandatory per NuoBiT convention and was missing because it
was created by the [REF] is_component commit (now reverted on this PR).
Recreated with only the strings introduced by this PR's sparepart
model + One2many: "Lighting Product Spare Part" and "Spare parts".

es/fr/pt .po files had stale `field_lighting_product__spareparts_ids`
xmlid references pointing at the plural-form field name from the
earlier draft of this PR. Renamed to `__sparepart_ids` to match the
field declaration in lighting/models/product.py.

Tests covering the four user-facing guarantees of the new model:
  * Free-text entries persist on the parent product
  * Ordering by sequence works
  * Cascade-delete on product unlink removes the entries
  * `name` is required at the SQL layer

Post-migration script for 16.0.1.4.0 drops the two orphan DB objects
left by the [REF] is_component revert on a database that previously
ran PR #99 (16.0.1.3.0):
  * Table lighting_product_spare_part_rel (was the m2m relation)
  * Column lighting_product_category.is_component (was stored Boolean)
Both verified empty on the target deployment before this work, so the
drop is safe; the IF EXISTS guards make the script idempotent on
databases that never ran PR #99.
eantones added a commit that referenced this pull request May 15, 2026
Adds a new One2many `sparepart_ids` on `lighting.product` backed by the
new model `lighting.product.sparepart` (sequence/name/description) and
exposes it inside a new Spare parts tab on the product form. The use
case is a manual reference list maintained by the After Sales team
without coupling to SAP product codes.

Files:
  * lighting/models/product_sparepart.py — new model (3 fields + a
    cascade-delete back-reference to lighting.product).
  * lighting/models/product.py — adds the One2many field.
  * lighting/security/ir.model.access.csv — guest/user/manager ACLs
    on the new model, matching the existing pattern for sub-models of
    lighting.product.
  * lighting/views/product_views.xml — new Spare parts tab with an
    inline editable_tree of sequence/name/description.

Translations: es.po / fr.po / pt.po extended; ca.po created (was
missing — mandatory per NuoBiT) with the new msgids only.

Tests: lighting/tests/test_product_sparepart.py covers persistence,
sequence ordering, cascade-delete, and the required-name constraint.

Migration: lighting/migrations/16.0.1.4.0/post-migration.py drops
the two orphan DB objects left on a database that previously ran
PR #99 (now fully reverted in commits 1-5 of this PR):
  * Table lighting_product_spare_part_rel (was the m2m relation)
  * Column lighting_product_category.is_component (was stored Boolean)
Both verified empty on the target deployment before this work; the
IF EXISTS guards make the script idempotent on databases that never
ran PR #99.

Manifest bumped 16.0.1.0.0 -> 16.0.1.4.0 so the upgrade stays monotonic
on top of the currently deployed 16.0.1.3.0.
eantones added a commit that referenced this pull request May 15, 2026
Adds a new One2many `sparepart_ids` on `lighting.product` backed by the
new model `lighting.product.sparepart` (sequence/name/description) and
exposes it inside a new Spare parts tab on the product form. The use
case is a manual reference list maintained by the After Sales team
without coupling to SAP product codes.

Files:
  * lighting/models/product_sparepart.py — new model (3 fields + a
    cascade-delete back-reference to lighting.product).
  * lighting/models/product.py — adds the One2many field.
  * lighting/security/ir.model.access.csv — guest/user/manager ACLs
    on the new model, matching the existing pattern for sub-models of
    lighting.product.
  * lighting/views/product_views.xml — new Spare parts tab with an
    inline editable_tree of sequence/name/description.

Translations: es.po / fr.po / pt.po extended; ca.po created (was
missing — mandatory per NuoBiT) with the new msgids only.

Tests: lighting/tests/test_product_sparepart.py covers persistence,
sequence ordering, cascade-delete, and the required-name constraint.

Migration: lighting/migrations/16.0.1.4.0/post-migration.py drops
the two orphan DB objects left on a database that previously ran
PR #99 (now fully reverted in commits 1-5 of this PR):
  * Table lighting_product_spare_part_rel (was the m2m relation)
  * Column lighting_product_category.is_component (was stored Boolean)
Both verified empty on the target deployment before this work; the
IF EXISTS guards make the script idempotent on databases that never
ran PR #99.

Manifest bumped 16.0.1.0.0 -> 16.0.1.4.0 so the upgrade stays monotonic
on top of the currently deployed 16.0.1.3.0.
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