20

Im trying to write what feels like it should be a simple Apex sharing rule but not having much luck.

Basically I want partner users to be able to see all "customers" (custom object) owned by customer community users within the same account. The communities sharing set settings allow me to automatically share customers with other customer community users but not partner community users. So i'm trying to setup a apex trigger to create sharing rules for all the partner community users within the same account.

When I edit a customer i'm getting this error:

Apex trigger shareCustomer caused an unexpected exception, contact your administrator: shareCustomer: execution of AfterUpdate caused by: System.DmlException: Insert failed. First exception on row 0; first error: INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY, insufficient access rights on cross-reference id: []: Trigger.shareCustomer: line 22, column 1

line 22 being the insert statement

and here is my code!

trigger shareCustomer on Customer__c (after insert, after update) {

    List<Customer__Share> customerShares  = new List<Customer__Share>();
    Customer__Share customerShare;

    for(Customer__c customer : trigger.new){
        list<User> users = [ Select Id from User where accountId = :customer.Organization_Id__c and profileId = '00eG0000000ycc8IAA' and isActive = true];

        for (User u: users) {

                customerShare = new Customer__Share();
                customerShare.ParentId = customer.Id;
                customerShare.UserOrGroupId = u.Id;
                customerShare.AccessLevel = 'edit';
                customerShare.RowCause = Schema.Customer__Share.RowCause.Organization__c;
                customerShares.add(customerShare);


        }
    }

    Database.SaveResult[] lsr = Database.insert(customerShares);

}

here is a simple diagram of the relationships im trying to get through:

enter image description here

UPDATE: Some of the responders seem to be getting confused because the data in question is "customers" what i've done is try and write a reusable class that could share any data, with any partner accounts roles.. but STILL not working, getting the same error as above.

public static void shareWithAccount(Id parentId, Id accountId) {

        List<Customer__Share> customerShares  = new List<Customer__Share>();
        Customer__Share customerShare;

        list<UserRole> roles = [ SELECT Name, Id FROM UserRole where PortalAccountId  = :accountId];

        string roleID;
        for (UserRole role: roles) {

           try {
            Group g = [select Id from group where RelatedId = :role.Id and type='Role' limit 1]; 

            customerShare = new Customer__Share();
            customerShare.ParentId = parentId;
            customerShare.UserOrGroupId = g.Id;
            customerShare.AccessLevel = 'edit';
            customerShare.RowCause = Schema.Customer__Share.RowCause.Organization__c;
            customerShares.add(customerShare);

           } catch (exception e) {

           }


        }



        if (!customerShares.isEmpty())
            Database.SaveResult[] customerSharesResult = Database.insert(customerShares);


    }
  • 1
    Hi Keith, Im sure someone will point this out in their answer: avoid SOQL in loops as you will hit a governor limits very. In this case you have a few nested for loops with SOQL queries which will hit the 101 SOQL limit very quickly. – BarCotter Nov 18 '13 at 23:46
  • For sure, im just trying to get it working then i can refactor to avoid SOQL in the loops – Keith Mancuso Nov 18 '13 at 23:47
  • Also make sure that the user you are selecting isActive. This may cause the error you are seeing. – BarCotter Nov 18 '13 at 23:52
  • hmm added isActive check and still failing. I should have mentioned customer.Organization_Id__c is a formula field that just gets the ORG id from the owner... so im getting all the contacts at that org and trying to find users for each – Keith Mancuso Nov 19 '13 at 0:00
  • What line in your code is the cross-ref error? You may need to remove your try/catch block to get the right line. – greenstork Nov 19 '13 at 0:07
12
+50

Update 03-11-16

Two important revisions to communities have occurred since I wrote this answer that should be considered. First is that User Sharing and visibility restrictions on the User Object in an Org are now in place (SU 15 as I recall). This impacts SF Users visibility to other users both inside an Org and within an Org's Customer Community.

The 2nd one is the introduction of Customer Community Plus licenses. These licenses DO NOT have the same sharing model as regular Customer Communities. That sharing is vastly different. However, Customer Community Plus licenses support Managed Apex Sharing which is often the only way in which records can be shared. Sharing groups are not available as they are with regular Customer Communities. Please keep this in mind when reading this answer.

EDIT 3

