diff --git a/create_tables.sql b/create_tables.sql new file mode 100644 index 0000000..1a1dc4c --- /dev/null +++ b/create_tables.sql @@ -0,0 +1,124 @@ +/* + Script de création des tables SQL Server pour la synchronisation Meta Lead Gen. + + IMPORTANT : + Avant de lancer ce script, la base de données doit déjà exister. + + Étapes : + 1. Se connecter à SQL Server avec un outil comme Adminer, SSMS, Azure Data Studio ou DBeaver. + 2. Créer la base de données si elle n'existe pas encore : + + CREATE DATABASE MetaLeadDb; + + 3. Se connecter ensuite à la base MetaLeadDb. + 4. Lancer ce script pour créer les tables : + - leadgen_forms + - lead_data + - sync_logs + + Ce script ne crée pas la base de données. + Il crée uniquement les tables nécessaires au projet.*/ + + + IF OBJECT_ID(N'dbo.sync_logs', N'U') IS NOT NULL + DROP TABLE dbo.sync_logs; + +IF OBJECT_ID(N'dbo.lead_data', N'U') IS NOT NULL + DROP TABLE dbo.lead_data; + +IF OBJECT_ID(N'dbo.leadgen_forms', N'U') IS NOT NULL + DROP TABLE dbo.leadgen_forms; + +CREATE TABLE dbo.leadgen_forms ( + id BIGINT IDENTITY(1,1) NOT NULL, + meta_form_id NVARCHAR(64) NOT NULL, + page_id NVARCHAR(64) NOT NULL, + name NVARCHAR(255) NOT NULL, + locale NVARCHAR(20) NULL, + status NVARCHAR(50) NOT NULL, + created_at DATETIME2 NOT NULL CONSTRAINT DF_leadgen_forms_created_at DEFAULT SYSDATETIME(), + updated_at DATETIME2 NULL, + + CONSTRAINT PK_leadgen_forms PRIMARY KEY (id), + CONSTRAINT UQ_leadgen_forms_meta_form_id UNIQUE (meta_form_id) +); + +CREATE TABLE dbo.lead_data ( + id BIGINT IDENTITY(1,1) NOT NULL, + meta_lead_id NVARCHAR(64) NOT NULL, + meta_form_id NVARCHAR(64) NOT NULL, + created_time DATETIME2 NULL, + je_suis NVARCHAR(255) NULL, + nom NVARCHAR(255) NULL, + first_name NVARCHAR(255) NULL, + adresse_email NVARCHAR(255) NULL, + phone_number NVARCHAR(50) NULL, + city NVARCHAR(255) NULL, + campus NVARCHAR(100) NULL, + status NVARCHAR(50) NOT NULL CONSTRAINT DF_lead_data_status DEFAULT N'RECEIVED', + error_message NVARCHAR(MAX) NULL, + synced_at DATETIME2 NULL, + created_at DATETIME2 NOT NULL CONSTRAINT DF_lead_data_created_at DEFAULT SYSDATETIME(), + updated_at DATETIME2 NULL, + + CONSTRAINT PK_lead_data PRIMARY KEY (id), + CONSTRAINT UQ_lead_data_meta_lead_id UNIQUE (meta_lead_id), + + CONSTRAINT FK_lead_data_leadgen_forms + FOREIGN KEY (meta_form_id) + REFERENCES dbo.leadgen_forms(meta_form_id) + ON UPDATE CASCADE + ON DELETE NO ACTION, + + CONSTRAINT CK_lead_data_status + CHECK (status IN (N'RECEIVED', N'READY_TO_SEND', N'SENT', N'ERROR', N'INVALID')) +); + +CREATE TABLE dbo.sync_logs ( + id BIGINT IDENTITY(1,1) NOT NULL, + lead_id BIGINT NOT NULL, + oscar_payload NVARCHAR(MAX) NULL, + oscar_response NVARCHAR(MAX) NULL, + status NVARCHAR(50) NOT NULL, + error_message NVARCHAR(MAX) NULL, + attempted_at DATETIME2 NOT NULL CONSTRAINT DF_sync_logs_attempted_at DEFAULT SYSDATETIME(), + + CONSTRAINT PK_sync_logs PRIMARY KEY (id), + + CONSTRAINT FK_sync_logs_lead_data + FOREIGN KEY (lead_id) + REFERENCES dbo.lead_data(id) + ON UPDATE CASCADE + ON DELETE CASCADE, + + CONSTRAINT CK_sync_logs_status + CHECK (status IN (N'SENT', N'ERROR', N'INVALID')) +); + +CREATE INDEX IX_leadgen_forms_status +ON dbo.leadgen_forms(status); + +CREATE INDEX IX_lead_data_meta_form_id +ON dbo.lead_data(meta_form_id); + +CREATE INDEX IX_lead_data_status +ON dbo.lead_data(status); + +CREATE INDEX IX_lead_data_created_time +ON dbo.lead_data(created_time); + +CREATE INDEX IX_lead_data_adresse_email +ON dbo.lead_data(adresse_email); + +CREATE INDEX IX_lead_data_campus +ON dbo.lead_data(campus); + +CREATE INDEX IX_sync_logs_lead_id +ON dbo.sync_logs(lead_id); + +CREATE INDEX IX_sync_logs_status +ON dbo.sync_logs(status); + +CREATE INDEX IX_sync_logs_attempted_at +ON dbo.sync_logs(attempted_at); + diff --git a/main.py b/main.py index caa955c..16b7801 100644 --- a/main.py +++ b/main.py @@ -1,52 +1,34 @@ """ -Synchronisation Meta Lead Gen vers MySQL local (V1). +Synchronisation Meta Lead Gen vers SQL Server local. + +Récupère les formulaires actifs et leurs leads via l'API Graph Meta, +puis les stocke dans SQL Server sans créer de doublons. -Récupère les formulaires actifs, leurs questions et leurs leads via l'API -Graph Meta, puis les stocke dans la base locale sans créer de doublons. L'injection vers OscarCRM n'est pas gérée à ce stade. """ -import json import os -from datetime import datetime +from datetime import datetime, timezone -import mysql.connector +import pyodbc import requests from dotenv import load_dotenv -# ============================================================ -# Chargement de la configuration -# ============================================================ -# Les informations sensibles sont lues depuis le fichier .env. -# Cela évite de stocker le token Meta ou le mot de passe MySQL -# directement dans le code source. -# ============================================================ - load_dotenv() META_ACCESS_TOKEN = os.getenv("META_ACCESS_TOKEN") META_API_VERSION = os.getenv("META_API_VERSION", "v25.0") META_PAGE_ID = os.getenv("META_PAGE_ID") -DB_CONFIG = { - "host": os.getenv("DB_HOST", "localhost"), - "port": int(os.getenv("DB_PORT", "3306")), - "database": os.getenv("DB_NAME", "meta_lead_db"), - "user": os.getenv("DB_USER", "root"), - "password": os.getenv("DB_PASSWORD", ""), - "charset": "utf8mb4", - "use_unicode": True, -} +DB_DRIVER = os.getenv("DB_DRIVER", "SQL Server") +DB_SERVER = os.getenv("DB_SERVER", "localhost") +DB_PORT = os.getenv("DB_PORT", "1433") +DB_NAME = os.getenv("DB_NAME", "MetaLeadDb") +DB_USER = os.getenv("DB_USER", "sa") +DB_PASSWORD = os.getenv("DB_PASSWORD") -# ============================================================ -# Formulaires autorisés pour le test -# ============================================================ -# On limite volontairement la synchronisation aux formulaires -# actifs connus afin d’éviter de traiter des formulaires archivés. -# ============================================================ - FORM_CAMPUSES = { "1016111097572602": "Marseille", "1032481262797041": "Nantes", @@ -56,25 +38,22 @@ FORM_CAMPUSES = { def get_database_connection(): - """Ouvre une connexion MySQL locale.""" + """Ouvre une connexion vers SQL Server.""" - return mysql.connector.connect(**DB_CONFIG) + connection_string = ( + f"DRIVER={{{DB_DRIVER}}};" + f"SERVER={DB_SERVER},{DB_PORT};" + f"DATABASE={DB_NAME};" + f"UID={DB_USER};" + f"PWD={DB_PASSWORD};" + "TrustServerCertificate=yes;" + ) + + return pyodbc.connect(connection_string) def call_meta_api(path, params=None): - """ - Exécute un appel GET vers l’API Graph Meta. - - Args: - path: Chemin Graph API, par exemple 'PAGE_ID/leadgen_forms'. - params: Paramètres complémentaires de la requête. - - Returns: - Réponse JSON retournée par Meta. - - Raises: - requests.HTTPError: si Meta retourne une erreur HTTP. - """ + """Exécute un appel GET vers l'API Graph Meta.""" url = f"https://graph.facebook.com/{META_API_VERSION}/{path}" @@ -88,39 +67,19 @@ def call_meta_api(path, params=None): def parse_meta_datetime(value): - """ - Convertit une date Meta en format compatible MySQL. - - Exemple Meta : - 2026-06-23T06:37:24+0000 - - Returns: - Date au format 'YYYY-MM-DD HH:MM:SS' ou None. - """ + """Convertit une date Meta en datetime compatible SQL Server.""" if not value: return None parsed_date = datetime.strptime(value, "%Y-%m-%dT%H:%M:%S%z") - return parsed_date.strftime("%Y-%m-%d %H:%M:%S") + utc_date = parsed_date.astimezone(timezone.utc) + + return utc_date.replace(tzinfo=None) def normalize_field_data(field_data): - """ - Transforme field_data Meta en dictionnaire simple. - - Exemple d’entrée Meta : - [ - {"name": "nom", "values": ["Dupont"]}, - {"name": "first_name", "values": ["Jean"]} - ] - - Exemple de sortie : - { - "nom": "Dupont", - "first_name": "Jean" - } - """ + """Convertit field_data Meta en dictionnaire simple.""" normalized = {} @@ -134,8 +93,19 @@ def normalize_field_data(field_data): return normalized +def get_email_from_fields(fields): + """Récupère l'email en gérant les variantes possibles du champ Meta.""" + + return ( + fields.get("adresse_e-mail") + or fields.get("adresse-e-mail") + or fields.get("adresse_email") + or fields.get("email") + ) + + def fetch_active_forms(): - """Récupère les formulaires Lead Gen actifs de la Page Meta.""" + """Récupère les formulaires Lead Gen actifs autorisés.""" result = call_meta_api( f"{META_PAGE_ID}/leadgen_forms", @@ -149,30 +119,13 @@ def fetch_active_forms(): return [ form for form in forms - if form.get("status") == "ACTIVE" and form.get("id") in FORM_CAMPUSES + if form.get("status") == "ACTIVE" + and form.get("id") in FORM_CAMPUSES ] -def fetch_form_questions(form_id): - """Récupère les questions/champs d’un formulaire Meta.""" - - result = call_meta_api( - form_id, - { - "fields": "id,name,status,locale,questions", - }, - ) - - return result.get("questions", []), result - - def fetch_form_leads(form_id): - """ - Récupère les leads d’un formulaire Meta. - - La pagination est prise en compte afin de récupérer toutes les - pages de résultats retournées par Meta. - """ + """Récupère tous les leads d'un formulaire Meta avec pagination.""" all_leads = [] @@ -185,7 +138,6 @@ def fetch_form_leads(form_id): ) all_leads.extend(result.get("data", [])) - next_url = result.get("paging", {}).get("next") while next_url: @@ -194,103 +146,89 @@ def fetch_form_leads(form_id): result = response.json() all_leads.extend(result.get("data", [])) - next_url = result.get("paging", {}).get("next") return all_leads -def upsert_form(connection, form, raw_payload): - """Insère ou met à jour un formulaire Meta en base.""" +def upsert_leadgen_form(connection, form): + """Insère ou met à jour un formulaire Meta dans leadgen_forms.""" - query = """ - INSERT INTO meta_forms ( + update_query = """ + UPDATE dbo.leadgen_forms + SET + page_id = ?, + name = ?, + locale = ?, + status = ?, + updated_at = SYSDATETIME() + WHERE meta_form_id = ? + """ + + insert_query = """ + INSERT INTO dbo.leadgen_forms ( meta_form_id, page_id, name, - status, locale, - campus, - raw_payload + status ) - VALUES (%s, %s, %s, %s, %s, %s, %s) - ON DUPLICATE KEY UPDATE - name = VALUES(name), - status = VALUES(status), - locale = VALUES(locale), - campus = VALUES(campus), - raw_payload = VALUES(raw_payload), - updated_at = CURRENT_TIMESTAMP + VALUES (?, ?, ?, ?, ?) """ - values = ( - form.get("id"), - META_PAGE_ID, - form.get("name"), - form.get("status"), - form.get("locale"), - FORM_CAMPUSES.get(form.get("id")), - json.dumps(raw_payload, ensure_ascii=False), + cursor = connection.cursor() + + cursor.execute( + update_query, + ( + META_PAGE_ID, + form.get("name"), + form.get("locale"), + form.get("status"), + form.get("id"), + ), ) - cursor = connection.cursor() - cursor.execute(query, values) + if cursor.rowcount == 0: + cursor.execute( + insert_query, + ( + form.get("id"), + META_PAGE_ID, + form.get("name"), + form.get("locale"), + form.get("status"), + ), + ) + connection.commit() cursor.close() -def upsert_question(connection, form_id, question): - """Insère ou met à jour une question de formulaire Meta.""" - - query = """ - INSERT INTO meta_form_questions ( - meta_form_id, - question_id, - field_key, - label, - question_type, - question_options, - raw_payload - ) - VALUES (%s, %s, %s, %s, %s, %s, %s) - ON DUPLICATE KEY UPDATE - question_id = VALUES(question_id), - label = VALUES(label), - question_type = VALUES(question_type), - question_options = VALUES(question_options), - raw_payload = VALUES(raw_payload), - updated_at = CURRENT_TIMESTAMP - """ - - values = ( - form_id, - question.get("id"), - question.get("key"), - question.get("label"), - question.get("type"), - json.dumps(question.get("options"), ensure_ascii=False) - if question.get("options") is not None else None, - json.dumps(question, ensure_ascii=False), - ) - - cursor = connection.cursor() - cursor.execute(query, values) - connection.commit() - cursor.close() - - -def insert_lead(connection, form, lead): - """ - Insère un lead Meta en base. - - Si le lead existe déjà, il n’est pas dupliqué grâce à la clé - unique meta_lead_id. Les données existantes sont conservées. - """ +def upsert_lead_data(connection, form, lead): + """Insère ou met à jour un lead Meta dans lead_data.""" fields = normalize_field_data(lead.get("field_data", [])) + campus = FORM_CAMPUSES.get(form.get("id")) - query = """ - INSERT INTO meta_leads ( + update_query = """ + UPDATE dbo.lead_data + SET + meta_form_id = ?, + created_time = ?, + je_suis = ?, + nom = ?, + first_name = ?, + adresse_email = ?, + phone_number = ?, + city = ?, + campus = ?, + updated_at = SYSDATETIME() + WHERE meta_lead_id = ? + """ + + insert_query = """ + INSERT INTO dbo.lead_data ( meta_lead_id, meta_form_id, created_time, @@ -301,75 +239,71 @@ def insert_lead(connection, form, lead): phone_number, city, campus, - raw_payload, status ) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, 'RECEIVED') - ON DUPLICATE KEY UPDATE - updated_at = CURRENT_TIMESTAMP + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, N'RECEIVED') """ - values = ( + values_for_update = ( + form.get("id"), + parse_meta_datetime(lead.get("created_time")), + fields.get("je_suis"), + fields.get("nom"), + fields.get("first_name"), + get_email_from_fields(fields), + fields.get("phone_number"), + fields.get("city"), + campus, + lead.get("id"), + ) + + values_for_insert = ( lead.get("id"), form.get("id"), parse_meta_datetime(lead.get("created_time")), fields.get("je_suis"), fields.get("nom"), fields.get("first_name"), - fields.get("adresse_e-mail"), + get_email_from_fields(fields), fields.get("phone_number"), fields.get("city"), - FORM_CAMPUSES.get(form.get("id")), - json.dumps(lead, ensure_ascii=False), + campus, ) cursor = connection.cursor() - cursor.execute(query, values) + + cursor.execute(update_query, values_for_update) + + if cursor.rowcount == 0: + cursor.execute(insert_query, values_for_insert) + connection.commit() cursor.close() -def synchronize_meta_to_mysql(): - """ - Lance la synchronisation Meta vers MySQL. - - Étapes : - 1. Récupération des formulaires actifs. - 2. Stockage des formulaires. - 3. Récupération et stockage des questions. - 4. Récupération et stockage des leads. - """ +def synchronize_meta_to_sql_server(): + """Synchronise les formulaires et les leads Meta vers SQL Server.""" connection = get_database_connection() total_forms = 0 - total_questions = 0 total_leads = 0 try: forms = fetch_active_forms() for form in forms: - form_id = form.get("id") - - questions, form_details = fetch_form_questions(form_id) - - upsert_form(connection, form, form_details) + upsert_leadgen_form(connection, form) total_forms += 1 - for question in questions: - upsert_question(connection, form_id, question) - total_questions += 1 - - leads = fetch_form_leads(form_id) + leads = fetch_form_leads(form.get("id")) for lead in leads: - insert_lead(connection, form, lead) + upsert_lead_data(connection, form, lead) total_leads += 1 print("Synchronisation terminée.") print(f"Formulaires traités : {total_forms}") - print(f"Questions traitées : {total_questions}") print(f"Leads traités : {total_leads}") finally: @@ -383,4 +317,7 @@ if __name__ == "__main__": if not META_PAGE_ID: raise RuntimeError("META_PAGE_ID est manquant dans le fichier .env.") - synchronize_meta_to_mysql() \ No newline at end of file + if not DB_PASSWORD: + raise RuntimeError("DB_PASSWORD est manquant dans le fichier .env.") + + synchronize_meta_to_sql_server() \ No newline at end of file diff --git a/meta_lead_db.sql b/meta_lead_db.sql deleted file mode 100644 index f735560..0000000 --- a/meta_lead_db.sql +++ /dev/null @@ -1,222 +0,0 @@ --- ============================================================ --- Script : meta_lead_db.sql --- Base : meta_lead_db --- --- Objectif : --- Créer une base intermédiaire permettant de stocker les --- formulaires et leads Meta Lead Gen avant leur injection --- dans OscarCRM. --- --- Remarques techniques : --- - Les IDs Meta sont stockés en VARCHAR car ils sont fournis --- sous forme de chaînes numériques. --- - Les données brutes Meta sont conservées en JSON pour garder --- une traçabilité complète. --- - Les colonnes indexées sont limitées pour rester compatibles --- avec les anciennes limites MySQL/InnoDB en utf8mb4. --- ============================================================ - -DROP DATABASE IF EXISTS meta_lead_db; - -CREATE DATABASE meta_lead_db -CHARACTER SET utf8mb4 -COLLATE utf8mb4_unicode_ci; - -USE meta_lead_db; - --- ============================================================ --- Table : meta_forms --- --- Rôle : --- Stocke les formulaires Lead Gen récupérés depuis Meta. --- Chaque formulaire peut être lié à un campus ou à une campagne. --- ============================================================ - -CREATE TABLE meta_forms ( - id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, - - meta_form_id VARCHAR(64) NOT NULL, - page_id VARCHAR(64) NOT NULL, - - name VARCHAR(255) NOT NULL, - status VARCHAR(50) NOT NULL, - locale VARCHAR(20) NULL, - - campus VARCHAR(100) NULL, - - raw_payload JSON NULL, - - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, - - CONSTRAINT uq_meta_forms_meta_form_id - UNIQUE (meta_form_id), - - INDEX idx_meta_forms_status (status), - INDEX idx_meta_forms_campus (campus) -) ENGINE=InnoDB - DEFAULT CHARSET=utf8mb4 - COLLATE=utf8mb4_unicode_ci - COMMENT='Formulaires Meta Lead Gen récupérés depuis l API Meta.'; - - --- ============================================================ --- Table : meta_form_questions --- --- Rôle : --- Stocke les questions/champs associés à chaque formulaire Meta. --- Cette table permet de connaître la structure exacte des --- formulaires utilisés pour générer les leads. --- --- Note : --- field_key est limité à 100 caractères afin d éviter les erreurs --- d index avec utf8mb4 sur certaines versions MySQL/InnoDB. --- ============================================================ - -CREATE TABLE meta_form_questions ( - id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, - - meta_form_id VARCHAR(64) NOT NULL, - - question_id VARCHAR(64) NULL, - field_key VARCHAR(100) NOT NULL, - label VARCHAR(255) NULL, - question_type VARCHAR(100) NULL, - - question_options JSON NULL, - raw_payload JSON NULL, - - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, - - CONSTRAINT uq_meta_form_questions_form_field - UNIQUE (meta_form_id, field_key), - - CONSTRAINT fk_meta_form_questions_form - FOREIGN KEY (meta_form_id) - REFERENCES meta_forms(meta_form_id) - ON UPDATE CASCADE - ON DELETE CASCADE, - - INDEX idx_meta_form_questions_form (meta_form_id) -) ENGINE=InnoDB - DEFAULT CHARSET=utf8mb4 - COLLATE=utf8mb4_unicode_ci - COMMENT='Questions et champs des formulaires Meta Lead Gen.'; - - --- ============================================================ --- Table : meta_leads --- --- Rôle : --- Stocke les leads récupérés depuis les formulaires Meta. --- Les colonnes principales reprennent les champs du formulaire --- Meta et correspondent au format attendu pour OscarCRM. --- --- Statuts : --- RECEIVED : lead récupéré depuis Meta --- READY_TO_SEND : lead prêt à être envoyé vers OscarCRM --- SENT : lead envoyé avec succès vers OscarCRM --- ERROR : erreur pendant le traitement ou l envoi --- INVALID : données obligatoires manquantes ou invalides --- ============================================================ - -CREATE TABLE meta_leads ( - id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, - - meta_lead_id VARCHAR(64) NOT NULL, - meta_form_id VARCHAR(64) NOT NULL, - - meta_ad_id VARCHAR(64) NULL, - meta_campaign_id VARCHAR(64) NULL, - - created_time DATETIME NULL, - - je_suis VARCHAR(255) NULL, - nom VARCHAR(255) NULL, - first_name VARCHAR(255) NULL, - adresse_email VARCHAR(191) NULL, - phone_number VARCHAR(50) NULL, - city VARCHAR(255) NULL, - - source VARCHAR(100) NOT NULL DEFAULT 'META_LEAD_GEN', - campus VARCHAR(100) NULL, - - raw_payload JSON NOT NULL, - oscar_payload JSON NULL, - oscar_response JSON NULL, - - status VARCHAR(50) NOT NULL DEFAULT 'RECEIVED', - - error_message TEXT NULL, - synced_at DATETIME NULL, - - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, - - CONSTRAINT uq_meta_leads_meta_lead_id - UNIQUE (meta_lead_id), - - CONSTRAINT fk_meta_leads_form - FOREIGN KEY (meta_form_id) - REFERENCES meta_forms(meta_form_id) - ON UPDATE CASCADE - ON DELETE RESTRICT, - - CONSTRAINT chk_meta_leads_status - CHECK (status IN ('RECEIVED', 'READY_TO_SEND', 'SENT', 'ERROR', 'INVALID')), - - INDEX idx_meta_leads_status (status), - INDEX idx_meta_leads_meta_form_id (meta_form_id), - INDEX idx_meta_leads_created_time (created_time), - INDEX idx_meta_leads_adresse_email (adresse_email), - INDEX idx_meta_leads_synced_at (synced_at) -) ENGINE=InnoDB - DEFAULT CHARSET=utf8mb4 - COLLATE=utf8mb4_unicode_ci - COMMENT='Leads Meta Lead Gen stockés avant injection vers OscarCRM.'; - - --- ============================================================ --- Table : oscar_sync_logs --- --- Rôle : --- Trace chaque tentative d injection d un lead vers OscarCRM. --- Cette table permet de conserver l historique des succès, --- erreurs et réponses API sans écraser les données du lead. --- --- Statuts : --- SENT : injection réussie --- ERROR : erreur lors de l injection --- INVALID : payload non valide pour OscarCRM --- ============================================================ - -CREATE TABLE oscar_sync_logs ( - id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, - - lead_id BIGINT UNSIGNED NOT NULL, - - oscar_payload JSON NOT NULL, - oscar_response JSON NULL, - - status VARCHAR(50) NOT NULL, - error_message TEXT NULL, - - attempted_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT fk_oscar_sync_logs_lead - FOREIGN KEY (lead_id) - REFERENCES meta_leads(id) - ON UPDATE CASCADE - ON DELETE CASCADE, - - CONSTRAINT chk_oscar_sync_logs_status - CHECK (status IN ('SENT', 'ERROR', 'INVALID')), - - INDEX idx_oscar_sync_logs_lead_id (lead_id), - INDEX idx_oscar_sync_logs_status (status), - INDEX idx_oscar_sync_logs_attempted_at (attempted_at) -) ENGINE=InnoDB - DEFAULT CHARSET=utf8mb4 - COLLATE=utf8mb4_unicode_ci - COMMENT='Historique des tentatives d injection des leads vers OscarCRM.'; \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index c96b2df..b34a4e2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ requests -mysql-connector-python -python-dotenv \ No newline at end of file +python-dotenv +pyodbc \ No newline at end of file