diff --git a/loko/common/static/common/base_style.css b/loko/common/static/common/base_style.css
index aa296a7..5e005f1 100644
--- a/loko/common/static/common/base_style.css
+++ b/loko/common/static/common/base_style.css
@@ -312,6 +312,11 @@ label,
color: #ffffff !important;
}
+.badge.status-to_be_processed_assigned {
+ background-color: #d81b60 !important;
+ color: #ffffff !important;
+}
+
.badge.status-assigned {
background-color: #63b3ed !important;
color: #000000 !important;
diff --git a/loko/interventions/models.py b/loko/interventions/models.py
index e5589b3..fc36bfb 100644
--- a/loko/interventions/models.py
+++ b/loko/interventions/models.py
@@ -67,8 +67,9 @@ STATUS_COLORS = {
'in_preparation': '#757575',
'to_be_approved': '#ffb74d',
'to_be_planned': '#ff8a80',
- 'to_be_processed': '#f4511e',
- 'assigned': '#63b3ed',
+ 'to_be_processed': '#f4511e',
+ 'to_be_processed_assigned': '#d81b60',
+ 'assigned': '#63b3ed',
'in_progress': '#3182ce',
'on_pause': '#6b226e',
'finished': '#26a69a',
@@ -928,8 +929,14 @@ class Intervention(models.Model):
return os.path.basename(self.intervention_sheet.name)
return None
+ @property
+ def is_assigned(self):
+ return bool(self.assigned_team_id or self.assigned_member_id)
+
@property
def status_color_hex(self):
+ if self.status == 'to_be_processed' and self.is_assigned:
+ return STATUS_COLORS.get('to_be_processed_assigned', '#d81b60')
return STATUS_COLORS.get(self.status, '#757575')
@property
diff --git a/loko/interventions/static/interventions/calendar/interventions_calendar.css b/loko/interventions/static/interventions/calendar/interventions_calendar.css
index 293dbcc..e273468 100644
--- a/loko/interventions/static/interventions/calendar/interventions_calendar.css
+++ b/loko/interventions/static/interventions/calendar/interventions_calendar.css
@@ -69,6 +69,15 @@
color: #ffffff !important;
}
+.vis-item.status-to_be_processed.is-assigned,
+.vis-item.status-to_be_processed_assigned { background-color: #d81b60; border-color: #b51750; color: #fff; }
+
+.badge.status-to_be_processed_assigned,
+.badge[class*="status-to_be_processed_assigned"] {
+ background-color: #d81b60 !important;
+ color: #ffffff !important;
+}
+
.canvas-timeline {
border: 1px solid var(--bs-border-color, #e2e8f0);
diff --git a/loko/interventions/static/interventions/calendar/interventions_calendar.js b/loko/interventions/static/interventions/calendar/interventions_calendar.js
index a70ea35..a05dcd2 100644
--- a/loko/interventions/static/interventions/calendar/interventions_calendar.js
+++ b/loko/interventions/static/interventions/calendar/interventions_calendar.js
@@ -2,6 +2,32 @@
// --- Status colors mapping (réfère à la constante de status_colors.js)
const STATUS_COLORS = INTERVENTION_STATUS_COLORS;
+/**
+ * Retourne la couleur d'une intervention en tenant compte du statut
+ * et de l'assignation (pour to_be_processed assigné -> #d81b60).
+ */
+function getInterventionColor(itvOrCode, isAssigned = false) {
+ let statusCode = '';
+ let assigned = isAssigned;
+ if (typeof itvOrCode === 'object' && itvOrCode !== null) {
+ statusCode = itvOrCode.status_code || itvOrCode.status || itvOrCode.statusCode || '';
+ if (itvOrCode.is_assigned !== undefined) {
+ assigned = Boolean(itvOrCode.is_assigned);
+ } else if (itvOrCode.member_name || itvOrCode.memberName || itvOrCode.team_name || itvOrCode.teamName) {
+ assigned = true;
+ }
+ } else {
+ statusCode = String(itvOrCode || '');
+ }
+ if (typeof getInterventionStatusColor === 'function') {
+ return getInterventionStatusColor(statusCode, assigned);
+ }
+ if (statusCode === 'to_be_processed' && assigned) {
+ return STATUS_COLORS.to_be_processed_assigned || '#d81b60';
+ }
+ return STATUS_COLORS[statusCode] || DEFAULT_STATUS_COLOR;
+}
+
const DEFAULT_STATUS_COLOR = '#888888';
const EDITABLE_STATUSES = ['in_preparation', 'to_be_approved', 'to_be_planned', 'to_be_processed', 'assigned'];
const DAY_MS = 24 * 60 * 60 * 1000;
@@ -562,7 +588,8 @@ document.addEventListener('DOMContentLoaded', function () {
let itvsHTML = '
';
if (dayData.interventions && dayData.interventions.length > 0) {
dayData.interventions.forEach(itv => {
- const cardColor = STATUS_COLORS[itv.status] || DEFAULT_STATUS_COLOR;
+ const isAssigned = Boolean(itv.is_assigned || (team && team.id !== null) || itv.member_name);
+ const cardColor = getInterventionColor(itv.status, isAssigned);
const textCol = idealTextColor(cardColor);
let isEditable = canMoveTimelineItems && EDITABLE_STATUSES.includes(itv.status);
@@ -961,6 +988,14 @@ document.addEventListener('DOMContentLoaded', function () {
};
}).filter(Boolean);
+ const isAssigned = Boolean(
+ source.extendedProps?.is_assigned !== undefined
+ ? source.extendedProps.is_assigned
+ : (source.is_assigned !== undefined
+ ? source.is_assigned
+ : (memberName || source.extendedProps?.team_name || source.team_name))
+ );
+
return {
id: source.id ?? `event-${index}`,
code,
@@ -970,6 +1005,8 @@ document.addEventListener('DOMContentLoaded', function () {
statusHistory,
milestones,
memberName,
+ teamName: source.extendedProps?.team_name || source.team_name || '',
+ isAssigned,
prepState,
maintainTypeLabel: source.extendedProps?.maintain_type_label || source.maintain_type_label || '',
startDate,
@@ -1671,7 +1708,9 @@ document.addEventListener('DOMContentLoaded', function () {
const sphereRadius = 8;
for (let i = 0; i < historyPoints.length; i++) {
const sh = historyPoints[i];
- const sphereColor = STATUS_COLORS[sh.statusCode] || DEFAULT_STATUS_COLOR;
+ const sphereColor = (sh.statusCode === 'to_be_processed' && (sh.isCurrent ? item.isAssigned : false))
+ ? (STATUS_COLORS.to_be_processed_assigned || '#d81b60')
+ : (STATUS_COLORS[sh.statusCode] || DEFAULT_STATUS_COLOR);
// Halo sur le statut actuel
if (sh.isCurrent) {
@@ -1707,6 +1746,7 @@ document.addEventListener('DOMContentLoaded', function () {
date: sh.date,
userName: sh.userName,
isCurrent: sh.isCurrent,
+ itemIsAssigned: item.isAssigned,
itemCode: item.code,
itemTitle: item.title,
x: sh.x - 10,
@@ -1933,7 +1973,8 @@ document.addEventListener('DOMContentLoaded', function () {
const hit = hitsList[0];
const dateStr = formatItemDate(hit.date);
if (hit.kind === 'status') {
- const color = STATUS_COLORS[hit.statusCode] || DEFAULT_STATUS_COLOR;
+ const isAssigned = Boolean(hit.itemIsAssigned || anchorHit?.itemIsAssigned);
+ const color = getInterventionColor(hit.statusCode, hit.isCurrent ? isAssigned : false);
const currentBadge = hit.isCurrent ? `
${gettext('Actuel')}` : '';
const userStr = hit.userName ? `
${escapeHtml(hit.userName)}
` : '';
content = `
@@ -1966,7 +2007,8 @@ document.addEventListener('DOMContentLoaded', function () {
const dateStr = formatItemDate(hit.date);
const isBorder = idx < hitsList.length - 1 ? 'border-bottom border-light border-opacity-10 pb-1 mb-1' : '';
if (hit.kind === 'status') {
- const color = STATUS_COLORS[hit.statusCode] || DEFAULT_STATUS_COLOR;
+ const isAssigned = Boolean(hit.itemIsAssigned || anchorHit?.itemIsAssigned);
+ const color = getInterventionColor(hit.statusCode, hit.isCurrent ? isAssigned : false);
const currentBadge = hit.isCurrent ? `
${gettext('Actuel')}` : '';
const userStr = hit.userName ? `
(${escapeHtml(hit.userName)})` : '';
return `
@@ -2265,7 +2307,9 @@ document.addEventListener('DOMContentLoaded', function () {
const sphereRadius = 8;
for (let i = 0; i < historyPoints.length; i++) {
const sh = historyPoints[i];
- const sphereColor = STATUS_COLORS[sh.statusCode] || DEFAULT_STATUS_COLOR;
+ const sphereColor = (sh.statusCode === 'to_be_processed' && sh.isCurrent)
+ ? (STATUS_COLORS.to_be_processed_assigned || '#d81b60')
+ : (STATUS_COLORS[sh.statusCode] || DEFAULT_STATUS_COLOR);
if (sh.isCurrent) {
ctx.save();
@@ -3094,7 +3138,7 @@ document.addEventListener('DOMContentLoaded', function () {
const barLeft = hasLayout ? layout.barLeft : Math.max(startX, MEMBERS_LABEL_WIDTH - 60);
const barRight = hasLayout ? layout.barRight : Math.min(endX, MEMBERS_LABEL_WIDTH + visibleWidth + 60);
const barWidth = Math.max(6, hasLayout ? layout.barWidth : (barRight - barLeft));
- const color = STATUS_COLORS[item.statusCode] || DEFAULT_STATUS_COLOR;
+ const color = getInterventionColor(item.statusCode, true);
const shouldDim = isDraggingThisItem && dragState?.hasMoved;
ctx.save();
@@ -3212,7 +3256,7 @@ document.addEventListener('DOMContentLoaded', function () {
const barLeft = hasLayout ? layout.barLeft : Math.max(startX, MEMBERS_LABEL_WIDTH - 60);
const barRight = hasLayout ? layout.barRight : Math.min(endX, MEMBERS_LABEL_WIDTH + visibleWidth + 60);
const barWidth = Math.max(6, hasLayout ? layout.barWidth : (barRight - barLeft));
- const color = STATUS_COLORS[item.statusCode] || DEFAULT_STATUS_COLOR;
+ const color = getInterventionColor(item.statusCode, true);
const shouldDim = isDraggingThisItem && dragState?.hasMoved;
ctx.save();
@@ -3310,7 +3354,7 @@ document.addEventListener('DOMContentLoaded', function () {
const laneBaseY = rowTop + MEMBERS_ROW_VERTICAL_PADDING;
const laneStep = TIMELINE_BAR_HEIGHT + MEMBERS_LANE_VERTICAL_SPACING;
const barY = laneBaseY + laneIndex * laneStep;
- const color = STATUS_COLORS[drag.item.statusCode] || DEFAULT_STATUS_COLOR;
+ const color = getInterventionColor(drag.item.statusCode, true);
ctx.save();
ctx.globalAlpha = 0.85;
@@ -4568,7 +4612,8 @@ document.addEventListener('DOMContentLoaded', function () {
item.draggable = true;
item.dataset.interventionId = intervention.id;
- const color = STATUS_COLORS[intervention.status_code] || DEFAULT_STATUS_COLOR;
+ const isAssigned = Boolean(intervention.is_assigned || intervention.member_name || intervention.team_name);
+ const color = getInterventionColor(intervention.status_code, isAssigned);
const textColor = idealTextColor(color);
const titleRow = document.createElement('div');
titleRow.className = 'd-flex justify-content-between align-items-start gap-2';
@@ -4619,6 +4664,12 @@ document.addEventListener('DOMContentLoaded', function () {
assigneeBadge.style.fontSize = '0.7rem';
assigneeBadge.textContent = intervention.member_name;
badgesColumn.appendChild(assigneeBadge);
+ } else if (intervention.team_name) {
+ const assigneeBadge = document.createElement('span');
+ assigneeBadge.className = 'badge rounded-pill text-bg-light text-dark border border-secondary fw-bold';
+ assigneeBadge.style.fontSize = '0.7rem';
+ assigneeBadge.textContent = intervention.team_name;
+ badgesColumn.appendChild(assigneeBadge);
}
titleRow.appendChild(titleSpan);
@@ -4756,7 +4807,8 @@ document.addEventListener('DOMContentLoaded', function () {
const status = record.status || record.extendedProps?.status || '';
const statusCode = record.status_code || record.extendedProps?.status_code || '';
- const color = STATUS_COLORS[statusCode] || DEFAULT_STATUS_COLOR;
+ const isAssigned = Boolean(record.is_assigned || record.extendedProps?.is_assigned || record.member_name || record.extendedProps?.member_name || record.team_name || record.extendedProps?.team_name);
+ const color = getInterventionColor(statusCode, isAssigned);
const textColor = idealTextColor(color);
modalInterventionStatus.textContent = status;
@@ -5265,7 +5317,7 @@ function openMemberHistoryModal(memberId) {
${escapeHtml(itv.code)} |
${escapeHtml(itv.title)} |
${escapeHtml(itv.team_name)} |
-
${escapeHtml(itv.status_display)} |
+
${escapeHtml(itv.status_display)} |
`;
}).join('');
diff --git a/loko/interventions/static/interventions/status_colors.js b/loko/interventions/static/interventions/status_colors.js
index ee420b0..b558faf 100644
--- a/loko/interventions/static/interventions/status_colors.js
+++ b/loko/interventions/static/interventions/status_colors.js
@@ -14,6 +14,7 @@ const INTERVENTION_STATUS_COLORS = {
to_be_approved: '#ffb74d',
to_be_planned: '#ff8a80',
to_be_processed: '#f4511e',
+ to_be_processed_assigned: '#d81b60',
assigned: '#63b3ed',
in_progress: '#3182ce',
on_pause: '#6b226e',
@@ -31,14 +32,42 @@ const INTERVENTION_STATUS_COLORS = {
const INTERVENTION_DEFAULT_STATUS_COLOR = '#6c757d';
/**
- * Expression MapLibre « match » prête à l'emploi dans les couches circle/fill.
- * Générée automatiquement depuis INTERVENTION_STATUS_COLORS.
+ * Expression MapLibre prête à l'emploi dans les couches circle/fill.
+ * Générée automatiquement depuis INTERVENTION_STATUS_COLORS,
+ * avec prise en compte du statut 'à traiter' assigné (#d81b60).
*/
const INTERVENTION_STATUS_COLOR_EXPR = (() => {
const expr = ['match', ['get', 'status']];
for (const [status, color] of Object.entries(INTERVENTION_STATUS_COLORS)) {
+ if (status === 'to_be_processed_assigned') continue;
expr.push(status, color);
}
expr.push(INTERVENTION_DEFAULT_STATUS_COLOR); // valeur par défaut
- return expr;
+
+ return [
+ 'case',
+ [
+ 'all',
+ ['==', ['get', 'status'], 'to_be_processed'],
+ [
+ 'any',
+ ['boolean', ['get', 'is_assigned'], false],
+ ['!=', ['coalesce', ['get', 'team'], ''], ''],
+ ['!=', ['coalesce', ['get', 'member'], ''], '']
+ ]
+ ],
+ INTERVENTION_STATUS_COLORS.to_be_processed_assigned,
+ expr
+ ];
})();
+
+/**
+ * Retourne la couleur hex du statut en tenant compte de l'assignation.
+ * Pour 'to_be_processed' assigné, retourne '#d81b60' (rose framboise).
+ */
+function getInterventionStatusColor(status, isAssigned = false) {
+ if (status === 'to_be_processed' && isAssigned) {
+ return INTERVENTION_STATUS_COLORS.to_be_processed_assigned;
+ }
+ return INTERVENTION_STATUS_COLORS[status] || INTERVENTION_DEFAULT_STATUS_COLOR;
+}
diff --git a/loko/interventions/templates/interventions/interventions_calendar.html b/loko/interventions/templates/interventions/interventions_calendar.html
index 290a584..1457739 100644
--- a/loko/interventions/templates/interventions/interventions_calendar.html
+++ b/loko/interventions/templates/interventions/interventions_calendar.html
@@ -948,25 +948,41 @@
// ---- Carte des interventions (modal) ----
(function () {
const MAPTILER_STYLE = window.MAP_STYLE_LIGHT;
- const STATUS_COLOR_EXPR = [
- 'match', ['get', 'status'],
- 'in_preparation', '#6c757d',
- 'to_be_approved', '#ffc107',
- 'to_be_planned', '#ffc107',
- 'to_be_processed', '#dc3545',
- 'assigned', '#ffc107',
- 'in_progress', '#007bff',
- 'on_pause', '#ffc107',
- 'finished', '#17a2b8',
- 'processed', '#17a2b8',
- 'to_be_corrected', '#dc3545',
- 'corrected', '#17a2b8',
- 'validated', '#28a745',
- 'invoiced', '#343a40',
- 'closed', '#343a40',
- 'canceled', '#6c757d',
- '#888888'
- ];
+ const STATUS_COLOR_EXPR = (typeof INTERVENTION_STATUS_COLOR_EXPR !== 'undefined')
+ ? INTERVENTION_STATUS_COLOR_EXPR
+ : [
+ 'case',
+ [
+ 'all',
+ ['==', ['get', 'status'], 'to_be_processed'],
+ [
+ 'any',
+ ['boolean', ['get', 'is_assigned'], false],
+ ['!=', ['coalesce', ['get', 'team'], ''], ''],
+ ['!=', ['coalesce', ['get', 'member'], ''], '']
+ ]
+ ],
+ '#d81b60',
+ [
+ 'match', ['get', 'status'],
+ 'in_preparation', '#6c757d',
+ 'to_be_approved', '#ffc107',
+ 'to_be_planned', '#ffc107',
+ 'to_be_processed', '#dc3545',
+ 'assigned', '#ffc107',
+ 'in_progress', '#007bff',
+ 'on_pause', '#ffc107',
+ 'finished', '#17a2b8',
+ 'processed', '#17a2b8',
+ 'to_be_corrected', '#dc3545',
+ 'corrected', '#17a2b8',
+ 'validated', '#28a745',
+ 'invoiced', '#343a40',
+ 'closed', '#343a40',
+ 'canceled', '#6c757d',
+ '#888888'
+ ]
+ ];
const DRAW_STYLES = [{id:'gl-draw-polygon-fill',type:'fill',filter:['all',['==','$type','Polygon']],paint:{'fill-color':['case',['==',['get','active'],'true'],'orange','#0066cc'],'fill-opacity':.15}},{id:'gl-draw-lines',type:'line',filter:['any',['==','$type','LineString'],['==','$type','Polygon']],layout:{'line-cap':'round','line-join':'round'},paint:{'line-color':['case',['==',['get','active'],'true'],'orange','#0066cc'],'line-width':2}},{id:'gl-draw-point-outer',type:'circle',filter:['all',['==','$type','Point'],['==','meta','feature']],paint:{'circle-radius':['case',['==',['get','active'],'true'],7,5],'circle-color':'white'}},{id:'gl-draw-point-inner',type:'circle',filter:['all',['==','$type','Point'],['==','meta','feature']],paint:{'circle-radius':['case',['==',['get','active'],'true'],5,3],'circle-color':['case',['==',['get','active'],'true'],'orange','#0066cc']}},{id:'gl-draw-vertex-outer',type:'circle',filter:['all',['==','$type','Point'],['==','meta','vertex'],['!=','mode','simple_select']],paint:{'circle-radius':['case',['==',['get','active'],'true'],7,5],'circle-color':'white'}},{id:'gl-draw-vertex-inner',type:'circle',filter:['all',['==','$type','Point'],['==','meta','vertex'],['!=','mode','simple_select']],paint:{'circle-radius':['case',['==',['get','active'],'true'],5,3],'circle-color':'orange'}},{id:'gl-draw-midpoint',type:'circle',filter:['all',['==','meta','midpoint']],paint:{'circle-radius':3,'circle-color':'orange'}}];
const TEAM_PALETTE = ['#0066CC','#1A9850','#F46D43','#9E0142','#3288BD','#66C2A5','#FF8800','#D53E4F','#5E4FA2','#66BD63'];
@@ -1090,9 +1106,30 @@
if (!e.features.length) return;
const props = e.features[0].properties;
if (calPopup) calPopup.remove();
+ function _esc(s) {
+ if (!s) return '';
+ return String(s).replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
+ }
+ const isAssigned = Boolean(props.is_assigned || props.team || props.member);
+ const statusColor = (props.status === 'to_be_processed' && isAssigned)
+ ? '#d81b60'
+ : (typeof getInterventionStatusColor === 'function'
+ ? getInterventionStatusColor(props.status)
+ : (props.status === 'to_be_processed' ? '#dc3545' : '#6c757d'));
+ const statusLabel = props.status_display || props.status || '';
+ const assignee = props.member || props.team || '';
+ const assigneeHtml = assignee ? `
${_esc(assignee)}
` : '';
+ const badgeHtml = statusLabel ? `
${_esc(statusLabel)}` : '';
calPopup = new maplibregl.Popup({ maxWidth: '280px' })
.setLngLat(e.lngLat)
- .setHTML(`
${props.code || ''}${props.title || ''}`)
+ .setHTML(`
+
+
${_esc(props.title || '')}
+ ${assigneeHtml}
+ `)
.addTo(calMap);
});
calMap.on('mouseenter', 'cal-ivt-circles', () => { calMap.getCanvas().style.cursor = 'pointer'; });
diff --git a/loko/interventions/tests/test_calendar.py b/loko/interventions/tests/test_calendar.py
index 6a413f1..5803b05 100644
--- a/loko/interventions/tests/test_calendar.py
+++ b/loko/interventions/tests/test_calendar.py
@@ -1140,6 +1140,111 @@ class ExternalManagerScheduleUpdateTests(TestCase):
self.assertIn("assigner", res.json().get('error', '').lower())
+class AssignedToBeProcessedColorTests(TestCase):
+ def setUp(self):
+ User = get_user_model()
+ self.user = User.objects.create_user(username='calendar-color-user', password='pwd')
+ self.role_admin = Role.objects.create(name='admin')
+ self.user_config = UserConfig.objects.create(user=self.user, is_intern=True)
+ self.user_config.roles.add(self.role_admin)
+
+ self.company = Company.objects.create(name='Test Company')
+ self.team = CompanyTeam.objects.create(name='Team Alpha', company=self.company)
+ self.member = CompanyMember.objects.create(user=self.user, name='Alice Tech')
+ self.member.teams.add(self.team)
+
+ self.client.login(username='calendar-color-user', password='pwd')
+
+ def test_model_status_color_hex(self):
+ # Unassigned to_be_processed
+ itv_unassigned = Intervention.objects.create(
+ title='Unassigned',
+ status='to_be_processed',
+ )
+ self.assertFalse(itv_unassigned.is_assigned)
+ self.assertEqual(itv_unassigned.status_color_hex, '#f4511e')
+
+ # Assigned to member
+ itv_member = Intervention.objects.create(
+ title='Assigned to member',
+ status='to_be_processed',
+ assigned_member=self.member,
+ )
+ self.assertTrue(itv_member.is_assigned)
+ self.assertEqual(itv_member.status_color_hex, '#d81b60')
+
+ # Assigned to team only
+ itv_team = Intervention.objects.create(
+ title='Assigned to team',
+ status='to_be_processed',
+ assigned_team=self.team,
+ )
+ self.assertTrue(itv_team.is_assigned)
+ self.assertEqual(itv_team.status_color_hex, '#d81b60')
+
+ # Other status assigned
+ itv_progress = Intervention.objects.create(
+ title='In progress',
+ status='in_progress',
+ assigned_member=self.member,
+ )
+ self.assertEqual(itv_progress.status_color_hex, '#3182ce')
+
+ def test_calendar_events_extended_props_is_assigned(self):
+ now = timezone.now()
+ itv_unassigned = Intervention.objects.create(
+ title='Unassigned ITV',
+ status='to_be_processed',
+ planned_begin_time=now + timedelta(hours=1),
+ planned_end_time=now + timedelta(hours=2),
+ )
+ itv_assigned = Intervention.objects.create(
+ title='Assigned ITV',
+ status='to_be_processed',
+ assigned_member=self.member,
+ planned_begin_time=now + timedelta(hours=3),
+ planned_end_time=now + timedelta(hours=4),
+ )
+
+ url = reverse('interventions:interventions_events')
+ res = self.client.get(url)
+ self.assertEqual(res.status_code, 200)
+ events = res.json()
+ ev_unassigned = next((e for e in events if e['id'] == itv_unassigned.id), None)
+ ev_assigned = next((e for e in events if e['id'] == itv_assigned.id), None)
+
+ self.assertIsNotNone(ev_unassigned)
+ self.assertFalse(ev_unassigned['extendedProps']['is_assigned'])
+
+ self.assertIsNotNone(ev_assigned)
+ self.assertTrue(ev_assigned['extendedProps']['is_assigned'])
+
+ def test_interventions_geojson_is_assigned(self):
+ itv_unassigned = Intervention.objects.create(
+ title='Geo Unassigned',
+ status='to_be_processed',
+ )
+ itv_assigned = Intervention.objects.create(
+ title='Geo Assigned',
+ status='to_be_processed',
+ assigned_team=self.team,
+ )
+
+ url = reverse('interventions:interventions_geojson')
+ res = self.client.get(url)
+ self.assertEqual(res.status_code, 200)
+ features = res.json()['features']
+ f_unassigned = next((f for f in features if f['id'] == itv_unassigned.id), None)
+ f_assigned = next((f for f in features if f['id'] == itv_assigned.id), None)
+
+ self.assertIsNotNone(f_unassigned)
+ self.assertFalse(f_unassigned['properties']['is_assigned'])
+
+ self.assertIsNotNone(f_assigned)
+ self.assertTrue(f_assigned['properties']['is_assigned'])
+ self.assertEqual(f_assigned['properties']['team'], 'Team Alpha')
+
+
diff --git a/loko/interventions/views/calendar.py b/loko/interventions/views/calendar.py
index 95f89e3..0223ef0 100644
--- a/loko/interventions/views/calendar.py
+++ b/loko/interventions/views/calendar.py
@@ -395,7 +395,7 @@ def interventions_events(request):
interventions = (
interventions
- .select_related('assigned_member')
+ .select_related('assigned_member', 'assigned_team')
.prefetch_related('assigned_member__teams', 'assigned_member__teams__company')
.order_by('effective_begin') # croissant: plus proche d'aujourd'hui au milieu/vers le bas
)
@@ -608,7 +608,8 @@ def interventions_events(request):
'actual_end': itv.end_time.isoformat() if itv.end_time else None,
'link': reverse('interventions:intervention_detail', args=[itv.id]),
'member_name': itv.assigned_member.name if itv.assigned_member else None,
- 'team_name': ", ".join(t.name for t in itv.assigned_member.teams.all()) if itv.assigned_member else None,
+ 'team_name': ", ".join(t.name for t in itv.assigned_member.teams.all()) if itv.assigned_member else (itv.assigned_team.name if itv.assigned_team else None),
+ 'is_assigned': bool(itv.assigned_team_id or itv.assigned_member_id),
}
})
@@ -708,6 +709,7 @@ def _interventions_events_by_members(
'team_id': team.id if team else (member.teams.all()[0].id if member and member.teams.all() else None),
'team_name': team.name if team else (member.teams.all()[0].name if member and member.teams.all() else None),
'assigned_to_team_only': bool(team and not member),
+ 'is_assigned': bool(team or member),
'duration_ms': duration_ms,
'link': reverse('interventions:intervention_detail', args=[itv.id]),
}
@@ -1056,6 +1058,7 @@ def _interventions_events_by_teams_daily(
'status': assign.status or itv.status,
'status_display': itv.get_status_display() if not assign.status else dict(STATUS_CHOICES).get(assign.status, assign.status),
'status_color': itv.get_status_color() if not assign.status else assign.status,
+ 'is_assigned': True,
'link': reverse('interventions:intervention_detail', args=[itv.id]),
'member_name': assign.member.name if assign.member else None,
})
@@ -1092,6 +1095,7 @@ def _interventions_events_by_teams_daily(
'status': itv.status,
'status_display': itv.get_status_display(),
'status_color': itv.get_status_color(),
+ 'is_assigned': bool(itv.assigned_team_id or itv.assigned_member_id),
'link': reverse('interventions:intervention_detail', args=[itv.id]),
'member_name': None,
})
@@ -1186,6 +1190,7 @@ def _interventions_events_by_teams_daily(
'team_id': team.id if team else (member.teams.all()[0].id if member and member.teams.all() else None),
'team_name': team.name if team else (member.teams.all()[0].name if member and member.teams.all() else None),
'assigned_to_team_only': bool(team and not member),
+ 'is_assigned': bool(team or member),
'duration_ms': duration_ms,
'link': reverse('interventions:intervention_detail', args=[itv.id]),
}
diff --git a/loko/interventions/views/general.py b/loko/interventions/views/general.py
index b10f5f8..a6fb862 100644
--- a/loko/interventions/views/general.py
+++ b/loko/interventions/views/general.py
@@ -743,7 +743,7 @@ def interventions_geojson(request):
# qs = qs.order_by('custom_priority', F('effective_begin').desc(nulls_last=True), '-id')\
qs = qs.order_by('-code')\
- .select_related('assigned_provider', 'contract', 'contract__company', 'assigned_team')\
+ .select_related('assigned_provider', 'contract', 'contract__company', 'assigned_team', 'assigned_member')\
.values(
'id', 'code', 'title', 'location_code',
'status', 'maintain_type', 'origin_type_label', 'prep_state',
@@ -753,7 +753,10 @@ def interventions_geojson(request):
'contract__company__name',
'contract__is_blocked',
'is_blocked_due_to_contract',
+ 'assigned_team_id',
'assigned_team__name',
+ 'assigned_member_id',
+ 'assigned_member__name',
source_category_field,
)[:MAX_FEATURES]
@@ -761,6 +764,7 @@ def interventions_geojson(request):
features = []
for r in qs:
geometry = json.loads(r['geom_geojson']) if r['geom_geojson'] else None
+ is_assigned = bool(r.get('assigned_team_id') or r.get('assigned_member_id'))
features.append({
"type": "Feature",
"id": r['id'],
@@ -772,8 +776,7 @@ def interventions_geojson(request):
"location_code": r['location_code'],
"begin_time": r['effective_begin'],
"status": r['status'],
- # If you need the display label, either map it here or annotate a Case() for it
- # "status_display": r['status'],
+ "status_display": str(STATUS_CHOICES.get(r['status'], r['status'])),
"maintain_type": r['maintain_type'],
"prep_state": r['prep_state'],
"assigned_provider": r['assigned_provider__name'],
@@ -783,6 +786,8 @@ def interventions_geojson(request):
"origin_type": r['origin_type_label'],
"source_category": r[source_category_field] if r[source_category_field] else '',
"team": r['assigned_team__name'] or '',
+ "member": r['assigned_member__name'] or '',
+ "is_assigned": is_assigned,
},
})