feat: add external_reference field to assets with permission support, improve intervention contract assignment logic, and sanitize template strings.
This commit is contained in:
parent
4135c6ef47
commit
0b190526f8
9 changed files with 351 additions and 33 deletions
85
loko/assets/management/commands/sync_all_geojson.py
Normal file
85
loko/assets/management/commands/sync_all_geojson.py
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.apps import apps
|
||||||
|
from django.db import connection, transaction
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Synchronizes the geojson field (and lon/lat if missing) from geom for all assets/models using PostGIS SQL."
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
'--dry-run',
|
||||||
|
action='store_true',
|
||||||
|
help='Perform a dry run without modifying the database.',
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--only-missing',
|
||||||
|
action='store_true',
|
||||||
|
help='Only update records where geojson is null or empty.',
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
dry_run = options['dry_run']
|
||||||
|
only_missing = options['only_missing']
|
||||||
|
|
||||||
|
total_scanned = 0
|
||||||
|
total_updated = 0
|
||||||
|
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
for model in apps.get_models():
|
||||||
|
fields = [f.name for f in model._meta.get_fields()]
|
||||||
|
if 'geom' not in fields or 'geojson' not in fields:
|
||||||
|
continue
|
||||||
|
|
||||||
|
table = model._meta.db_table
|
||||||
|
is_json_field = model._meta.get_field('geojson').get_internal_type() == 'JSONField'
|
||||||
|
has_lon = 'lon' in fields
|
||||||
|
has_lat = 'lat' in fields
|
||||||
|
|
||||||
|
# Count total candidates with non-null geom
|
||||||
|
cursor.execute(f'SELECT COUNT(*) FROM "{table}" WHERE geom IS NOT NULL;')
|
||||||
|
count = cursor.fetchone()[0]
|
||||||
|
if count == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.stdout.write(f"Processing {model._meta.app_label}.{model.__name__} ({table}, {count} candidate records)...")
|
||||||
|
|
||||||
|
if only_missing:
|
||||||
|
where_clause = "geom IS NOT NULL AND (geojson IS NULL OR geojson::text = '' OR geojson::text = 'null')"
|
||||||
|
else:
|
||||||
|
where_clause = "geom IS NOT NULL"
|
||||||
|
|
||||||
|
cursor.execute(f'SELECT COUNT(*) FROM "{table}" WHERE {where_clause};')
|
||||||
|
records_to_update = cursor.fetchone()[0]
|
||||||
|
|
||||||
|
if records_to_update == 0:
|
||||||
|
self.stdout.write(self.style.SUCCESS(f" {model.__name__}: 0 records need update."))
|
||||||
|
continue
|
||||||
|
|
||||||
|
if dry_run:
|
||||||
|
self.stdout.write(self.style.SUCCESS(f" [DRY-RUN] {model.__name__}: Would update {records_to_update}/{count} records."))
|
||||||
|
total_scanned += count
|
||||||
|
total_updated += records_to_update
|
||||||
|
continue
|
||||||
|
|
||||||
|
cast_geojson = "ST_AsGeoJSON(ST_Transform(geom, 4326))::jsonb" if is_json_field else "ST_AsGeoJSON(ST_Transform(geom, 4326))"
|
||||||
|
|
||||||
|
set_clauses = [f"geojson = {cast_geojson}"]
|
||||||
|
if has_lon:
|
||||||
|
set_clauses.append("lon = COALESCE(lon, ST_X(ST_PointOnSurface(ST_Transform(geom, 4326))))")
|
||||||
|
if has_lat:
|
||||||
|
set_clauses.append("lat = COALESCE(lat, ST_Y(ST_PointOnSurface(ST_Transform(geom, 4326))))")
|
||||||
|
|
||||||
|
set_str = ", ".join(set_clauses)
|
||||||
|
update_sql = f'UPDATE "{table}" SET {set_str} WHERE {where_clause};'
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
cursor.execute(update_sql)
|
||||||
|
updated_count = cursor.rowcount
|
||||||
|
|
||||||
|
self.stdout.write(self.style.SUCCESS(f" {model.__name__}: {updated_count}/{count} records updated in DB."))
|
||||||
|
total_scanned += count
|
||||||
|
total_updated += updated_count
|
||||||
|
|
||||||
|
prefix = "[DRY-RUN] Would update" if dry_run else "Successfully updated"
|
||||||
|
self.stdout.write(self.style.SUCCESS(f"\n{prefix} {total_updated} / {total_scanned} records across all models."))
|
||||||
|
|
@ -10,6 +10,7 @@ def sync_parent_data(apps, schema_editor):
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
atomic = False
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('assets', '0020_trafficlightlantern_geojson_trafficlightlantern_geom_and_more'),
|
('assets', '0020_trafficlightlantern_geojson_trafficlightlantern_geom_and_more'),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,213 @@
|
||||||
|
# Generated by Django 6.0.7 on 2026-08-26 13:10
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('assets', '0024_structureexceptionaltransport_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='artworkfountain',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='artworkmonument',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='buildingasset',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='cleanglasscontainer',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='cleanlitterbin',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='controlcentergeoasset',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='itsgeoasset',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='naturebench',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='naturegreensurface',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='naturerwiasset',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='natureshrub',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='naturetree',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='outdoorasset',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='parkingspot',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='publiclightingelectricalcabinet',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='publiclightinglightfixture',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='publiclightingpole',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='roadconcreteblockset',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='roaddrain',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='roadfurniture',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='roadsidewalk',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='roadway',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='signpanel',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='signparkingspot',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='signpole',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='structuregeoasset',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='trafficlightaccessory',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='trafficlightcable',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='trafficlightcontroller',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='trafficlightcontrollerhardware',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='trafficlightdetector',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='trafficlightelectricalcabinet',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='trafficlightelectricalcabinetcontent',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='trafficlightlantern',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='trafficlightnetworkhardware',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='trafficlightpole',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='trafficlightprogramming',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='trafficlightradar',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='trafficlightradarpole',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='trafficlightvoltagehardware',
|
||||||
|
name='external_reference',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Référence externe'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -268,6 +268,7 @@ class AbstractAsset(models.Model):
|
||||||
code = models.CharField(max_length=50)
|
code = models.CharField(max_length=50)
|
||||||
name_fr = models.CharField(max_length=200,null=True,blank=True)
|
name_fr = models.CharField(max_length=200,null=True,blank=True)
|
||||||
name_nl = models.CharField(max_length=200,null=True,blank=True)
|
name_nl = models.CharField(max_length=200,null=True,blank=True)
|
||||||
|
external_reference = models.CharField(max_length=100, null=True, blank=True, verbose_name=_("Référence externe"))
|
||||||
status = models.CharField(choices=ASSET_STATUS_CHOICES, default='active', max_length=50)
|
status = models.CharField(choices=ASSET_STATUS_CHOICES, default='active', max_length=50)
|
||||||
obsolescence = models.CharField(choices=ASSET_OBSOLESCENCE_CHOICES, default='unknown', null=True, blank=True)
|
obsolescence = models.CharField(choices=ASSET_OBSOLESCENCE_CHOICES, default='unknown', null=True, blank=True)
|
||||||
installation_date = models.DateTimeField(null=True,blank=True)
|
installation_date = models.DateTimeField(null=True,blank=True)
|
||||||
|
|
|
||||||
|
|
@ -14,32 +14,32 @@ from common.models import UserConfig, UserThematics, UserAssetAccess, UserAssetT
|
||||||
# Fields editable by role for assets
|
# Fields editable by role for assets
|
||||||
ASSET_EDITABLE_FIELDS_BY_ROLE = {
|
ASSET_EDITABLE_FIELDS_BY_ROLE = {
|
||||||
'admin': [
|
'admin': [
|
||||||
'code', 'name_fr', 'name_nl', 'status', 'model', 'category',
|
'code', 'external_reference', 'name_fr', 'name_nl', 'status', 'model', 'category',
|
||||||
'serial_number', 'installation_date', 'brand', 'warranty_duration',
|
'serial_number', 'installation_date', 'brand', 'warranty_duration',
|
||||||
'last_inspection_date', 'funding_program', 'geom', 'lon', 'lat',
|
'last_inspection_date', 'funding_program', 'geom', 'lon', 'lat',
|
||||||
'location_id', 'intersection_id',
|
'location_id', 'intersection_id',
|
||||||
],
|
],
|
||||||
'manager': [
|
'manager': [
|
||||||
'code', 'name_fr', 'name_nl', 'status', 'model', 'category',
|
'code', 'external_reference', 'name_fr', 'name_nl', 'status', 'model', 'category',
|
||||||
'serial_number', 'installation_date', 'brand', 'warranty_duration',
|
'serial_number', 'installation_date', 'brand', 'warranty_duration',
|
||||||
'last_inspection_date', 'funding_program', 'geom', 'lon', 'lat',
|
'last_inspection_date', 'funding_program', 'geom', 'lon', 'lat',
|
||||||
'location_id', 'intersection_id',
|
'location_id', 'intersection_id',
|
||||||
],
|
],
|
||||||
'controller': [
|
'controller': [
|
||||||
'code', 'name_fr', 'name_nl', 'status', 'model', 'category',
|
'code', 'external_reference', 'name_fr', 'name_nl', 'status', 'model', 'category',
|
||||||
'serial_number', 'installation_date', 'brand', 'warranty_duration',
|
'serial_number', 'installation_date', 'brand', 'warranty_duration',
|
||||||
'last_inspection_date', 'funding_program', 'geom', 'lon', 'lat',
|
'last_inspection_date', 'funding_program', 'geom', 'lon', 'lat',
|
||||||
'location_id', 'intersection_id',
|
'location_id', 'intersection_id',
|
||||||
],
|
],
|
||||||
'external_manager': [
|
'external_manager': [
|
||||||
'name_fr', 'name_nl', 'category', 'brand', 'serial_number', 'funding_program'
|
'name_fr', 'name_nl', 'external_reference', 'category', 'brand', 'serial_number', 'funding_program'
|
||||||
],
|
],
|
||||||
'technician': ['name_fr', 'name_nl', 'category', 'brand', 'serial_number', 'funding_program'],
|
'technician': ['name_fr', 'name_nl', 'external_reference', 'category', 'brand', 'serial_number', 'funding_program'],
|
||||||
'operator': [],
|
'operator': [],
|
||||||
'observer': [],
|
'observer': [],
|
||||||
'viewer': [],
|
'viewer': [],
|
||||||
'editor': [
|
'editor': [
|
||||||
'code', 'name_fr', 'name_nl', 'status', 'model', 'category',
|
'code', 'external_reference', 'name_fr', 'name_nl', 'status', 'model', 'category',
|
||||||
'serial_number', 'installation_date', 'brand', 'warranty_duration',
|
'serial_number', 'installation_date', 'brand', 'warranty_duration',
|
||||||
'last_inspection_date', 'funding_program', 'geom', 'lon', 'lat',
|
'last_inspection_date', 'funding_program', 'geom', 'lon', 'lat',
|
||||||
'location_id', 'intersection_id',
|
'location_id', 'intersection_id',
|
||||||
|
|
|
||||||
|
|
@ -223,6 +223,21 @@
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
<!-- Référence externe -->
|
||||||
|
{% if asset.external_reference or 'external_reference' in allowed_update_fields %}
|
||||||
|
<tr class="asset-editable-field" data-field="external_reference" data-can-edit="{% if 'external_reference' in allowed_update_fields %}true{% else %}false{% endif %}">
|
||||||
|
<th>{% translate "Référence externe" %}</th>
|
||||||
|
<td>
|
||||||
|
<div class="field-display">
|
||||||
|
<span class="field-value">{{ asset.external_reference|default:"-" }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="field-edit d-none">
|
||||||
|
<input type="text" class="form-control form-control-sm" value="{{ asset.external_reference|default:'' }}">
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<!-- Nom FR -->
|
<!-- Nom FR -->
|
||||||
<tr class="asset-editable-field" data-field="name_fr" data-can-edit="{% if 'name_fr' in allowed_update_fields %}true{% else %}false{% endif %}">
|
<tr class="asset-editable-field" data-field="name_fr" data-can-edit="{% if 'name_fr' in allowed_update_fields %}true{% else %}false{% endif %}">
|
||||||
<th>{% translate "Nom" %} (FR)</th>
|
<th>{% translate "Nom" %} (FR)</th>
|
||||||
|
|
|
||||||
|
|
@ -908,7 +908,7 @@
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
alert('{% translate "Une erreur est survenue lors de l\'enregistrement." %}');
|
alert("{% filter escapejs %}{% translate "Une erreur est survenue lors de l'enregistrement." %}{% endfilter %}");
|
||||||
if (submitBtn) submitBtn.disabled = false;
|
if (submitBtn) submitBtn.disabled = false;
|
||||||
if (spinner) spinner.classList.add('d-none');
|
if (spinner) spinner.classList.add('d-none');
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1738,12 +1738,6 @@ def interventions_add(request, thematic_code):
|
||||||
|
|
||||||
intervention.save()
|
intervention.save()
|
||||||
|
|
||||||
# Si aucun contrat encore défini via la commande, on utilise celui du symptôme
|
|
||||||
if not intervention.contract and intervention.symptom:
|
|
||||||
intervention.contract = intervention.symptom.contract
|
|
||||||
if intervention.symptom and not intervention.assigned_provider:
|
|
||||||
intervention.assigned_provider = intervention.symptom.provider
|
|
||||||
|
|
||||||
# Si le statut est "assigned" (pris en charge) et aucun membre n'est assigné, assigner l'utilisateur créateur et sa première équipe
|
# Si le statut est "assigned" (pris en charge) et aucun membre n'est assigné, assigner l'utilisateur créateur et sa première équipe
|
||||||
if intervention.status == 'assigned' and not getattr(intervention, 'assigned_member_id', None):
|
if intervention.status == 'assigned' and not getattr(intervention, 'assigned_member_id', None):
|
||||||
from contracts.models import CompanyMember
|
from contracts.models import CompanyMember
|
||||||
|
|
|
||||||
|
|
@ -3269,15 +3269,7 @@ def resolve_intervention_contract_and_provider(thematic, symptom, asset=None, lo
|
||||||
|
|
||||||
thematic_code = thematic.code if hasattr(thematic, 'code') else (str(thematic) if thematic else None)
|
thematic_code = thematic.code if hasattr(thematic, 'code') else (str(thematic) if thematic else None)
|
||||||
|
|
||||||
# 1. Contrat / prestataire direct du symptôme
|
# 1. Routage direct (thématique 'structures') - prioritaire sur les contrats par défaut
|
||||||
if symptom:
|
|
||||||
if symptom.contract:
|
|
||||||
contract = symptom.contract
|
|
||||||
assigned_provider = symptom.provider or (contract.company if contract else None)
|
|
||||||
elif symptom.provider:
|
|
||||||
assigned_provider = symptom.provider
|
|
||||||
|
|
||||||
# 2. Routage direct (thématique 'structures')
|
|
||||||
if not contract and thematic_code == 'structures':
|
if not contract and thematic_code == 'structures':
|
||||||
structure = None
|
structure = None
|
||||||
if asset:
|
if asset:
|
||||||
|
|
@ -3310,10 +3302,10 @@ def resolve_intervention_contract_and_provider(thematic, symptom, asset=None, lo
|
||||||
if not assigned_provider:
|
if not assigned_provider:
|
||||||
assigned_provider = contract.company
|
assigned_provider = contract.company
|
||||||
|
|
||||||
# 3. Contrat actif lié au maintenance_type du symptôme
|
# 2. Contrat actif lié à la localisation/équipement et au maintenance_type
|
||||||
if not contract and symptom and symptom.maintenance_type:
|
if not contract:
|
||||||
mt = symptom.maintenance_type
|
mt = symptom.maintenance_type if symptom else None
|
||||||
if thematic_code == 'trafficlights':
|
if thematic_code == 'trafficlights' and mt:
|
||||||
intersection_id = None
|
intersection_id = None
|
||||||
if location:
|
if location:
|
||||||
if isinstance(location, TrafficLightIntersection):
|
if isinstance(location, TrafficLightIntersection):
|
||||||
|
|
@ -3351,17 +3343,26 @@ def resolve_intervention_contract_and_provider(thematic, symptom, asset=None, lo
|
||||||
structure_id = asset.structure_id
|
structure_id = asset.structure_id
|
||||||
|
|
||||||
if structure_id:
|
if structure_id:
|
||||||
|
struct_contract = None
|
||||||
|
if mt:
|
||||||
struct_contract = StructureContract.objects.filter(
|
struct_contract = StructureContract.objects.filter(
|
||||||
structure_id=structure_id,
|
structure_id=structure_id,
|
||||||
maintenance_type=mt,
|
maintenance_type=mt,
|
||||||
status='active'
|
status='active'
|
||||||
).select_related('contract__company').first()
|
).select_related('contract__company').first()
|
||||||
|
if not struct_contract:
|
||||||
|
struct_contracts = StructureContract.objects.filter(
|
||||||
|
structure_id=structure_id,
|
||||||
|
status='active'
|
||||||
|
).select_related('contract__company')
|
||||||
|
if struct_contracts.count() == 1:
|
||||||
|
struct_contract = struct_contracts.first()
|
||||||
if struct_contract and struct_contract.contract:
|
if struct_contract and struct_contract.contract:
|
||||||
contract = struct_contract.contract
|
contract = struct_contract.contract
|
||||||
if not assigned_provider:
|
if not assigned_provider:
|
||||||
assigned_provider = contract.company
|
assigned_provider = contract.company
|
||||||
|
|
||||||
elif thematic_code == 'clean':
|
elif thematic_code == 'clean' and mt:
|
||||||
clean_loc_id = None
|
clean_loc_id = None
|
||||||
if location:
|
if location:
|
||||||
if isinstance(location, CleanLocation):
|
if isinstance(location, CleanLocation):
|
||||||
|
|
@ -3385,7 +3386,7 @@ def resolve_intervention_contract_and_provider(thematic, symptom, asset=None, lo
|
||||||
if not assigned_provider:
|
if not assigned_provider:
|
||||||
assigned_provider = contract.company
|
assigned_provider = contract.company
|
||||||
|
|
||||||
elif thematic_code == 'its':
|
elif thematic_code == 'its' and mt:
|
||||||
its_contract = None
|
its_contract = None
|
||||||
if asset and isinstance(asset, ITSGeoAsset):
|
if asset and isinstance(asset, ITSGeoAsset):
|
||||||
its_contract = ITSGeoAssetContract.objects.filter(
|
its_contract = ITSGeoAssetContract.objects.filter(
|
||||||
|
|
@ -3405,6 +3406,14 @@ def resolve_intervention_contract_and_provider(thematic, symptom, asset=None, lo
|
||||||
if not assigned_provider:
|
if not assigned_provider:
|
||||||
assigned_provider = contract.company
|
assigned_provider = contract.company
|
||||||
|
|
||||||
|
# 3. Contrat / prestataire direct du symptôme (fallback par défaut du symptôme)
|
||||||
|
if not contract and symptom:
|
||||||
|
if symptom.contract:
|
||||||
|
contract = symptom.contract
|
||||||
|
assigned_provider = symptom.provider or (contract.company if contract else None)
|
||||||
|
elif symptom.provider:
|
||||||
|
assigned_provider = symptom.provider
|
||||||
|
|
||||||
# 4. Fallback contrat par défaut de l'utilisateur (limit_interventions_to_contracts)
|
# 4. Fallback contrat par défaut de l'utilisateur (limit_interventions_to_contracts)
|
||||||
user_config = None
|
user_config = None
|
||||||
if user:
|
if user:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue