locked
CRM 4.0 - Make child record read only when parent opportunity is won/lost RRS feed

  • Question

  • Hi all,

    Ok I've got a situation that I need to solve. I have a custom entity called 'Lines' in a N:1 relationship with Opportunities. Our sales guys will add one or more lines to each opportunity (this is dealt with by passing the associated view for lines into an iframe on the opportunity form).

    Anyway - my problem is this. As things stand, when an opportunity is won or lost, the 'lines' related to it remain editable - both via the iframe on the opportunity form, via the default view for the Lines entity, and also via advanced find. This is far from ideal as we have some important reports being run against the Lines entity, and there is no way these should change once an opportunity is closed as won or lost.

    So far I've tried a workflow to make the Lines 'inactive' once the parent opportunity is won/lost, but that doesn't solve the problem as the lines disappear from the associated view (I assume this means the Assoc view only displays active records).

    Also, I've tried adding code to the Opportunity form to lock the iframe that displays the Associated view when the status changes from 'Open', but this still means users are able to ammend Lines via advanced find and/or the default view for the Lines entity.

    The only solution I can think of is to have some OnLoad jscript on the Lines entity which checks the status of the parent opportunity - if status is won/lost then all fields on the Lines record are set to read only. Which is where you guys come in, I'm pretty basic level at jscript and while I can happily construct code to check teh status of the current entity, I've no idea how to move up one level and check it's parent. Any pointers and or code snippets would be greatly appreciated.

    thanks,

    Wayne

     

     

    Wednesday, February 2, 2011 10:51 AM

Answers

  • With this custom assembly you the advantage is instead of doing a wait on Line entity you can trigger the workflow on change status of opportunity. Anyway if you happy with JS than put this code onload of Lines and get the result.

    if (crmForm.all.new_opportunityid.DataValue != null) {
    var opportunityId = crmForm.all.new_opportunityid.DataValue[0].id;
    
    
      //alert(id);
      var xml = "" +
    "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
    "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">" +
    GenerateAuthenticationHeader() +
    " <soap:Body>" +
    " <RetrieveMultiple xmlns=\"http://schemas.microsoft.com/crm/2007/WebServices\">" +
    " <query xmlns:q1=\"http://schemas.microsoft.com/crm/2006/Query\" xsi:type=\"q1:QueryExpression\">" +
    " <q1:EntityName>opportunity</q1:EntityName>" +
    " <q1:ColumnSet xsi:type=\"q1:ColumnSet\">" +
    " <q1:Attributes>" +
    " <q1:Attribute>opportunityid</q1:Attribute>" +
    " <q1:Attribute>statecode</q1:Attribute>" +
    " </q1:Attributes>" +
    " </q1:ColumnSet>" +
    " <q1:Distinct>false</q1:Distinct>" +
    " <q1:Criteria>" +
    " <q1:FilterOperator>And</q1:FilterOperator>" +
    " <q1:Conditions>" +
    " <q1:Condition>" +
    " <q1:AttributeName>opportunityid</q1:AttributeName>" +
    " <q1:Operator>Equal</q1:Operator>" +
    " <q1:Values>" +
    " <q1:Value xsi:type=\"xsd:string\">" + opportunityId + "</q1:Value>" +
    " </q1:Values>" +
    " </q1:Condition>" +
    " </q1:Conditions>" +
    " </q1:Criteria>" +
    " </query>" +
    " </RetrieveMultiple>" +
    " </soap:Body>" +
    "</soap:Envelope>";
    
      var xmlHttpRequest = new ActiveXObject("Msxml2.XMLHTTP");
    
      xmlHttpRequest.Open("POST", "/mscrmservices/2007/CrmService.asmx", false);
      xmlHttpRequest.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/crm/2007/WebServices/RetrieveMultiple");
      xmlHttpRequest.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
      xmlHttpRequest.setRequestHeader("Content-Length", xml.length);
      xmlHttpRequest.send(xml);
    
      var resultXml = xmlHttpRequest.responseXML.xml;
      //alert(resultXml );
      if (resultXml != null || resultXml != "") {
        var xmlDocument = new ActiveXObject("Microsoft.XMLDOM");
        var bLoaded = xmlDocument.loadXML(resultXml);
    
        if (bLoaded)
    
          try {
          var oOppStatus = xmlDocument.selectSingleNode("//BusinessEntity/q1:statecode").text;
    alert(oOppStatus );
    if ((oOppStatus =='Won') || (oOppStatus =='Lost'))
    
    {
    OnCrmPageLoad();
    
    }
    
        }
        catch (err) {
          alert("Data Missing");
        }
      }
    }
    
    
    function OnCrmPageLoad() 
    
    { 
     
     ToggleFormFields(true); 
    
    } 
    
    
    
    function ToggleFormFields( disable ) 
    { 
     
     for( var i = 0 ; i < crmForm.all.length ; i++ ) 
     
    { 
       
     if( crmForm.all[ i ].req ) 
         
    crmForm.all[ i ].Disabled = disable; 
     
     } 
    
    } 
    

     

     


    Regards Faisal
    • Marked as answer by wayneiles Wednesday, February 2, 2011 2:34 PM
    • Unmarked as answer by wayneiles Wednesday, February 2, 2011 4:14 PM
    • Marked as answer by wayneiles Wednesday, February 2, 2011 5:27 PM
    Wednesday, February 2, 2011 2:08 PM

