Module 5 — Deluge

Le langage de script Zoho : custom functions, intégrations API, automation cross-applis. Le vrai super-pouvoir de Zoho.

🐉 Qu'est-ce que Deluge ?

Deluge = Data Enriched Language for the Universal Grid Environment. Langage de script propriétaire Zoho, conçu pour étendre les applis sans framework lourd. Comparable à AppScript chez Google ou Apex chez Salesforce.

Où s'exécute Deluge ?

ContexteTriggerUsage
Custom FunctionsWorkflow Rule, Blueprint, ScheduleLe cas le plus fréquent — logique async
ButtonsClic utilisateur dans une page recordAction ponctuelle déclenchée par un user
Validation RulesSauvegarde d'un recordVérifications complexes pré-sauvegarde
SchedulesCron (toutes les heures, daily, weekly)Batch jobs (sync nocturne, reports auto)
Connections / WebhooksTrigger via HTTP externeInbound webhook depuis Stripe, GitHub, n8n
Zoho CreatorFormulaires custom appsLogique métier dans des apps no-code
💡
Limites de durée

Une custom function Deluge a un timeout de 5 min. Au-delà, l'exécution est avortée. Pour les jobs longs, splittez en chunks avec Schedule Recursive ou délégez à un n8n externe via invokeurl.

📝 Syntaxe Deluge — l'essentiel

Variables et types

// Typage dynamique, déclaration implicite
nom = "François";           // string
age = 42;                    // int
salary = 250.50;             // float (decimal)
isActive = true;             // boolean
today = zoho.currenttime;    // datetime

// Collections
fruits = {"pomme", "poire", "kiwi"};       // list
user = Map();                              // map (dict)
user.put("nom", "Dupont");
user.put("email", "j@example.com");

// Accès map
emailUser = user.get("email");

// Iteration list
for each fruit in fruits
{
    info "Fruit : " + fruit;
}

// Iteration map
for each key in user.keys()
{
    info key + " = " + user.get(key);
}

Conditions

if(age >= 18)
{
    statut = "majeur";
}
else if(age >= 13)
{
    statut = "ado";
}
else
{
    statut = "enfant";
}

// Ternaire-like (via if expression)
status = if(amount > 1000, "high-value", "regular");

Strings et manipulations

// Concaténation
fullName = firstName + " " + lastName;

// Méthodes string
upper = "hello".toUpperCase();              // "HELLO"
parts = "a,b,c".toList(",");                // {"a", "b", "c"}
joined = parts.toString(" - ");             // "a - b - c"
trimmed = "  spaces  ".trim();              // "spaces"
substr = "Hello World".subString(0, 5);     // "Hello"
hasZoho = "I love Zoho".contains("Zoho");   // true
replaced = "old".replaceAll("old", "new");  // "new"

// Numbers et dates
rounded = round(3.14159, 2);                // 3.14
formatted = today.toString("yyyy-MM-dd");   // "2026-05-20"
tomorrow = today.addDay(1);
diffDays = (date2 - date1) / 86400000;

🔌 APIs Zoho intégrées — zoho.crm.* / zoho.books.* / zoho.invoice.*

Deluge expose des wrappers natifs autour des APIs Zoho. Pas besoin de gérer OAuth, datacenter, etc. — tout est implicite quand vous appelez depuis votre tenant.

Zoho CRM — opérations courantes

// Lire un record par ID
lead = zoho.crm.getRecordById("Leads", "554023000001235001");
info lead.get("Email");

// Chercher des records (criteria string)
deals = zoho.crm.searchRecords("Deals", "(Stage:equals:Negotiation)");
info "Found " + deals.size() + " deals";

// Créer un record
newLead = Map();
newLead.put("Last_Name", "Martin");
newLead.put("First_Name", "Sophie");
newLead.put("Email", "sophie@example.com");
newLead.put("Company", "Example Corp");
response = zoho.crm.createRecord("Leads", newLead);
newId = response.get("details").get("id");

// Mettre à jour
updateMap = Map();
updateMap.put("Lead_Status", "Qualified");
zoho.crm.updateRecord("Leads", "554023000001235001", updateMap);

// Supprimer
zoho.crm.deleteRecord("Leads", "554023000001235001");

// Convertir Lead → Deal+Contact+Account
convertMap = Map();
convertMap.put("Deals", {"Deal_Name": "Sophie - Software", "Amount": 5000});
zoho.crm.convertLead("554023000001235001", convertMap);

