diff --git a/app/services/itop_service.py b/app/services/itop_service.py
index 3c21c4d..f90fcdc 100644
--- a/app/services/itop_service.py
+++ b/app/services/itop_service.py
@@ -144,63 +144,63 @@ def sync_from_itop(db, itop_url, itop_user, itop_pass):
except Exception:
pass
- # ─── 1. Typologies: Environnements ───
+ # ─── 1. Typologies: Environnements (batch) ───
+ existing_envs = {r.name.lower() for r in db.execute(text("SELECT name FROM environments")).fetchall()}
for item in client.get_all("Environnement", "name"):
name = item.get("name", "")
- if not name:
+ if not name or name.lower() in existing_envs:
continue
- existing = db.execute(text("SELECT id FROM environments WHERE LOWER(name)=LOWER(:n)"), {"n": name}).fetchone()
- if not existing:
- try:
- db.execute(text("INSERT INTO environments (name, code) VALUES (:n, :c)"),
- {"n": name, "c": name[:10].upper().replace(" ", "").replace("-", "")})
- db.commit()
- stats["environments"] += 1
- except Exception:
- db.rollback()
+ try:
+ db.execute(text("INSERT INTO environments (name, code) VALUES (:n, :c)"),
+ {"n": name, "c": name[:10].upper().replace(" ", "").replace("-", "")})
+ existing_envs.add(name.lower())
+ stats["environments"] += 1
+ except Exception:
+ db.rollback()
+ db.commit()
- # ─── 2. Typologies: Domaines applicatifs ───
+ # ─── 2. Typologies: Domaines applicatifs (batch) ───
+ existing_doms = {r.name.lower() for r in db.execute(text("SELECT name FROM domains")).fetchall()}
for item in client.get_all("DomaineApplicatif", "name"):
name = item.get("name", "")
- if not name:
+ if not name or name.lower() in existing_doms:
continue
- existing = db.execute(text("SELECT id FROM domains WHERE LOWER(name)=LOWER(:n)"), {"n": name}).fetchone()
- if not existing:
- try:
- db.execute(text("INSERT INTO domains (name, code) VALUES (:n, :c)"),
- {"n": name, "c": name[:10].upper().replace(" ", "")})
- db.commit()
- stats["domains"] += 1
- except Exception:
- db.rollback()
+ try:
+ db.execute(text("INSERT INTO domains (name, code) VALUES (:n, :c)"),
+ {"n": name, "c": name[:10].upper().replace(" ", "")})
+ existing_doms.add(name.lower())
+ stats["domains"] += 1
+ except Exception:
+ db.rollback()
+ db.commit()
- # ─── 3. Typologies: Zones ───
+ # ─── 3. Typologies: Zones (batch) ───
+ existing_zones = {r.name.lower() for r in db.execute(text("SELECT name FROM zones")).fetchall()}
for item in client.get_all("Zone", "name"):
name = item.get("name", "")
- if not name:
+ if not name or name.lower() in existing_zones:
continue
- existing = db.execute(text("SELECT id FROM zones WHERE LOWER(name)=LOWER(:n)"), {"n": name}).fetchone()
- if not existing:
- try:
- db.execute(text("INSERT INTO zones (name, is_dmz) VALUES (:n, :d)"),
- {"n": name, "d": "dmz" in name.lower()})
- db.commit()
- stats["zones"] += 1
- except Exception:
- db.rollback()
+ try:
+ db.execute(text("INSERT INTO zones (name, is_dmz) VALUES (:n, :d)"),
+ {"n": name, "d": "dmz" in name.lower()})
+ existing_zones.add(name.lower())
+ stats["zones"] += 1
+ except Exception:
+ db.rollback()
+ db.commit()
- # ─── 4. Typologies: DomainLdap → domain_ltd_list ───
+ # ─── 4. Typologies: DomainLdap → domain_ltd_list (batch) ───
+ existing_ltd = {r.name.lower() for r in db.execute(text("SELECT name FROM domain_ltd_list")).fetchall()}
for item in client.get_all("DomainLdap", "name"):
name = item.get("name", "")
- if not name:
+ if not name or name.lower() in existing_ltd:
continue
- existing = db.execute(text("SELECT id FROM domain_ltd_list WHERE LOWER(name)=LOWER(:n)"), {"n": name}).fetchone()
- if not existing:
- try:
- db.execute(text("INSERT INTO domain_ltd_list (name) VALUES (:n)"), {"n": name})
- db.commit()
- except Exception:
- db.rollback()
+ try:
+ db.execute(text("INSERT INTO domain_ltd_list (name) VALUES (:n)"), {"n": name})
+ existing_ltd.add(name.lower())
+ except Exception:
+ db.rollback()
+ db.commit()
# ─── 5. Contacts + Teams (filtre périmètre IT uniquement) ───
persons = client.get_all("Person", "name,first_name,email,phone,org_name,function,status")
@@ -233,6 +233,9 @@ def sync_from_itop(db, itop_url, itop_user, itop_pass):
seen_emails = set()
stats["contacts_deactivated"] = 0
+ contacts_by_email = {}
+ for r in db.execute(text("SELECT id, email FROM contacts")).fetchall():
+ contacts_by_email[r.email.lower()] = r
for p in persons:
fullname = f"{p.get('first_name','')} {p.get('name','')}".strip()
email = p.get("email", "")
@@ -254,7 +257,7 @@ def sync_from_itop(db, itop_url, itop_user, itop_pass):
seen_itop_ids.add(int(itop_id))
seen_emails.add(email.lower())
- existing = db.execute(text("SELECT id FROM contacts WHERE LOWER(email)=LOWER(:e)"), {"e": email}).fetchone()
+ existing = contacts_by_email.get(email.lower())
if existing:
db.execute(text("""UPDATE contacts SET name=:n, role=:r, itop_id=:iid,
telephone=:tel, team=:t, function=:f, is_active=:a, updated_at=NOW() WHERE id=:id"""),
@@ -292,6 +295,9 @@ def sync_from_itop(db, itop_url, itop_user, itop_pass):
# Person name → email lookup
person_email = {}
+ contacts_by_email = {}
+ for r in db.execute(text("SELECT id, email FROM contacts")).fetchall():
+ contacts_by_email[r.email.lower()] = r
for p in persons:
fullname = f"{p.get('first_name','')} {p.get('name','')}".strip()
person_email[fullname.lower()] = p.get("email", "")
diff --git a/app/templates/macros.html b/app/templates/macros.html
new file mode 100644
index 0000000..f730399
--- /dev/null
+++ b/app/templates/macros.html
@@ -0,0 +1,33 @@
+{% macro badge_etat(etat) -%}
+{{ (etat or '-')[:6] }}
+{%- endmacro %}
+
+{% macro badge_env(env) -%}
+{{ (env or '-')[:6] }}
+{%- endmacro %}
+
+{% macro badge_os(os) -%}
+{{ (os or '-')[:6] }}
+{%- endmacro %}
+
+{% macro badge_zone(zone) -%}
+{{ zone or '-' }}
+{%- endmacro %}
+
+{% macro badge_status(status) -%}
+{{ status or '-' }}
+{%- endmacro %}
+
+{% macro badge_source(source_type, campaign_id=None, campaign_label=None, run_id=None, run_label=None) -%}
+{% if source_type == 'import' %}xlsx
+{% elif source_type == 'standard' %}{{ campaign_label or 'Campagne' }}
+{% elif source_type == 'quickwin' %}{{ run_label or 'QuickWin' }}
+{% else %}{{ source_type or '?' }}{% endif %}
+{%- endmacro %}
+
+{% macro filter_select(name, label, options, selected) -%}
+
+{%- endmacro %}