diff --git a/spp_graduation/README.rst b/spp_graduation/README.rst index d81a083c..d3c53913 100644 --- a/spp_graduation/README.rst +++ b/spp_graduation/README.rst @@ -22,28 +22,38 @@ OpenSPP Graduation Management |badge1| |badge2| |badge3| -Manages beneficiary graduation from time-bound social protection -programs. Defines graduation pathways with weighted criteria, conducts -assessments against those criteria, calculates readiness scores, and -tracks graduation outcomes with post-graduation monitoring periods. -Supports both positive exits (graduation) and negative exits (program -removal). +Manages beneficiary graduation and exit from time-bound social +protection programs. Defines graduation pathways with weighted criteria, +conducts assessments against those criteria, calculates readiness +scores, and tracks graduation outcomes with post-graduation monitoring +periods. Supports both positive exits (graduation) and negative exits +(program removal). Key Capabilities ~~~~~~~~~~~~~~~~ -- Define graduation pathways with configurable criteria, exit type, and - monitoring duration -- Create weighted criteria with different assessment methods - (self-report, verification, computed, observation) -- Conduct beneficiary assessments with criteria responses and evidence - attachments -- Calculate readiness scores based on weighted criteria and enforce - required criteria -- Submit assessments for manager approval through a - draft/submitted/approved/rejected workflow -- Track graduation dates and compute post-graduation monitoring periods -- Filter assessments by assessor, state, pathway, and recommendation +- Define graduation pathways with configurable criteria, exit type + (``is_positive_exit``), and monitoring duration +- Create weighted criteria with four assessment methods: self-report, + verification, computed, observation +- Conduct beneficiary assessments with per-criterion scores, a manual + met/not-met judgment, and notes +- Calculate weighted readiness scores (0–1) from ``score`` fields and + enforce required criteria via ``is_met`` flags through + ``_compute_scores()``. The ``score`` (numeric, 0–1) and ``is_met`` + (boolean) fields serve different purposes: ``score`` drives the + weighted readiness score, while ``is_met`` is a qualitative assessor + judgment used to check whether required criteria are satisfied. They + are intentionally independent because some assessment methods (e.g., + field observation) may not map cleanly to a numeric score. +- Approve assessments through a draft → submitted → approved/rejected + workflow; approval auto-sets ``graduation_date`` when recommendation + is "graduate" +- Compute ``monitoring_end_date`` from ``graduation_date`` + pathway's + ``post_graduation_monitoring_months`` +- Ships with three pre-configured pathways: Standard Graduation (12 + months monitoring), Early Graduation (18 months), and Administrative + Exit (negative, 0 months) Key Models ~~~~~~~~~~ @@ -51,17 +61,22 @@ Key Models +--------------------------------------+----------------------------------+ | Model | Description | +======================================+==================================+ -| ``spp.graduation.pathway`` | Defines a graduation pathway | -| | with criteria and exit type | +| ``spp.graduation.pathway`` | Graduation pathway with exit | +| | type, approval/assessment flags, | +| | and criteria | +--------------------------------------+----------------------------------+ -| ``spp.graduation.criteria`` | Individual criterion within a | -| | pathway with weight and method | +| ``spp.graduation.criteria`` | Weighted criterion within a | +| | pathway; has assessment method | +| | and required flag | +--------------------------------------+----------------------------------+ | ``spp.graduation.assessment`` | Assessment of a beneficiary | -| | against a pathway with scores | +| | against a pathway; tracks scores | +| | and approval state | +--------------------------------------+----------------------------------+ -| ``spp.graduation.criteria.response`` | Response to a specific criterion | -| | within an assessment | +| ``spp.graduation.criteria.response`` | Per-criterion response with | +| | ``score``, ``is_met``, | +| | ``value``, ``notes``, and | +| | ``evidence_attachment_ids`` | +--------------------------------------+----------------------------------+ Configuration @@ -69,26 +84,40 @@ Configuration After installing: -1. Navigate to **Graduation > Configuration > Pathways** -2. Create graduation pathways specifying exit type (positive/negative) - and monitoring months -3. Add criteria to each pathway with weight, assessment method, and - required flag -4. Users can then create assessments under **Graduation > Assessments > - All Assessments** +1. Navigate to **Graduation > Configuration > Pathways** (managers only) +2. Three default pathways are pre-installed; create additional ones as + needed +3. On each pathway, set ``is_positive_exit``, + ``is_assessment_required``, ``is_approval_required``, and + ``post_graduation_monitoring_months`` +4. Open the **Criteria** tab on the pathway form to add criteria with + weight, assessment method, and required flag (inline editable list) +5. Users create assessments under **Graduation > Assessments > All + Assessments** UI Location ~~~~~~~~~~~ -- **Menu**: Graduation (top-level menu) -- **Assessments**: Graduation > Assessments > All Assessments / My - Assessments -- **Configuration**: Graduation > Configuration > Pathways (managers - only) -- **Views**: List, kanban (grouped by state), and form views with - approval workflow -- **Pathway Form**: Criteria tab shows inline editable criteria list -- **Assessment Form**: Criteria Responses and Recommendation tabs +- **Top-level menu**: Graduation (visible to + ``group_spp_graduation_user`` and above) +- **Graduation > Assessments > All Assessments**: List, kanban (grouped + by state), form, graph, and pivot views +- **Graduation > Assessments > My Assessments**: Same views, + pre-filtered to current user's assessments +- **Graduation > Configuration > Pathways**: List and form views + (managers only) +- **Pathway form**: Two-column layout with a **Criteria** tab containing + an inline editable list +- **Assessment form**: **Overview** tab (beneficiary, pathway, scores, + dates), **Criteria Responses** tab (inline editable list with + ``criteria_id``, ``score``, ``is_met``, ``value``, ``notes``, + ``evidence_attachment_ids``), **Recommendation** tab (selection + + notes), and **History** tab (audit metadata). Statusbar shows + draft/submitted/approved. Alert banners for submitted and rejected + states. +- **Assessment form buttons**: Submit (draft), Approve/Reject + (submitted, managers only), Reset to Draft (submitted or rejected, + managers only) Security ~~~~~~~~ @@ -97,33 +126,509 @@ Security | Group | Access | +=================================================+==================================+ | ``spp_graduation.group_spp_graduation_user`` | Read pathways/criteria; | -| | create/edit own assessments (no | -| | delete) | +| | read/write/create own | +| | assessments (no delete); full | +| | CRUD on own criteria responses | +-------------------------------------------------+----------------------------------+ -| ``spp_graduation.group_spp_graduation_manager`` | Full CRUD on all graduation data | -| | and configuration | +| ``spp_graduation.group_spp_graduation_manager`` | Full CRUD on all graduation | +| | models | +-------------------------------------------------+----------------------------------+ +Record rules restrict users to assessments where +``assessor_id = current user`` and responses on those assessments. +Managers have unrestricted access. Multi-company isolation rules apply +to pathways and assessments. + Extension Points ~~~~~~~~~~~~~~~~ -- Inherit ``spp.graduation.assessment`` and override - ``_compute_scores()`` to customize readiness calculation +- Override ``_compute_scores()`` on ``spp.graduation.assessment`` to + customize readiness calculation logic +- Override ``_compute_monitoring_end()`` to change how monitoring end + dates are derived - Inherit ``spp.graduation.pathway`` to add domain-specific pathway fields -- Extend approval workflow by inheriting assessment actions - (``action_submit``, ``action_approve``) +- Inherit assessment workflow actions (``action_submit``, + ``action_approve``, ``action_reject``, ``action_reset_draft``) Dependencies ~~~~~~~~~~~~ -``base``, ``spp_security``, ``mail`` +``base``, ``spp_registry``, ``spp_security``, ``mail`` **Table of contents** .. contents:: :local: +Usage +===== + +UI Testing Guide +---------------- + +Manual QA test plan for the Graduation Management module. Tests are +organized by feature area and should be executed in order since later +tests depend on data created in earlier ones. + +Prerequisites +~~~~~~~~~~~~~ + +- Install ``spp_graduation`` module +- Two test users configured: + + - **QA User**: assigned to Graduation > User privilege + - **QA Manager**: assigned to Graduation > Manager privilege + +- At least one registrant (``res.partner`` with + ``is_registrant = True``) to use as a beneficiary + +After installation, three default pathways exist: Standard Graduation, +Early Graduation, and Administrative Exit. + +-------------- + +Test 1: Pathway Configuration (as QA Manager) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Menu**: Graduation > Configuration > Pathways + +**1.1 Verify pre-installed pathways** + +1. Open **Graduation > Configuration > Pathways** +2. Verify three pathways exist in the list: + + - Standard Graduation (code: STANDARD, positive exit, 12 months + monitoring) + - Early Graduation (code: EARLY, positive exit, 18 months monitoring) + - Administrative Exit (code: ADMIN_EXIT, negative exit, 0 months + monitoring) + +3. Verify the **Criteria** column shows a count for each pathway (5 for + Standard, 2 for Early, 0 for Admin Exit) +4. Verify optional columns can be toggled: Code, Is Assessment Required, + Is Approval Required, Post Graduation Monitoring Months + +**1.2 Search and filter pathways** + +1. Use the search bar to search by name (e.g., "Standard") +2. Apply the **Positive Exit** filter — only Standard and Early should + appear +3. Apply the **Negative Exit** filter — only Administrative Exit should + appear +4. Apply the **Archived** filter — should show no results (none are + archived) + +**1.3 Create a new pathway** + +1. Click **New** +2. Fill in: + + - Name: "Test Graduation Pathway" + - Code: "TEST_QA" + - Is Positive Exit: ON (toggle) + - Is Assessment Required: ON (toggle) + - Is Approval Required: ON (toggle) + - Post Graduation Monitoring Months: 6 + - Description: "QA test pathway" + +3. Open the **Criteria** tab +4. Click **Add a line** and create two criteria: + + - Name: "Income Criterion", Weight: 2.0, Assessment Method: + Verification Required, Is Required: checked + - Name: "Education Criterion", Weight: 1.0, Assessment Method: Self + Report, Is Required: unchecked + +5. Save +6. Verify the **Criteria** count in the list view shows 2 + +**1.4 Verify weight constraint** + +1. Open the Test Graduation Pathway +2. In the Criteria tab, try to add a criterion with **Weight: 0** +3. **Expected**: Validation error "Weight must be greater than zero" +4. Try **Weight: -1** +5. **Expected**: Same validation error + +**1.5 Archive and unarchive** + +1. Open any pathway, click **Action > Archive** +2. Verify the red "Archived" ribbon appears +3. Go back to the list, apply the **Archived** filter, verify the + pathway appears +4. Open it and click **Action > Unarchive** + +**1.6 Drag to reorder** + +1. In the list view, drag the handle (≡) on a pathway row to reorder +2. Verify the sequence updates + +-------------- + +Test 2: Assessment Creation and Form Layout (as QA User) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Menu**: Graduation > Assessments > All Assessments + +**2.1 List view** + +1. Open **Graduation > Assessments > All Assessments** +2. Verify the list is empty (or shows existing assessments) +3. Verify columns: Name, Beneficiary, Pathway, Assessment Date, Assessor + (with avatar), Readiness Score, Required Criteria Met, + Recommendation, State (badge) +4. Verify optional columns can be toggled via the column selector icon + +**2.2 Create an assessment** + +1. Click **New** +2. Verify the form opens with: + + - State: **Draft** in the statusbar + - **Submit** button visible in the header + - No Approve/Reject/Reset to Draft buttons (user is not a manager) + - Title shows "New Assessment" + +3. On the **Overview** tab, fill in: + + - Beneficiary: select a registrant record + - Pathway: select "Test Graduation Pathway" (created in Test 1.3) + - Assessment Date: today (should be pre-filled) + - Assessor: current user (should be pre-filled) + +4. Verify the title updates to "{Beneficiary Name} - Test Graduation + Pathway" +5. Verify the right column shows: + + - Readiness Score: 0% + - Required Criteria Met: OFF + - Graduation Date: empty + - Monitoring End Date: empty + +6. Save the record + +**2.3 Add criteria responses** + +**Important — understanding ``score`` vs ``is_met``**: These two fields +serve different purposes and are set independently by the assessor: + +- **Score** (0–1): A numeric rating that feeds into the weighted + **Readiness Score** calculation. For example, 0.8 means the + beneficiary scored 80% on this criterion. +- **Is Met** (toggle): A qualitative yes/no judgment by the assessor + indicating whether the criterion is satisfied. This is used only to + check the **Required Criteria Met** flag — if a criterion is marked as + "required" on the pathway, its ``is_met`` must be ON for the overall + check to pass. + +These fields are intentionally independent because some assessment +methods (e.g., field observation) may not map cleanly to a numeric +score. An assessor might give a low numeric score but still consider the +criterion met based on qualitative judgment, or vice versa. + +1. Open the **Criteria Responses** tab +2. Click **Add a line** +3. Add a response for "Income Criterion": + + - Score: 0.8 + - Is Met: ON (toggle) — assessor judges this criterion is satisfied + - Value: "Above threshold" + - Notes: "Verified via documentation" + +4. Add a response for "Education Criterion": + + - Score: 0.6 + - Is Met: OFF — assessor judges this criterion is not yet satisfied + - Value: "Partial" + +5. Save +6. Go back to the **Overview** tab and verify: + + - Readiness Score is computed (should be around 73% based on weights + 2.0 and 1.0) + - Required Criteria Met: ON (because Income Criterion is required and + is_met = True) + +**2.3a Verify score and is_met independence** + +1. Change the Income Criterion response to Score: 0.3, Is Met: ON +2. Save and verify: + + - Readiness Score drops (lower numeric score) + - Required Criteria Met stays ON (because is_met is still toggled on) + +3. Change it to Score: 1.0, Is Met: OFF +4. Save and verify: + + - Readiness Score increases (higher numeric score) + - Required Criteria Met changes to OFF (because is_met is toggled off + on a required criterion) + +5. Restore to Score: 0.8, Is Met: ON for subsequent tests + +**2.4 Evidence attachments** + +1. Go to the **Criteria Responses** tab +2. On the Income Criterion response row, click the attachment icon in + the Evidence Attachments column +3. Upload a test file (e.g., a small PDF or image) +4. Verify the file appears attached +5. Click on the response row to open the popup form +6. Verify the popup shows: Criteria (readonly), Score, Is Met, Value, + Notes, and an Evidence section with the uploaded file + +**2.5 Score constraint** + +1. In the Criteria Responses tab, try to change a score to **1.5** +2. **Expected**: Validation error "Score must be between 0 and 1" +3. Try **-0.1** +4. **Expected**: Same validation error +5. Try **0** and **1** — both should be accepted + +**2.6 Recommendation tab** + +1. Open the **Recommendation** tab +2. Select a recommendation: "Ready to Graduate" +3. Enter recommendation notes: "Beneficiary meets all criteria" +4. Save + +-------------- + +Test 3: Approval Workflow +~~~~~~~~~~~~~~~~~~~~~~~~~ + +**3.1 Submit (as QA User)** + +1. Open the assessment created in Test 2 +2. Click the **Submit** button +3. Verify: + + - State changes to **Submitted** + - Submit button disappears + - A yellow **Pending Review** alert banner appears: "This assessment + is awaiting manager approval." + - Overview fields (Beneficiary, Pathway, Date, Assessor) are still + editable + - No Approve/Reject buttons visible (user is not a manager) + +**3.2 Verify double-submit is blocked (as QA User)** + +1. The assessment is already in Submitted state +2. There should be no Submit button visible +3. (Programmatic check: calling ``action_submit()`` on a submitted + record raises an error) + +**3.3 Approve (as QA Manager)** + +1. Log in as QA Manager +2. Open **Graduation > Assessments > All Assessments** +3. Verify the manager can see the submitted assessment (even though + another user created it) +4. Open the assessment +5. Verify **Approve** and **Reject** buttons are visible, plus **Reset + to Draft** +6. Click **Approve** +7. Verify: + + - State changes to **Approved** + - All buttons disappear (no further actions on approved records) + - **Graduation Date** is set to today (because recommendation was + "Ready to Graduate") + - **Monitoring End Date** is set to 6 months from today (pathway has + 6 months monitoring) + - **Approved By** shows the manager's name + - **Approved Date** shows the current datetime + - Overview fields (Beneficiary, Pathway, etc.) become readonly + - Criteria Responses tab becomes readonly + - Recommendation tab becomes readonly + +**3.4 Verify reset from approved is blocked** + +1. On the approved assessment, verify there is no **Reset to Draft** + button + +-------------- + +Test 4: Rejection and Reset Flow +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**4.1 Create and submit another assessment (as QA User)** + +1. Create a new assessment with: + + - Beneficiary: any registrant + - Pathway: "Standard Graduation" + - Recommendation: "Extend Participation" + +2. Submit it + +**4.2 Reject (as QA Manager)** + +1. Open the submitted assessment +2. Click **Reject** +3. Verify: + + - State changes to **Rejected** + - A red **Rejected** alert banner appears: "This assessment was + rejected. Review and reset to draft to resubmit." + - **Reset to Draft** button is visible + - Overview and Criteria Responses fields are readonly + +**4.3 Reset to draft (as QA Manager)** + +1. Click **Reset to Draft** +2. Verify: + + - State returns to **Draft** + - Alert banners disappear + - **Submit** button reappears + - Fields become editable again + +3. Make changes and re-submit + +**4.4 Approve non-graduate recommendation** + +1. Approve the re-submitted assessment (recommendation is "Extend + Participation") +2. Verify: + + - State is Approved + - **Graduation Date is empty** (not set because recommendation is not + "Ready to Graduate") + - **Monitoring End Date is empty** + +-------------- + +Test 5: Kanban View +~~~~~~~~~~~~~~~~~~~ + +1. Navigate to **Graduation > Assessments > All Assessments** +2. Switch to **Kanban** view +3. Verify: + + - Cards are grouped by state columns: Draft, Submitted, Approved, + Rejected + - A colored progress bar appears at the top of each column + - Each card shows: Beneficiary name, state badge, pathway, date with + calendar icon, readiness score as percentage, and recommendation + badge (if set) + - Recommendation badges have colors: green (Graduate), yellow + (Extend), red (Exit), blue (Defer) + +-------------- + +Test 6: Graph and Pivot Views +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**6.1 Graph view** + +1. Switch to **Graph** view (bar chart icon) +2. Verify a bar chart appears grouped by Pathway and State +3. Toggle between bar, line, and pie chart options + +**6.2 Pivot view** + +1. Switch to **Pivot** view (grid icon) +2. Verify a pivot table appears with: + + - Rows: Pathway + - Columns: State + - Measure: Readiness Score + +3. Verify you can add/remove measures and change row/column groupings + +-------------- + +Test 7: Search and Filters +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +1. Navigate to **Graduation > Assessments > All Assessments** +2. Test each filter: + + - **My Assessments**: shows only assessments where you are the + assessor + - **Draft / Submitted / Approved / Rejected**: filters by state + - **Ready to Graduate**: shows only assessments with "Ready to + Graduate" recommendation + - **Assessment Date**: date range filter + +3. Test group-by options: + + - Group by **Pathway**: assessments grouped under pathway names + - Group by **Assessor**: assessments grouped under assessor names + - Group by **State**: assessments grouped by state + - Group by **Recommendation**: assessments grouped by recommendation + - Group by **Assessment Date**: assessments grouped by date + +-------------- + +Test 8: My Assessments +~~~~~~~~~~~~~~~~~~~~~~ + +1. Navigate to **Graduation > Assessments > My Assessments** +2. Verify the "My Assessments" filter is active by default +3. Verify only assessments where the current user is the assessor are + shown + +-------------- + +Test 9: Security / Access Control +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**9.1 User cannot access configuration** + +1. Log in as **QA User** +2. Verify the **Graduation > Configuration** menu is NOT visible + +**9.2 User cannot modify pathways** + +1. Navigate to a pathway via URL (e.g., + ``/odoo/spp-graduation-pathway/{id}``) +2. Verify the form is readonly — no edit capability + +**9.3 User cannot see other users' assessments** + +1. Log in as QA User +2. Navigate to **Graduation > Assessments > All Assessments** +3. Verify only assessments where QA User is the assessor appear +4. Log in as QA Manager +5. Verify all assessments from all users appear + +**9.4 User cannot delete assessments** + +1. Log in as QA User +2. Open an assessment, try to delete it (Action > Delete) +3. **Expected**: Access error — user does not have delete permission on + assessments + +-------------- + +Test 10: Edge Cases +~~~~~~~~~~~~~~~~~~~ + +**10.1 Assessment with no responses** + +1. Create a new assessment, do NOT add any criteria responses +2. Verify: Readiness Score = 0%, Required Criteria Met = OFF +3. Submit and approve it +4. Verify: No graduation date (no recommendation set) + +**10.2 Pathway with zero monitoring months** + +1. Create an assessment using "Administrative Exit" pathway (0 months + monitoring) +2. Set recommendation to "Ready to Graduate" +3. Submit and approve +4. Verify: Graduation Date = today, Monitoring End Date = empty (0 + months means no monitoring) + +**10.3 Multiple assessments for same beneficiary** + +1. Create two assessments for the same beneficiary with different + pathways +2. Verify both can exist independently and have separate scores/states + Bug Tracker =========== diff --git a/spp_graduation/__manifest__.py b/spp_graduation/__manifest__.py index 3b7aba8e..cb0935cc 100644 --- a/spp_graduation/__manifest__.py +++ b/spp_graduation/__manifest__.py @@ -11,10 +11,11 @@ "maintainers": ["jeremi", "gonzalesedwin1123", "emjay0921"], "depends": [ "base", + "spp_registry", "spp_security", "mail", ], - "external_dependencies": {"python": ["dateutil"]}, + "external_dependencies": {"python": ["python-dateutil"]}, "data": [ "security/privileges.xml", "security/graduation_security.xml", diff --git a/spp_graduation/data/graduation_data.xml b/spp_graduation/data/graduation_data.xml index d9950d99..b7df1573 100644 --- a/spp_graduation/data/graduation_data.xml +++ b/spp_graduation/data/graduation_data.xml @@ -6,8 +6,8 @@ STANDARD 10 - - + + 12 EARLY 20 - - + + 18 ADMIN_EXIT 30 - - + + 0 1: + raise ValidationError(_("Score must be between 0 and 1. Got %(score)s.", score=response.score)) diff --git a/spp_graduation/models/graduation_criteria.py b/spp_graduation/models/graduation_criteria.py index e036ee8a..9e4ded19 100644 --- a/spp_graduation/models/graduation_criteria.py +++ b/spp_graduation/models/graduation_criteria.py @@ -1,4 +1,5 @@ -from odoo import fields, models +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError class GraduationCriteria(models.Model): @@ -34,3 +35,11 @@ class GraduationCriteria(models.Model): ) active = fields.Boolean(default=True) + + @api.constrains("weight") + def _check_weight_positive(self): + for criteria in self: + if criteria.weight <= 0: + raise ValidationError( + _("Weight must be greater than zero for criteria '%(name)s'.", name=criteria.name) + ) diff --git a/spp_graduation/models/graduation_pathway.py b/spp_graduation/models/graduation_pathway.py index 0bf3631c..00a217ec 100644 --- a/spp_graduation/models/graduation_pathway.py +++ b/spp_graduation/models/graduation_pathway.py @@ -5,6 +5,7 @@ class GraduationPathway(models.Model): _name = "spp.graduation.pathway" _description = "Graduation Pathway" _order = "sequence, name" + _check_company_auto = True name = fields.Char(required=True) code = fields.Char() @@ -17,8 +18,8 @@ class GraduationPathway(models.Model): help="Positive exit (graduation) vs negative exit (removed)", ) - is_requires_assessment = fields.Boolean(default=True) - is_requires_approval = fields.Boolean(default=True) + is_assessment_required = fields.Boolean(default=True) + is_approval_required = fields.Boolean(default=True) criteria_ids = fields.One2many("spp.graduation.criteria", "pathway_id", string="Criteria") @@ -30,7 +31,6 @@ class GraduationPathway(models.Model): criteria_count = fields.Integer( compute="_compute_criteria_count", store=True, - default=0, ) company_id = fields.Many2one("res.company", default=lambda self: self.env.company) diff --git a/spp_graduation/readme/DESCRIPTION.md b/spp_graduation/readme/DESCRIPTION.md index 5ece70b5..da8b316f 100644 --- a/spp_graduation/readme/DESCRIPTION.md +++ b/spp_graduation/readme/DESCRIPTION.md @@ -1,55 +1,62 @@ -Manages beneficiary graduation from time-bound social protection programs. Defines graduation pathways with weighted criteria, conducts assessments against those criteria, calculates readiness scores, and tracks graduation outcomes with post-graduation monitoring periods. Supports both positive exits (graduation) and negative exits (program removal). +Manages beneficiary graduation and exit from time-bound social protection programs. Defines +graduation pathways with weighted criteria, conducts assessments against those criteria, calculates +readiness scores, and tracks graduation outcomes with post-graduation monitoring periods. Supports +both positive exits (graduation) and negative exits (program removal). ### Key Capabilities -- Define graduation pathways with configurable criteria, exit type, and monitoring duration -- Create weighted criteria with different assessment methods (self-report, verification, computed, observation) -- Conduct beneficiary assessments with criteria responses and evidence attachments -- Calculate readiness scores based on weighted criteria and enforce required criteria -- Submit assessments for manager approval through a draft/submitted/approved/rejected workflow -- Track graduation dates and compute post-graduation monitoring periods -- Filter assessments by assessor, state, pathway, and recommendation +- Define graduation pathways with configurable criteria, exit type (`is_positive_exit`), and monitoring duration +- Create weighted criteria with four assessment methods: self-report, verification, computed, observation +- Conduct beneficiary assessments with per-criterion scores, a manual met/not-met judgment, and notes +- Calculate weighted readiness scores (0–1) from `score` fields and enforce required criteria via `is_met` flags through `_compute_scores()`. The `score` (numeric, 0–1) and `is_met` (boolean) fields serve different purposes: `score` drives the weighted readiness score, while `is_met` is a qualitative assessor judgment used to check whether required criteria are satisfied. They are intentionally independent because some assessment methods (e.g., field observation) may not map cleanly to a numeric score. +- Approve assessments through a draft → submitted → approved/rejected workflow; approval auto-sets `graduation_date` when recommendation is "graduate" +- Compute `monitoring_end_date` from `graduation_date` + pathway's `post_graduation_monitoring_months` +- Ships with three pre-configured pathways: Standard Graduation (12 months monitoring), Early Graduation (18 months), and Administrative Exit (negative, 0 months) ### Key Models -| Model | Description | -| ---------------------------------- | -------------------------------------------------------- | -| `spp.graduation.pathway` | Defines a graduation pathway with criteria and exit type | -| `spp.graduation.criteria` | Individual criterion within a pathway with weight and method | -| `spp.graduation.assessment` | Assessment of a beneficiary against a pathway with scores | -| `spp.graduation.criteria.response` | Response to a specific criterion within an assessment | +| Model | Description | +| ---------------------------------- | ------------------------------------------------------------------------ | +| `spp.graduation.pathway` | Graduation pathway with exit type, approval/assessment flags, and criteria | +| `spp.graduation.criteria` | Weighted criterion within a pathway; has assessment method and required flag | +| `spp.graduation.assessment` | Assessment of a beneficiary against a pathway; tracks scores and approval state | +| `spp.graduation.criteria.response` | Per-criterion response with `score`, `is_met`, `value`, `notes`, and `evidence_attachment_ids` | ### Configuration After installing: -1. Navigate to **Graduation > Configuration > Pathways** -2. Create graduation pathways specifying exit type (positive/negative) and monitoring months -3. Add criteria to each pathway with weight, assessment method, and required flag -4. Users can then create assessments under **Graduation > Assessments > All Assessments** +1. Navigate to **Graduation > Configuration > Pathways** (managers only) +2. Three default pathways are pre-installed; create additional ones as needed +3. On each pathway, set `is_positive_exit`, `is_assessment_required`, `is_approval_required`, and `post_graduation_monitoring_months` +4. Open the **Criteria** tab on the pathway form to add criteria with weight, assessment method, and required flag (inline editable list) +5. Users create assessments under **Graduation > Assessments > All Assessments** ### UI Location -- **Menu**: Graduation (top-level menu) -- **Assessments**: Graduation > Assessments > All Assessments / My Assessments -- **Configuration**: Graduation > Configuration > Pathways (managers only) -- **Views**: List, kanban (grouped by state), and form views with approval workflow -- **Pathway Form**: Criteria tab shows inline editable criteria list -- **Assessment Form**: Criteria Responses and Recommendation tabs +- **Top-level menu**: Graduation (visible to `group_spp_graduation_user` and above) +- **Graduation > Assessments > All Assessments**: List, kanban (grouped by state), form, graph, and pivot views +- **Graduation > Assessments > My Assessments**: Same views, pre-filtered to current user's assessments +- **Graduation > Configuration > Pathways**: List and form views (managers only) +- **Pathway form**: Two-column layout with a **Criteria** tab containing an inline editable list +- **Assessment form**: **Overview** tab (beneficiary, pathway, scores, dates), **Criteria Responses** tab (inline editable list with `criteria_id`, `score`, `is_met`, `value`, `notes`, `evidence_attachment_ids`), **Recommendation** tab (selection + notes), and **History** tab (audit metadata). Statusbar shows draft/submitted/approved. Alert banners for submitted and rejected states. +- **Assessment form buttons**: Submit (draft), Approve/Reject (submitted, managers only), Reset to Draft (submitted or rejected, managers only) ### Security -| Group | Access | -| ------------------------------------------ | --------------------------------------------------------- | -| `spp_graduation.group_spp_graduation_user` | Read pathways/criteria; create/edit own assessments (no delete) | -| `spp_graduation.group_spp_graduation_manager` | Full CRUD on all graduation data and configuration | +| Group | Access | +| --------------------------------------------- | ----------------------------------------------------------------------- | +| `spp_graduation.group_spp_graduation_user` | Read pathways/criteria; read/write/create own assessments (no delete); full CRUD on own criteria responses | +| `spp_graduation.group_spp_graduation_manager` | Full CRUD on all graduation models | + +Record rules restrict users to assessments where `assessor_id = current user` and responses on those assessments. Managers have unrestricted access. Multi-company isolation rules apply to pathways and assessments. ### Extension Points -- Inherit `spp.graduation.assessment` and override `_compute_scores()` to customize readiness calculation +- Override `_compute_scores()` on `spp.graduation.assessment` to customize readiness calculation logic +- Override `_compute_monitoring_end()` to change how monitoring end dates are derived - Inherit `spp.graduation.pathway` to add domain-specific pathway fields -- Extend approval workflow by inheriting assessment actions (`action_submit`, `action_approve`) - +- Inherit assessment workflow actions (`action_submit`, `action_approve`, `action_reject`, `action_reset_draft`) ### Dependencies -`base`, `spp_security`, `mail` +`base`, `spp_registry`, `spp_security`, `mail` diff --git a/spp_graduation/readme/USAGE.md b/spp_graduation/readme/USAGE.md new file mode 100644 index 00000000..52877c95 --- /dev/null +++ b/spp_graduation/readme/USAGE.md @@ -0,0 +1,372 @@ +## UI Testing Guide + +Manual QA test plan for the Graduation Management module. Tests are organized by feature area and should be +executed in order since later tests depend on data created in earlier ones. + +### Prerequisites + +- Install `spp_graduation` module +- Two test users configured: + - **QA User**: assigned to Graduation > User privilege + - **QA Manager**: assigned to Graduation > Manager privilege +- At least one registrant (`res.partner` with `is_registrant = True`) to use as a beneficiary + +After installation, three default pathways exist: Standard Graduation, Early Graduation, and Administrative Exit. + +--- + +### Test 1: Pathway Configuration (as QA Manager) + +**Menu**: Graduation > Configuration > Pathways + +**1.1 Verify pre-installed pathways** + +1. Open **Graduation > Configuration > Pathways** +2. Verify three pathways exist in the list: + - Standard Graduation (code: STANDARD, positive exit, 12 months monitoring) + - Early Graduation (code: EARLY, positive exit, 18 months monitoring) + - Administrative Exit (code: ADMIN_EXIT, negative exit, 0 months monitoring) +3. Verify the **Criteria** column shows a count for each pathway (5 for Standard, 2 for Early, 0 for Admin Exit) +4. Verify optional columns can be toggled: Code, Is Assessment Required, Is Approval Required, Post Graduation + Monitoring Months + +**1.2 Search and filter pathways** + +1. Use the search bar to search by name (e.g., "Standard") +2. Apply the **Positive Exit** filter — only Standard and Early should appear +3. Apply the **Negative Exit** filter — only Administrative Exit should appear +4. Apply the **Archived** filter — should show no results (none are archived) + +**1.3 Create a new pathway** + +1. Click **New** +2. Fill in: + - Name: "Test Graduation Pathway" + - Code: "TEST_QA" + - Is Positive Exit: ON (toggle) + - Is Assessment Required: ON (toggle) + - Is Approval Required: ON (toggle) + - Post Graduation Monitoring Months: 6 + - Description: "QA test pathway" +3. Open the **Criteria** tab +4. Click **Add a line** and create two criteria: + - Name: "Income Criterion", Weight: 2.0, Assessment Method: Verification Required, Is Required: checked + - Name: "Education Criterion", Weight: 1.0, Assessment Method: Self Report, Is Required: unchecked +5. Save +6. Verify the **Criteria** count in the list view shows 2 + +**1.4 Verify weight constraint** + +1. Open the Test Graduation Pathway +2. In the Criteria tab, try to add a criterion with **Weight: 0** +3. **Expected**: Validation error "Weight must be greater than zero" +4. Try **Weight: -1** +5. **Expected**: Same validation error + +**1.5 Archive and unarchive** + +1. Open any pathway, click **Action > Archive** +2. Verify the red "Archived" ribbon appears +3. Go back to the list, apply the **Archived** filter, verify the pathway appears +4. Open it and click **Action > Unarchive** + +**1.6 Drag to reorder** + +1. In the list view, drag the handle (≡) on a pathway row to reorder +2. Verify the sequence updates + +--- + +### Test 2: Assessment Creation and Form Layout (as QA User) + +**Menu**: Graduation > Assessments > All Assessments + +**2.1 List view** + +1. Open **Graduation > Assessments > All Assessments** +2. Verify the list is empty (or shows existing assessments) +3. Verify columns: Name, Beneficiary, Pathway, Assessment Date, Assessor (with avatar), Readiness Score, Required + Criteria Met, Recommendation, State (badge) +4. Verify optional columns can be toggled via the column selector icon + +**2.2 Create an assessment** + +1. Click **New** +2. Verify the form opens with: + - State: **Draft** in the statusbar + - **Submit** button visible in the header + - No Approve/Reject/Reset to Draft buttons (user is not a manager) + - Title shows "New Assessment" +3. On the **Overview** tab, fill in: + - Beneficiary: select a registrant record + - Pathway: select "Test Graduation Pathway" (created in Test 1.3) + - Assessment Date: today (should be pre-filled) + - Assessor: current user (should be pre-filled) +4. Verify the title updates to "{Beneficiary Name} - Test Graduation Pathway" +5. Verify the right column shows: + - Readiness Score: 0% + - Required Criteria Met: OFF + - Graduation Date: empty + - Monitoring End Date: empty +6. Save the record + +**2.3 Add criteria responses** + +**Important — understanding `score` vs `is_met`**: These two fields serve different purposes and are set +independently by the assessor: + +- **Score** (0–1): A numeric rating that feeds into the weighted **Readiness Score** calculation. For example, + 0.8 means the beneficiary scored 80% on this criterion. +- **Is Met** (toggle): A qualitative yes/no judgment by the assessor indicating whether the criterion is + satisfied. This is used only to check the **Required Criteria Met** flag — if a criterion is marked as + "required" on the pathway, its `is_met` must be ON for the overall check to pass. + +These fields are intentionally independent because some assessment methods (e.g., field observation) may not map +cleanly to a numeric score. An assessor might give a low numeric score but still consider the criterion met based +on qualitative judgment, or vice versa. + +1. Open the **Criteria Responses** tab +2. Click **Add a line** +3. Add a response for "Income Criterion": + - Score: 0.8 + - Is Met: ON (toggle) — assessor judges this criterion is satisfied + - Value: "Above threshold" + - Notes: "Verified via documentation" +4. Add a response for "Education Criterion": + - Score: 0.6 + - Is Met: OFF — assessor judges this criterion is not yet satisfied + - Value: "Partial" +5. Save +6. Go back to the **Overview** tab and verify: + - Readiness Score is computed (should be around 73% based on weights 2.0 and 1.0) + - Required Criteria Met: ON (because Income Criterion is required and is_met = True) + +**2.3a Verify score and is_met independence** + +1. Change the Income Criterion response to Score: 0.3, Is Met: ON +2. Save and verify: + - Readiness Score drops (lower numeric score) + - Required Criteria Met stays ON (because is_met is still toggled on) +3. Change it to Score: 1.0, Is Met: OFF +4. Save and verify: + - Readiness Score increases (higher numeric score) + - Required Criteria Met changes to OFF (because is_met is toggled off on a required criterion) +5. Restore to Score: 0.8, Is Met: ON for subsequent tests + +**2.4 Evidence attachments** + +1. Go to the **Criteria Responses** tab +2. On the Income Criterion response row, click the attachment icon in the Evidence Attachments column +3. Upload a test file (e.g., a small PDF or image) +4. Verify the file appears attached +5. Click on the response row to open the popup form +6. Verify the popup shows: Criteria (readonly), Score, Is Met, Value, Notes, and an Evidence section with the + uploaded file + +**2.5 Score constraint** + +1. In the Criteria Responses tab, try to change a score to **1.5** +2. **Expected**: Validation error "Score must be between 0 and 1" +3. Try **-0.1** +4. **Expected**: Same validation error +5. Try **0** and **1** — both should be accepted + +**2.6 Recommendation tab** + +1. Open the **Recommendation** tab +2. Select a recommendation: "Ready to Graduate" +3. Enter recommendation notes: "Beneficiary meets all criteria" +4. Save + +--- + +### Test 3: Approval Workflow + +**3.1 Submit (as QA User)** + +1. Open the assessment created in Test 2 +2. Click the **Submit** button +3. Verify: + - State changes to **Submitted** + - Submit button disappears + - A yellow **Pending Review** alert banner appears: "This assessment is awaiting manager approval." + - Overview fields (Beneficiary, Pathway, Date, Assessor) are still editable + - No Approve/Reject buttons visible (user is not a manager) + +**3.2 Verify double-submit is blocked (as QA User)** + +1. The assessment is already in Submitted state +2. There should be no Submit button visible +3. (Programmatic check: calling `action_submit()` on a submitted record raises an error) + +**3.3 Approve (as QA Manager)** + +1. Log in as QA Manager +2. Open **Graduation > Assessments > All Assessments** +3. Verify the manager can see the submitted assessment (even though another user created it) +4. Open the assessment +5. Verify **Approve** and **Reject** buttons are visible, plus **Reset to Draft** +6. Click **Approve** +7. Verify: + - State changes to **Approved** + - All buttons disappear (no further actions on approved records) + - **Graduation Date** is set to today (because recommendation was "Ready to Graduate") + - **Monitoring End Date** is set to 6 months from today (pathway has 6 months monitoring) + - **Approved By** shows the manager's name + - **Approved Date** shows the current datetime + - Overview fields (Beneficiary, Pathway, etc.) become readonly + - Criteria Responses tab becomes readonly + - Recommendation tab becomes readonly + +**3.4 Verify reset from approved is blocked** + +1. On the approved assessment, verify there is no **Reset to Draft** button + +--- + +### Test 4: Rejection and Reset Flow + +**4.1 Create and submit another assessment (as QA User)** + +1. Create a new assessment with: + - Beneficiary: any registrant + - Pathway: "Standard Graduation" + - Recommendation: "Extend Participation" +2. Submit it + +**4.2 Reject (as QA Manager)** + +1. Open the submitted assessment +2. Click **Reject** +3. Verify: + - State changes to **Rejected** + - A red **Rejected** alert banner appears: "This assessment was rejected. Review and reset to draft to + resubmit." + - **Reset to Draft** button is visible + - Overview and Criteria Responses fields are readonly + +**4.3 Reset to draft (as QA Manager)** + +1. Click **Reset to Draft** +2. Verify: + - State returns to **Draft** + - Alert banners disappear + - **Submit** button reappears + - Fields become editable again +3. Make changes and re-submit + +**4.4 Approve non-graduate recommendation** + +1. Approve the re-submitted assessment (recommendation is "Extend Participation") +2. Verify: + - State is Approved + - **Graduation Date is empty** (not set because recommendation is not "Ready to Graduate") + - **Monitoring End Date is empty** + +--- + +### Test 5: Kanban View + +1. Navigate to **Graduation > Assessments > All Assessments** +2. Switch to **Kanban** view +3. Verify: + - Cards are grouped by state columns: Draft, Submitted, Approved, Rejected + - A colored progress bar appears at the top of each column + - Each card shows: Beneficiary name, state badge, pathway, date with calendar icon, readiness score as + percentage, and recommendation badge (if set) + - Recommendation badges have colors: green (Graduate), yellow (Extend), red (Exit), blue (Defer) + +--- + +### Test 6: Graph and Pivot Views + +**6.1 Graph view** + +1. Switch to **Graph** view (bar chart icon) +2. Verify a bar chart appears grouped by Pathway and State +3. Toggle between bar, line, and pie chart options + +**6.2 Pivot view** + +1. Switch to **Pivot** view (grid icon) +2. Verify a pivot table appears with: + - Rows: Pathway + - Columns: State + - Measure: Readiness Score +3. Verify you can add/remove measures and change row/column groupings + +--- + +### Test 7: Search and Filters + +1. Navigate to **Graduation > Assessments > All Assessments** +2. Test each filter: + - **My Assessments**: shows only assessments where you are the assessor + - **Draft / Submitted / Approved / Rejected**: filters by state + - **Ready to Graduate**: shows only assessments with "Ready to Graduate" recommendation + - **Assessment Date**: date range filter +3. Test group-by options: + - Group by **Pathway**: assessments grouped under pathway names + - Group by **Assessor**: assessments grouped under assessor names + - Group by **State**: assessments grouped by state + - Group by **Recommendation**: assessments grouped by recommendation + - Group by **Assessment Date**: assessments grouped by date + +--- + +### Test 8: My Assessments + +1. Navigate to **Graduation > Assessments > My Assessments** +2. Verify the "My Assessments" filter is active by default +3. Verify only assessments where the current user is the assessor are shown + +--- + +### Test 9: Security / Access Control + +**9.1 User cannot access configuration** + +1. Log in as **QA User** +2. Verify the **Graduation > Configuration** menu is NOT visible + +**9.2 User cannot modify pathways** + +1. Navigate to a pathway via URL (e.g., `/odoo/spp-graduation-pathway/{id}`) +2. Verify the form is readonly — no edit capability + +**9.3 User cannot see other users' assessments** + +1. Log in as QA User +2. Navigate to **Graduation > Assessments > All Assessments** +3. Verify only assessments where QA User is the assessor appear +4. Log in as QA Manager +5. Verify all assessments from all users appear + +**9.4 User cannot delete assessments** + +1. Log in as QA User +2. Open an assessment, try to delete it (Action > Delete) +3. **Expected**: Access error — user does not have delete permission on assessments + +--- + +### Test 10: Edge Cases + +**10.1 Assessment with no responses** + +1. Create a new assessment, do NOT add any criteria responses +2. Verify: Readiness Score = 0%, Required Criteria Met = OFF +3. Submit and approve it +4. Verify: No graduation date (no recommendation set) + +**10.2 Pathway with zero monitoring months** + +1. Create an assessment using "Administrative Exit" pathway (0 months monitoring) +2. Set recommendation to "Ready to Graduate" +3. Submit and approve +4. Verify: Graduation Date = today, Monitoring End Date = empty (0 months means no monitoring) + +**10.3 Multiple assessments for same beneficiary** + +1. Create two assessments for the same beneficiary with different pathways +2. Verify both can exist independently and have separate scores/states diff --git a/spp_graduation/security/graduation_rules.xml b/spp_graduation/security/graduation_rules.xml index 86500121..73d7c262 100644 --- a/spp_graduation/security/graduation_rules.xml +++ b/spp_graduation/security/graduation_rules.xml @@ -19,58 +19,6 @@ True - - - Graduation Pathway: User - All - - [(1, '=', 1)] - - - - - - - - - Graduation Pathway: Manager - All - - [(1, '=', 1)] - - - - - - - - - - Graduation Criteria: User - All - - [(1, '=', 1)] - - - - - - - - - Graduation Criteria: Manager - All - - [(1, '=', 1)] - - - - - - - Graduation Assessment: User - Own diff --git a/spp_graduation/static/description/index.html b/spp_graduation/static/description/index.html index 6e33b82e..e8c20e6a 100644 --- a/spp_graduation/static/description/index.html +++ b/spp_graduation/static/description/index.html @@ -370,27 +370,37 @@

