Назад до шаблонів
ШАБЛОН ВОРКФЛОУ
HTTP Request | Operasyon için n8n Otomasyon İş Akışı Şablonu (HTTP, Google Sheets, Filtre)
n8n için hazır otomasyon şablonu: HTTP Request. 24 düğüm. Entegrasyonlar: HTTP, Google Sheets, Filtre. JSON'u kopyalayıp n8n'e içe aktarın.
24 вузлиHTTP_Request-workflow.json
{"meta": {"instanceId": "393ca9e36a1f81b0f643c72792946a5fe5e49eb4864181ba4032e5a408278263","templateCredsSetupCompleted": true},"nodes": [{"id": "97a8d56f-29ec-4bd7-a9e9-52707fdaa646","name": "HTTP Request","type": "n8n-nodes-base.httpRequest","position": [304,-304],"parameters": {"url": "https://boamp-datadila.opendatasoft.com/api/explore/v2.1/catalog/datasets/boamp/records","options": {},"sendQuery": true,"sendHeaders": true,"queryParameters": {"parameters": [{"name": "select","value": "idweb,dateparution,objet,nomacheteur,datelimitereponse,type_marche,url_avis,code_departement"},{"name": "order_by","value": "dateparution desc"},{"name": "where","value": "=dateparution >= \"{{ $now.minus({days: $('Get config').item.json['Période']}).format('yyyy-MM-dd') }}\""},{"name": "limit","value": "100"},{"name": "offset","value": "={{ $json.Offset }}"}]},"headerParameters": {"parameters": [{"name": "Accept","value": "application/json"},{"name": "User-Agent","value": "n8n-workflow-veille"}]}},"typeVersion": 4.2},{"id": "fe0416bf-3443-4d8d-a209-303a03dca6b3","name": "Process Response","type": "n8n-nodes-base.code","position": [1728,-304],"parameters": {"jsCode": "// Récupérer la réponse de l'API\nconst response = $input.first().json;\nconst results = response.results || [];\nconst totalCount = response.total_count || 0;\n\n// Récupérer l'offset actuel depuis le nœud \"Get Offset\"\nconst currentOffset = $('Get Offset').first().json.Offset || 0;\nconst limit = 100;\n\n// Calculer le prochain offset\nconst nextOffset = currentOffset + limit;\n\n// Vérifier s'il y a encore des données à récupérer\nconst hasMoreData = nextOffset < totalCount && results.length > 0;\n\nreturn {\n records: results,\n currentOffset: currentOffset,\n nextOffset: nextOffset,\n totalCount: totalCount,\n hasMoreData: hasMoreData,\n recordsInBatch: results.length\n};"},"typeVersion": 2},{"id": "80744cd7-4e88-4a6e-8935-6798605fda88","name": "Check Continue Loop","type": "n8n-nodes-base.if","position": [2352,-304],"parameters": {"options": {},"conditions": {"options": {"version": 2,"leftValue": "","caseSensitive": true,"typeValidation": "strict"},"combinator": "and","conditions": [{"id": "115caab4-3fbe-492f-aefc-b7b639832df8","operator": {"type": "boolean","operation": "true","singleValue": true},"leftValue": "={{ $('Process Response').item.json.hasMoreData }}","rightValue": ""}]}},"typeVersion": 2.2},{"id": "ee8d1ff9-7df2-49f0-a8ce-120c6ce252ac","name": "Append row in sheet","type": "n8n-nodes-base.googleSheets","position": [1376,-320],"parameters": {"columns": {"value": {},"schema": [{"id": "Parution","type": "string","display": true,"required": false,"displayName": "Parution","defaultMatch": false,"canBeUsedToMatch": true},{"id": "Objet","type": "string","display": true,"required": false,"displayName": "Objet","defaultMatch": false,"canBeUsedToMatch": true},{"id": "URL","type": "string","display": true,"required": false,"displayName": "URL","defaultMatch": false,"canBeUsedToMatch": true},{"id": "Acheteur","type": "string","display": true,"required": false,"displayName": "Acheteur","defaultMatch": false,"canBeUsedToMatch": true},{"id": "Date limite","type": "string","display": true,"required": false,"displayName": "Date limite","defaultMatch": false,"canBeUsedToMatch": true},{"id": "_metadata","type": "string","display": true,"removed": false,"required": false,"displayName": "_metadata","defaultMatch": false,"canBeUsedToMatch": true}],"mappingMode": "autoMapInputData","matchingColumns": [],"attemptToConvertTypes": false,"convertFieldsToString": false},"options": {},"operation": "append","sheetName": {"__rl": true,"mode": "list","value": "gid=0","cachedResultUrl": "https://docs.google.com/spreadsheets/d/1WCVR7NOEvsM5CnPKD6R1aLuNHKaQbIpAUvoK1wS3bgY/edit#gid=0","cachedResultName": "All"},"documentId": {"__rl": true,"mode": "url","value": "https://docs.google.com/spreadsheets/d/1WCVR7NOEvsM5CnPKD6R1aLuNHKaQbIpAUvoK1wS3bgY/edit?gid=0#gid=0"}},"credentials": {"googleSheetsOAuth2Api": {"id": "wBRLUCktxqXE6DVJ","name": "Google Sheets account"}},"typeVersion": 4.7},{"id": "fadbf1a6-d255-47fc-9a9c-26ceaa88c69f","name": "Format Results","type": "n8n-nodes-base.code","position": [1168,-320],"parameters": {"jsCode": "// Récupérer la réponse HTTP brute\nconst response = $input.first().json;\nconst results = response.results || [];\n\n// Fonction pour formater la date en dd/MM/yyyy\nfunction formatDate(dateString) {\n if (!dateString) return null;\n \n try {\n const date = new Date(dateString);\n const day = date.getDate().toString().padStart(2, '0');\n const month = (date.getMonth() + 1).toString().padStart(2, '0');\n const year = date.getFullYear();\n return `${day}/${month}/${year}`;\n } catch (error) {\n return dateString;\n }\n}\n\n// Fonction pour générer l'URL du PDF\nfunction generatePdfUrl(urlAvis, dateParution) {\n if (!urlAvis || !dateParution) return null;\n \n try {\n // Extraire l'idweb de l'URL d'annonce\n const match = urlAvis.match(/idweb:(%22)?([^%\"&]+)(%22)?/);\n if (!match) return null;\n \n const idweb = match[2]; // 25-102635\n \n // Extraire le mois de la date de parution\n const date = new Date(dateParution);\n const month = (date.getMonth() + 1).toString().padStart(2, '0');\n const year = date.getFullYear();\n \n // Construire l'URL PDF\n return `https://www.boamp.fr/telechargements/FILES/PDF/${year}/${month}/${idweb}.pdf`;\n \n } catch (error) {\n return null;\n }\n}\n\n// Créer un item séparé pour chaque appel d'offre\nreturn results.map(item => ({\n json: {\n \"Parution\": item.dateparution,\n \"Objet\": item.objet,\n \"URL\": generatePdfUrl(item.url_avis, item.dateparution),\n \"Acheteur\": item.nomacheteur,\n \"Date limite\": formatDate(item.datelimitereponse)\n }\n}));"},"typeVersion": 2},{"id": "69b51766-63c7-4e2d-8906-5f29ae0e92e4","name": "Get Offset","type": "n8n-nodes-base.googleSheets","position": [-144,-304],"parameters": {"options": {},"sheetName": {"__rl": true,"mode": "list","value": 862285542,"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1WCVR7NOEvsM5CnPKD6R1aLuNHKaQbIpAUvoK1wS3bgY/edit#gid=862285542","cachedResultName": "Offset"},"documentId": {"__rl": true,"mode": "url","value": "https://docs.google.com/spreadsheets/d/1WCVR7NOEvsM5CnPKD6R1aLuNHKaQbIpAUvoK1wS3bgY/edit?gid=862285542#gid=862285542"}},"credentials": {"googleSheetsOAuth2Api": {"id": "wBRLUCktxqXE6DVJ","name": "Google Sheets account"}},"typeVersion": 4.7},{"id": "adf9bd65-b1af-453f-aa91-7b3a9c4f7fed","name": "Schedule Trigger","type": "n8n-nodes-base.scheduleTrigger","position": [-960,928],"parameters": {"rule": {"interval": [{"field": "cronExpression","expression": "0 10 1 * *"}]}},"typeVersion": 1.2},{"id": "1d7e0c25-b6a7-468d-99d4-3efbad6dac2e","name": "Get All","type": "n8n-nodes-base.googleSheets","position": [-160,928],"parameters": {"options": {},"sheetName": {"__rl": true,"mode": "list","value": "gid=0","cachedResultUrl": "https://docs.google.com/spreadsheets/d/1WCVR7NOEvsM5CnPKD6R1aLuNHKaQbIpAUvoK1wS3bgY/edit#gid=0","cachedResultName": "All"},"documentId": {"__rl": true,"mode": "url","value": "https://docs.google.com/spreadsheets/d/1WCVR7NOEvsM5CnPKD6R1aLuNHKaQbIpAUvoK1wS3bgY/edit?gid=862285542#gid=862285542"}},"credentials": {"googleSheetsOAuth2Api": {"id": "wBRLUCktxqXE6DVJ","name": "Google Sheets account"}},"typeVersion": 4.7},{"id": "fac28307-193c-4b9f-9e51-a3fd759084e3","name": "Query match ?","type": "n8n-nodes-base.if","position": [2144,960],"parameters": {"options": {},"conditions": {"options": {"version": 2,"leftValue": "","caseSensitive": true,"typeValidation": "strict"},"combinator": "and","conditions": [{"id": "081889ef-d60f-4ea5-8771-484603075cc2","operator": {"type": "boolean","operation": "true","singleValue": true},"leftValue": "={{ $('Get query').item.json.hasMatch }}","rightValue": ""}]}},"typeVersion": 2.2},{"id": "f68fe0b2-7ccd-4a29-a74d-a8c62936d02b","name": "Loop Over offres","type": "n8n-nodes-base.splitInBatches","position": [384,928],"parameters": {"options": {}},"typeVersion": 3},{"id": "0074c6c1-6ec4-4036-9213-4e1caf6023cf","name": "Get query","type": "n8n-nodes-base.code","position": [1216,928],"parameters": {"jsCode": "// Récupération des mots-clés depuis l'objet\nconst keywordData = $('Get keyword').first().json['Mot clé'];\nconst keywordsString = keywordData[0]['Mots clés'] || '';\n\n// Transformation de la chaîne en tableau et nettoyage\nconst termesToSearch = keywordsString\n .split(',')\n .map(term => term.trim()) // Supprime les espaces avant/après\n .filter(term => term.length > 0); // Supprime les éléments vides\n\n// Récupération du texte d'entrée depuis le champ \"text\" de la node précédente\nconst inputText = $input.first().json.text || '';\n\n// Fonction pour normaliser le texte (supprime accents, met en minuscules)\nfunction normalizeText(text) {\n return text\n .toLowerCase()\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, ''); // Supprime les accents\n}\n\n// Normalisation du texte d'entrée\nconst normalizedInput = normalizeText(inputText);\n\n// Recherche des correspondances\nconst foundTerms = [];\nlet hasMatch = false;\n\ntermesToSearch.forEach(term => {\n const normalizedTerm = normalizeText(term);\n \n // Recherche du terme exact ou avec variations\n if (normalizedInput.includes(normalizedTerm)) {\n foundTerms.push(term);\n hasMatch = true;\n }\n});\n\n// Construction du résultat\nconst result = {\n hasMatch: hasMatch,\n foundTerms: foundTerms,\n totalMatches: foundTerms.length,\n searchedTerms: termesToSearch,\n originalText: inputText.substring(0, 200) + (inputText.length > 200 ? '...' : '') // Extrait pour debug\n};\n\n// Ajout de détails sur les correspondances trouvées\nif (hasMatch) {\n result.matchDetails = foundTerms.map(term => {\n const normalizedTerm = normalizeText(term);\n const index = normalizedInput.indexOf(normalizedTerm);\n return {\n term: term,\n position: index,\n context: inputText.substring(Math.max(0, index - 50), index + term.length + 50)\n };\n });\n}\n\nreturn [{ json: result }];"},"typeVersion": 2},{"id": "b6b5f98f-4894-4979-b0e9-8d968559fd93","name": "Target offre","type": "n8n-nodes-base.googleSheets","position": [2496,928],"parameters": {"columns": {"value": {"URL": "={{ $('Loop Over offres').item.json.URL }}","Match": "={{ $('Get query').item.json.foundTerms }}","Objet": "={{ $('Loop Over offres').item.json.Objet }}","Acheteur": "={{ $('Loop Over offres').item.json.Acheteur }}","Parution": "={{ $('Loop Over offres').item.json.Parution }}","Date ajout": "={{ DateTime.now().toFormat('dd/MM/yyyy') }}","Date limite": "={{ $('Loop Over offres').item.json['Date limite'] }}"},"schema": [{"id": "Date ajout","type": "string","display": true,"required": false,"displayName": "Date ajout","defaultMatch": false,"canBeUsedToMatch": true},{"id": "Parution","type": "string","display": true,"required": false,"displayName": "Parution","defaultMatch": false,"canBeUsedToMatch": true},{"id": "Objet","type": "string","display": true,"required": false,"displayName": "Objet","defaultMatch": false,"canBeUsedToMatch": true},{"id": "URL","type": "string","display": true,"required": false,"displayName": "URL","defaultMatch": false,"canBeUsedToMatch": true},{"id": "Acheteur","type": "string","display": true,"required": false,"displayName": "Acheteur","defaultMatch": false,"canBeUsedToMatch": true},{"id": "Date limite","type": "string","display": true,"required": false,"displayName": "Date limite","defaultMatch": false,"canBeUsedToMatch": true},{"id": "Match","type": "string","display": true,"required": false,"displayName": "Match","defaultMatch": false,"canBeUsedToMatch": true}],"mappingMode": "defineBelow","matchingColumns": [],"attemptToConvertTypes": false,"convertFieldsToString": false},"options": {},"operation": "append","sheetName": {"__rl": true,"mode": "list","value": 151804153,"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1WCVR7NOEvsM5CnPKD6R1aLuNHKaQbIpAUvoK1wS3bgY/edit#gid=151804153","cachedResultName": "Target"},"documentId": {"__rl": true,"mode": "url","value": "https://docs.google.com/spreadsheets/d/1WCVR7NOEvsM5CnPKD6R1aLuNHKaQbIpAUvoK1wS3bgY/edit?gid=151804153#gid=151804153"}},"credentials": {"googleSheetsOAuth2Api": {"id": "wBRLUCktxqXE6DVJ","name": "Google Sheets account"}},"typeVersion": 4.7},{"id": "10e05eea-708b-4460-9482-fab208fc3632","name": "Wait","type": "n8n-nodes-base.wait","position": [2800,272],"webhookId": "63dee3e9-695f-42dc-8689-6ef472d8f0d3","parameters": {"amount": 10},"typeVersion": 1.1},{"id": "6bd78099-8371-43a0-82e7-84a3890afe3a","name": "Reset Offset","type": "n8n-nodes-base.googleSheets","position": [2880,-320],"parameters": {"columns": {"value": {"Offset": "0","row_number": 2},"schema": [{"id": "Offset","type": "string","display": true,"required": false,"displayName": "Offset","defaultMatch": false,"canBeUsedToMatch": true},{"id": "row_number","type": "number","display": true,"removed": false,"readOnly": true,"required": false,"displayName": "row_number","defaultMatch": false,"canBeUsedToMatch": true}],"mappingMode": "defineBelow","matchingColumns": ["row_number"],"attemptToConvertTypes": false,"convertFieldsToString": false},"options": {},"operation": "update","sheetName": {"__rl": true,"mode": "list","value": 862285542,"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1WCVR7NOEvsM5CnPKD6R1aLuNHKaQbIpAUvoK1wS3bgY/edit#gid=862285542","cachedResultName": "Offset"},"documentId": {"__rl": true,"mode": "url","value": "https://docs.google.com/spreadsheets/d/1WCVR7NOEvsM5CnPKD6R1aLuNHKaQbIpAUvoK1wS3bgY/edit?gid=862285542#gid=862285542"}},"credentials": {"googleSheetsOAuth2Api": {"id": "wBRLUCktxqXE6DVJ","name": "Google Sheets account"}},"typeVersion": 4.7},{"id": "cbb7af10-393b-459b-99dc-6b806c5f4f92","name": "HTTP Request4","type": "n8n-nodes-base.httpRequest","position": [624,928],"parameters": {"url": "={{ $json.URL }}","options": {}},"typeVersion": 4.2},{"id": "332d9b79-208b-42a5-84f0-cb64f2cef73e","name": "Extract from File","type": "n8n-nodes-base.extractFromFile","position": [832,928],"parameters": {"options": {},"operation": "pdf"},"typeVersion": 1},{"id": "122a1cb2-4a35-4120-b693-aae378b8f841","name": "Wait1","type": "n8n-nodes-base.wait","position": [2896,1024],"webhookId": "8dfc6b2f-d2e9-49db-952b-98e1ee306b4c","parameters": {},"typeVersion": 1.1},{"id": "6e69ac57-2209-4e5f-9a45-957c3b2df683","name": "Filter","type": "n8n-nodes-base.filter","position": [96,928],"parameters": {"options": {},"conditions": {"options": {"version": 2,"leftValue": "","caseSensitive": true,"typeValidation": "strict"},"combinator": "and","conditions": [{"id": "16a31f78-cd21-430f-ac05-9b4a4379d67d","operator": {"type": "string","operation": "notEquals"},"leftValue": "={{ $json.Ok }}","rightValue": "Ok"}]}},"typeVersion": 2.2},{"id": "088b07af-f113-49b0-be85-f2d3f508e963","name": "Ok","type": "n8n-nodes-base.googleSheets","position": [1776,928],"parameters": {"columns": {"value": {"Ok": "Ok","row_number": "={{ $('Loop Over offres').item.json.row_number }}"},"schema": [{"id": "Parution","type": "string","display": true,"required": false,"displayName": "Parution","defaultMatch": false,"canBeUsedToMatch": true},{"id": "Objet","type": "string","display": true,"required": false,"displayName": "Objet","defaultMatch": false,"canBeUsedToMatch": true},{"id": "URL","type": "string","display": true,"required": false,"displayName": "URL","defaultMatch": false,"canBeUsedToMatch": true},{"id": "Acheteur","type": "string","display": true,"required": false,"displayName": "Acheteur","defaultMatch": false,"canBeUsedToMatch": true},{"id": "Date limite","type": "string","display": true,"required": false,"displayName": "Date limite","defaultMatch": false,"canBeUsedToMatch": true},{"id": "Ok","type": "string","display": true,"required": false,"displayName": "Ok","defaultMatch": false,"canBeUsedToMatch": true},{"id": "row_number","type": "number","display": true,"removed": false,"readOnly": true,"required": false,"displayName": "row_number","defaultMatch": false,"canBeUsedToMatch": true}],"mappingMode": "defineBelow","matchingColumns": ["row_number"],"attemptToConvertTypes": false,"convertFieldsToString": false},"options": {},"operation": "update","sheetName": {"__rl": true,"mode": "list","value": "gid=0","cachedResultUrl": "https://docs.google.com/spreadsheets/d/1WCVR7NOEvsM5CnPKD6R1aLuNHKaQbIpAUvoK1wS3bgY/edit#gid=0","cachedResultName": "All"},"documentId": {"__rl": true,"mode": "url","value": "https://docs.google.com/spreadsheets/d/1WCVR7NOEvsM5CnPKD6R1aLuNHKaQbIpAUvoK1wS3bgY/edit?gid=0#gid=0"}},"credentials": {"googleSheetsOAuth2Api": {"id": "wBRLUCktxqXE6DVJ","name": "Google Sheets account"}},"typeVersion": 4.7},{"id": "08d027bd-ad2b-4f00-8fde-b1900aca6b7d","name": "Update offset","type": "n8n-nodes-base.googleSheets","position": [2000,-304],"parameters": {"columns": {"value": {"Offset": "={{ $json.nextOffset }}","row_number": 2},"schema": [{"id": "Offset","type": "string","display": true,"required": false,"displayName": "Offset","defaultMatch": false,"canBeUsedToMatch": true},{"id": "row_number","type": "number","display": true,"removed": false,"readOnly": true,"required": false,"displayName": "row_number","defaultMatch": false,"canBeUsedToMatch": true}],"mappingMode": "defineBelow","matchingColumns": ["row_number"],"attemptToConvertTypes": false,"convertFieldsToString": false},"options": {},"operation": "update","sheetName": {"__rl": true,"mode": "list","value": 862285542,"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1WCVR7NOEvsM5CnPKD6R1aLuNHKaQbIpAUvoK1wS3bgY/edit#gid=862285542","cachedResultName": "Offset"},"documentId": {"__rl": true,"mode": "url","value": "https://docs.google.com/spreadsheets/d/1WCVR7NOEvsM5CnPKD6R1aLuNHKaQbIpAUvoK1wS3bgY/edit?gid=862285542#gid=862285542"}},"credentials": {"googleSheetsOAuth2Api": {"id": "wBRLUCktxqXE6DVJ","name": "Google Sheets account"}},"typeVersion": 4.7},{"id": "6c7e50b2-c920-405b-9382-638ef5a4c620","name": "Get config","type": "n8n-nodes-base.googleSheets","position": [-624,-304],"parameters": {"options": {},"sheetName": {"__rl": true,"mode": "list","value": 966659321,"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1wapLLWjwzo7SfG_YEsUlFaPRs1MmjxPRhRc6BlwBUAY/edit#gid=966659321","cachedResultName": "Config"},"documentId": {"__rl": true,"mode": "url","value": "https://docs.google.com/spreadsheets/d/1wapLLWjwzo7SfG_YEsUlFaPRs1MmjxPRhRc6BlwBUAY/edit?gid=966659321#gid=966659321"}},"credentials": {"googleSheetsOAuth2Api": {"id": "wBRLUCktxqXE6DVJ","name": "Google Sheets account"}},"typeVersion": 4.7},{"id": "049a5165-d7bb-4c82-a631-37c448c6f8d2","name": "Sticky Note1","type": "n8n-nodes-base.stickyNote","position": [-1040,560],"parameters": {"color": 3,"width": 4320,"height": 1136,"content": "# Big phase 2 : filtrer les appels d'offres en fonction de mots clés"},"typeVersion": 1},{"id": "435daded-cb60-462a-bf29-5182beb34fa9","name": "Sticky Note","type": "n8n-nodes-base.stickyNote","position": [-1040,-592],"parameters": {"color": 5,"width": 4320,"height": 1088,"content": "# Big phase 1 : Récupérer tout les appels d'offre avec une configuration"},"typeVersion": 1},{"id": "4e23fa14-6253-4456-90d8-0b9f99cf873e","name": "Sticky Note2","type": "n8n-nodes-base.stickyNote","position": [-816,-448],"parameters": {"width": 480,"height": 320,"content": "# Phase 1.1 : Get configuration"},"typeVersion": 1},{"id": "3533cc22-225d-4e7e-b139-1db9c8266a91","name": "Sticky Note3","type": "n8n-nodes-base.stickyNote","position": [-304,-448],"parameters": {"width": 400,"height": 320,"content": "# Phase 1.2 : Get offset"},"typeVersion": 1},{"id": "c08dcdb6-dad2-41c6-a3b1-c787662f0dbd","name": "Sticky Note4","type": "n8n-nodes-base.stickyNote","position": [128,-448],"parameters": {"width": 416,"height": 320,"content": "# Phase 1.3: Get Tenders (API Call)"},"typeVersion": 1},{"id": "265b9db5-c103-4fcf-943f-18f42908264f","name": "Sticky Note5","type": "n8n-nodes-base.stickyNote","position": [576,-448],"parameters": {"width": 432,"height": 320,"content": "# Phase 1.4: Tenders Sorting (Market Type Filter)\n"},"typeVersion": 1},{"id": "3a1ebce4-785d-4558-a5bf-5a2aeb94886e","name": "Sticky Note6","type": "n8n-nodes-base.stickyNote","position": [1024,-464],"parameters": {"width": 624,"height": 336,"content": "# Phase 1.5 : import data to sheet"},"typeVersion": 1},{"id": "53cdf26f-baa7-4c10-9281-614134d12058","name": "Sticky Note7","type": "n8n-nodes-base.stickyNote","position": [1664,-464],"parameters": {"width": 496,"height": 336,"content": "# Phase 1.6 : Calculate Offset (Pagination) + Update Offset"},"typeVersion": 1},{"id": "180969ad-bedf-4f5f-8b9e-68a82b8dd883","name": "Sticky Note8","type": "n8n-nodes-base.stickyNote","position": [2176,-464],"parameters": {"width": 496,"height": 336,"content": "# Phase 1.7 : Is Offset Done? (Loop Control)"},"typeVersion": 1},{"id": "e5342f99-2545-47c4-a80b-26d979cd7828","name": "Sticky Note9","type": "n8n-nodes-base.stickyNote","position": [2704,-464],"parameters": {"width": 496,"height": 336,"content": "# Phase 1.8 : Reset offset if done"},"typeVersion": 1},{"id": "3d9d0e75-5699-406d-9926-54d190ca4672","name": "Schedule Trigger1","type": "n8n-nodes-base.scheduleTrigger","position": [-992,-304],"parameters": {"rule": {"interval": [{"field": "cronExpression","expression": "0 8 1 * *"}]}},"typeVersion": 1.2},{"id": "59fcdbd5-2a44-472b-a6f0-24edcbc4ad74","name": "Sticky Note10","type": "n8n-nodes-base.stickyNote","position": [-256,816],"parameters": {"width": 528,"height": 304,"content": "# Phase 2.2 : Get All Tenders & Filter Processed Ones"},"typeVersion": 1},{"id": "62d988fa-e756-4d2c-a4d4-7776af88ed67","name": "Sticky Note11","type": "n8n-nodes-base.stickyNote","position": [336,784],"parameters": {"width": 624,"height": 336,"content": "# Phase 2.3 : Loop Over Tenders & Extract Content"},"typeVersion": 1},{"id": "93025880-cce5-49d9-b66b-06471a10665c","name": "Sticky Note12","type": "n8n-nodes-base.stickyNote","position": [1024,816],"parameters": {"width": 512,"height": 304,"content": "# Phase 2.4 : Analyze Tender Content (Keyword Matching)\n"},"typeVersion": 1},{"id": "cb5a8329-12a0-49f1-8a0e-04a55ce169f8","name": "Get keyword","type": "n8n-nodes-base.googleSheets","position": [-608,928],"parameters": {"options": {},"sheetName": {"__rl": true,"mode": "list","value": 966659321,"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1wapLLWjwzo7SfG_YEsUlFaPRs1MmjxPRhRc6BlwBUAY/edit#gid=966659321","cachedResultName": "Config"},"documentId": {"__rl": true,"mode": "url","value": "https://docs.google.com/spreadsheets/d/1wapLLWjwzo7SfG_YEsUlFaPRs1MmjxPRhRc6BlwBUAY/edit?gid=966659321#gid=966659321"}},"credentials": {"googleSheetsOAuth2Api": {"id": "wBRLUCktxqXE6DVJ","name": "Google Sheets account"}},"typeVersion": 4.7},{"id": "6d56dc34-92ae-4d30-96be-76123b9f1c46","name": "Sticky Note13","type": "n8n-nodes-base.stickyNote","position": [-816,816],"parameters": {"width": 528,"height": 304,"content": "# Phase 2.1 : Filter Tenders Based on Keywords\n"},"typeVersion": 1},{"id": "7881bd24-3287-4689-8e3c-6436807d030a","name": "Sticky Note15","type": "n8n-nodes-base.stickyNote","position": [2112,816],"parameters": {"width": 576,"height": 304,"content": "# Phase 2.6: Save Matching Tenders to Target Sheet"},"typeVersion": 1},{"id": "f4302f99-e21f-4a95-836c-49e9b6cb03e7","name": "Tenders sorting","type": "n8n-nodes-base.code","position": [752,-304],"parameters": {"jsCode": "// Node code pour filtrer les appels d'offres selon les types définis dans la config (n8n)\n// Récupération de la configuration depuis le node \"Get config\"\nconst configData = $node[\"Get config\"].json;\nconsole.log(\"Configuration:\", JSON.stringify(configData, null, 2));\n\n// Extraction des types de marché activés (checkbox à true) depuis la config\nlet marketTypes = [];\n\nif (Array.isArray(configData)) {\n // Si c'est un tableau, on prend le premier élément\n const config = configData[0];\n if (config.Travaux === true) marketTypes.push(\"TRAVAUX\");\n if (config.Services === true) marketTypes.push(\"SERVICES\");\n if (config.Fournitures === true) marketTypes.push(\"FOURNITURES\");\n} else {\n // Si c'est un objet unique\n if (configData.Travaux === true) marketTypes.push(\"TRAVAUX\");\n if (configData.Services === true) marketTypes.push(\"SERVICES\");\n if (configData.Fournitures === true) marketTypes.push(\"FOURNITURES\");\n}\n\nconsole.log(\"Types de marché à filtrer:\", marketTypes);\n\n// Vérification qu'au moins un type est sélectionné\nif (marketTypes.length === 0) {\n throw new Error(\"Aucun type de marché sélectionné dans la configuration\");\n}\n\n// Récupération des données depuis l'input\nconst inputData = $input.all();\n\n// Debug : afficher la structure des données\nconsole.log(\"inputData:\", JSON.stringify(inputData, null, 2));\n\n// Vérification que les données sont bien structurées\nif (!inputData || inputData.length === 0) {\n throw new Error(\"Aucune donnée en entrée\");\n}\n\n// Récupération du premier item\nconst firstItem = inputData[0];\nconsole.log(\"firstItem:\", JSON.stringify(firstItem, null, 2));\n\nconst data = firstItem.json;\nconsole.log(\"data:\", JSON.stringify(data, null, 2));\n\n// La structure semble être directement un tableau avec total_count et results\nlet payload;\nif (Array.isArray(data)) {\n payload = data[0];\n} else if (data && data.total_count !== undefined && data.results) {\n payload = data;\n} else {\n throw new Error(\"Structure de données non reconnue\");\n}\n\nconsole.log(\"payload:\", JSON.stringify(payload, null, 2));\n\nif (!payload.results || !Array.isArray(payload.results)) {\n throw new Error(\"Structure de données invalide - results non trouvé\");\n}\n\n// Filtrage des résultats pour ne garder que les appels d'offres correspondant aux types de la config\nconst filteredResults = payload.results.filter(item => {\n if (!item.type_marche || !Array.isArray(item.type_marche)) {\n return false;\n }\n \n // Vérifie si au moins un des types de marché de l'item correspond à ceux de la config\n return item.type_marche.some(type => \n marketTypes.includes(type)\n );\n});\n\n// Construction du tableau de sortie avec la même structure\nconst output = [{\n total_count: payload.total_count, // Garde le total_count original\n filtered_count: filteredResults.length, // Ajoute le nombre filtré\n market_types_used: marketTypes, // Ajoute les types utilisés pour le filtre\n results: filteredResults\n}];\n\n// Log pour debug\nconsole.log(`Nombre d'appels d'offres filtrés: ${filteredResults.length} sur ${payload.total_count} total`);\nconsole.log(`Types de marché filtrés: ${marketTypes.join(', ')}`);\n\n// Retour du résultat\nreturn output;"},"typeVersion": 2},{"id": "f54308af-1c9e-477a-bca2-4b9ede90d33b","name": "Sticky Note16","type": "n8n-nodes-base.stickyNote","position": [-816,-80],"parameters": {"width": 480,"height": 368,"content": "### What the system does:\n\nConnects to Google Sheets to retrieve the configuration settings\nReads the filtering parameters including:\n\nTypes of markets to filter (Travaux/Works, Services, Fournitures/Supplies)\nTime period for tender retrieval (in days)\n\n\nPasses configuration to subsequent workflow steps\n\n### Result:\n\n✅ Configuration parameters loaded and ready for filtering\n✅ Market type preferences identified (Works, Services, Supplies)\n✅ Search period defined for API queries"},"typeVersion": 1},{"id": "aca85321-0d84-4522-b21b-1500d709320d","name": "Sticky Note17","type": "n8n-nodes-base.stickyNote","position": [-1680,-608],"parameters": {"color": 4,"width": 592,"height": 1504,"content": "# Phase 0: Initial Configuration SetupWhat you do:\n\n## Duplicate the Google Sheets template by accessing this link: https://docs.google.com/spreadsheets/d/1wapLLWjwzo7SfG_YEsUlFaPRs1MmjxPRhRc6BlwBUAY/edit?gid=966659321#gid=966659321\n\nClick on \"File\" → \"Make a copy\"\nSave it to your Google Drive\n\n\n### Configure the \"Config\" sheet with your preferences:\n\nCheck the market types you want to monitor:\n\n☐ Travaux (Works)\n☐ Services (Services)\n☐ Fournitures (Supplies)\n\n\n\n### Set the search period (in days):\n\nEnter the number of days to look back (e.g., \"30\" for the last 30 days)\nThis defines how far back the system will search for tenders\n\n\n\n### Add your keywords for tender filtering:\n\nEnter the keywords you want to find in tender announcements\nThese will be used to filter relevant opportunities\n\n\n\n## Update the workflow with your duplicated spreadsheet URL:\n\nCopy the URL of your new spreadsheet\nReplace the spreadsheet links in the n8n workflow nodes\n\n## Configure the Schedule Trigger in n8n:\n\nOpen the \"Schedule Trigger\" node in your workflow\nSet your preferred execution frequency:\n\nDaily, weekly, monthly, or custom cron expression\nDefault: 1st day of each month at 8:00 AM (0 8 1 * *)\n\n\nAdjust the schedule according to your monitoring needs\n\n\n## What the system does:\n\nStores your configuration in a centralized Google Sheet\nReads your preferences at each workflow execution\nApplies your filters automatically to all tender searches\nResult:\n✅ Personalized configuration ready for use\n✅ Market types and period defined according to your needs\n✅ Keywords set for precise filtering\n✅ Workflow configured with your own spreadsheet\n✅ Ready to start Big Phase 1 (automatic tender retrieval)"},"typeVersion": 1},{"id": "9ef13b1e-cfd7-4f58-90d7-3c6200b63fb5","name": "Sticky Note18","type": "n8n-nodes-base.stickyNote","position": [-304,-80],"parameters": {"width": 400,"height": 304,"content": "### What the system does:\n\nRetrieves the current offset value from Google Sheets (Offset tab)\nDetermines the starting point for paginated API requests\nEnables batch processing of large datasets by tracking pagination position\n\n### Result:\n\n✅ Current pagination position retrieved\n✅ Workflow ready to fetch the correct batch of tenders\n✅ Prevents duplicate data retrieval"},"typeVersion": 1},{"id": "09ff5fd8-ecf2-49c4-a0a6-192a924c30dd","name": "Sticky Note19","type": "n8n-nodes-base.stickyNote","position": [128,-80],"parameters": {"width": 416,"height": 448,"content": "### What the system does:\n\nCalls the BOAMP API (French public procurement bulletin) with configured parameters\nApplies filters based on:\n\nPublication date (using period from configuration)\nCurrent offset for pagination (100 records per batch)\n\n\nRetrieves tender data including:\n\nWeb ID, publication date, tender object/description\nBuyer name, deadline date\nMarket type, announcement URL, department code\n\n### Result:\n\n✅ Up to 100 tender records retrieved per API call\n✅ Data filtered by publication date period\n✅ Complete tender information collected for processing\n"},"typeVersion": 1},{"id": "4a43bad3-a932-4d7e-a66c-be7e10aa83d1","name": "Sticky Note20","type": "n8n-nodes-base.stickyNote","position": [576,-80],"parameters": {"width": 432,"height": 352,"content": "### What the system does:\n\nFilters the API results based on market types from configuration\nCompares each tender's market type against enabled categories (Works, Services, Supplies)\nKeeps only matching tenders that correspond to selected market types\nLogs filtering statistics (total count vs. filtered count)\n\n### Result:\n\n✅ Only relevant tender types retained\n✅ Unwanted market categories excluded\n✅ Optimized dataset for further processing\n"},"typeVersion": 1},{"id": "ce64a66d-9cbb-41ad-8226-cfd6c2646f1f","name": "Sticky Note21","type": "n8n-nodes-base.stickyNote","position": [1024,-80],"parameters": {"width": 624,"height": 384,"content": "### What the system does:\n\nFormats the filtered tender data into a structured format:\n\nPublication date (dd/MM/yyyy format)\nTender object/description\nPDF URL (automatically generated from web ID and publication date)\nBuyer name\nResponse deadline (dd/MM/yyyy format)\n\n\nAppends formatted data to Google Sheets (All tab)\nPreserves data structure for easy reading and analysis\n\n### Result:\n✅ Tender data saved in readable format\n✅ Direct PDF links generated for each tender\n✅ All information centralized in Google Sheets"},"typeVersion": 1},{"id": "d721845b-d424-4671-8141-9db827f4b9cb","name": "Sticky Note22","type": "n8n-nodes-base.stickyNote","position": [1664,-80],"parameters": {"width": 496,"height": 368,"content": "### What the system does:\n\nAnalyzes the API response to determine:\n\nTotal number of available tenders\nNumber of records in current batch\nWhether more data remains to fetch\n\n\nCalculates the next offset value (current offset + 100)\nUpdates the offset in Google Sheets for the next iteration\n\n### Result:\n\n✅ Pagination position calculated automatically\n✅ Next batch position saved for continuation\n✅ Progress tracked throughout the retrieval process"},"typeVersion": 1},{"id": "a2fea90a-160c-4605-a08a-5ea12b9d6c88","name": "Sticky Note23","type": "n8n-nodes-base.stickyNote","position": [2176,-80],"parameters": {"width": 496,"height": 432,"content": "### What the system does:\n\nChecks if more data is available to retrieve\nEvaluates two conditions:\n\nAre there still records to fetch? (nextOffset < totalCount)\nDid the last batch return results?\n\n\nRoutes the workflow based on the result:\n\nIf more data exists → continue to wait phase\nIf all data retrieved → proceed to reset phase\n\n\n\n### Result:\n\n✅ Automatic decision on workflow continuation\n✅ Prevents unnecessary API calls when data is exhausted\n✅ Ensures complete data retrieval"},"typeVersion": 1},{"id": "5d559d01-3175-49d5-a228-a087f3f082aa","name": "Sticky Note24","type": "n8n-nodes-base.stickyNote","position": [2704,-80],"parameters": {"width": 496,"height": 272,"content": "### What the system does:\n\nResets the offset value to 0 in Google Sheets\nPrepares the workflow for the next scheduled execution\nCompletes the Big Phase 1 data collection cycle\n\n### Result:\n\n✅ Workflow ready for next execution\n✅ Offset counter reset for fresh start\n✅ Big Phase 1 complete - all tenders retrieved and stored"},"typeVersion": 1},{"id": "ce07b9ef-230d-49e5-8c41-6bea96b194cd","name": "Sticky Note25","type": "n8n-nodes-base.stickyNote","position": [-816,1136],"parameters": {"width": 528,"height": 272,"content": "### What the system does:\n\nRetrieves keywords from the Config sheet in Google Sheets\nReads the comma-separated list of search terms you configured in Phase 0\nPrepares the keyword list for content matching across all tender documents\n\n### Result:\n\n✅ Keywords loaded from your configuration\n✅ Search terms ready for document analysis\n✅ Filter criteria established for tender matching"},"typeVersion": 1},{"id": "b041359a-cbee-462e-ba40-90c989cc527c","name": "Sticky Note26","type": "n8n-nodes-base.stickyNote","position": [-256,1136],"parameters": {"width": 528,"height": 304,"content": "### What the system does:\n\nRetrieves all tenders from the \"All\" sheet (populated in Big Phase 1)\nFilters out already processed tenders by checking the \"Ok\" column\nIdentifies new tenders that need keyword analysis\nPrevents duplicate processing of the same tender\n\n### Result:\n\n✅ Complete list of tenders retrieved\n✅ Already-analyzed tenders excluded\n✅ Only new tenders queued for processing\n✅ Efficient workflow with no redundant analysis"},"typeVersion": 1},{"id": "b0dae467-4f37-4d99-a70d-76c53f0302df","name": "Sticky Note27","type": "n8n-nodes-base.stickyNote","position": [336,1136],"parameters": {"width": 624,"height": 304,"content": "### What the system does:\n\nProcesses tenders one by one in a sequential loop\nDownloads the PDF file for each tender using the generated URL\nExtracts text content from the PDF document\nPrepares the text for keyword matching analysis\n\n### Result:\n\n✅ PDF documents downloaded automatically\n✅ Text content extracted from each tender\n✅ Data ready for keyword search\n✅ Sequential processing ensures stability"},"typeVersion": 1},{"id": "7e4da99d-ab37-4c14-83f0-1bfdaebfca5e","name": "Sticky Note28","type": "n8n-nodes-base.stickyNote","position": [1024,1136],"parameters": {"width": 512,"height": 448,"content": "### What the system does:\n\nNormalizes both the tender text and keywords (removes accents, converts to lowercase)\nSearches for keyword matches within the extracted tender content\nIdentifies all matching terms and their positions in the document\nRecords match details including:\n\nWhich keywords were found\nNumber of matches\nContext around each match (50 characters before/after)\n\n\nDetermines if the tender is relevant based on keyword presence\n\n### Result:\n\n✅ Tender content analyzed for keyword presence\n✅ All matching terms identified and logged\n✅ Relevance score calculated automatically\n✅ Context provided for each match\n"},"typeVersion": 1},{"id": "63e86445-b28e-4695-9d64-cc23e786cfe9","name": "Sticky Note14","type": "n8n-nodes-base.stickyNote","position": [1568,816],"parameters": {"width": 512,"height": 304,"content": "# Phase 2.5: Mark Tender as Processed"},"typeVersion": 1},{"id": "cf19c558-d1c0-4643-8bf4-c1dbcc3c8305","name": "Sticky Note29","type": "n8n-nodes-base.stickyNote","position": [1568,1136],"parameters": {"width": 512,"height": 288,"content": "### What the system does:\n\nUpdates the \"All\" sheet by adding \"Ok\" in the status column\nMarks the tender as analyzed regardless of match result\nPrevents reprocessing in future workflow executions\nMaintains processing history for tracking\n\n### Result:\n\n✅ Tender marked as processed in the sheet\n✅ Status tracking enabled for all tenders\n✅ Workflow progress visible and trackable"},"typeVersion": 1},{"id": "0d1d396d-9c99-4cfb-b70d-222673eb5cd2","name": "Sticky Note30","type": "n8n-nodes-base.stickyNote","position": [2112,1136],"parameters": {"width": 576,"height": 432,"content": "### What the system does:\n\nEvaluates if keywords were found in the tender\nIf match found, routes to the Target sheet\nIf no match, waits briefly and continues to next tender\nAppends matching tender to the \"Target\" sheet with:\n\nDate added (current date)\nAll tender details (publication date, object, URL, buyer, deadline)\nList of matched keywords\n\n\nCreates a curated list of relevant opportunities only\n\n### Result:\n\n✅ Only relevant tenders saved to Target sheet\n✅ Matched keywords listed for each tender\n✅ Date tracking for when tender was identified\n✅ Clean, filtered list of opportunities ready for review\n✅ Non-matching tenders excluded automatically"},"typeVersion": 1}],"pinData": {},"connections": {"Ok": {"main": [[{"node": "Query match ?","type": "main","index": 0}]]},"Wait": {"main": [[{"node": "Get Offset","type": "main","index": 0}]]},"Wait1": {"main": [[{"node": "Loop Over offres","type": "main","index": 0}]]},"Filter": {"main": [[{"node": "Loop Over offres","type": "main","index": 0}]]},"Get All": {"main": [[{"node": "Filter","type": "main","index": 0}]]},"Get query": {"main": [[{"node": "Ok","type": "main","index": 0}]]},"Get Offset": {"main": [[{"node": "HTTP Request","type": "main","index": 0}]]},"Get config": {"main": [[{"node": "Get Offset","type": "main","index": 0}]]},"Get keyword": {"main": [[{"node": "Get All","type": "main","index": 0}]]},"HTTP Request": {"main": [[{"node": "Tenders sorting","type": "main","index": 0}]]},"Target offre": {"main": [[{"node": "Loop Over offres","type": "main","index": 0}]]},"HTTP Request4": {"main": [[{"node": "Extract from File","type": "main","index": 0}]]},"Query match ?": {"main": [[{"node": "Target offre","type": "main","index": 0}],[{"node": "Wait1","type": "main","index": 0}]]},"Update offset": {"main": [[{"node": "Check Continue Loop","type": "main","index": 0}]]},"Format Results": {"main": [[{"node": "Append row in sheet","type": "main","index": 0}]]},"Tenders sorting": {"main": [[{"node": "Format Results","type": "main","index": 0},{"node": "Process Response","type": "main","index": 0}]]},"Loop Over offres": {"main": [[],[{"node": "HTTP Request4","type": "main","index": 0}]]},"Process Response": {"main": [[{"node": "Update offset","type": "main","index": 0}]]},"Schedule Trigger": {"main": [[{"node": "Get keyword","type": "main","index": 0}]]},"Extract from File": {"main": [[{"node": "Get query","type": "main","index": 0}]]},"Schedule Trigger1": {"main": [[{"node": "Get config","type": "main","index": 0}]]},"Check Continue Loop": {"main": [[{"node": "Wait","type": "main","index": 0}],[{"node": "Reset Offset","type": "main","index": 0}]]}}}
В редакторі n8n: вставте через Ctrl+V→Workflow буде створено