Showing posts with label Territory Management. Show all posts
Showing posts with label Territory Management. Show all posts

Sunday, May 5, 2013

AccountShare Object


Recipe: Definition of AccountShare Object

Discussion:
At times, users may need to manage their record Sharing tables via the API.
For example, if an administrator wishes to remove all manually-assigned Territories from Accounts or Opportunities, it may be inconvenient to manage this change via the GUI.  Instead, the admin may prefer to delete these references in mass.  This would be done by accessing the Share tables through the API (for example, via the DataLoader).  The administrator would look for all records with a ROWCAUSE of TerritoryManual and delete these records.

Explanations of Fields of AccountShare object:
When accessing "Share" tables via the API, there are a number of columns that may not be immediately familiar.  Below are descriptions of each column and the possible values (the following refer to the AccountShare table, but the structure is very similar for other Share tables, such as OpportunityShare):

1. ID
This is the unique record ID for the AccountShare record.

2. ACCOUNTID
This column stores the IDs for the Account records being shared.  There may be multiple rows with the same AccountID value, as each Account record might be shared with more than one User or Group.

3. USERORGROUPID
This column stores the IDs for the User or Group to whom the Account record is shared.

4. ACCOUNTACCESSLEVEL
This column determines the access level granted for the Account record to the specified User or Group.
Values are:
A. All (Full access).
B. Edit (Read/Write).
C. Read (ReadOnly).

5. OPPORTUNITYACCESSLEVEL
This column determines the access level granted - for the Opportunities associated with this Account record - to the specified User or Group.
Values are:
A. Edit (Read/Write).
B. Read (ReadOnly).
C. None (No sharing granted).

6. CASEACCESSLEVEL
This column determines the access level granted - for the Cases associated with this Account record - to the specified User or Group.
Values are:
A. Edit (Read/Write).
B. Read (ReadOnly).
C. None (No sharing granted).

7. CONTACTACCESSLEVEL
This column determines the access level granted - for the Contacts associated with this Account record - to the specified User or Group.
Values are:
A. Edit (Read/Write)
B. Read (ReadOnly).
C. None (No sharing granted).

8. ROWCAUSE
This column explains *why* the Account record is shared to the specified User or Group.
Values are:
A. Owner (the specified User is the record owner).
B. ImplicitParent (a child record related to this Account is owned by the specified User).
C. Team (the specified User is an Account Team member).
D. Manual (sharing was manually granted to the specified User).
E. TerritoryManual (the Account record was manually assigned to a Territory).
F. Territory (a Territory assignment rule granted access for this Account record to the specified Group.  Note: records marked as Territory cannot be edited/deleted via DataLoader/API).

Reference Help Link for Account Share Object:
How do I interpret 'Share' tables?
http://help.salesforce.com/apex/HTViewSolution?id=000004856&language=en_US

Saturday, May 4, 2013

Territory Management in Apex


Recipe: Territory Management
Territory management is account sharing system that grants access to accounts based on the characteristics of the accounts. It enables your company to structure your Salesforce data and users the same way you structure your sales territories.
If you want to use territory management in Apex then you should read this document for limitations and territory functionality in Apex.

Relationships among Territory, Account, Opportunity and User objects:
a) A territory can have unlimited number of users, and a user can be assigned to an unlimited number of Territories.
b) A territory can have unlimited number of accounts, and an account can be assigned to an unlimited number of territories.
c) An Opportunity can only be assigned to a Territory.

Territories in Opportunity object:

Problem:
If you want to access the territory field of Opportunity in Apex you can access it easily. Because the field Territory on Opportunity object is exactly present in Opportunity object.

Solution with Example Code:
/*
Author: @Abrar Haq
Purpose:
    (a)To populate Parent Territory Name and Territory User Name of assigned Territory of Opportunity.
*/
trigger OpportunityTrg on Opportunity (before insert) {
    if(Trigger.isBefore){
        /* Declaration of collection data types */
        Set<Id> setOfTerritoryIds = new Set<Id>();

        for(Opportunity oppty : Trigger.New){
            if(Trigger.isInsert){
                if(oppty.TerritoryId != null){
                    setOfTerritoryIds.add(oppty.TerritoryId);
                }
            }
        }

        /*
            (b)To populate Parent Territory Name of assigned Territory of Opportunity.
        */    
        if(setOfTerritoryIds.size() > 0){
            Map<Id, Territory> mapOfTerritory = new Map<Id, Territory>([Select t.RestrictOpportunityTransfer, t.ParentTerritoryId, t.OpportunityAccessLevel, t.Name, t.MayForecastManagerShare, t.Id, t.ForecastUserId, t.Description, t.ContactAccessLevel, t.CaseAccessLevel, t.AccountAccessLevel From Territory t Where Id IN :setOfTerritoryIds]);
        }
    }
}