OpenSPP Graduation Management

!! source digest: sha256:aad197a1403a39fb971b395c0091745c0d10f8c6978896d4671688fcdd3a85cd !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Beta License: LGPL-3 OpenSPP/OpenSPP2

-

Manages beneficiary graduation from time-bound social protection -programs. Defines graduation pathways with weighted criteria, conducts -assessments against those criteria, calculates readiness scores, and -tracks graduation outcomes with post-graduation monitoring periods. -Supports both positive exits (graduation) and negative exits (program -removal).

+

Manages beneficiary graduation and exit from time-bound social +protection programs. Defines graduation pathways with weighted criteria, +conducts assessments against those criteria, calculates readiness +scores, and tracks graduation outcomes with post-graduation monitoring +periods. Supports both positive exits (graduation) and negative exits +(program removal).

Key Capabilities

    -
  • Define graduation pathways with configurable criteria, exit type, and -monitoring duration
  • -
  • Create weighted criteria with different assessment methods -(self-report, verification, computed, observation)
  • -
  • Conduct beneficiary assessments with criteria responses and evidence -attachments
  • -
  • Calculate readiness scores based on weighted criteria and enforce -required criteria
  • -
  • Submit assessments for manager approval through a -draft/submitted/approved/rejected workflow
  • -
  • Track graduation dates and compute post-graduation monitoring periods
  • -
  • Filter assessments by assessor, state, pathway, and recommendation
  • +
  • Define graduation pathways with configurable criteria, exit type +(is_positive_exit), and monitoring duration
  • +
  • Create weighted criteria with four assessment methods: self-report, +verification, computed, observation
  • +
  • Conduct beneficiary assessments with per-criterion scores, a manual +met/not-met judgment, and notes
  • +
  • Calculate weighted readiness scores (0–1) from score fields and +enforce required criteria via is_met flags through +_compute_scores(). The score (numeric, 0–1) and is_met +(boolean) fields serve different purposes: score drives the +weighted readiness score, while is_met is a qualitative assessor +judgment used to check whether required criteria are satisfied. They +are intentionally independent because some assessment methods (e.g., +field observation) may not map cleanly to a numeric score.
  • +
  • Approve assessments through a draft → submitted → approved/rejected +workflow; approval auto-sets graduation_date when recommendation +is “graduate”
  • +
  • Compute monitoring_end_date from graduation_date + pathway’s +post_graduation_monitoring_months
  • +
  • Ships with three pre-configured pathways: Standard Graduation (12 +months monitoring), Early Graduation (18 months), and Administrative +Exit (negative, 0 months)
