Skip to content
Merged
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
31 changes: 10 additions & 21 deletions spp_gis_report_programs/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,10 @@ Key Capabilities
Key Models
~~~~~~~~~~

+---------------------------+------------------------------------------+
| Model | Extension |
+===========================+==========================================+
| ``spp.gis.report`` | Adds ``program_id`` field and filters |
| | domain by program members |
+---------------------------+------------------------------------------+
| ``spp.gis.report.wizard`` | Adds ``program_id`` selection with |
| | validation and code suffix |
+---------------------------+------------------------------------------+
- ``spp.gis.report`` — Adds ``program_id`` field and filters domain by
program members
- ``spp.gis.report.wizard`` — Adds ``program_id`` selection with
validation and code suffix

Configuration
~~~~~~~~~~~~~
Expand All @@ -67,18 +62,12 @@ UI Location
Security
~~~~~~~~

+------------------------------------------+----------------------------------+
| Group | Access |
+==========================================+==================================+
| ``spp_programs.group_programs_officer`` | Read GIS reports, data, |
| | templates, etc. |
+------------------------------------------+----------------------------------+
| ``spp_programs.group_programs_manager`` | Read/Write (no create/unlink) |
| | all models |
+------------------------------------------+----------------------------------+
| ``spp_gis_report.group_gis_report_user`` | Implied for program managers via |
| | XML |
+------------------------------------------+----------------------------------+
- ``spp_programs.group_programs_officer`` — Read GIS reports, data,
templates, etc.
- ``spp_programs.group_programs_manager`` — Read/Write (no
create/unlink) all models
- ``spp_gis_report.group_gis_report_user`` — Implied for program
managers via XML

Models with access: ``spp.gis.report``, ``spp.gis.report.data``,
``spp.gis.report.threshold``, ``spp.gis.report.template``,
Expand Down
1 change: 1 addition & 0 deletions spp_gis_report_programs/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"views/gis_report_views.xml",
"views/gis_report_wizard_views.xml",
],
"development_status": "Beta",
"installable": True,
"application": False,
"auto_install": True,
Expand Down
14 changes: 5 additions & 9 deletions spp_gis_report_programs/readme/DESCRIPTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,8 @@ Auto-install glue module that adds program context filtering to GIS reports. Ext

### Key Models

| Model | Extension |
| ----------------------- | -------------------------------------------------------------- |
| `spp.gis.report` | Adds `program_id` field and filters domain by program members |
| `spp.gis.report.wizard` | Adds `program_id` selection with validation and code suffix |
- `spp.gis.report` — Adds `program_id` field and filters domain by program members
- `spp.gis.report.wizard` — Adds `program_id` selection with validation and code suffix

### Configuration

