diff --git a/subscription_oca/models/sale_order.py b/subscription_oca/models/sale_order.py index 43d4f9bbea..a470e495f8 100644 --- a/subscription_oca/models/sale_order.py +++ b/subscription_oca/models/sale_order.py @@ -6,6 +6,7 @@ from dateutil.relativedelta import relativedelta from odoo import Command, api, fields, models +from odoo.tools import clean_context class SaleOrder(models.Model): @@ -54,16 +55,25 @@ def create_subscription(self, lines, subscription_tmpl): subscription_lines = [ Command.create(line.get_subscription_line_values()) for line in lines ] - rec = self.env["sale.subscription"].create( - { - "partner_id": self.partner_id.id, - "user_id": self.env.context.get("uid", self.env.uid), - "template_id": subscription_tmpl.id, - "pricelist_id": self.partner_id.property_product_pricelist.id, - "date_start": date.today(), - "sale_order_id": self.id, - "sale_subscription_line_ids": subscription_lines, - } + # Strip all default_* keys from context to prevent cross-model context + # pollution. sale_crm injects default_tag_ids with crm.tag IDs which + # would otherwise be applied by default_get() to sale.subscription.tag_ids, + # causing a FK constraint violation. + # pylint: disable=context-overridden + rec = ( + self.env["sale.subscription"] + .with_context(clean_context(self.env.context)) + .create( + { + "partner_id": self.partner_id.id, + "user_id": self.env.context.get("uid", self.env.uid), + "template_id": subscription_tmpl.id, + "pricelist_id": self.partner_id.property_product_pricelist.id, + "date_start": date.today(), + "sale_order_id": self.id, + "sale_subscription_line_ids": subscription_lines, + } + ) ) rec.action_start_subscription() rec.recurring_next_date = self.get_next_interval( diff --git a/subscription_oca/tests/test_subscription_oca.py b/subscription_oca/tests/test_subscription_oca.py index 0d9ca7afca..9c17068fdd 100644 --- a/subscription_oca/tests/test_subscription_oca.py +++ b/subscription_oca/tests/test_subscription_oca.py @@ -683,3 +683,72 @@ def _collect_all_sub_test_results(self, subscription): ) test_res.append(group_stage_ids) return test_res + + def test_action_confirm_with_crm_context_and_subscribable_product(self): + """Test that action_confirm with CRM context doesn't cause FK violation. + + This test verifies that when a sale.order with a subscribable product is + confirmed with default_tag_ids in context (as happens when confirming from + a CRM opportunity), the subscription is created successfully without + a Foreign Key violation. + + The fix ensures that default_tag_ids from CRM context (containing + crm.tag IDs) are not applied to sale.subscription.tag_ids (which + expects sale.subscription.tag IDs). + + Reproduces the production scenario: + 1. A sale.order is created with a subscribable product linked to + a subscription template + 2. action_confirm() is called with context containing CRM tag IDs + in default_tag_ids + 3. Without fix: FK constraint violation occurs on + sale_subscription_sale_subscription_tag_rel + 4. With fix: subscription is created successfully, tag_ids remains + empty + """ + # Create a crm.tag (represents tags from CRM opportunity) + crm_tag = self.env["crm.tag"].create({"name": "Test CRM Tag for SO"}) + + # Ensure product_1 has a subscription template linked + # (this is needed for subscription to be created on SO confirmation) + if not self.product_1.product_tmpl_id.subscription_template_id: + self.product_1.product_tmpl_id.subscription_template_id = self.tmpl1.id + + # Create a sale order with a subscribable product + sale_order = self.env["sale.order"].create( + { + "partner_id": self.partner.id, + "partner_invoice_id": self.partner.id, + "partner_shipping_id": self.partner.id, + "order_line": [ + Command.create( + { + "name": self.product_1.name, + "product_id": self.product_1.id, + "product_uom_qty": 1, + "product_uom": self.product_1.uom_id.id, + "price_unit": self.product_1.list_price, + } + ) + ], + } + ) + + # Call action_confirm with context containing crm.tag ID in default_tag_ids + # This simulates confirming a quotation from a CRM opportunity with tags + # With the fix: subscription should be created without FK violation + sale_order.with_context(default_tag_ids=[(6, 0, [crm_tag.id])]).action_confirm() + + # Verify that subscription was created + subscriptions = self.env["sale.subscription"].search( + [("sale_order_id", "=", sale_order.id)] + ) + self.assertEqual(len(subscriptions), 1, "Subscription should be created") + + # Verify that subscription tag_ids is empty (not polluted by crm.tag) + subscription = subscriptions[0] + self.assertEqual( + len(subscription.tag_ids), + 0, + "subscription.tag_ids should be empty, not contaminated by crm.tag IDs", + )