You are not logged in. Click here to log in.

codebeamer Application Lifecycle Management (ALM)

Search In Project

Search inClear

Tags:  not added yet

The requirement: Synchronizing various trackers on a Workflow transition

The Sales Template project contains the following trackers:

  • Leads - An item representing a prospective customer that is created when an individual or business shows interest and provides his or her contact information.
  • Accounts - The Account, along with contacts as related records, is like an address book. It is an entity to store a company’s name, address, phone number and other important pieces of information.
  • Opportunities - An Opportunity represents a potential sale to a new or established customer. Helps us forecast future business demands and sales revenues.
  • Concats - An individual’s personal information by which we can reach the individual to discuss their needs & what we can offer.
  • Activities - An Activity is basically a record of actions undertaken by our sales team and other stakeholders.

The goal is that when a Lead finalizes, i.e. it changes to the "SQL" workflow state then we should create appropriate items in the Accounts, Opportunities, Contants trackers.

This diagram illustrates how these trackers relate to each other

Synchronization mapping between trackers

Here is the required mapping of fields. The Workflow should copy these fields from the "Leads" tracker to the fields of the other tracker as described here.
Source Tracker fields Mapping to ...
Lead Contact Account Opportunity
Description Company Profile
First name First name
Last name Last name
Title Title
Department Department
Company Account name
Phone Phone Phone
E-mail contact E-mail
Street Street Street
Post code Zip/Postal code Post code
City City City
Country Country Country
Employees Employees
Website Company website
Lead source Lead source, Opportunity, Description
Comment Comment
Geolocation Geolocation
Industry Industry
Account should refer the new "Account" Account should refer to the new "Account"
Contacts should refer to the new "Contact"

The script code

Here is the Groovy script, which contains the logic for the Workflow-action. For that you should create a new file as $CB_HOME/CB/tomcat/webapps/cb/WEB-INF/classes/synchronizeLeads.groovy. Paste this Groovy script below to that file, and save it.


// Groovy script implements a Workflow state-transition action as requested here: https://codebeamer.com/cb/issue/326159
// registered in my-applicationContext.xml on cb.com only
import com.intland.codebeamer.persistence.dto.*;
import com.intland.codebeamer.persistence.dto.base.*;
import com.intland.codebeamer.persistence.dao.*;
import com.intland.codebeamer.manager.*;
import com.intland.codebeamer.controller.importexport.*;
import org.apache.commons.lang3.*;

if (!beforeEvent) {
    return;    // do NOTHING on after-event, everything is already handled in the before-event!
}

logger.info("-------------------------------------");
logger.info("Synchronizing Lead issue:" + subject);

trackerDao = applicationContext.getBean(TrackerDao.class);
trackerItemManager = applicationContext.getBean(TrackerItemManager.class);

projectId = subject.tracker.project.id

// read custom fields in "Leads" tracker
// using a helper class to access fields by name
fieldAccessor = new com.intland.codebeamer.text.excel.FieldAccessor(applicationContext);
fieldAccessor.setUser(user);
def getByLabel = { fieldName -> fieldAccessor.getByLabel(subject, fieldName) };

// set a field on contact by finding the field using its label
def setField(issue, fieldName, value) {
    field = fieldAccessor.getFieldByName(issue, fieldName);
    if (field != null) {
        field.setValue(issue, value);
    } else {
      logger.warn("Can not find field <" + fieldName +"> on " + issue);
    }
};

def copyField(toIssue, fieldName, toFieldName=null, defaultValue=null) {
    value = fieldAccessor.getByLabel(subject, fieldName);
    if (value == null && defaultValue != null) {
        value = defaultValue;
    }

    if (toFieldName == null) {
        toFieldName = fieldName;
    }
    setField(toIssue, toFieldName, value);
}

def updateOriginal(fieldName, value) {
    setField(subject, fieldName, value);
}

def getOrCreateChoice(issue, fieldName, value) {
    if (StringUtils.isBlank(value)) {
        return null;
    }

    choicesProvider = new ChoicesProvider(applicationContext);
    choiceField = choicesProvider.getFieldByName(user, issue, fieldName);
    if (choiceField == null) {
        return null;
    }
    asChoice = choicesProvider.getOrCreateChoiceByName(user, issue.tracker, choiceField, value, null);
    return asChoice;
}

