diff --git a/spp_registry_name_suffix/__init__.py b/spp_registry_name_suffix/__init__.py
new file mode 100644
index 00000000..0650744f
--- /dev/null
+++ b/spp_registry_name_suffix/__init__.py
@@ -0,0 +1 @@
+from . import models
diff --git a/spp_registry_name_suffix/__manifest__.py b/spp_registry_name_suffix/__manifest__.py
new file mode 100644
index 00000000..f644d5f4
--- /dev/null
+++ b/spp_registry_name_suffix/__manifest__.py
@@ -0,0 +1,28 @@
+# Part of OpenSPP. See LICENSE file for full copyright and licensing details.
+{
+ "name": "OpenSPP Registry Name Suffix",
+ "summary": "Adds a configurable suffix field (Jr., Sr., III, etc.) to Individual registrant names in OpenSPP.",
+ "category": "OpenSPP",
+ "version": "17.0.1.4.1",
+ "sequence": 1,
+ "author": "OpenSPP.org",
+ "website": "https://github.com/OpenSPP/openspp-modules",
+ "license": "LGPL-3",
+ "development_status": "Beta",
+ "maintainers": ["jeremi", "gonzalesedwin1123"],
+ "depends": [
+ "g2p_registry_individual",
+ ],
+ "data": [
+ "security/ir.model.access.csv",
+ "views/name_suffix_views.xml",
+ "views/res_partner_views.xml",
+ "data/name_suffix_data.xml",
+ ],
+ "assets": {},
+ "demo": [],
+ "images": [],
+ "application": False,
+ "installable": True,
+ "auto_install": False,
+}
diff --git a/spp_registry_name_suffix/data/name_suffix_data.xml b/spp_registry_name_suffix/data/name_suffix_data.xml
new file mode 100644
index 00000000..6077580a
--- /dev/null
+++ b/spp_registry_name_suffix/data/name_suffix_data.xml
@@ -0,0 +1,171 @@
+
+
+
+
+
+ Jr.
+ JR
+ 10
+
+ Junior - typically used for a son named after his father
+
+
+
+ Sr.
+ SR
+ 20
+
+ Senior - typically used for a father when a son has the same name
+
+
+
+ Jra.
+ JRA
+ 25
+
+ JRA
+
+
+
+ I
+ I
+ 30
+
+ The First
+
+
+
+ II
+ II
+ 40
+
+ The Second
+
+
+
+ III
+ III
+ 50
+
+ The Third
+
+
+
+ IV
+ IV
+ 60
+
+ The Fourth
+
+
+
+ V
+ V
+ 70
+
+ The Fifth
+
+
+
+ VI
+ VI
+ 71
+
+ The Sixth
+
+
+
+ VII
+ VII
+ 72
+
+ The Seventh
+
+
+
+ VIII
+ VIII
+ 73
+
+ The Eighth
+
+
+
+ IX
+ IX
+ 74
+
+ The Ninth
+
+
+
+ X
+ X
+ 75
+
+ The Tenth
+
+
+
+ XI
+ XI
+ 76
+
+ The Eleventh
+
+
+
+ XII
+ XII
+ 77
+
+ The Twelfth
+
+
+
+ XIII
+ XIII
+ 78
+
+ The Thirteenth
+
+
+
+ XIV
+ XIV
+ 79
+
+ The Fourteenth
+
+
+
+ XV
+ XV
+ 80
+
+ The Fifteenth
+
+
+
+
+ PhD
+ PHD
+ 100
+ Doctor of Philosophy
+
+
+
+ MD
+ MD
+ 110
+ Doctor of Medicine
+
+
+
+ Esq.
+ ESQ
+ 120
+ Esquire - typically used for attorneys
+
+
+
diff --git a/spp_registry_name_suffix/models/__init__.py b/spp_registry_name_suffix/models/__init__.py
new file mode 100644
index 00000000..607fd527
--- /dev/null
+++ b/spp_registry_name_suffix/models/__init__.py
@@ -0,0 +1,2 @@
+from . import name_suffix
+from . import res_partner
diff --git a/spp_registry_name_suffix/models/name_suffix.py b/spp_registry_name_suffix/models/name_suffix.py
new file mode 100644
index 00000000..75b8d3ae
--- /dev/null
+++ b/spp_registry_name_suffix/models/name_suffix.py
@@ -0,0 +1,62 @@
+from odoo import fields, models
+
+
+class SPPNameSuffix(models.Model):
+ _name = "spp.name.suffix"
+ _description = "Name Suffix"
+ _order = "sequence, name"
+
+ name = fields.Char(
+ string="Suffix",
+ required=True,
+ help="The suffix value (e.g., Jr., Sr., III, PhD)",
+ )
+ code = fields.Char(
+ string="Code",
+ required=True,
+ help="Short code for the suffix",
+ )
+ sequence = fields.Integer(
+ string="Sequence",
+ default=10,
+ help="Used to order suffixes in dropdown lists",
+ )
+ active = fields.Boolean(
+ string="Active",
+ default=True,
+ help="If unchecked, the suffix will not be available for selection",
+ )
+ description = fields.Text(
+ string="Description",
+ help="Additional description or usage notes for this suffix",
+ )
+ is_generational = fields.Boolean(
+ string="Generational Suffix",
+ default=False,
+ help="Check this for generational suffixes (Jr., Sr., I, II, III, etc.). "
+ "Only one generational suffix can be used per individual.",
+ )
+
+ _sql_constraints = [
+ (
+ "name_uniq",
+ "unique(name)",
+ "Suffix name must be unique!",
+ ),
+ (
+ "code_uniq",
+ "unique(code)",
+ "Suffix code must be unique!",
+ ),
+ ]
+
+ def name_get(self):
+ """Display suffix name with code if different."""
+ result = []
+ for record in self:
+ if record.code and record.code != record.name:
+ name = f"{record.name} ({record.code})"
+ else:
+ name = record.name
+ result.append((record.id, name))
+ return result
diff --git a/spp_registry_name_suffix/models/res_partner.py b/spp_registry_name_suffix/models/res_partner.py
new file mode 100644
index 00000000..4ae404f2
--- /dev/null
+++ b/spp_registry_name_suffix/models/res_partner.py
@@ -0,0 +1,44 @@
+from odoo import _, api, fields, models
+from odoo.exceptions import ValidationError
+
+
+class ResPartner(models.Model):
+ _inherit = "res.partner"
+
+ suffix_ids = fields.Many2many(
+ comodel_name="spp.name.suffix",
+ relation="res_partner_name_suffix_rel",
+ column1="partner_id",
+ column2="suffix_id",
+ string="Suffixes",
+ help="Name suffixes",
+ )
+
+ @api.constrains("suffix_ids")
+ def _check_generational_suffix_conflict(self):
+ """Validate that only one generational suffix is selected."""
+ for record in self:
+ if not record.suffix_ids:
+ continue
+ generational_suffixes = record.suffix_ids.filtered(lambda s: s.is_generational)
+ if len(generational_suffixes) > 1:
+ suffix_names = ", ".join(generational_suffixes.mapped("name"))
+ raise ValidationError(
+ _(
+ "Only one generational suffix can be used at a time. "
+ "The following are generational suffixes: %(suffixes)s",
+ suffixes=suffix_names,
+ )
+ )
+
+ @api.onchange("is_group", "family_name", "given_name", "addl_name", "suffix_ids")
+ def name_change(self):
+ """Extend name change to include suffixes for individuals."""
+ super().name_change()
+ if not self.is_group and self.suffix_ids and self.name:
+ # Join all suffixes in sequence order, separated by comma
+ suffixes_str = ", ".join(self.suffix_ids.sorted("sequence").mapped(lambda s: s.name.upper()))
+ suffix_part = f", {suffixes_str}"
+ # Only append suffixes if not already present (avoid double-append)
+ if not self.name.endswith(suffix_part):
+ self.name = f"{self.name}{suffix_part}"
diff --git a/spp_registry_name_suffix/pyproject.toml b/spp_registry_name_suffix/pyproject.toml
new file mode 100644
index 00000000..4231d0cc
--- /dev/null
+++ b/spp_registry_name_suffix/pyproject.toml
@@ -0,0 +1,3 @@
+[build-system]
+requires = ["whool"]
+build-backend = "whool.buildapi"
diff --git a/spp_registry_name_suffix/readme/DESCRIPTION.md b/spp_registry_name_suffix/readme/DESCRIPTION.md
new file mode 100644
index 00000000..049cff0a
--- /dev/null
+++ b/spp_registry_name_suffix/readme/DESCRIPTION.md
@@ -0,0 +1,59 @@
+# OpenSPP Registry Name Suffix
+
+The `spp_registry_name_suffix` module adds a configurable suffix field to Individual registrant names in OpenSPP, enabling proper recording of name suffixes such as Jr., Sr., III, IV, PhD, MD, etc.
+
+## Purpose
+
+This module enhances individual registrant data by:
+
+* **Providing configurable suffixes**: Administrators can manage available suffixes through the Registry Configuration menu.
+* **Adding suffix support**: Record name suffixes using a standardized Many2one field reference.
+* **Extending name generation**: Automatically includes the suffix in the generated full name.
+* **Maintaining data integrity**: The suffix is stored as a reference to a configurable suffix record.
+
+## Features
+
+### Suffix Configuration
+A new "Name Suffixes" menu is available under Registry > Configuration, allowing administrators to:
+- Create, edit, and archive name suffixes
+- Define suffix codes for data integration
+- Set display order using sequence numbers
+- Add descriptions for suffix usage guidance
+
+### Pre-configured Suffixes
+The module comes with commonly used suffixes:
+- **Generational**: Jr., Sr., I, II, III, IV, V
+- **Academic/Professional**: PhD, MD, Esq.
+
+### Individual Registrant Integration
+The suffix field appears on the Individual registrant form after the "Additional Name" field. It uses a dropdown selection with the following features:
+- Quick search by suffix name or code
+- No inline creation (to maintain data quality)
+- Optional display in the registrant list view
+
+### Automatic Name Generation
+The suffix is automatically appended to the registrant's name when using the form. The module extends the `name_change` method from `g2p_registry_individual` to include the suffix in the generated name format:
+`FAMILY_NAME, GIVEN_NAME ADDL_NAME, SUFFIX`
+
+For example: "SMITH, JOHN MICHAEL, JR."
+
+## Dependencies
+
+This module depends on:
+- **g2p_registry_individual**: Provides the individual registrant views, model, and the base `name_change` method.
+
+## Configuration
+
+1. Navigate to Registry > Configuration > Name Suffixes
+2. Create additional suffixes as needed for your implementation
+3. Set the sequence to control display order in dropdowns
+
+## Usage
+
+1. Navigate to an Individual registrant form
+2. Select a suffix from the "Suffix" dropdown field
+3. The full name will automatically update to include the suffix
+
+## References
+
+- OpenG2P Registry Individual: https://github.com/OpenSPP/openg2p-registry/tree/17.0-develop-openspp/g2p_registry_individual
diff --git a/spp_registry_name_suffix/security/ir.model.access.csv b/spp_registry_name_suffix/security/ir.model.access.csv
new file mode 100644
index 00000000..35b546d4
--- /dev/null
+++ b/spp_registry_name_suffix/security/ir.model.access.csv
@@ -0,0 +1,4 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+
+spp_name_suffix_registrar,SPP Name Suffix Registrar Access,spp_registry_name_suffix.model_spp_name_suffix,g2p_registry_base.group_g2p_registrar,1,1,1,1
+spp_name_suffix_admin,SPP Name Suffix Admin Access,spp_registry_name_suffix.model_spp_name_suffix,g2p_registry_base.group_g2p_admin,1,1,1,1
diff --git a/spp_registry_name_suffix/static/description/icon.png b/spp_registry_name_suffix/static/description/icon.png
new file mode 100644
index 00000000..c7dbdaaf
Binary files /dev/null and b/spp_registry_name_suffix/static/description/icon.png differ
diff --git a/spp_registry_name_suffix/static/description/index.html b/spp_registry_name_suffix/static/description/index.html
new file mode 100644
index 00000000..5868206a
--- /dev/null
+++ b/spp_registry_name_suffix/static/description/index.html
@@ -0,0 +1,450 @@
+
+
+
+
+
+OpenSPP: Registry Name Suffix
+
+
+
+
+
OpenSPP: Registry Name Suffix
+
+

