diff --git a/spp_change_request_edit_farmer/README.rst b/spp_change_request_edit_farmer/README.rst new file mode 100644 index 000000000..04677b2c6 --- /dev/null +++ b/spp_change_request_edit_farmer/README.rst @@ -0,0 +1,170 @@ +=================================== +OpenSPP Change Request: Edit Farmer +=================================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:acaecaa0c5a4b0eeeacac2e19f416521d252c7c913eff91aeb22d41b9d7de0b8 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png + :target: https://odoo-community.org/page/development-status + :alt: Alpha +.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OpenSPP%2Fopenspp--modules-lightgray.png?logo=github + :target: https://github.com/OpenSPP/openspp-modules/tree/17.0/spp_change_request_edit_farmer + :alt: OpenSPP/openspp-modules + +|badge1| |badge2| |badge3| + +OpenSPP Change Request: Edit Farmer +=================================== + +Overview +-------- + +The `spp_change_request_edit_farmer `__ +module extends the OpenSPP Change Request system to specifically handle +requests for adding new farmers to existing groups within the registry. +It leverages the framework provided by the +`spp_change_request `__ module and integrates with +other registry modules to streamline the process of adding farmers while +maintaining data integrity and consistency. + +Purpose +------- + +- **Specialized Change Request Type**: Introduce a dedicated change + request type for adding farmers, distinct from other types of + registrant modifications. +- **Streamlined Data Collection**: Provide a tailored form for + capturing essential information about the new farmer, including + personal details, farmer-specific attributes, and desired group + membership details. +- **Group Membership Management**: Integrate with the + `g2p_registry_membership `__ module to + seamlessly add the new farmer to the specified group upon approval. +- **Enhanced Validation**: Implement specific validation rules relevant + to adding farmers, ensuring the accuracy and completeness of the + submitted information. + +Module Integration +------------------ + +Dependencies +------------ + +The module relies heavily on the following modules: + +- `spp_change_request `__: Inherits the core change + request functionality, including the request workflow, validation + processes, approval mechanisms, and integration with the Document + Management System (`spp_dms `__). +- `spp_farmer_registry_base `__: Leverages + the farmer-specific data models and attributes to capture and store + information about the new farmer. +- `g2p_registry_membership `__: Integrates + with the membership management system to create the appropriate group + membership record for the new farmer upon change request approval. +- `phone_validation `__: Utilizes the phone + validation module to ensure phone number entries for the new farmer + adhere to correct formatting. +- `g2p_registry_group `__: Accesses group + information and functionality to display details about the target + group for the new farmer. +- `g2p_registry_individual `__: Leverages + individual registrant management features, inheriting from the + individual registrant model. +- `spp_service_points `__: Integrates with service + points, allowing change requests to be initiated and managed through + designated service points. + +Additional Functionality +------------------------ + +- **Custom Change Request Model**: Introduces the + ``spp.change.request.edit.farmer`` model, inheriting from the base + ``spp.change.request`` model and adding fields specific to adding + farmers, such as farmer-specific details and group membership + information. +- **Tailored Forms**: Provides specialized views for creating, + displaying, and validating ``Edit Farmer`` change requests, including + a dedicated form (``view_change_request_edit_farmer_form``) with + relevant fields and a validation-focused form + (``view_change_request_edit_farmer_validation_form``). +- **Automated Individual and Membership Creation**: Upon validation and + approval of the change request, the module automatically creates: + + - A new individual registrant record (``res.partner``) for the + farmer, populating it with the submitted data. + - A corresponding group membership record + (``g2p.group.membership``), linking the newly created farmer to + the designated group. + +- **Enhanced User Interface**: Adds specific menu items and actions to + the OpenSPP interface, allowing users to efficiently manage + ``Edit Farmer`` requests. + +Conclusion +---------- + +The `spp_change_request_edit_farmer `__ +module provides a robust and specialized workflow for adding new farmers +to existing groups within the OpenSPP registry. By seamlessly +integrating with core change management and registry modules, it ensures +data accuracy, consistency, and a streamlined user experience for +managing farmer additions. + +.. IMPORTANT:: + This is an alpha version, the data model and design can change at any time without warning. + Only for development or testing purpose, do not use in production. + `More details on development status `_ + +**Table of contents** + +.. contents:: + :local: + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* OpenSPP.org + +Maintainers +----------- + +.. |maintainer-jeremi| image:: https://github.com/jeremi.png?size=40px + :target: https://github.com/jeremi + :alt: jeremi +.. |maintainer-gonzalesedwin1123| image:: https://github.com/gonzalesedwin1123.png?size=40px + :target: https://github.com/gonzalesedwin1123 + :alt: gonzalesedwin1123 +.. |maintainer-emjay0921| image:: https://github.com/emjay0921.png?size=40px + :target: https://github.com/emjay0921 + :alt: emjay0921 + +Current maintainers: + +|maintainer-jeremi| |maintainer-gonzalesedwin1123| |maintainer-emjay0921| + +This module is part of the `OpenSPP/openspp-modules `_ project on GitHub. + +You are welcome to contribute. diff --git a/spp_change_request_edit_farmer/__init__.py b/spp_change_request_edit_farmer/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/spp_change_request_edit_farmer/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/spp_change_request_edit_farmer/__manifest__.py b/spp_change_request_edit_farmer/__manifest__.py new file mode 100644 index 000000000..522e70266 --- /dev/null +++ b/spp_change_request_edit_farmer/__manifest__.py @@ -0,0 +1,41 @@ +{ + "name": "OpenSPP Change Request: Edit Farmer", + "summary": "Provides a specialized workflow for updating existing Farmer in the registry.", + "category": "OpenSPP", + "version": "17.0.1.3.0", + "sequence": 1, + "author": "OpenSPP.org", + "website": "https://github.com/OpenSPP/openspp-modules", + "license": "LGPL-3", + "development_status": "Beta", + "maintainers": ["jeremi", "gonzalesedwin1123", "emjay0921"], + "depends": [ + "spp_change_request", + "g2p_registry_individual", + "g2p_registry_group", + "g2p_registry_membership", + "spp_service_points", + "spp_idpass", + "spp_farmer_registry_base", + ], + "excludes": [ + "spp_change_request_create_group", + ], + "data": [ + "security/change_request_security.xml", + "security/ir.model.access.csv", + "data/dms.xml", + "data/change_request_stage.xml", + "data/change_request_sequence.xml", + "data/change_request_target.xml", + "views/change_request_edit_farmer_view.xml", + "views/change_request_view.xml", + "views/dms_file_view.xml", + ], + "assets": {}, + "demo": [], + "images": [], + "application": True, + "installable": True, + "auto_install": False, +} diff --git a/spp_change_request_edit_farmer/data/change_request_sequence.xml b/spp_change_request_edit_farmer/data/change_request_sequence.xml new file mode 100644 index 000000000..fa32af107 --- /dev/null +++ b/spp_change_request_edit_farmer/data/change_request_sequence.xml @@ -0,0 +1,23 @@ + + + + + + 10 + + spp.change.request.edit.farmer + + both + + + + 20 + + spp.change.request.edit.farmer + + both + + + diff --git a/spp_change_request_edit_farmer/data/change_request_stage.xml b/spp_change_request_edit_farmer/data/change_request_stage.xml new file mode 100644 index 000000000..70babe1da --- /dev/null +++ b/spp_change_request_edit_farmer/data/change_request_stage.xml @@ -0,0 +1,15 @@ + + + + + + Local + + + + HQ + + + diff --git a/spp_change_request_edit_farmer/data/change_request_target.xml b/spp_change_request_edit_farmer/data/change_request_target.xml new file mode 100644 index 000000000..8190724a8 --- /dev/null +++ b/spp_change_request_edit_farmer/data/change_request_target.xml @@ -0,0 +1,12 @@ + + + + + + spp.change.request.edit.farmer + group + + + diff --git a/spp_change_request_edit_farmer/data/dms.xml b/spp_change_request_edit_farmer/data/dms.xml new file mode 100644 index 000000000..2b61c4189 --- /dev/null +++ b/spp_change_request_edit_farmer/data/dms.xml @@ -0,0 +1,9 @@ + + + + + + Edit Farmer Request Form + + + diff --git a/spp_change_request_edit_farmer/models/__init__.py b/spp_change_request_edit_farmer/models/__init__.py new file mode 100644 index 000000000..f2a6bf1b0 --- /dev/null +++ b/spp_change_request_edit_farmer/models/__init__.py @@ -0,0 +1,4 @@ +from . import change_request +from . import change_request_edit_farmer +from . import change_request_edit_farmer_validation_sequence +from . import dms diff --git a/spp_change_request_edit_farmer/models/change_request.py b/spp_change_request_edit_farmer/models/change_request.py new file mode 100644 index 000000000..5f8a93c4f --- /dev/null +++ b/spp_change_request_edit_farmer/models/change_request.py @@ -0,0 +1,77 @@ +import logging + +from odoo import Command, api, models + +_logger = logging.getLogger(__name__) + + +class ChangeRequestBaseCustomDemo(models.Model): + _inherit = "spp.change.request" + + def create_request_detail_demo(self): + """ + A version of spp.change.request create_request_detail function + that does not check the applicant's phone number and + does not load the CR details form + :return: + """ + for rec in self: + if rec.state in ("draft", "pending"): + # Set the request_type_ref_id + res_model = rec.request_type + # Set the dms directory + _logger.debug("Change Request: DMS Directory Creation (%s)" % len(self.dms_directory_ids)) + # self.env.ref(self.env[res_model].DMS_STORAGE) + dmsval = { + "is_root_directory": True, + "name": rec.name, + } + + # Prepare CR type model data + cr_type_vals = { + "registrant_id": rec.registrant_id.id, + "applicant_id": rec.applicant_id.id, + "change_request_id": rec.id, + "dms_directory_ids": [(Command.create(dmsval))], + } + + # Create the change request detail record + ref_id = self.env[res_model].create(cr_type_vals) + directory_id = ref_id.dms_directory_ids[0].id + + self.env["spp.dms.directory"].create( + { + "name": "Applicant", + "parent_id": directory_id, + "is_root_directory": False, + } + ) + + # Upload Scanned IDs to DMS + dms_file_ids = [] + for id_fld in ["id_document_details", "qr_code_details"]: + if rec[id_fld]: + dms_id_doc = rec._get_id_doc_vals(directory_id, id_fld) + if dms_id_doc: + dms_file_ids.append(Command.create(dms_id_doc)) + if dms_file_ids: + ref_id.update({"dms_file_ids": dms_file_ids}) + + ref_id._onchange_registrant_id() + request_type_ref_id = f"{res_model},{ref_id.id}" + _logger.debug("DEBUG! request_type_ref_id: %s", request_type_ref_id) + rec.update( + { + "request_type_ref_id": request_type_ref_id, + "id_document_details": "", + } + ) + + # Temporary solution to phone number fo + # def _check_phone_exist(self): + # pass + + @api.constrains("registrant_id", "applicant_phone") + def _check_applicant_phone(self): + # Temporary solution to phone number format error + pass diff --git a/spp_change_request_edit_farmer/models/change_request_edit_farmer.py b/spp_change_request_edit_farmer/models/change_request_edit_farmer.py new file mode 100644 index 000000000..4fc5debfb --- /dev/null +++ b/spp_change_request_edit_farmer/models/change_request_edit_farmer.py @@ -0,0 +1,319 @@ +import logging + +from odoo import _, api, fields, models +from odoo.exceptions import UserError, ValidationError + +from odoo.addons.phone_validation.tools import phone_validation + +_logger = logging.getLogger(__name__) + + +class ChangeRequestTypeCustomEditFarmer(models.Model): + _inherit = "spp.change.request" # Not merging classes as it might require significant refactoring. + + registrant_id = fields.Many2one( + "res.partner", + "Registrant", + domain=[("is_registrant", "=", True), ("is_group", "=", True)], + ) + + def _check_phone_exist(self): + """ + Checks if phone is existing + + :raise UserError: Exception raised when applicant_phone is not existing. + """ + request_type = self.request_type + if "farm" not in request_type: + if not self.applicant_phone: + raise UserError(_("Phone No. is required.")) + + @api.model + def _selection_request_type_ref_id(self): + selection = super()._selection_request_type_ref_id() + new_request_type = ("spp.change.request.edit.farmer", "Edit Farmer") + if new_request_type not in selection: + selection.append(new_request_type) + return selection + + +class ChangeRequestEditFarmer(models.Model): + _name = "spp.change.request.edit.farmer" + _inherit = [ + "spp.change.request.source.mixin", + "spp.change.request.validation.sequence.mixin", + ] + _description = "Edit Farmer Change Request Type" + _order = "id desc" + + # Initialize CR constants + VALIDATION_FORM = "spp_change_request_edit_farmer.view_change_request_edit_farmer_validation_form" + REQUIRED_DOCUMENT_TYPE = [ + "spp_change_request_edit_farmer.spp_dms_edit_farmer", + # "spp_change_request.spp_dms_birth_certificate", + # "spp_change_request.spp_dms_applicant_spp_card", + # "spp_change_request.spp_dms_applicant_uid_card", + # "spp_change_request.spp_dms_custody_certificate", + ] + + # Mandatory initialize source and destination center areas + # If validators will be allowed for both, make the values the same + SRC_AREA_FLD = ["registrant_id", "area_center_id"] + DST_AREA_FLD = SRC_AREA_FLD + + FARMER_FIELDS = [ + "family_name", + "given_name", + "addl_name", + "farmer_national_id", + "gender", + "marital_status", + "birthdate", + "farmer_household_size", + "farmer_postal_address", + "email", + "formal_agricultural_training", + "highest_education_level", + ] + + def _get_dynamic_selection(self): + options = self.env["gender.type"].search([]) + return [(option.value, option.code) for option in options] + + registrant_id = fields.Many2one( + "res.partner", + "Group", + domain=[("is_registrant", "=", True), ("is_group", "=", True)], + ) + farmer_id = fields.Many2one( + "res.partner", + "Farmer", + domain=[("is_registrant", "=", True), ("is_group", "=", False)], + ) + + request_type = fields.Selection(related="change_request_id.request_type") + + # For ID Scanner Widget + id_document_details = fields.Text("ID Document") + + # Member Details + family_name = fields.Char() + given_name = fields.Char() + addl_name = fields.Char(string="Farmer Additional Name") + farmer_national_id = fields.Char(string="National ID Number") + mobile_tel = fields.Char(string="Mobile Telephone Number") + gender = fields.Many2one("gender.type", "Sex") + birthdate = fields.Date("Farmer Date of Birth") + farmer_household_size = fields.Char() + farmer_postal_address = fields.Char("Postal Address") + email = fields.Char("E-mail Address") + formal_agricultural_training = fields.Boolean() + highest_education_level = fields.Selection( + [ + ("none", "None"), + ("primary", "Primary"), + ("secondary", "Secondary"), + ("tertiary", "Tertiary"), + ], + string="Farmer Highest Educational Level", + ) + marital_status = fields.Selection( + [ + ("single", "Single"), + ("married", "Married"), + ("widowed", "Widowed"), + ("separated", "Separated"), + ] + ) + + # Add domain to inherited field: validation_ids + validation_ids = fields.Many2many( + relation="spp_change_request_edit_farmer_rel", + domain=[("request_type", "=", _name)], + ) + + # DMS Field + dms_directory_ids = fields.One2many( + "spp.dms.directory", + "change_request_edit_farmer_id", + string="DMS Directories", + auto_join=True, + ) + dms_file_ids = fields.One2many( + "spp.dms.file", + "change_request_edit_farmer_id", + string="DMS Files", + auto_join=True, + ) + + @api.onchange("registrant_id") + def _onchange_registrant_id(self): + return + + @api.onchange("birthdate") + def _onchange_birthdate(self): + if self.birthdate and self.birthdate > fields.date.today(): + raise ValidationError(_("Birthdate should not be on a later date.")) + + @api.constrains("mobile_tel") + def _check_mobile_tel(self): + for rec in self: + cr = rec.change_request_id + country_code = ( + cr.registrant_id.country_id.code + if cr.registrant_id and cr.registrant_id.country_id and cr.registrant_id.country_id.code + else None + ) + if country_code is None: + country_code = ( + cr.company_id.country_id.code + if cr.company_id.country_id and cr.company_id.country_id.code + else None + ) + try: + phone_validation.phone_parse(rec.mobile_tel, country_code) + except UserError as e: + raise ValidationError(_("Incorrect phone number format")) from e + + @api.onchange("id_document_details") + def _onchange_scan_id_document_details(self): + return + # TODO: Implement this method + # if self.dms_directory_ids: + # if self.id_document_details: + # try: + # details = json.loads(self.id_document_details) + # except json.decoder.JSONDecodeError as e: + # details = None + # _logger.error(e) + # if details: + # # Upload to DMS + # if details["image"]: + # if self._origin: + # directory_id = self._origin.dms_directory_ids[0].id + # else: + # directory_id = self.dms_directory_ids[0].id + # dms_vals = { + # "name": "UID_" + details["document_number"] + ".jpg", + # "directory_id": directory_id, + # "category_id": self.env.ref("spp_change_request.spp_dms_uid_card").id, + # "content": details["image"], + # } + # # TODO: Should be added to vals["dms_file_ids"] but it is + # # not writing to one2many field using Command.create() + # self.env["spp.dms.file"].create(dms_vals) + # + # # TODO: grand_father_name and father_name + # vals = { + # "family_name": details["family_name"], + # "given_name": details["given_name"], + # "birthdate": details["birth_date"], + # "gender": details["gender"], + # "id_document_details": "", + # "birth_place": details["birth_place_city"], + # # TODO: Fix not writing to one2many field: dms_file_ids + # # "dms_file_ids": [(Command.create(dms_vals))], + # } + # self.update(vals) + # else: + # raise UserError(_("There are no directories defined for this change request.")) + + def _get_default_change_request_id(self): + """ + Get the default field name for change request id. + """ + return "default_change_request_edit_farmer_id" + + def validate_data(self): + super().validate_data() + error_message = [] + if not self.farmer_id: + error_message.append(_("The Farmer is required!")) + if error_message: + raise ValidationError("\n".join(error_message)) + + return + + def update_live_data(self): + self.ensure_one() + + # Edit the Farmer (res.partner) + farmer_vals = {} + for farmer_field in self.FARMER_FIELDS: + if self[farmer_field]: + farmer_vals[farmer_field] = self[farmer_field] + self.farmer_id.write(farmer_vals) + if self.mobile_tel: + self.insert_phone_number(self.farmer_id.id, self.mobile_tel) + + if self.farmer_national_id: + self.insert_id(self.farmer_id.id, self.farmer_national_id) + + self.farmer_id.name_change() + + return self.farmer_id + + def insert_phone_number(self, individual_id, mobile_no): + if mobile_no: + current_phone = self.env["g2p.phone.number"].search([("partner_id", "=", individual_id)], limit=1) + if not current_phone: + individual_phone_vals = {"partner_id": individual_id, "phone_no": mobile_no} + self.env["g2p.phone.number"].create(individual_phone_vals) + else: + current_phone.write({"phone_no": mobile_no}) + + def insert_id(self, individual_id, national_id): + if national_id: + current_id = self.env["g2p.reg.id"].search( + [ + ("partner_id", "=", individual_id), + ("value", "=", national_id), + ( + "id_type", + "=", + self.env.ref("spp_farmer_registry_base.id_type_national_id").id, + ), + ] + ) + if not current_id: + existing_national_id = self.env["g2p.reg.id"].search( + [ + ("partner_id", "=", individual_id), + ( + "id_type", + "=", + self.env.ref("spp_farmer_registry_base.id_type_national_id").id, + ), + ] + ) + id_vals = { + "partner_id": individual_id, + "value": national_id, + "id_type": self.env.ref("spp_farmer_registry_base.id_type_national_id").id, + } + if existing_national_id: + existing_national_id.write(id_vals) + else: + self.env["g2p.reg.id"].create(id_vals) + + def open_registrant_details_form(self): + self.ensure_one() + res_id = self.registrant_id.id + form_id = self.env.ref("g2p_registry_group.view_groups_form").id + action = self.env["res.partner"].get_formview_action() + context = { + "create": False, + "edit": False, + "hide_from_cr": 1, + } + action.update( + { + "name": _("Group Details"), + "views": [(form_id, "form")], + "res_id": res_id, + "target": "new", + "context": context, + "flags": {"mode": "readonly"}, + } + ) + return action diff --git a/spp_change_request_edit_farmer/models/change_request_edit_farmer_validation_sequence.py b/spp_change_request_edit_farmer/models/change_request_edit_farmer_validation_sequence.py new file mode 100644 index 000000000..958ed067b --- /dev/null +++ b/spp_change_request_edit_farmer/models/change_request_edit_farmer_validation_sequence.py @@ -0,0 +1,13 @@ +from odoo import api, models + + +class ChangeRequestEditFarmerValidationSequence(models.Model): + _inherit = "spp.change.request.validation.sequence" + + @api.model + def _selection_request_type_ref_id(self): + selection = super()._selection_request_type_ref_id() + new_request_type = ("spp.change.request.edit.farmer", "Edit Farmer") + if new_request_type not in selection: + selection.append(new_request_type) + return selection diff --git a/spp_change_request_edit_farmer/models/dms.py b/spp_change_request_edit_farmer/models/dms.py new file mode 100644 index 000000000..7492201f6 --- /dev/null +++ b/spp_change_request_edit_farmer/models/dms.py @@ -0,0 +1,13 @@ +from odoo import fields, models + + +class SPPDMSDirectoryCustom(models.Model): + _inherit = "spp.dms.directory" + + change_request_edit_farmer_id = fields.Many2one("spp.change.request.edit.farmer", "Change request") + + +class SPPDMSFileCustom(models.Model): + _inherit = "spp.dms.file" + + change_request_edit_farmer_id = fields.Many2one("spp.change.request.edit.farmer", "Change request") diff --git a/spp_change_request_edit_farmer/pyproject.toml b/spp_change_request_edit_farmer/pyproject.toml new file mode 100644 index 000000000..4231d0ccc --- /dev/null +++ b/spp_change_request_edit_farmer/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/spp_change_request_edit_farmer/readme/DESCRIPTION.md b/spp_change_request_edit_farmer/readme/DESCRIPTION.md new file mode 100644 index 000000000..f33753e0d --- /dev/null +++ b/spp_change_request_edit_farmer/readme/DESCRIPTION.md @@ -0,0 +1,38 @@ +# OpenSPP Change Request: Create Group + +## Overview + +The [spp_change_request_edit_farmer](spp_change_request_edit_farmer) module extends the OpenSPP Change Request system to specifically handle requests for editing existing Farmer within the registry. It leverages the framework provided by the [spp_change_request](spp_change_request) module and integrates with other registry modules to streamline the process of adding farmers while maintaining data integrity and consistency. + +## Purpose + +* **Specialized Change Request Type**: Introduce a dedicated change request type for adding farmers, distinct from other types of registrant modifications. +* **Streamlined Data Collection**: Provide a tailored form for capturing essential information about the new farmer, including personal details, farmer-specific attributes, and desired group membership details. +* **Group Membership Management**: Integrate with the [g2p_registry_membership](g2p_registry_membership) module to seamlessly add the new farmer to the specified group upon approval. +* **Enhanced Validation**: Implement specific validation rules relevant to adding farmers, ensuring the accuracy and completeness of the submitted information. + +## Module Integration + +## Dependencies + +The module relies heavily on the following modules: + +* **[spp_change_request](spp_change_request)**: Inherits the core change request functionality, including the request workflow, validation processes, approval mechanisms, and integration with the Document Management System ([spp_dms](spp_dms)). +* **[g2p_registry_membership](g2p_registry_membership)**: Integrates with the membership management system to create the appropriate group membership record for the new farmer upon change request approval. +* **[phone_validation](phone_validation)**: Utilizes the phone validation module to ensure phone number entries for the new farmer adhere to correct formatting. +* **[g2p_registry_group](g2p_registry_group)**: Accesses group information and functionality to display details about the target group for the new farmer. +* **[g2p_registry_individual](g2p_registry_individual)**: Leverages individual registrant management features, inheriting from the individual registrant model. +* **[spp_service_points](spp_service_points)**: Integrates with service points, allowing change requests to be initiated and managed through designated service points. + +## Additional Functionality + +* **Custom Change Request Model**: Introduces the `spp.change.request.edit.farmer` model, inheriting from the base `spp.change.request` model and adding fields specific to adding farmers, such as farmer-specific details and group membership information. +* **Tailored Forms**: Provides specialized views for creating, displaying, and validating `Edit Farmer` change requests, including a dedicated form (`view_change_request_edit_farmer_form`) with relevant fields and a validation-focused form (`view_change_request_edit_farmer_validation_form`). +* **Automated Individual and Membership Creation**: Upon validation and approval of the change request, the module automatically creates: + * A new individual registrant record (`res.partner`) for the Individual, populating it with the submitted data. + * A corresponding group membership record (`g2p.group.membership`), linking the newly created Individual to the designated group. +* **Enhanced User Interface**: Adds specific menu items and actions to the OpenSPP interface, allowing users to efficiently manage `Create Group` requests. + +## Conclusion + +The [spp_change_request_edit_farmer](spp_change_request_edit_farmer) module provides a robust and specialized workflow for editing existing Farmer within the OpenSPP registry. By seamlessly integrating with core change management and registry modules, it ensures data accuracy, consistency, and a streamlined user experience for managing farmer additions. diff --git a/spp_change_request_edit_farmer/security/change_request_security.xml b/spp_change_request_edit_farmer/security/change_request_security.xml new file mode 100644 index 000000000..15e09f332 --- /dev/null +++ b/spp_change_request_edit_farmer/security/change_request_security.xml @@ -0,0 +1,16 @@ + + + + + CR Demo: Edit Farmer allowed validators + + [] + + + + + + + + + diff --git a/spp_change_request_edit_farmer/security/ir.model.access.csv b/spp_change_request_edit_farmer/security/ir.model.access.csv new file mode 100644 index 000000000..f5cb87d17 --- /dev/null +++ b/spp_change_request_edit_farmer/security/ir.model.access.csv @@ -0,0 +1,14 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +spp_change_request_edit_farmer_admin,Change Request Type: Edit Farmer Admin Access,spp_change_request_edit_farmer.model_spp_change_request_edit_farmer,g2p_registry_base.group_g2p_admin,1,1,1,1 + +spp_change_request_edit_farmer_registrar,Change Request Type: Edit Farmer Registrar Access,spp_change_request_edit_farmer.model_spp_change_request_edit_farmer,g2p_registry_base.group_g2p_registrar,1,0,0,0 + +spp_change_request_edit_farmer_validator,Change Request Type: Edit Farmer Validator Access,spp_change_request_edit_farmer.model_spp_change_request_edit_farmer,spp_change_request.group_spp_change_request_validator,1,1,0,0 + +spp_change_request_edit_farmer_hq_validator,Change Request Type: Edit Farmer HQ Validator Access,spp_change_request_edit_farmer.model_spp_change_request_edit_farmer,spp_change_request.group_spp_change_request_hq_validator,1,1,0,0 + +spp_change_request_edit_farmer_administrator,Change Request Type: Edit Farmer Administrator Access,spp_change_request_edit_farmer.model_spp_change_request_edit_farmer,spp_change_request.group_spp_change_request_administrator,1,1,0,0 + +spp_change_request_edit_farmer_applicator,Change Request Type: Edit Farmer Applicator Access,spp_change_request_edit_farmer.model_spp_change_request_edit_farmer,spp_change_request.group_spp_change_request_applicator,1,1,0,0 + +spp_change_request_edit_farmer_agent,Change Request Type: Edit Farmer Agent Access,spp_change_request_edit_farmer.model_spp_change_request_edit_farmer,spp_change_request.group_spp_change_request_agent,1,1,1,1 diff --git a/spp_change_request_edit_farmer/static/description/icon.png b/spp_change_request_edit_farmer/static/description/icon.png new file mode 100644 index 000000000..35f8fec26 Binary files /dev/null and b/spp_change_request_edit_farmer/static/description/icon.png differ diff --git a/spp_change_request_edit_farmer/static/description/index.html b/spp_change_request_edit_farmer/static/description/index.html new file mode 100644 index 000000000..9a4c01ae5 --- /dev/null +++ b/spp_change_request_edit_farmer/static/description/index.html @@ -0,0 +1,504 @@ + + + + + +OpenSPP Change Request: Add Farmer + + + +
+