I just found this: "You can share a standard or custom object with users or groups. Apex sharing is not available for Customer Community users." on the following page http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_bulk_sharing_creating_with_apex.htm. I think that's the definitive answer regarding Apex Sharing with Community Users. You'll need to either change owners or set up a sharing group.

EDIT 2

I'd gotten that the customer wasn't a contact, but that the Portal User was. You still need to get the Owner of the Portal User.contact to set up your sharing if you're going to do it by User. Remember: Portal Users do not have a Role. You're not going to be able to do any kind of role based sharing unless you do it as a child of the Owner.

Your idea of transferring ownership to the system admin has a lot of merit, especially if the Portal User doesn't need to retain ownership and only needs to be able to view the record. That would likely be the simplest solution to your problem.

EDIT 1

I've edited my post to included some of my comments below and try to clarify them rather than create a long back and forth thread.

Fist, depending on your actual schema, I believe the above query should retrieve the Owner's ID and UserRoleId, not the Portal User's RoleId provided the contact__c object is the same as the contact object.

Since both Owners who have SF Licenses and Portal Users that have Communities licenses are all Users, in theory, you could get them all in one query. You can also do them as two separate queries. How best to do them depends on what info you need and whether one depends on the other. Its also a matter of what method you're most comfortable with.

The Owner(a SF User) owns the Contact and the Account associated with the Contact, but not the Communities User. To be clear, a Communities User cannot own another Contact nor an Account simply because they don't have anything other than read access to those objects for their own information (both contact and account)!

However any kind of User can be retrieved via a query on User. Additionally, a User's Contact info can be retrieved by a query on User (contact = User.contact.Id, contact.Name = User.contact.Name, etc.).

You say below that the Owner of the contact for the Portal User is also a Portal User, the question I'd have is what license that Portal User has? It can't be a Communities License.

If this helps, here's how Communities Users are created using Apex:

Account> a = new Account(Name = Acme ); 
/* OwnerID will either be specified or will be ID of User who performs this operation.*/
insert a;

Contact c = new Contact(FirstName = John, LastName = Doe, Account = a[0].Id (ID of Ajax Acct), email = test@salesforce.stackexchangetest.com, Phone = 800-555-1212, MailingStreet = .....); 
/* you know the drill for the rest of the data required. */
/* Again, the OwnerID is for the User who performs the operation unless an OwnerID is specified */
insert c;