Zoho Books

// Créer une facture
invoice = Map();
invoice.put("customer_id", "1234567000000123456");
invoice.put("line_items", {
  Map().put("name", "Consulting Day").put("rate", 800).put("quantity", 3)
});
response = zoho.books.createRecord("Invoices", organizationId, invoice);

// Récupérer un client
customer = zoho.books.getRecordsByID("Contacts", organizationId, "1234567000000123456");

// Lister les factures impayées
invoices = zoho.books.getRecords("Invoices", organizationId, {"status": "unpaid"});

Zoho Mail

// Envoyer un email
sendmail
[
    from : "marketing@ezway-technology.com"
    to : "client@example.com"
    cc : "manager@ezway-technology.com"
    subject : "Votre devis #" + dealName
    message : "<p>Bonjour " + firstName + ",</p><p>Voici votre devis...</p>"
];

🌐 invokeurl — appels HTTP externes

Pour intégrer avec n'importe quel service externe (n8n, Stripe, Mailjet, votre backend custom), utilisez invokeurl.

GET simple

response = invokeurl
[
    url: "https://api.exchangerate.host/latest?base=EUR&symbols=USD,MAD"
    type: GET
];

usdRate = response.get("rates").get("USD");
info "EUR/USD = " + usdRate;

POST avec body JSON

payload = Map();
payload.put("event", "deal_closed_won");
payload.put("deal_id", dealId);
payload.put("amount", dealAmount);
payload.put("contact_email", contactEmail);

response = invokeurl
[
    url: "https://n8n.ezway.technology/webhook/zoho-deal-closed"
    type: POST
    parameters: payload.toString()
    headers: { "Content-Type": "application/json", "X-Source": "zoho-deluge" }
];

if(response.get("status") == "ok")
{
    info "n8n a bien reçu l'event";
}

POST avec authentification Bearer

// Token stocké dans Connection (recommandé), ici en dur pour l'exemple
apiToken = "Bearer " + zoho.encryption.aesDecode("vault_key", encryptedToken);

response = invokeurl
[
    url: "https://api.stripe.com/v1/customers"
    type: POST
    parameters: { "email": clientEmail, "name": clientName }
    headers: { "Authorization": apiToken }
];
JAMAIS de credentials hardcodés

N'écrivez JAMAIS "Bearer sk_live_xxx" en dur dans le code Deluge. Tout dev avec accès au code source peut voir et exfiltrer. Utilisez les Connections (cf. section suivante) pour stocker tokens et secrets de manière sécurisée.

🔑 Connections — gérer les secrets proprement

Les Connections Zoho sont un coffre intégré pour stocker les credentials des APIs externes. Setup : Setup → Developer Space → Connections.

Créer une Connection pour Stripe

  1. Nom : stripe_prod
  2. Auth Type : OAuth 2.0 ou API Key
  3. Champ API Key : votre sk_live_xxx (chiffré côté Zoho)
  4. Save

Utiliser la Connection dans Deluge

response = invokeurl
[
    url: "https://api.stripe.com/v1/customers"
    type: POST
    parameters: { "email": "j@example.com" }
    connection: "stripe_prod"
];
// La clé API est injectée par Zoho dans le header Authorization automatiquement

Avantages :

🔧 Exemple complet — sync CRM → Books au "Closed Won"

Use case classique : quand un Deal CRM passe en Closed Won, créer automatiquement un client + facture brouillon dans Books.

// Custom function attachée à un Workflow Rule
// Trigger : Deal → on edit → Stage = Closed Won
// Function name : sync_won_deal_to_books
// Argument : dealId (CRM ID du Deal)