@@ -407,20 +417,25 @@

Key Models

spp.graduation.pathway -Defines a graduation pathway -with criteria and exit type +Graduation pathway with exit +type, approval/assessment flags, +and criteria spp.graduation.criteria -Individual criterion within a -pathway with weight and method +Weighted criterion within a +pathway; has assessment method +and required flag spp.graduation.assessment Assessment of a beneficiary -against a pathway with scores +against a pathway; tracks scores +and approval state spp.graduation.criteria.response -Response to a specific criterion -within an assessment +Per-criterion response with +score, is_met, +value, notes, and +evidence_attachment_ids @@ -429,27 +444,41 @@

Key Models

Configuration

After installing:

    -
  1. Navigate to Graduation > Configuration > Pathways
  2. -
  3. Create graduation pathways specifying exit type (positive/negative) -and monitoring months
  4. -
  5. Add criteria to each pathway with weight, assessment method, and -required flag
  6. -
  7. Users can then create assessments under Graduation > Assessments > -All Assessments
  8. +
  9. Navigate to Graduation > Configuration > Pathways (managers only)
  10. +
  11. Three default pathways are pre-installed; create additional ones as +needed
  12. +
  13. On each pathway, set is_positive_exit, +is_assessment_required, is_approval_required, and +post_graduation_monitoring_months
  14. +
  15. Open the Criteria tab on the pathway form to add criteria with +weight, assessment method, and required flag (inline editable list)
  16. +
  17. Users create assessments under Graduation > Assessments > All +Assessments

