feat: add location management, asset creation modal, and intervention proximity tracking to details view
This commit is contained in:
parent
1bdb771f3b
commit
ca6d09c6b1
6 changed files with 88 additions and 9 deletions
|
|
@ -45,13 +45,15 @@
|
||||||
const thematicCode = form.dataset.thematic || form.querySelector('[name="thematic_code"]')?.value || '';
|
const thematicCode = form.dataset.thematic || form.querySelector('[name="thematic_code"]')?.value || '';
|
||||||
const locationId = form.querySelector('[name="location_id"]')?.value || '';
|
const locationId = form.querySelector('[name="location_id"]')?.value || '';
|
||||||
const structureId = form.querySelector('[name="structure_id"]')?.value || '';
|
const structureId = form.querySelector('[name="structure_id"]')?.value || '';
|
||||||
|
const interventionId = form.dataset.interventionId || form.querySelector('[name="intervention_id"]')?.value || '';
|
||||||
|
|
||||||
if (!thematicCode) return;
|
if (!thematicCode) return;
|
||||||
|
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
thematic_code: thematicCode,
|
thematic_code: thematicCode,
|
||||||
location_id: locationId,
|
location_id: locationId,
|
||||||
structure_id: structureId
|
structure_id: structureId,
|
||||||
|
intervention_id: interventionId
|
||||||
});
|
});
|
||||||
|
|
||||||
fetch(`/assets/api/suggested-code/?${params.toString()}`)
|
fetch(`/assets/api/suggested-code/?${params.toString()}`)
|
||||||
|
|
|
||||||
|
|
@ -477,6 +477,33 @@ class StructuresLocationDetailAddAssetModalTest(TestCase):
|
||||||
self.assertEqual(page_res.context["suggested_code"], "WTB30-000025")
|
self.assertEqual(page_res.context["suggested_code"], "WTB30-000025")
|
||||||
self.assertContains(page_res, 'value="WTB30-000025"')
|
self.assertContains(page_res, 'value="WTB30-000025"')
|
||||||
|
|
||||||
|
# Test intervention code suggestion (mobile / desktop intervention)
|
||||||
|
from interventions.models import Intervention, InterventionLocation
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
|
itv = Intervention.objects.create(
|
||||||
|
code="ITV_TEST_99",
|
||||||
|
title="Intervention Test Structures",
|
||||||
|
thematic=self.thematic
|
||||||
|
)
|
||||||
|
loc_ct = ContentType.objects.get_for_model(self.location)
|
||||||
|
InterventionLocation.objects.create(
|
||||||
|
intervention=itv,
|
||||||
|
content_type=loc_ct,
|
||||||
|
object_id=self.location.id
|
||||||
|
)
|
||||||
|
|
||||||
|
itv_suggested = get_suggested_asset_code("structures", intervention_id=itv.id)
|
||||||
|
self.assertEqual(itv_suggested, "WTB30-000025")
|
||||||
|
|
||||||
|
itv_api_res = self.client.get(reverse("assets:get_suggested_asset_code_api"), {
|
||||||
|
"thematic_code": "structures",
|
||||||
|
"intervention_id": itv.id
|
||||||
|
})
|
||||||
|
self.assertEqual(itv_api_res.status_code, 200)
|
||||||
|
self.assertEqual(itv_api_res.json().get("suggested_code"), "WTB30-000025")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,16 +7,54 @@ def get_next_structure_asset_code(
|
||||||
structure=None,
|
structure=None,
|
||||||
location=None,
|
location=None,
|
||||||
structure_id: Optional[int] = None,
|
structure_id: Optional[int] = None,
|
||||||
location_id: Optional[int] = None
|
location_id: Optional[int] = None,
|
||||||
|
intervention=None,
|
||||||
|
intervention_id: Optional[int] = None
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
Computes the next incremental asset code for a structure / tunnel location.
|
Computes the next incremental asset code for a structure / tunnel location / intervention.
|
||||||
Looks up existing asset codes (e.g. WTB30-000023, WTB30-000412), determines
|
Looks up existing asset codes (e.g. WTB30-000023, WTB30-000412), determines
|
||||||
the dominant prefix and maximum integer number, and returns the next code
|
the dominant prefix and maximum integer number, and returns the next code
|
||||||
(e.g. WTB30-000413), ensuring it does not collide with existing codes.
|
(e.g. WTB30-000413), ensuring it does not collide with existing codes.
|
||||||
"""
|
"""
|
||||||
from assets.models.structures import Structure, StructureLocation, StructureGeoAsset
|
from assets.models.structures import Structure, StructureLocation, StructureGeoAsset
|
||||||
|
|
||||||
|
if intervention_id and not intervention:
|
||||||
|
try:
|
||||||
|
from interventions.models import Intervention
|
||||||
|
intervention = Intervention.objects.get(pk=intervention_id)
|
||||||
|
except Exception:
|
||||||
|
intervention = None
|
||||||
|
|
||||||
|
if intervention and not location and not structure:
|
||||||
|
# 1. Look for linked structure locations
|
||||||
|
for loc_link in intervention.locations.all():
|
||||||
|
loc_obj = loc_link.location
|
||||||
|
if isinstance(loc_obj, StructureLocation) or hasattr(loc_obj, 'structure'):
|
||||||
|
location = loc_obj
|
||||||
|
structure = getattr(loc_obj, 'structure', None)
|
||||||
|
break
|
||||||
|
|
||||||
|
# 2. Look for linked structure assets
|
||||||
|
if not structure and not location:
|
||||||
|
for a_link in intervention.interventionasset_set.all():
|
||||||
|
asset_obj = a_link.asset
|
||||||
|
if asset_obj and hasattr(asset_obj, 'structure') and asset_obj.structure:
|
||||||
|
structure = asset_obj.structure
|
||||||
|
location = getattr(asset_obj, 'location', None)
|
||||||
|
break
|
||||||
|
|
||||||
|
# 3. Fallback: match by location_code / address if structure exists
|
||||||
|
if not structure and not location and intervention.location_code:
|
||||||
|
loc_match = StructureLocation.objects.filter(code__iexact=intervention.location_code).first()
|
||||||
|
if loc_match:
|
||||||
|
location = loc_match
|
||||||
|
structure = loc_match.structure
|
||||||
|
else:
|
||||||
|
struct_match = Structure.objects.filter(code__iexact=intervention.location_code).first()
|
||||||
|
if struct_match:
|
||||||
|
structure = struct_match
|
||||||
|
|
||||||
if location_id and not location:
|
if location_id and not location:
|
||||||
try:
|
try:
|
||||||
location = StructureLocation.objects.select_related('structure').get(pk=location_id)
|
location = StructureLocation.objects.select_related('structure').get(pk=location_id)
|
||||||
|
|
@ -79,17 +117,21 @@ def get_suggested_asset_code(
|
||||||
thematic_code: str,
|
thematic_code: str,
|
||||||
location_id: Optional[int] = None,
|
location_id: Optional[int] = None,
|
||||||
structure_id: Optional[int] = None,
|
structure_id: Optional[int] = None,
|
||||||
|
intervention_id: Optional[int] = None,
|
||||||
location=None,
|
location=None,
|
||||||
structure=None
|
structure=None,
|
||||||
|
intervention=None
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
Returns a suggested asset code based on thematic and context.
|
Returns a suggested asset code based on thematic and context (location, structure, or intervention).
|
||||||
"""
|
"""
|
||||||
if thematic_code == 'structures':
|
if thematic_code == 'structures':
|
||||||
return get_next_structure_asset_code(
|
return get_next_structure_asset_code(
|
||||||
structure=structure,
|
structure=structure,
|
||||||
location=location,
|
location=location,
|
||||||
structure_id=structure_id,
|
structure_id=structure_id,
|
||||||
location_id=location_id
|
location_id=location_id,
|
||||||
|
intervention=intervention,
|
||||||
|
intervention_id=intervention_id
|
||||||
)
|
)
|
||||||
return ""
|
return ""
|
||||||
|
|
|
||||||
|
|
@ -1286,12 +1286,14 @@ def get_suggested_asset_code_api(request):
|
||||||
- thematic_code (str)
|
- thematic_code (str)
|
||||||
- location_id (int, optional)
|
- location_id (int, optional)
|
||||||
- structure_id (int, optional)
|
- structure_id (int, optional)
|
||||||
|
- intervention_id (int, optional)
|
||||||
"""
|
"""
|
||||||
from assets.utils.asset_code_generator import get_suggested_asset_code
|
from assets.utils.asset_code_generator import get_suggested_asset_code
|
||||||
|
|
||||||
thematic_code = request.GET.get('thematic_code') or ''
|
thematic_code = request.GET.get('thematic_code') or ''
|
||||||
location_id = request.GET.get('location_id')
|
location_id = request.GET.get('location_id')
|
||||||
structure_id = request.GET.get('structure_id')
|
structure_id = request.GET.get('structure_id')
|
||||||
|
intervention_id = request.GET.get('intervention_id')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
loc_id = int(location_id) if location_id else None
|
loc_id = int(location_id) if location_id else None
|
||||||
|
|
@ -1303,10 +1305,16 @@ def get_suggested_asset_code_api(request):
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
struct_id = None
|
struct_id = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
itv_id = int(intervention_id) if intervention_id else None
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
itv_id = None
|
||||||
|
|
||||||
suggested_code = get_suggested_asset_code(
|
suggested_code = get_suggested_asset_code(
|
||||||
thematic_code=thematic_code,
|
thematic_code=thematic_code,
|
||||||
location_id=loc_id,
|
location_id=loc_id,
|
||||||
structure_id=struct_id
|
structure_id=struct_id,
|
||||||
|
intervention_id=itv_id
|
||||||
)
|
)
|
||||||
|
|
||||||
return JsonResponse({
|
return JsonResponse({
|
||||||
|
|
|
||||||
|
|
@ -169,7 +169,7 @@
|
||||||
<label for="addAssetDesktopModalCode" class="form-label">
|
<label for="addAssetDesktopModalCode" class="form-label">
|
||||||
<i class="bi bi-hash me-1"></i>{% translate "Code" %} <span class="text-danger">*</span>
|
<i class="bi bi-hash me-1"></i>{% translate "Code" %} <span class="text-danger">*</span>
|
||||||
</label>
|
</label>
|
||||||
<input type="text" class="form-control" id="addAssetDesktopModalCode" name="code" required
|
<input type="text" class="form-control asset-code-input" id="addAssetDesktopModalCode" name="code" required
|
||||||
placeholder="{% translate 'Ex: STR-001' %}" maxlength="50">
|
placeholder="{% translate 'Ex: STR-001' %}" maxlength="50">
|
||||||
<div class="form-text text-muted">
|
<div class="form-text text-muted">
|
||||||
{% translate "Code unique pour identifier l'équipement." %}
|
{% translate "Code unique pour identifier l'équipement." %}
|
||||||
|
|
|
||||||
|
|
@ -1517,7 +1517,7 @@
|
||||||
<label for="addAssetMobileModalCode" class="form-label">
|
<label for="addAssetMobileModalCode" class="form-label">
|
||||||
{% translate "Code" %} <span class="text-danger">*</span>
|
{% translate "Code" %} <span class="text-danger">*</span>
|
||||||
</label>
|
</label>
|
||||||
<input type="text" class="form-control" id="addAssetMobileModalCode" name="code" required
|
<input type="text" class="form-control asset-code-input" id="addAssetMobileModalCode" name="code" required
|
||||||
placeholder="{% translate 'Ex: STR-001' %}" maxlength="50">
|
placeholder="{% translate 'Ex: STR-001' %}" maxlength="50">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue