📝 Problem Statement: The "Orphaned Variants" Trap
In industrial and retail environments, product catalog integrity is critical. Currently, standard Odoo has a built-in behavior regarding product attributes that leads to severe catalog corruption and search conflicts if users make a common operational mistake.
The Real-World Scenario (The "Human Error" Factor):
A user intends to create a new product ("Product B") by using an existing one ("Product A") as a base. Due to a common operational oversight—and instead of using the standard "Duplicate" action—the user accidentally edits "Product A" directly.
They proceed to rename the template to "Product B", delete the existing attribute line (e.g., "Serafil Color" with 80 values), and add a new one (e.g., "Outdoor Color" with 34 values).
- The User's Mistake: Forgetting to duplicate the template before editing.
- The Expected Safeguard: In a high-stakes industrial environment, the system should prevent such a destructive "despiste" (oversight) if historical data is at risk.
- The Reality: Standard Odoo allows this change to proceed silently. It leaves the 80 original variants "orphaned" and active, now inheriting the name "Product B" but keeping their old SKUs and stock.
The Core Behavior Under the Hood:
Because the original 80 variants already have a transactional history (stock.move, sale.order.line, etc.), the database prevents their deletion due to Foreign Key constraints (ondelete='restrict'). Odoo's core silently catches this database exception. Since it cannot delete the variants, it leaves them active and attached to the template as "stale combinations," stripping them of the deleted attribute values.
The Business Impact:
These 80 orphaned variants retain their original Internal References (SKUs), stock levels, and historical data, but they now inherit the new template name ("Product B").
When sales reps or warehouse workers search for the old SKU to make a sale or move stock, Odoo displays "Product B". The user, thinking there is a typo, renames the template back to "Product A", instantly corrupting the 34 newly created variants. The catalog becomes a tangled mess of mixed SKUs and names.
💡 Proposed Solution: A Configurable Guardrail
We propose a new module in OCA/product-attribute named product_attribute_deletion_protection.
Since we respect Odoo's default design for standard implementations, this module will introduce an opt-in protection mechanism, designed for strict environments where catalog traceability is mandatory.
Key Features & Architecture:
- Configurable: A boolean setting in
Inventory > Configuration > Settings (e.g., "Protect attributes with historical data"). It saves an ir.config_parameter.
- Non-invasive override: We override the
unlink method on product.template.attribute.line.
- Dynamic Dependency Checking: To keep the module lightweight and avoid installation dependency hell, we check for transactional records dynamically. If the parameter is active, the
unlink method checks:
if 'stock.move' in self.env:
# Check for stock moves linked to variants
if 'sale.order.line' in self.env:
# Check for sale order lines
Clear User Feedback: If historical data is found, it raises a friendly UserError preventing the deletion: "Cannot delete this attribute. Variants associated with it already have stock or commercial history. Please archive this product and duplicate it to create a new one instead."
🔄 Version Target & Roadmap
We intend to develop and submit the PR for 16.0 first, as this is currently affecting our live production environments.
However, since this core behavior remains unchanged in recent versions, our goal is to design the module to be easily forward-ported to 17.0, 18.0, and 19.0.
Request for Comments (RFC)
We would love the maintainers' feedback on this approach before submitting the PR:
Do you agree with OCA/product-attribute as the target repository?
Do you prefer the dynamic checking (if 'stock.move' in self.env:) inside a single module, or would you strictly require "bridge modules" (e.g., product_attribute_deletion_protection_stock)?
Thank you for your time and for maintaining this great repository!
📝 Problem Statement: The "Orphaned Variants" Trap
In industrial and retail environments, product catalog integrity is critical. Currently, standard Odoo has a built-in behavior regarding product attributes that leads to severe catalog corruption and search conflicts if users make a common operational mistake.
The Real-World Scenario (The "Human Error" Factor):
A user intends to create a new product ("Product B") by using an existing one ("Product A") as a base. Due to a common operational oversight—and instead of using the standard "Duplicate" action—the user accidentally edits "Product A" directly.
They proceed to rename the template to "Product B", delete the existing attribute line (e.g., "Serafil Color" with 80 values), and add a new one (e.g., "Outdoor Color" with 34 values).
The Core Behavior Under the Hood:
Because the original 80 variants already have a transactional history (
stock.move,sale.order.line, etc.), the database prevents their deletion due to Foreign Key constraints (ondelete='restrict'). Odoo's core silently catches this database exception. Since it cannot delete the variants, it leaves them active and attached to the template as "stale combinations," stripping them of the deleted attribute values.The Business Impact:
These 80 orphaned variants retain their original Internal References (SKUs), stock levels, and historical data, but they now inherit the new template name ("Product B").
When sales reps or warehouse workers search for the old SKU to make a sale or move stock, Odoo displays "Product B". The user, thinking there is a typo, renames the template back to "Product A", instantly corrupting the 34 newly created variants. The catalog becomes a tangled mess of mixed SKUs and names.
💡 Proposed Solution: A Configurable Guardrail
We propose a new module in
OCA/product-attributenamedproduct_attribute_deletion_protection.Since we respect Odoo's default design for standard implementations, this module will introduce an opt-in protection mechanism, designed for strict environments where catalog traceability is mandatory.
Key Features & Architecture:
Inventory > Configuration > Settings(e.g., "Protect attributes with historical data"). It saves anir.config_parameter.unlinkmethod onproduct.template.attribute.line.unlinkmethod checks:Clear User Feedback: If historical data is found, it raises a friendly UserError preventing the deletion: "Cannot delete this attribute. Variants associated with it already have stock or commercial history. Please archive this product and duplicate it to create a new one instead."
🔄 Version Target & Roadmap
We intend to develop and submit the PR for 16.0 first, as this is currently affecting our live production environments.
However, since this core behavior remains unchanged in recent versions, our goal is to design the module to be easily forward-ported to 17.0, 18.0, and 19.0.
Request for Comments (RFC)
We would love the maintainers' feedback on this approach before submitting the PR:
Do you agree with OCA/product-attribute as the target repository?
Do you prefer the dynamic checking (if 'stock.move' in self.env:) inside a single module, or would you strictly require "bridge modules" (e.g., product_attribute_deletion_protection_stock)?
Thank you for your time and for maintaining this great repository!