Ne yapar
Tetiklenen kaydı master kabul eder. Phone ve Mobile alanlarının kayıtlı değeriyle telefon araması, küçük harfe çevrilmiş Email ile e-posta araması yapar. Dönen kayıtların numaralarını rakama indirip (uzunluk tutarsa 0090 / 90 / 0 önekini atar) master'ınkilerle, e-postalarını küçük harfle karşılaştırır ve yalnız tam eşleşenleri mükerrer sayar. Zoho'nun telefon araması ülke kodu ve boşluk farkını tolere eder: canlı testte 0544 123 45 67 ile yapılan arama +905441234567 olarak kaydedilmiş kişiyi de buldu. Başka biçimleri kendi verinizle deneyin. Her mükerreri Merge API ile master'a katar: aktarım listesindeki 23 standart alandan master'da boş, child'da dolu olanlar child'dan doldurulur. Özel alanlar ve listede olmayan diğer alanlar child'dan alınmaz; bu alanlarda master'daki değer kalır. SCHEDULED dönerse döngü durur; kalanlar kutu yeniden işaretlenince birleşir.
Kurulum mantığı
- 01
Setup içinde Developer Hub, Connections altında Zoho CRM bağlantısı. Kodun kullandığı ad bağlantının Link Name değeridir ve
crm_oauth_v8olmalı (koddakiconnection:ile birebir); oluşturduktan sonra açılan sayfada kontrol edin. ScopeZohoCRM.modules.ALLve Zoho'nun arama API'sinin istediğiZohoSearch.securesearch.READ. Bağlantı bu adla kurulmadan Zoho kodu kaydetmez, derleme hatası verir. - 02
Otomatik için Workflow On Create: yeni kayıt master olur. On Create kurulsa da 3. adımdaki onay kutusu kuralını da kurun; 200 sınırında ve SCHEDULED durumunda kalanlar yalnız onunla birleşir.
- 03
Elle çalıştırmak için Workflow On Edit, onay kutusuyla: modüle "Mükerrerleri Birleştir" adlı bir onay kutusu alanı ekleyin. Kural On Edit, "Repeat this workflow whenever a Contact is edited" açık, koşul "Mükerrerleri Birleştir = seçili"; eylemler Function ve kutuyu yeniden boşaltan bir Field Update. Temsilci kutuyu işaretleyip kaydettiğinde o kayıt master olur. Custom Button bu
void automationimzasıyla bağlanamaz. - 04
Hangi kuralı kurarsanız kurun argüman
contactId, Contact Id alanına eşlenir. - 05
Veri merkezi:
zohoapis.eu; ABD içinzohoapis.com.
Alan eşlemesi
Koda yazılan alanlar ve kendi org'unuzda dikkat etmeniz gerekenler.
| Alan | Değer | Not |
|---|---|---|
| Phone, Mobile | okunur | Aramada kayıtlı değer olduğu gibi kullanılır; karşılaştırma için rakam dışı atılır, uzunluk tutarsa 0090/90/0 öneki kırpılır. |
| okunur | Küçük harf tam eşleşme; yalnız master'ın Email değeri aranır ve bulunan kaydın Email alanıyla karşılaştırılır, Secondary_Email eşleşmesi mükerrer sayılmaz. | |
| _fields | 23 standart alan | Hitap, ad, soyad, Email ve Secondary_Email, 4 telefon alanı (Phone, Mobile, Home_Phone, Other_Phone) ve Fax, unvan, bölüm, Mailing_* ve Other_* adresleri, Description. Master'da boş olanlar child'dan alınır. |
| Merge API | POST /crm/v8/Contacts/{master}/actions/merge | İstek başına tek child. |
Deluge kodu
Kodu olduğu gibi kopyalayın; değiştirmeniz gereken yerler yukarıdaki tabloda işaretli. İzle düğmesi, fonksiyonu adım adım okutur.
void automation.mergeContactsByPhoneMobileEmail(String contactId)
{
try
{
info "===== Contact Mükerrer Birleştirme Başladı =====";
info "Master Contact ID: " + contactId;
if(contactId == null || contactId.trim() == "")
{
info "Contact ID boş.";
return;
}
contactId = contactId.trim();
masterContact = zoho.crm.getRecordById("Contacts",contactId.toLong());
if(masterContact == null || masterContact.isEmpty())
{
info "Master Contact bulunamadı.";
return;
}
searchPhones = List();
masterPhoneKeys = List();
masterEmail = "";
if(masterContact.get("Phone") != null && masterContact.get("Phone").toString().trim() != "")
{
searchPhones.add(masterContact.get("Phone").toString().trim());
}
if(masterContact.get("Mobile") != null && masterContact.get("Mobile").toString().trim() != "")
{
mobileValue = masterContact.get("Mobile").toString().trim();
if(!searchPhones.contains(mobileValue))
{
searchPhones.add(mobileValue);
}
}
if(masterContact.get("Email") != null && masterContact.get("Email").toString().trim() != "")
{
masterEmail = masterContact.get("Email").toString().trim().toLowerCase();
}
if(searchPhones.size() == 0 && masterEmail == "")
{
info "Phone, Mobile ve Email alanları boş.";
return;
}
for each searchPhone in searchPhones
{
phoneKey = searchPhone.replaceAll("[^0-9]","");
if(phoneKey.startsWith("0090") && phoneKey.length() == 14)
{
phoneKey = phoneKey.subString(4,phoneKey.length());
}
else if(phoneKey.startsWith("90") && phoneKey.length() == 12)
{
phoneKey = phoneKey.subString(2,phoneKey.length());
}
else if(phoneKey.startsWith("0") && phoneKey.length() == 11)
{
phoneKey = phoneKey.subString(1,phoneKey.length());
}
if(phoneKey != "" && !masterPhoneKeys.contains(phoneKey))
{
masterPhoneKeys.add(phoneKey);
}
}
duplicateIds = List();
for each searchPhone in searchPhones
{
try
{
searchParams = Map();
searchParams.put("phone",searchPhone);
searchParams.put("page",1);
searchParams.put("per_page",200);
searchResponse = invokeurl
[
url :"https://www.zohoapis.eu/crm/v8/Contacts/search"
type :GET
parameters:searchParams
connection:"crm_oauth_v8"
];
if(searchResponse != null && searchResponse.get("data") != null)
{
for each foundContact in searchResponse.get("data")
{
foundId = ifnull(foundContact.get("id"),"").toString();
foundPhoneKey = ifnull(foundContact.get("Phone"),"").toString().replaceAll("[^0-9]","");
foundMobileKey = ifnull(foundContact.get("Mobile"),"").toString().replaceAll("[^0-9]","");
if(foundPhoneKey.startsWith("0090") && foundPhoneKey.length() == 14)
{
foundPhoneKey = foundPhoneKey.subString(4,foundPhoneKey.length());
}
else if(foundPhoneKey.startsWith("90") && foundPhoneKey.length() == 12)
{
foundPhoneKey = foundPhoneKey.subString(2,foundPhoneKey.length());
}
else if(foundPhoneKey.startsWith("0") && foundPhoneKey.length() == 11)
{
foundPhoneKey = foundPhoneKey.subString(1,foundPhoneKey.length());
}
if(foundMobileKey.startsWith("0090") && foundMobileKey.length() == 14)
{
foundMobileKey = foundMobileKey.subString(4,foundMobileKey.length());
}
else if(foundMobileKey.startsWith("90") && foundMobileKey.length() == 12)
{
foundMobileKey = foundMobileKey.subString(2,foundMobileKey.length());
}
else if(foundMobileKey.startsWith("0") && foundMobileKey.length() == 11)
{
foundMobileKey = foundMobileKey.subString(1,foundMobileKey.length());
}
exactMatch = false;
if(foundPhoneKey != "" && masterPhoneKeys.contains(foundPhoneKey))
{
exactMatch = true;
}
if(foundMobileKey != "" && masterPhoneKeys.contains(foundMobileKey))
{
exactMatch = true;
}
if(foundId != "" && foundId != contactId && exactMatch && !duplicateIds.contains(foundId))
{
duplicateIds.add(foundId);
}
}
}
}
catch (searchError)
{
info "Telefon arama hatası: " + searchError.toString();
}
}
if(masterEmail != "")
{
try
{
emailParams = Map();
emailParams.put("email",masterEmail);
emailParams.put("page",1);
emailParams.put("per_page",200);
emailResponse = invokeurl
[
url :"https://www.zohoapis.eu/crm/v8/Contacts/search"
type :GET
parameters:emailParams
connection:"crm_oauth_v8"
];
if(emailResponse != null && emailResponse.get("data") != null)
{
for each foundContact in emailResponse.get("data")
{
foundId = ifnull(foundContact.get("id"),"").toString();
foundEmail = ifnull(foundContact.get("Email"),"").toString().trim().toLowerCase();
if(foundId != "" && foundId != contactId && foundEmail == masterEmail && !duplicateIds.contains(foundId))
{
duplicateIds.add(foundId);
}
}
}
}
catch (emailSearchError)
{
info "Email arama hatası: " + emailSearchError.toString();
}
}
if(duplicateIds.size() == 0)
{
info "Birleştirilecek mükerrer Contact bulunamadı.";
return;
}
info "Mükerrer Contact ID'leri: " + duplicateIds;
transferFields = List();
transferFields.add("Salutation");
transferFields.add("First_Name");
transferFields.add("Last_Name");
transferFields.add("Email");
transferFields.add("Secondary_Email");
transferFields.add("Phone");
transferFields.add("Mobile");
transferFields.add("Home_Phone");
transferFields.add("Other_Phone");
transferFields.add("Fax");
transferFields.add("Title");
transferFields.add("Department");
transferFields.add("Mailing_Street");
transferFields.add("Mailing_City");
transferFields.add("Mailing_State");
transferFields.add("Mailing_Zip");
transferFields.add("Mailing_Country");
transferFields.add("Other_Street");
transferFields.add("Other_City");
transferFields.add("Other_State");
transferFields.add("Other_Zip");
transferFields.add("Other_Country");
transferFields.add("Description");
requestHeaders = Map();
requestHeaders.put("Content-Type","application/json");
mergedCount = 0;
mergeCanContinue = true;
for each childId in duplicateIds
{
if(mergeCanContinue)
{
currentMaster = zoho.crm.getRecordById("Contacts",contactId.toLong());
childContact = zoho.crm.getRecordById("Contacts",childId.toLong());
if(currentMaster == null || currentMaster.isEmpty())
{
info "Master Contact bulunamadığı için işlem durduruldu.";
mergeCanContinue = false;
}
else if(childContact == null || childContact.isEmpty())
{
info "Child Contact bulunamadı: " + childId;
}
else
{
fieldList = List();
for each fieldApiName in transferFields
{
masterValue = currentMaster.get(fieldApiName);
childValue = childContact.get(fieldApiName);
if((masterValue == null || masterValue.toString().trim() == "") && childValue != null && childValue.toString().trim() != "")
{
fieldMap = Map();
fieldMap.put("api_name",fieldApiName);
fieldList.add(fieldMap);
}
}
if(fieldList.size() == 0)
{
for each fieldApiName in transferFields
{
if(fieldList.size() == 0)
{
masterValue = currentMaster.get(fieldApiName);
childValue = childContact.get(fieldApiName);
if(masterValue != null && childValue != null && masterValue.toString().trim() != "" && masterValue.toString().trim().toLowerCase() == childValue.toString().trim().toLowerCase())
{
fieldMap = Map();
fieldMap.put("api_name",fieldApiName);
fieldList.add(fieldMap);
}
}
}
}
if(fieldList.size() == 0)
{
info "Güvenli aktarım alanı bulunamadı; merge atlandı: " + childId;
}
else
{
childMap = Map();
childMap.put("id",childId);
childMap.put("_fields",fieldList);
dataList = List();
dataList.add(childMap);
mergeObject = Map();
mergeObject.put("data",dataList);
mergeList = List();
mergeList.add(mergeObject);
payload = Map();
payload.put("merge",mergeList);
try
{
mergeResponse = invokeurl
[
url :"https://www.zohoapis.eu/crm/v8/Contacts/" + contactId + "/actions/merge"
type :POST
body:payload.toString()
headers:requestHeaders
connection:"crm_oauth_v8"
];
info "Merge response: " + mergeResponse;
if(mergeResponse != null && mergeResponse.get("merge") != null && mergeResponse.get("merge").size() > 0)
{
mergeResult = mergeResponse.get("merge").get(0);
mergeCode = ifnull(mergeResult.get("code"),"").toString();
if(mergeCode == "SUCCESS")
{
mergedCount = mergedCount + 1;
}
else if(mergeCode == "SCHEDULED")
{
info "Merge zamanlandı. Kalan kayıtlar sonraki çalışmaya bırakıldı.";
mergeCanContinue = false;
}
}
}
catch (mergeError)
{
info "Merge hatası | Child ID: " + childId + " | " + mergeError.toString();
}
}
}
}
}
info "Başarılı merge sayısı: " + mergedCount;
}
catch (e)
{
info "Genel hata: " + e.toString();
}
}Dikkat edilecekler
- Master = tetiklenen kayıt
On Create'te en yeni kişi kalır, eskiler ona katılır. Kişiye bağlı fırsatlar, faturalar ve aktiviteler master'a taşınır; kayıt tarihi ve sahip yeni kaydınki olur. Eski kaydın kalması gerekiyorsa On Create kuralını kurmayın; yalnız onay kutusu kuralını kurup kutuyu eski kayıtta işaretleyin.
- Birleştirme geri alınamaz
Child kayıt silinir ve Zoho belgelerinde birleştirmeyi geri alan bir yol tanımlanmaz; önce sandbox ya da onay kutusuyla elle.
- Ortak e-posta tuzağı
info@firma.comgibi ortak adresle açılmış farklı kişiler e-posta eşleşmesiyle birleşir. Böyle veri varsa e-posta aramasını (155-190) kapatın ya da tetik koşuluyla eleyin. - `phone` araması geniş tarar
Fax ve Home_Phone dahil tüm telefon alanlarında eşleşir; kod yalnız Phone/Mobile tam eşleşmesini kabul eder.
- 200 kayıt sınırı
Her arama tek sayfa okur, en fazla 200 sonuç döner ve sayfalama yoktur. Bu 200 sonuca tam eşleşmeyen kayıtlar da girebilir. Fazlası kendiliğinden işlenmez; On Create aynı kayıt için bir daha tetiklenmez. Kalanlar, aynı master'da onay kutusu yeniden işaretlenince yakalanır. Aynı telefon ya da e-postayla yeni bir kişi gelirse de yakalanır, ama bu kez master yeni kişi olur.
- Kutu boşaltılmalı
Kural yalnız kutu işaretliyken çalışır, sıradan düzenleme birleştirme yapmaz. Kutuyu boşaltan Field Update eylemi unutulursa kutu işaretli kaldıkça her düzenleme arama ve birleştirmeyi yeniden çalıştırır.
- `_fields` boş gönderilmez
Merge API en az bir alan bekler; uygun alan yoksa o child atlanır ve loglanır.
- SCHEDULED
Birleştirilecek child kaydın ilişkili listelerinden birinde 1000'den fazla kayıt varsa Zoho o birleştirmeyi arka plana alır,
SCHEDULEDdöner ve zamanlanan birleştirmeyi kendisi tamamlar. Kod o anda döngüyü durdurur, kalan mükerrerlere dokunmaz ve yeniden denemez. On Create aynı kayıt için bir daha tetiklenmez. Arka plan işi bittikten sonra master kayıtta onay kutusunu yeniden işaretleyin; iş numarası (job_id) log'daki "Merge response" satırında görünür.
İlgili kayıtlar
- 09Contacts · Tasks
Kişinin tüm açık görevlerini kapat
Müşteri ilişkisi bittiğinde ya da süreç tamamlandığında kişi kaydındaki tüm açık görevleri tek seferde kapatır; Lead sürümünün Contacts modülü karşılığı.
- 10Contacts
Kişi telefonlarını +90 biçimine getir
Kişi kaydındaki Telefon ve Mobil alanlarında 05…, 5…, 90… ya da 0090… ile girilmiş numaraları +90… biçimine çevirir; WhatsApp, SMS ve santral entegrasyonları bu numaraları tek biçimde alır.
- 13Contacts · Accounts
Firma fatura adresini kişiye aktar
Kişi kaydı bir firmaya bağlı açıldığında firmanın fatura adresini kişinin posta adresi (Mailing) alanlarına kopyalar; kişi kartında adres boş kalmaz, toplu posta ve fatura yazdırmada eksik adres çıkmaz.