User u =new User(ContactID = c[0].Id (John Doe's contact.Id), Alias = 'PrtlUsr1', Email= c[0].Email, FirstName= c[0].FirstName, LastName= c[0].LastName, userName=c[0].Email, ProfileID = CommunitiesLicenceID for your org);
/* Again, the OwnerID is for the User who performs the operation unless an OwnerID is specified */
Insert u;

In my experience, it requires a SF User license to create a Portal User.

The owner of the customer is a portal user tho, i guess thats what im not understanding. Are you saying is should be getting the portal user -> Contact -> and that owner? how does that help when what im trying to share is the "customer" I think people are getting confused because they are called "customers" but they are just a custom object.

This could very well be the "disconnect" that's been occurring. Since there is no "role hierarchy", yes, you need to be retrieving the Portal User Contact and the Owner of the Portal User Contact in order to share anything that belongs to the Portal User. Why? Because the only other person that can see everything the Portal User "owns" is the Owner of the Portal User Contact -> (Portal) User.contact.OwnerID!

The only other way that immediately comes to mind that might help you accomplish what you desire would be to utilize your trigger to add these Users to a Sharing Group of some kind; something you might also want to consider as an alternative.

I hope this provides the clarification you needed in order to help you move forward with your trigger.

BTW, when you do your test class, I recommend you utilize "Run As" when you create your Account, Contact and Portal User. That will automatically create the Portal User with the Run As User as it's Owner.

Original Answer

Communities Users have an Owner. They do not have a Role, all they have is a User Profile. Essentially, they quasi inherit their Role from their Owner. Their Owner can see any of the records they create. So, if you want to share something a Communities User "owns", you're going to need to do that through their Owner.

trigger shareCustomer on Customer__c (after insert, after update) {

   List<Customer__Share> customerShares  = new List<Customer__Share>();
   Customer__Share customerShare;

I agree with Greenstork, you need the User Role Id from related Owners (Users) in the trigger set. Now the question is:

Are your customers logging in as Communities Users or as Customers (Contacts)?

If as Users, then your query may need to reflect that. Your query currently shows as Customer__c, which is a Custom Object. Is there a relationship between Customer__c and either Contacts or Communities Users?

List<Customer__c> enhancedCustomerList = [SELECT id, Owner.UserRoleId, OwnerID FROM Customer__c WHERE id IN :trigger.new];

If there is, you may want to utilize that relationship in your trigger. That's for you to decide. I primarily say that because it may simplify things for you because of Ownership when it comes to Communities Users.

The rest of your trigger would look pretty much the same as what Greenstork has shown depending on the relationships you use between contact, account, Portal User, and Portal User.OwnerID. The sharing is going to occur based on the Owner of contact/customer__c/Portal User.

  • Hey thanks for the information. Customers is a custom object, and the relationship with the Communities Users is through record ownership, they create and own the customers (note: customers don't every log in, its just a custom object). They are logging as communities users to a Force.com site and creating the customer records (among other things). – Keith Mancuso Nov 27 '13 at 21:02
  • So my question for you is your query is pulling the Owner.UserRoleId which would bull the Communities users roleID which is blank. What you are saying is i need to get the owner, then in another query get THAT users owner and share things through that? – Keith Mancuso Nov 27 '13 at 21:03
  • Depending on your actual schema, I believe the above query should retrieve the Owner's ID and UserRoleId, not the Portal User's RoleId. Since they're all Users, in theory, you could get them all in one query or you can do them as two separate queries if you need all the info. Its a matter of what method you're most comfortable with. The Owner(a SF User) owns the contact, not the Communities User (contact = User.contact.Id). However both kinds of Users can be retrieved via a query on User. – crmprogdev Nov 27 '13 at 21:59
  • The owner of the customer is a portal user tho, i guess thats what im not understanding. Are you saying is should be getting the portal user -> Contact -> and that owner? how does that help when what im trying to share is the "customer" I think people are getting confused because they are called "customers" but they are just a custom object. – Keith Mancuso Nov 27 '13 at 22:34
  • See my edited answer that addresses all of your comments – crmprogdev Nov 28 '13 at 15:50
3

EDIT: This answer was given prior to the OP making it clear that the owner of the Customer__c records was a customer community user and not a partner community user.

In the case of partner community users, the role of the record owner is shared by all users with the same account. Instead of trying to create share records for each user, consider just sharing to that role, which is unique by all accounts. That is to say, there is no danger of sharing across accounts since roles are unique for each partner.

Also, as a side note, your code is far from bulk safe. You have SOQL statements inside two for loops and this could lead to big problems with bulk DML operations on the Customer__c object.

Try this instead:

trigger shareCustomer on Customer__c (after insert, after update) {

    List<Customer__Share> customerShares  = new List<Customer__Share>();
    Customer__Share customerShare;

    //you need the User Role Id from related owners (users) in the trigger set
    List<Customer__c> enhancedCustomerList = [SELECT id, Owner.UserRoleId FROM Customer__c WHERE id IN :trigger.new];

    for(Customer__c customer : enhancedCustomerList){

        customerShare = new Customer__Share();
        customerShare.ParentId = customer.Id;
        customerShare.UserOrGroupId = customer.Owner.UserRoleId;
        customerShare.AccessLevel = 'edit';

        customerShares.add(customerShare);
    }

    try {
        Database.SaveResult[] lsr = Database.insert(customerShares,false);
    } catch (Exception e) {
        //handle your exception
    }

}

What's not included here and you might consider is some cleanup code to remove shares when record ownership changes. I also don't remember what happens when you try to create a share record when it already exists.

  • Hmm this is helpful... but the role thing wont work because the records are "owned" by customer community users and i want to share them with partner community users. To the partner community user will have a role, but the customer community wont? – Keith Mancuso Nov 18 '13 at 23:56
  • You may want to update your question to explicitly state that. I was under the impression that you were asking about partner community users sharing with each other. That is how both the title of this post and question reads. – greenstork Nov 19 '13 at 0:01
  • Customer Community users do not have access to the sharing model, nor are they directly associated with an account. Their contact of course, is associated with an account but the user is not. – greenstork Nov 19 '13 at 0:03
  • That is to say, I'm not sure Customer Communities users are allowed to create sharing records – greenstork Nov 19 '13 at 0:04
  • right... thats part of the problem. Im open to other approaches? – Keith Mancuso Nov 19 '13 at 0:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.