[16.0][REF] lighting: is_spare_part now category-derived (symmetric with is_accessory)#99
Merged
Conversation
Codecov Report❌ Patch coverage is
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. 🚀 New features to boost your workflow:
|
b80de92 to
545c598
Compare
…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).
545c598 to
e19d40e
Compare
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.
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
Refactor
is_spare_partonlighting.productto follow the same pattern asis_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_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
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.pyis_spare_partnext to the existingis_accessory(line 59)._get_is_spare_part(self)next to_get_is_accessory— same parent-chain semantics.lighting/models/product.pyis_spare_part(categorical) right after_search_is_accessory(line 557). Compute viacategory_id._get_is_spare_part(). Direct mirror ofis_accessory/_compute_is_accessory/_search_is_accessory.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<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. OldIs spare partmsgid replaced by the auto-derivedIs Spare Part(now shared between the product and category fields) + newIs used as spare partfor the renamed field.i18n/ca.po: created (was previously missing — required per NuoBiT convention).lighting/__manifest__.py16.0.1.0.0→16.0.1.1.0(minor).Migration
No data migration script required.
lighting_product_spare_part_relm2m table is unchanged. The renamedis_used_as_spare_partfield uses the same compute/search logic as before, reading the same rows.is_spare_partBoolean onlighting.product.categoryis a stored column that Odoo auto-adds on-u lighting. Defaults toFalse— no observable change for existing customers unless they explicitly flag a category.Behavioral change for downstream consumers
[('is_spare_part', '=', True)]inlighting/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.is_spare_part.is_spare_partin custom code/views will get the categorical semantics. The relational semantics are still available via the renamedis_used_as_spare_partfield — a one-line search/replace migration path.Test plan
-u lightingon a database that previously installed this module — verify the new column is added, no exceptions.lighting.product.categorywithis_spare_part=True.is_spare_partset) and alighting.productwhosecategory_idis the child.is_spare_part=True(inherited via the parent-chain walker).domain="[('is_spare_part', '=', True)]"returns the product.spare_part_ids— verifyis_used_as_spare_part=Trueon the linked product.parent_spare_part_product_countstill works on the linked product (uses the m2m count directly, independent of the renamed flag).