UI Location

    -
  • Menu: Graduation (top-level menu)
  • -
  • Assessments: Graduation > Assessments > All Assessments / My -Assessments
  • -
  • Configuration: Graduation > Configuration > Pathways (managers -only)
  • -
  • Views: List, kanban (grouped by state), and form views with -approval workflow
  • -
  • Pathway Form: Criteria tab shows inline editable criteria list
  • -
  • Assessment Form: Criteria Responses and Recommendation tabs
  • +
  • Top-level menu: Graduation (visible to +group_spp_graduation_user and above)
  • +
  • Graduation > Assessments > All Assessments: List, kanban (grouped +by state), form, graph, and pivot views
  • +
  • Graduation > Assessments > My Assessments: Same views, +pre-filtered to current user’s assessments
  • +
  • Graduation > Configuration > Pathways: List and form views +(managers only)
  • +
  • Pathway form: Two-column layout with a Criteria tab containing +an inline editable list
  • +
  • Assessment form: Overview tab (beneficiary, pathway, scores, +dates), Criteria Responses tab (inline editable list with +criteria_id, score, is_met, value, notes, +evidence_attachment_ids), Recommendation tab (selection + +notes), and History tab (audit metadata). Statusbar shows +draft/submitted/approved. Alert banners for submitted and rejected +states.
  • +
  • Assessment form buttons: Submit (draft), Approve/Reject +(submitted, managers only), Reset to Draft (submitted or rejected, +managers only)
