diff --git a/spp_farmer_registry_demo/README.rst b/spp_farmer_registry_demo/README.rst index 1d41a26d..41e532d9 100644 --- a/spp_farmer_registry_demo/README.rst +++ b/spp_farmer_registry_demo/README.rst @@ -120,6 +120,14 @@ Dependencies Changelog ========= +19.0.2.1.3 +~~~~~~~~~~ + +- fix(farmer_demo): type demo farm groups as FARM. Farm groups were + created with no group type, so they did not read as farms in the + registry; a farm stays typed FARM even when it joins a cooperative, + and only the cooperative container is typed COOPERATIVE (#1120) + 19.0.2.1.2 ~~~~~~~~~~ diff --git a/spp_farmer_registry_demo/__manifest__.py b/spp_farmer_registry_demo/__manifest__.py index f92f36d6..49baee95 100644 --- a/spp_farmer_registry_demo/__manifest__.py +++ b/spp_farmer_registry_demo/__manifest__.py @@ -3,7 +3,7 @@ "name": "OpenSPP Farmer Registry Demo", "summary": "Demo generator for Farmer Registry with fixed stories and volume generation", "category": "OpenSPP", - "version": "19.0.2.1.2", + "version": "19.0.2.1.3", "sequence": 1, "author": "OpenSPP.org", "website": "https://github.com/OpenSPP/OpenSPP2", diff --git a/spp_farmer_registry_demo/models/farmer_demo_generator.py b/spp_farmer_registry_demo/models/farmer_demo_generator.py index d7dc01e5..f1d5856f 100644 --- a/spp_farmer_registry_demo/models/farmer_demo_generator.py +++ b/spp_farmer_registry_demo/models/farmer_demo_generator.py @@ -808,12 +808,16 @@ def _create_story_farms(self): story_farms = {} + # OP#1120 review: resolved once for the run rather than per farm. + farm_group_type_id = self._ensure_farm_group_type() + for story_id, story_data in STORY_FARMS.items(): farm_type_id = self._get_vocab_code("urn:openspp:vocab:farm-type", story_data["farm_type"]) tenure_id = self._get_vocab_code("urn:openspp:vocab:land-tenure", story_data["tenure"]) holder_id = self._get_vocab_code("urn:openspp:vocab:holder-type", "individual") farm = self._create_farm( + farm_group_type_id=farm_group_type_id, name=story_data["farm_name"], farmer_name=story_data["farmer_name"], farm_type_id=farm_type_id, @@ -882,8 +886,13 @@ def _create_farm( phone=None, bank_name=None, age=None, + farm_group_type_id=None, ): - """Create a farm with the given attributes.""" + """Create a farm with the given attributes. + + ``farm_group_type_id`` is resolved by the caller once per run; left None + (a direct call, as in the tests) it is looked up here. + """ Partner = self.env["res.partner"].sudo() # nosemgrep farm_vals = { @@ -901,6 +910,13 @@ def _create_farm( "farm_size_idle": farm_size_idle, "experience_years": experience_years, } + # OP#1120: farm groups default to the FARM group type (a farm stays a + # farm even when it becomes a member of a cooperative — only the + # cooperative container is typed COOPERATIVE, in _create_cooperatives). + if farm_group_type_id is None: + farm_group_type_id = self._ensure_farm_group_type() + if farm_group_type_id: + farm_vals["group_type_id"] = farm_group_type_id if phone: farm_vals["phone"] = phone farm = Partner.create(farm_vals) @@ -1395,6 +1411,31 @@ def _ensure_cooperative_group_type(self): _logger.warning("Could not create cooperative group type vocabulary code") return False + def _ensure_farm_group_type(self): + """Ensure the 'farm' group type vocabulary code exists (OP#1120). + + Farm groups default to this type. Mirrors _ensure_cooperative_group_type + (ADR-016: local codes with is_local=True bypass system protection). + + Resolved once per run by the caller and passed down, rather than looked + up per farm: recordsets use __slots__, so the run-scoped attribute the + seeded generator caches on (it is a plain utility class, not a model) + is not available here (OP#1120 review). + + Returns: + int: vocabulary code ID for the farm group type, or False. + """ + try: + code = self.env["spp.vocabulary.code"].get_or_create_local( + "urn:openspp:vocab:group-type", + "farm", + display="Farm", + ) + return code.id + except Exception: + _logger.warning("Could not create farm group type vocabulary code") + return False + # ────────────────────────────────────────────────────────────────────── # GIS Data (Coordinates + Land Records) # ────────────────────────────────────────────────────────────────────── diff --git a/spp_farmer_registry_demo/models/seeded_farm_generator.py b/spp_farmer_registry_demo/models/seeded_farm_generator.py index ffafea0b..c5c5e4a0 100644 --- a/spp_farmer_registry_demo/models/seeded_farm_generator.py +++ b/spp_farmer_registry_demo/models/seeded_farm_generator.py @@ -369,6 +369,7 @@ def __init__(self, env, locale="fil_PH", seed=42): self._vocab_cache = {} self._species_cache = {} self._head_type_id = None + self._farm_group_type_id = None # OP#1120 # OP#1114: names already handed out this run, so the ~730 farms drawn # from an 86-surname pool don't end up with duplicate farm names (and, @@ -500,6 +501,10 @@ def generate_all_farms(self, blueprints): "farm_size_idle": idle, "experience_years": experience, } + # OP#1120: seeded farm groups default to the FARM group type. + farm_group_type_id = self._ensure_farm_group_type() + if farm_group_type_id: + gvals["group_type_id"] = farm_group_type_id if gps: gvals["coordinates"] = json.dumps({"type": "Point", "coordinates": [gps[0], gps[1]]}) if area_id: @@ -1151,6 +1156,26 @@ def _get_vocab_code(self, namespace_uri, code): self._vocab_cache[cache_key] = vocab.id if vocab else False return self._vocab_cache[cache_key] + def _ensure_farm_group_type(self): + """Ensure and return the 'farm' group type vocabulary code ID (OP#1120). + + Seeded farm groups default to this type. Created via get_or_create_local + so it exists even if the wizard hasn't already made it, and cached for + the run. + """ + if self._farm_group_type_id is None: + try: + code = self.env["spp.vocabulary.code"].get_or_create_local( + "urn:openspp:vocab:group-type", + "farm", + display="Farm", + ) + self._farm_group_type_id = code.id + except Exception: + _logger.warning("Could not create farm group type vocabulary code") + self._farm_group_type_id = False + return self._farm_group_type_id + def _resolve_species(self, species_code): """Map a species code string to a vocabulary code ID.""" if species_code in self._species_cache: diff --git a/spp_farmer_registry_demo/readme/HISTORY.md b/spp_farmer_registry_demo/readme/HISTORY.md index 34d7fb54..739567d7 100644 --- a/spp_farmer_registry_demo/readme/HISTORY.md +++ b/spp_farmer_registry_demo/readme/HISTORY.md @@ -1,3 +1,7 @@ +### 19.0.2.1.3 + +- fix(farmer_demo): type demo farm groups as FARM. Farm groups were created with no group type, so they did not read as farms in the registry; a farm stays typed FARM even when it joins a cooperative, and only the cooperative container is typed COOPERATIVE (#1120) + ### 19.0.2.1.2 - fix(demo): put the Input Subsidy Program on **manual** entitlement approval (`auto_approve_entitlements=False`) so a demo user can walk the full cycle → entitlement approval chain, not just cycle approval. The flag is now per-program (every other demo program stays auto-approve), and historically seeded cycles are unaffected because the generator force-approves their pending entitlements (#1122) diff --git a/spp_farmer_registry_demo/static/description/index.html b/spp_farmer_registry_demo/static/description/index.html index fe080f1f..0817fbb4 100644 --- a/spp_farmer_registry_demo/static/description/index.html +++ b/spp_farmer_registry_demo/static/description/index.html @@ -488,6 +488,15 @@

Changelog

+

19.0.2.1.3

+ +
+

19.0.2.1.2

-
+

19.0.2.1.1

-
+

19.0.2.1.0

  • feat(demo): add GIS + irrigation scenario (FM4) with reservoir + canal @@ -532,7 +541,7 @@

    19.0.2.1.0

    tables and the CR overview
-
+

19.0.2.0.0

  • Initial migration to OpenSPP2
  • diff --git a/spp_farmer_registry_demo/tests/test_demo_generator.py b/spp_farmer_registry_demo/tests/test_demo_generator.py index 54236099..42e738a6 100644 --- a/spp_farmer_registry_demo/tests/test_demo_generator.py +++ b/spp_farmer_registry_demo/tests/test_demo_generator.py @@ -219,6 +219,8 @@ def test_create_story_farms_are_groups(self): for farm in story_farms.values(): self.assertTrue(farm.is_group) self.assertTrue(farm.is_registrant) + # OP#1120: story farm groups are typed FARM. + self.assertEqual(farm.group_type_id.code, "farm") def test_create_story_farms_have_members(self): """Test story farms have farmer members.""" @@ -262,6 +264,24 @@ def test_create_farm_helper(self): self.assertEqual(farm.name, "Test Farm") self.assertTrue(farm.is_group) self.assertEqual(farm.farm_total_size, 3.0) + # OP#1120: farm groups default to the FARM group type. + self.assertEqual(farm.group_type_id.code, "farm") + + def test_1120_cooperative_container_typed_cooperative(self): + """OP#1120: the cooperative container is typed COOPERATIVE, while its + member farms stay FARM (a farm is still a farm inside a cooperative).""" + wizard = self.Generator.create({"name": _unique("Coop Type Test")}) + story_farms = wizard._create_story_farms() + + cooperatives = wizard._create_cooperatives(story_farms) + self.assertTrue(cooperatives, "expected at least one cooperative") + + for coop in cooperatives.values(): + self.assertEqual(coop.group_type_id.code, "cooperative") + members = self.env["spp.group.membership"].search([("group", "=", coop.id)]) + self.assertTrue(members, "cooperative should have member farms") + for membership in members: + self.assertEqual(membership.individual.group_type_id.code, "farm") def test_maria_santos_profile(self): """Test Maria Santos persona - smallholder rice farmer.""" diff --git a/spp_farmer_registry_demo/tests/test_seeded_farm_generator.py b/spp_farmer_registry_demo/tests/test_seeded_farm_generator.py index a965a2b1..bbdd7dbc 100644 --- a/spp_farmer_registry_demo/tests/test_seeded_farm_generator.py +++ b/spp_farmer_registry_demo/tests/test_seeded_farm_generator.py @@ -964,6 +964,9 @@ def test_generate_all_farms_mixed_farm_type(self): self.assertTrue(farm.is_group) self.assertTrue(farm.is_registrant) self.assertEqual(len(result["members"]), 2) + # OP#1120: seeded farm groups are typed FARM. + self.assertTrue(farm.group_type_id, "farm group must have a group type") + self.assertEqual(farm.group_type_id.code, "farm") # Verify farm details details = farm.farm_details_id