OpenSPP Change Request: Add Farmer

+ + +

Alpha License: LGPL-3 OpenSPP/openspp-modules

+
+

OpenSPP Change Request: Add Farmer

+
+

Overview

+

The spp_change_request_add_farmer +module extends the OpenSPP Change Request system to specifically handle +requests for adding new farmers to existing groups within the registry. +It leverages the framework provided by the +spp_change_request module and integrates with +other registry modules to streamline the process of adding farmers while +maintaining data integrity and consistency.

+
+
+

Purpose

+
    +
  • Specialized Change Request Type: Introduce a dedicated change +request type for adding farmers, distinct from other types of +registrant modifications.
  • +
  • Streamlined Data Collection: Provide a tailored form for +capturing essential information about the new farmer, including +personal details, farmer-specific attributes, and desired group +membership details.
  • +
  • Group Membership Management: Integrate with the +g2p_registry_membership module to +seamlessly add the new farmer to the specified group upon approval.
  • +
  • Enhanced Validation: Implement specific validation rules relevant +to adding farmers, ensuring the accuracy and completeness of the +submitted information.
  • +
+
+
+

Module Integration

+
+
+

Dependencies

+

The module relies heavily on the following modules:

+
    +
  • spp_change_request: Inherits the core change +request functionality, including the request workflow, validation +processes, approval mechanisms, and integration with the Document +Management System (spp_dms).
  • +
  • spp_farmer_registry_base: Leverages +the farmer-specific data models and attributes to capture and store +information about the new farmer.
  • +
  • g2p_registry_membership: Integrates +with the membership management system to create the appropriate group +membership record for the new farmer upon change request approval.
  • +
  • phone_validation: Utilizes the phone +validation module to ensure phone number entries for the new farmer +adhere to correct formatting.
  • +
  • g2p_registry_group: Accesses group +information and functionality to display details about the target +group for the new farmer.
  • +
  • g2p_registry_individual: Leverages +individual registrant management features, inheriting from the +individual registrant model.
  • +
  • spp_service_points: Integrates with service +points, allowing change requests to be initiated and managed through +designated service points.
  • +
