feat: add default contract support to user configurations and auto-assign during intervention creation
This commit is contained in:
parent
6cfd80d61e
commit
718403c295
8 changed files with 141 additions and 2 deletions
|
|
@ -617,7 +617,7 @@ class UserConfigAdmin(admin.ModelAdmin):
|
|||
|
||||
fieldsets = (
|
||||
(None, {
|
||||
'fields': ('user', 'is_intern', 'roles', 'company', 'company_members_link', 'company_teams_link', 'default_thematic', 'generic_email')
|
||||
'fields': ('user', 'is_intern', 'roles', 'company', 'company_members_link', 'company_teams_link', 'default_thematic', 'default_contract', 'generic_email')
|
||||
}),
|
||||
('Accès aux vues', {
|
||||
'fields': ('accessible_views',),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
# Generated by Django 6.0.7 on 2026-08-12 03:20
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('common', '0008_reset_can_inspect_for_non_admins'),
|
||||
('contracts', '0002_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='userconfig',
|
||||
name='default_contract',
|
||||
field=models.ForeignKey(blank=True, help_text="Contrat assigné par défaut lors de la création d'une intervention si aucun contrat n'est résolu automatiquement.", null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='default_user_accesses', to='contracts.contract', verbose_name='Contrat par défaut'),
|
||||
),
|
||||
]
|
||||
|
|
@ -98,6 +98,7 @@ class UserConfig(models.Model):
|
|||
is_intern = models.BooleanField(default=False)
|
||||
roles = models.ManyToManyField(Role, blank=True)
|
||||
default_thematic = models.ForeignKey(Thematic, on_delete=models.SET_NULL, null=True, blank=True, related_name="default_user_accesses")
|
||||
default_contract = models.ForeignKey("contracts.Contract", on_delete=models.SET_NULL, null=True, blank=True, related_name="default_user_accesses", verbose_name="Contrat par défaut", help_text="Contrat assigné par défaut lors de la création d'une intervention si aucun contrat n'est résolu automatiquement.")
|
||||
thematics = models.ManyToManyField("common.Thematic", through="UserThematics", related_name="user_accesses")
|
||||
contracts = models.ManyToManyField("contracts.Contract", through="UserContractAccess", related_name="user_accesses")
|
||||
warehouses = models.ManyToManyField("stock.Warehouse", through="UserWarehouseAccess", related_name="user_accesses", blank=True)
|
||||
|
|
@ -479,6 +480,20 @@ class UserConfig(models.Model):
|
|||
|
||||
return self.userthematics.filter(thematic=thematic, can_inspect=True).exists()
|
||||
|
||||
def get_default_contract(self):
|
||||
"""
|
||||
Retourne le contrat par défaut de l'utilisateur.
|
||||
Si aucun contrat par défaut n'est spécifié mais que l'utilisateur a limit_interventions_to_contracts=True
|
||||
et n'a accès qu'à un seul contrat (via UserContractAccess), retourne ce contrat unique.
|
||||
"""
|
||||
if self.default_contract_id:
|
||||
return self.default_contract
|
||||
if self.limit_interventions_to_contracts:
|
||||
allowed_contracts = list(self.contracts.all()[:2])
|
||||
if len(allowed_contracts) == 1:
|
||||
return allowed_contracts[0]
|
||||
return None
|
||||
|
||||
|
||||
|
||||
class UserThematics(models.Model):
|
||||
|
|
|
|||
|
|
@ -407,7 +407,7 @@ def get_allowed_update_fields(user, intervention):
|
|||
_initial_statuses = ('in_preparation', 'to_be_approved')
|
||||
if _thematic_allows_planning_without_contract:
|
||||
_initial_statuses = ('in_preparation', 'to_be_approved', 'to_be_planned')
|
||||
if intervention.status in _initial_statuses and user_config.is_intern:
|
||||
if intervention.status in _initial_statuses and (user_config.is_intern or (intervention.contract is None and intervention.created_by_id == user.id)):
|
||||
allowed_fields.update(['assigned_provider', 'contract', 'maintain_type'])
|
||||
|
||||
# Localisation : modifiable par les internes sur toutes les interventions non validées
|
||||
|
|
|
|||
|
|
@ -469,5 +469,67 @@ class CreatorVisibilityTests(TestCase):
|
|||
self.assertNotIn(itv, filter_viewable_interventions_for_user(self.user2))
|
||||
|
||||
|
||||
class DefaultContractTests(TestCase):
|
||||
def setUp(self):
|
||||
from datetime import date
|
||||
from django.contrib.auth import get_user_model
|
||||
from common.models import UserConfig, UserContractAccess
|
||||
from contracts.models import Contract, Company
|
||||
|
||||
self.User = get_user_model()
|
||||
self.user = self.User.objects.create_user(username='default-contract-user', password='pwd')
|
||||
self.company = Company.objects.create(name='Test Company')
|
||||
self.contract1 = Contract.objects.create(contract_number='CNT-001', company=self.company, start_date=date(2025, 1, 1), end_date=date(2030, 1, 1))
|
||||
self.contract2 = Contract.objects.create(contract_number='CNT-002', company=self.company, start_date=date(2025, 1, 1), end_date=date(2030, 1, 1))
|
||||
|
||||
self.config = UserConfig.objects.create(
|
||||
user=self.user,
|
||||
is_intern=False,
|
||||
limit_interventions_to_contracts=True,
|
||||
)
|
||||
|
||||
def test_get_default_contract_explicit(self):
|
||||
self.config.default_contract = self.contract1
|
||||
self.config.save()
|
||||
self.assertEqual(self.config.get_default_contract(), self.contract1)
|
||||
|
||||
def test_get_default_contract_single_contract_fallback(self):
|
||||
from common.models import UserContractAccess
|
||||
UserContractAccess.objects.create(user_config=self.config, contract=self.contract2)
|
||||
# With only 1 contract in UserContractAccess, get_default_contract returns contract2
|
||||
self.assertEqual(self.config.get_default_contract(), self.contract2)
|
||||
|
||||
# With 2 contracts, get_default_contract returns None if default_contract not set
|
||||
UserContractAccess.objects.create(user_config=self.config, contract=self.contract1)
|
||||
self.assertIsNone(self.config.get_default_contract())
|
||||
|
||||
def test_observation_conversion_assigns_default_contract(self):
|
||||
from observations.models import Observation
|
||||
from interventions.models import Symptom
|
||||
from common.models import Thematic, UserContractAccess
|
||||
|
||||
self.config.default_contract = self.contract1
|
||||
self.config.save()
|
||||
UserContractAccess.objects.create(user_config=self.config, contract=self.contract1)
|
||||
|
||||
thematic = Thematic.objects.create(code='roads_def', name_fr='Voirie Def')
|
||||
symptom = Symptom.objects.create(name_fr='Symptom test', thematic=thematic)
|
||||
|
||||
obs = Observation.objects.create(
|
||||
symptom=symptom,
|
||||
thematic=thematic,
|
||||
created_by=self.user,
|
||||
latitude=50.85,
|
||||
longitude=4.35,
|
||||
address='123 Test Street'
|
||||
)
|
||||
obs.create_auto_intervention()
|
||||
itv = obs.intervention
|
||||
self.assertIsNotNone(itv)
|
||||
self.assertEqual(itv.contract, self.contract1)
|
||||
self.assertEqual(itv.assigned_provider, self.company)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1798,6 +1798,15 @@ def interventions_add(request, thematic_code):
|
|||
intervention.assigned_provider = its_contract.contract.company
|
||||
intervention.save(update_fields=['contract', 'assigned_provider'])
|
||||
|
||||
# Fallback contrat par défaut pour les utilisateurs à visibilité limitée aux contrats
|
||||
if not intervention.contract and user_config and user_config.limit_interventions_to_contracts:
|
||||
default_contract = user_config.get_default_contract()
|
||||
if default_contract:
|
||||
intervention.contract = default_contract
|
||||
if not intervention.assigned_provider:
|
||||
intervention.assigned_provider = default_contract.company
|
||||
intervention.save(update_fields=['contract', 'assigned_provider'])
|
||||
|
||||
# -------- Forcer le statut 'à approuver' pour les utilisateurs externes --------
|
||||
# Applicable si limit_interventions_to_contracts est activé et que l'utilisateur
|
||||
# n'a pas accès au contrat résolu (ou à celui du symptôme).
|
||||
|
|
|
|||
|
|
@ -1548,6 +1548,16 @@ def add_repair_intervention_mobile(request):
|
|||
break
|
||||
|
||||
with transaction.atomic():
|
||||
if not contract:
|
||||
from common.models import UserConfig
|
||||
user_config = UserConfig.objects.filter(user=request.user).first()
|
||||
if user_config and user_config.limit_interventions_to_contracts:
|
||||
default_contract = user_config.get_default_contract()
|
||||
if default_contract:
|
||||
contract = default_contract
|
||||
if not assigned_provider:
|
||||
assigned_provider = default_contract.company
|
||||
|
||||
new_intervention = Intervention(
|
||||
title=title,
|
||||
description=_("Intervention de réparation créée depuis l'application mobile."),
|
||||
|
|
@ -1775,6 +1785,16 @@ def add_intervention_mobile(request, thematic_code):
|
|||
return _render_form([_("Catégorie non trouvée.")])
|
||||
|
||||
with transaction.atomic():
|
||||
mobile_contract = None
|
||||
mobile_provider = None
|
||||
from common.models import UserConfig
|
||||
user_config = UserConfig.objects.filter(user=request.user).first()
|
||||
if user_config and user_config.limit_interventions_to_contracts:
|
||||
default_contract = user_config.get_default_contract()
|
||||
if default_contract:
|
||||
mobile_contract = default_contract
|
||||
mobile_provider = default_contract.company
|
||||
|
||||
new_intervention = Intervention(
|
||||
title=title,
|
||||
description='',
|
||||
|
|
@ -1783,6 +1803,8 @@ def add_intervention_mobile(request, thematic_code):
|
|||
priority='3',
|
||||
thematic=thematic,
|
||||
asset_category=category,
|
||||
contract=mobile_contract,
|
||||
assigned_provider=mobile_provider,
|
||||
lon=lon,
|
||||
lat=lat,
|
||||
location_code=location_code,
|
||||
|
|
|
|||
|
|
@ -538,6 +538,17 @@ class Observation(models.Model):
|
|||
assigned_provider=self.symptom.provider,
|
||||
)
|
||||
|
||||
if not intervention.contract and self.created_by:
|
||||
from common.models import UserConfig
|
||||
user_config = UserConfig.objects.filter(user=self.created_by).first()
|
||||
if user_config and user_config.limit_interventions_to_contracts:
|
||||
default_contract = user_config.get_default_contract()
|
||||
if default_contract:
|
||||
intervention.contract = default_contract
|
||||
if not intervention.assigned_provider:
|
||||
intervention.assigned_provider = default_contract.company
|
||||
intervention.save(update_fields=['contract', 'assigned_provider'])
|
||||
|
||||
# Link to observation and set observation status to 'in_progress'
|
||||
self.intervention = intervention
|
||||
self.status = 'in_progress'
|
||||
|
|
|
|||
Loading…
Reference in a new issue