@@ -467,43 +496,512 @@

Security

spp_graduation.group_spp_graduation_user Read pathways/criteria; -create/edit own assessments (no -delete) +read/write/create own +assessments (no delete); full +CRUD on own criteria responses spp_graduation.group_spp_graduation_manager -Full CRUD on all graduation data -and configuration +Full CRUD on all graduation +models +

Record rules restrict users to assessments where +assessor_id = current user and responses on those assessments. +Managers have unrestricted access. Multi-company isolation rules apply +to pathways and assessments.

Extension Points

    -
  • Inherit spp.graduation.assessment and override -_compute_scores() to customize readiness calculation
  • +
  • Override _compute_scores() on spp.graduation.assessment to +customize readiness calculation logic
  • +
  • Override _compute_monitoring_end() to change how monitoring end +dates are derived
  • Inherit spp.graduation.pathway to add domain-specific pathway fields
  • -
  • Extend approval workflow by inheriting assessment actions -(action_submit, action_approve)
  • +
  • Inherit assessment workflow actions (action_submit, +action_approve, action_reject, action_reset_draft)

Dependencies

-

base, spp_security, mail

+

base, spp_registry, spp_security, mail

Table of contents

+
+

Usage

+
+

UI Testing Guide

+

Manual QA test plan for the Graduation Management module. Tests are +organized by feature area and should be executed in order since later +tests depend on data created in earlier ones.

+
+
+
+
+

Prerequisites

+
    +
  • Install spp_graduation module
  • +
  • Two test users configured:
      +
    • QA User: assigned to Graduation > User privilege
    • +
    • QA Manager: assigned to Graduation > Manager privilege
    • +
    +
  • +
  • At least one registrant (res.partner with +is_registrant = True) to use as a beneficiary
  • +
+

After installation, three default pathways exist: Standard Graduation, +Early Graduation, and Administrative Exit.

+
+
+
+

Test 1: Pathway Configuration (as QA Manager)

+

Menu: Graduation > Configuration > Pathways

+

1.1 Verify pre-installed pathways

+
    +
  1. Open Graduation > Configuration > Pathways
  2. +
  3. Verify three pathways exist in the list:
      +
    • Standard Graduation (code: STANDARD, positive exit, 12 months +monitoring)
    • +
    • Early Graduation (code: EARLY, positive exit, 18 months monitoring)
    • +
    • Administrative Exit (code: ADMIN_EXIT, negative exit, 0 months +monitoring)
    • +
    +
  4. +
  5. Verify the Criteria column shows a count for each pathway (5 for +Standard, 2 for Early, 0 for Admin Exit)
  6. +
  7. Verify optional columns can be toggled: Code, Is Assessment Required, +Is Approval Required, Post Graduation Monitoring Months
  8. +
+

1.2 Search and filter pathways

+
    +
  1. Use the search bar to search by name (e.g., “Standard”)
  2. +
  3. Apply the Positive Exit filter — only Standard and Early should +appear
  4. +
  5. Apply the Negative Exit filter — only Administrative Exit should +appear
  6. +
  7. Apply the Archived filter — should show no results (none are +archived)
  8. +
+

1.3 Create a new pathway

+
    +
  1. Click New
  2. +
  3. Fill in:
      +
    • Name: “Test Graduation Pathway”
    • +
    • Code: “TEST_QA”
    • +
    • Is Positive Exit: ON (toggle)
    • +
    • Is Assessment Required: ON (toggle)
    • +
    • Is Approval Required: ON (toggle)
    • +
    • Post Graduation Monitoring Months: 6
    • +
    • Description: “QA test pathway”
    • +
    +
  4. +
  5. Open the Criteria tab
  6. +
  7. Click Add a line and create two criteria:
      +
    • Name: “Income Criterion”, Weight: 2.0, Assessment Method: +Verification Required, Is Required: checked
    • +
    • Name: “Education Criterion”, Weight: 1.0, Assessment Method: Self +Report, Is Required: unchecked
    • +
    +
  8. +
  9. Save
  10. +
  11. Verify the Criteria count in the list view shows 2
  12. +
+

1.4 Verify weight constraint

