192 lines
6.7 KiB
Python
192 lines
6.7 KiB
Python
"""
|
|
Commande Django pour importer des interventions préventives depuis un fichier Excel.
|
|
|
|
Format du fichier Excel :
|
|
- B1 : code de la thématique (ex: 'structures')
|
|
- B2 : code du contrat (ex: 'CONT-2024-001')
|
|
- B3 : code de la commande/order (ex: 'ORD-2025-001')
|
|
- Ligne 5 : en-têtes de colonnes = codes de localisations (B5, C5, D5, …)
|
|
- Colonne A à partir de la ligne 6 : codes de templates d'opérations (A6, A7, …)
|
|
- Cellules B6 et au-delà : 'x', '1' ou 'v' (insensible à la casse) = créer une intervention
|
|
|
|
Usage :
|
|
python manage.py import_preventive_interventions --file scripts/data/preventif_tunnels.xlsx
|
|
python manage.py import_preventive_interventions --file scripts/data/preventif_tunnels.xlsx --dry-run
|
|
python manage.py import_preventive_interventions --file scripts/data/preventif_tunnels.xlsx \\
|
|
--user admin --begin 2026-01-01 --end 2026-12-31 --status in_preparation
|
|
"""
|
|
|
|
import os
|
|
from datetime import datetime
|
|
|
|
from django.core.management.base import BaseCommand, CommandError
|
|
from django.contrib.auth import get_user_model
|
|
from django.utils import timezone
|
|
|
|
try:
|
|
import openpyxl
|
|
except ImportError:
|
|
openpyxl = None
|
|
|
|
from interventions.views.preventive_import import process_worksheet
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
class Command(BaseCommand):
|
|
help = (
|
|
"Importe des interventions préventives depuis un fichier Excel croisé "
|
|
"(lignes = templates, colonnes = localisations). "
|
|
"Crée également les opérations génériques et les opérations équipement "
|
|
"pour les assets enfants des localisations."
|
|
)
|
|
|
|
def add_arguments(self, parser):
|
|
parser.add_argument(
|
|
"--file",
|
|
required=True,
|
|
metavar="CHEMIN_FICHIER",
|
|
help="Chemin vers le fichier Excel (.xlsx). Ex: scripts/data/preventif_tunnels.xlsx",
|
|
)
|
|
parser.add_argument(
|
|
"--dry-run",
|
|
action="store_true",
|
|
default=False,
|
|
help="Simule l'import sans écrire en base de données.",
|
|
)
|
|
parser.add_argument(
|
|
"--user",
|
|
default=None,
|
|
metavar="USERNAME",
|
|
help="Nom d'utilisateur (created_by) pour les interventions créées.",
|
|
)
|
|
parser.add_argument(
|
|
"--begin",
|
|
default=None,
|
|
metavar="YYYY-MM-DD",
|
|
help="Date de début prévisionnelle des interventions (ex: 2026-01-01).",
|
|
)
|
|
parser.add_argument(
|
|
"--end",
|
|
default=None,
|
|
metavar="YYYY-MM-DD",
|
|
help="Date de fin prévisionnelle des interventions (ex: 2026-12-31).",
|
|
)
|
|
parser.add_argument(
|
|
"--status",
|
|
default="in_preparation",
|
|
metavar="STATUS",
|
|
help=(
|
|
"Statut des interventions créées. "
|
|
"Valeurs : in_preparation, to_be_planned, to_be_processed, … "
|
|
"(défaut : in_preparation)"
|
|
),
|
|
)
|
|
parser.add_argument(
|
|
"--sheet",
|
|
default=0,
|
|
metavar="SHEET",
|
|
help="Nom ou index (0-basé) de la feuille Excel à lire (défaut : 0 = première feuille).",
|
|
)
|
|
|
|
def handle(self, *args, **options):
|
|
if openpyxl is None:
|
|
raise CommandError(
|
|
"Le module 'openpyxl' est requis. Installez-le avec : pip install openpyxl"
|
|
)
|
|
|
|
file_path = options["file"]
|
|
dry_run = options["dry_run"]
|
|
username = options["user"]
|
|
status = options["status"]
|
|
sheet_ref = options["sheet"]
|
|
|
|
# --- Dates ---
|
|
expected_begin = self._parse_date(options["begin"], "--begin")
|
|
expected_end = self._parse_date(options["end"], "--end")
|
|
if (expected_begin is None) != (expected_end is None):
|
|
raise CommandError("--begin et --end doivent être fournis ensemble ou pas du tout.")
|
|
|
|
# --- Fichier ---
|
|
if not os.path.isabs(file_path):
|
|
file_path = os.path.join(os.getcwd(), file_path)
|
|
if not os.path.exists(file_path):
|
|
raise CommandError(f"Fichier introuvable : {file_path}")
|
|
|
|
# --- Utilisateur ---
|
|
user = None
|
|
if username:
|
|
try:
|
|
user = User.objects.get(username=username)
|
|
except User.DoesNotExist:
|
|
raise CommandError(f"Utilisateur introuvable : '{username}'")
|
|
|
|
# --- Lecture du fichier Excel ---
|
|
self.stdout.write(f"Lecture du fichier : {file_path}")
|
|
wb = openpyxl.load_workbook(file_path, data_only=True)
|
|
|
|
# --- Sélection de la feuille ---
|
|
try:
|
|
sheet_idx = int(sheet_ref)
|
|
ws = wb.worksheets[sheet_idx]
|
|
except (ValueError, TypeError):
|
|
if sheet_ref not in wb.sheetnames:
|
|
raise CommandError(
|
|
f"Feuille '{sheet_ref}' introuvable. Feuilles disponibles : {wb.sheetnames}"
|
|
)
|
|
ws = wb[sheet_ref]
|
|
except IndexError:
|
|
raise CommandError(
|
|
f"Index de feuille {sheet_ref} invalide. "
|
|
f"Le fichier contient {len(wb.worksheets)} feuille(s)."
|
|
)
|
|
|
|
self.stdout.write(f"Feuille : '{ws.title}'")
|
|
|
|
# --- Délégation au service ---
|
|
result = process_worksheet(
|
|
ws=ws,
|
|
user=user,
|
|
expected_begin=expected_begin,
|
|
expected_end=expected_end,
|
|
status=status,
|
|
dry_run=dry_run,
|
|
log=self.stdout.write,
|
|
)
|
|
|
|
# --- Affichage des avertissements ---
|
|
for w in result.warnings:
|
|
self.stderr.write(self.style.WARNING(w))
|
|
for e in result.errors:
|
|
self.stderr.write(self.style.ERROR(e))
|
|
|
|
# --- Résumé ---
|
|
self.stdout.write("")
|
|
if dry_run:
|
|
self.stdout.write(
|
|
self.style.WARNING(
|
|
f"[DRY-RUN] {result.created} intervention(s) auraient été créées. "
|
|
"Aucune donnée écrite en base."
|
|
)
|
|
)
|
|
else:
|
|
self.stdout.write(
|
|
self.style.SUCCESS(f"{result.created} intervention(s) créée(s) avec succès.")
|
|
)
|
|
if result.skipped:
|
|
self.stdout.write(
|
|
f"{result.skipped} cellule(s) ignorée(s) "
|
|
"(vides ou localisation/template manquant)."
|
|
)
|
|
|
|
@staticmethod
|
|
def _parse_date(value, arg_name: str):
|
|
if not value:
|
|
return None
|
|
try:
|
|
d = datetime.strptime(value, "%Y-%m-%d")
|
|
return timezone.make_aware(d)
|
|
except ValueError:
|
|
raise CommandError(
|
|
f"Format de date invalide pour {arg_name} : '{value}'. Attendu : YYYY-MM-DD"
|
|
)
|