Territories in Account object:

Problem:
If you want to access to the Territories field on Account then you need to do extra work. Because Territories field on Account is not present in Account object. You have to query in different objects to get the assigned territory of Account.

Algorithm (Pseudo Code):
1) Query AccountShare object where AccountId = Trigger.New (Account Ids)
where RowCause = 'Territory' (Note: If Account has assigned via Territory Assignment Rules).
where RowCause = 'TerritoryManual' (Note: If Account has assigned via “Manually Assigned Accounts” related list on Territory detail page or AccountShare record has created in Apex Code).
2) Query Group object where Id = AccountShare.UserOrGroupId
3) Query Territory object where Id = Group.RelatedId
4) Query UserTerritory object where Id = Territory.UserId


Solution with Example Code:
/*
Author: @Abrar Haq
Purpose:
    (a)To populate Parent Territory Name and Territory User Name of assigned Territory of Account.
*/
trigger AccountTrg on Account (before update){
    if(Trigger.isBefore){
        /* Declaration of collection data types */
        Set<Id> setOfAccountIds = new Set<Id>();

        for(Account acct : Trigger.New){
            if(Trigger.isUpdate){
                if(acct.Id != null){
                    setOfAccountIds.add(acct.Id);
                }
            }
        }

        /*
            (b) To populate Parent Territory Name of assigned Territory of Opportunity.
        */    
        if(setOfAccountIds.size() > 0){

        /* Declaration of collection data types */
Map<Id, Id> mapOfAccountShare = new Map<Id, Id>();
       Map<Id, Id> mapOfGroup = new Map<Id, Id>();
       Map<Id, Territory> mapOfUserTerritory = new Map<Id, Territory>();


(1)

//Query in Account Share object
    /*
    Those Accounts which are assigned via Territory Assignment Rules.
You can query those Accounts by filtering RowCause = 'Territory' in AccountShare object query.
    */
    List<AccountShare> listOfAccountShare =
    [Select Id, UserOrGroupId, AccountId from AccountShare where RowCause = 'Territory' and AccountId IN :setOfAccountIds];

    //Query in Account Share object
    /*
    Those Accounts which are assigned via Manually Assigned Accounts related list on Territory detail page or create an AccountShare record in Apex code.
    You can query those Accounts by filtering RowCause = 'TerritoryManual' in AccountShare object query.
    */
    List<AccountShare> listOfAccountShare =
    [Select Id, UserOrGroupId, AccountId from AccountShare where RowCause = 'TerritoryManual' and AccountId IN :setOfAccountIds];

//Map of Account Share
    for(AccountShare acctShare : listOfAccountShare){
    mapOfAccountShare.put(acctShare.AccountId, acctShare.UserOrGroupId);        
    }      

(2)

    //Query in Group object            
    List<Group> listOfGroup = [Select Id, RelatedId from Group where Type='Territory' and Id IN :mapOfAccountShare.Values()];

//Map of Group object
    for(Group groupRecord : listOfGroup){
    mapOfGroup.put(groupRecord.Id, groupRecord.RelatedId);        
    }

(3)

    //Query in Territory object
    //Map<Id, Territory> mapOfTerritories =
    new Map<Id, Territory>([select id, name, ParentTerritoryId from Territory where Id IN:mapOfGroup.Values() ]);      

(4)

    //Query in User Territory object
    List<UserTerritory> listOfUserTerritory = [Select u.UserId, u.TerritoryId, u.IsActive, u.Id From UserTerritory u WHERE IsActive = true AND TerritoryId IN :mapOfTerritories.KeySet()];

//Map of User Territory object
    for(UserTerritory userTerritory : listOfUserTerritory){
    mapOfUserTerritory.put(userTerritory.TerritoryId, userTerritory);              
    }

}

}
}


Limitation(s):
1) You cannot edit / delete AccountShare record where ROWCAUSE = ‘Territory’ via Data Loader and API. If you do then you will get the following error.
“System.DmlException: Delete failed. First exception on row 0 with id 00rZ000001G3hDoIAJ; first error: INVALID_CROSS_REFERENCE_KEY, id does not exist: []”.