+
    +
  1. Open the Test Graduation Pathway
  2. +
  3. In the Criteria tab, try to add a criterion with Weight: 0
  4. +
  5. Expected: Validation error “Weight must be greater than zero”
  6. +
  7. Try Weight: -1
  8. +
  9. Expected: Same validation error
  10. +
+

1.5 Archive and unarchive

+
    +
  1. Open any pathway, click Action > Archive
  2. +
  3. Verify the red “Archived” ribbon appears
  4. +
  5. Go back to the list, apply the Archived filter, verify the +pathway appears
  6. +
  7. Open it and click Action > Unarchive
  8. +
+

1.6 Drag to reorder

+
    +
  1. In the list view, drag the handle (≡) on a pathway row to reorder
  2. +
  3. Verify the sequence updates
  4. +
+
+
+
+

Test 2: Assessment Creation and Form Layout (as QA User)

+

Menu: Graduation > Assessments > All Assessments

+

2.1 List view

+
    +
  1. Open Graduation > Assessments > All Assessments
  2. +
  3. Verify the list is empty (or shows existing assessments)
  4. +
  5. Verify columns: Name, Beneficiary, Pathway, Assessment Date, Assessor +(with avatar), Readiness Score, Required Criteria Met, +Recommendation, State (badge)
  6. +
  7. Verify optional columns can be toggled via the column selector icon
  8. +
+

2.2 Create an assessment

+
    +
  1. Click New
  2. +
  3. Verify the form opens with:
      +
    • State: Draft in the statusbar
    • +
    • Submit button visible in the header
    • +
    • No Approve/Reject/Reset to Draft buttons (user is not a manager)
    • +
    • Title shows “New Assessment”
    • +
    +
  4. +
  5. On the Overview tab, fill in:
      +
    • Beneficiary: select a registrant record
    • +
    • Pathway: select “Test Graduation Pathway” (created in Test 1.3)
    • +
    • Assessment Date: today (should be pre-filled)
    • +
    • Assessor: current user (should be pre-filled)
    • +
    +
  6. +
  7. Verify the title updates to “{Beneficiary Name} - Test Graduation +Pathway”
  8. +
  9. Verify the right column shows:
      +
    • Readiness Score: 0%
    • +
    • Required Criteria Met: OFF
    • +
    • Graduation Date: empty
    • +
    • Monitoring End Date: empty
    • +
    +
  10. +
  11. Save the record
  12. +
+

2.3 Add criteria responses

+

Important — understanding ``score`` vs ``is_met``: These two fields +serve different purposes and are set independently by the assessor:

+
    +
  • Score (0–1): A numeric rating that feeds into the weighted +Readiness Score calculation. For example, 0.8 means the +beneficiary scored 80% on this criterion.
  • +
  • Is Met (toggle): A qualitative yes/no judgment by the assessor +indicating whether the criterion is satisfied. This is used only to +check the Required Criteria Met flag — if a criterion is marked as +“required” on the pathway, its is_met must be ON for the overall +check to pass.
  • +
+

These fields are intentionally independent because some assessment +methods (e.g., field observation) may not map cleanly to a numeric +score. An assessor might give a low numeric score but still consider the +criterion met based on qualitative judgment, or vice versa.

+
    +
  1. Open the Criteria Responses tab
  2. +
  3. Click Add a line
  4. +
  5. Add a response for “Income Criterion”:
      +
    • Score: 0.8
    • +
    • Is Met: ON (toggle) — assessor judges this criterion is satisfied
    • +
    • Value: “Above threshold”
    • +
    • Notes: “Verified via documentation”
    • +
    +
  6. +
  7. Add a response for “Education Criterion”:
      +
    • Score: 0.6
    • +
    • Is Met: OFF — assessor judges this criterion is not yet satisfied
    • +
    • Value: “Partial”
    • +
    +
  8. +
  9. Save
  10. +
  11. Go back to the Overview tab and verify:
      +
    • Readiness Score is computed (should be around 73% based on weights +2.0 and 1.0)
    • +
    • Required Criteria Met: ON (because Income Criterion is required and +is_met = True)
    • +
    +
  12. +
+

2.3a Verify score and is_met independence

+
    +
  1. Change the Income Criterion response to Score: 0.3, Is Met: ON
  2. +
  3. Save and verify:
      +
    • Readiness Score drops (lower numeric score)
    • +
    • Required Criteria Met stays ON (because is_met is still toggled on)
    • +
    +
  4. +
  5. Change it to Score: 1.0, Is Met: OFF
  6. +
  7. Save and verify:
      +
    • Readiness Score increases (higher numeric score)
    • +
    • Required Criteria Met changes to OFF (because is_met is toggled off +on a required criterion)
    • +
    +
  8. +
  9. Restore to Score: 0.8, Is Met: ON for subsequent tests
  10. +
+

2.4 Evidence attachments

+
    +
  1. Go to the Criteria Responses tab
  2. +
  3. On the Income Criterion response row, click the attachment icon in +the Evidence Attachments column
  4. +
  5. Upload a test file (e.g., a small PDF or image)
  6. +
  7. Verify the file appears attached
  8. +
  9. Click on the response row to open the popup form
  10. +
  11. Verify the popup shows: Criteria (readonly), Score, Is Met, Value, +Notes, and an Evidence section with the uploaded file
  12. +
+

2.5 Score constraint

+
    +
  1. In the Criteria Responses tab, try to change a score to 1.5
  2. +
  3. Expected: Validation error “Score must be between 0 and 1”
  4. +
  5. Try -0.1
  6. +
  7. Expected: Same validation error
  8. +
  9. Try 0 and 1 — both should be accepted
  10. +
+

2.6 Recommendation tab

+
    +
  1. Open the Recommendation tab
  2. +
  3. Select a recommendation: “Ready to Graduate”
  4. +
  5. Enter recommendation notes: “Beneficiary meets all criteria”
  6. +
  7. Save
  8. +
+
+
+
+

Test 3: Approval Workflow

+

3.1 Submit (as QA User)

+
    +
  1. Open the assessment created in Test 2
  2. +
  3. Click the Submit button
  4. +
  5. Verify:
      +
    • State changes to Submitted
    • +
    • Submit button disappears
    • +
    • A yellow Pending Review alert banner appears: “This assessment +is awaiting manager approval.”
    • +
    • Overview fields (Beneficiary, Pathway, Date, Assessor) are still +editable
    • +
    • No Approve/Reject buttons visible (user is not a manager)
    • +
    +
  6. +
+

3.2 Verify double-submit is blocked (as QA User)

+
    +
  1. The assessment is already in Submitted state
  2. +
  3. There should be no Submit button visible
  4. +
  5. (Programmatic check: calling action_submit() on a submitted +record raises an error)
  6. +
+

3.3 Approve (as QA Manager)

+
    +
  1. Log in as QA Manager
  2. +
  3. Open Graduation > Assessments > All Assessments
  4. +
  5. Verify the manager can see the submitted assessment (even though +another user created it)
  6. +
  7. Open the assessment
  8. +
  9. Verify Approve and Reject buttons are visible, plus Reset +to Draft
  10. +
  11. Click Approve
  12. +
  13. Verify:
      +
    • State changes to Approved
    • +
    • All buttons disappear (no further actions on approved records)
    • +
    • Graduation Date is set to today (because recommendation was +“Ready to Graduate”)
    • +
    • Monitoring End Date is set to 6 months from today (pathway has +6 months monitoring)
    • +
    • Approved By shows the manager’s name
    • +
    • Approved Date shows the current datetime
    • +
    • Overview fields (Beneficiary, Pathway, etc.) become readonly
    • +
    • Criteria Responses tab becomes readonly
    • +
    • Recommendation tab becomes readonly
    • +
    +
  14. +
+

3.4 Verify reset from approved is blocked

+
    +
  1. On the approved assessment, verify there is no Reset to Draft +button
  2. +
+
+
+
+

Test 4: Rejection and Reset Flow

+

4.1 Create and submit another assessment (as QA User)

+
    +
  1. Create a new assessment with:
      +
    • Beneficiary: any registrant
    • +
    • Pathway: “Standard Graduation”
    • +
    • Recommendation: “Extend Participation”
    • +
    +
  2. +
  3. Submit it
  4. +
+

4.2 Reject (as QA Manager)

+
    +
  1. Open the submitted assessment
  2. +
  3. Click Reject
  4. +
  5. Verify:
      +
    • State changes to Rejected
    • +
    • A red Rejected alert banner appears: “This assessment was +rejected. Review and reset to draft to resubmit.”
    • +
    • Reset to Draft button is visible
    • +
    • Overview and Criteria Responses fields are readonly
    • +
    +
  6. +
+

4.3 Reset to draft (as QA Manager)

+
    +
  1. Click Reset to Draft
  2. +
  3. Verify:
      +
    • State returns to Draft
    • +
    • Alert banners disappear
    • +
    • Submit button reappears
    • +
    • Fields become editable again
    • +
    +
  4. +
  5. Make changes and re-submit
  6. +
+

4.4 Approve non-graduate recommendation

+
    +
  1. Approve the re-submitted assessment (recommendation is “Extend +Participation”)
  2. +
  3. Verify:
      +
    • State is Approved
    • +
    • Graduation Date is empty (not set because recommendation is not +“Ready to Graduate”)
    • +
    • Monitoring End Date is empty
    • +
    +
  4. +
+
+
+
+

Test 5: Kanban View

+
    +
  1. Navigate to Graduation > Assessments > All Assessments
  2. +
  3. Switch to Kanban view
  4. +
  5. Verify:
      +
    • Cards are grouped by state columns: Draft, Submitted, Approved, +Rejected
    • +
    • A colored progress bar appears at the top of each column
    • +
    • Each card shows: Beneficiary name, state badge, pathway, date with +calendar icon, readiness score as percentage, and recommendation +badge (if set)
    • +
    • Recommendation badges have colors: green (Graduate), yellow +(Extend), red (Exit), blue (Defer)
    • +
    +
  6. +
+
+
+
+

Test 6: Graph and Pivot Views

+

6.1 Graph view

+
    +
  1. Switch to Graph view (bar chart icon)
  2. +
  3. Verify a bar chart appears grouped by Pathway and State
  4. +
  5. Toggle between bar, line, and pie chart options
  6. +
+

6.2 Pivot view

+
    +
  1. Switch to Pivot view (grid icon)
  2. +
  3. Verify a pivot table appears with:
      +
    • Rows: Pathway
    • +
    • Columns: State
    • +
    • Measure: Readiness Score
    • +
    +
  4. +
  5. Verify you can add/remove measures and change row/column groupings
  6. +
+
+
+
+

Test 7: Search and Filters

+
    +
  1. Navigate to Graduation > Assessments > All Assessments
  2. +
  3. Test each filter:
      +
    • My Assessments: shows only assessments where you are the +assessor
    • +
    • Draft / Submitted / Approved / Rejected: filters by state
    • +
    • Ready to Graduate: shows only assessments with “Ready to +Graduate” recommendation
    • +
    • Assessment Date: date range filter
    • +
    +
  4. +
  5. Test group-by options:
      +
    • Group by Pathway: assessments grouped under pathway names
    • +
    • Group by Assessor: assessments grouped under assessor names
    • +
    • Group by State: assessments grouped by state
    • +
    • Group by Recommendation: assessments grouped by recommendation
    • +
    • Group by Assessment Date: assessments grouped by date
    • +
    +
  6. +