+
+
+

Additional Functionality

+
    +
  • Custom Change Request Model: Introduces the +spp.change.request.add.farmer model, inheriting from the base +spp.change.request model and adding fields specific to adding +farmers, such as farmer-specific details and group membership +information.
  • +
  • Tailored Forms: Provides specialized views for creating, +displaying, and validating Add Farmer change requests, including +a dedicated form (view_change_request_add_farmer_form) with +relevant fields and a validation-focused form +(view_change_request_add_farmer_validation_form).
  • +
  • Automated Individual and Membership Creation: Upon validation and +approval of the change request, the module automatically creates:
      +
    • A new individual registrant record (res.partner) for the +farmer, populating it with the submitted data.
    • +
    • A corresponding group membership record +(g2p.group.membership), linking the newly created farmer to +the designated group.
    • +
    +
  • +
  • Enhanced User Interface: Adds specific menu items and actions to +the OpenSPP interface, allowing users to efficiently manage +Add Farmer requests.
  • +
+
+
+

Conclusion

+

The spp_change_request_add_farmer +module provides a robust and specialized workflow for adding new farmers +to existing groups within the OpenSPP registry. By seamlessly +integrating with core change management and registry modules, it ensures +data accuracy, consistency, and a streamlined user experience for +managing farmer additions.

