Friday, July 20, 2018

Options and settings when building relationship fields in Salesforce


Lookup relationship options

When you create a lookup field on an object, you can choose whether the lookup field is required or optional. If the lookup field is optional, you can specify one of three behaviors to occur if the lookup record is deleted:
  1. Clear the value of this field: This is a default option and is a good choice when the field does not have to contain a value from the associated lookup record.

  2. Don’t allow deletion of the lookup record that’s part of a lookup relationship: This option prevents the lookup record from being deleted and is a good choice for restricting deletions if you have dependencies, such as workflow rules, based on the lookup relationship.

  3. Delete this record also: This option works similar to the master-detail relationship and deletes the record whenever the lookup record is deleted. Choosing this option can result in a cascade-delete. A cascade-delete bypasses security and sharing settings, which means users can delete records when the target lookup record is deleted even if they don’t have access to the records. To prevent records from being accidentally deleted, cascade-delete is disabled by default. Contact Salesforce to get the cascade-delete option enabled for your organization.

    This option is only available within custom objects and is not available for standard objects. However, the lookup field object can be either a standard or custom object.

Master-detail relationship options

  1. Allow reparenting option: By default, records in master-detail relationships cannot be reparented. However, you can allow child records in a master-detail relationship to be reparented to a different parent by selecting Allow reparenting option in the master-detail relationship definition.

Other links for references:
Share This:    Facebook Twitter

Wednesday, July 18, 2018

Salesforce Enterprise Territory Management: running assignment rules via API


Enterprise Territory Management is an account-sharing model that grants access to accounts based on the attributes of the accounts. A territory is a collection of the account records and business users where the users have minimum read access to the accounts, irrespective of who owns the account records. By enabling territory settings, users can get read, read/write, or owner-like access to the accounts in that territory. For comprehensive instructions for implementing this feature, see the Enterprise Territory Management Implementation Guide.

Salesforce allows you to add accounts manually to territories or define account assignment rules that assign accounts to territories for you. If you want the rule to run automatically when a user creates or updates an account, then you need to understand how the dynamic assignment will work when an account record is updated via UI or API.

1. If the account is updated via the UI, "Evaluate this account against territory rules on save" checkbox needs to be selected. You can refer the article below to add this checkbox to the page layout:
https://help.salesforce.com/articleView?id=000004725&type=1

2. If the account is updated via API, AssignmentRuleHeader must be specified in the update() call of an Account for the territory assignment rules to be applied as per the SOAP API Developer Guide.

Make sure that the Active checkbox is selected on the rule edit page, or else rules won't run automatically.

To test the automatic assignment of territories using the 2nd approach, I created a territory assignment rule DEMO_TERRITORY. I configured this rule in such a way that it will assign an account record to a territory with the same name, i.e. DEMO_TERRITORY, in my territory hierarchy.

I also created a custom field "Account Territory" on Account object; when this field is populated with the name of the territory, a trigger is invoked which will then call a future method.

trigger AccountAfterUpdate on Account (after update) { List<Id> accountIds = new List<Id>(); for (Account acc : Trigger.new) { if (acc.Run_Territory__c) { accountIds.add(acc.Id); } } if (accountIds.size() > 0) { Territory2Controller.runTerritoryRulesFuture(accountIds, UserInfo.getSessionId()); } }
This future method contains the main logic to run the assignment rules. It will create a soap envelope, populate the sessionId and relevant account Ids for which assignment rules need to run. Finally using HTTP Request and Response objects, I invoked this SOAP web service.

All territory assignment rules are applied for the account(s) if for the element name useDefaultRule, value is true. If the value is false, then no territory assignment rules are applied.

This is the source code of the future method.

I also updated Run_Territory__c to false in the webservice call to prevent recursive future calls as I was getting System.CalloutException: Callout loop not allowed exception.

Please remember that the maximum records in an update request can be 200. You can't just dump in thousands of records in an API call.
Share This:    Facebook Twitter

Tuesday, June 26, 2018

Asynchronous Apex: Using Queueable Apex


Queueable jobs are similar to future methods, but they provide you with these additional benefits.
  • Getting an ID for your job: When you submit your job by invoking the System.enqueueJob method, the method returns the ID of the new job. This ID corresponds to the ID of the AsyncApexJob record. You can use this ID to identify your job and monitor its progress.
  • Using non-primitive types: Your queueable class can contain member variables of non-primitive data types, such as sObjects or custom Apex types.
  • Chaining jobs: You can chain one job to another job by starting a second job from a running job. Chaining jobs is useful if you need to do some processing that depends on another process to have run first.
public class AsyncExecutionExample implements Queueable {
    public void execute(QueueableContext context) {
        Account a = new Account(Name='Acme',Phone='(415) 555-1212');
        insert a;        
    }
}

To add this class as a job on the queue, call this method:
ID jobID = System.enqueueJob(new AsyncExecutionExample());

Chaining Jobs

To chain a job to another job, submit the second job from the execute() method of your queueable class. You can add only one job from an executing job, which means that only one child job can exist for each parent job. You can refer the code below for chaining jobs:

...
...
...
for (Case caseRecord : cases)
 caseRecord.Status = 'Closed';

for (Lead lead : leads)
 lead.Status = 'Inactive';