+
+
+
+

Test 8: My Assessments

+
    +
  1. Navigate to Graduation > Assessments > My Assessments
  2. +
  3. Verify the “My Assessments” filter is active by default
  4. +
  5. Verify only assessments where the current user is the assessor are +shown
  6. +
+
+
+
+

Test 9: Security / Access Control

+

9.1 User cannot access configuration

+
    +
  1. Log in as QA User
  2. +
  3. Verify the Graduation > Configuration menu is NOT visible
  4. +
+

9.2 User cannot modify pathways

+
    +
  1. Navigate to a pathway via URL (e.g., +/odoo/spp-graduation-pathway/{id})
  2. +
  3. Verify the form is readonly — no edit capability
  4. +
+

9.3 User cannot see other users’ assessments

+
    +
  1. Log in as QA User
  2. +
  3. Navigate to Graduation > Assessments > All Assessments
  4. +
  5. Verify only assessments where QA User is the assessor appear
  6. +
  7. Log in as QA Manager
  8. +
  9. Verify all assessments from all users appear
  10. +
+

9.4 User cannot delete assessments

+
    +
  1. Log in as QA User
  2. +
  3. Open an assessment, try to delete it (Action > Delete)
  4. +
  5. Expected: Access error — user does not have delete permission on +assessments
  6. +
+
+
+
+

Test 10: Edge Cases

+

10.1 Assessment with no responses

+
    +
  1. Create a new assessment, do NOT add any criteria responses
  2. +
  3. Verify: Readiness Score = 0%, Required Criteria Met = OFF
  4. +
  5. Submit and approve it
  6. +
  7. Verify: No graduation date (no recommendation set)
  8. +
+

10.2 Pathway with zero monitoring months

+
    +
  1. Create an assessment using “Administrative Exit” pathway (0 months +monitoring)
  2. +
  3. Set recommendation to “Ready to Graduate”
  4. +
  5. Submit and approve
  6. +
  7. Verify: Graduation Date = today, Monitoring End Date = empty (0 +months means no monitoring)
  8. +
+

10.3 Multiple assessments for same beneficiary

+
    +
  1. Create two assessments for the same beneficiary with different +pathways
  2. +
  3. Verify both can exist independently and have separate scores/states
  4. +
-

Bug Tracker

+

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 @@ -511,15 +1009,15 @@

Bug Tracker

Do not contact contributors directly about support or help with technical issues.

-

Credits

+

Credits

-

Authors

+

Authors

  • OpenSPP.org
-

Maintainers

+

Maintainers

Current maintainers:

jeremi gonzalesedwin1123 emjay0921

This module is part of the OpenSPP/OpenSPP2 project on GitHub.