// first check/create account if does not exist, because this is required by contact
account = getByLabel("Account");
if (account == null) {
    try {
        account = new TrackerItemDto();
        accounts = trackerDao.findByNameAndProjectId("Accounts", projectId);
        account.tracker = accounts;

        // use the trackerItemManager to copy of the source issue, because this copies comments and attachments too
        request = event.getRequest();
        fieldMapping = new HashMap();
        Map<TrackerItemDto,TrackerItemDto> copied = trackerItemManager.copy(request, user, Collections.singletonList(subject), null, account, null, fieldMapping);
        account = copied.get(subject);
        logger.info("copied account's id:" + account.id);

        // required fields
        copyField(account, "Company", "Account Name");
        copyField(account, "Description", "Company Profile");    // must fill with something, this is a required field

        copyField(account, "Street");
        copyField(account, "Post code");
        copyField(account, "City");
        copyField(account, "Country");
        copyField(account, "Employees");
        copyField(account, "Website", "Company website");
        copyField(account, "Comment");
        copyField(account, "Phone");

        // industry is a choice field in the target, creating a new choice if necessary
        industry = getByLabel("Industry");
        logger.warn("Creating industry:" + industry)
        industryAsChoice = getOrCreateChoice(account, "Industry", industry);
        logger.warn("industryAsChoice:" + industryAsChoice);
        if (industryAsChoice != null) {
            setField(account, "Industry", Arrays.asList(industryAsChoice));
        }

        logger.info("Created Account:" + account);
        // trackerItemManager.create(user, account, event.getData());
        trackerItemManager.update(user, account, event.getData());

        updateOriginal("Account", Arrays.asList(account));
    } catch (Throwable th) {
        logger.warn("Failed to create Account for " + subject, th);
    }
}

// only create a new contact if that does not exist yet!, this also avoids infinite event loops !
contact = getByLabel("contact");
if (contact == null) {
    try {
        // create a new Contact
        contact = new TrackerItemDto();
        contacts = trackerDao.findByNameAndProjectId("Contacts", projectId);
        contact.tracker = contacts;

        copyField(contact, "First Name");
        copyField(contact, "Last Name");
        copyField(contact, "Title");    // TODO: there is NO such field here
        copyField(contact, "Department");    // TODO: there is NO such field here
        copyField(contact,"Phone");
        copyField(contact,"E-mail", "Email");
        copyField(contact,"Street");
        copyField(contact,"Post code", "Zip/Postal code");
        copyField(contact,"City");
        copyField(contact,"Country");
        copyField(contact, "Geolocation"); // TODO: no such field!

        // fill the Mandatory Account field
        setField(contact, "Account", Arrays.asList(account));

        trackerItemManager.create(user, contact, event.getData());
        logger.info("Created Contact:" + contact);

        updateOriginal("Contact", Arrays.asList(contact));
    } catch (Throwable th) {
        logger.warn("Failed to create Contact for " + subject, th);
    }
}

try {
    // Account - Main Contact field should have a default value for Contact person created upon conversion
    mainContact = fieldAccessor.getByLabel(account, "Main Contact");
    if (mainContact == null || mainContact.isEmpty()) {
        setField(account, "Main Contact", Arrays.asList(contact));
        trackerItemManager.update(user, account, event.getData());
    }
} catch (Throwable th) {
    logger.warn("Failed to set Main Contanct field", th);
}

opportunity = getByLabel("opportunity");
if (opportunity == null) {
    try {
        opportunities = trackerDao.findByNameAndProjectId("Opportunities", projectId);

        // create a new opportunity
        opportunity = new TrackerItemDto();
        opportunity.tracker = opportunities;

        defaultVal = getByLabel("Account");    // use this as default value if the "opportunity" field would be empty, because this is a required field
        opportunity.name = account.name; // REQUIRED field
        opportunity.description = "--";    // REQUIRED field
//        copyField(opportunity, "Lead Source", "Opportunity", defaultVal);  // REQUIRED field
//        copyField(opportunity, "Lead Source", "Description", defaultVal);  // REQUIRED field
        copyField(opportunity, "Lead Source");
        setField(opportunity, "Account", Arrays.asList(account));

        // store the "Account" to the "Contacts" table
        opportunityTables = new com.intland.codebeamer.manager.trackeritems.TableFields(user, opportunity, applicationContext);
        contactsTable = opportunityTables.getTableByName("Contacts");
        contactColumn = contactsTable.getTableColumnByName("Contact");
        contactColumn.setReferenceValues(0, Arrays.asList(contact));

        trackerItemManager.create(user, opportunity, event.getData());
        logger.info("Created Opportunity:" + opportunity);

        updateOriginal("opportunity", Arrays.asList(opportunity));
    } catch (Throwable th) {
        logger.warn("Failed to create Opportunity for " + subject, th);
    }
}

logger.info("-------------------------------------");