diff --git a/docs/changelog.rst b/docs/changelog.rst index 2ff843d06f..bc823c28ca 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -22,6 +22,7 @@ CHANGELOG - Add missing translations for fields on ``Courses`` and ``Sites`` in APIv2 (#3569) - Allow Apidae Trek parser to handle traces not in utf-8 - Add workforce cost into intervention model (#3824) +- Add contractor to intervention model (#3820) 2.101.5 (2024-01-11) -------------------- diff --git a/geotrek/maintenance/filters.py b/geotrek/maintenance/filters.py index 6ad9c6451c..fa2081e468 100644 --- a/geotrek/maintenance/filters.py +++ b/geotrek/maintenance/filters.py @@ -151,7 +151,7 @@ class InterventionFilterSet(AltimetryInterventionFilterSet, ZoningFilterSet, Str class Meta(StructureRelatedFilterSet.Meta): model = Intervention fields = StructureRelatedFilterSet.Meta.fields + [ - 'status', 'type', 'stake', 'subcontracting', 'project', 'on', + 'status', 'type', 'stake', 'subcontracting', 'project', 'contractors', 'on', ] def __init__(self, *args, **kwargs): diff --git a/geotrek/maintenance/forms.py b/geotrek/maintenance/forms.py index 6af260f05d..dede6bc6b6 100644 --- a/geotrek/maintenance/forms.py +++ b/geotrek/maintenance/forms.py @@ -90,6 +90,7 @@ class InterventionForm(CommonForm): 'height', 'stake', 'project', + 'contractors', 'access', 'description', 'material_cost', @@ -105,7 +106,7 @@ class Meta(CommonForm.Meta): model = Intervention fields = CommonForm.Meta.fields + \ ['structure', 'name', 'begin_date', 'end_date', 'status', 'disorders', 'type', 'description', 'subcontracting', 'length', 'width', - 'height', 'stake', 'project', 'access', 'material_cost', 'heliport_cost', 'contractor_cost', 'workforce_cost', 'topology'] + 'height', 'stake', 'project', 'contractors', 'access', 'material_cost', 'heliport_cost', 'contractor_cost', 'workforce_cost', 'topology'] def __init__(self, *args, target_type=None, target_id=None, **kwargs): super().__init__(*args, **kwargs) @@ -205,9 +206,7 @@ class ProjectForm(CommonForm): 'constraint', 'global_cost', 'comments', - - css_class="span6"), - Div('project_owner', + 'project_owner', 'project_manager', 'contractors', Fieldset(_("Fundings")), diff --git a/geotrek/maintenance/locale/de/LC_MESSAGES/django.po b/geotrek/maintenance/locale/de/LC_MESSAGES/django.po index dc111f7130..88b28167c4 100644 --- a/geotrek/maintenance/locale/de/LC_MESSAGES/django.po +++ b/geotrek/maintenance/locale/de/LC_MESSAGES/django.po @@ -164,6 +164,9 @@ msgstr "" msgid "Workforce cost" msgstr "" +msgid "Contractors" +msgstr "" + msgid "3D Length" msgstr "" @@ -269,9 +272,6 @@ msgstr "" msgid "Domain" msgstr "" -msgid "Contractors" -msgstr "" - msgid "Project owner" msgstr "" @@ -284,6 +284,9 @@ msgstr "" msgid "Projects" msgstr "" +msgid "Intervention contractors" +msgstr "" + msgid "Period" msgstr "" diff --git a/geotrek/maintenance/locale/en/LC_MESSAGES/django.po b/geotrek/maintenance/locale/en/LC_MESSAGES/django.po index dc111f7130..88b28167c4 100644 --- a/geotrek/maintenance/locale/en/LC_MESSAGES/django.po +++ b/geotrek/maintenance/locale/en/LC_MESSAGES/django.po @@ -164,6 +164,9 @@ msgstr "" msgid "Workforce cost" msgstr "" +msgid "Contractors" +msgstr "" + msgid "3D Length" msgstr "" @@ -269,9 +272,6 @@ msgstr "" msgid "Domain" msgstr "" -msgid "Contractors" -msgstr "" - msgid "Project owner" msgstr "" @@ -284,6 +284,9 @@ msgstr "" msgid "Projects" msgstr "" +msgid "Intervention contractors" +msgstr "" + msgid "Period" msgstr "" diff --git a/geotrek/maintenance/locale/es/LC_MESSAGES/django.po b/geotrek/maintenance/locale/es/LC_MESSAGES/django.po index dc111f7130..88b28167c4 100644 --- a/geotrek/maintenance/locale/es/LC_MESSAGES/django.po +++ b/geotrek/maintenance/locale/es/LC_MESSAGES/django.po @@ -164,6 +164,9 @@ msgstr "" msgid "Workforce cost" msgstr "" +msgid "Contractors" +msgstr "" + msgid "3D Length" msgstr "" @@ -269,9 +272,6 @@ msgstr "" msgid "Domain" msgstr "" -msgid "Contractors" -msgstr "" - msgid "Project owner" msgstr "" @@ -284,6 +284,9 @@ msgstr "" msgid "Projects" msgstr "" +msgid "Intervention contractors" +msgstr "" + msgid "Period" msgstr "" diff --git a/geotrek/maintenance/locale/fr/LC_MESSAGES/django.po b/geotrek/maintenance/locale/fr/LC_MESSAGES/django.po index 6d2afc1e59..d76e24be9f 100644 --- a/geotrek/maintenance/locale/fr/LC_MESSAGES/django.po +++ b/geotrek/maintenance/locale/fr/LC_MESSAGES/django.po @@ -166,6 +166,9 @@ msgstr "Coût prestataire" msgid "Workforce cost" msgstr "Coût main d'oeuvre" +msgid "Contractors" +msgstr "Prestataires" + msgid "3D Length" msgstr "Longueur 3D" @@ -271,9 +274,6 @@ msgstr "Commentaires" msgid "Domain" msgstr "Domaine" -msgid "Contractors" -msgstr "Prestataires" - msgid "Project owner" msgstr "Maître d'ouvrage" @@ -286,6 +286,9 @@ msgstr "Financeurs" msgid "Projects" msgstr "Chantiers" +msgid "Intervention contractors" +msgstr "Prestataires des interventions" + msgid "Period" msgstr "Période" diff --git a/geotrek/maintenance/locale/it/LC_MESSAGES/django.po b/geotrek/maintenance/locale/it/LC_MESSAGES/django.po index dc111f7130..88b28167c4 100644 --- a/geotrek/maintenance/locale/it/LC_MESSAGES/django.po +++ b/geotrek/maintenance/locale/it/LC_MESSAGES/django.po @@ -164,6 +164,9 @@ msgstr "" msgid "Workforce cost" msgstr "" +msgid "Contractors" +msgstr "" + msgid "3D Length" msgstr "" @@ -269,9 +272,6 @@ msgstr "" msgid "Domain" msgstr "" -msgid "Contractors" -msgstr "" - msgid "Project owner" msgstr "" @@ -284,6 +284,9 @@ msgstr "" msgid "Projects" msgstr "" +msgid "Intervention contractors" +msgstr "" + msgid "Period" msgstr "" diff --git a/geotrek/maintenance/locale/nl/LC_MESSAGES/django.po b/geotrek/maintenance/locale/nl/LC_MESSAGES/django.po index dc111f7130..88b28167c4 100644 --- a/geotrek/maintenance/locale/nl/LC_MESSAGES/django.po +++ b/geotrek/maintenance/locale/nl/LC_MESSAGES/django.po @@ -164,6 +164,9 @@ msgstr "" msgid "Workforce cost" msgstr "" +msgid "Contractors" +msgstr "" + msgid "3D Length" msgstr "" @@ -269,9 +272,6 @@ msgstr "" msgid "Domain" msgstr "" -msgid "Contractors" -msgstr "" - msgid "Project owner" msgstr "" @@ -284,6 +284,9 @@ msgstr "" msgid "Projects" msgstr "" +msgid "Intervention contractors" +msgstr "" + msgid "Period" msgstr "" diff --git a/geotrek/maintenance/migrations/0028_intervention_contractors.py b/geotrek/maintenance/migrations/0028_intervention_contractors.py new file mode 100644 index 0000000000..8b6a50a4fb --- /dev/null +++ b/geotrek/maintenance/migrations/0028_intervention_contractors.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.23 on 2024-02-05 09:27 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('maintenance', '0027_intervention_workforce_cost'), + ] + + operations = [ + migrations.AddField( + model_name='intervention', + name='contractors', + field=models.ManyToManyField(blank=True, related_name='interventions', to='maintenance.Contractor', verbose_name='Contractors'), + ), + ] diff --git a/geotrek/maintenance/models.py b/geotrek/maintenance/models.py index c305658037..f6e08c9d73 100755 --- a/geotrek/maintenance/models.py +++ b/geotrek/maintenance/models.py @@ -23,7 +23,7 @@ from geotrek.zoning.mixins import ZoningPropertiesMixin from mapentity.models import DuplicateMixin - +from django.contrib.postgres.aggregates import ArrayAgg if 'geotrek.signage' in settings.INSTALLED_APPS: from geotrek.signage.models import Blade @@ -51,6 +51,8 @@ class Intervention(ZoningPropertiesMixin, AddPropertyMixin, GeotrekMapEntityMixi heliport_cost = models.FloatField(default=0.0, blank=True, null=True, verbose_name=_("Heliport cost")) contractor_cost = models.FloatField(default=0.0, blank=True, null=True, verbose_name=_("Contractor cost")) workforce_cost = models.FloatField(default=0.0, blank=True, null=True, verbose_name=_("Workforce cost")) + contractors = models.ManyToManyField('Contractor', related_name="interventions", blank=True, + verbose_name=_("Contractors")) # AltimetryMixin for denormalized fields from related topology, updated via trigger. length = models.FloatField(editable=True, default=0.0, null=True, blank=True, verbose_name=_("3D Length")) @@ -551,8 +553,13 @@ def interventions_csv_display(self): return [str(i) for i in self.interventions.existing()] @property - def contractors_display(self): - return [str(c) for c in self.contractors.all()] + def intervention_contractors(self): + return self.interventions.aggregate( + intervention_contractors=ArrayAgg('contractors__contractor', distinct=True))['intervention_contractors'] + + @classproperty + def intervention_contractors_verbose_name(cls): + return _("Intervention contractors") @property def founders_display(self): diff --git a/geotrek/maintenance/templates/maintenance/intervention_detail.html b/geotrek/maintenance/templates/maintenance/intervention_detail.html index ee0291ee27..acf2f523c7 100644 --- a/geotrek/maintenance/templates/maintenance/intervention_detail.html +++ b/geotrek/maintenance/templates/maintenance/intervention_detail.html @@ -99,6 +99,12 @@ {% else %} {% trans "None" %} {% endif %} + + + {{ intervention|verbose:"contractors" }} + + {% valuelist intervention.contractors.all %} + {% trans "Related object" %} diff --git a/geotrek/maintenance/templates/maintenance/project_detail.html b/geotrek/maintenance/templates/maintenance/project_detail.html index 9be09c10f0..db3a89b083 100644 --- a/geotrek/maintenance/templates/maintenance/project_detail.html +++ b/geotrek/maintenance/templates/maintenance/project_detail.html @@ -59,6 +59,12 @@ {% valuelist project.contractors.all %} + + {{ project|verbose:"intervention_contractors" }} + + {% valuelist project.intervention_contractors %} + + {% trans "Fundings" %} diff --git a/geotrek/maintenance/templates/maintenance/sql/post_20_views.sql b/geotrek/maintenance/templates/maintenance/sql/post_20_views.sql index 23d322e59b..7e51d5e8a1 100644 --- a/geotrek/maintenance/templates/maintenance/sql/post_20_views.sql +++ b/geotrek/maintenance/templates/maintenance/sql/post_20_views.sql @@ -26,6 +26,7 @@ SELECT a.id, j.nb_days AS "Cost", (a.material_cost+a.heliport_cost+a.contractor_cost+a.workforce_cost)::float AS "Total cost", h.name AS "Project", + m.contractor AS "Contractors", CASE WHEN k.app_label = 'core' THEN 'Tronçons' WHEN k.app_label = 'infrastructure' THEN 'Aménagements' @@ -39,6 +40,13 @@ SELECT a.id, a.date_update AS "Update date", a.geom_3d FROM maintenance_intervention a +LEFT JOIN + (SELECT array_to_string(array_agg(a.contractor), ', '::text, '_'::text) contractor, + intervention_id + FROM maintenance_contractor a + JOIN maintenance_intervention_contractors b ON a.id = b.contractor_id + JOIN maintenance_intervention c ON b.intervention_id = c.id + GROUP BY intervention_id) m ON a.id = m.intervention_id LEFT JOIN maintenance_interventiontype b ON a.type_id = b.id LEFT JOIN maintenance_interventionstatus c ON a.status_id = c.id LEFT JOIN core_stake d ON a.stake_id = d.id diff --git a/geotrek/maintenance/tests/test_models.py b/geotrek/maintenance/tests/test_models.py index 8a3cebbc2f..2098641733 100644 --- a/geotrek/maintenance/tests/test_models.py +++ b/geotrek/maintenance/tests/test_models.py @@ -17,7 +17,7 @@ FundingFactory, InfrastructureInterventionFactory, InfrastructurePointInterventionFactory, InterventionDisorderFactory, InterventionFactory, InterventionJobFactory, ManDayFactory, ProjectFactory, - SignageInterventionFactory) + SignageInterventionFactory, ContractorFactory) from geotrek.outdoor.tests.factories import CourseFactory, SiteFactory from geotrek.signage.tests.factories import BladeFactory, SignageFactory @@ -362,3 +362,11 @@ def test_trails_property(self): project.interventions.add(intervention_blade) project.interventions.add(intervention_course) self.assertQuerysetEqual(list(project.trails), ['trail_1', 'trail_2', 'trail_signage'], ordered=False, transform=str) + + def test_intervention_contractors(self): + project = ProjectFactory.create() + contractor1 = ContractorFactory.create(contractor="contractor1") + contractor2 = ContractorFactory.create(contractor="contractor2") + intervention = InterventionFactory.create(project=project) + intervention.contractors.set([contractor1, contractor2]) + self.assertEqual(project.intervention_contractors, ["contractor1", "contractor2"]) diff --git a/geotrek/maintenance/tests/test_views.py b/geotrek/maintenance/tests/test_views.py index 636a250b6f..35ad943675 100644 --- a/geotrek/maintenance/tests/test_views.py +++ b/geotrek/maintenance/tests/test_views.py @@ -85,6 +85,7 @@ def get_good_data(self): 'stake': StakeFactory.create().pk, 'height': 0.0, 'project': '', + 'contractors': [], 'width': 0.0, 'length': 0.0, 'status': InterventionStatus.objects.all()[0].pk, @@ -528,6 +529,7 @@ def get_good_data(self): 'global_cost': '12', 'comments': '', 'contractors': ContractorFactory.create().pk, + 'intervention_contractors_verbose_name': [], 'project_owner': OrganismFactory.create().pk, 'project_manager': OrganismFactory.create().pk, diff --git a/geotrek/maintenance/views.py b/geotrek/maintenance/views.py index aebcab49e5..23f1ecf139 100755 --- a/geotrek/maintenance/views.py +++ b/geotrek/maintenance/views.py @@ -98,7 +98,7 @@ def get_mandatory_columns(cls): default_extra_columns = [ 'name', 'begin_date', 'end_date', 'type', 'target', 'status', 'stake', - 'disorders', 'total_manday', 'project', 'subcontracting', + 'disorders', 'total_manday', 'project', 'contractors', 'subcontracting', 'width', 'height', 'area', 'structure', 'description', 'date_insert', 'date_update', 'material_cost', 'heliport_cost', 'contractor_cost', 'workforce_cost', @@ -198,7 +198,7 @@ class ProjectFormatList(MapEntityFormat, ProjectList): mandatory_columns = ['id'] default_extra_columns = [ 'structure', 'name', 'period', 'type', 'domain', 'constraint', 'global_cost', - 'interventions', 'interventions_total_cost', 'comments', 'contractors', + 'interventions', 'interventions_total_cost', 'comments', 'contractors', 'intervention_contractors', 'project_owner', 'project_manager', 'founders', 'date_insert', 'date_update', 'cities', 'districts', 'areas',