diff --git a/spp_graduation/tests/test_assessment.py b/spp_graduation/tests/test_assessment.py index a2973975..5fa71fbb 100644 --- a/spp_graduation/tests/test_assessment.py +++ b/spp_graduation/tests/test_assessment.py @@ -1,6 +1,11 @@ # Part of OpenSPP. See LICENSE file for full copyright and licensing details. +from datetime import date + +from dateutil.relativedelta import relativedelta + from odoo import fields +from odoo.exceptions import UserError, ValidationError from odoo.tests.common import TransactionCase @@ -19,6 +24,7 @@ def setUpClass(cls): cls.beneficiary = cls.Partner.create( { "name": "Test Beneficiary", + "is_registrant": True, } ) @@ -26,6 +32,7 @@ def setUpClass(cls): { "name": "Test Pathway", "code": "TEST", + "post_graduation_monitoring_months": 12, } ) @@ -68,8 +75,10 @@ def test_assessment_name_computation(self): self.assertIn(self.beneficiary.name, assessment.name) self.assertIn(self.pathway.name, assessment.name) + # --- State transition tests --- + def test_assessment_workflow(self): - """Test assessment state workflow.""" + """Test assessment state workflow: draft -> submitted -> approved.""" assessment = self.Assessment.create( { "partner_id": self.beneficiary.id, @@ -100,8 +109,8 @@ def test_assessment_rejection(self): assessment.action_reject() self.assertEqual(assessment.state, "rejected") - def test_assessment_reset(self): - """Test assessment reset to draft.""" + def test_assessment_reset_from_submitted(self): + """Test assessment reset to draft from submitted.""" assessment = self.Assessment.create( { "partner_id": self.beneficiary.id, @@ -113,6 +122,104 @@ def test_assessment_reset(self): assessment.action_reset_draft() self.assertEqual(assessment.state, "draft") + def test_assessment_reset_from_rejected(self): + """Test assessment reset to draft from rejected.""" + assessment = self.Assessment.create( + { + "partner_id": self.beneficiary.id, + "pathway_id": self.pathway.id, + } + ) + + assessment.action_submit() + assessment.action_reject() + assessment.action_reset_draft() + self.assertEqual(assessment.state, "draft") + + # --- State transition guard tests --- + + def test_submit_only_from_draft(self): + """Test submit raises UserError if not in draft state.""" + assessment = self.Assessment.create( + { + "partner_id": self.beneficiary.id, + "pathway_id": self.pathway.id, + } + ) + assessment.action_submit() + # Already submitted, cannot submit again + with self.assertRaises(UserError): + assessment.action_submit() + + def test_approve_only_from_submitted(self): + """Test approve raises UserError if not in submitted state.""" + assessment = self.Assessment.create( + { + "partner_id": self.beneficiary.id, + "pathway_id": self.pathway.id, + } + ) + # Cannot approve from draft + with self.assertRaises(UserError): + assessment.action_approve() + + def test_reject_only_from_submitted(self): + """Test reject raises UserError if not in submitted state.""" + assessment = self.Assessment.create( + { + "partner_id": self.beneficiary.id, + "pathway_id": self.pathway.id, + } + ) + # Cannot reject from draft + with self.assertRaises(UserError): + assessment.action_reject() + + def test_reset_not_from_draft(self): + """Test reset raises UserError if in draft state.""" + assessment = self.Assessment.create( + { + "partner_id": self.beneficiary.id, + "pathway_id": self.pathway.id, + } + ) + with self.assertRaises(UserError): + assessment.action_reset_draft() + + def test_reset_not_from_approved(self): + """Test reset raises UserError if in approved state.""" + assessment = self.Assessment.create( + { + "partner_id": self.beneficiary.id, + "pathway_id": self.pathway.id, + } + ) + assessment.action_submit() + assessment.action_approve() + with self.assertRaises(UserError): + assessment.action_reset_draft() + + # --- Multi-record action tests --- + + def test_multi_record_submit(self): + """Test submit works on multiple records.""" + a1 = self.Assessment.create({"partner_id": self.beneficiary.id, "pathway_id": self.pathway.id}) + a2 = self.Assessment.create({"partner_id": self.beneficiary.id, "pathway_id": self.pathway.id}) + (a1 | a2).action_submit() + self.assertEqual(a1.state, "submitted") + self.assertEqual(a2.state, "submitted") + + def test_multi_record_approve(self): + """Test approve works on multiple records.""" + a1 = self.Assessment.create({"partner_id": self.beneficiary.id, "pathway_id": self.pathway.id}) + a2 = self.Assessment.create({"partner_id": self.beneficiary.id, "pathway_id": self.pathway.id}) + (a1 | a2).action_submit() + (a1 | a2).action_approve() + self.assertEqual(a1.state, "approved") + self.assertEqual(a2.state, "approved") + + # --- Score computation tests --- + def test_readiness_score_computation(self): """Test readiness score computation.""" assessment = self.Assessment.create( @@ -126,7 +233,7 @@ def test_readiness_score_computation(self): { "assessment_id": assessment.id, "criteria_id": self.criteria1.id, - "score": 0.8, # 80% + "score": 0.8, "is_met": True, } ) @@ -134,7 +241,7 @@ def test_readiness_score_computation(self): { "assessment_id": assessment.id, "criteria_id": self.criteria2.id, - "score": 0.6, # 60% + "score": 0.6, "is_met": False, } ) @@ -154,7 +261,6 @@ def test_required_criteria_check(self): } ) - # Only required criterion met self.Response.create( { "assessment_id": assessment.id, @@ -181,13 +287,88 @@ def test_required_criteria_not_met(self): "assessment_id": assessment.id, "criteria_id": self.criteria1.id, "score": 0.4, - "is_met": False, # Required but not met + "is_met": False, } ) assessment.invalidate_recordset(["is_required_criteria_met"]) self.assertFalse(assessment.is_required_criteria_met) + def test_no_responses_scores_zero(self): + """Test assessment with no responses has zero score and required not met.""" + assessment = self.Assessment.create( + { + "partner_id": self.beneficiary.id, + "pathway_id": self.pathway.id, + } + ) + self.assertEqual(assessment.readiness_score, 0) + self.assertFalse(assessment.is_required_criteria_met) + + # --- Score constraint tests --- + + def test_score_constraint_above_one(self): + """Test score above 1 raises ValidationError.""" + assessment = self.Assessment.create( + { + "partner_id": self.beneficiary.id, + "pathway_id": self.pathway.id, + } + ) + with self.assertRaises(ValidationError): + self.Response.create( + { + "assessment_id": assessment.id, + "criteria_id": self.criteria1.id, + "score": 1.5, + } + ) + + def test_score_constraint_negative(self): + """Test negative score raises ValidationError.""" + assessment = self.Assessment.create( + { + "partner_id": self.beneficiary.id, + "pathway_id": self.pathway.id, + } + ) + with self.assertRaises(ValidationError): + self.Response.create( + { + "assessment_id": assessment.id, + "criteria_id": self.criteria1.id, + "score": -0.1, + } + ) + + def test_score_boundary_values(self): + """Test score at boundaries (0 and 1) is accepted.""" + assessment = self.Assessment.create( + { + "partner_id": self.beneficiary.id, + "pathway_id": self.pathway.id, + } + ) + r1 = self.Response.create( + { + "assessment_id": assessment.id, + "criteria_id": self.criteria1.id, + "score": 0.0, + } + ) + self.assertEqual(r1.score, 0.0) + + r2 = self.Response.create( + { + "assessment_id": assessment.id, + "criteria_id": self.criteria2.id, + "score": 1.0, + } + ) + self.assertEqual(r2.score, 1.0) + + # --- Graduation and monitoring tests --- + def test_graduation_date_on_approval(self): """Test graduation date is set on approval with graduate recommendation.""" assessment = self.Assessment.create( @@ -202,3 +383,96 @@ def test_graduation_date_on_approval(self): assessment.action_approve() self.assertEqual(assessment.graduation_date, fields.Date.today()) + + def test_no_graduation_date_without_graduate_recommendation(self): + """Test graduation date is NOT set when recommendation is not graduate.""" + assessment = self.Assessment.create( + { + "partner_id": self.beneficiary.id, + "pathway_id": self.pathway.id, + "recommendation": "extend", + } + ) + + assessment.action_submit() + assessment.action_approve() + + self.assertFalse(assessment.graduation_date) + + def test_monitoring_end_date_computation(self): + """Test monitoring end date is computed from graduation date + pathway months.""" + assessment = self.Assessment.create( + { + "partner_id": self.beneficiary.id, + "pathway_id": self.pathway.id, + "recommendation": "graduate", + } + ) + + assessment.action_submit() + assessment.action_approve() + + expected_end = date.today() + relativedelta(months=12) + self.assertEqual(assessment.monitoring_end_date, expected_end) + + def test_monitoring_end_date_no_graduation(self): + """Test monitoring end date is False when no graduation date.""" + assessment = self.Assessment.create( + { + "partner_id": self.beneficiary.id, + "pathway_id": self.pathway.id, + } + ) + self.assertFalse(assessment.monitoring_end_date) + + # --- Name computation edge case --- + + def test_assessment_name_without_pathway(self): + """Test assessment name defaults when pathway is missing.""" + assessment = self.Assessment.create( + { + "partner_id": self.beneficiary.id, + "pathway_id": self.pathway.id, + } + ) + # Remove pathway to trigger else branch + assessment.pathway_id = False + self.assertEqual(assessment.name, "New Assessment") + + # --- Response field tests --- + + def test_response_value_and_notes(self): + """Test response value and notes fields are stored correctly.""" + assessment = self.Assessment.create( + { + "partner_id": self.beneficiary.id, + "pathway_id": self.pathway.id, + } + ) + response = self.Response.create( + { + "assessment_id": assessment.id, + "criteria_id": self.criteria1.id, + "score": 0.7, + "is_met": True, + "value": "Above threshold", + "notes": "Verified via documentation", + } + ) + self.assertEqual(response.value, "Above threshold") + self.assertEqual(response.notes, "Verified via documentation") + + def test_recommendation_notes(self): + """Test recommendation notes field is stored correctly.""" + assessment = self.Assessment.create( + { + "partner_id": self.beneficiary.id, + "pathway_id": self.pathway.id, + "recommendation": "graduate", + "recommendation_notes": "Beneficiary meets all criteria for graduation.", + } + ) + self.assertEqual( + assessment.recommendation_notes, + "Beneficiary meets all criteria for graduation.", + ) diff --git a/spp_graduation/tests/test_graduation_security.py b/spp_graduation/tests/test_graduation_security.py index 87d213f1..97308b6d 100644 --- a/spp_graduation/tests/test_graduation_security.py +++ b/spp_graduation/tests/test_graduation_security.py @@ -36,6 +36,7 @@ def setUpClass(cls): cls.beneficiary = cls.env["res.partner"].create( { "name": "Test Beneficiary", + "is_registrant": True, } ) @@ -70,7 +71,6 @@ def test_user_sees_own_assessments(self): ) assessment = self.env["spp.graduation.assessment"].create( { - "name": "Test Assessment", "pathway_id": pathway.id, "partner_id": self.beneficiary.id, "assessor_id": self.user.id, @@ -88,7 +88,6 @@ def test_manager_sees_all_assessments(self): ) assessment1 = self.env["spp.graduation.assessment"].create( { - "name": "Assessment 1", "pathway_id": pathway.id, "partner_id": self.beneficiary.id, "assessor_id": self.user.id, @@ -96,7 +95,6 @@ def test_manager_sees_all_assessments(self): ) assessment2 = self.env["spp.graduation.assessment"].create( { - "name": "Assessment 2", "pathway_id": pathway.id, "partner_id": self.beneficiary.id, "assessor_id": self.manager.id, @@ -135,3 +133,60 @@ def test_manager_can_write_pathways(self): ) pathway.with_user(self.manager).write({"name": "Modified by Manager"}) self.assertEqual(pathway.name, "Modified by Manager") + + def test_user_workflow_as_user(self): + """Test user can submit own assessments.""" + pathway = self.env["spp.graduation.pathway"].create( + { + "name": "Test Pathway", + } + ) + assessment = ( + self.env["spp.graduation.assessment"] + .with_user(self.user) + .create( + { + "pathway_id": pathway.id, + "partner_id": self.beneficiary.id, + } + ) + ) + assessment.action_submit() + self.assertEqual(assessment.state, "submitted") + + def test_manager_can_approve(self): + """Test manager can approve submitted assessments.""" + pathway = self.env["spp.graduation.pathway"].create( + { + "name": "Test Pathway", + } + ) + assessment = self.env["spp.graduation.assessment"].create( + { + "pathway_id": pathway.id, + "partner_id": self.beneficiary.id, + "assessor_id": self.user.id, + } + ) + assessment.action_submit() + assessment.with_user(self.manager).action_approve() + self.assertEqual(assessment.state, "approved") + self.assertEqual(assessment.approved_by_id, self.manager) + + def test_manager_can_reject(self): + """Test manager can reject submitted assessments.""" + pathway = self.env["spp.graduation.pathway"].create( + { + "name": "Test Pathway", + } + ) + assessment = self.env["spp.graduation.assessment"].create( + { + "pathway_id": pathway.id, + "partner_id": self.beneficiary.id, + "assessor_id": self.user.id, + } + ) + assessment.action_submit() + assessment.with_user(self.manager).action_reject() + self.assertEqual(assessment.state, "rejected") diff --git a/spp_graduation/tests/test_pathway.py b/spp_graduation/tests/test_pathway.py index 93db72ff..e79d902a 100644 --- a/spp_graduation/tests/test_pathway.py +++ b/spp_graduation/tests/test_pathway.py @@ -1,5 +1,6 @@ # Part of OpenSPP. See LICENSE file for full copyright and licensing details. +from odoo.exceptions import ValidationError from odoo.tests.common import TransactionCase @@ -89,3 +90,107 @@ def test_criteria_weight_total(self): total = sum(c.weight for c in pathway.criteria_ids) self.assertEqual(total, 100) + + def test_criteria_weight_must_be_positive(self): + """Test criteria weight constraint rejects zero and negative values.""" + pathway = self.Pathway.create( + { + "name": "Test Pathway", + "code": "TST", + } + ) + + with self.assertRaises(ValidationError): + self.Criteria.create( + { + "pathway_id": pathway.id, + "name": "Zero Weight", + "weight": 0, + } + ) + + with self.assertRaises(ValidationError): + self.Criteria.create( + { + "pathway_id": pathway.id, + "name": "Negative Weight", + "weight": -5, + } + ) + + def test_pathway_boolean_field_defaults(self): + """Test renamed boolean fields have correct defaults.""" + pathway = self.Pathway.create( + { + "name": "Default Pathway", + } + ) + self.assertTrue(pathway.is_positive_exit) + self.assertTrue(pathway.is_assessment_required) + self.assertTrue(pathway.is_approval_required) + + def test_criteria_assessment_method_values(self): + """Test all assessment method selection values are accepted.""" + pathway = self.Pathway.create({"name": "Method Test Pathway"}) + methods = ["self_report", "verification", "computed", "observation"] + for method in methods: + criteria = self.Criteria.create( + { + "pathway_id": pathway.id, + "name": f"Criterion {method}", + "weight": 1.0, + "assessment_method": method, + } + ) + self.assertEqual(criteria.assessment_method, method) + + def test_criteria_assessment_method_default(self): + """Test assessment method defaults to verification.""" + pathway = self.Pathway.create({"name": "Default Method Pathway"}) + criteria = self.Criteria.create( + { + "pathway_id": pathway.id, + "name": "Default Method", + "weight": 1.0, + } + ) + self.assertEqual(criteria.assessment_method, "verification") + + def test_criteria_active_field(self): + """Test criteria active field defaults to True and can be archived.""" + pathway = self.Pathway.create({"name": "Active Test Pathway"}) + criteria = self.Criteria.create( + { + "pathway_id": pathway.id, + "name": "Active Criterion", + "weight": 1.0, + } + ) + self.assertTrue(criteria.active) + criteria.active = False + self.assertFalse(criteria.active) + + def test_pathway_description(self): + """Test pathway description field is stored correctly.""" + pathway = self.Pathway.create( + { + "name": "Described Pathway", + "description": "A pathway for testing description storage.", + } + ) + self.assertEqual(pathway.description, "A pathway for testing description storage.") + + def test_criteria_code_and_description(self): + """Test criteria code and description fields are stored correctly.""" + pathway = self.Pathway.create({"name": "Code Test Pathway"}) + criteria = self.Criteria.create( + { + "pathway_id": pathway.id, + "name": "Coded Criterion", + "code": "ECON_01", + "weight": 1.0, + "description": "Measures economic stability.", + } + ) + self.assertEqual(criteria.code, "ECON_01") + self.assertEqual(criteria.description, "Measures economic stability.") diff --git a/spp_graduation/views/graduation_assessment_views.xml b/spp_graduation/views/graduation_assessment_views.xml index e16ceacb..4f5afa6d 100644 --- a/spp_graduation/views/graduation_assessment_views.xml +++ b/spp_graduation/views/graduation_assessment_views.xml @@ -1,26 +1,65 @@ - + + + spp.graduation.criteria.response.form + spp.graduation.criteria.response + +
+ + + + + + + + + + + + + + +
+
+
+ + - spp.graduation.assessment.tree + spp.graduation.assessment.list spp.graduation.assessment - + - - - - + + + + @@ -31,12 +70,12 @@ spp.graduation.assessment.kanban spp.graduation.assessment - - - - - - + +
@@ -46,20 +85,33 @@
+
- - + +
- Score: - % + Score: +
-
- +
+
@@ -101,7 +153,7 @@ name="action_reset_draft" string="Reset to Draft" type="object" - invisible="state in ('draft', 'approved')" + invisible="state not in ('submitted', 'rejected')" groups="spp_graduation.group_spp_graduation_manager" />
+

- - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + +
+ + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + +
@@ -200,11 +341,13 @@ + + + + + + + spp.graduation.assessment.graph + spp.graduation.assessment + + + + + + + + + + + spp.graduation.assessment.pivot + spp.graduation.assessment + + + + + + + + + Graduation Assessments spp.graduation.assessment - list,kanban,form + list,kanban,form,graph,pivot

Create a new Graduation Assessment @@ -281,7 +455,7 @@ My Assessments spp.graduation.assessment - list,kanban,form + list,kanban,form,graph,pivot {'search_default_my_assessments': 1}

diff --git a/spp_graduation/views/graduation_pathway_views.xml b/spp_graduation/views/graduation_pathway_views.xml index e678361d..fd13bca0 100644 --- a/spp_graduation/views/graduation_pathway_views.xml +++ b/spp_graduation/views/graduation_pathway_views.xml @@ -1,18 +1,48 @@ - + + + spp.graduation.pathway.search + spp.graduation.pathway + + + + + + + + + + + + + + - spp.graduation.pathway.tree + spp.graduation.pathway.list spp.graduation.pathway - + - - - + + + + @@ -32,17 +62,27 @@ bg_color="text-bg-danger" invisible="active" /> - - - +

+

+ +

+
+ + - - - - + + + + - + - + +