feat: allow intervention association with contracts via orders and improve order selection UI in detail view

This commit is contained in:
kdeterme 2026-08-19 16:02:02 +02:00
parent be04a5e4c0
commit 5bfde04f1a
4 changed files with 79 additions and 9 deletions

View file

@ -2654,6 +2654,8 @@
if (!orderSelect) return;
const initialVal = orderSelect.getAttribute('data-initial-order-id') || $(orderSelect).val() || orderSelect.value || '';
// Initialiser Select2 avec recherche
$(orderSelect).select2({
placeholder: gettext('-- Sélectionnez une commande --'),
@ -2670,6 +2672,10 @@
dropdownParent: $(CONFIG.selectors.modal)
});
if (initialVal) {
$(orderSelect).val(initialVal).trigger('change.select2');
}
// Gérer le changement de commande
$(orderSelect).on('change', async function() {
const orderId = $(this).val();
@ -2933,6 +2939,7 @@
fixTableColumnWidths(CONFIG.selectors.table);
const orderSelect = document.getElementById('orderSelect');
if (orderSelect && $.fn.select2) {
const currentVal = $(orderSelect).val() || orderSelect.getAttribute('data-initial-order-id') || orderSelect.value || '';
$(orderSelect).select2('destroy');
$(orderSelect).select2({
placeholder: gettext('-- Sélectionnez une commande --'),
@ -2950,7 +2957,11 @@
});
// S'assurer que la valeur sélectionnée est correcte
$(orderSelect).trigger('change.select2');
if (currentVal) {
$(orderSelect).val(currentVal).trigger('change.select2');
} else {
$(orderSelect).trigger('change.select2');
}
}
// Initialiser la visibilité du bouton d'ajout du devis

View file

@ -355,7 +355,7 @@
</label>
</div>
<div class="col-md-9">
<select class="form-select" id="orderSelect" style="width: 100%;">
<select class="form-select" id="orderSelect" style="width: 100%;" data-initial-order-id="{% if intervention.order_id %}{{ intervention.order_id }}{% endif %}">
<option value="">-- {% translate "Sélectionnez une commande" %} --</option>
{% for order in orders %}
<option value="{{ order.id }}"
@ -363,7 +363,7 @@
data-order-code="{{ order.order_code }}"
data-has-quote="{% if order.quote %}true{% else %}false{% endif %}"
data-contract-posts-count="{{ order.contract.posts.count }}"
{% if intervention.order and order.id == intervention.order.id %}selected{% endif %}>
{% if intervention.order_id and order.id == intervention.order_id %}selected{% endif %}>
{{ order.contract.contract_number }} - {{ order.order_code }}
</option>
{% endfor %}

View file

@ -907,6 +907,11 @@ class ContractOperationTemplatePostTests(TestCase):
)
self.contract.thematics.add(self.thematic)
self.user_config.contracts.add(self.contract)
uca = UserContractAccess.objects.get(user_config=self.user_config, contract=self.contract)
uca.can_edit_interventions = True
uca.can_view_posts = True
uca.can_view_interventions = True
uca.save()
UserThematics.objects.create(user_config=self.user_config, thematic=self.thematic, can_edit_interventions=True)
self.contract_other = Contract.objects.create(
@ -1162,6 +1167,46 @@ class ContractOperationTemplatePostTests(TestCase):
self.assertEqual(posts[0].quantity, Decimal('4.00'))
self.assertEqual(posts[0].comment, 'Auto Post')
def test_detail_view_includes_associated_order_and_posts_modal(self):
"""Test that the detail view context includes orders and correctly preselects the associated order."""
admin_role, _ = Role.objects.get_or_create(name='admin')
self.user_config.roles.add(admin_role)
from contracts.models import ContractOrder
order = ContractOrder.objects.create(
contract=self.contract,
order_code='CMD-2026-001',
order_date=timezone.now().date(),
delivery_date=timezone.now().date() + timedelta(days=30),
order_status='pending',
total_amount=Decimal('500.00'),
)
intervention = Intervention.objects.create(
title='Intervention With Order',
contract=self.contract,
order=order,
thematic=self.thematic,
status='in_preparation',
created_by=self.user,
)
self.client.login(username='test_user_posts', password='password123')
detail_url = reverse('interventions:intervention_detail', kwargs={'intervention_id': intervention.id})
response = self.client.get(detail_url)
self.assertEqual(response.status_code, 200)
# Check context
self.assertIn('orders', response.context)
orders = response.context['orders']
self.assertIsNotNone(orders)
self.assertIn(order, orders)
self.assertEqual(response.context['contract_id'], self.contract.id)
# Check rendered HTML for modal selector
content = response.content.decode('utf-8')
self.assertIn(f'data-initial-order-id="{order.id}"', content)
self.assertIn(f'value="{order.id}"', content)
self.assertIn('selected', content)

View file

@ -149,7 +149,7 @@ def intervention_detail(request, intervention_id):
lang = get_language()
contract = intervention.contract
contract = intervention.contract or (intervention.order.contract if intervention.order else None)
thematic = intervention.thematic
allowed_update_fields = get_allowed_update_fields(request.user, intervention)
@ -223,11 +223,21 @@ def intervention_detail(request, intervention_id):
else:
has_coefficient_posts = False
orders = ContractOrder.objects.filter(contract=contract).exclude(order_status__in=['cancelled', 'paid'])
orders_qs = ContractOrder.objects.filter(contract=contract)
if intervention.order_id:
orders = orders_qs.filter(
Q(id=intervention.order_id) | ~Q(order_status__in=['cancelled', 'paid'])
).distinct().select_related('contract').order_by('order_code')
else:
orders = orders_qs.exclude(order_status__in=['cancelled', 'paid']).select_related('contract').order_by('order_code')
elif intervention.order_id:
has_coefficient_posts = False
orders = ContractOrder.objects.filter(id=intervention.order_id).select_related('contract')
else:
has_coefficient_posts = False
orders = ContractOrder.objects.none()
contract_id = intervention.order.contract.id if intervention.order else None
contract_id = contract.id if contract else None
# Limiter les providers et contracts selon les permissions de l'utilisateur
if user_config.is_intern and not user_config.limit_interventions_to_contracts:
@ -2188,10 +2198,12 @@ def intervention_link_order(request, intervention_id):
intervention = Intervention.objects.get(pk=intervention_id)
allowed_update_fields = get_allowed_update_fields(request.user, intervention)
if data['order_id'] == '':
if not data.get('order_id'):
intervention.order = None
else:
intervention.order = ContractOrder.objects.get(pk=data['order_id'])
if intervention.order and not intervention.contract:
intervention.contract = intervention.order.contract
intervention.save()
quote_items = []
@ -2227,13 +2239,15 @@ def intervention_link_order(request, intervention_id):
quote_total = format_decimal(intervention.order.get_quote_total_amount())
active_contract = intervention.contract or (intervention.order.contract if intervention.order else None)
return JsonResponse({
'success': True,
'contract_id': intervention.order.contract.id if intervention.order else None,
'contract_posts_count': intervention.order.contract.posts.count() if intervention.order and intervention.order.contract else 0,
'contract_id': active_contract.id if active_contract else None,
'contract_posts_count': active_contract.posts.count() if active_contract else 0,
'quote_items': quote_items,
'quote_total': quote_total,
'can_manage_posts': 'posts' in allowed_update_fields,
'order_display': f"{intervention.order.contract.contract_number} - {intervention.order.order_code}" if intervention.order else None,
})
except (Intervention.DoesNotExist, ContractOrder.DoesNotExist) as e:
return JsonResponse({'success': False, 'message': str(e)})