+
+
OpenSPP: Registry Name Suffix
+
This module adds a configurable suffix field to Individual registrant names in OpenSPP, enabling proper recording of name suffixes such as Jr., Sr., III, IV, PhD, MD, etc.
+
+
Purpose
+
The OpenSPP Registry Name Suffix module provides:
+
+- Configurable Suffixes: Administrators can manage available suffixes through the Registry Configuration menu.
+- Suffix Support: Record name suffixes using a standardized Many2one field reference.
+- Extended Name Generation: Automatically includes the suffix in the computed full name.
+- Data Integrity: The suffix is stored as a reference to a configurable suffix record.
+
+
+
+
Features
+
+
Suffix Configuration
+
A new "Name Suffixes" menu is available under Registry > Configuration, allowing administrators to:
+
+- Create, edit, and archive name suffixes
+- Define suffix codes for data integration
+- Set display order using sequence numbers
+- Add descriptions for suffix usage guidance
+
+
+
+
+
Individual Registrant Integration
+
The suffix field appears on the Individual registrant form after the "Additional Name" field. It uses a dropdown selection with the following features:
+
+- Quick search by suffix name or code
+- No inline creation (to maintain data quality)
+- Optional display in the registrant list view
+
+
+
+
Automatic Name Generation
+
The suffix is automatically appended to the registrant's computed name in the format:
+FAMILY_NAME, GIVEN_NAME, ADDL_NAME, SUFFIX
+
For example: "SMITH, JOHN, MICHAEL, JR."
+
+
+
+
Dependencies
+
This module depends on:
+
+- g2p_registry_individual: Provides the individual registrant views, model, and the base name_change method.
+
+
+
+
Configuration
+
+- Navigate to Registry > Configuration > Name Suffixes
+- Create additional suffixes as needed for your implementation
+- Set the sequence to control display order in dropdowns
+
+
+
+
Usage
+
+- Navigate to an Individual registrant form
+- Select a suffix from the "Suffix" dropdown field
+- The full name will automatically update to include the suffix
+
+
+
+
+
Bug Tracker
+
Bugs are tracked on GitHub Issues.
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us to smash it by providing a detailed and welcomed
+feedback.
+
Do not contact contributors directly about support or help with technical issues.
+
+
+
Credits
+
+
+
Maintainers
+
Current maintainers:
+

