Générer des rapports de performance de support GLPI (avec suivi SLA et envoi d'e-mails)

Avancé

Ceci est unContent Creation, Multimodal AIworkflow d'automatisation du domainecontenant 20 nœuds.Utilise principalement des nœuds comme Set, Code, Html, Gmail, SplitOut. Générer des rapports de performance de support GLPI avec suivi de SLA et envoi par e-mail

Prérequis
  • Compte Google et informations d'identification Gmail API
  • Peut nécessiter les informations d'identification d'authentification de l'API cible
Aperçu du workflow
Visualisation des connexions entre les nœuds, avec support du zoom et du déplacement
Exporter le workflow
Copiez la configuration JSON suivante dans n8n pour importer et utiliser ce workflow
{
  "meta": {
    "instanceId": "6fe840c9f744c9e44a1c50b6467124995157353b1fc80f3a8dd173276e4fc270",
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "d17beade-5e8d-482f-ad6a-d3f692b3ddc0",
      "name": "Variables",
      "type": "n8n-nodes-base.set",
      "position": [
        -448,
        32
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "ea290016-88f0-4287-be92-7a3f8c7046d6",
              "name": "Server URL",
              "type": "string",
              "value": "https://server_glpi.com/"
            },
            {
              "id": "5cd7f69c-f079-4155-b887-c4b5e6e1cb64",
              "name": "App Token",
              "type": "string",
              "value": "\"You app token\""
            },
            {
              "id": "4c2a3325-1547-4333-805a-c74b711e3bc5",
              "name": "Entity name",
              "type": "string",
              "value": "\"name_entity\""
            },
            {
              "id": "96765e53-2275-41e9-a297-b64207fa4a79",
              "name": "WORK_START",
              "type": "string",
              "value": "8"
            },
            {
              "id": "4c04dfd8-64ef-4ae0-8375-e3108222a4b8",
              "name": "LUNCH_START",
              "type": "string",
              "value": "12"
            },
            {
              "id": "b76f06aa-405f-421d-b64f-7468222ddfb8",
              "name": "LUNCH_END",
              "type": "string",
              "value": "13"
            },
            {
              "id": "1a1a5672-226e-439e-8121-bb48fbe46f67",
              "name": "WORK_END",
              "type": "string",
              "value": "18"
            },
            {
              "id": "b8ee7859-6533-4642-861b-d0e0136c3b39",
              "name": "SLA_LIMIT_HOURS",
              "type": "string",
              "value": "24"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "3042ce97-b7b8-47c0-b702-905a31114297",
      "name": "Modifier les champs",
      "type": "n8n-nodes-base.set",
      "position": [
        416,
        32
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "5a116716-658c-425a-9817-b84cfa462819",
              "name": "Ticket_id",
              "type": "string",
              "value": "={{ $json[\"2\"] }}"
            },
            {
              "id": "6deaea47-3c4a-4a2f-80de-a790f4dcd417",
              "name": "Title",
              "type": "string",
              "value": "={{ $json[\"1\"] }}"
            },
            {
              "id": "a710ff8c-c898-435d-91eb-c67e71ddc5f7",
              "name": "technical_id",
              "type": "string",
              "value": "={{ $json[\"5\"] }}"
            },
            {
              "id": "0ee2215d-760e-433e-9f1f-3237a8d0860c",
              "name": "Entity",
              "type": "string",
              "value": "={{ $json[\"80\"] }}"
            },
            {
              "id": "7b02dcbd-49df-43f9-b4e4-01b7d93a9a8e",
              "name": "Opening date",
              "type": "string",
              "value": "={{ $json[\"15\"] }}"
            },
            {
              "id": "62e29ecb-0dc2-4e94-ada5-c9e076dc596d",
              "name": "Solution date",
              "type": "string",
              "value": "={{ $json[\"17\"] }}"
            },
            {
              "id": "9741e21c-b42a-42e7-a58b-68947b24f29e",
              "name": "Closing date",
              "type": "string",
              "value": "={{ $json[\"16\"] }}"
            },
            {
              "id": "06abb1e0-7d26-4ac1-bd5e-a1c3b1823e3e",
              "name": "Status",
              "type": "string",
              "value": "={{ $json[\"12\"] }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "7cf4932f-9afd-4fb4-94e7-22f2f7c634a9",
      "name": "Diviser les données",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        192,
        32
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "data"
      },
      "typeVersion": 1
    },
    {
      "id": "e583f78f-0288-4166-b930-ebb39a88f7ef",
      "name": "Envoyer un message",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1056,
        -176
      ],
      "webhookId": "4bee6d0c-8e87-46ff-90f0-a3539439b8cf",
      "parameters": {
        "sendTo": "info@example.com",
        "message": "={{ $json.html }}",
        "options": {},
        "subject": "=Report - {{ $('Date range').first().json.month }}"
      },
      "credentials": {
        "gmailOAuth2": {
          "id": "1vGUy7BPigbNRllM",
          "name": "Gmail account"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "0cd4a9c1-5e1e-47b8-ba4c-92ffbdfef6d2",
      "name": "Obtenir les tickets",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -16,
        -176
      ],
      "parameters": {
        "url": "={{ $('Variables').item.json[\"Server URL\"] }}apirest.php/search/Ticket",
        "options": {},
        "sendQuery": true,
        "sendHeaders": true,
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBasicAuth",
        "queryParameters": {
          "parameters": [
            {
              "name": "criteria[0][field]",
              "value": "15"
            },
            {
              "name": "criteria[0][searchtype]",
              "value": "morethan"
            },
            {
              "name": "criteria[0][value]",
              "value": "={{ $('Date range').item.json.startDate }}"
            },
            {
              "name": "criteria[1][link]",
              "value": "AND"
            },
            {
              "name": "criteria[1][field]",
              "value": "15"
            },
            {
              "name": "criteria[1][searchtype]",
              "value": "lessthan"
            },
            {
              "name": "criteria[1][value]",
              "value": "={{ $('Date range').item.json.endDate }}"
            },
            {
              "name": "criteria[2][link]",
              "value": "AND"
            },
            {
              "name": "criteria[2][field]",
              "value": "80"
            },
            {
              "name": "criteria[2][searchtype]",
              "value": "contains"
            },
            {
              "name": "criteria[2][value]",
              "value": "={{ $('Variables').item.json[\"Entity name\"] }}"
            },
            {
              "name": "order",
              "value": "DESC"
            },
            {
              "name": "range",
              "value": "0-999"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "Session-Token",
              "value": "={{ $json.session_token }}"
            },
            {
              "name": "App-Token",
              "value": "={{ $('Variables').item.json[\"App Token\"] }}"
            }
          ]
        }
      },
      "credentials": {
        "httpBasicAuth": {
          "id": "1ri8MpwPvPOW9M8n",
          "name": "Api GLPI"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "0dc82824-be50-485d-b49a-398fff0dd4e1",
      "name": "Déclencheur planifié",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -464,
        -544
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "months",
              "triggerAtDayOfMonth": 6
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "3b5061f5-6fb0-4620-8d1d-d699f973e755",
      "name": "Obtenir le jeton de session",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -208,
        32
      ],
      "parameters": {
        "url": "={{ $json[\"Server URL\"] }}apirest.php/initSession",
        "options": {},
        "sendHeaders": true,
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBasicAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "App-Token",
              "value": "={{ $json[\"App Token\"] }}"
            }
          ]
        }
      },
      "credentials": {
        "httpBasicAuth": {
          "id": "1ri8MpwPvPOW9M8n",
          "name": "Api GLPI"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "06ea7bb1-1347-4e14-8499-8df9c4b0619b",
      "name": "Terminer la session",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1232,
        -176
      ],
      "parameters": {
        "url": "={{ $('Variables').first().json[\"Server URL\"] }}apirest.php/killSession",
        "options": {},
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Session-Token",
              "value": "={{ $('Get session token').first().json.session_token }}"
            },
            {
              "name": "App-Token",
              "value": "={{ $('Variables').first().json[\"App Token\"] }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "5af4ce0e-c957-49a0-a081-7e5c9b84a714",
      "name": "Aucune opération, ne rien faire",
      "type": "n8n-nodes-base.noOp",
      "position": [
        1408,
        -176
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "51e2a4d5-d133-4ba7-9013-e24e10529a15",
      "name": "Plage de dates",
      "type": "n8n-nodes-base.code",
      "position": [
        -464,
        -272
      ],
      "parameters": {
        "jsCode": "// Get the date from the Schedule Trigger\nconst today = new Date($input.item.json.timestamp);\n\n// Calculate the previous month\nconst previousMonth = new Date(today.getFullYear(), today.getMonth() - 1, 1);\n\n// Start date: last day of the month before the previous month\nconst startDate = new Date(previousMonth.getFullYear(), previousMonth.getMonth(), 0);\n\n// End date: first day of the month after the previous month\nconst endDate = new Date(previousMonth.getFullYear(), previousMonth.getMonth() + 1, 1);\n\n// Format as YYYY-MM-DD\nconst startDateStr = startDate.toISOString().split('T')[0];\nconst endDateStr = endDate.toISOString().split('T')[0];\n\n// Get the report month and year\nconst monthNames = [\n  'January', 'February', 'March', 'April', 'May', 'June',\n  'July', 'August', 'September', 'October', 'November', 'December'\n];\nconst reportMonth = monthNames[previousMonth.getMonth()];\nconst reportYear = previousMonth.getFullYear();\n\nreturn {\n  json: {\n    month: `${reportMonth} ${reportYear}`,\n    startDate: startDateStr,\n    endDate: endDateStr\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "1946b065-da80-413f-aaee-0d23ce11eb6f",
      "name": "Note adhésive",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -864,
        -720
      ],
      "parameters": {
        "color": 4,
        "width": 544,
        "height": 304,
        "content": "## Schedule\n\nThe configuration is set to start on the 6th day of each month to ensure accurate SLA measurement. This is because if the cut-off date were set to the last day of the month, cases opened in the days immediately prior would not be measured correctly, negatively impacting the SLA calculation."
      },
      "typeVersion": 1
    },
    {
      "id": "1d5b834b-be04-4c55-9282-33586fd9a6a6",
      "name": "Note adhésive 1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -864,
        -400
      ],
      "parameters": {
        "width": 544,
        "height": 288,
        "content": "## Data range \nThe month prior to the execution month is identified, and the range covering all days of that month is determined.\n\nExample:\n\nmonth: September 2025\nstartDate: 2025-08-31\nendDate: 2025-10-01\n\ndays\": 30,\ndates\": [\"2025-09-01\", \"...\", \"2025-09-30\"]\n"
      },
      "typeVersion": 1
    },
    {
      "id": "1284666f-52b4-49c0-b398-5e028eb80823",
      "name": "Note adhésive 2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -864,
        -96
      ],
      "parameters": {
        "color": 3,
        "width": 544,
        "height": 400,
        "content": "## Variables\n\nIn this node, the connection parameters to the GLPI server are updated:\n\nGLPI server URL: https://server_glpi.com/\n\nGLPI API App-Token: Here_App-Token\n\nGLPI entity name: name_entity\n\n\n\nSetting working hours \n\n(WORK_START, LUNCH_START, LUNCH_END, WORK_END) and SLA limit in hours (SLA_LIMIT_HOURS)"
      },
      "typeVersion": 1
    },
    {
      "id": "9ec74986-a8f0-43ec-935b-e26f16e45376",
      "name": "Note adhésive 4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -256,
        -368
      ],
      "parameters": {
        "color": 5,
        "width": 480,
        "height": 320,
        "content": "## Get tickets\n\nThe node retrieves GLPI cases based on the defined entity, applying the date range configured in Data range to filter by opening date. By default, the query is limited to a range of 0–999 cases, which can be adjusted as needed."
      },
      "typeVersion": 1
    },
    {
      "id": "f38ad03a-8f49-4169-94da-ae3765796acc",
      "name": "Note adhésive 3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        256,
        -576
      ],
      "parameters": {
        "width": 544,
        "height": 528,
        "content": "## Get report\nhe node calculates the effective working hours between ticket opening and resolution/closure, considering only business hours (8:00–12:00 and 13:00–16:00, excluding weekends).\n\nChecks SLA compliance based on SLA_LIMIT_HOURS (default: 24 hours).\n\n- Classifies tickets by status (open, in progress, resolved, closed).\n- Sums total working hours and calculates the average hours per ticket.\n- Determines the SLA compliance percentage.\n\n\n### Generates two reports:\n- General Report: metrics for all incidents.\n- Report by Technician: metrics per technician.\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "15df2869-8142-46b3-98f5-ef58bb750794",
      "name": "Métriques",
      "type": "n8n-nodes-base.code",
      "position": [
        544,
        -176
      ],
      "parameters": {
        "jsCode": "// n8n Function Node - GLPI Report with Case Totals\n// =================================================\n\n// Business hours configuration\nconst WORK_START = $('Variables').first().json.WORK_START;   // 8 AM\nconst LUNCH_START = $('Variables').first().json.LUNCH_START; // 12 PM\nconst LUNCH_END = $('Variables').first().json.WORK_END;   // 1 PM\nconst WORK_END = $('Variables').first().json.WORK_END;    // 6 PM\n\n// SLA configuration\nconst SLA_LIMIT_HOURS = $('Variables').first().json.SLA_LIMIT_HOURS; // cambiar aquí según SLA\n\nfunction toDate(str) {\n  return str ? new Date(str.replace(\" \", \"T\")) : null;\n}\n\nfunction isWeekend(date) {\n  const day = date.getDay();\n  return day === 0 || day === 6;\n}\n\n// Calculate effective working hours within business schedule\nfunction businessHoursDiff(start, end) {\n  if (!start || !end) return 0;\n  if (end < start) return 0;\n\n  let totalHours = 0;\n  let current = new Date(start);\n\n  while (current < end) {\n    if (isWeekend(current)) {\n      current.setDate(current.getDate() + 1);\n      current.setHours(WORK_START, 0, 0, 0);\n      continue;\n    }\n\n    const workMorningStart = new Date(current.setHours(WORK_START, 0, 0, 0));\n    const workMorningEnd = new Date(current.setHours(LUNCH_START, 0, 0, 0));\n    const workAfternoonStart = new Date(current.setHours(LUNCH_END, 0, 0, 0));\n    const workAfternoonEnd = new Date(current.setHours(WORK_END, 0, 0, 0));\n\n    const dayStart = start > workMorningStart ? start : workMorningStart;\n    const dayEnd = end < workAfternoonEnd ? end : workAfternoonEnd;\n\n    if (dayStart < workMorningEnd) {\n      totalHours += Math.max(0, (Math.min(dayEnd, workMorningEnd) - dayStart) / 3600000);\n    }\n    if (dayEnd > workAfternoonStart) {\n      totalHours += Math.max(0, (dayEnd - Math.max(dayStart, workAfternoonStart)) / 3600000);\n    }\n\n    current.setDate(current.getDate() + 1);\n    current.setHours(WORK_START, 0, 0, 0);\n  }\n\n  return totalHours;\n}\n\n// Convert decimal → \"Xh Ym\"\nfunction decimalToHM(decimal) {\n  const hours = Math.floor(decimal);\n  const minutes = Math.round((decimal - hours) * 60);\n  return `${hours}h ${minutes}m`;\n}\n\n// Initialize metrics\nlet general = { open: 0, in_progress: 0, solved: 0, closed: 0, hours: 0, sla_ok: 0, total: 0 };\nlet byTechnician = {};\n\n// Process tickets\nfor (const item of items) {\n  const t = item.json;\n\n  const opening = toDate(t[\"Opening date\"]);\n  const solution = toDate(t[\"Solution date\"]);\n  const closing = toDate(t[\"Closing date\"]);\n  const technician = t.technical_id || \"Unassigned\";\n  const status = parseInt(t.Status, 10);\n\n  const end = solution || closing;\n  const hours = end ? businessHoursDiff(opening, end) : 0;\n  const sla_ok = solution ? (hours <= SLA_LIMIT_HOURS ? 1 : 0) : 0;\n\n  // General report\n  if (status === 1) general.open++;\n  if (status === 2) general.in_progress++;\n  if (status === 5) general.solved++;\n  if (status === 6) general.closed++;\n  general.hours += hours;\n  general.sla_ok += sla_ok;\n  general.total++;\n\n  // Report by technician\n  if (!byTechnician[technician]) {\n    byTechnician[technician] = { open: 0, in_progress: 0, solved: 0, closed: 0, hours: 0, sla_ok: 0, total: 0 };\n  }\n  if (status === 1) byTechnician[technician].open++;\n  if (status === 2) byTechnician[technician].in_progress++;\n  if (status === 5) byTechnician[technician].solved++;\n  if (status === 6) byTechnician[technician].closed++;\n  byTechnician[technician].hours += hours;\n  byTechnician[technician].sla_ok += sla_ok;\n  byTechnician[technician].total++;\n}\n\n// Function to build summary\nfunction summary(data) {\n  return {\n    \"Total cases\": data.total,\n    \"Open cases\": data.open,\n    \"Cases in progress\": data.in_progress,\n    \"Solved cases\": data.solved,\n    \"Closed cases\": data.closed,\n    \"Total hours (decimal)\": data.hours.toFixed(2),\n    \"Total hours (formatted)\": decimalToHM(data.hours),\n    \"Average hours (decimal)\": data.total > 0 ? (data.hours / data.total).toFixed(2) : 0,\n    \"Average hours (formatted)\": data.total > 0 ? decimalToHM(data.hours / data.total) : \"0h 0m\",\n    \"SLA compliance\": data.total > 0 ? ((data.sla_ok / data.total) * 100).toFixed(2) + \"%\" : \"0%\"\n  };\n}\n\n// Final output\nreturn [\n  {\n    json: {\n      \"General Report\": summary(general),\n      \"Report by Technician\": Object.fromEntries(\n        Object.entries(byTechnician).map(([tech, data]) => [tech, summary(data)])\n      )\n    }\n  }\n];"
      },
      "typeVersion": 2
    },
    {
      "id": "4dc2fc78-aedc-44ac-9c12-8343dee0ef20",
      "name": "Générer le rapport",
      "type": "n8n-nodes-base.html",
      "position": [
        880,
        -176
      ],
      "parameters": {
        "html": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"/>\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n    <title>Case Report - Technical Support</title>\n    <style>\n        /* Reset styles */\n        body { margin: 0; padding: 0; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }\n        table { border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; }\n        img { border: 0; height: auto; line-height: 100%; outline: none; text-decoration: none; -ms-interpolation-mode: bicubic; }\n        \n        /* Responsive styles */\n        @media only screen and (max-width: 620px) {\n            .wrapper { width: 100% !important; }\n            .container { width: 100% !important; min-width: 100% !important; }\n            .mobile-padding { padding-left: 15px !important; padding-right: 15px !important; }\n            .metric-card { display: block !important; width: 100% !important; margin-bottom: 10px !important; }\n            .metric-row td { display: block !important; width: 100% !important; padding-bottom: 10px !important; }\n            .hide-mobile { display: none !important; }\n            .table-responsive { font-size: 12px !important; }\n            .table-responsive td { padding: 8px 4px !important; }\n        }\n    </style>\n</head>\n<body style=\"margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; background-color: #f3f4f6;\">\n    <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" role=\"presentation\" style=\"background-color: #f3f4f6; padding: 20px;\" class=\"wrapper\">\n        <tr>\n            <td align=\"center\">\n                <table width=\"600\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" role=\"presentation\" style=\"background-color: #ffffff; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); max-width: 600px;\" class=\"container\">\n                    \n                    <!-- Header -->\n                    <tr>\n                        <td style=\"background: linear-gradient(135deg, #2F3F64 0%, #1a2744 100%); background-color: #2F3F64; padding: 40px 30px; text-align: center;\" class=\"mobile-padding\">\n                            <h1 style=\"margin: 0; color: #ffffff; font-size: 26px; font-weight: bold; line-height: 1.3;\">\n                                📊 Technical Support Case Report - {{ $('Date range').first().json.month }}\n                            </h1>\n                            <p style=\"margin: 8px 0 0 0; color: #e2e8f0; font-size: 14px;\">\n                                Complete analysis of performance and case status\n                            </p>\n                        </td>\n                    </tr>\n                    \n                    <!-- Content -->\n                    <tr>\n                        <td style=\"padding: 30px;\" class=\"mobile-padding\">\n                            \n                            <!-- Section Title -->\n                            <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" role=\"presentation\" style=\"margin-bottom: 20px;\">\n                                <tr>\n                                    <td style=\"border-left: 4px solid #2F3F64; padding-left: 12px;\">\n                                        <h2 style=\"margin: 0; font-size: 18px; color: #0f172a; font-weight: bold;\">\n                                            📈 General Summary\n                                        </h2>\n                                    </td>\n                                </tr>\n                            </table>\n                            \n                            <!-- Metrics Row 1 -->\n                            <table width=\"100%\" cellpadding=\"8\" cellspacing=\"0\" border=\"0\" role=\"presentation\" style=\"margin-bottom: 12px;\" class=\"metric-row\">\n                                <tr>\n                                    <!-- Total Cases -->\n                                    <td width=\"25%\" style=\"background-color: #f8fafc; border-left: 4px solid #3b82f6; border-radius: 8px; padding: 16px; vertical-align: top;\" class=\"metric-card\">\n                                        <div style=\"font-size: 10px; color: #64748b; font-weight: bold; text-transform: uppercase; margin-bottom: 8px; letter-spacing: 0.5px;\">\n                                            TOTAL CASES\n                                        </div>\n                                        <div style=\"font-size: 26px; color: #0f172a; font-weight: bold; line-height: 1;\">\n                                            {{$json[\"General Report\"][\"Total cases\"]}}\n                                        </div>\n                                    </td>\n                                    \n                                    <td width=\"2%\" class=\"hide-mobile\"></td>\n                                    \n                                    <!-- Open Cases -->\n                                    <td width=\"25%\" style=\"background-color: #f8fafc; border-left: 4px solid #f59e0b; border-radius: 8px; padding: 16px; vertical-align: top;\" class=\"metric-card\">\n                                        <div style=\"font-size: 10px; color: #64748b; font-weight: bold; text-transform: uppercase; margin-bottom: 8px; letter-spacing: 0.5px;\">\n                                            OPEN CASES\n                                        </div>\n                                        <div style=\"font-size: 26px; color: #0f172a; font-weight: bold; line-height: 1;\">\n                                            {{$json[\"General Report\"][\"Open cases\"]}}\n                                        </div>\n                                    </td>\n                                    \n                                    <td width=\"2%\" class=\"hide-mobile\"></td>\n                                    \n                                    <!-- In Progress with Alert -->\n                                    <td width=\"23%\" style=\"background-color: #f8fafc; border-left: 4px solid #8b5cf6; border-radius: 8px; padding: 16px; vertical-align: top; position: relative;\" class=\"metric-card\">\n                                        <div style=\"font-size: 10px; color: #64748b; font-weight: bold; text-transform: uppercase; margin-bottom: 8px; letter-spacing: 0.5px;\">\n                                            IN PROGRESS\n                                        </div>\n                                        <div style=\"font-size: 26px; color: #0f172a; font-weight: bold; line-height: 1;\">\n                                            {{$json[\"General Report\"][\"Cases in progress\"]}}\n                                        </div>\n                                        {{parseInt($json[\"General Report\"][\"Cases in progress\"]) > 100 ? `\n                                        <div style=\"margin-top: 8px; padding: 4px 8px; background-color: #fef3c7; border-radius: 4px; font-size: 10px; color: #92400e; font-weight: bold;\">\n                                            ⚠️ HIGH VOLUME\n                                        </div>\n                                        ` : ''}}\n                                    </td>\n                                    \n                                    <td width=\"2%\" class=\"hide-mobile\"></td>\n                                    \n                                    <!-- Resolved -->\n                                    <td width=\"23%\" style=\"background-color: #f8fafc; border-left: 4px solid #10b981; border-radius: 8px; padding: 16px; vertical-align: top;\" class=\"metric-card\">\n                                        <div style=\"font-size: 10px; color: #64748b; font-weight: bold; text-transform: uppercase; margin-bottom: 8px; letter-spacing: 0.5px;\">\n                                            RESOLVED\n                                        </div>\n                                        <div style=\"font-size: 26px; color: #0f172a; font-weight: bold; line-height: 1;\">\n                                            {{$json[\"General Report\"][\"Solved cases\"]}}\n                                        </div>\n                                    </td>\n                                </tr>\n                            </table>\n                            \n                            <!-- Metrics Row 2 -->\n                            <table width=\"100%\" cellpadding=\"8\" cellspacing=\"0\" border=\"0\" role=\"presentation\" style=\"margin-bottom: 30px;\" class=\"metric-row\">\n                                <tr>\n                                    <!-- Closed -->\n                                    <td width=\"25%\" style=\"background-color: #f8fafc; border-left: 4px solid #6b7280; border-radius: 8px; padding: 16px; vertical-align: top;\" class=\"metric-card\">\n                                        <div style=\"font-size: 10px; color: #64748b; font-weight: bold; text-transform: uppercase; margin-bottom: 8px; letter-spacing: 0.5px;\">\n                                            CLOSED\n                                        </div>\n                                        <div style=\"font-size: 26px; color: #0f172a; font-weight: bold; line-height: 1;\">\n                                            {{$json[\"General Report\"][\"Closed cases\"]}}\n                                        </div>\n                                    </td>\n                                    \n                                    <td width=\"2%\" class=\"hide-mobile\"></td>\n                                    \n                                    <!-- Total Hours -->\n                                    <td width=\"25%\" style=\"background-color: #f8fafc; border-left: 4px solid #3b82f6; border-radius: 8px; padding: 16px; vertical-align: top;\" class=\"metric-card\">\n                                        <div style=\"font-size: 10px; color: #64748b; font-weight: bold; text-transform: uppercase; margin-bottom: 8px; letter-spacing: 0.5px;\">\n                                            TOTAL HOURS\n                                        </div>\n                                        <div style=\"font-size: 20px; color: #0f172a; font-weight: bold; line-height: 1;\">\n                                            {{$json[\"General Report\"][\"Total hours (formatted)\"]}}\n                                        </div>\n                                    </td>\n                                    \n                                    <td width=\"2%\" class=\"hide-mobile\"></td>\n                                    \n                                    <!-- Average -->\n                                    <td width=\"23%\" style=\"background-color: #f8fafc; border-left: 4px solid #f59e0b; border-radius: 8px; padding: 16px; vertical-align: top;\" class=\"metric-card\">\n                                        <div style=\"font-size: 10px; color: #64748b; font-weight: bold; text-transform: uppercase; margin-bottom: 8px; letter-spacing: 0.5px;\">\n                                            AVERAGE\n                                        </div>\n                                        <div style=\"font-size: 20px; color: #0f172a; font-weight: bold; line-height: 1;\">\n                                            {{$json[\"General Report\"][\"Average hours (formatted)\"]}}\n                                        </div>\n                                    </td>\n                                    \n                                    <td width=\"2%\" class=\"hide-mobile\"></td>\n                                    \n                                    <!-- SLA Compliance with Dynamic Color -->\n                                    <td width=\"23%\" style=\"background-color: #f8fafc; border-radius: 8px; padding: 16px; vertical-align: top;\" class=\"metric-card\">\n                                        {{(() => {\n                                            const sla = parseFloat($json[\"General Report\"][\"SLA compliance\"]);\n                                            const borderColor = sla >= 80 ? '#10b981' : sla >= 50 ? '#f59e0b' : '#ef4444';\n                                            return `<div style=\"border-left: 4px solid ${borderColor}; padding-left: 12px;\">\n                                                <div style=\"font-size: 10px; color: #64748b; font-weight: bold; text-transform: uppercase; margin-bottom: 8px; letter-spacing: 0.5px;\">\n                                                    SLA COMPLIANCE\n                                                </div>\n                                                <div style=\"font-size: 22px; color: #0f172a; font-weight: bold; line-height: 1;\">\n                                                    ${$json[\"General Report\"][\"SLA compliance\"]}\n                                                </div>\n                                            </div>`;\n                                        })()}}\n                                    </td>\n                                </tr>\n                            </table>\n                            \n                            <!-- Alert Section for Critical SLA -->\n                            {{parseFloat($json[\"General Report\"][\"SLA compliance\"]) < 50 ? `\n                            <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" role=\"presentation\" style=\"margin-bottom: 20px;\">\n                                <tr>\n                                    <td style=\"background-color: #fef2f2; border-left: 4px solid #dc2626; border-radius: 8px; padding: 16px;\">\n                                        <div style=\"font-size: 14px; color: #991b1b; font-weight: bold; margin-bottom: 4px;\">\n                                            ⚠️ CRITICAL ALERT\n                                        </div>\n                                        <div style=\"font-size: 13px; color: #7f1d1d; line-height: 1.5;\">\n                                            SLA compliance is below 50%. Immediate action required to improve service levels.\n                                        </div>\n                                    </td>\n                                </tr>\n                            </table>\n                            ` : ''}}\n                            \n                            <!-- Divider -->\n                            <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" role=\"presentation\" style=\"margin: 30px 0;\">\n                                <tr>\n                                    <td style=\"border-bottom: 1px solid #e2e8f0;\"></td>\n                                </tr>\n                            </table>\n                            \n                            <!-- Section Title 2 -->\n                            <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" role=\"presentation\" style=\"margin-bottom: 20px;\">\n                                <tr>\n                                    <td style=\"border-left: 4px solid #2F3F64; padding-left: 12px;\">\n                                        <h2 style=\"margin: 0; font-size: 18px; color: #0f172a; font-weight: bold;\">\n                                            👥 Technician Details\n                                        </h2>\n                                    </td>\n                                </tr>\n                            </table>\n                            \n                            <!-- Table -->\n                            <table width=\"100%\" cellpadding=\"12\" cellspacing=\"0\" border=\"0\" role=\"presentation\" style=\"border: 1px solid #e2e8f0; border-radius: 8px; overflow: hidden;\" class=\"table-responsive\">\n                                <thead>\n                                    <tr style=\"background-color: #f8fafc;\">\n                                        <th style=\"text-align: left; font-size: 11px; color: #475569; font-weight: bold; text-transform: uppercase; padding: 12px; border-bottom: 1px solid #e2e8f0;\">Technician</th>\n                                        <th style=\"text-align: center; font-size: 11px; color: #475569; font-weight: bold; text-transform: uppercase; padding: 12px; border-bottom: 1px solid #e2e8f0;\">Total</th>\n                                        <th style=\"text-align: center; font-size: 11px; color: #475569; font-weight: bold; text-transform: uppercase; padding: 12px; border-bottom: 1px solid #e2e8f0;\" class=\"hide-mobile\">Open</th>\n                                        <th style=\"text-align: center; font-size: 11px; color: #475569; font-weight: bold; text-transform: uppercase; padding: 12px; border-bottom: 1px solid #e2e8f0;\">Progress</th>\n                                        <th style=\"text-align: center; font-size: 11px; color: #475569; font-weight: bold; text-transform: uppercase; padding: 12px; border-bottom: 1px solid #e2e8f0;\" class=\"hide-mobile\">Solved</th>\n                                        <th style=\"text-align: center; font-size: 11px; color: #475569; font-weight: bold; text-transform: uppercase; padding: 12px; border-bottom: 1px solid #e2e8f0;\">Closed</th>\n                                        <th style=\"text-align: center; font-size: 11px; color: #475569; font-weight: bold; text-transform: uppercase; padding: 12px; border-bottom: 1px solid #e2e8f0;\" class=\"hide-mobile\">Total Hrs</th>\n                                        <th style=\"text-align: center; font-size: 11px; color: #475569; font-weight: bold; text-transform: uppercase; padding: 12px; border-bottom: 1px solid #e2e8f0;\" class=\"hide-mobile\">Average</th>\n                                        <th style=\"text-align: center; font-size: 11px; color: #475569; font-weight: bold; text-transform: uppercase; padding: 12px; border-bottom: 1px solid #e2e8f0;\">SLA</th>\n                                    </tr>\n                                </thead>\n                                <tbody>\n                                    {{($json[\"Report by Technician\"] && Object.keys($json[\"Report by Technician\"]).length > 0) \n                                        ? Object.keys($json[\"Report by Technician\"]).map(techId => {\n                                            const tech = $json[\"Report by Technician\"][techId];\n                                            const sla = parseFloat(tech[\"SLA compliance\"]);\n                                            const bgColor = sla >= 80 ? '#d1fae5' : sla >= 50 ? '#fef3c7' : '#fee2e2';\n                                            const textColor = sla >= 80 ? '#065f46' : sla >= 50 ? '#92400e' : '#991b1b';\n                                            const inProgress = parseInt(tech[\"Cases in progress\"]);\n                                            const progressAlert = inProgress > 100 ? 'background-color: #fef3c7;' : '';\n                                            \n                                            return `\n                                            <tr style=\"${progressAlert}\">\n                                                <td style=\"padding: 12px; font-size: 14px; color: #0f172a; font-weight: bold; border-bottom: 1px solid #f1f5f9;\">\n                                                    Technician #${techId}\n                                                </td>\n                                                <td style=\"padding: 12px; font-size: 14px; color: #334155; border-bottom: 1px solid #f1f5f9; text-align: center;\">\n                                                    ${tech[\"Total cases\"]}\n                                                </td>\n                                                <td style=\"padding: 12px; font-size: 14px; color: #334155; border-bottom: 1px solid #f1f5f9; text-align: center;\" class=\"hide-mobile\">\n                                                    ${tech[\"Open cases\"]}\n                                                </td>\n                                                <td style=\"padding: 12px; font-size: 14px; border-bottom: 1px solid #f1f5f9; text-align: center;\">\n                                                    <span style=\"${inProgress > 100 ? 'font-weight: bold; color: #92400e;' : 'color: #334155;'}\">\n                                                        ${tech[\"Cases in progress\"]}${inProgress > 100 ? ' ⚠️' : ''}\n                                                    </span>\n                                                </td>\n                                                <td style=\"padding: 12px; font-size: 14px; color: #334155; border-bottom: 1px solid #f1f5f9; text-align: center;\" class=\"hide-mobile\">\n                                                    ${tech[\"Solved cases\"]}\n                                                </td>\n                                                <td style=\"padding: 12px; font-size: 14px; color: #334155; border-bottom: 1px solid #f1f5f9; text-align: center;\">\n                                                    ${tech[\"Closed cases\"]}\n                                                </td>\n                                                <td style=\"padding: 12px; font-size: 14px; color: #334155; border-bottom: 1px solid #f1f5f9; text-align: center;\" class=\"hide-mobile\">\n                                                    ${tech[\"Total hours (formatted)\"]}\n                                                </td>\n                                                <td style=\"padding: 12px; font-size: 14px; color: #334155; border-bottom: 1px solid #f1f5f9; text-align: center;\" class=\"hide-mobile\">\n                                                    ${tech[\"Average hours (formatted)\"]}\n                                                </td>\n                                                <td style=\"padding: 12px; font-size: 14px; border-bottom: 1px solid #f1f5f9; text-align: center;\">\n                                                    <span style=\"display: inline-block; padding: 6px 12px; background-color: ${bgColor}; color: ${textColor}; font-size: 12px; font-weight: bold; border-radius: 12px; white-space: nowrap;\">\n                                                        ${tech[\"SLA compliance\"]}\n                                                    </span>\n                                                </td>\n                                            </tr>\n                                            `;\n                                        }).join('')\n                                        : '<tr><td colspan=\"9\" style=\"padding: 30px; text-align: center; color: #64748b; font-style: italic; border-bottom: 1px solid #f1f5f9;\">No technician data available</td></tr>'\n                                    }}\n                                </tbody>\n                            </table>\n                            \n                        </td>\n                    </tr>\n                    \n                    <!-- Footer -->\n                    <tr>\n                        <td style=\"background-color: #f8fafc; padding: 20px; text-align: center; border-top: 1px solid #e2e8f0;\">\n                            <p style=\"margin: 0 0 8px 0; color: #64748b; font-size: 12px;\">\n                                Report automatically generated by the technical support system\n                            </p>\n                            <p style=\"margin: 0; color: #94a3b8; font-size: 11px;\">\n                                Generated on: {{new Date().toLocaleString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit', timeZone: 'America/Bogota' })}}\n                            </p>\n                        </td>\n                    </tr>\n                    \n                </table>\n            </td>\n        </tr>\n    </table>\n</body>\n</html>"
      },
      "typeVersion": 1.2
    },
    {
      "id": "b8578b10-7ce4-4bcc-945b-b938cfbd2fb7",
      "name": "Note adhésive 5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -368,
        -1280
      ],
      "parameters": {
        "color": 7,
        "width": 480,
        "height": 304,
        "content": "## Create GLPI API Credentials in n8n\n## Node https request\nSet up your GLPI credentials before configuring the workflow:\nCredential Name: Api GLPI (or your preferred name)\n\nAuthentication: Generic Credential Type → Basic Auth\nGeneric Auth Type: Basic Auth\nUsername: Your GLPI API username\nPassword: Your GLPI API password"
      },
      "typeVersion": 1
    },
    {
      "id": "dcfed78e-cb52-4a20-87b9-41e1699743b8",
      "name": "Note adhésive 6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -864,
        -1280
      ],
      "parameters": {
        "color": 7,
        "width": 480,
        "height": 304,
        "content": "## Tickets panel GLPI\nConfigure the tickets module in Assistance > Tickets with the following items using the tool icon.\n\nID\nTitle\nStatus\nOpening Date\nClosing Date\nResolution Date\nPriority\nRequester - Requester\nAssigned To - Technician"
      },
      "typeVersion": 1
    },
    {
      "id": "5623a271-7c94-40cb-9043-b9e6c2f35c71",
      "name": "Note adhésive 7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1008,
        -320
      ],
      "parameters": {
        "width": 192,
        "height": 128,
        "content": "## Email\nAdd your email account"
      },
      "typeVersion": 1
    }
  ],
  "pinData": {},
  "connections": {
    "15df2869-8142-46b3-98f5-ef58bb750794": {
      "main": [
        [
          {
            "node": "4dc2fc78-aedc-44ac-9c12-8343dee0ef20",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "7cf4932f-9afd-4fb4-94e7-22f2f7c634a9": {
      "main": [
        [
          {
            "node": "3042ce97-b7b8-47c0-b702-905a31114297",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "d17beade-5e8d-482f-ad6a-d3f692b3ddc0": {
      "main": [
        [
          {
            "node": "3b5061f5-6fb0-4620-8d1d-d699f973e755",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "51e2a4d5-d133-4ba7-9013-e24e10529a15": {
      "main": [
        [
          {
            "node": "d17beade-5e8d-482f-ad6a-d3f692b3ddc0",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "3042ce97-b7b8-47c0-b702-905a31114297": {
      "main": [
        [
          {
            "node": "15df2869-8142-46b3-98f5-ef58bb750794",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "06ea7bb1-1347-4e14-8499-8df9c4b0619b": {
      "main": [
        [
          {
            "node": "5af4ce0e-c957-49a0-a081-7e5c9b84a714",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "0cd4a9c1-5e1e-47b8-ba4c-92ffbdfef6d2": {
      "main": [
        [
          {
            "node": "7cf4932f-9afd-4fb4-94e7-22f2f7c634a9",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "e583f78f-0288-4166-b930-ebb39a88f7ef": {
      "main": [
        [
          {
            "node": "06ea7bb1-1347-4e14-8499-8df9c4b0619b",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4dc2fc78-aedc-44ac-9c12-8343dee0ef20": {
      "main": [
        [
          {
            "node": "e583f78f-0288-4166-b930-ebb39a88f7ef",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "0dc82824-be50-485d-b49a-398fff0dd4e1": {
      "main": [
        [
          {
            "node": "51e2a4d5-d133-4ba7-9013-e24e10529a15",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "3b5061f5-6fb0-4620-8d1d-d699f973e755": {
      "main": [
        [
          {
            "node": "0cd4a9c1-5e1e-47b8-ba4c-92ffbdfef6d2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Foire aux questions

Comment utiliser ce workflow ?

Copiez le code de configuration JSON ci-dessus, créez un nouveau workflow dans votre instance n8n et sélectionnez "Importer depuis le JSON", collez la configuration et modifiez les paramètres d'authentification selon vos besoins.

Dans quelles scénarios ce workflow est-il adapté ?

Avancé - Création de contenu, IA Multimodale

Est-ce payant ?

Ce workflow est entièrement gratuit et peut être utilisé directement. Veuillez noter que les services tiers utilisés dans le workflow (comme l'API OpenAI) peuvent nécessiter un paiement de votre part.

Informations sur le workflow
Niveau de difficulté
Avancé
Nombre de nœuds20
Catégorie2
Types de nœuds9
Description de la difficulté

Adapté aux utilisateurs avancés, avec des workflows complexes contenant 16+ nœuds

Auteur
Luis Hernandez

Luis Hernandez

@integropen

I like solving problems and have a true passion for no-code automation. I'm thoughtful by nature and enjoy finding ways to optimize processes, always guided by my principles and values.

Liens externes
Voir sur n8n.io

Partager ce workflow

Catégories

Catégories: 34