Error in getting Contract ID from Contract template - acumatica

I was trying to setup and activate contract while releasing invoice after creating Sales Order.
But everytime I try to release, I get this error.
The message reads as follows:
Inserting 'Contract' record raised at least one error. Please review the errors. Error: 'Contract ID' cannot be empty.
But before that I have made Contract template and a Customer Contract for the Customer as well which uses the Contract template.
Here is the code that is raising this error.
private Contract CreateActivateContract(ContractMaint contractMaint, DateTime? invoiceDate, int? customerID, int? customerLocationID, string simCardID, string phoneNumber, CTBillEngine engine)
{
contractMaint.Clear();
//initialize new contract
Contract contract = (Contract)contractMaint.Contracts.Cache.CreateInstance();
contract = contractMaint.Contracts.Insert(contract);
//look up contract template id
Contract template = PXSelect<Contract,
Where<Contract.isTemplate, Equal<boolTrue>,
And<Contract.contractCD, Equal<Required<Contract.contractCD>>>>>
.Select(Base, "SIMCARD");
if(template==null)
{
throw new PXException("The SIMCARD contract template was not found.");
}
contract.TemplateID = template.ContractID;
contract.CustomerID = customerID;
contract = contractMaint.Contracts.Update(contract);
contract.LocationID = customerLocationID;
contract.StartDate = invoiceDate;
contract.ActivationDate = invoiceDate;
contract = contractMaint.Contracts.Update(contract);
//store simcardid and phone number into the contract attributes
foreach(CSAnswers attribute in contractMaint.Answers.Select())
{
switch(attribute.AttributeID)
{
case "SIMCARDID":
attribute.Value = simCardID;
contractMaint.Answers.Update(attribute);
break;
case "PHONENUM":
attribute.Value = phoneNumber;
contractMaint.Answers.Update(attribute);
break;
}
}
//save the generated contract
contractMaint.Save.Press();
//setup and actiave the contract
engine.SetupAndActivate(contract.ContractID, contract.ActivationDate);
return contract;
}
Below is the screenshot from Contract Template and Customer Contract screens.

Most likely you have auto-numbering disabled on the CONTRACT segmented key:
If that is the case, your 2 options are:
enable auto-numbering for the CONTRACT segmented key
modify the CreateActivateContract method to assign unique identifier to contract.ContactID:
Contract contract =(Contract)contractMaint.Contracts.Cache.CreateInstance();
contract.ContactID = "SOMEKEY";
contract = contractMaint.Contracts.Insert(contract);

Related

Custom Print Invoice Action on Process Screen

I am trying to have a print invoice Action on my new process screen which points to new custom report taking Customer Ref Nbr as a parameter. Any help on how to start with this?
In Acumatica redirection to another page (points to new custom report) is done by throwing redirection exceptions. For redirecting to a report page the exception you should use is 'PXReportRequiredException'.
Code for launching your custom report with parameter:
public PXAction<Customer> printInvoice;
[PXUIField(DisplayName = "Print Invoice", MapEnableRights = PXCacheRights.Select, MapViewRights = PXCacheRights.Select)]
public virtual IEnumerable PrintInvoice(PXAdapter adapter)
{
Customer customer = [fetch desired customer record here];
if (customer != null && customer.RefNbr != null)
{
// Add your report parameters to a Dictionary<string, string> collection.
// The dictionary key is the parameter name as shown in the report editor.
// The dictionary value is the value you assign to that parameter.
Dictionary<string, string> parameters = new Dictionary<string, string>();
parameters["RefNbr"] = customer.RefNbr;
// Provide your custom report ReportID
string reportID = "AR641000";
// Provide a title name for your report page
string reportName = "Customer Invoice"
// Redirect to report page by throwing a PXReportRequiredException object
throw new PXReportRequiredException(parameters, reportID, reportName);
}
return adapter.Get();
}
You can look up the parameter names in the Parameters tab of the Schema Builder dialog of Acumatica Report Designer:

How to customize the sales order process to trigger an automatic "adding contract" process when sales order is successfully completed

