feat(patching/import): ajout colonnes Resp Domaine DTS, Referent technique, Mode operatoire, Impacts, BDD - support nouveau format S07+ + Date au lieu de Jour

This commit is contained in:
Pierre & Lumière 2026-05-04 13:12:09 +02:00
parent 13a5625710
commit 488b5a980b
3 changed files with 70 additions and 23 deletions

View File

@ -23,24 +23,31 @@ from ..config import APP_NAME
router = APIRouter() router = APIRouter()
templates = Jinja2Templates(directory="app/templates") templates = Jinja2Templates(directory="app/templates")
# Colonnes attendues dans les feuilles Sxx (ordre indicatif, on matche par regex/lower) # Colonnes attendues dans les feuilles Sxx (ordre = priorité, on matche par regex/lower)
# Le fichier 2026 a 12 variantes d'en-têtes selon la semaine
# (ancien format S02-S06, nouveau format DTS S07+)
KNOWN_COLUMNS = { KNOWN_COLUMNS = {
"asset_name": [r"asset\s*name", r"\bnom\b"], "asset_name": [r"asset\s*name", r"\bnom\b"],
"intervenant": [r"intervenant"], "intervenant": [r"intervenant"],
"environnement": [r"environnement|environement"], "environnement": [r"environnement|environement"],
"domaine": [r"^domaine"], "domaine": [r"^domaine"],
"os": [r"^\s*os\s*$"], "os": [r"^\s*os\s*$"],
"os_version": [r"version\s*os"], "os_version": [r"version\s*os"], # matche "Version OS" et "Version OS->Nom"
"application_name": [r"application", r"logiciel"], "application_name": [r"logiciel", r"application", r"^nom\s*complet$"],
"valideur_ra": [r"valideur"], "valideur_ra": [r"valideur"],
"description": [r"description"], "responsable_domaine_dts":[r"responsable\s*domaine"],
"assistant": [r"^assistant"], "description": [r"description"],
"commentaire": [r"commentaire|impact"], "assistant": [r"^assistant"],
"duree_coupure": [r"dur.+coupure"], "referent_technique": [r"r.f.rent\s*tech"],
"jour": [r"^\s*jour\s*$|date\s*pr.+vis"], "mode_operatoire": [r"mode\s*op.ratoire"],
"heure": [r"^\s*heure"], "impacts": [r"^impact"],
"pb_espace_disque": [r"espace\s*disque"], "commentaire": [r"commentaire"],
"date_patch_realise":[r"date\s*du?\s*patch.+r.+alis"], "base_de_donnees": [r"base\s*de\s*donn"],
"duree_coupure": [r"dur.+coupure"],
"jour": [r"^\s*jour\s*$", r"^\s*date\s*$", r"date\s*pr.+vis"],
"heure": [r"^\s*heure"],
"pb_espace_disque": [r"espace\s*disque"],
"date_patch_realise": [r"date\s*du?\s*patch.+r.+alis"],
} }
SHEET_WEEK_RE = re.compile(r"^S\s*0?(\d+)$", re.IGNORECASE) SHEET_WEEK_RE = re.compile(r"^S\s*0?(\d+)$", re.IGNORECASE)
@ -228,9 +235,11 @@ async def import_sheet_json(request: Request, import_id: int, sheet_name: str,
rows = db.execute(text(""" rows = db.execute(text("""
SELECT r.id, r.row_index, r.asset_name, r.intervenant, r.environnement, SELECT r.id, r.row_index, r.asset_name, r.intervenant, r.environnement,
r.domaine, r.os, r.os_version, r.application_name, r.valideur_ra, r.domaine, r.os, r.os_version, r.application_name,
r.description, r.assistant, r.commentaire, r.duree_coupure, r.valideur_ra, r.responsable_domaine_dts,
r.jour, r.heure, r.pb_espace_disque, r.date_patch_realise, r.description, r.assistant, r.referent_technique,
r.mode_operatoire, r.impacts, r.commentaire, r.base_de_donnees,
r.duree_coupure, r.jour, r.heure, r.pb_espace_disque, r.date_patch_realise,
r.server_id, s.hostname as resolved_hostname r.server_id, s.hostname as resolved_hostname
FROM patch_planning_import_rows r FROM patch_planning_import_rows r
LEFT JOIN servers s ON s.id = r.server_id LEFT JOIN servers s ON s.id = r.server_id
@ -251,9 +260,14 @@ async def import_sheet_json(request: Request, import_id: int, sheet_name: str,
"os_version": r.os_version, "os_version": r.os_version,
"application_name": r.application_name, "application_name": r.application_name,
"valideur_ra": r.valideur_ra, "valideur_ra": r.valideur_ra,
"responsable_domaine_dts": r.responsable_domaine_dts,
"description": r.description, "description": r.description,
"assistant": r.assistant, "assistant": r.assistant,
"referent_technique": r.referent_technique,
"mode_operatoire": r.mode_operatoire,
"impacts": r.impacts,
"commentaire": r.commentaire, "commentaire": r.commentaire,
"base_de_donnees": r.base_de_donnees,
"duree_coupure": r.duree_coupure, "duree_coupure": r.duree_coupure,
"jour": r.jour.isoformat() if r.jour else None, "jour": r.jour.isoformat() if r.jour else None,
"heure": r.heure, "heure": r.heure,
@ -338,13 +352,17 @@ async def import_upload(request: Request, db=Depends(get_db),
INSERT INTO patch_planning_import_rows ( INSERT INTO patch_planning_import_rows (
import_id, sheet_name, week_number, row_index, import_id, sheet_name, week_number, row_index,
asset_name, intervenant, environnement, domaine, os, os_version, asset_name, intervenant, environnement, domaine, os, os_version,
application_name, valideur_ra, description, assistant, commentaire, application_name, valideur_ra, responsable_domaine_dts,
description, assistant, referent_technique, mode_operatoire, impacts,
commentaire, base_de_donnees,
duree_coupure, jour, heure, pb_espace_disque, date_patch_realise, duree_coupure, jour, heure, pb_espace_disque, date_patch_realise,
raw_data, server_id raw_data, server_id
) VALUES ( ) VALUES (
:imp, :sn, :wn, :ri, :imp, :sn, :wn, :ri,
:an, :it, :en, :do, :os, :ov, :an, :it, :en, :do, :os, :ov,
:ap, :vr, :de, :as_, :co, :ap, :vr, :rd,
:de, :as_, :rt, :mo, :im,
:co, :bdd,
:dc, :jr, :hr, :pb, :dpr, :dc, :jr, :hr, :pb, :dpr,
:raw, :sid :raw, :sid
) )
@ -358,9 +376,14 @@ async def import_upload(request: Request, db=Depends(get_db),
"ov": str(rec.get("os_version")) if rec.get("os_version") else None, "ov": str(rec.get("os_version")) if rec.get("os_version") else None,
"ap": str(rec.get("application_name")) if rec.get("application_name") else None, "ap": str(rec.get("application_name")) if rec.get("application_name") else None,
"vr": str(rec.get("valideur_ra")) if rec.get("valideur_ra") else None, "vr": str(rec.get("valideur_ra")) if rec.get("valideur_ra") else None,
"rd": str(rec.get("responsable_domaine_dts")) if rec.get("responsable_domaine_dts") else None,
"de": str(rec.get("description")) if rec.get("description") else None, "de": str(rec.get("description")) if rec.get("description") else None,
"as_": str(rec.get("assistant")) if rec.get("assistant") else None, "as_": str(rec.get("assistant")) if rec.get("assistant") else None,
"rt": str(rec.get("referent_technique")) if rec.get("referent_technique") else None,
"mo": str(rec.get("mode_operatoire")) if rec.get("mode_operatoire") else None,
"im": str(rec.get("impacts")) if rec.get("impacts") else None,
"co": str(rec.get("commentaire")) if rec.get("commentaire") else None, "co": str(rec.get("commentaire")) if rec.get("commentaire") else None,
"bdd": str(rec.get("base_de_donnees")) if rec.get("base_de_donnees") else None,
"dc": str(rec.get("duree_coupure")) if rec.get("duree_coupure") else None, "dc": str(rec.get("duree_coupure")) if rec.get("duree_coupure") else None,
"jr": _coerce_date(rec.get("jour")), "jr": _coerce_date(rec.get("jour")),
"hr": str(rec.get("heure")) if rec.get("heure") else None, "hr": str(rec.get("heure")) if rec.get("heure") else None,

View File

@ -137,10 +137,16 @@
<th class="text-left p-1">Env</th> <th class="text-left p-1">Env</th>
<th class="text-left p-1">Domaine</th> <th class="text-left p-1">Domaine</th>
<th class="text-left p-1">OS</th> <th class="text-left p-1">OS</th>
<th class="text-left p-1">Version OS</th>
<th class="text-left p-1">Application</th> <th class="text-left p-1">Application</th>
<th class="text-left p-1">Intervenant</th> <th class="text-left p-1">Intervenant</th>
<th class="text-left p-1">Valideur RA</th> <th class="text-left p-1">Valideur RA</th>
<th class="text-left p-1">Jour</th> <th class="text-left p-1">Resp. Domaine DTS</th>
<th class="text-left p-1">Référent tech.</th>
<th class="text-left p-1">Mode op.</th>
<th class="text-left p-1">Impacts</th>
<th class="text-left p-1">BDD</th>
<th class="text-left p-1">Date</th>
<th class="text-left p-1">Heure</th> <th class="text-left p-1">Heure</th>
<th class="text-left p-1">Coupure</th> <th class="text-left p-1">Coupure</th>
<th class="text-left p-1">Pb disque</th> <th class="text-left p-1">Pb disque</th>
@ -246,9 +252,15 @@
+ '<td class="p-1">' + escapeHTML(r.environnement||'') + '</td>' + '<td class="p-1">' + escapeHTML(r.environnement||'') + '</td>'
+ '<td class="p-1">' + escapeHTML(r.domaine||'') + '</td>' + '<td class="p-1">' + escapeHTML(r.domaine||'') + '</td>'
+ '<td class="p-1">' + escapeHTML(r.os||'') + '</td>' + '<td class="p-1">' + escapeHTML(r.os||'') + '</td>'
+ '<td class="p-1">' + escapeHTML(r.os_version||'') + '</td>'
+ '<td class="p-1">' + escapeHTML(r.application_name||'') + '</td>' + '<td class="p-1">' + escapeHTML(r.application_name||'') + '</td>'
+ '<td class="p-1">' + escapeHTML(r.intervenant||'') + '</td>' + '<td class="p-1">' + escapeHTML(r.intervenant||'') + '</td>'
+ '<td class="p-1">' + escapeHTML(r.valideur_ra||'') + '</td>' + '<td class="p-1">' + escapeHTML(r.valideur_ra||'') + '</td>'
+ '<td class="p-1">' + escapeHTML(r.responsable_domaine_dts||'') + '</td>'
+ '<td class="p-1">' + escapeHTML(r.referent_technique||'') + '</td>'
+ '<td class="p-1">' + escapeHTML(r.mode_operatoire||'') + '</td>'
+ '<td class="p-1">' + escapeHTML(r.impacts||'') + '</td>'
+ '<td class="p-1">' + escapeHTML(r.base_de_donnees||'') + '</td>'
+ '<td class="p-1">' + escapeHTML(r.jour||'') + '</td>' + '<td class="p-1">' + escapeHTML(r.jour||'') + '</td>'
+ '<td class="p-1">' + escapeHTML(r.heure||'') + '</td>' + '<td class="p-1">' + escapeHTML(r.heure||'') + '</td>'
+ '<td class="p-1">' + escapeHTML(r.duree_coupure||'') + '</td>' + '<td class="p-1">' + escapeHTML(r.duree_coupure||'') + '</td>'

View File

@ -0,0 +1,12 @@
-- Migration v2 : ajout des colonnes manquantes dans patch_planning_import_rows
-- (variations d'en-têtes selon semaines : nouveau format DTS introduit en cours d'année)
-- Idempotent
ALTER TABLE public.patch_planning_import_rows
ADD COLUMN IF NOT EXISTS responsable_domaine_dts text,
ADD COLUMN IF NOT EXISTS referent_technique text,
ADD COLUMN IF NOT EXISTS mode_operatoire text,
ADD COLUMN IF NOT EXISTS impacts text,
ADD COLUMN IF NOT EXISTS base_de_donnees text;
-- Pas de nouveau GRANT nécessaire (la table est déjà accessible à patchcenter)