feat(interventions): distinguer les interventions 'à traiter' déjà assignées en rose (#d81b60)
- Ajout de la couleur 'to_be_processed_assigned' (#d81b60) dans STATUS_COLORS (Python & JS). - Ajout de la propriété is_assigned sur le modèle Intervention. - Prise en compte de la couleur dans les vues calendrier (membres, timeline, backlog, équipes) et sur la carte MapLibre du modal calendrier. - Enrichissement des popups carte et des badges du backlog avec l'équipe/membre assigné. - Ajout de tests unitaires validant la couleur et la sérialisation is_assigned.
This commit is contained in:
parent
0bbe267498
commit
37c1016b57
9 changed files with 295 additions and 41 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ 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',
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 = '<div class="teams-daily-interventions-stack">';
|
||||
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 ? ` <span class="badge bg-light text-dark ms-1" style="font-size:0.65rem;">${gettext('Actuel')}</span>` : '';
|
||||
const userStr = hit.userName ? `<div style="font-size:0.7rem;opacity:0.85;margin-top:2px;">${escapeHtml(hit.userName)}</div>` : '';
|
||||
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 ? ` <span class="badge bg-light text-dark ms-1" style="font-size:0.6rem;">${gettext('Actuel')}</span>` : '';
|
||||
const userStr = hit.userName ? `<span class="text-white-50 ms-1" style="font-size:0.68rem;">(${escapeHtml(hit.userName)})</span>` : '';
|
||||
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) {
|
|||
<td><a href="${itv.detail_url}" target="_blank" class="fw-bold text-primary text-decoration-none">${escapeHtml(itv.code)}</a></td>
|
||||
<td class="text-truncate" style="max-width: 180px;" title="${escapeHtml(itv.title)}">${escapeHtml(itv.title)}</td>
|
||||
<td>${escapeHtml(itv.team_name)}</td>
|
||||
<td><span class="badge rounded-pill" style="background-color: ${STATUS_COLORS[itv.status_code] || DEFAULT_STATUS_COLOR}; color: ${idealTextColor(STATUS_COLORS[itv.status_code] || DEFAULT_STATUS_COLOR)};">${escapeHtml(itv.status_display)}</span></td>
|
||||
<td><span class="badge rounded-pill" style="background-color: ${getInterventionColor(itv.status_code, true)}; color: ${idealTextColor(getInterventionColor(itv.status_code, true))};">${escapeHtml(itv.status_display)}</span></td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('');
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -948,7 +948,22 @@
|
|||
// ---- Carte des interventions (modal) ----
|
||||
(function () {
|
||||
const MAPTILER_STYLE = window.MAP_STYLE_LIGHT;
|
||||
const STATUS_COLOR_EXPR = [
|
||||
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',
|
||||
|
|
@ -966,6 +981,7 @@
|
|||
'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'}}];
|
||||
|
||||
|
|
@ -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 ? `<div class="mt-1 text-muted small"><i class="bi bi-person-badge me-1"></i>${_esc(assignee)}</div>` : '';
|
||||
const badgeHtml = statusLabel ? `<span class="badge" style="background-color: ${statusColor}; color: #fff; font-size: 0.7rem;">${_esc(statusLabel)}</span>` : '';
|
||||
calPopup = new maplibregl.Popup({ maxWidth: '280px' })
|
||||
.setLngLat(e.lngLat)
|
||||
.setHTML(`<strong><a href="/interventions/${props.id}/" target="_blank">${props.code || ''}</a></strong><br><span class="text-muted small">${props.title || ''}</span>`)
|
||||
.setHTML(`
|
||||
<div class="d-flex align-items-center justify-content-between gap-2 mb-1">
|
||||
<strong><a href="/interventions/${props.id}/" target="_blank">${_esc(props.code || '')}</a></strong>
|
||||
${badgeHtml}
|
||||
</div>
|
||||
<span class="text-muted small">${_esc(props.title || '')}</span>
|
||||
${assigneeHtml}
|
||||
`)
|
||||
.addTo(calMap);
|
||||
});
|
||||
calMap.on('mouseenter', 'cal-ivt-circles', () => { calMap.getCanvas().style.cursor = 'pointer'; });
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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]),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue