[SalesForce] How to get Pricebook2 Sharing Rules using UserRecordAccess

I have an Apex class with sharing where I get a list of every Pricebook2 available in the database. Considering the fact that we use sharing rules to prevent access to some of them, my SOQL shouldn't return a full list to every users.

Here's my super simple method:

public SelectOption[] getPricebookList() {
    Pricebook2[] Records =
    [
        SELECT
            Name
        FROM
            Pricebook2
        WHERE
            IsActive = true
        ORDER BY
            Name ASC
    ];

    SelectOption[] Options = new SelectOption[]{};

    for(Pricebook2 R : Records)
        Options.add(new SelectOption(R.Id, R.Name));

    return Options;
}

The data returned by this method is then outputted using a selectoption component inside a Visualforce page.

The problem I have is that every price books are displayed to every single users. Also, any user can use the Visualforce module I created to create Quotes and QuoteLineItems with any Pricebook, even if they don't have access to them according to the sharing settings.

How can I prevent this from happening?

EDIT (@SF_Ninja)

I tried what you answer, here's my code:

// Requesting available price books
Map<ID, Pricebook2> Records = new Map<ID, Pricebook2>([
    SELECT   Name
    FROM     Pricebook2
    WHERE    IsActive = true
    ORDER BY Name ASC
]);

SelectOption[] Options = new SelectOption[]{};

// Requesting price books user access
// NOTE: Workaround since Pricebook2 doesn't enforce Sharing Rules
UserRecordAccess[] UserRecordAccessList =
[
    SELECT  RecordId, HasReadAccess, HasEditAccess, HasDeleteAccess
    FROM    UserRecordAccess
    WHERE   UserId = :UserInfo.getUserId() AND 
            RecordID IN :Records.keySet()
];

System.debug('User ID: ' + UserInfo.getUserId());
System.debug('Pricebook IDs size: ' + Records.keySet().size());

System.debug('IDs to search:');

for(Pricebook2 R : Records.values())
    System.debug('----> ' + R.Name + ' (' + R.Id + ')');

System.debug('Result: ' + UserRecordAccessList); // <-- returning () as admin

According to the documentation, I'm requesting the right values, have the right WHERE operations (http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_objects_userrecordaccess.htm).

Here's the Developer console output:

enter image description here

UserID and Pricebook IDs are both returning values, but the last debug is returning an empty list. Still trying to figure out whats going on here.

EDIT (2013-11-14 10:00)

Here's the final version of the code provided above. The reason why the version above wasn't working is because I passed a set instead of a list in the WHERE condition. Normally, this would be working in a SOQL request, but not in this case. I guess there's an undocumented limitation in UserRecordAccessList.

// Requesting available price books
Map<ID, Pricebook2> Records = new Map<ID, Pricebook2>([
    SELECT   Name
    FROM     Pricebook2
    WHERE    IsActive = true
    ORDER BY Name ASC
]);

SelectOption[] Options = new SelectOption[]{};

Id[] PBIDs = new Id[]{};

PBIDs.addAll(Records.keySet());

// Requesting price books user access
// NOTE: Workaround since Pricebook2 doesn't enforce Sharing Rules
UserRecordAccess[] UserRecordAccessList =
[
    SELECT  RecordId, HasReadAccess, HasEditAccess, HasDeleteAccess
    FROM    UserRecordAccess
    WHERE   UserId = :UserInfo.getUserId() AND 
            RecordID IN :PBIDs
];

System.debug('User ID: ' + UserInfo.getUserId());
System.debug('Pricebook IDs size: ' + Records.keySet().size());

System.debug('IDs to search:');

for(Pricebook2 R : Records.values())
    System.debug('----> ' + R.Name + ' (' + R.Id + ')');

System.debug('Result: ' + UserRecordAccessList);

Hope this might fix others issue.

EDIT (2013-11-14 10:37)

The code above is working (we can get UserRecordAccessList records), but every permissions are set to yes, so it's pointless.

What is left to do?

Hack in Salesforce features to set the Pricebook:

https://[INSTANCE].salesforce.com/oppitm/choosepricebook.jsp?id=[OPP_ID]&retURL=[RET_URL]

This might break your flow, but it should be working and support sharing settings.

Best Answer

This is a known issue with the pricebook2 object in Salesforce.

From the Apex Developers guide.

enter image description here

See here

http://www.salesforce.com/us/developer/docs/apexcode/index_Left.htm#StartTopic=Content/apex_security_sharing_rules.htm?SearchType=Stem

There is an idea you can vote on to get this feature here

https://success.salesforce.com/ideaView?id=08730000000ZE28AAG

As a workaround you can use the UserRecordAccess object in your Apex to only add the pricebook that the current user has access to, to the list of selectoptions. This is not ideal, but its a work around.

Note I did this in a text browser, so it hasnt been tested, but something like this should work for you

public SelectOption[] getPricebookList() {
    Pricebook2[] Records = [SELECT Name FROM Pricebook2 WHERE IsActive = true ORDER BY Name ASC];

    SelectOption[] Options = new SelectOption[]{};

    set<Id> pricebookIDs = new set<Id>();
    for(Pricebook2 R : Records){
        pricebookIDs.add(R.Id);
    }

    List<UserRecordAccess> listUserRecordAccess = [SELECT RecordId, HasReadAccess, HasEditAccess, 
       HasDeleteAccess FROM UserRecordAccess WHERE UserId=:UserInfo.getUserId() AND RecordId IN: pricebookIDs];

    map<Id,boolean> accessMap = new map<Id,boolean>();

    for(UserRecordAccess recAccess : listUserRecordAccess){
        accessMap.put(recAccess.RecordId, recAccess.HasReadAccess);
    }

    for(Pricebook2 R : Records){
        if(accessMap.get(R.Id)){
            Options.add(new SelectOption(R.Id, R.Name));
        }
    }

    return Options;
}
Related Topic