Map<Integer, List<sObject>> indexToObjects = new Map<Integer, List<SObject>>();
indexToObjects.put(0, cases);
indexToObjects.put(1, leads);

Integer startingIndex = 0;
System.enqueueJob(new Worker(indexToObjects, startingIndex));

Worker.cls
public without sharing class Worker implements Queueable {
 Map<Integer, List<sObject>> indexToObjectUpdates;
 Integer index;

 public Worker(Map<Integer, List<sObject>> indexToObjectUpdates, Integer index) {
  this.indexToObjectUpdates = indexToObjectUpdates;
  this.index = index;
 }

 public void execute(QueueableContext context) {
  if (!indexToObjectUpdates.containsKey(index))
   return;

  update indexToObjectUpdates.get(index);

  Integer nextIndex = index + 1;
  if (indexToObjectUpdates.containsKey(nextIndex))
   System.enqueueJob(new Worker(indexToObjectUpdates, nextIndex));
 }
}

Points to remember:
  • Apex allows HTTP and web service callouts from queueable and chained queueable jobs, if they implement the Database.AllowsCallouts marker interface.
  • You can monitor the status of your job programmatically by querying AsyncApexJob or through the user interface in Setup by entering Apex Jobs in the Quick Find box, then selecting Apex Jobs.
Share This:    Facebook Twitter

Sunday, May 13, 2018

A simple Node.js web application


const express = require("express");
const app = express();

const Joi = require("joi");

app.use(express.json());

const genres = [
  { id: 1, name: "Action" },
  { id: 2, name: "Horror" },
  { id: 3, name: "Romance" }
];

app.get("/api/genres", (req, res) => {
  res.send(genres);
});

app.get("/api/genres/:id", (req, res) => {
  const genre = genres.find(c => c.id === parseInt(req.params.id));
  if (!genre)
    return res.status(404).send("The genre with the given ID was not found.");
  res.send(genre);
});

app.post("/api/genres", (req, res) => {
  const { error } = validateGenre(req.body);
  if (error) return res.status(400).send(error.details[0].message);

  const genre = {
    id: genres.length + 1,
    name: req.body.name
  };
  genres.push(genre);
  res.send(genre);
});

app.put("/api/genres/:id", (req, res) => {
  const genre = genres.find(c => c.id === parseInt(req.params.id));
  if (!genre)
    return res.status(404).send("The genre with the given ID was not found.");

  const { error } = validateGenre(req.body);
  if (error) return res.status(400).send(error.details[0].message);

  genre.name = req.body.name;
  res.send(genre);
});

app.delete("/api/genres/:id", (req, res) => {
  const genre = genres.find(c => c.id === parseInt(req.params.id));
  if (!genre)
    return res.status(404).send("The genre with the given ID was not found.");

  const index = genres.indexOf(genre);
  genres.splice(index, 1);

  res.send(genre);
});

function validateGenre(genre) {
  const schema = {
    name: Joi.string()
      .min(3)
      .required()
  };

  return Joi.validate(genre, schema);
}

const port = process.env.PORT || 3000;
app.listen(port, () => console.log(`Listening on port ${port}...`));

Share This:    Facebook Twitter

Total Pageviews

My Social Profiles

View Sonal's profile on LinkedIn

Tags

__proto__ $Browser Access Grants Accessor properties Admin Ajax AllowsCallouts Apex Apex Map Apex Sharing AssignmentRuleHeader AsyncApexJob Asynchronous Auth Provider AWS Callbacks Connected app constructor Cookie CPU Time CSP Trusted Sites CSS Custom settings CustomLabels Data properties Database.Batchable Database.BatchableContext Database.query Describe Result Destructuring Dynamic Apex Dynamic SOQL Einstein Analytics enqueueJob Enterprise Territory Management Enumeration escapeSingleQuotes featured Flows geolocation getGlobalDescribe getOrgDefaults() getPicklistValues getRecordTypeId() getRecordTypeInfosByName() getURLParameters Google Maps Governor Limits hasOwnProperty() Heap Heap Size IIFE Immediately Invoked Function Expression Interview questions isCustom() Javascript Javascript Array jsForce Lightning Lightning Components Lightning Events lightning-record-edit-form lightning:combobox lightning:icon lightning:input lightning:select LockerService Lookup LWC Manual Sharing Map Modal Module Pattern Named Credentials NodeJS OAuth Object.freeze() Object.keys() Object.preventExtensions() Object.seal() Organization Wide Defaults Override PDF Reader Performance performance.now() Permission Sets Picklist Platform events Popup Postman Primitive Types Profiles Promise propertyIsEnumerable() prototype Query Selectivity Queueable Record types Reference Types Regex Regular Expressions Relationships Rest API Rest Operator Revealing Module Pattern Role Hierarchy Salesforce Salesforce Security Schema.DescribeFieldResult Schema.DescribeSObjectResult Schema.PicklistEntry Schema.SObjectField Schema.SObjectType Security Service Components Shadow DOM Sharing Sharing Rules Singleton Slots SOAP API SOAP Web Services SOQL SOQL injection Spread Operator Star Rating stripInaccessible svg svgIcon Synchronous this Token Triggers uiObjectInfoApi Upload Files VSCode Web Services XHR
Scroll To Top