We want to accommodate our web subscription service into Acumatica, which means we sell a service as a subscription product that has starting date and expiration date, and we want to be able to enter the sale by adding sales order and then adding/changing an extra "contract" associated to that product to handle the subscription expiration/renewal issues.
Our idea is to somehow customize the sales order process to run some kind of check automatically every time when a sales order is completed - if a subscription product is in that order, we want a process to be triggered automatically to add/update a contract based on the order information.
Could it be done through customization?
Just want to mention, I have been working with Web Service API to integrate our e-commerce with Acumatica and I know I could implement this by polling the order table and then using web service API to add contract, however, it looks to me it would be better to do this inside Acumatica through some kind of customization if it is doable.
Does anybody know if this customization could be done and how to do it if it does?
Thanks.
Edited:
Having looked responses from #Gabriel and #Hybridzz, I have tried a piece of code as below:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Avalara.AvaTax.Adapter;
using Avalara.AvaTax.Adapter.TaxService;
using PX.CCProcessingBase;
using PX.Common;
using PX.Data;
using PX.Objects.AP;
using PX.Objects.AR;
using PX.Objects.CA;
using PX.Objects.CM;
using PX.Objects.CR;
using PX.Objects.CS;
using PX.Objects.EP;
using PX.Objects.GL;
using PX.Objects.IN;
using PX.Objects.PO;
using PX.Objects.TX;
using AvaMessage = Avalara.AvaTax.Adapter.Message;
using POLine = PX.Objects.PO.POLine;
using POOrder = PX.Objects.PO.POOrder;
using PX.Objects;
using PX.Objects.SO;
using PX.Objects.CT;
namespace PX.Objects.SO
{
public class SOOrderEntry_Extension:PXGraphExtension<SOOrderEntry>
{
public delegate void PersistDelegate();
[PXOverride]
public void Persist(PersistDelegate baseMethod)
{
using (PXTransactionScope ts = new PXTransactionScope())
{
// Create, setup and activate contracts
ContractMaint contractMaint = PXGraph.CreateInstance<ContractMaint>();
CTBillEngine engine = PXGraph.CreateInstance<CTBillEngine>();
//var tranExt = PXCache<ARTran>.GetExtension<ARTranExt>(tran);
string contractCD = "1234567";
DateTime startDate = new DateTime(2015,1,1);
Contract contract = SetupActivateContract(contractMaint, contractCD, startDate , 13128,14330, engine);
}
baseMethod();
}
private Contract SetupActivateContract(ContractMaint contractMaint, string contractCD, DateTime? invoiceDate, int? customerID,
int? customerLocationID, CTBillEngine engine)
{
contractMaint.Clear();
// Initialize new contract
Contract contract = (Contract)contractMaint.Contracts.Cache.CreateInstance();
contract.ContractCD = contractCD;
contract = contractMaint.Contracts.Insert(contract);
// Lookup contract template ID
Contract template = PXSelect<Contract,
Where<Contract.isTemplate, Equal<boolTrue>, And<Contract.contractCD, Equal<Required<Contract.contractCD>>>>>
.Select(Base, "MMS");
if (template == null) throw new PXException("The MMS contract template was not found.");
// Set required fields
contract.TemplateID = template.ContractID;
contract.CustomerID = customerID;
contract = contractMaint.Contracts.Update(contract);
contract.LocationID = customerLocationID;
contract.StartDate = invoiceDate;
contract.ActivationDate = invoiceDate;
ContractMaint.SetExpireDate(contract);
contract = contractMaint.Contracts.Update(contract);
// Save generated contract
contractMaint.Save.Press();
// Setup and activate the contract
engine.SetupAndActivate(contract.ContractID, contract.ActivationDate);
return contract;
}
}
}
The code was validated and published without any problem, however, when I tried to add a sales order, I didn't see any contract being added into database as I expected. I did add some "throw exception" statements to make sure this piece of code was actually called during the sales order process, but I just don't understand why the contract wasn't added.
Please note this is the first time I tried to do customization, although I have some experiences in web service API, there could be something basic that I wasn't aware of.
Any help would be appreciated.
This topic is covered in the (yet to be published) customization training. The training is centered around a fictitious mobile phone company called "YogiFon". When release an invoice, system will check whether invoice contains an item with inventory code "SIMCARD", and setup the contract automatically as part of the release process. As part of this customization, two custom fields were added to the invoice lines, to have user input the phone number and SIM Card ID. These fields are stored with the contract attributes.
There are two graph extensions needed, one for the ARReleaseProcess graph, and another one for the SOInvoiceEntry graph. I wrote the original example, but credits goes to Ruslan Devyatko for reviewing it.
ARReleaseProcess extension:
public class ARReleaseProcess_Extension : PXGraphExtension<ARReleaseProcess>
{
public bool SetupContract = false;
public delegate void PersistDelegate();
[PXOverride]
public void Persist(PersistDelegate baseMethod)
{
// use ARDocument.Current
ARRegister invoice = (ARRegister)Base.Caches[typeof(ARRegister)].Current;
List<Contract> setupContracts = new List<Contract>();
if (SetupContract)
{
// Create, setup and activate contracts
ContractMaint contractMaint = PXGraph.CreateInstance<ContractMaint>();
CTBillEngine engine = PXGraph.CreateInstance<CTBillEngine>();
int seq = 1;
//reuse ARTran_TranType_RefNbr from ARReleaseProcess
foreach (ARTran tran in
PXSelect<ARTran,
Where<ARTran.tranType, Equal<Required<ARInvoice.docType>>,
And<ARTran.refNbr, Equal<Required<ARInvoice.refNbr>>,
And<ARTranExt.usrSIMCardID, IsNotNull,
And<ARTranExt.usrContractID, IsNull>>>>,
OrderBy<Asc<ARTran.tranType, Asc<ARTran.refNbr, Asc<ARTran.lineNbr>>>>>.
Select(Base, invoice.DocType, invoice.RefNbr))
{
// Create, setup and activate contract for a particular SOInvoice line
var tranExt = PXCache<ARTran>.GetExtension<ARTranExt>(tran);
string contractCD = String.Format("{0}{1:00}", invoice.RefNbr, seq);
Contract contract = SetupActivateContract(contractMaint, contractCD, invoice.DocDate, invoice.CustomerID,
invoice.CustomerLocationID, tranExt.UsrSIMCardID, tranExt.UsrPhoneNumber, engine);
setupContracts.Add(contract);
// Associate generated contract with the SOInvoice line
tranExt.UsrContractID = contract.ContractID;
Base.ARTran_TranType_RefNbr.Cache.Update(tran);
seq++;
}
}
baseMethod();
}
private Contract SetupActivateContract(ContractMaint contractMaint, string contractCD, DateTime? invoiceDate, int? customerID,
int? customerLocationID, string simCardID, string phoneNumber, CTBillEngine engine)
{
contractMaint.Clear();
// Initialize new contract
Contract contract = (Contract)contractMaint.Contracts.Cache.CreateInstance();
contract.ContractCD = contractCD;
contract = contractMaint.Contracts.Insert(contract);
// Lookup contract template ID
Contract template = PXSelect<Contract,
Where<Contract.isTemplate, Equal<boolTrue>, And<Contract.contractCD, Equal<Required<Contract.contractCD>>>>>
.Select(Base, "SIMCARD");
if (template == null) throw new PXException("The SIMCARD contract template was not found.");
// Set required fields
contract.TemplateID = template.ContractID;
contract.CustomerID = customerID;
contract = contractMaint.Contracts.Update(contract);
contract.LocationID = customerLocationID;
contract.StartDate = invoiceDate;
contract.ActivationDate = invoiceDate;
ContractMaint.SetExpireDate(contract);
contract = contractMaint.Contracts.Update(contract);
// Store SIM/Phone Number into attributes
foreach (CSAnswers attribute in contractMaint.Answers.Select())
{
switch (attribute.AttributeID)
{
case "SIMCARDID":
attribute.Value = simCardID;
contractMaint.Answers.Update(attribute);
break;
case "PHONENUM":
attribute.Value = phoneNumber;
contractMaint.Answers.Update(attribute);
break;
}
}
// Save generated contract
contractMaint.Save.Press();
// Setup and activate the contract
engine.SetupAndActivate(contract.ContractID, contract.ActivationDate);
return contract;
}
}
SOInvoiceEntry extension:
public class SOInvoiceEntry_Extension : PXGraphExtension<SOInvoiceEntry>
{
#region Event Handlers
protected void ARTran_RowSelected(PXCache cache, PXRowSelectedEventArgs e, PXRowSelected InvokeBaseHandler)
{
if (InvokeBaseHandler != null)
InvokeBaseHandler(cache, e);
var row = (ARTran)e.Row;
if (row == null) return;
// The SIM Card ID and the Phone Number fields are only editable when the SIMCARD item is used
// In real life you would have a flag in InventoryItem to indicate that, rather than hardcoding based on InventoryCD
InventoryItem item = (InventoryItem)PXSelectorAttribute.Select<ARTran.inventoryID>(Base.Transactions.Cache, row);
bool enableFields = item != null && item.InventoryCD.StartsWith("SIMCARD");
PXUIFieldAttribute.SetEnabled<ARTranExt.usrSIMCardID>(cache, row, enableFields);
PXUIFieldAttribute.SetEnabled<ARTranExt.usrPhoneNumber>(cache, row, enableFields);
}
#endregion
public PXAction<ARInvoice> release;
[PXUIField(DisplayName = "Release", Visible = false)]
[PXButton()]
public IEnumerable Release(PXAdapter adapter)
{
PXGraph.InstanceCreated.AddHandler<ARReleaseProcess>((graph) =>
{
// Create, setup and activate contracts while releasing SOInvoice
graph.GetExtension<ARReleaseProcess_Extension>().SetupContract = true;
});
return Base.release.Press(adapter);
}
}
You can override the Persist of the salesorder graph SOOrderEntry
[PXOverride]
public void Persist(Action persit)
{
using (PXTransactionScope ts = new PXTransactionScope())
{
persit(); // this will call base graph Persist();
//If no error the document save is completed, but still wrapped in a transaction and you can do your logic below this
}
}

