loko/loko/assets/tests_import_fulcrum.py
kdeterme af411b2105 feat(nature): create timeline planning events and display due date on asset interventions
- Create InterventionPlanificationTimeLine events (expected_begin and expected_end)
  during Fulcrum tree interventions import and maintenance dates synchronization
- Display planned/completion date instead of creation_time in asset_interventions.html
- Add unit test assertions for planning timeline events
2026-10-10 14:09:15 +02:00

237 lines
10 KiB
Python

import json
import tempfile
import os
from datetime import date
from django.test import TestCase
from django.core.management import call_command
from django.contrib.gis.geos import Point
from common.models import Thematic
from assets.models import NatureTree, AssetCategory
from contracts.models import Contract, ContractOrder, Company
from interventions.models import Intervention, InterventionAsset
class ImportFulcrumTreesCommandTest(TestCase):
def setUp(self):
self.thematic = Thematic.objects.create(name_fr="Nature", name_nl="Natuur")
self.category = AssetCategory.objects.create(id=60, name_fr="Arbres", name_nl="Bomen")
self.company = Company.objects.create(name="Krinkels")
self.contract = Contract.objects.create(
company=self.company,
contract_number="E19.051",
description="Marché E19.051",
start_date=date(2020, 1, 1),
end_date=date(2030, 12, 31)
)
self.contract.thematics.add(self.thematic)
def test_import_fulcrum_trees_and_interventions(self):
# 1. Préparer gestion.csv temporaire
gestion_content = (
"com_id,com_typ,com_nom,_status,com_dat,com_ech\n"
"BC-TEST-001,Marché E19.051 - Diagnostic,Élagage test,commande,2024-05-10,2024-06-15\n"
)
with tempfile.NamedTemporaryFile(mode='w', suffix='.csv', delete=False) as f_ges:
f_ges.write(gestion_content)
ges_path = f_ges.name
# 2. Préparer arbes.geojson temporaire
mock_tree_uuid = "11111111-2222-3333-4444-555555555555"
tree_geojson = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [4.3517, 50.8503]
},
"properties": {
"_record_id": mock_tree_uuid,
"id_sit_pos": "099.01.001",
"id_visuel": "V-9901",
"sit_id": "099.01",
"pos_id": "001",
"sit_nom": "Boulevard du Test",
"comm_commune": "Bruxelles",
"arb_ess": "Platanus x hispanica",
"arb_vern": "Platane commun",
"arb_tax": "Platanus hispanica",
"arb_haut": 18.5,
"arb_circ": 140.0,
"arb_diam_tmp": 8.0,
"arb_st": "V",
"arb_vital": "Bo",
"arb_ont": "Ad",
"ges_conduite": "LI",
"cf_sit": 4.5,
"arb_anpla": "1995",
"maj_date": "2024-05-10T00:00:00.000Z"
}
}
]
}
with tempfile.NamedTemporaryFile(mode='w', suffix='.geojson', delete=False) as f_arb:
json.dump(tree_geojson, f_arb)
arb_path = f_arb.name
# 3. Préparer interventions.geojson temporaire
mock_itv_uuid = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
itv_geojson = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [4.3517, 50.8503]
},
"properties": {
"_record_id": mock_itv_uuid,
"_title": "099.01.001, Formation, Dans 1 an",
"rec_interv_com": "Taille branches basses",
"sit_nom": "Boulevard du Test",
"comm_commune": "Bruxelles",
"sit_id": "099.01",
"pos_id": "001",
"dia_comma": "BC-TEST-001",
"dia_date": "2024-05-10T00:00:00.000Z",
"in_deadline": "2025-05-10T00:00:00.000Z",
"rec_delai": "Pla-1",
"rec_interv": "FO",
"in_fin_yn": "no",
"mater_arbres_lk": [mock_tree_uuid]
}
}
]
}
with tempfile.NamedTemporaryFile(mode='w', suffix='.geojson', delete=False) as f_itv:
json.dump(itv_geojson, f_itv)
itv_path = f_itv.name
try:
# Exécuter la commande
call_command(
'import_fulcrum_trees',
arbes_file=arb_path,
gestion_file=ges_path,
interventions_file=itv_path,
managing_authority="Test Authority",
management_type="regional",
code_prefix="TEST-ARB-"
)
# Vérifications gestion / bon de commande
order = ContractOrder.objects.filter(order_code="BC-TEST-001").first()
self.assertIsNotNone(order)
self.assertEqual(order.contract.contract_number, "E19.051")
self.assertEqual(order.order_status, "sent")
# Vérifications arbre
tree = NatureTree.objects.filter(external_reference=mock_tree_uuid).first()
self.assertIsNotNone(tree)
self.assertEqual(tree.code, "TEST-ARB-099.01.001")
self.assertEqual(tree.managing_authority, "Test Authority")
self.assertEqual(tree.management_type, "regional")
self.assertEqual(tree.circumference, 140.0)
self.assertEqual(tree.height, 18.5)
self.assertIsNotNone(tree.amenity_value)
self.assertGreater(tree.amenity_value, 0)
self.assertIsNotNone(tree.carbon_stock)
self.assertGreater(tree.carbon_stock, 0)
self.assertIsNotNone(tree.cooling_indicator)
# Vérifications intervention
itv = Intervention.objects.filter(source_ref=mock_itv_uuid).first()
self.assertIsNotNone(itv)
self.assertEqual(itv.order_id, order.id)
self.assertEqual(itv.priority, "3") # Pla-1 -> 3
self.assertEqual(itv.status, "to_be_planned")
self.assertIsNotNone(itv.expected_begin_time)
self.assertEqual(itv.expected_begin_time.date(), date(2024, 5, 10))
self.assertIsNotNone(itv.expected_end_time)
self.assertEqual(itv.expected_end_time.date(), date(2025, 5, 10))
# Vérifications dates de maintenance sur l'arbre
self.assertEqual(tree.last_phytosanitary_date, date(2024, 5, 10))
self.assertEqual(tree.next_inspection_date, date(2025, 5, 10))
self.assertEqual(tree.pruning_frequency_years, 1)
# Vérifications événements de planification (timeline)
self.assertEqual(itv.planned_events.count(), 2)
begin_event = itv.planned_events.filter(event_type='expected_begin').first()
self.assertIsNotNone(begin_event)
self.assertEqual(begin_event.event_time.date(), date(2024, 5, 10))
end_event = itv.planned_events.filter(event_type='expected_end').first()
self.assertIsNotNone(end_event)
self.assertEqual(end_event.event_time.date(), date(2025, 5, 10))
# Vérification liaison InterventionAsset
links = InterventionAsset.objects.filter(intervention=itv)
self.assertEqual(links.count(), 1)
self.assertEqual(links.first().object_id, tree.id)
# Test mise à jour sans duplication
tree_geojson['features'][0]['properties']['arb_circ'] = 145.0
with open(arb_path, 'w') as f_arb_update:
json.dump(tree_geojson, f_arb_update)
call_command(
'import_fulcrum_trees',
arbes_file=arb_path,
gestion_file=ges_path,
interventions_file=itv_path,
skip_gestion=True,
skip_interventions=True
)
# Le nombre d'arbres ne doit pas avoir augmenté
self.assertEqual(NatureTree.objects.filter(external_reference=mock_tree_uuid).count(), 1)
tree.refresh_from_db()
self.assertEqual(tree.circumference, 145.0)
finally:
if os.path.exists(ges_path): os.remove(ges_path)
if os.path.exists(arb_path): os.remove(arb_path)
if os.path.exists(itv_path): os.remove(itv_path)
def test_import_fulcrum_regional_trees_alias(self):
from io import StringIO
out = StringIO()
call_command('import_fulcrum_regional_trees', skip_trees=True, skip_gestion=True, skip_interventions=True, stdout=out)
self.assertIn("FIN DE L'IMPORTATION", out.getvalue())
def test_attach_trees_to_green_sections(self):
from assets.models import NatureLocation
from django.contrib.gis.geos import Polygon, MultiPolygon
# Créer une section verte polygonale
poly = Polygon(((648000, 670000), (648100, 670000), (648100, 670100), (648000, 670100), (648000, 670000)), srid=3812)
loc = NatureLocation.objects.create(
code="WAT_R22_008",
name_fr="Boulevard du Souverain",
geom=MultiPolygon(poly, srid=3812)
)
# Créer un arbre à l'intérieur du polygone (sans location)
pt = Point(648050, 670050, srid=3812)
pt_4326 = pt.clone().transform(4326, clone=True)
tree = NatureTree.objects.create(
code="TR-ATTACH-TEST",
name_fr="Arbre test Souverain",
category=self.category,
geom=pt,
lon=pt_4326.x,
lat=pt_4326.y,
location=None
)
self.assertEqual(loc.nature_trees.count(), 0)
# Exécuter le rattachement
call_command('attach_trees_to_green_sections', max_distance=25, skip_sync_munis=True)
tree.refresh_from_db()
self.assertEqual(tree.location_id, loc.id)
self.assertEqual(loc.nature_trees.count(), 1)