I am creating a payables invoices using Web Services on GP2013. Optionally, my users can provide line item distributions. I can create invoices, but unless I modify the CreatePayablesInvoice policy to "Distributions Will Be Provided" in the dynamics security console, the invoice gets both the system provided distributions as well as the distribution lines I am creating. I want to have the ability to provide distributions if necessary, otherwise I want the system to handle it.
The documentation suggests I should be able to alter the policy in code, but when I get the policy object back from GetPolicyByOperation, the Behaviors array is empty. I have tried creating the behavior manually in code and it doesn't alter what happens when the invoice is created. The only thing that impacts the result is editing the property in the security console.
My code for altering the the policy is below:
payablesInvoiceCreatePolicy = wsDynamicsGP.GetPolicyByOperation("CreatePayablesInvoice", context);
BehaviorKey bk = new BehaviorKey();
bk.Id = new Guid("e476a157-ecf0-4dae-8cef-317dd2cfbe41");
Behavior b = new Behavior();
b.Key = bk;
BehaviorOption opt0 = new BehaviorOption();
opt0.Key = new BehaviorOptionKey();
opt0.Key.Id = 0;
opt0.Name = "Distributions Will Be Provided";
BehaviorOption opt1 = new BehaviorOption();
opt1.Key = new BehaviorOptionKey();
opt1.Key.Id = 1;
opt1.Name = "Automatically Create Distributions";
b.Options = new BehaviorOption[] { opt0, opt1 };
b.SelectedOption = b.Options[1];
payablesInvoiceCreatePolicy.Behaviors = new Behavior[]{b};
wsDynamicsGP.CreatePayablesInvoice(payablesInvoice, context, payablesInvoiceCreatePolicy);
Documentation seems to be sparse on what should or shouldn't work here. I have to assume I should be able to update the policy as I see fit at runtime based on whether or not my user has decided to provide line item distributions.
Does anyone know what I am missing?
Yes. It took me 2 days to fix this myself. After adding the behaviour to the policy object UpdatePolicy before calling the create invoice as below:
wsDynamicsGP.UpdatePolicy(payablesInvoiceCreatePolicy, new RoleKey { Id = "00000000-0000-0000-0000-000000000000" }, context)
wsDynamicsGP.CreatePayablesInvoice(payablesInvoice, context, payablesInvoiceCreatePolicy);
Note that you selected the behavior that automatically creates distribution lines on invoice creation. The behavior's Internal property also must be set to true.
Here is a working example also utilizing FlowerKing's answer:
BehaviorKey bk = new BehaviorKey();
bk.Id = new Guid("e476a157-ecf0-4dae-8cef-317dd2cfbe41");
bk.PolicyKey = payablesInvoiceCreatePolicy.Key;
Behavior b = new Behavior();
b.Key = bk;
b.Internal = true;
BehaviorOption opt0 = new BehaviorOption();
opt0.Key = new BehaviorOptionKey();
opt0.Key.Id = 0;
opt0.Name = "Distributions Will Be Provided";
BehaviorOption opt1 = new BehaviorOption();
opt1.Key = new BehaviorOptionKey();
opt1.Key.Id = 1;
opt1.Name = "Automatically Create Distributions";
b.Options = new BehaviorOption[] { opt0, opt1 };
b.SelectedOption = b.Options[0];
policy.Behaviors = new Behavior[] { b };
client.UpdatePolicy(payablesInvoiceCreatePolicy, new RoleKey { Id = "00000000-0000-0000-0000-000000000000" }, context);
client.CreatePayablesInvoice(payablesInvoice, context, payablesInvoiceCreatePolicy);
Related
implementing Google Billing Library 5.0. While I can get from productDetails list the base plan id and relative attributes (formatted pricing etc), I am not able to get the offer id of the promotion set for this same base plan. I run out of tests to debug the problem, as such any help on this would be very much appreciated!!
Below you can check my code when querying data from the returned productDetails list in the onProductDetailsResponse method.
subs_BasePlan_ID_str array gets populated correclty (with all product ids), subs_Offer_ID_str array keeps at the declared state (all null elements) and no trace of it in the structure of the JSON object returned with productDetails.
I am also providing a screen showing the setup of base plan + offer
baseplan setup with discounted price offer
billingClient.queryProductDetailsAsync(
queryProductDetailsParams,
new ProductDetailsResponseListener() {
public void onProductDetailsResponse(BillingResult billingResult,
List\<ProductDetails\> productDetailsList) {
// check billingResult
// process returned productDetailsList
if (runMode.equals(consts.BILLING_RUN_MODE_OFFER)) {
String[] subs_BasePlan_ID_str = new String[4];
String[] subs_Offer_ID_str = new String[4];
String[] subs_BasePlan_FormattedPrice_str = new String[4];
String[] subs_Offer_FormattedPrice_str = new String[4];
for (int i = 0; i < subs_BasePlan_FormattedPrice_str.length; i++) {
// Get sub plan ID
subs_BasePlan_ID_str[i] = productDetailsList.get(i).getSubscriptionOfferDetails().get(0).getBasePlanId();
subs_Offer_ID_str[i] = productDetailsList.get(i).getSubscriptionOfferDetails().get(0).getOfferId();
}
I get the below error when running a custom workflow action that uses JSON.NET to serialize a dynamics object to JSON.
Is there a restriction on using reflection in CRM Dynamics customer workflow activities / plugins?
Is it because I am using dynamic variables?
System.TypeAccessException: Attempt by method
'DynamicClass.CallSite.Target(System.Runtime.CompilerServices.Closure,
System.Runtime.CompilerServices.CallSite, System.Object,
System.String)' to access type
'Newtonsoft.Json.Linq.JObject+JObjectDynamicProxy' failed. at
CallSite.Target(Closure , CallSite , Object , String ) at
WSWA.CRM.Logic.MyobIntegrationLogic.CreateInvoice(Boolean retry) at
WSWA.CRM.Workflows.MyobJob.MyobIntegrationTester.Execute(CodeActivityContext
context)
dynamic account = new JObject();
account.UID = GetAccount("Undeposited Funds Account");
dynamic job = new JObject();
job.UID = GetJob("JFC Interiors");
dynamic gstTaxCode = new JObject();
gstTaxCode.UID = GetTaxUidByCode("GST");
dynamic customer = new JObject();
customer.UID = GetCustomerUid("Bar001.test");
dynamic line1 = new JObject();
line1.Total = 22.55;
line1.Account = account;
line1.Job = job;
line1.TaxCode = gstTaxCode;
dynamic line2 = new JObject();
line1.Total = 23.55;
line1.Account = account;
line1.Job = job;
line1.TaxCode = gstTaxCode;
var lines = new JArray();
lines.Add(line1);
lines.Add(line2);
dynamic invoice = new JObject();
invoice.Date = DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss");
invoice.Customer = customer;
invoice.CustomerPurchaseOrderNumber = "PO Number";
invoice.Number = "INV-1000";
invoice.Lines = lines;
var content = new StringContent(contact.ToString());
content.Headers.ContentType = MediaTypeHeaderValue.Parse("text/json");
var responseTask = httpClient.PostAsync(url, content);
Task.WaitAll(responseTask);
You can't use dynamic types in Dynamics (ironic isn't it?) CRM Sandboxed plugins. You can use reflection, as long as whatever you're reflecting is publicly available. i.e. You can use reflection to get a list of public properties, but you can't get a list of private fields.
You can always farm your work out to an Azure service and do whatever you'd like there.
There are known issues with dynamic types and using NuGet packages like Microsoft.AspNet.WebApi.Client.
Could you try with a WebClient?
More info here
I am trying to connect to the AX web services. What I will do it to fetch the right price given a product and a customer.
I realised the right webservice to use is PriceListServiceClient and I am able to log in to it using windows authentication, but I cannot retrieve any data from it.
Here is my code:
PriceListServiceClient priceListServiceClient = new PriceListServiceClient();
priceListServiceClient.ClientCredentials.Windows.ClientCredential.UserName = "yyy";
priceListServiceClient.ClientCredentials.Windows.ClientCredential.Password = "zzz!";
priceListServiceClient.ClientCredentials.Windows.ClientCredential.Domain = "xxx";
CriteriaElement[] criteriaElement = new CriteriaElement[1];
criteriaElement[0] = new CriteriaElement();
criteriaElement[0].DataSourceName = "SalesPrice";
criteriaElement[0].FieldName = "ItemId";
criteriaElement[0].Operator = Operator.NotEqual;
criteriaElement[0].Value1 = "5637153175";
QueryCriteria queryCriteria = new QueryCriteria();
queryCriteria.CriteriaElement = criteriaElement;
CallContext callContext = new CallContext();
var found = priceListServiceClient.find(callContext, queryCriteria);
Console.WriteLine(found.Currency);
priceListServiceClient.Close();
Any idea about why this is happening?
Try filling in the properties in the CallContext (company and language).
new CallContext { Company = "zzz", Language = "nl" };
I found the answer here: http://community.dynamics.com/ax/f/33/p/118741/246784.aspx
The Ax class for Price List document is AxPriceDiscTmpPrintout Class. This class wraps the TmpPriceDiscPrintout table, which is a TMP table. That's why you are not getting anything in return.
My goal is to get a listing of all the DataStores in a specific datacenter. I'm able to list all of the Hosts, and VM's, but not the Datastores, and I don't understand why (I'm still learning the API's). Any insight would be appreciated.
Here's the code for grabbing all of the VM's (this works as expected):
public List<VM> getVMsInDatacenter(String datacenter, IEnumerable<String> properties)
{
List<VM> VMs = null;
this.joinConnection((appUtil) =>
{
var svcUtil = appUtil.getServiceUtil();
var dcMoRef = svcUtil.GetDecendentMoRef(null, "Datacenter", datacenter);
var typeinfo = buildTypeInfo("VirtualMachine", properties.ToList());
VMs = buildVMsFromObjectContent(svcUtil.GetContentsRecursively(null, dcMoRef, typeinfo, true));
});
return VMs;
}
Here is the analogous code for the Datastore (which does not work as expected):
public List<DataStore> getDataStoresInDatacenter(String datacenter, IEnumerable<String> properties)
{
List<DataStore> DataStores = null;
this.joinConnection((appUtil) =>
{
var svcUtil = appUtil.getServiceUtil();
var dcMoRef = svcUtil.GetDecendentMoRef(null, "Datacenter", datacenter);
var typeinfo = buildTypeInfo("Datastore", properties.ToList());
DataStores = buildDataStoresFromObjectContent(svcUtil.GetContentsRecursively(null, dcMoRef, typeinfo, true));
});
return DataStores;
}
appUtil is an instantiation of the AppUtil class that came with the VIM SDK samples. It houses functionality for connecting, querying, etc.
joinConnection is a method for connecting, or re-using a connection if we've already connected.
If there are any other questions about the code, please let me know.
Also, if there's a better way, I'd like to know that too :)
Found the problem. The method getContentsRecursively is calling a method called 'buildFullTraversal' that builds a traversal/selection spec. This method was not adding a traversal for the datastore. I added one that like so:
TraversalSpec vmToDs = new TraversalSpec();
vmToDs.name = "vmToDs";
vmToDs.type = "VirtualMachine";
vmToDs.path = "datastore";
HToVm.skip = false;
HToVm.skipSpecified = true;
And then I modified the visitFolders traversal like so:
// Recurse through the folders
TraversalSpec visitFolders = new TraversalSpec();
visitFolders.name = "visitFolders";
visitFolders.type = "Folder";
visitFolders.path = "childEntity";
visitFolders.skip = false;
visitFolders.skipSpecified = true;
visitFolders.selectSet = new SelectionSpec[] { new SelectionSpec(), new SelectionSpec(), new SelectionSpec(), new SelectionSpec(), new SelectionSpec(), new SelectionSpec(), new SelectionSpec(), new SelectionSpec() };
visitFolders.selectSet[0].name = "visitFolders";
visitFolders.selectSet[1].name = "dcToHf";
visitFolders.selectSet[2].name = "dcToVmf";
visitFolders.selectSet[3].name = "crToH";
visitFolders.selectSet[4].name = "crToRp";
visitFolders.selectSet[5].name = "HToVm";
visitFolders.selectSet[6].name = "rpToVm";
visitFolders.selectSet[7].name = "vmToDs";
return new SelectionSpec[] { visitFolders, dcToVmf, dcToHf, crToH, crToRp, rpToRp, HToVm, rpToVm, vmToDs };
Now, calls to getContentsRecursively will also include the datastores that belong to a VM, so the method in the question will works as expected.
I'm trying to use the following code to create a new SalesInvoice based on an existing SalesOrder:
SalesInvoice invoice = new SalesInvoice();
invoice.DocumentTypeKey = new SalesDocumentTypeKey { Type = SalesDocumentType.Invoice };
invoice.CustomerKey = originalOrder.CustomerKey;
invoice.BatchKey = originalOrder.BatchKey;
invoice.Terms = new SalesTerms { DiscountTakenAmount = new MoneyAmount { Value = 0, Currency = "USD", DecimalDigits = 2 }, DiscountAvailableAmount = new MoneyAmount { Value = 0, Currency = "USD", DecimalDigits = 0 } };
invoice.OriginalSalesDocumentKey = originalOrder.Key;
List<SalesInvoiceLine> lineList = new List<SalesInvoiceLine>();
for (int i = 0; i < originalOrder.Lines.Length; i++)
{
SalesInvoiceLine line = new SalesInvoiceLine();
line.ItemKey = originalOrder.Lines[i].ItemKey;
line.Key = new SalesLineKey { LineSequenceNumber = originalOrder.Lines[i].Key.LineSequenceNumber; }
SalesLineLot lot = new SalesLineLot();
lot.LotNumber = originalOrder.Lines[i].Lots[0].LotNumber;
lot.Quantity = new Quantity { Value = 2200 };
lot.Key = new SalesLineLotKey { SequenceNumber = originalOrder.Lines[i].Lots[0].Key.SequenceNumber };
line.Lots = new SalesLineLot[] { lot };
line.Quantity = new Quantity { Value = 2200 };
lineList.Add(line);
}
invoice.Lines = lineList.ToArray();
DynamicsWS.CreateSalesInvoice(invoice, DynamicsContext, DynamicsWS.GetPolicyByOperation("CreateSalesInvoice", DynamicsContext));
When executed, I receive the following error:
SQL Server Exception: Operation expects a parameter which was not supplied.
And the more detailed exception from the Exception Console in Dynamics:
Procedure or function 'taSopLotAuto' expects parameter '#I_vLNITMSEQ',
which was not supplied.
After a considerable amount of digging through Google, I discovered a few things.
'taSopLotAuto' is an eConnect procedure within the Sales Order Processing component that attempts to automatically fill lots. I do not want the lots automatically filled, which is why I try to fill them manually in the code. I've also modified the CreateSalesInvoice policy from Automatic lot fulfillment to Manual lot fulfillment for the GP web services user, but that didn't change which eConnect procedure was called.
'#I_vLNITMSEQ' refers to the LineSequenceNumber. The LineSequenceNumber and SequenceNumber (of the Lot itself) must match. In my case they are both the default: 16384. Not only is this parameter set in the code above, but it also appears in the SOAP message that the server attempted to process - hardly "not supplied."
I can create an invoice sans line items without a hitch, but if I add line items it fails. I do not understand why I am receiving an error for a missing parameter that is clearly present.
Any ideas on how to successfully create a SalesInvoice through Dynamics GP 10.0 Web Services?
Maybe you mess to add the line key to the lot:
lot.Key = new SalesLineKey();
lot.Key.SalesDocumentKey = new SalesDocumentKey();
lot.Key.SalesDocumentKey.Id = seq.ToString();