feat: allow intervention association with contracts via orders and improve order selection UI in detail view
This commit is contained in:
parent
be04a5e4c0
commit
5bfde04f1a
4 changed files with 79 additions and 9 deletions
|
|
@ -2654,6 +2654,8 @@
|
||||||
|
|
||||||
if (!orderSelect) return;
|
if (!orderSelect) return;
|
||||||
|
|
||||||
|
const initialVal = orderSelect.getAttribute('data-initial-order-id') || $(orderSelect).val() || orderSelect.value || '';
|
||||||
|
|
||||||
// Initialiser Select2 avec recherche
|
// Initialiser Select2 avec recherche
|
||||||
$(orderSelect).select2({
|
$(orderSelect).select2({
|
||||||
placeholder: gettext('-- Sélectionnez une commande --'),
|
placeholder: gettext('-- Sélectionnez une commande --'),
|
||||||
|
|
@ -2670,6 +2672,10 @@
|
||||||
dropdownParent: $(CONFIG.selectors.modal)
|
dropdownParent: $(CONFIG.selectors.modal)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (initialVal) {
|
||||||
|
$(orderSelect).val(initialVal).trigger('change.select2');
|
||||||
|
}
|
||||||
|
|
||||||
// Gérer le changement de commande
|
// Gérer le changement de commande
|
||||||
$(orderSelect).on('change', async function() {
|
$(orderSelect).on('change', async function() {
|
||||||
const orderId = $(this).val();
|
const orderId = $(this).val();
|
||||||
|
|
@ -2933,6 +2939,7 @@
|
||||||
fixTableColumnWidths(CONFIG.selectors.table);
|
fixTableColumnWidths(CONFIG.selectors.table);
|
||||||
const orderSelect = document.getElementById('orderSelect');
|
const orderSelect = document.getElementById('orderSelect');
|
||||||
if (orderSelect && $.fn.select2) {
|
if (orderSelect && $.fn.select2) {
|
||||||
|
const currentVal = $(orderSelect).val() || orderSelect.getAttribute('data-initial-order-id') || orderSelect.value || '';
|
||||||
$(orderSelect).select2('destroy');
|
$(orderSelect).select2('destroy');
|
||||||
$(orderSelect).select2({
|
$(orderSelect).select2({
|
||||||
placeholder: gettext('-- Sélectionnez une commande --'),
|
placeholder: gettext('-- Sélectionnez une commande --'),
|
||||||
|
|
@ -2950,7 +2957,11 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
// S'assurer que la valeur sélectionnée est correcte
|
// 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
|
// Initialiser la visibilité du bouton d'ajout du devis
|
||||||
|
|
|
||||||
|
|
@ -355,7 +355,7 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-9">
|
<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>
|
<option value="">-- {% translate "Sélectionnez une commande" %} --</option>
|
||||||
{% for order in orders %}
|
{% for order in orders %}
|
||||||
<option value="{{ order.id }}"
|
<option value="{{ order.id }}"
|
||||||
|
|
@ -363,7 +363,7 @@
|
||||||
data-order-code="{{ order.order_code }}"
|
data-order-code="{{ order.order_code }}"
|
||||||
data-has-quote="{% if order.quote %}true{% else %}false{% endif %}"
|
data-has-quote="{% if order.quote %}true{% else %}false{% endif %}"
|
||||||
data-contract-posts-count="{{ order.contract.posts.count }}"
|
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 }}
|
{{ order.contract.contract_number }} - {{ order.order_code }}
|
||||||
</option>
|
</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
||||||
|
|
@ -907,6 +907,11 @@ class ContractOperationTemplatePostTests(TestCase):
|
||||||
)
|
)
|
||||||
self.contract.thematics.add(self.thematic)
|
self.contract.thematics.add(self.thematic)
|
||||||
self.user_config.contracts.add(self.contract)
|
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)
|
UserThematics.objects.create(user_config=self.user_config, thematic=self.thematic, can_edit_interventions=True)
|
||||||
|
|
||||||
self.contract_other = Contract.objects.create(
|
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].quantity, Decimal('4.00'))
|
||||||
self.assertEqual(posts[0].comment, 'Auto Post')
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,7 @@ def intervention_detail(request, intervention_id):
|
||||||
|
|
||||||
lang = get_language()
|
lang = get_language()
|
||||||
|
|
||||||
contract = intervention.contract
|
contract = intervention.contract or (intervention.order.contract if intervention.order else None)
|
||||||
thematic = intervention.thematic
|
thematic = intervention.thematic
|
||||||
|
|
||||||
allowed_update_fields = get_allowed_update_fields(request.user, intervention)
|
allowed_update_fields = get_allowed_update_fields(request.user, intervention)
|
||||||
|
|
@ -223,11 +223,21 @@ def intervention_detail(request, intervention_id):
|
||||||
else:
|
else:
|
||||||
has_coefficient_posts = False
|
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:
|
else:
|
||||||
has_coefficient_posts = False
|
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
|
# Limiter les providers et contracts selon les permissions de l'utilisateur
|
||||||
if user_config.is_intern and not user_config.limit_interventions_to_contracts:
|
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)
|
intervention = Intervention.objects.get(pk=intervention_id)
|
||||||
allowed_update_fields = get_allowed_update_fields(request.user, intervention)
|
allowed_update_fields = get_allowed_update_fields(request.user, intervention)
|
||||||
|
|
||||||
if data['order_id'] == '':
|
if not data.get('order_id'):
|
||||||
intervention.order = None
|
intervention.order = None
|
||||||
else:
|
else:
|
||||||
intervention.order = ContractOrder.objects.get(pk=data['order_id'])
|
intervention.order = ContractOrder.objects.get(pk=data['order_id'])
|
||||||
|
if intervention.order and not intervention.contract:
|
||||||
|
intervention.contract = intervention.order.contract
|
||||||
intervention.save()
|
intervention.save()
|
||||||
|
|
||||||
quote_items = []
|
quote_items = []
|
||||||
|
|
@ -2227,13 +2239,15 @@ def intervention_link_order(request, intervention_id):
|
||||||
|
|
||||||
quote_total = format_decimal(intervention.order.get_quote_total_amount())
|
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({
|
return JsonResponse({
|
||||||
'success': True,
|
'success': True,
|
||||||
'contract_id': intervention.order.contract.id if intervention.order else None,
|
'contract_id': active_contract.id if active_contract else None,
|
||||||
'contract_posts_count': intervention.order.contract.posts.count() if intervention.order and intervention.order.contract else 0,
|
'contract_posts_count': active_contract.posts.count() if active_contract else 0,
|
||||||
'quote_items': quote_items,
|
'quote_items': quote_items,
|
||||||
'quote_total': quote_total,
|
'quote_total': quote_total,
|
||||||
'can_manage_posts': 'posts' in allowed_update_fields,
|
'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:
|
except (Intervention.DoesNotExist, ContractOrder.DoesNotExist) as e:
|
||||||
return JsonResponse({'success': False, 'message': str(e)})
|
return JsonResponse({'success': False, 'message': str(e)})
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue