Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/backend/InvenTree/part/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1032,6 +1032,15 @@ def validate_unique(self, exclude=None):
parts = Part.objects.filter(IPN__iexact=self.IPN)
parts = parts.exclude(pk=self.pk)

# Parts in the same revision family share the same IPN by design,
# so exclude them from the duplicate check
if self.revision_of:
# Exclude the parent revision and all sibling revisions
parts = parts.exclude(pk=self.revision_of.pk)
parts = parts.exclude(revision_of=self.revision_of)
# Exclude any revisions of this part
parts = parts.exclude(revision_of=self)

if parts.exists():
raise ValidationError({
'IPN': _('Duplicate IPN not allowed in part settings')
Expand Down
38 changes: 38 additions & 0 deletions src/backend/InvenTree/part/test_part.py
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,44 @@ def test_duplicate_ipn(self):
Part.objects.create(name='abc', revision='5', description='A part', IPN=' ')
Part.objects.create(name='abc', revision='6', description='A part', IPN=' ')

def test_duplicate_ipn_with_revisions(self):
"""Revisions of the same part share the same IPN and must not trigger a duplicate IPN error.

Regression test for https://github.com/inventree/InvenTree/issues/12017
"""
set_global_setting('PART_ALLOW_DUPLICATE_IPN', False, self.user)

# Create the base part (revision 4)
base = Part.objects.create(
name='Widget',
description='A widget',
IPN='AS0001234',
revision='4',
assembly=True,
)

# Create a revision of the base part with the same IPN — must not raise
revision = Part(
name='Widget',
description='A widget',
IPN='AS0001234',
revision='5',
revision_of=base,
assembly=True,
)
revision.validate_unique() # should not raise
revision.save()

# Saving the revision again (e.g. after unlocking) must also not raise
revision.validate_unique()

# An unrelated part with the same IPN must still be rejected
with self.assertRaises(ValidationError):
unrelated = Part(
name='Other', description='Other part', IPN='AS0001234', revision='1'
)
unrelated.validate_unique()


class PartSubscriptionTests(InvenTreeTestCase):
"""Unit tests for part 'subscription'."""
Expand Down