All replies

  • You can call the CRM Web Services Retrieve method to obtain information about an entity instance, given it's GUID identifier:

    http://technet.microsoft.com/en-us/library/cc677076.aspx

    To obtain the Opportunity GUID:

    1. If the Opportunity is not already available on the Line entity form, place it on there somewhere (it should be available as a lookup attribute if there is an N:1 relationship between Line and Opportunity)
    2. Your javascript can then use the following:
    var opportunityField = crmForm.all.<opportunityattributename>;
    
    var opportunityId = opportunityField.DataValue[0].id;
    
    Where <opportunityattributename> is replaced by the attribute name of your opportunity lookup.
    --pogo (pat)
    Wednesday, February 2, 2011 11:59 AM
  • Hi Pogo,

    Thanks for your reply. Amazingly I kind of follow what you're saying.

    Yes the opportunity attribute is on the form as a lookup - new_opportunityid

     

    I've attempted to tweak the code in the link to replicate the entities/relationship I'm working with, but have unsurprisingly I've run into some trouble.

    // Prepare variables 
    
    var opportunityField = crmForm.all.new_opportunityid
    var opportunityId = opportunityField.DataValue[0].id;
    var authenticationHeader = GenerateAuthenticationHeader();
    
    
    // Prepare the SOAP message.
    var xml = "<?xml version='1.0' encoding='utf-8'?>"+ 
    "<soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'"+
    " xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'"+
    " xmlns:xsd='http://www.w3.org/2001/XMLSchema'>"+ 
    authenticationHeader+ 
    "<soap:Body>"+ 
    "<Retrieve xmlns='http://schemas.microsoft.com/crm/2007/WebServices'>"+ 
    "<entityName>opportunity</entityName>"+ 
    "<id>"+opportunityid+"</id>"+ 
    "<columnSet xmlns:q1='http://schemas.microsoft.com/crm/2006/Query' xsi:type='q1:ColumnSet'>"+ 
    "<q1:Attributes>"+ 
    "<q1:Attribute>status</q1:Attribute>"+ 
    "</q1:Attributes>"+ 
    "</columnSet>"+ 
    "</Retrieve>"+ 
    "</soap:Body>"+ 
    "</soap:Envelope>";
    // Prepare the xmlHttpObject and send the request.
    var xHReq = new ActiveXObject("Msxml2.XMLHTTP");
    xHReq.Open("POST", "/mscrmservices/2007/CrmService.asmx", false);
    xHReq.setRequestHeader("SOAPAction","http://schemas.microsoft.com/crm/2007/WebServices/Retrieve");
    xHReq.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
    xHReq.setRequestHeader("Content-Length", xml.length);
    xHReq.send(xml);
    // Capture the result.
    var resultXml = xHReq.responseXML;
    
    // Check for errors.
    var errorCount = resultXml.selectNodes('//error').length;
    if (errorCount != 0)
    {
     var msg = resultXml.selectSingleNode('//description').nodeTypedValue;
     alert(msg);
    }
    // Display the retrieved value.
    else
    {
    alert(resultXml.selectSingleNode("//q1:status").nodeTypedValue);
    }
    
    

    Obviously at this stage I'm just trying to get it to return the status of the parent opportunity so I know it all works. However at the moment I'm getting the following error: 'opportunityid is undefined' - I assume it's a problem with the bolded part of the code?

    Any pointers? Also - how can i use the retrieved statecode in order to dictate what I want to do with the Line record? i.e. if the parent opportunities statecode is anything but 0, then Line record(s) are to be read only.

    Sorry for being an amateur - this is like hieroglyphics to me and our usual goto guy for jscript is off for a MONTH!

    • Edited by wayneiles Wednesday, February 2, 2011 12:38 PM edit
    Wednesday, February 2, 2011 12:37 PM
  • No worries.

    Just download the dll from http://crm40distributewf.codeplex.com/ and register the assembly. Create a workflow to do this job. Change the status of line to Inactive once opportunity is won.

     


    Regards Faisal
    Wednesday, February 2, 2011 12:42 PM
  • No worries.

    Just download the dll from http://crm40distributewf.codeplex.com/ and register the assembly. Create a workflow to do this job. Change the status of line to Inactive once opportunity is won.

     


    Regards Faisal


    Don't need dll's or assembly registering for that, constructed a workflow from standard that achieves this. Workflow on Opp Line, wait until Parent opportunity status changes then change Line Status to inactive. Am aware this isn't ideal as there'll be a lot of workflows 'waiting', but our opportunities are opened and closed pretty quickly so don't envisage that being a problem.

    The problem with this approach is that the Associated view for Lines only shows ACTIVE lines, which means when going back into a closed opportunity (won or lost), all related lines to it are 'invisible'.

    • Proposed as answer by Faisal Fiaz Wednesday, February 2, 2011 5:11 PM
    Wednesday, February 2, 2011 12:57 PM
  • I think the best solution in this case is to make a simple plugin on Update of line, which checks if parent opportunity is won/lost and if so, it will reject any changes to line. So users will still see editable form, but will not be able to change any data. This will ensure data integrity in all scenarios.

    Also you can make OnLoad script to disable all fields.

    In your script (bold part) there is a mistake. You have to write opportunityId instead of opportunityid. Case matters.

    Then you can use this http://dmcrm.blogspot.com/2008/11/disabling-all-fields-on-formdynamically.html to disable all fields


    Oleksandr Klymenko,
    My Blog: www.darkaxe.wordpress.com
    • Marked as answer by wayneiles Wednesday, February 2, 2011 2:34 PM
    • Unmarked as answer by wayneiles Wednesday, February 2, 2011 2:35 PM
    Wednesday, February 2, 2011 1:50 PM
  • With this custom assembly you the advantage is instead of doing a wait on Line entity you can trigger the workflow on change status of opportunity. Anyway if you happy with JS than put this code onload of Lines and get the result.

    if (crmForm.all.new_opportunityid.DataValue != null) {
    var opportunityId = crmForm.all.new_opportunityid.DataValue[0].id;
    
    
      //alert(id);
      var xml = "" +
    "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
    "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">" +
    GenerateAuthenticationHeader() +
    " <soap:Body>" +
    " <RetrieveMultiple xmlns=\"http://schemas.microsoft.com/crm/2007/WebServices\">" +
    " <query xmlns:q1=\"http://schemas.microsoft.com/crm/2006/Query\" xsi:type=\"q1:QueryExpression\">" +
    " <q1:EntityName>opportunity</q1:EntityName>" +
    " <q1:ColumnSet xsi:type=\"q1:ColumnSet\">" +
    " <q1:Attributes>" +
    " <q1:Attribute>opportunityid</q1:Attribute>" +
    " <q1:Attribute>statecode</q1:Attribute>" +
    " </q1:Attributes>" +
    " </q1:ColumnSet>" +
    " <q1:Distinct>false</q1:Distinct>" +
    " <q1:Criteria>" +
    " <q1:FilterOperator>And</q1:FilterOperator>" +
    " <q1:Conditions>" +
    " <q1:Condition>" +
    " <q1:AttributeName>opportunityid</q1:AttributeName>" +
    " <q1:Operator>Equal</q1:Operator>" +
    " <q1:Values>" +
    " <q1:Value xsi:type=\"xsd:string\">" + opportunityId + "</q1:Value>" +
    " </q1:Values>" +
    " </q1:Condition>" +
    " </q1:Conditions>" +
    " </q1:Criteria>" +
    " </query>" +
    " </RetrieveMultiple>" +
    " </soap:Body>" +
    "</soap:Envelope>";
    
      var xmlHttpRequest = new ActiveXObject("Msxml2.XMLHTTP");
    
      xmlHttpRequest.Open("POST", "/mscrmservices/2007/CrmService.asmx", false);
      xmlHttpRequest.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/crm/2007/WebServices/RetrieveMultiple");
      xmlHttpRequest.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
      xmlHttpRequest.setRequestHeader("Content-Length", xml.length);
      xmlHttpRequest.send(xml);
    
      var resultXml = xmlHttpRequest.responseXML.xml;
      //alert(resultXml );
      if (resultXml != null || resultXml != "") {
        var xmlDocument = new ActiveXObject("Microsoft.XMLDOM");
        var bLoaded = xmlDocument.loadXML(resultXml);
    
        if (bLoaded)
    
          try {
          var oOppStatus = xmlDocument.selectSingleNode("//BusinessEntity/q1:statecode").text;
    alert(oOppStatus );
    if ((oOppStatus =='Won') || (oOppStatus =='Lost'))
    
    {
    OnCrmPageLoad();
    
    }
    
        }
        catch (err) {
          alert("Data Missing");
        }
      }
    }
    
    
    function OnCrmPageLoad() 
    
    { 
     
     ToggleFormFields(true); 
    
    } 
    
    
    
    function ToggleFormFields( disable ) 
    { 
     
     for( var i = 0 ; i < crmForm.all.length ; i++ ) 
     
    { 
       
     if( crmForm.all[ i ].req ) 
         
    crmForm.all[ i ].Disabled = disable; 
     
     } 
    
    } 
    

     

     


    Regards Faisal
    • Marked as answer by wayneiles Wednesday, February 2, 2011 2:34 PM
    • Unmarked as answer by wayneiles Wednesday, February 2, 2011 4:14 PM
    • Marked as answer by wayneiles Wednesday, February 2, 2011 5:27 PM
    Wednesday, February 2, 2011 2:08 PM