+
+

Important

+

This is an alpha version, the data model and design can change at any time without warning. +Only for development or testing purpose, do not use in production. +More details on development status

+
+

Table of contents

+
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • OpenSPP.org
  • +
+
+
+

Maintainers

+

Current maintainers:

+

jeremi gonzalesedwin1123

+

This module is part of the OpenSPP/openspp-modules project on GitHub.

+

You are welcome to contribute.

+
+
+
+ + diff --git a/spp_change_request_edit_farmer/tests/__init__.py b/spp_change_request_edit_farmer/tests/__init__.py new file mode 100644 index 000000000..bd1cad282 --- /dev/null +++ b/spp_change_request_edit_farmer/tests/__init__.py @@ -0,0 +1 @@ +# from . import test_create_cr diff --git a/spp_change_request_edit_farmer/tests/sample_document.jpeg b/spp_change_request_edit_farmer/tests/sample_document.jpeg new file mode 100644 index 000000000..9c6b4f8d6 Binary files /dev/null and b/spp_change_request_edit_farmer/tests/sample_document.jpeg differ diff --git a/spp_change_request_edit_farmer/tests/test_create_cr.py b/spp_change_request_edit_farmer/tests/test_create_cr.py new file mode 100644 index 000000000..acbd0f306 --- /dev/null +++ b/spp_change_request_edit_farmer/tests/test_create_cr.py @@ -0,0 +1,244 @@ +import base64 +import os + +from odoo import Command, _ +from odoo.exceptions import ValidationError +from odoo.tests.common import TransactionCase + + +class ChangeRequestAddFarmerTest(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + cls.group = cls.env["res.partner"].create( + { + "name": "Test Group", + "is_registrant": True, + "is_group": True, + } + ) + + cls.individual = cls.env["res.partner"].create( + { + "name": "Test Individual", + "is_registrant": True, + "is_group": False, + } + ) + + cls.membership = cls.env["g2p.group.membership"].create( + { + "group": cls.group.id, + "individual": cls.individual.id, + } + ) + + cls.change_request = cls.env["spp.change.request"].create( + { + "request_type": "spp.change.request.add.farmer", + "registrant_id": cls.group.id, + "applicant_id": cls.individual.id, + "applicant_phone": "09123456789", + } + ) + cls.change_request.create_request_detail_no_redirect() + cls.res_id = cls.env["spp.change.request.add.farmer"].search( + [("id", "=", cls.change_request.request_type_ref_id.id)] + ) + + group_hq = [ + Command.link(cls.env.ref("spp_change_request.group_spp_change_request_validator").id), + Command.link(cls.env.ref("spp_change_request.group_spp_change_request_hq_validator").id), + Command.link(cls.env.ref("g2p_registry_base.group_g2p_admin").id), + Command.link(cls.env.ref("base.group_user").id), + ] + group_local = [ + Command.link(cls.env.ref("spp_change_request.group_spp_change_request_validator").id), + Command.link(cls.env.ref("spp_change_request.group_spp_change_request_local_validator").id), + Command.link(cls.env.ref("base.group_user").id), + ] + cls.cr_validator_local = cls.env["res.users"].create( + {"name": "Validator Local", "login": "validator@local", "groups_id": group_local} + ) + cls.cr_validator_hq = cls.env["res.users"].create( + {"name": "Validator HQ", "login": "Validator@hq", "groups_id": group_hq} + ) + + def get_vals(self): + if not self.env["gender.type"].search([]): + gender = self.env["gender.type"].create({"code": "M", "value": "Male"}) + else: + gender = self.env["gender.type"].search([])[0] + + return { + "full_name": "Test Farmer", + "family_name": "Farmer", + "given_name": "Test", + "addl_name": "Jr", + "birth_place": "Hometown", + "birthdate_not_exact": True, + "birthdate": "1990-01-01", + "gender": gender.value, + "image_1920": None, + "experience_years": 2, + "formal_agricultural_training": True, + "farmer_national_id": "1234567890", + "farmer_household_size": 5, + "farmer_postal_address": "123 Main St", + "marital_status": "single", + "highest_education_level": "primary", + "phone": "09123456789", + "uid_number": "123456789125", + } + + def test_01_validate_cr_without_attachments(self): + vals = self.get_vals() + self.res_id.write(vals) + with self.assertRaisesRegex(ValidationError, "The required document Add Farmer Request Form is missing."): + self.res_id.action_submit() + + # TODO: removed below test cases because they are having errors in the CI + + # def test_02_validate_cr_with_complete_data(self): + # vals = self.get_vals() + # self.res_id.write(vals) + # file = None + # filename = None + # file_path = f"{os.path.dirname(os.path.abspath(__file__))}/sample_document.jpeg" + # with open(file_path, "rb") as f: + # filename = f.name + # file = base64.b64encode(f.read()) + + # vals = { + # "content": file, + # "name": filename, + # "category_id": self.env.ref("spp_change_request_add_farmer.spp_dms_add_farmer").id, + # "directory_id": self.res_id.dms_directory_ids[0].id, + # "change_request_add_farmer_id": self.res_id.id, + # } + # self.env["spp.dms.file"].create(vals) + # self.res_id.action_submit() + + # self.assertEqual( + # self.change_request.state, + # "pending", + # "CR should now be in Pending Validation!", + # ) + # self.res_id.with_user(self.cr_validator_local.id).action_validate() + # self.res_id.with_user(self.cr_validator_hq.id).action_validate() + # self.assertEqual( + # self.change_request.state, + # "applied", + # "CR should now be in Applied!", + # ) + # open_form = self.res_id.open_registrant_details_form() + # self.assertEqual( + # open_form["name"], + # _("Group Details"), + # "Incorrect Name!", + # ) + + def test_03_validate_cr_with_invalid_data(self): + reg_vals = { + "birth_place": "Hometown", + "birthdate_not_exact": True, + "image_1920": None, + "experience_years": 2, + "formal_agricultural_training": True, + "farmer_national_id": "1234567890", + "farmer_household_size": 5, + "farmer_postal_address": "123 Main St", + "marital_status": "single", + "highest_education_level": "primary", + "phone": "09123456789", + "uid_number": "123456789125", + } + self.res_id.write(reg_vals) + + file_path = f"{os.path.dirname(os.path.abspath(__file__))}/sample_document.jpeg" + with open(file_path, "rb") as f: + filename = f.name + file = base64.b64encode(f.read()) + + vals = { + "content": file, + "name": filename, + "category_id": self.env.ref("spp_change_request_add_farmer.spp_dms_add_farmer").id, + "directory_id": self.res_id.dms_directory_ids[0].id, + "change_request_add_farmer_id": self.res_id.id, + } + self.env["spp.dms.file"].create(vals) + error_message = [ + _("The Family Name is required!"), + _("The First Name is required!"), + _("The Date of Birth is required!"), + _("The Gender is required!"), + ] + error_message = "\n".join(error_message) + with self.assertRaisesRegex(ValidationError, error_message): + self.res_id.validate_data() + + def test_04_update_live_data_with_kinds(self): + vals = self.get_vals() + kind = self.env.ref("g2p_registry_membership.group_membership_kind_head").id + vals["kind"] = [(6, 0, [kind])] + self.res_id.write(vals) + file = None + filename = None + file_path = f"{os.path.dirname(os.path.abspath(__file__))}/sample_document.jpeg" + with open(file_path, "rb") as f: + filename = f.name + file = base64.b64encode(f.read()) + + vals = { + "content": file, + "name": filename, + "category_id": self.env.ref("spp_change_request_add_farmer.spp_dms_add_farmer").id, + "directory_id": self.res_id.dms_directory_ids[0].id, + "change_request_add_farmer_id": self.res_id.id, + } + self.env["spp.dms.file"].create(vals) + self.res_id.update_live_data() + + individual_id = self.env["g2p.group.membership"].search([("group", "=", self.group.id), ("kind", "in", kind)]) + self.assertTrue(individual_id, "Individual not found!") + + def test_05_update_live_data_without_phone_uid(self): + vals = self.get_vals() + vals.pop("phone") + vals.pop("uid_number") + + self.res_id.write(vals) + file = None + filename = None + file_path = f"{os.path.dirname(os.path.abspath(__file__))}/sample_document.jpeg" + with open(file_path, "rb") as f: + filename = f.name + file = base64.b64encode(f.read()) + + vals = { + "content": file, + "name": filename, + "category_id": self.env.ref("spp_change_request_add_farmer.spp_dms_add_farmer").id, + "directory_id": self.res_id.dms_directory_ids[0].id, + "change_request_add_farmer_id": self.res_id.id, + } + self.env["spp.dms.file"].create(vals) + self.res_id.update_live_data() + + individual_id = self.env["res.partner"].search([("name", "=", "Test Farmer")]) + self.assertFalse(individual_id.phone_number_ids, "Should not have phone number!") + self.assertFalse(individual_id.reg_ids, "Should not have registrant ID!") + + def test_06_create_request_detail_demo(self): + change_request = self.env["spp.change.request"].create( + { + "request_type": "spp.change.request.add.farmer", + "registrant_id": self.group.id, + "applicant_id": self.individual.id, + "applicant_phone": "09123456789", + } + ) + change_request.create_request_detail_demo() + self.assertTrue(change_request.request_type_ref_id, "Request Type Reference ID not set!") diff --git a/spp_change_request_edit_farmer/views/change_request_edit_farmer_view.xml b/spp_change_request_edit_farmer/views/change_request_edit_farmer_view.xml new file mode 100644 index 000000000..15368fdf2 --- /dev/null +++ b/spp_change_request_edit_farmer/views/change_request_edit_farmer_view.xml @@ -0,0 +1,737 @@ + + + + + + change_request_edit_farmer_tree + spp.change.request.edit.farmer + 0 + + + + + + + + + + + + + + + view_change_request_edit_farmer_form + spp.change.request.edit.farmer + 0 + +
+
+
+ +
+ +
+
+
+
+
+
+
+