How Can I Check The Status (Active/Inactive) In Acumatica Event Handler?

Can anybody tell me how to check if a Customer Status is Active or Inactive in Acumatica. I am using the following code but can't figure out what the real value for should be:
if (row != null){
if (row.Status == <status.Active>){
//DO SOMETHING
}
else{
//DO SOMETHING ELSE
}
}
With what can I replace the if I want to check if the Customer status is Active or not?
Thanks,
G
I think if you have something like :
if (row.status == Customer.status.Active)
It should work. You will need to have a using/reference for the AR objects.
You can see what the attributes are on the status field by opening the customer form in Design mode. By doing that, you can see what field in the DAC it actually is and what is available in the dropdown for that field. Here are the attributes for the status field.
new string[] { Active, Hold, CreditHold, Inactive, OneTime },
new string[] { CR.Messages.Active, CR.Messages.Hold,CR.Messages.CreditHold, CR.Messages.Inactive, CR.Messages.OneTime }) { }
}}
[PXDBString(1, IsFixed = true)]
[PXDefault(status.Active)]
[PXUIField(DisplayName = "Status")]
[status.List()]
// below are the values for the CR.Messages if you care to see
public const string Active = "Active";
public const string Hold = "On Hold";
public const string HoldPayments = "Hold Payments";
public const string Inactive = "Inactive";
public const string OneTime = "One-Time";
public const string CreditHold = "Credit Hold";
Further review of the source code shows that the customer status field is actually a field in the BAccount table called Status. It is a single character field and the value for Active = 'A', Inactive = 'I'. (there are more values)
So, if you are simply wanting to know if the customer is Active, similar to what Max posted, you would simply do this in one of the event handlers where the current row is defined as row:
if (row.status == 'A') // if customer status is Active...
{
//do something
}
There may be a constant you can use, which would be better.

