I need to be able to only execute my code upon the condition that the related opportunity has a statecode of 1
In my code I am able to use the GenerateSalesOrderFromOpportunityRequest class provided by the Microsoft Dynamics SDK to create a new sales order when a opportunityclose activity is created.
The drawback of this approach is that an opportunityclose activity is created by the system when an opportunity is closed as won(1) or lost(2). Also, there are no attributes on the opportunityclose activity that say if it was won or lost. So the only way to find it out is to get that attribute from the related opportunity.
In my code I'm able to get other attributes from the related opportunity, like name, but I have not been able to get any other value for statecode other that 0.
Here is my code:
Entity postImageEntity = (context.PostEntityImages != null && context.PostEntityImages.Contains(this.postImageAlias)) ? context.PostEntityImages[this.postImageAlias] : null;
if (postImageEntity.LogicalName == "opportunityclose" && postImageEntity.Attributes.Contains("opportunityid") && postImageEntity.Attributes["opportunityid"] != null)
{
// Create an entity reference for the related opportunity to get the id for the GenerateSalesOrderFromOpportunityRequest class
EntityReference entityRef = (EntityReference)postImageEntity.Attributes["opportunityid"];
// Retrieve the opportunity that the closed opportunity activity was created for.
Entity RelatedEntityRef = service.Retrieve("opportunity", entityRef.Id, new ColumnSet( new String[] {"statecode","statuscode", "name"}));
OptionSetValue StateCode = (OptionSetValue)RelatedEntityRef.Attributes["statecode"];
OptionSetValue StatusCode = (OptionSetValue)RelatedEntityRef.Attributes["statuscode"];
string OppName = (string)RelatedEntityRef.Attributes["name"];
if (entityRef.LogicalName == "opportunity" && StateCode.Value == 1)
{
try
{
GenerateSalesOrderFromOpportunityRequest req = new GenerateSalesOrderFromOpportunityRequest();
req.OpportunityId = entityRef.Id;
req.ColumnSet = new ColumnSet(true);
GenerateSalesOrderFromOpportunityResponse resp = (GenerateSalesOrderFromOpportunityResponse)service.Execute(req);
}
catch (FaultException ex)
{
throw new InvalidPluginExecutionException("An error occurred in the plug-in.", ex);
}
}
}
Recap: For this to work I just need to be able to get the actual statecode value of the opportunity related to the opportunityclose. Currently I have only been able to get 0 even if I know that the state code of the opportunity is 1.
Other Info:
This is for Microsoft Dynamics Online 2013/2015(works with both)
Using SKD v6.1.1
Plugin works, but fires whether the opportunity is won or lost. (not intended)
Can't you view the opportunity to see if the status is won or lost. I
You can retrieve the OpportunityClose and change the status if you need to
https://msdn.microsoft.com/en-us/library/gg334301.aspx
I assume it's setting the opportunityclose to 1 because you are executing GenerateSalesOrderFromOpportunityRequest which you would only do if you won the opportunity (e.g. you wouldn't progress a lost opportunity).
Related
Hi I'm new to Dynamics and plugins in dynamics. I have created a simple Entity called Library that holds books.
After a new book is created I want the price of the book to increment by a GST of 10% on the server side via a plugin.
I know this would normally occur on the page before saving by I'm trying to work out how server side logic works.
I have created a postOperation (synchronous) step for the "Create" message to call the Plugin Execute() method. From my reading this should occur AFTER the record is saved in the database.
I also have a post image entity that I access.
In the Execute method I try to access the saved record via the PostMessageEntity to update the price, but I get an exception saying the record does not exist based on the record identifier that i have obtained. I can confirm the record was never created in the system, yet the postOperation has been called.
How do I access the just saved record in the plugin so that I can update the Price?
My code:
public void Execute(IServiceProvider serviceProvider)
{
// Obtain the execution context from the service provider.
Microsoft.Xrm.Sdk.IPluginExecutionContext context = (Microsoft.Xrm.Sdk.IPluginExecutionContext)
serviceProvider.GetService(typeof(Microsoft.Xrm.Sdk.IPluginExecutionContext));
// create a trace log so you can see where in the code it breaks
ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
// create access to service
IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
tracingService.Trace("have reached execute event in plugin.");
// The InputParameters collection contains all the data passed in the message request.
if (context.InputParameters.Contains("Target") &&
context.InputParameters["Target"] is Entity)
{
tracingService.Trace("We have a target and it is an entity.");
// Obtain the target entity from the input parameters.
Entity entity = (Entity)context.InputParameters["Target"];
if (entity.LogicalName == "new_books")
{
tracingService.Trace("the entity id of the record that was created is .." + entity.Attributes["new_booksid"].ToString());
// do we have a post update image of the new_books entity
if (context.PostEntityImages.Contains("newbookpostImage") && context.PostEntityImages["newbookpostImage"] is Entity)
{
tracingService.Trace("we have a postEntityImage.");
// // yep lets grab it.
Entity postMessageEntity = (Entity)context.PostEntityImages["newbookpostImage"];
// get book price as just saved to db
decimal bookPrice = ((Money)postMessageEntity.Attributes["new_price"]).Value;
// get id of the the record we have
Guid RecordID = ((Guid)postMessageEntity.Attributes["new_booksid"]);
tracingService.Trace("we have a post update bookprice.");
tracingService.Trace("the entity id of the post image entity is ..." + postMessageEntity.Attributes["new_booksid"].ToString());
Entity created_book = new Entity("new_books");
// use service to access a field of the current record as it is in the database and column we want to update.
created_book = service.Retrieve(created_book.LogicalName, RecordID, new ColumnSet(true));
//And the last line is where it dies and tells me new_books with id with d7bfc9e2 - 2257 - ec11 - 8f8f - 00224814e6e0 does not exist.
}
}
}
}
Entity postMessageEntity = (Entity)context.PostEntityImages["newbookpostImage"];
Is your PostEntityImage new_books entity?
Also if you have an entity postMessageEntity you can directly get Entity Record ID by
postMessageEntity.ID
rather than Guid RecordID = ((Guid)postMessageEntity.Attributes["new_booksid"]);
Here your code does nothing more than create empty object of type Entity new_books.
You have not set Priamry name field of entiy or any other. Also you have not created a record, you should use
Entity created_book = new Entity("new_books")
service.Create(created_book);
Below you are trying to fecth Record from Entity new_books based on postMessageEntity.Id
You should check postMessageEntity logical name is same as created_book.LogicalName and then use postMessageEntity.ID rather than RecordID
created_book = service.Retrieve(created_book.LogicalName, RecordID, new ColumnSet(true));
In the plugin pipeline you can actually add, modify and even remove attributes in the entity object on the fly. This must be done before the main operations take place: in the prevalidation or in the preoperation stage.
So, your code can be simplified like this:
public void Execute(IServiceProvider serviceProvider)
{
var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
Debug.Assert(context.Stage <= 20); // This only works in prevalidation and preoperation stages.
var book = (Entity)context.InputParameters["Target"]; // For message Create a Target parameter is always available.
// Using GetAttributeValue is more safe, because when price is not set, the attribute will not be available in the collection.
decimal? price = book.GetAttributeValue<Money>("new_price")?.Value;
if (price.HasValue)
book["new_price"] = new Money(price.Value * 1.1M);
}
In the synchronous post create stage you are still in a database transaction. At that point the record is created, but not yet committed.
The ID of the record created can be found in the OutputParameters collection. You can pick it up like this:
var recordId = (Guid)context.OutputParameters["id"];
There is no need to do checks on the context object. When your plugin is registered properly, all items you would expect to be available will be there. If not, a proper exception log will be your best friend. Just add a generic exception handler responsible for writing the error context to the plugin trace log.
In CRM when emails arrive and have the tracking token in them they automatically set the regarding field to be the incident (or whatever they relate to)
Unfortunately the Record wall isn't updated with this info so even if you are following the case nothing alerts you to the new activity.
I want to write a plugin on email or incident (or both) that updates the record wall and creates a task to follow up on that email with in 3 days.
I'm looking at the SDK and I can't see what the appropriate event in the pipe line would be to work out when an email is/has its regarding field set on arrival in the CRM.
The CRM email creation life-cycle is not well described in the documentation. [shakes fist]
Extra things that are bothering me
I can't seem to include a reference to get a strongly typed Email, Post or Case (driving me crazy)
Testing this is really hard (harder than it should be)
EDIT Here is my current code
namespace Assembly.Plugins
{
using System;
using System.ServiceModel;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
/// <summary>
/// PostEmailDeliverIncoming Plugin.
/// </summary>
public class PostEmailDeliverIncoming : Plugin
{
/// <summary>
/// Initializes a new instance of the <see cref="PostEmailDeliverIncoming"/> class.
/// </summary>
public PostEmailDeliverIncoming()
: base(typeof(PostEmailDeliverIncoming))
{
RegisteredEvents.Add(new Tuple<int, string, string, Action<LocalPluginContext>>(40, "DeliverIncoming", "email", ExecutePostEmailDeliverIncoming));
// Note : you can register for more events here if this plugin is not specific to an individual entity and message combination.
// You may also need to update your RegisterFile.crmregister plug-in registration file to reflect any change.
}
protected void ExecutePostEmailDeliverIncoming(LocalPluginContext localContext)
{
if (localContext == null)
{
throw new ArgumentNullException("localContext");
}
//Extract the tracing service for use in debugging sandboxed plug-ins.
ITracingService tracingService = localContext.TracingService;
// Obtain the execution context from the service provider.
IPluginExecutionContext context = localContext.PluginExecutionContext;
// Obtain the organization service reference.
var service = localContext.OrganizationService;
// The InputParameters collection contains all the data passed in the message request.
if (!context.InputParameters.Contains("Target") || !(context.InputParameters["Target"] is Entity))
return;
// Obtain the target entity from the input parmameters.
var target = (Entity)context.InputParameters["Target"];
// Verify that the target entity represents an account.
// If not, this plug-in was not registered correctly.
if (target.LogicalName != "email")
return;
if((string)target["direction"] != "Incoming")
return;
if (target["regardingobjectid"] == null)
return;
try
{
// if its not a case I don't care
var incident = service.Retrieve("incident", (Guid)target["regardingobjectid"], new ColumnSet(true));
if (incident == null)
return;
var post = new Entity("post");
post["regardingobjectid"] = target["regardingobjectid"];
post["source"]=new OptionSetValue(0);
post["text"] = String.Format("a new email has arrived.");
// Create the task in Microsoft Dynamics CRM.
tracingService.Trace("FollowupPlugin: Creating the post.");
service.Create(post);
// Create a task activity to follow up with the account customer in 7 days.
var followup = new Entity("task");
followup["subject"] = "Follow up incoming email.";
followup["description"] = "An email arrived that was assigned to a case please follow it up.";
followup["scheduledstart"] = DateTime.Now.AddDays(3);
followup["scheduledend"] = DateTime.Now.AddDays(3);
followup["category"] = context.PrimaryEntityName;
// Refer to the email in the task activity.
if (context.OutputParameters.Contains("id"))
{
var regardingobjectid = new Guid(context.OutputParameters["id"].ToString());
followup["regardingobjectid"] = new EntityReference("email", regardingobjectid);
}
// Create the task in Microsoft Dynamics CRM.
tracingService.Trace("FollowupPlugin: Creating the task activity.");
service.Create(followup);
}
catch (FaultException<OrganizationServiceFault> ex)
{
throw new InvalidPluginExecutionException("An error occurred in the FollupupPlugin plug-in.", ex);
}
catch (Exception ex)
{
tracingService.Trace("FollowupPlugin: {0}", ex.ToString());
throw;
}
}
}
}
I've just been fighting with this exact same issue and came across this post. I thought I'd post the solution for you (if you still need it) and anyone else who comes across the issue in the future.
Here's the solution I arrived at:
- Using the Plugin Registration Tool register a New Image on the appropriate step( Stage = "40", MessageName = "DeliverIncoming")
- Set the New Image to be a Post Image
- In your plugin fetch the Post Image's entity ID:
Guid emailID = context.PostEntityImages["PostImage"].Id;
Entity emailFromRetrieve = localContext.OrganizationService.Retrieve(
"email",
emailID,
new Microsoft.Xrm.Sdk.Query.ColumnSet(true));
Email email = emailFromRetrieve.ToEntity<Email>();
if (email.RegardingObjectId == null)
{
return;
}
var regardingObject = email.RegardingObjectId;
Hope this helps!
I'm actually working on a very similar plugin at the moment. Mine creates a custom entity upon arrival of an email addressed to a certain email address. It also associates the incoming email with that new record via the Regarding field. I've added a Pre-Operation step on Create of Email and it works great, including incoming email from the router.
What I'm not sure of is when CRM fills in the Regarding field. You might look at Post-Operation and see if it is set there?
One interesting caveat regarding the Regarding field (haha!): Unlike single lookup fields, the Regarding object's name is actually stored in the ActivityPointer table, so when you update the Regarding field, be sure to set the Name on the EntityReference. If you don't, the Regarding lookup will still have a clickable icon but there won't be any text. I do it like this:
email.RegardingObjectId = [yourentity].ToEntityReference();
email.RegardingObjectId.Name = email.Subject;
Hope that helps!
I ended up doing this in a workflow on the email entity
Steps
Create new workflow, I called it 'incoming email workflow'
Scope is Organisation
Choose Email as the entity and check 'Record field changes'
Add a step that checks Regarding (Case):Case Contains Data
if true:
Add a step that creates a Post
Edit the properties in the Post
Text : This case has had {Direction(E-mail)} email activity from {From(E-mail)}
Source : Auto Post
Regarding : {Regarding(E-mail)}
Add a step that creates a Task
Edit the properties in the Task
Subject : Follow up {Subject(E-mail)}
Regarding : {Regarding(E-mail)}
Try to use the following code:
if ((bool)entity["directioncode"] == false)
Instead of your code:
if((string)target["direction"] != "Incoming")
i have developing project in c# for creating a user in AD.
i create a user and i want to create a attribute,like "mobilenumber"for this user.
when,i create this,the below error will occured.
here my code.
if (userDetails.GetUnderlyingObjectType() == typeof(DirectoryEntry))
{
dEntry = (DirectoryEntry)userDetails.GetUnderlyingObject();
if (User.UsrPassword != null && User.UsrPassword.Trim() != "")
{
if (dEntry.Properties.Contains("mobilenumber"))
{
Console.WriteLine("mobilenumberAttribute:Already created");
dEntry.Properties["mobilenumber"][0] = User.UsrPassword;
dEntry.CommitChanges();
}
else
{
Console.WriteLine("mobilenumber Attribute: Adding");
dEntry.Properties["mobilenumber"].Add(User.UsrPassword);
dEntry.CommitChanges();
}
userDetails.Save();
result = true;
}
}
The requested operation did not satisfy one or more constraints associated with the class of the object. (Exception from HRESULT: 0x80072014)
How can i resolve this?
Create an attribute? You mean like extending the schema? You can't do that by just adding it to an object. As you can see here, there is no such attribute as "mobilenumber". Maybe you want otherMobile (Phone-Mobile-Other) or mobile (Phone-Mobile-Primary)?
What are you trying to do? Why keep a copy of the password in the user object. If the user changes it, your copy will not be updated. If you need it to somehow inform the user, do something different like infoming his supervisor... Just a thought.
I am using nopcommerce for my web shop and I am using Tasks that are getting information from an external system when an order has been shipped. When it is shipped I want to capture the payment and then set it as shipped. However, I keep getting EF errors. Any way to get around this for now? I need to have it up and running
An entity object cannot be referenced by multiple instances of IEntityChangeTracker.
See my code below:
int orderId = PBSManager.GetOrderIdByCustomOrderNumber(customOrderNumber);
NopObjectContext db = ObjectContextHelper.CurrentObjectContext;
Order order = db.Orders.SingleOrDefault(c => c.OrderId == orderId);
//Incorrect order id
if (order == null)
{
//Skip this one if we cannot find the id
continue;
}
if (OrderManager.CanCapture(order))
{
string error = string.Empty;
OrderManager.Capture(order, ref error);
if (!string.IsNullOrEmpty(error))
{
PBSManager.HandleCaptureError(order, error);
return;
}
}
if (OrderManager.CanShip(order))
{
OrderManager.Ship(order.OrderId, true);
}
I am just guessing that probably you are creating another context in the OrderManager class. You should use the same context.
Can this link be of any help
Multiple instances of context
Doesn't nopCommerce store the current context in the HttpContext, have you tried looking for it in there?
Im trying to update "StatE Code" (Active|Inactive) to Active through the CRM web service on a product in the database.
...
crmProduct.statecode = new ProductStateInfo() { Value = ProductState.Active };
//crmProduct.statuscode = new Status() { Value = 1 };
crmProduct.name = "...";
service.Update(crmProduct);
It seem to work okay, I get no errors and the name changes, but its still Inactive!
When trying to set "StatUS Code" as well to Active, I get an error saying I cant set status to Active when state is Inactive... but Im setting both to Active at the same time... hmmmm.. dont now whats wrong here...
Any clues?
Setting the state code in an entity has no effect when you save it. You must use an appropriate SetState request. As Matt said, for dynamic entities this is the SetStateDynamicEntityRequest. In your case I am assuming you are using a "product" object, so you need to use the SetStateProductRequest class.
var request = new SetStateProductRequest()
{
EntityId = [GUID of product],
ProductState = ProductState.Active,
ProductStatus = -1
}
var response = (SetStateProductResponse)crmService.Execute(request);
Check out this link: http://msdn.microsoft.com/en-us/library/bb958061.aspx
The -1 for the ProductStatus tells CRM to use to appropriate default statuscode value for the statecode.
You have to use the SetStateDynamicEntityRequest to update the state of a record. You can update the statuscode using the regular update message, but only if the code you're updating to is in the same state that the record is currently in, as you've found.