void sync_won_deal_to_books(String dealId)
{
    // 1. Récupérer le Deal CRM et son contact
    deal = zoho.crm.getRecordById("Deals", dealId);
    if(deal == null || deal.get("Stage") != "Closed Won")
    {
        info "Skipping deal " + dealId + " - not Closed Won";
        return;
    }

    accountId = deal.get("Account_Name").get("id");
    account = zoho.crm.getRecordById("Accounts", accountId);
    contactId = deal.get("Contact_Name").get("id");
    contact = zoho.crm.getRecordById("Contacts", contactId);

    booksOrgId = "60023456789";  // À mettre en variable d'environnement

    // 2. Chercher si client existe déjà dans Books
    existingResp = zoho.books.getRecords(
        "Contacts",
        booksOrgId,
        { "email": contact.get("Email") }
    );
    contacts = existingResp.get("contacts");

    if(contacts.size() > 0)
    {
        booksContactId = contacts.get(0).get("contact_id");
        info "Client Books existant : " + booksContactId;
    }
    else
    {
        // Créer le contact dans Books
        newContact = Map();
        newContact.put("contact_name", account.get("Account_Name"));
        newContact.put("email", contact.get("Email"));
        newContact.put("phone", contact.get("Phone"));
        newContact.put("billing_address", Map()
            .put("address", account.get("Billing_Street"))
            .put("city", account.get("Billing_City"))
            .put("zip", account.get("Billing_Code"))
            .put("country", account.get("Billing_Country"))
        );
        createResp = zoho.books.createRecord("Contacts", booksOrgId, newContact);
        booksContactId = createResp.get("contact").get("contact_id");
        info "Client Books créé : " + booksContactId;
    }

    // 3. Créer la facture brouillon
    invoice = Map();
    invoice.put("customer_id", booksContactId);
    invoice.put("invoice_number", "INV-" + deal.get("Deal_Name"));
    invoice.put("date", zoho.currentdate.toString("yyyy-MM-dd"));
    invoice.put("line_items", { Map()
        .put("name", deal.get("Deal_Name"))
        .put("description", "Vente issue du Deal CRM " + dealId)
        .put("rate", deal.get("Amount"))
        .put("quantity", 1)
        .put("tax_id", "FR_VAT_20_TAX_ID")  // ID de votre taux 20%
    });
    invoice.put("status", "draft");

    invoiceResp = zoho.books.createRecord("Invoices", booksOrgId, invoice);

    if(invoiceResp.get("code") == 0)
    {
        invoiceId = invoiceResp.get("invoice").get("invoice_id");
        info "Facture créée : " + invoiceId;

        // 4. Mettre à jour le Deal CRM avec l'ID Books pour traçabilité
        updateMap = Map();
        updateMap.put("Books_Invoice_ID", invoiceId);
        zoho.crm.updateRecord("Deals", dealId, updateMap);
    }
    else
    {
        // Erreur : log + alert
        errorMsg = invoiceResp.get("message");
        info "ERREUR création facture : " + errorMsg;
        sendmail
        [
            from : "alerts@ezway-technology.com"
            to : "ops@ezway-technology.com"
            subject : "[ALERTE] Sync CRM→Books échouée pour Deal " + dealId
            message : errorMsg
        ];
    }
}
Idempotence et reprise sur erreur

Toujours vérifier si l'opération a déjà été faite (champ Books_Invoice_ID sur le Deal) avant de créer un doublon. En cas d'échec, loguer + notifier — ne JAMAIS silencer une erreur dans une custom function de prod.

🐛 Debug et logs

Les bons outils

Accéder aux logs

Setup → Functions → [votre function] → Execution History — voir les logs de chaque run avec inputs/outputs/erreurs.

Tester localement (sandbox)

Sur la page d'édition d'une function, bouton Edit Arguments + Save and Execute → exécution en sandbox avec arguments mockés. Les info apparaissent en bas.

⚠️
Webhooks Deluge — logs séparés

Si vous déclenchez Deluge via webhook entrant, regardez aussi Setup → Webhooks → Logs — les erreurs HTTP (auth, payload mal formé) y apparaissent AVANT que la function ne s'exécute.

🎯 Bonnes pratiques production

  1. Toujours stocker les secrets dans Connections, pas dans le code source.
  2. Gérer les erreurs explicitement : vérifier le status ou code de chaque réponse API, logger + alerter en cas d'échec.
  3. Idempotence : marquer le record source avec l'ID cible pour éviter les doublons en cas de re-trigger.
  4. Limiter les boucles : timeout 5 min, donc pas de loop sur 10 000 records. Splittez en batches via Schedule.
  5. Logger sobrement : pas de info dans chaque itération si vous traitez 1000 records — ça pollue les logs.
  6. Versioning manuel : Zoho Deluge n'a pas de git natif. Copiez vos functions importantes dans un repo git externe en backup (manuellement ou via API export).
  7. Tester en sandbox tenant avant de déployer en prod, surtout pour les opérations destructives (delete, updates en masse).

🔗 Ressources officielles

📋 Quiz de validation

← Module 4 — Desk/Mail/Admin Exercices pratiques →