PostEntityImage in update message post-operartion has no key at all

I have registered a plugin for update message of some entity (in post-operation) via CRM tool in Visual Studio and also registered post-image for that plugin like below:
and here is my code :
protected void ExecutePostOpportunityUpdate(LocalPluginContext
localContext)
{
if (localContext == null)
{
throw new ArgumentNullException("localContext");
}
// TODO: Implement your custom Plug-in business logic.
IPluginExecutionContext context = localContext.PluginExecutionContext;
Entity postImage = (Entity)context.PostEntityImages["PostImage"];
....
}
But it throws an error and says that there is no key in PostEntityImages at all. I debugged the plugin and saw that there is no key in that at all.
Would you help me please?
Well looking at your code you have to retrieve the actual entity of Opportunity: Try this
try
{
Entity postOpportunityService = (Entity)context.PostEntityImages["PostImage"];
// Opportunity service's parent opportunity lookup reference
EntityReference opportunityReference = (EntityReference)postOpportunityService.Attributes["mpc_opportunityid"];
// Columns to be retrieved for opportunity (aka. columns to be edited)
ColumnSet opportunityColumnSet = new ColumnSet(new string[] { "estimatedvalue", "mpc_estoneoffinvoicing", "mpc_estinvoicingperyear" });
// Retrieve actual opportunity entity
Entity opportunity = service.Retrieve(opportunityReference.LogicalName, opportunityReference.Id, opportunityColumnSet);
}
catch (FaultException<OrganizationServiceFault> ex) { tracingService.Trace("FaultException", ex.ToString()); }