+
+
+

+
+
+
+
+
+
+

+
+
+

+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+ + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+
+
+ + + view_change_request_edit_farmer_validation_form + spp.change.request.edit.farmer + 100 + +
+
+
+ + +
+
+ + + +
+
+ + + + +
+
+ +
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+ + + change_request_edit_farmer_filter + spp.change.request.edit.farmer + + + + + + + + + + + + + Edit Farmer Change Request + ir.actions.act_window + spp.change.request.edit.farmer + tree,form + + {} + [] + +

+ No 'Edit Farmer' change requests found! +

+ There are no records found based on the current filter. +

+
+
+ + + + tree + + + + + + + form + + + + + + + + + Edit Farmer + ir.actions.act_window + spp.change.request.edit.farmer + tree,form + + {'search_default_filter_validator': 1} + [('state','=','pending')] + +

+ No pending 'Edit Farmer' change requests found! +

+ There are no records found based on the current filter. +

+
+
+ + + + tree + + + + + + + form + + + + + + + + +
diff --git a/spp_change_request_edit_farmer/views/change_request_view.xml b/spp_change_request_edit_farmer/views/change_request_view.xml new file mode 100644 index 000000000..6bd398c11 --- /dev/null +++ b/spp_change_request_edit_farmer/views/change_request_view.xml @@ -0,0 +1,28 @@ + + + + view_change_request_create_farm_custom_form + spp.change.request + + + + 'farm' not in request_type + + + 'farm' in request_type and not registrant_id + + + 'farm' in request_type and not registrant_id + + + 'farm' in request_type and not registrant_id + + + 'farm' in request_type and not registrant_id + + + 'farm' in request_type and not registrant_id + + + + diff --git a/spp_change_request_edit_farmer/views/dms_file_view.xml b/spp_change_request_edit_farmer/views/dms_file_view.xml new file mode 100644 index 000000000..bf0233a81 --- /dev/null +++ b/spp_change_request_edit_farmer/views/dms_file_view.xml @@ -0,0 +1,12 @@ + + + view_dms_file_spp_edit_farmer_form + spp.dms.file + + + + + + + +