160 lines
5.3 KiB
Python
160 lines
5.3 KiB
Python
"""Import tour de garde SecOps depuis Tour de garde secops_2026.xlsx.
|
|
|
|
Lit la feuille 'Tour de garde', UPSERT dans secops_duty.
|
|
|
|
Usage:
|
|
python tools/import_tour_de_garde_xlsx.py [xlsx] [--truncate]
|
|
"""
|
|
import os
|
|
import sys
|
|
import re
|
|
import glob
|
|
from pathlib import Path
|
|
from datetime import date, datetime, timedelta
|
|
|
|
import openpyxl
|
|
from sqlalchemy import create_engine, text
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
DATABASE_URL = (os.getenv("DATABASE_URL_DEMO")
|
|
or os.getenv("DATABASE_URL")
|
|
or "postgresql://patchcenter:PatchCenter2026!@localhost:5432/patchcenter_db")
|
|
|
|
|
|
def parse_dates(c_val):
|
|
if not c_val:
|
|
return None, None
|
|
s = str(c_val).strip()
|
|
m = re.search(r"(\d{2})/(\d{2})/(\d{4}).*?(\d{2})/(\d{2})/(\d{4})", s)
|
|
if m:
|
|
d1 = date(int(m.group(3)), int(m.group(2)), int(m.group(1)))
|
|
d2 = date(int(m.group(6)), int(m.group(5)), int(m.group(4)))
|
|
return d1, d2
|
|
return None, None
|
|
|
|
|
|
def s(val):
|
|
if val is None:
|
|
return None
|
|
t = str(val).strip()
|
|
return t or None
|
|
|
|
|
|
def find_xlsx():
|
|
for p in [
|
|
ROOT / "deploy" / "Tour de garde secops_2026.xlsx",
|
|
ROOT / "deploy" / "Tour_de_garde_secops_2026.xlsx",
|
|
]:
|
|
if p.exists():
|
|
return str(p)
|
|
hits = glob.glob(str(ROOT / "deploy" / "*our*garde*.xlsx"))
|
|
return hits[0] if hits else None
|
|
|
|
|
|
def parse_tour_de_garde(xlsx_path):
|
|
wb = openpyxl.load_workbook(xlsx_path, data_only=True)
|
|
ws_name = next((n for n in wb.sheetnames if "garde" in n.lower()), None)
|
|
if not ws_name:
|
|
raise SystemExit(f"[ERR] Sheet 'Tour de garde' introuvable. Sheets: {wb.sheetnames}")
|
|
ws = wb[ws_name]
|
|
|
|
rows = []
|
|
for i, row in enumerate(ws.iter_rows(values_only=True)):
|
|
if i == 0:
|
|
continue
|
|
week_code = row[0]
|
|
if not week_code or not str(week_code).strip().startswith("S"):
|
|
continue
|
|
|
|
wc = str(week_code).strip()
|
|
m = re.match(r"S(\d+)", wc)
|
|
if not m:
|
|
continue
|
|
week_num = int(m.group(1))
|
|
|
|
d1, d2 = parse_dates(row[2])
|
|
year = d1.year if d1 and d1.month > 6 else (d2.year if d2 else 2026)
|
|
if d1 and d1.month == 12 and week_num <= 1:
|
|
year = d2.year if d2 else d1.year + 1
|
|
|
|
rows.append({
|
|
"year": year,
|
|
"week_number": week_num,
|
|
"week_code": wc,
|
|
"week_start": d1,
|
|
"week_end": d2,
|
|
"absences": s(row[1]),
|
|
"tdg_s1": s(row[3]),
|
|
"tdg_symantec": s(row[4]),
|
|
"tdg_m365": s(row[5]),
|
|
"emails_dest": s(row[6]),
|
|
"tdg_commvault": s(row[7]),
|
|
"tdg_meteo": s(row[8]),
|
|
"tdg_dmz": s(row[11]) if len(row) > 11 else None,
|
|
"tdg_safenet": s(row[12]) if len(row) > 12 else None,
|
|
"tdg_quarantaine": s(row[13]) if len(row) > 13 else None,
|
|
"tdg_securisation": s(row[14]) if len(row) > 14 else None,
|
|
"tdg_incident_majeur": s(row[16]) if len(row) > 16 else None,
|
|
"tdg_incident_critique": s(row[17]) if len(row) > 17 else None,
|
|
})
|
|
return rows
|
|
|
|
|
|
SQL_UPSERT = text("""
|
|
INSERT INTO secops_duty
|
|
(year, week_number, week_code, week_start, week_end, absences,
|
|
tdg_s1, tdg_symantec, tdg_m365, tdg_commvault, tdg_meteo,
|
|
tdg_dmz, tdg_safenet, tdg_quarantaine, tdg_securisation,
|
|
tdg_incident_majeur, tdg_incident_critique, emails_dest)
|
|
VALUES
|
|
(:year, :week_number, :week_code, :week_start, :week_end, :absences,
|
|
:tdg_s1, :tdg_symantec, :tdg_m365, :tdg_commvault, :tdg_meteo,
|
|
:tdg_dmz, :tdg_safenet, :tdg_quarantaine, :tdg_securisation,
|
|
:tdg_incident_majeur, :tdg_incident_critique, :emails_dest)
|
|
ON CONFLICT (year, week_number) DO UPDATE SET
|
|
week_code = EXCLUDED.week_code,
|
|
week_start = EXCLUDED.week_start,
|
|
week_end = EXCLUDED.week_end,
|
|
absences = EXCLUDED.absences,
|
|
tdg_s1 = EXCLUDED.tdg_s1,
|
|
tdg_symantec = EXCLUDED.tdg_symantec,
|
|
tdg_m365 = EXCLUDED.tdg_m365,
|
|
tdg_commvault = EXCLUDED.tdg_commvault,
|
|
tdg_meteo = EXCLUDED.tdg_meteo,
|
|
tdg_dmz = EXCLUDED.tdg_dmz,
|
|
tdg_safenet = EXCLUDED.tdg_safenet,
|
|
tdg_quarantaine = EXCLUDED.tdg_quarantaine,
|
|
tdg_securisation = EXCLUDED.tdg_securisation,
|
|
tdg_incident_majeur = EXCLUDED.tdg_incident_majeur,
|
|
tdg_incident_critique = EXCLUDED.tdg_incident_critique,
|
|
emails_dest = EXCLUDED.emails_dest
|
|
""")
|
|
|
|
|
|
def main():
|
|
xlsx = sys.argv[1] if len(sys.argv) > 1 else find_xlsx()
|
|
if not xlsx or not os.path.exists(xlsx):
|
|
print("[ERR] Fichier Tour de garde introuvable. Place-le dans deploy/")
|
|
sys.exit(1)
|
|
|
|
print(f"[INFO] Fichier: {xlsx}")
|
|
rows = parse_tour_de_garde(xlsx)
|
|
print(f"[INFO] Semaines parsees: {len(rows)}")
|
|
|
|
engine = create_engine(DATABASE_URL)
|
|
print(f"[INFO] DB: {DATABASE_URL.rsplit('@', 1)[-1]}")
|
|
|
|
truncate = "--truncate" in sys.argv
|
|
with engine.begin() as conn:
|
|
if truncate:
|
|
conn.execute(text("TRUNCATE TABLE secops_duty RESTART IDENTITY"))
|
|
print("[INFO] TRUNCATE secops_duty")
|
|
for r in rows:
|
|
conn.execute(SQL_UPSERT, r)
|
|
|
|
print(f"[OK] UPSERT: {len(rows)} semaines")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|