Expand All @@ -26,11 +24,9 @@ No configuration required. Auto-installs when both `spp_gis_report` and `spp_pro

### Security

| Group | Access |
| ------------------------------------- | ----------------------------------------- |
| `spp_programs.group_programs_officer` | Read GIS reports, data, templates, etc. |
| `spp_programs.group_programs_manager` | Read/Write (no create/unlink) all models |
| `spp_gis_report.group_gis_report_user` | Implied for program managers via XML |
- `spp_programs.group_programs_officer` — Read GIS reports, data, templates, etc.
- `spp_programs.group_programs_manager` — Read/Write (no create/unlink) all models
- `spp_gis_report.group_gis_report_user` — Implied for program managers via XML

Models with access: `spp.gis.report`, `spp.gis.report.data`, `spp.gis.report.threshold`, `spp.gis.report.template`, `spp.gis.report.category`

Expand Down
60 changes: 14 additions & 46 deletions spp_gis_report_programs/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -387,27 +387,12 @@ <h1>Key Capabilities</h1>
</div>
<div class="section" id="key-models">
<h1>Key Models</h1>
<table border="1" class="docutils">
<colgroup>
<col width="39%" />
<col width="61%" />
</colgroup>
<thead valign="bottom">
<tr><th class="head">Model</th>
<th class="head">Extension</th>
</tr>
</thead>
<tbody valign="top">
<tr><td><tt class="docutils literal">spp.gis.report</tt></td>
<td>Adds <tt class="docutils literal">program_id</tt> field and filters
domain by program members</td>
</tr>
<tr><td><tt class="docutils literal">spp.gis.report.wizard</tt></td>
<td>Adds <tt class="docutils literal">program_id</tt> selection with
validation and code suffix</td>
</tr>
</tbody>
</table>
<ul class="simple">
<li><tt class="docutils literal">spp.gis.report</tt> — Adds <tt class="docutils literal">program_id</tt> field and filters domain by
program members</li>
<li><tt class="docutils literal">spp.gis.report.wizard</tt> — Adds <tt class="docutils literal">program_id</tt> selection with
validation and code suffix</li>
</ul>
</div>
<div class="section" id="configuration">
<h1>Configuration</h1>
Expand All @@ -425,31 +410,14 @@ <h1>UI Location</h1>
</div>
<div class="section" id="security">
<h1>Security</h1>
<table border="1" class="docutils">
<colgroup>
<col width="55%" />
<col width="45%" />
</colgroup>
<thead valign="bottom">
<tr><th class="head">Group</th>
<th class="head">Access</th>
</tr>
</thead>
<tbody valign="top">
<tr><td><tt class="docutils literal">spp_programs.group_programs_officer</tt></td>
<td>Read GIS reports, data,
templates, etc.</td>
</tr>
<tr><td><tt class="docutils literal">spp_programs.group_programs_manager</tt></td>
<td>Read/Write (no create/unlink)
all models</td>
</tr>
<tr><td><tt class="docutils literal">spp_gis_report.group_gis_report_user</tt></td>
<td>Implied for program managers via
XML</td>
</tr>
</tbody>
</table>
<ul class="simple">
<li><tt class="docutils literal">spp_programs.group_programs_officer</tt> — Read GIS reports, data,
templates, etc.</li>
<li><tt class="docutils literal">spp_programs.group_programs_manager</tt> — Read/Write (no
create/unlink) all models</li>
<li><tt class="docutils literal">spp_gis_report.group_gis_report_user</tt> — Implied for program
managers via XML</li>
</ul>
<p>Models with access: <tt class="docutils literal">spp.gis.report</tt>, <tt class="docutils literal">spp.gis.report.data</tt>,
<tt class="docutils literal">spp.gis.report.threshold</tt>, <tt class="docutils literal">spp.gis.report.template</tt>,
<tt class="docutils literal">spp.gis.report.category</tt></p>
Expand Down
2 changes: 2 additions & 0 deletions spp_gis_report_programs/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Part of OpenSPP. See LICENSE file for full copyright and licensing details.
from . import test_gis_report_programs
197 changes: 197 additions & 0 deletions spp_gis_report_programs/tests/test_gis_report_programs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
# Part of OpenSPP. See LICENSE file for full copyright and licensing details.


from odoo.exceptions import ValidationError
from odoo.tests import tagged
from odoo.tests.common import TransactionCase


@tagged("post_install", "-at_install")
class TestGISReportProgramsModel(TransactionCase):
"""Test GIS Report extension for program context filtering."""

@classmethod
def setUpClass(cls):
super().setUpClass()
cls.Report = cls.env["spp.gis.report"]
cls.Program = cls.env["spp.program"]

cls.program = cls.Program.create(
{
"name": "Test Program for GIS",
"target_type": "group",
}
)

# Get source model for res.partner
cls.partner_model = cls.env["ir.model"].search([("model", "=", "res.partner")], limit=1)

def _make_report_vals(self, **overrides):
"""Build minimal vals dict for a spp.gis.report record."""
vals = {
"name": "Test Report",
"code": "TEST_RPT_001",
"source_model_id": self.partner_model.id,
"area_field_path": "area_id",
"filter_mode": "domain",
"aggregation_method": "count",
"base_area_level": 2,
"normalization_method": "raw",
"threshold_mode": "auto_quartile",
"refresh_mode": "manual",
"geometry_type": "polygon",
}
vals.update(overrides)
return vals

def test_program_id_field_exists(self):
"""Test that program_id field is added to spp.gis.report."""
field_info = self.Report.fields_get(["program_id"])
self.assertIn("program_id", field_info)
self.assertEqual(field_info["program_id"]["relation"], "spp.program")

def test_report_create_with_program(self):
"""Test creating a report with program_id set."""
report = self.Report.create(self._make_report_vals(program_id=self.program.id))
self.assertEqual(report.program_id, self.program)

def test_report_create_without_program(self):
"""Test creating a report without program_id."""
report = self.Report.create(self._make_report_vals())
self.assertFalse(report.program_id)

def test_apply_context_filters_with_program(self):
"""Test _apply_context_filters adds program enrollment domain."""
report = self.Report.new(
{
"program_id": self.program.id,
"source_model": "res.partner",
}
)
domain = [("is_registrant", "=", True)]
result = report._apply_context_filters(domain)

# Should contain the original domain + program membership filter
flat = str(result)
self.assertIn("program_membership_ids.program_id", flat)

def test_apply_context_filters_without_program(self):
"""Test _apply_context_filters is a no-op without program_id."""
report = self.Report.new(
{
"source_model": "res.partner",
}
)
domain = [("is_registrant", "=", True)]
result = report._apply_context_filters(domain)
self.assertEqual(result, domain)

def test_apply_context_filters_non_partner_model(self):
"""Test _apply_context_filters skips filter for non-partner models."""
report = self.Report.new(
{
"program_id": self.program.id,
"source_model": "spp.area",
}
)
domain = [("active", "=", True)]
result = report._apply_context_filters(domain)
# Should NOT add program filter since source_model is not res.partner
self.assertEqual(result, domain)


@tagged("post_install", "-at_install")
class TestGISReportWizardPrograms(TransactionCase):
"""Test GIS Report Wizard extension for program context."""

@classmethod
def setUpClass(cls):
super().setUpClass()
cls.Wizard = cls.env["spp.gis.report.wizard"]
cls.Program = cls.env["spp.program"]

cls.program = cls.Program.create(
{
"name": "Wizard Test Program",
"target_type": "group",
}
)

def test_wizard_program_id_field(self):
"""Test that program_id field exists on the wizard."""
field_info = self.Wizard.fields_get(["program_id"])
self.assertIn("program_id", field_info)
self.assertEqual(field_info["program_id"]["relation"], "spp.program")

def test_validate_context_requirements_program_required(self):
"""Test validation when template requires program but none set."""
wizard = self.Wizard.new(
{
"template_requires_program": True,
"program_id": False,
}
)
with self.assertRaises(ValidationError):
wizard._validate_context_requirements()

def test_validate_context_requirements_program_provided(self):
"""Test validation passes when required program is provided."""
wizard = self.Wizard.new(
{
"template_requires_program": True,
"program_id": self.program.id,
}
)
# Should not raise
wizard._validate_context_requirements()

def test_validate_context_requirements_not_required(self):
"""Test validation passes when program is not required."""
wizard = self.Wizard.new(
{
"template_requires_program": False,
"program_id": False,
}
)
# Should not raise
wizard._validate_context_requirements()

def test_get_context_filter_vals_with_program(self):
"""Test _get_context_filter_vals includes program_id."""
wizard = self.Wizard.new(
{
"program_id": self.program.id,
}
)
vals = wizard._get_context_filter_vals()
self.assertEqual(vals.get("program_id"), self.program.id)

def test_get_context_filter_vals_without_program(self):
"""Test _get_context_filter_vals without program."""
wizard = self.Wizard.new(
{
"program_id": False,
}
)
vals = wizard._get_context_filter_vals()
self.assertNotIn("program_id", vals)

def test_get_context_code_suffix_with_program(self):
"""Test _get_context_code_suffix includes program ID."""
wizard = self.Wizard.new(
{
"program_id": self.program.id,
}
)
suffix = wizard._get_context_code_suffix()
self.assertIn(str(self.program.id), suffix)

def test_get_context_code_suffix_without_program(self):
"""Test _get_context_code_suffix returns empty without program."""
wizard = self.Wizard.new(
{
"program_id": False,
}
)
suffix = wizard._get_context_code_suffix()
self.assertEqual(suffix, "")
Loading
Loading