+
This module is part of the OpenSPP/openspp-modules project on GitHub.
+
You are welcome to contribute.
+
+
+
+
+
+
diff --git a/spp_registry_name_suffix/tests/__init__.py b/spp_registry_name_suffix/tests/__init__.py
new file mode 100644
index 00000000..25566a92
--- /dev/null
+++ b/spp_registry_name_suffix/tests/__init__.py
@@ -0,0 +1 @@
+from . import test_name_suffix
diff --git a/spp_registry_name_suffix/tests/test_name_suffix.py b/spp_registry_name_suffix/tests/test_name_suffix.py
new file mode 100644
index 00000000..f07a7d19
--- /dev/null
+++ b/spp_registry_name_suffix/tests/test_name_suffix.py
@@ -0,0 +1,328 @@
+from odoo.exceptions import ValidationError
+from odoo.tests import tagged
+from odoo.tests.common import TransactionCase
+
+
+@tagged("post_install", "-at_install")
+class TestNameSuffix(TransactionCase):
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ cls.env = cls.env(
+ context=dict(
+ cls.env.context,
+ test_queue_job_no_delay=True,
+ )
+ )
+
+ # Use existing suffixes from data file
+ cls.suffix_jr = cls.env.ref("spp_registry_name_suffix.suffix_jr")
+ cls.suffix_sr = cls.env.ref("spp_registry_name_suffix.suffix_sr")
+ cls.suffix_iii = cls.env.ref("spp_registry_name_suffix.suffix_iii")
+ cls.suffix_phd = cls.env.ref("spp_registry_name_suffix.suffix_phd")
+
+ def test_01_suffix_model_creation(self):
+ """Test that suffix model can be created correctly."""
+ suffix = self.env["spp.name.suffix"].create(
+ {
+ "name": "Test Suffix",
+ "code": "TEST",
+ }
+ )
+ self.assertTrue(suffix.active)
+ self.assertEqual(suffix.sequence, 10) # Default
+
+ def test_02_suffix_data_loaded(self):
+ """Test that default suffix data is loaded correctly."""
+ self.assertEqual(self.suffix_jr.name, "Jr.")
+ self.assertEqual(self.suffix_jr.code, "JR")
+ self.assertEqual(self.suffix_phd.name, "PhD")
+ self.assertEqual(self.suffix_phd.code, "PHD")
+
+ def test_03_name_with_single_suffix(self):
+ """Test that a single suffix is appended to the computed name."""
+ individual = self.env["res.partner"].create(
+ {
+ "name": "Temp", # Required by res_partner_check_name constraint
+ "family_name": "Doe",
+ "given_name": "John",
+ "suffix_ids": [(6, 0, [self.suffix_jr.id])],
+ "is_registrant": True,
+ "is_group": False,
+ }
+ )
+ # Call name_change to generate name (simulates form onchange)
+ individual.name_change()
+ self.assertEqual(
+ individual.name,
+ "DOE, JOHN, JR.",
+ "Name should include suffix",
+ )
+
+ def test_04_name_without_suffix(self):
+ """Test that name is computed correctly without suffix."""
+ individual = self.env["res.partner"].create(
+ {
+ "name": "Temp", # Required by res_partner_check_name constraint
+ "family_name": "Doe",
+ "given_name": "Jane",
+ "is_registrant": True,
+ "is_group": False,
+ }
+ )
+ individual.name_change()
+ self.assertEqual(
+ individual.name,
+ "DOE, JANE",
+ "Name should not have trailing comma when no suffix",
+ )
+
+ def test_05_name_with_multiple_suffixes(self):
+ """Test name with multiple suffixes (e.g., Jr. and PhD)."""
+ individual = self.env["res.partner"].create(
+ {
+ "name": "Temp", # Required by res_partner_check_name constraint
+ "family_name": "Smith",
+ "given_name": "Robert",
+ "suffix_ids": [(6, 0, [self.suffix_jr.id, self.suffix_phd.id])],
+ "is_registrant": True,
+ "is_group": False,
+ }
+ )
+ individual.name_change()
+ # Jr. has sequence 10, PhD has sequence 100, so Jr. comes first
+ self.assertEqual(
+ individual.name,
+ "SMITH, ROBERT, JR., PHD",
+ "Name should include multiple suffixes in sequence order",
+ )
+
+ def test_06_name_with_all_fields_and_multiple_suffixes(self):
+ """Test name with all fields including addl_name and multiple suffixes."""
+ individual = self.env["res.partner"].create(
+ {
+ "name": "Temp", # Required by res_partner_check_name constraint
+ "family_name": "Williams",
+ "given_name": "James",
+ "addl_name": "Edward",
+ "suffix_ids": [(6, 0, [self.suffix_jr.id, self.suffix_phd.id])],
+ "is_registrant": True,
+ "is_group": False,
+ }
+ )
+ individual.name_change()
+ self.assertEqual(
+ individual.name,
+ "WILLIAMS, JAMES EDWARD, JR., PHD",
+ "Name should include all parts including multiple suffixes",
+ )
+
+ def test_07_group_name_unaffected(self):
+ """Test that group name is not affected by suffix logic."""
+ group = self.env["res.partner"].create(
+ {
+ "name": "Test Group",
+ "suffix_ids": [(6, 0, [self.suffix_jr.id])],
+ "is_registrant": True,
+ "is_group": True,
+ }
+ )
+ # Call name_change to simulate form behavior
+ group.name_change()
+ self.assertEqual(
+ group.name,
+ "Test Group",
+ "Group name should not include suffix",
+ )
+
+ def test_08_suffix_update_triggers_name_change(self):
+ """Test that updating suffixes and calling name_change updates name."""
+ individual = self.env["res.partner"].create(
+ {
+ "name": "Temp", # Required by res_partner_check_name constraint
+ "family_name": "Johnson",
+ "given_name": "Michael",
+ "is_registrant": True,
+ "is_group": False,
+ }
+ )
+ # Call name_change to generate name
+ individual.name_change()
+ self.assertEqual(individual.name, "JOHNSON, MICHAEL")
+
+ # Add suffix and call name_change again
+ individual.suffix_ids = [(6, 0, [self.suffix_phd.id])]
+ individual.name_change()
+ self.assertEqual(
+ individual.name,
+ "JOHNSON, MICHAEL, PHD",
+ "Name should update when suffix is added",
+ )
+
+ def test_09_suffix_removal(self):
+ """Test that removing suffixes updates the name correctly."""
+ individual = self.env["res.partner"].create(
+ {
+ "name": "Temp", # Required by res_partner_check_name constraint
+ "family_name": "Williams",
+ "given_name": "Sarah",
+ "suffix_ids": [(6, 0, [self.suffix_jr.id])],
+ "is_registrant": True,
+ "is_group": False,
+ }
+ )
+ individual.name_change()
+ self.assertEqual(individual.name, "WILLIAMS, SARAH, JR.")
+
+ individual.suffix_ids = [(5, 0, 0)] # Clear all suffixes
+ individual.name_change()
+ self.assertEqual(
+ individual.name,
+ "WILLIAMS, SARAH",
+ "Name should update when suffixes are removed",
+ )
+
+ def test_10_suffix_sequence_ordering(self):
+ """Test that suffixes are ordered by sequence field."""
+ # PhD has sequence 100, Jr. has sequence 10
+ # Even if we add PhD first, Jr. should appear first in the name
+ individual = self.env["res.partner"].create(
+ {
+ "name": "Temp",
+ "family_name": "Brown",
+ "given_name": "David",
+ "suffix_ids": [(6, 0, [self.suffix_phd.id, self.suffix_jr.id])],
+ "is_registrant": True,
+ "is_group": False,
+ }
+ )
+ individual.name_change()
+ self.assertEqual(
+ individual.name,
+ "BROWN, DAVID, JR., PHD",
+ "Suffixes should be ordered by sequence regardless of selection order",
+ )
+
+ def test_11_name_get_with_different_code(self):
+ """Test name_get when code differs from name."""
+ # suffix_jr has name="Jr." and code="JR" (different)
+ result = self.suffix_jr.name_get()
+ self.assertEqual(len(result), 1)
+ self.assertEqual(result[0][0], self.suffix_jr.id)
+ self.assertEqual(
+ result[0][1],
+ "Jr. (JR)",
+ "name_get should show name with code in parentheses",
+ )
+
+ def test_12_name_get_with_same_code(self):
+ """Test name_get when code equals name."""
+ # Create a suffix where name and code are the same
+ suffix_same = self.env["spp.name.suffix"].create(
+ {
+ "name": "SAME",
+ "code": "SAME",
+ }
+ )
+ result = suffix_same.name_get()
+ self.assertEqual(len(result), 1)
+ self.assertEqual(result[0][0], suffix_same.id)
+ self.assertEqual(
+ result[0][1],
+ "SAME",
+ "name_get should show only name when code equals name",
+ )
+
+ def test_13_name_get_multiple_records(self):
+ """Test name_get with multiple records."""
+ # Get multiple suffixes at once
+ suffixes = self.suffix_jr | self.suffix_phd
+ result = suffixes.name_get()
+ self.assertEqual(len(result), 2)
+ # Check that all record IDs are in the result
+ result_ids = [r[0] for r in result]
+ self.assertIn(self.suffix_jr.id, result_ids)
+ self.assertIn(self.suffix_phd.id, result_ids)
+
+ def test_14_is_generational_set_on_generational_suffixes(self):
+ """Test that generational suffixes have is_generational flag set."""
+ self.assertTrue(
+ self.suffix_jr.is_generational,
+ "Jr. should be marked as generational",
+ )
+ self.assertTrue(
+ self.suffix_sr.is_generational,
+ "Sr. should be marked as generational",
+ )
+ self.assertTrue(
+ self.suffix_iii.is_generational,
+ "III should be marked as generational",
+ )
+ self.assertFalse(
+ self.suffix_phd.is_generational,
+ "PhD should not be marked as generational",
+ )
+
+ def test_15_generational_suffix_conflict_prevented(self):
+ """Test that two generational suffixes cannot be selected together."""
+ individual = self.env["res.partner"].create(
+ {
+ "name": "Temp",
+ "family_name": "Doe",
+ "given_name": "John",
+ "is_registrant": True,
+ "is_group": False,
+ }
+ )
+ # Try to add both Jr. and Sr. (both generational)
+ with self.assertRaises(ValidationError) as context:
+ individual.write({"suffix_ids": [(6, 0, [self.suffix_jr.id, self.suffix_sr.id])]})
+ self.assertIn("generational", str(context.exception).lower())
+
+ def test_16_generational_with_non_generational_allowed(self):
+ """Test that generational + non-generational suffixes can be combined."""
+ # Jr. (generational) + PhD (non-generational) should work
+ individual = self.env["res.partner"].create(
+ {
+ "name": "Temp",
+ "family_name": "Smith",
+ "given_name": "Jane",
+ "suffix_ids": [(6, 0, [self.suffix_jr.id, self.suffix_phd.id])],
+ "is_registrant": True,
+ "is_group": False,
+ }
+ )
+ self.assertEqual(len(individual.suffix_ids), 2)
+ individual.name_change()
+ self.assertEqual(individual.name, "SMITH, JANE, JR., PHD")
+
+ def test_17_roman_numerals_conflict(self):
+ """Test that two roman numeral suffixes cannot be selected together."""
+ individual = self.env["res.partner"].create(
+ {
+ "name": "Temp",
+ "family_name": "King",
+ "given_name": "Henry",
+ "is_registrant": True,
+ "is_group": False,
+ }
+ )
+ suffix_iv = self.env.ref("spp_registry_name_suffix.suffix_iv")
+ # Try to add both III and IV (both generational)
+ with self.assertRaises(ValidationError):
+ individual.write({"suffix_ids": [(6, 0, [self.suffix_iii.id, suffix_iv.id])]})
+
+ def test_18_jr_and_roman_numeral_conflict(self):
+ """Test that Jr. and roman numerals cannot be selected together."""
+ individual = self.env["res.partner"].create(
+ {
+ "name": "Temp",
+ "family_name": "Windsor",
+ "given_name": "Charles",
+ "is_registrant": True,
+ "is_group": False,
+ }
+ )
+ # Try to add Jr. and III (both generational)
+ with self.assertRaises(ValidationError):
+ individual.write({"suffix_ids": [(6, 0, [self.suffix_jr.id, self.suffix_iii.id])]})
diff --git a/spp_registry_name_suffix/views/name_suffix_views.xml b/spp_registry_name_suffix/views/name_suffix_views.xml
new file mode 100644
index 00000000..0b6384c5
--- /dev/null
+++ b/spp_registry_name_suffix/views/name_suffix_views.xml
@@ -0,0 +1,90 @@
+
+
+
+
+
+ spp.name.suffix.tree
+ spp.name.suffix
+
+
+
+
+
+
+
+
+
+
+
+
+
+ spp.name.suffix.form
+ spp.name.suffix
+
+
+
+
+
+
+
+ spp.name.suffix.search
+ spp.name.suffix
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name Suffixes
+ spp.name.suffix
+ tree,form
+
+
+
+ Create your first name suffix
+
+
+ Name suffixes like Jr., Sr., III, PhD, MD can be configured here
+ and then selected on individual registrant records.
+
+
+
+
+
+
+
+
diff --git a/spp_registry_name_suffix/views/res_partner_views.xml b/spp_registry_name_suffix/views/res_partner_views.xml
new file mode 100644
index 00000000..821c5412
--- /dev/null
+++ b/spp_registry_name_suffix/views/res_partner_views.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+ res.partner.view.form.inherit.name.suffix
+ res.partner
+
+
+
+
+
+
+
+
+
+
+ res.partner.view.tree.inherit.name.suffix
+ res.partner
+
+
+
+
+
+
+
+
+