From 3045663f8176f972fa652ee9b631c73bdd2adcc2 Mon Sep 17 00:00:00 2001 From: jacquesfize Date: Tue, 28 Jul 2026 10:44:35 +0200 Subject: [PATCH 1/6] feat: Add create/read/update routes for individuals --- .../monitoring/schemas.py | 1 + .../routes/individuals.py | 69 +++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/backend/gn_module_monitoring/monitoring/schemas.py b/backend/gn_module_monitoring/monitoring/schemas.py index ae89f7fe0..e8bf5aba1 100644 --- a/backend/gn_module_monitoring/monitoring/schemas.py +++ b/backend/gn_module_monitoring/monitoring/schemas.py @@ -365,6 +365,7 @@ class Meta: model = TMonitoringIndividuals include_fk = True load_relationships = True + load_instance = True medias = MA.Nested(MediaSchema, many=True) diff --git a/backend/gn_module_monitoring/routes/individuals.py b/backend/gn_module_monitoring/routes/individuals.py index 33c796ecf..3d0c33e2b 100644 --- a/backend/gn_module_monitoring/routes/individuals.py +++ b/backend/gn_module_monitoring/routes/individuals.py @@ -1,11 +1,13 @@ from flask import g, request from gn_module_monitoring.config.utils import get_specific_properties +from marshmallow import EXCLUDE from sqlalchemy import select from werkzeug.datastructures import MultiDict from werkzeug.exceptions import Forbidden from geonature.utils.env import db +from geonature.core.gn_permissions import decorators as permissions from geonature.core.gn_permissions.decorators import check_cruved_scope from gn_module_monitoring import MODULE_CODE from gn_module_monitoring.blueprint import blueprint @@ -23,10 +25,14 @@ get_sort, paginate, paginate_scope, + process_json_data_for_db_upsert, sort, sort_according_to_column_type_for_site, ) +default_route_object_type = "individual" +OBJECT_CODE = "MONITORINGS_INDIVIDUALS" + @blueprint.route("/refacto/individuals", methods=["GET"], defaults={"object_type": "individual"}) @blueprint.route( @@ -73,6 +79,50 @@ def get_individuals(object_type, module_code=None): ) +@blueprint.route( + "/individuals//", + methods=["GET"], + defaults={"object_type": default_route_object_type}, +) +@permissions.check_cruved_scope("R", get_scope=True, object_code=OBJECT_CODE) +def get_individual_by_id(scope: int, module_code: str, id: int, object_type: str): + individual = db.get_or_404(TMonitoringIndividuals, id) + if not individual.has_instance_permission(scope=scope): + raise Forbidden(f"User {g.current_user} cannot read individual {individual.id_individual}") + data = MonitoringIndividualsSchema().dump(individual) + + return data + + +@blueprint.route( + "//individuals", + methods=["POST"], + defaults={"object_type": default_route_object_type}, +) +@permissions.check_cruved_scope("C", object_code=OBJECT_CODE) +def post_individual(object_type: str, module_code: str): + post_data = dict(request.get_json()) + return create_or_update_individual(post_data, module_code=module_code) + + +@blueprint.route( + "//individuals/", + methods=["PATCH"], + defaults={"object_type": default_route_object_type}, +) +@permissions.check_cruved_scope("U", get_scope=True, object_code=OBJECT_CODE) +def patch_individual(scope, object_type: str, module_code: str, _id: int): + individual = db.get_or_404(TMonitoringIndividuals, _id) + if not individual.has_instance_permission(scope=scope): + raise Forbidden( + f"User {g.current_user} cannot update individual {individual.id_individual}" + ) + post_data = dict(request.get_json()) + if not "id_individual" in post_data: + post_data["id_individual"] = _id + return create_or_update_individual(post_data, module_code=module_code) + + @blueprint.route( "/individuals/", methods=["DELETE"], defaults={"object_type": "individual"} ) @@ -86,3 +136,22 @@ def delete_individual(scope, _id: int, object_type: str): db.session.delete(individual) db.session.commit() return {"success": "Item is successfully deleted"}, 200 + + +def create_or_update_individual(post_data: dict, module_code: str = "generic"): + """ + Create or update an individual. + + :param post_data: dict containing data to create or update an individual + :param module_code: str, module code, default is "generic" + :return: dict, serialized individual + """ + config = get_config(module_code, force=True) + process_data = process_json_data_for_db_upsert(config, post_data, default_route_object_type) + + individual = MonitoringIndividualsSchema(unknown=EXCLUDE).load(process_data) + + db.session.add(individual) + db.session.commit() + + return MonitoringIndividualsSchema().dump(individual) From 176eaf592a718ef2192cff8d0af32d311f6ce525 Mon Sep 17 00:00:00 2001 From: jacquesfize Date: Tue, 28 Jul 2026 10:45:26 +0200 Subject: [PATCH 2/6] chore: add tests --- .../test_schemas/test_individual_schema.py | 8 +- .../tests/test_routes/test_individuals.py | 128 ++++++++++++++++++ 2 files changed, 132 insertions(+), 4 deletions(-) diff --git a/backend/gn_module_monitoring/tests/test_monitoring/test_schemas/test_individual_schema.py b/backend/gn_module_monitoring/tests/test_monitoring/test_schemas/test_individual_schema.py index 15e2f4a0c..41479ac29 100644 --- a/backend/gn_module_monitoring/tests/test_monitoring/test_schemas/test_individual_schema.py +++ b/backend/gn_module_monitoring/tests/test_monitoring/test_schemas/test_individual_schema.py @@ -35,10 +35,10 @@ def test_monitoring_individuals_schema_deserialization(self, users): deserialized_data = schema.load(input_data) # Vérifications - assert deserialized_data["individual_name"] == input_data["individual_name"] - assert deserialized_data["cd_nom"] == input_data["cd_nom"] - assert deserialized_data["active"] == input_data["active"] - assert deserialized_data["id_digitiser"] == input_data["id_digitiser"] + assert deserialized_data.individual_name == input_data["individual_name"] + assert deserialized_data.cd_nom == input_data["cd_nom"] + assert deserialized_data.active == input_data["active"] + assert deserialized_data.id_digitiser == input_data["id_digitiser"] def test_monitoring_individuals_schema_validation(self): # Données d'entrée invalides diff --git a/backend/gn_module_monitoring/tests/test_routes/test_individuals.py b/backend/gn_module_monitoring/tests/test_routes/test_individuals.py index 1f54d35c0..ebbeadad8 100644 --- a/backend/gn_module_monitoring/tests/test_routes/test_individuals.py +++ b/backend/gn_module_monitoring/tests/test_routes/test_individuals.py @@ -3,6 +3,9 @@ from pypnusershub.tests.utils import set_logged_user_cookie +from geonature.utils.env import db +from gn_module_monitoring.monitoring.models import TMonitoringIndividuals + @pytest.mark.usefixtures("client_class") class TestIndividuals: @@ -83,3 +86,128 @@ def test_get_individuals_scope_2_organism(self, install_module_test_indi, users) response = self.client.get(url_for("monitorings.get_individuals", module_code="test_indi")) assert response.status_code == 200 assert len(response.json["items"]) == 4 + + def test_get_individual_by_id(self, install_module_test_indi, users): + set_logged_user_cookie(self.client, users["admin_user"]) + individual_instance = install_module_test_indi[0] + + r = self.client.get( + url_for( + "monitorings.get_individual_by_id", + module_code="test_indi", + id=individual_instance.id_individual, + ) + ) + + assert r.status_code == 200 + assert r.json["id_individual"] == individual_instance.id_individual + assert r.json["individual_name"] == individual_instance.individual_name + assert r.json["cd_nom"] == individual_instance.cd_nom + + def test_get_individual_by_id_not_found(self, users): + set_logged_user_cookie(self.client, users["admin_user"]) + + r = self.client.get( + url_for( + "monitorings.get_individual_by_id", + module_code="test_indi", + id=999999999, + ) + ) + + assert r.status_code == 404 + + def test_post_individual(self, install_module_test_indi, users): + set_logged_user_cookie(self.client, users["admin_user"]) + + data = { + "individual_name": "new_individual", + "cd_nom": 79273, + "active": True, + "comment": "new_comment", + "id_digitiser": users["admin_user"].id_role, + } + + r = self.client.post( + url_for( + "monitorings.post_individual", + module_code="test_indi", + ), + json=data, + ) + assert r.status_code == 200 + assert r.json["individual_name"] == data["individual_name"] + assert r.json["cd_nom"] == data["cd_nom"] + assert r.json["comment"] == data["comment"] + + def test_patch_individual(self, install_module_test_indi, users): + set_logged_user_cookie(self.client, users["admin_user"]) + individual_instance = install_module_test_indi[0] + + data = { + "id_individual": individual_instance.id_individual, + "individual_name": "updated_individual", + "cd_nom": individual_instance.cd_nom, + "id_digitiser": individual_instance.id_digitiser, + "comment": "updated_comment", + } + + r = self.client.patch( + url_for( + "monitorings.patch_individual", + module_code="test_indi", + _id=individual_instance.id_individual, + ), + json=data, + ) + assert r.status_code == 200 + assert r.json["id_individual"] == individual_instance.id_individual + assert r.json["individual_name"] == data["individual_name"] + assert r.json["comment"] == data["comment"] + + def test_patch_individual_not_found(self, install_module_test_indi, users): + set_logged_user_cookie(self.client, users["admin_user"]) + + r = self.client.patch( + url_for( + "monitorings.patch_individual", + module_code="test_indi", + _id=999999999, + ), + json={}, + ) + + assert r.status_code == 404 + + def test_delete_individual(self, install_module_test_indi, users): + set_logged_user_cookie(self.client, users["admin_user"]) + individual_instance = install_module_test_indi[-1] + + r = self.client.delete( + url_for( + "monitorings.delete_individual", + _id=individual_instance.id_individual, + module_code="test_indi", + ) + ) + + assert r.status_code == 200 + assert r.json == {"success": "Item is successfully deleted"} + + r = self.client.get( + url_for( + "monitorings.get_individual_by_id", + module_code="test_indi", + id=individual_instance.id_individual, + ) + ) + assert r.status_code == 404 + + def test_delete_individual_not_found(self, users): + set_logged_user_cookie(self.client, users["admin_user"]) + + r = self.client.delete( + url_for("monitorings.delete_individual", _id=999999999, module_code="test_indi") + ) + + assert r.status_code == 404 From 0643f792d3872feede9c65e42f081e35370fafbd Mon Sep 17 00:00:00 2001 From: jacquesfize Date: Tue, 28 Jul 2026 13:10:47 +0200 Subject: [PATCH 3/6] fixup! feat: Add create/read/update routes for individuals --- backend/gn_module_monitoring/monitoring/schemas.py | 8 ++++++++ backend/gn_module_monitoring/routes/individuals.py | 10 ++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/backend/gn_module_monitoring/monitoring/schemas.py b/backend/gn_module_monitoring/monitoring/schemas.py index e8bf5aba1..00b30d1f4 100644 --- a/backend/gn_module_monitoring/monitoring/schemas.py +++ b/backend/gn_module_monitoring/monitoring/schemas.py @@ -368,12 +368,20 @@ class Meta: load_instance = True medias = MA.Nested(MediaSchema, many=True) + id_individual = auto_field(required=False, allow_none=True) + uuid_individual = fields.String(required=False, allow_none=True) pk = fields.Method("set_pk", dump_only=True) def set_pk(self, obj): return "id_individual" + @pre_load + def normalize(self, data, **kwargs): + data["medias"] = data.get("medias") or [] + + return data + class MonitoringIndividualsSchemaCruved(MonitoringCruvedSchemaMixin, MonitoringIndividualsSchema): pass diff --git a/backend/gn_module_monitoring/routes/individuals.py b/backend/gn_module_monitoring/routes/individuals.py index 3d0c33e2b..84c0988fe 100644 --- a/backend/gn_module_monitoring/routes/individuals.py +++ b/backend/gn_module_monitoring/routes/individuals.py @@ -1,5 +1,5 @@ from flask import g, request -from gn_module_monitoring.config.utils import get_specific_properties +from gn_module_monitoring.config.utils import get_module, get_specific_properties from marshmallow import EXCLUDE from sqlalchemy import select from werkzeug.datastructures import MultiDict @@ -118,6 +118,7 @@ def patch_individual(scope, object_type: str, module_code: str, _id: int): f"User {g.current_user} cannot update individual {individual.id_individual}" ) post_data = dict(request.get_json()) + print(post_data) if not "id_individual" in post_data: post_data["id_individual"] = _id return create_or_update_individual(post_data, module_code=module_code) @@ -148,9 +149,10 @@ def create_or_update_individual(post_data: dict, module_code: str = "generic"): """ config = get_config(module_code, force=True) process_data = process_json_data_for_db_upsert(config, post_data, default_route_object_type) - - individual = MonitoringIndividualsSchema(unknown=EXCLUDE).load(process_data) - + try: + individual = MonitoringIndividualsSchema(unknown=EXCLUDE).load(process_data) + except Exception as e: + raise Exception(f"create_or_update_individual : {str(e)}") db.session.add(individual) db.session.commit() From 501f022c3b7ba11fd47025283be2f4c51fe25fe3 Mon Sep 17 00:00:00 2001 From: jacquesfize Date: Tue, 28 Jul 2026 13:11:16 +0200 Subject: [PATCH 4/6] feat: add new components for individuals creation and details --- ...onitoring-individuals-create.component.css | 9 ++ ...nitoring-individuals-create.component.html | 13 ++ ...monitoring-individuals-create.component.ts | 56 +++++++++ ...onitoring-individuals-detail.component.css | 9 ++ ...nitoring-individuals-detail.component.html | 19 +++ ...monitoring-individuals-detail.component.ts | 117 ++++++++++++++++++ .../monitoring-sitesgroups.component.ts | 15 +-- frontend/app/gnModule.module.ts | 19 +++ .../resolver/detail-individuals-resolver.ts | 13 ++ 9 files changed, 259 insertions(+), 11 deletions(-) create mode 100644 frontend/app/components/monitoring-individuals-create/monitoring-individuals-create.component.css create mode 100644 frontend/app/components/monitoring-individuals-create/monitoring-individuals-create.component.html create mode 100644 frontend/app/components/monitoring-individuals-create/monitoring-individuals-create.component.ts create mode 100644 frontend/app/components/monitoring-individuals-detail/monitoring-individuals-detail.component.css create mode 100644 frontend/app/components/monitoring-individuals-detail/monitoring-individuals-detail.component.html create mode 100644 frontend/app/components/monitoring-individuals-detail/monitoring-individuals-detail.component.ts create mode 100644 frontend/app/resolver/detail-individuals-resolver.ts diff --git a/frontend/app/components/monitoring-individuals-create/monitoring-individuals-create.component.css b/frontend/app/components/monitoring-individuals-create/monitoring-individuals-create.component.css new file mode 100644 index 000000000..976cb071d --- /dev/null +++ b/frontend/app/components/monitoring-individuals-create/monitoring-individuals-create.component.css @@ -0,0 +1,9 @@ +.obj-title { + font-size: 1.75rem; + padding: 5px 15px; +} + +:host ::ng-deep .obj-description { + font-size: 2rem; + font-weight: bold; +} diff --git a/frontend/app/components/monitoring-individuals-create/monitoring-individuals-create.component.html b/frontend/app/components/monitoring-individuals-create/monitoring-individuals-create.component.html new file mode 100644 index 000000000..479a52a4a --- /dev/null +++ b/frontend/app/components/monitoring-individuals-create/monitoring-individuals-create.component.html @@ -0,0 +1,13 @@ +
+ {{ 'Actions.Add' | translate }} +
+
+ +
diff --git a/frontend/app/components/monitoring-individuals-create/monitoring-individuals-create.component.ts b/frontend/app/components/monitoring-individuals-create/monitoring-individuals-create.component.ts new file mode 100644 index 000000000..904ae984a --- /dev/null +++ b/frontend/app/components/monitoring-individuals-create/monitoring-individuals-create.component.ts @@ -0,0 +1,56 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { AuthService, User } from '@geonature/components/auth/auth.service'; + +import { ActivatedRoute } from '@angular/router'; +import { FormService } from '../../services/form.service'; +import { IndividualsService } from '../../services/api-geom.service'; +import { ObjectService } from '../../services/object.service'; +import { ConfigServiceG } from '../../services/config-g.service'; +import { IIndividual } from '../../interfaces/individual'; + +@Component({ + selector: 'monitoring-individuals-create', + templateUrl: './monitoring-individuals-create.component.html', + styleUrls: ['./monitoring-individuals-create.component.css'], +}) +export class MonitoringIndividualsCreateComponent implements OnInit, OnDestroy { + currentUser: User; + + public individual: IIndividual; + + public moduleConfig; + public moduleCode; + public form: FormGroup; + + constructor( + private _auth: AuthService, + private _formService: FormService, + private _formBuilder: FormBuilder, + private _objService: ObjectService, + public _individualsService: IndividualsService, + private _configServiceG: ConfigServiceG, + private _route: ActivatedRoute + ) {} + + ngOnInit() { + // Initialisation des variables + this.moduleConfig = this._configServiceG.config(); + this.moduleCode = this._configServiceG.moduleCode(); + this.form = this._formBuilder.group({}); + this.currentUser = this._auth.getCurrentUser(); + this._individualsService.initConfig(); + this.individual = {} as IIndividual; + + // breadcrumb + const queryParams = this._route.snapshot.queryParams; + this._objService.loadBreadCrumb(this.moduleCode, 'individual', null, queryParams); + + // Passage en mode édition + this._formService.changeCurrentEditMode(true); + } + + ngOnDestroy() { + this._formService.changeCurrentEditMode(false); + } +} diff --git a/frontend/app/components/monitoring-individuals-detail/monitoring-individuals-detail.component.css b/frontend/app/components/monitoring-individuals-detail/monitoring-individuals-detail.component.css new file mode 100644 index 000000000..976cb071d --- /dev/null +++ b/frontend/app/components/monitoring-individuals-detail/monitoring-individuals-detail.component.css @@ -0,0 +1,9 @@ +.obj-title { + font-size: 1.75rem; + padding: 5px 15px; +} + +:host ::ng-deep .obj-description { + font-size: 2rem; + font-weight: bold; +} diff --git a/frontend/app/components/monitoring-individuals-detail/monitoring-individuals-detail.component.html b/frontend/app/components/monitoring-individuals-detail/monitoring-individuals-detail.component.html new file mode 100644 index 000000000..46607637f --- /dev/null +++ b/frontend/app/components/monitoring-individuals-detail/monitoring-individuals-detail.component.html @@ -0,0 +1,19 @@ + +
+ + {{ 'Actions.Edit' | translate }} + + + {{ 'Detail' | translate }} + + + {{ objectDataResolved.individual_name }} + +
+ + + diff --git a/frontend/app/components/monitoring-individuals-detail/monitoring-individuals-detail.component.ts b/frontend/app/components/monitoring-individuals-detail/monitoring-individuals-detail.component.ts new file mode 100644 index 000000000..bc4aa232e --- /dev/null +++ b/frontend/app/components/monitoring-individuals-detail/monitoring-individuals-detail.component.ts @@ -0,0 +1,117 @@ +import { Component, OnInit, EventEmitter } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { MonitoringGeomComponent } from '../../class/monitoring-geom-component'; +import { Popup } from '../../utils/popup'; +import { FormGroup, FormBuilder } from '@angular/forms'; +import { IndividualsService } from '../../services/api-geom.service'; +import { ObjectService } from '../../services/object.service'; +import { FormService } from '../../services/form.service'; +import { AuthService } from '@geonature/components/auth/auth.service'; +import { PermissionService } from '../../services/permission.service'; +import { resolveObjectProperties } from '../../utils/utils'; +import { CacheService } from '../../services/cache.service'; + +@Component({ + selector: 'monitoring-individuals-detail', + templateUrl: './monitoring-individuals-detail.component.html', + styleUrls: ['./monitoring-individuals-detail.component.css'], +}) +export class MonitoringIndividualsDetailComponent extends MonitoringGeomComponent implements OnInit { + private moduleCode: string; + public moduleConfig; + public currentUser; + public objectType: string = 'individual'; + private checkEditParam: boolean = false; + public form: FormGroup; + private dataId: number; + public objectData: any; + public objectDataResolved: any; + public bEdit: boolean = false; + + bDeleteModalEmitter = new EventEmitter(); + + constructor( + private _auth: AuthService, + public _individualsService: IndividualsService, + private _objService: ObjectService, + private _Activatedroute: ActivatedRoute, + private _formBuilder: FormBuilder, + private _formService: FormService, + public _permissionService: PermissionService, + public _popup: Popup, + private _cacheService: CacheService + ) { + super(_permissionService, _popup); + this.getAllItemsCallback = undefined; + } + + ngOnInit() { + this._formService.currentEditMode.subscribe((bEdit: boolean) => { + // Permet d'identifier si l'objet est passé en mode édition + // si c'est le cas, on refresh les données de l'objet + this.onbEditChange(bEdit); + this.bEdit = bEdit; + }); + // Initialisation des variables config + this.moduleCode = this._configServiceG.moduleCode(); + this.moduleConfig = this._configServiceG.config(); + this.currentUser = this._auth.getCurrentUser(); + // Création d'un objet form + this.form = this._formBuilder.group({}); + + this._individualsService.initConfig(); + this._permissionService.setPermissionMonitorings(this.moduleCode); + + // Récupération des paramètres de la route + this._Activatedroute.params.subscribe((params) => { + this.dataId = params['id']; + this.baseFilters = { id_individual: this.dataId }; + + // breadcrumb + const queryParams = this._Activatedroute.snapshot.queryParams; + this.parentPath = queryParams['parents_path']; + this.checkEditParam = JSON.parse(queryParams?.edit || 'false'); + if (this.checkEditParam === true) { + this.bEdit = true; + this._formService.changeCurrentEditMode(this.bEdit); + } + // Initialisation des données + this.initData(); + this._objService.loadBreadCrumb(this.moduleCode, this.objectType, this.dataId, queryParams); + + this.setTemplateData(this.objectType); + }); + } + + initData() { + // Get data detail + const fieldsConfig = this._configServiceG.config()[this.objectType]['fields']; + this._individualsService.getById(this.dataId, this.moduleCode).subscribe((detailData) => { + this.objectData = detailData; + + // Resolve data + resolveObjectProperties( + detailData, + fieldsConfig, + this._configServiceG, + this._cacheService + ).subscribe((data) => { + this.objectDataResolved = data; + }); + }); + } + + onbEditChange(event: boolean) { + if (this.bEdit == true && event == false) { + // Passage du mode édition au mode consultation + this.initData(); + } + } + + onDelete($event) { + this._individualsService.delete($event.rowSelected.id_individual).subscribe((del) => { + this.bDeleteModalEmitter.emit(false); + this.initData(); + }); + } +} diff --git a/frontend/app/components/monitoring-sitesgroups/monitoring-sitesgroups.component.ts b/frontend/app/components/monitoring-sitesgroups/monitoring-sitesgroups.component.ts index 8f683f756..ca46c0cf9 100644 --- a/frontend/app/components/monitoring-sitesgroups/monitoring-sitesgroups.component.ts +++ b/frontend/app/components/monitoring-sitesgroups/monitoring-sitesgroups.component.ts @@ -317,17 +317,10 @@ export class MonitoringSitesGroupsComponent extends MonitoringGeomComponent impl const queryParams = { parents_path: ['module'], }; - if (type == 'individual') { - // Patch individual tant que la page détail des individus est générique - queryParams['parents_path'] = ['module', 'individual']; - this.router.navigate([`/monitorings/create_object/${this.moduleCode}/`, type], { - queryParams: queryParams, - }); - } else { - this.router.navigate([`/monitorings/object/${this.moduleCode}/`, type, 'create'], { - queryParams: queryParams, - }); - } + + this.router.navigate([`/monitorings/object/${this.moduleCode}/`, type, 'create'], { + queryParams: queryParams, + }); } onDelete(event) { diff --git a/frontend/app/gnModule.module.ts b/frontend/app/gnModule.module.ts index 4921f9ed1..a65ea4466 100644 --- a/frontend/app/gnModule.module.ts +++ b/frontend/app/gnModule.module.ts @@ -86,6 +86,9 @@ import { ImportButtonComponent } from '@geonature/shared/importButton/import-but import { MonitoringObservationsDetailCreateComponent } from './components/monitoring-observationsdetails-create/monitoring-observationsdetail-create.component'; import { MonitoringObservationsDetailDetailComponent } from './components/monitoring-observationsdetail-detail/monitoring-observationsdetail-detail.component'; import { DetailObservationsDetailResolver } from './resolver/detail-observationsdetail-resolver'; +import { MonitoringIndividualsCreateComponent } from './components/monitoring-individuals-create/monitoring-individuals-create.component'; +import { MonitoringIndividualsDetailComponent } from './components/monitoring-individuals-detail/monitoring-individuals-detail.component'; +import { DetailIndividualsResolver } from './resolver/detail-individuals-resolver'; const routes: Routes = [ { path: '', component: ModulesComponent }, @@ -242,6 +245,20 @@ const routes: Routes = [ }, runGuardsAndResolvers: 'always', }, + { + path: 'create', + component: MonitoringIndividualsCreateComponent, + resolve: { + createSite: DetailIndividualsResolver, + }, + }, + { + path: ':id', + component: MonitoringIndividualsDetailComponent, + resolve: { + detailSites: DetailIndividualsResolver, + }, + }, ], }, ], @@ -288,6 +305,8 @@ export function createTranslateLoader(http: HttpClient, config: cs) { MonitoringObservationsCreateComponent, MonitoringObservationsDetailCreateComponent, MonitoringObservationsDetailDetailComponent, + MonitoringIndividualsCreateComponent, + MonitoringIndividualsDetailComponent, MonitoringDatatableGComponent, MonitoringPropertiesGComponent, MonitoringSitesGroupsCreateComponent, diff --git a/frontend/app/resolver/detail-individuals-resolver.ts b/frontend/app/resolver/detail-individuals-resolver.ts new file mode 100644 index 000000000..f8ca46736 --- /dev/null +++ b/frontend/app/resolver/detail-individuals-resolver.ts @@ -0,0 +1,13 @@ +import { Injectable } from '@angular/core'; +import { Resolve, ActivatedRouteSnapshot } from '@angular/router'; +import { ConfigServiceG } from '../services/config-g.service'; +import { Observable, of } from 'rxjs'; + +@Injectable({ providedIn: 'root' }) +export class DetailIndividualsResolver implements Resolve<{ moduleCode: string }> { + constructor(private _configServiceG: ConfigServiceG) {} + + resolve(route: ActivatedRouteSnapshot): Observable<{ moduleCode: string }> { + return of({ moduleCode: route.params.moduleCode }); + } +} From 9dbad24cb8d5df82d4f0b1fac78fcc190fe45677 Mon Sep 17 00:00:00 2001 From: jacquesfize Date: Tue, 28 Jul 2026 13:38:34 +0200 Subject: [PATCH 5/6] fix: overide delete for individual --- frontend/app/services/api-geom.service.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frontend/app/services/api-geom.service.ts b/frontend/app/services/api-geom.service.ts index 79a2708ed..e0a381734 100644 --- a/frontend/app/services/api-geom.service.ts +++ b/frontend/app/services/api-geom.service.ts @@ -381,6 +381,12 @@ export class IndividualsService extends ApiService { }; super.init(endPoint, objectObs); } + + delete(id: number, params: JsonData = {}): Observable { + return this._cacheService.request('delete', `${this.objectObs.endPoint}/${id}`, { + queryParams: params, + }); + } } @Injectable() From 823d39827ec357b330526aea10b932c7eedd316a Mon Sep 17 00:00:00 2001 From: jacquesfize Date: Tue, 28 Jul 2026 13:42:06 +0200 Subject: [PATCH 6/6] fix lint --- ...nitoring-individuals-detail.component.html | 42 ++++++++++++++----- ...monitoring-individuals-detail.component.ts | 5 ++- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/frontend/app/components/monitoring-individuals-detail/monitoring-individuals-detail.component.html b/frontend/app/components/monitoring-individuals-detail/monitoring-individuals-detail.component.html index 46607637f..0d0b5ad30 100644 --- a/frontend/app/components/monitoring-individuals-detail/monitoring-individuals-detail.component.html +++ b/frontend/app/components/monitoring-individuals-detail/monitoring-individuals-detail.component.html @@ -1,19 +1,39 @@ -
- + {{ 'Actions.Edit' | translate }} - + {{ 'Detail' | translate }} - + {{ objectDataResolved.individual_name }}
- - - + + diff --git a/frontend/app/components/monitoring-individuals-detail/monitoring-individuals-detail.component.ts b/frontend/app/components/monitoring-individuals-detail/monitoring-individuals-detail.component.ts index bc4aa232e..ace0ca598 100644 --- a/frontend/app/components/monitoring-individuals-detail/monitoring-individuals-detail.component.ts +++ b/frontend/app/components/monitoring-individuals-detail/monitoring-individuals-detail.component.ts @@ -16,7 +16,10 @@ import { CacheService } from '../../services/cache.service'; templateUrl: './monitoring-individuals-detail.component.html', styleUrls: ['./monitoring-individuals-detail.component.css'], }) -export class MonitoringIndividualsDetailComponent extends MonitoringGeomComponent implements OnInit { +export class MonitoringIndividualsDetailComponent + extends MonitoringGeomComponent + implements OnInit +{ private moduleCode: string; public moduleConfig; public currentUser;