diff --git a/loko/interventions/locale/en/LC_MESSAGES/django.mo b/loko/interventions/locale/en/LC_MESSAGES/django.mo index 0e5c610..1dcf27f 100644 Binary files a/loko/interventions/locale/en/LC_MESSAGES/django.mo and b/loko/interventions/locale/en/LC_MESSAGES/django.mo differ diff --git a/loko/interventions/locale/en/LC_MESSAGES/django.po b/loko/interventions/locale/en/LC_MESSAGES/django.po index 9e5956f..46bdf93 100644 --- a/loko/interventions/locale/en/LC_MESSAGES/django.po +++ b/loko/interventions/locale/en/LC_MESSAGES/django.po @@ -8240,3 +8240,137 @@ msgstr "Assignment update" msgid "Champs modifiés : %(fields)s" msgstr "Modified fields: %(fields)s" +#: interventions/models.py:1259 +msgid "Lien" +msgstr "Link" + +#: interventions/services/timeline_service.py:417 +#, python-format +msgid "Note ajoutée (%(type)s)" +msgstr "Note added (%(type)s)" + +#: interventions/services/timeline_service.py:418 +msgid "Ajout" +msgstr "Addition" + +#: interventions/services/timeline_service.py:423 +#, python-format +msgid "Note modifiée (%(type)s)" +msgstr "Note updated (%(type)s)" + +#: interventions/services/timeline_service.py:433 +msgid "Mise à jour" +msgstr "Update" + +#: interventions/services/timeline_service.py:435 +#, python-format +msgid "Note supprimée (%(type)s)" +msgstr "Note deleted (%(type)s)" + +#: interventions/services/timeline_service.py:436 +msgid "Suppression" +msgstr "Deletion" + +#: interventions/services/timeline_service.py:441 +#, python-format +msgid "Note : %(action)s" +msgstr "Note: %(action)s" + +#: interventions/services/timeline_service.py:465 +#, python-format +msgid "Document ajouté : %(name)s" +msgstr "Document added: %(name)s" + +#: interventions/services/timeline_service.py:467 +#, python-format +msgid "%(count)d documents ajoutés : %(names)s" +msgstr "%(count)d documents added: %(names)s" + +#: interventions/services/timeline_service.py:472 +msgid "Document(s) ajouté(s)" +msgstr "Added document(s)" + +#: interventions/services/timeline_service.py:478 +#, python-format +msgid "Document supprimé : %(name)s" +msgstr "Document deleted: %(name)s" + +#: interventions/services/timeline_service.py:480 +#, python-format +msgid "%(count)d documents supprimés : %(names)s" +msgstr "%(count)d documents deleted: %(names)s" + +#: interventions/services/timeline_service.py:485 +msgid "Document(s) supprimé(s)" +msgstr "Deleted document(s)" + +#: interventions/services/timeline_service.py:490 +#, python-format +msgid "Document : %(action)s" +msgstr "Document: %(action)s" + +#: interventions/services/timeline_service.py:549 +#, python-format +msgid "Équipement lié : %(name)s" +msgstr "Equipment linked: %(name)s" + +#: interventions/services/timeline_service.py:551 +#, python-format +msgid "%(count)d équipements liés : %(names)s" +msgstr "%(count)d equipment linked: %(names)s" + +#: interventions/services/timeline_service.py:557 +#, python-format +msgid "Équipement délié : %(name)s" +msgstr "Equipment unlinked: %(name)s" + +#: interventions/services/timeline_service.py:559 +#, python-format +msgid "%(count)d équipements déliés : %(names)s" +msgstr "%(count)d equipment unlinked: %(names)s" + +#: interventions/services/timeline_service.py:564 +#, python-format +msgid "Équipements modifiés (+%(added)d, -%(removed)d)" +msgstr "Equipment updated (+%(added)d, -%(removed)d)" + +#: interventions/services/timeline_service.py:570 +msgid "Équipement(s) lié(s)" +msgstr "Linked equipment" + +#: interventions/services/timeline_service.py:576 +msgid "Équipement(s) délié(s)" +msgstr "Unlinked equipment" + +#: interventions/services/timeline_service.py:603 +#, python-format +msgid "%(relation)s liée : %(target)s" +msgstr "Linked %(relation)s: %(target)s" + +#: interventions/services/timeline_service.py:608 +#, python-format +msgid "%(count)d %(relation)ss liées : %(targets)s" +msgstr "%(count)d linked %(relation)ss: %(targets)s" + +#: interventions/services/timeline_service.py:614 +#, python-format +msgid "%(relation)s liée(s)" +msgstr "Linked %(relation)s(s)" + +#: interventions/services/timeline_service.py:620 +#, python-format +msgid "%(relation)s déliée : %(target)s" +msgstr "Unlinked %(relation)s: %(target)s" + +#: interventions/services/timeline_service.py:625 +#, python-format +msgid "%(count)d %(relation)ss déliées : %(targets)s" +msgstr "%(count)d unlinked %(relation)ss: %(targets)s" + +#: interventions/services/timeline_service.py:631 +#, python-format +msgid "%(relation)s déliée(s)" +msgstr "Unlinked %(relation)s(s)" + + + diff --git a/loko/interventions/locale/fr/LC_MESSAGES/django.mo b/loko/interventions/locale/fr/LC_MESSAGES/django.mo index dc55020..7d226c0 100644 Binary files a/loko/interventions/locale/fr/LC_MESSAGES/django.mo and b/loko/interventions/locale/fr/LC_MESSAGES/django.mo differ diff --git a/loko/interventions/locale/fr/LC_MESSAGES/django.po b/loko/interventions/locale/fr/LC_MESSAGES/django.po index 8a2aa86..e5c7136 100644 --- a/loko/interventions/locale/fr/LC_MESSAGES/django.po +++ b/loko/interventions/locale/fr/LC_MESSAGES/django.po @@ -8347,3 +8347,137 @@ msgstr "Mise à jour de l'assignation" msgid "Champs modifiés : %(fields)s" msgstr "Champs modifiés : %(fields)s" +#: interventions/models.py:1259 +msgid "Lien" +msgstr "Lien" + +#: interventions/services/timeline_service.py:417 +#, python-format +msgid "Note ajoutée (%(type)s)" +msgstr "Note ajoutée (%(type)s)" + +#: interventions/services/timeline_service.py:418 +msgid "Ajout" +msgstr "Ajout" + +#: interventions/services/timeline_service.py:423 +#, python-format +msgid "Note modifiée (%(type)s)" +msgstr "Note modifiée (%(type)s)" + +#: interventions/services/timeline_service.py:433 +msgid "Mise à jour" +msgstr "Mise à jour" + +#: interventions/services/timeline_service.py:435 +#, python-format +msgid "Note supprimée (%(type)s)" +msgstr "Note supprimée (%(type)s)" + +#: interventions/services/timeline_service.py:436 +msgid "Suppression" +msgstr "Suppression" + +#: interventions/services/timeline_service.py:441 +#, python-format +msgid "Note : %(action)s" +msgstr "Note : %(action)s" + +#: interventions/services/timeline_service.py:465 +#, python-format +msgid "Document ajouté : %(name)s" +msgstr "Document ajouté : %(name)s" + +#: interventions/services/timeline_service.py:467 +#, python-format +msgid "%(count)d documents ajoutés : %(names)s" +msgstr "%(count)d documents ajoutés : %(names)s" + +#: interventions/services/timeline_service.py:472 +msgid "Document(s) ajouté(s)" +msgstr "Document(s) ajouté(s)" + +#: interventions/services/timeline_service.py:478 +#, python-format +msgid "Document supprimé : %(name)s" +msgstr "Document supprimé : %(name)s" + +#: interventions/services/timeline_service.py:480 +#, python-format +msgid "%(count)d documents supprimés : %(names)s" +msgstr "%(count)d documents supprimés : %(names)s" + +#: interventions/services/timeline_service.py:485 +msgid "Document(s) supprimé(s)" +msgstr "Document(s) supprimé(s)" + +#: interventions/services/timeline_service.py:490 +#, python-format +msgid "Document : %(action)s" +msgstr "Document : %(action)s" + +#: interventions/services/timeline_service.py:549 +#, python-format +msgid "Équipement lié : %(name)s" +msgstr "Équipement lié : %(name)s" + +#: interventions/services/timeline_service.py:551 +#, python-format +msgid "%(count)d équipements liés : %(names)s" +msgstr "%(count)d équipements liés : %(names)s" + +#: interventions/services/timeline_service.py:557 +#, python-format +msgid "Équipement délié : %(name)s" +msgstr "Équipement délié : %(name)s" + +#: interventions/services/timeline_service.py:559 +#, python-format +msgid "%(count)d équipements déliés : %(names)s" +msgstr "%(count)d équipements déliés : %(names)s" + +#: interventions/services/timeline_service.py:564 +#, python-format +msgid "Équipements modifiés (+%(added)d, -%(removed)d)" +msgstr "Équipements modifiés (+%(added)d, -%(removed)d)" + +#: interventions/services/timeline_service.py:570 +msgid "Équipement(s) lié(s)" +msgstr "Équipement(s) lié(s)" + +#: interventions/services/timeline_service.py:576 +msgid "Équipement(s) délié(s)" +msgstr "Équipement(s) délié(s)" + +#: interventions/services/timeline_service.py:603 +#, python-format +msgid "%(relation)s liée : %(target)s" +msgstr "%(relation)s liée : %(target)s" + +#: interventions/services/timeline_service.py:608 +#, python-format +msgid "%(count)d %(relation)ss liées : %(targets)s" +msgstr "%(count)d %(relation)ss liées : %(targets)s" + +#: interventions/services/timeline_service.py:614 +#, python-format +msgid "%(relation)s liée(s)" +msgstr "%(relation)s liée(s)" + +#: interventions/services/timeline_service.py:620 +#, python-format +msgid "%(relation)s déliée : %(target)s" +msgstr "%(relation)s déliée : %(target)s" + +#: interventions/services/timeline_service.py:625 +#, python-format +msgid "%(count)d %(relation)ss déliées : %(targets)s" +msgstr "%(count)d %(relation)ss déliées : %(targets)s" + +#: interventions/services/timeline_service.py:631 +#, python-format +msgid "%(relation)s déliée(s)" +msgstr "%(relation)s déliée(s)" + + + diff --git a/loko/interventions/locale/nl/LC_MESSAGES/django.mo b/loko/interventions/locale/nl/LC_MESSAGES/django.mo index 475ac13..3f33f2e 100644 Binary files a/loko/interventions/locale/nl/LC_MESSAGES/django.mo and b/loko/interventions/locale/nl/LC_MESSAGES/django.mo differ diff --git a/loko/interventions/locale/nl/LC_MESSAGES/django.po b/loko/interventions/locale/nl/LC_MESSAGES/django.po index 2263962..e52720a 100644 --- a/loko/interventions/locale/nl/LC_MESSAGES/django.po +++ b/loko/interventions/locale/nl/LC_MESSAGES/django.po @@ -8281,3 +8281,136 @@ msgstr "Toewijzing bijgewerkt" msgid "Champs modifiés : %(fields)s" msgstr "Gewijzigde velden: %(fields)s" +#: interventions/models.py:1259 +msgid "Lien" +msgstr "Koppeling" + +#: interventions/services/timeline_service.py:417 +#, python-format +msgid "Note ajoutée (%(type)s)" +msgstr "Notitie toegevoegd (%(type)s)" + +#: interventions/services/timeline_service.py:418 +msgid "Ajout" +msgstr "Toevoeging" + +#: interventions/services/timeline_service.py:423 +#, python-format +msgid "Note modifiée (%(type)s)" +msgstr "Notitie gewijzigd (%(type)s)" + +#: interventions/services/timeline_service.py:433 +msgid "Mise à jour" +msgstr "Bijwerking" + +#: interventions/services/timeline_service.py:435 +#, python-format +msgid "Note supprimée (%(type)s)" +msgstr "Notitie verwijderd (%(type)s)" + +#: interventions/services/timeline_service.py:436 +msgid "Suppression" +msgstr "Verwijdering" + +#: interventions/services/timeline_service.py:441 +#, python-format +msgid "Note : %(action)s" +msgstr "Notitie: %(action)s" + +#: interventions/services/timeline_service.py:465 +#, python-format +msgid "Document ajouté : %(name)s" +msgstr "Document toegevoegd: %(name)s" + +#: interventions/services/timeline_service.py:467 +#, python-format +msgid "%(count)d documents ajoutés : %(names)s" +msgstr "%(count)d documenten toegevoegd: %(names)s" + +#: interventions/services/timeline_service.py:472 +msgid "Document(s) ajouté(s)" +msgstr "Toegevoegd(e) document(en)" + +#: interventions/services/timeline_service.py:478 +#, python-format +msgid "Document supprimé : %(name)s" +msgstr "Document verwijderd: %(name)s" + +#: interventions/services/timeline_service.py:480 +#, python-format +msgid "%(count)d documents supprimés : %(names)s" +msgstr "%(count)d documenten verwijderd: %(names)s" + +#: interventions/services/timeline_service.py:485 +msgid "Document(s) supprimé(s)" +msgstr "Verwijderd(e) document(en)" + +#: interventions/services/timeline_service.py:490 +#, python-format +msgid "Document : %(action)s" +msgstr "Document: %(action)s" + +#: interventions/services/timeline_service.py:549 +#, python-format +msgid "Équipement lié : %(name)s" +msgstr "Apparaat gekoppeld: %(name)s" + +#: interventions/services/timeline_service.py:551 +#, python-format +msgid "%(count)d équipements liés : %(names)s" +msgstr "%(count)d apparaten gekoppeld: %(names)s" + +#: interventions/services/timeline_service.py:557 +#, python-format +msgid "Équipement délié : %(name)s" +msgstr "Apparaat ontkoppeld: %(name)s" + +#: interventions/services/timeline_service.py:559 +#, python-format +msgid "%(count)d équipements déliés : %(names)s" +msgstr "%(count)d apparaten ontkoppeld: %(names)s" + +#: interventions/services/timeline_service.py:564 +#, python-format +msgid "Équipements modifiés (+%(added)d, -%(removed)d)" +msgstr "Apparaten gewijzigd (+%(added)d, -%(removed)d)" + +#: interventions/services/timeline_service.py:570 +msgid "Équipement(s) lié(s)" +msgstr "Gekoppeld(e) appara(a)t(en)" + +#: interventions/services/timeline_service.py:576 +msgid "Équipement(s) délié(s)" +msgstr "Ontkoppeld(e) appara(a)t(en)" + +#: interventions/services/timeline_service.py:603 +#, python-format +msgid "%(relation)s liée : %(target)s" +msgstr "%(relation)s gekoppeld: %(target)s" + +#: interventions/services/timeline_service.py:608 +#, python-format +msgid "%(count)d %(relation)ss liées : %(targets)s" +msgstr "%(count)d %(relation)ss gekoppeld: %(targets)s" + +#: interventions/services/timeline_service.py:614 +#, python-format +msgid "%(relation)s liée(s)" +msgstr "Gekoppelde %(relation)s(s)" + +#: interventions/services/timeline_service.py:620 +#, python-format +msgid "%(relation)s déliée : %(target)s" +msgstr "%(relation)s ontkoppeld: %(target)s" + +#: interventions/services/timeline_service.py:625 +#, python-format +msgid "%(count)d %(relation)ss déliées : %(targets)s" +msgstr "%(count)d %(relation)ss ontkoppeld: %(targets)s" + +#: interventions/services/timeline_service.py:631 +#, python-format +msgid "%(relation)s déliée(s)" +msgstr "Ontkoppelde %(relation)s(s)" + + diff --git a/loko/interventions/migrations/0015_alter_interventiontimeline_event_type.py b/loko/interventions/migrations/0015_alter_interventiontimeline_event_type.py new file mode 100644 index 0000000..4018d8e --- /dev/null +++ b/loko/interventions/migrations/0015_alter_interventiontimeline_event_type.py @@ -0,0 +1,18 @@ +# Generated by Django 6.0.7 on 2026-09-25 19:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('interventions', '0014_interventiontimeline_event_description_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='interventiontimeline', + name='event_type', + field=models.CharField(choices=[('creation', 'Création'), ('status_change', 'Changement de statut'), ('transfer', 'Transfert'), ('assignment', 'Assignation'), ('modification', 'Modification'), ('note', 'Note'), ('document', 'Document'), ('link', 'Lien')]), + ), + ] diff --git a/loko/interventions/models.py b/loko/interventions/models.py index 6bb4156..47c4c3b 100644 --- a/loko/interventions/models.py +++ b/loko/interventions/models.py @@ -1254,6 +1254,9 @@ ACTION_TYPE_CHOICES = { 'transfer': _('Transfert'), 'assignment': _('Assignation'), 'modification': _('Modification'), + 'note': _('Note'), + 'document': _('Document'), + 'link': _('Lien'), } STATUS_COLOR_MAP = { @@ -1295,6 +1298,18 @@ class InterventionTimeLine(models.Model): def is_modification(self): return self.event_type == 'modification' + @property + def is_note(self): + return self.event_type == 'note' + + @property + def is_document(self): + return self.event_type == 'document' + + @property + def is_link(self): + return self.event_type == 'link' + @property def is_status_change(self): return self.event_type == 'status_change' @@ -1320,6 +1335,9 @@ class InterventionTimeLine(models.Model): 'assignment': 'bg-info text-dark', 'transfer': 'bg-warning text-dark', 'modification': 'bg-secondary', + 'note': 'bg-info text-dark', + 'document': 'bg-secondary', + 'link': 'bg-dark', }.get(self.event_type, 'bg-secondary') def get_icon(self): @@ -1329,6 +1347,9 @@ class InterventionTimeLine(models.Model): 'assignment': 'bi-person-check-fill', 'transfer': 'bi-arrow-left-right', 'modification': 'bi-pencil-fill', + 'note': 'bi-chat-left-text-fill', + 'document': 'bi-paperclip', + 'link': 'bi-link-45deg', }.get(self.event_type, 'bi-dot') diff --git a/loko/interventions/services/timeline_service.py b/loko/interventions/services/timeline_service.py index 9dafed7..7008b7e 100644 --- a/loko/interventions/services/timeline_service.py +++ b/loko/interventions/services/timeline_service.py @@ -8,6 +8,7 @@ from django.contrib.auth import get_user_model from interventions.models import ( Intervention, InterventionTimeLine, + InterventionNote, MAINTAIN_CHOICES, ORIGIN_TYPE_CHOICES, STATUS_CHOICES, @@ -391,3 +392,291 @@ class InterventionChangeTracker: ) return assignment_event, modification_event + + +def truncate_text(text: Optional[str], max_len: int = 120) -> str: + """Tronque un texte pour un affichage concis dans l'historique.""" + if not text: + return "" + text = text.strip().replace('\r\n', ' ').replace('\n', ' ') + if len(text) <= max_len: + return text + return text[:max_len - 3] + "..." + + +def record_note_event( + intervention: Intervention, + user, + action: str, # 'create', 'update', 'delete' + note_type: str, + content: str, + old_note_type: Optional[str] = None, + old_content: Optional[str] = None, + event_time=None, +) -> InterventionTimeLine: + """Enregistre un événement lié aux notes dans la chronologie.""" + type_choices = dict(InterventionNote.NoteType.choices) + type_label = type_choices.get(note_type, note_type) + old_type_label = type_choices.get(old_note_type, old_note_type) if old_note_type else None + + changes = {} + if action == 'create': + desc = _("Note ajoutée (%(type)s)") % {'type': type_label} + changes['note_action'] = {'label': str(_("Action")), 'old': None, 'new': str(_("Ajout"))} + changes['note_type'] = {'label': str(_("Type de note")), 'old': None, 'new': str(type_label)} + if content: + changes['content'] = {'label': str(_("Contenu")), 'old': None, 'new': truncate_text(content)} + elif action == 'update': + desc = _("Note modifiée (%(type)s)") % {'type': type_label} + if old_note_type and old_note_type != note_type: + changes['note_type'] = {'label': str(_("Type de note")), 'old': str(old_type_label), 'new': str(type_label)} + if old_content is not None and old_content != content: + changes['content'] = { + 'label': str(_("Contenu")), + 'old': truncate_text(old_content), + 'new': truncate_text(content), + } + if not changes: + changes['note_action'] = {'label': str(_("Action")), 'old': None, 'new': str(_("Mise à jour"))} + elif action == 'delete': + desc = _("Note supprimée (%(type)s)") % {'type': type_label} + changes['note_action'] = {'label': str(_("Action")), 'old': str(_("Suppression")), 'new': None} + changes['note_type'] = {'label': str(_("Type de note")), 'old': str(type_label), 'new': None} + if content: + changes['content'] = {'label': str(_("Contenu")), 'old': truncate_text(content), 'new': None} + else: + desc = _("Note : %(action)s") % {'action': action} + + return record_timeline_event( + intervention=intervention, + user=user, + event_type='note', + description=str(desc), + field_changes=changes, + event_time=event_time, + ) + + +def record_document_event( + intervention: Intervention, + user, + action: str, # 'upload', 'delete' + filenames: list, + event_time=None, +) -> Optional[InterventionTimeLine]: + """Enregistre un événement de document (téléversement ou suppression).""" + if not filenames: + return None + + import os + clean_filenames = [os.path.basename(str(f)) for f in filenames if f] + if not clean_filenames: + return None + + changes = {} + names_str = ", ".join(clean_filenames) + + if action == 'upload': + if len(clean_filenames) == 1: + desc = _("Document ajouté : %(name)s") % {'name': clean_filenames[0]} + else: + desc = _("%(count)d documents ajoutés : %(names)s") % { + 'count': len(clean_filenames), + 'names': names_str, + } + changes['documents'] = { + 'label': str(_("Document(s) ajouté(s)")), + 'old': None, + 'new': names_str, + } + elif action == 'delete': + if len(clean_filenames) == 1: + desc = _("Document supprimé : %(name)s") % {'name': clean_filenames[0]} + else: + desc = _("%(count)d documents supprimés : %(names)s") % { + 'count': len(clean_filenames), + 'names': names_str, + } + changes['documents'] = { + 'label': str(_("Document(s) supprimé(s)")), + 'old': names_str, + 'new': None, + } + else: + desc = _("Document : %(action)s") % {'action': action} + + return record_timeline_event( + intervention=intervention, + user=user, + event_type='document', + description=str(desc), + field_changes=changes, + event_time=event_time, + ) + + +def resolve_asset_label(asset_obj, ct=None) -> str: + """Retourne une désignation lisible d'un équipement.""" + if not asset_obj: + return "" + if hasattr(asset_obj, 'get_name') and callable(asset_obj.get_name): + name = asset_obj.get_name() + elif hasattr(asset_obj, 'name') and asset_obj.name: + name = str(asset_obj.name) + else: + name = str(asset_obj) + + if ct and hasattr(ct, 'name') and ct.name: + return f"{name} ({ct.name})" + return str(name) + + +def get_asset_labels_from_pairs(pairs) -> list: + """Résout une collection de (content_type_id, object_id) en liste de désignations lisibles.""" + if not pairs: + return [] + from collections import defaultdict + from django.contrib.contenttypes.models import ContentType + + by_ct = defaultdict(set) + for ct_id, obj_id in pairs: + by_ct[ct_id].add(obj_id) + + labels = [] + ct_map = {ct.id: ct for ct in ContentType.objects.filter(id__in=by_ct.keys())} + for ct_id, obj_ids in by_ct.items(): + ct = ct_map.get(ct_id) + if not ct: + continue + model_class = ct.model_class() + if not model_class: + continue + obj_map = {obj.pk: obj for obj in model_class.objects.filter(pk__in=obj_ids)} + for obj_id in obj_ids: + asset_obj = obj_map.get(obj_id) + if asset_obj: + labels.append(resolve_asset_label(asset_obj, ct)) + else: + labels.append(f"{ct.model} #{obj_id}") + return labels + + +def record_asset_link_event( + intervention: Intervention, + user, + added_asset_names: Optional[list] = None, + removed_asset_names: Optional[list] = None, + event_time=None, +) -> Optional[InterventionTimeLine]: + """Enregistre un événement de liaison / déliaison d'équipements (assets).""" + added_names = [str(n) for n in (added_asset_names or []) if n] + removed_names = [str(n) for n in (removed_asset_names or []) if n] + + if not added_names and not removed_names: + return None + + changes = {} + if added_names and not removed_names: + if len(added_names) == 1: + desc = _("Équipement lié : %(name)s") % {'name': added_names[0]} + else: + desc = _("%(count)d équipements liés : %(names)s") % { + 'count': len(added_names), + 'names': ", ".join(added_names), + } + elif removed_names and not added_names: + if len(removed_names) == 1: + desc = _("Équipement délié : %(name)s") % {'name': removed_names[0]} + else: + desc = _("%(count)d équipements déliés : %(names)s") % { + 'count': len(removed_names), + 'names': ", ".join(removed_names), + } + else: + desc = _("Équipements modifiés (+%(added)d, -%(removed)d)") % { + 'added': len(added_names), + 'removed': len(removed_names), + } + + if added_names: + changes['linked_assets'] = { + 'label': str(_("Équipement(s) lié(s)")), + 'old': None, + 'new': ", ".join(added_names), + } + if removed_names: + changes['unlinked_assets'] = { + 'label': str(_("Équipement(s) délié(s)")), + 'old': ", ".join(removed_names), + 'new': None, + } + + return record_timeline_event( + intervention=intervention, + user=user, + event_type='link', + description=str(desc), + field_changes=changes, + event_time=event_time, + ) + + +def record_relation_link_event( + intervention: Intervention, + user, + relation_label: str, + target_labels: list, + action: str = 'link', + event_time=None, +) -> Optional[InterventionTimeLine]: + """Enregistre un événement de liaison avec un autre élément (ex: intervention liée).""" + targets = [str(t) for t in target_labels if t] + if not targets: + return None + + targets_str = ", ".join(targets) + changes = {} + + if action == 'link': + if len(targets) == 1: + desc = _("%(relation)s liée : %(target)s") % { + 'relation': relation_label, + 'target': targets[0], + } + else: + desc = _("%(count)d %(relation)ss liées : %(targets)s") % { + 'count': len(targets), + 'relation': relation_label, + 'targets': targets_str, + } + changes['linked_items'] = { + 'label': str(_("%(relation)s liée(s)") % {'relation': relation_label}), + 'old': None, + 'new': targets_str, + } + else: + if len(targets) == 1: + desc = _("%(relation)s déliée : %(target)s") % { + 'relation': relation_label, + 'target': targets[0], + } + else: + desc = _("%(count)d %(relation)ss déliées : %(targets)s") % { + 'count': len(targets), + 'relation': relation_label, + 'targets': targets_str, + } + changes['unlinked_items'] = { + 'label': str(_("%(relation)s déliée(s)") % {'relation': relation_label}), + 'old': targets_str, + 'new': None, + } + + return record_timeline_event( + intervention=intervention, + user=user, + event_type='link', + description=str(desc), + field_changes=changes, + event_time=event_time, + ) diff --git a/loko/interventions/static/interventions/timeline.css b/loko/interventions/static/interventions/timeline.css index e27fa1d..742d99a 100644 --- a/loko/interventions/static/interventions/timeline.css +++ b/loko/interventions/static/interventions/timeline.css @@ -200,11 +200,24 @@ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.07); } -.history-item-modification .history-timeline-card { +.history-item-modification .history-timeline-card, +.history-item-detailed .history-timeline-card { background-color: #fafbfc; border-left: 3px solid #94a3b8; } +.history-timeline-item[data-event-type="note"] .history-timeline-card { + border-left: 3px solid #0dcaf0; +} + +.history-timeline-item[data-event-type="document"] .history-timeline-card { + border-left: 3px solid #6c757d; +} + +.history-timeline-item[data-event-type="link"] .history-timeline-card { + border-left: 3px solid #212529; +} + .history-changes { background: #f8fafc; border-radius: 6px; diff --git a/loko/interventions/templates/interventions/detail/card_chronologie.html b/loko/interventions/templates/interventions/detail/card_chronologie.html index 8e97430..ad3ac8e 100644 --- a/loko/interventions/templates/interventions/detail/card_chronologie.html +++ b/loko/interventions/templates/interventions/detail/card_chronologie.html @@ -29,8 +29,8 @@ onclick="switchHistoryTimeline('full')" title="{% translate 'Afficher toutes les modifications détaillées' %}"> {% translate "Historique complet" %} - {% if timeline_modification_count %} - +{{ timeline_modification_count }} + {% if timeline_detailed_count or timeline_modification_count %} + +{{ timeline_detailed_count|default:timeline_modification_count }} {% endif %} @@ -42,7 +42,7 @@ {% if events_list %}