CRM 2011: Custom Workflow Activity Output Parameters don´t show

everyone,
I'm having a problem that does not appear in the output parameter list on the right side under "Look for" underneath "Local Values", I do not understand the problem or reason for not appear, since in terms of input parameters's okay.
protected override void Execute(CodeActivityContext executionContext)
{
ITracingService tracingService = executionContext.GetExtension<ITracingService>();
//Create the context
IWorkflowContext context = executionContext.GetExtension<IWorkflowContext>();
IOrganizationServiceFactory serviceFactory = executionContext.GetExtension<IOrganizationServiceFactory>();
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
tracingService.Trace("Creating Account");
Account entity = new Account();
entity.Name = AccountName.Get<string>(executionContext);
Guid entityId = service.Create(entity);
string a = entity.Name;
AccountNameTest.Set(executionContext, a);
tracingService.Trace("Account created with Id {0}", entityId.ToString());
tracingService.Trace("Create a task for the account");
Task newTask = new Task();
newTask.Subject = TaskSubject.Get<string>(executionContext);
newTask.RegardingObjectId = new EntityReference(Account.EntityLogicalName, entityId);
Guid taskId = service.Create(newTask);
tracingService.Trace("Task has been created");
tracingService.Trace("Retrieve the task using QueryByAttribute");
QueryByAttribute query = new QueryByAttribute();
query.Attributes.AddRange(new string[] { "regardingobjectid" });
query.ColumnSet = new ColumnSet(new string[] { "subject" });
query.EntityName = Task.EntityLogicalName;
query.Values.AddRange(new object[] { entityId });
tracingService.Trace("Executing the Query for entity {0}", query.EntityName);
//Execute using a request to test the OOB (XRM) message contracts
RetrieveMultipleRequest request = new RetrieveMultipleRequest();
request.Query = query;
Collection<Entity> entityList = ((RetrieveMultipleResponse)service.Execute(request)).EntityCollection.Entities;
//Execute a request from the CRM message assembly
tracingService.Trace("Executing a WhoAmIRequest");
service.Execute(new WhoAmIRequest());
if (1 != entityList.Count)
{
tracingService.Trace("The entity list was too long");
throw new InvalidPluginExecutionException("Query did not execute correctly");
}
else
{
tracingService.Trace("Casting the Task from RetrieveMultiple to strong type");
Task retrievedTask = (Task)entityList[0];
if (retrievedTask.ActivityId != taskId)
{
throw new InvalidPluginExecutionException("Incorrect task was retrieved");
}
tracingService.Trace("Retrieving the entity from IOrganizationService");
//Retrieve the task using Retrieve
retrievedTask = (Task)service.Retrieve(Task.EntityLogicalName, retrievedTask.Id, new ColumnSet("subject"));
if (!string.Equals(newTask.Subject, retrievedTask.Subject, StringComparison.Ordinal))
{
throw new InvalidPluginExecutionException("Task's subject did not get retrieved correctly");
}
//Update the task
retrievedTask.Subject = UpdatedTaskSubject.Get<string>(executionContext);
service.Update(retrievedTask);
}
}
//
[Input("Name conta")]
[Default("testv01")]
public InArgument<string> AccountName { get; set; }
[Input("Task")]
[Default("testv01")]
public InArgument<string> TaskSubject { get; set; }
[Input("Update task")]
[Default("testUPDATED:v01}")]
public InArgument<string> UpdatedTaskSubject { get; set; }
[Output("Account ID Guid")]
[Default("testUPDATED:v01")]
public OutArgument<string> AccountNameTest { get; set; }
Ok, problem solved, just restart IIS to assume the fields, or through version change. The problem was the update of the plugin, this also happens with workflows. According to CRM 4.0 i realized this situation does not happen in CRM 4.0.
Even though this Question is already answered I wanted to share two Cases where this solution didn't work (even in a recent Version of CRM):
Case 1
Having choosen Input-Parameter-Names that contained german Umlauts (äöüß).
IIS-restart did not help.
Choosing Names without Umlauts solved the Problem for me.
Case 2
We recently also had a case where a normal In-Argument did not show up, even after restarting the entire Maschine CRM was running on. The Solution was not to obvious:
Open PluginRegistrationTool from SDK
Choose the Assembly that contains your CWA
Choose your CWA
Hit the Save-Button in the Properties-Tab of your CWA

Resources