Acumatica, how to check action is completed - acumatica

I have a graph call action Create Shipment as below
var soshipmentEntry = PXGraph.CreateInstance<SOShipmentEntry>();
var confirmShipment =(soshipmenEntry.Actions["action"].GetState(null) as PXButtonState).Menus.FirstOrDefault(p => p.Command == "Confirm Shipment");
var adapter = new PXAdapter(new DummyView(Base, Base.Document.View.BqlSelect,
new List<object> { Base.Document.Current }))
{
Menu = confirmShipment.Command
};
soshipmentEntry.Actions["action"].PressButton(adapter);
And i don't know when action is completed. How can i get status like GetProcessStatus?

I propose you to consider folder where acumatica is installed. There is a code graph SOShipmentEntry.cs.
It has interesting code:
switch (actionID)
{
case 1:
{
.
.
.
Save.Press();
PXAutomation.CompleteAction(this);
PXLongOperation.WaitCompletion(this.UID);
PXLongOperation.ClearStatus(this.UID);
}
So I recommend you to give a try to PXLongOperation.WaitCompletion

Related

docusignapi | place to sign are lost when updating an envelope

my UI when create and wh
My code to update:
var recipients = envelopesApi.ListRecipients(accountId, envelopeId, new EnvelopesApi.ListRecipientsOptions { includeTabs = true.ToString() });
foreach (var doc in documents)
{
var retornoDelete = envelopesApi.DeleteDocuments(accountId, envelopeId, new EnvelopeDefinition { Documents = new List<Document> { doc } });
}
var resultUpdateDocuments = envelopesApi.UpdateDocuments(accountId, envelopeId, new EnvelopeDefinition { Documents = envelope.Documents, Recipients = recipients });
var resultUpdateRecipients = envelopesApi.UpdateRecipients(accountId, envelopeId, recipients, new EnvelopesApi.UpdateRecipientsOptions { resendEnvelope = "true" });
I have try this link bellow, but not work for me:
DocuSign: Signer Tabs are lost when updating an envelope
Im not sure what you are trying to do in this code, but you are both updating the recipient and the document. It make a lot more sense to create a new envelope at this point, instead of 3 API calls - you can make just one.
The reason you don't see the tab (SignIn) is because you are updating the recipients and documents again and that won't by itself update the tabs. You need to make yet another API call for this (https://developers.docusign.com/docs/esign-rest-api/reference/envelopes/envelopedocumenttabs/update/)
I recommend you reconsider this flow to just create a new envelope with all the information in one call. But if you need to do it this way - you'll need another API call.
the Create Tabs work here, i guess this will sufficient
foreach (var item in recipients.Signers)
{
try
{
var tabsRecipient = envelope.Recipients.Signers.Where(x => x.Email == item.Email).Select(x => x.Tabs).First();
var resultUpdateTabs = envelopesApi.CreateTabs(accountId, envelopeId, item.RecipientId, tabsRecipient);
}
catch (Exception ex)
{
// this recipient have a constraint
}
}

How to set output contexts while creating a intent in dialogflow PHP?

I am trying to set output context to a particular intent via v2 create intent API.
Please check my code.
use Google\Cloud\Dialogflow\V2\SessionsClient;
use Google\Cloud\Dialogflow\V2\TextInput;
use Google\Cloud\Dialogflow\V2\QueryInput;
use Google\Cloud\Dialogflow\V2\IntentsClient;
use Google\Cloud\Dialogflow\V2\Intent_TrainingPhrase_Part;
use Google\Cloud\Dialogflow\V2\Intent_TrainingPhrase;
use Google\Cloud\Dialogflow\V2\Intent_Message_Text;
use Google\Cloud\Dialogflow\V2\Intent_Message;
use Google\Cloud\Dialogflow\V2\Intent;
use Google\Cloud\Dialogflow\V2\Context;
use Google\Cloud\Dialogflow\V2\ContextsClient;
private function intent_create(){
putenv('GOOGLE_APPLICATION_CREDENTIALS='.getcwd() . '/strive_stage.json');
$intentsClient = new IntentsClient();
/** Create Intent **/
$disaplayName = "Where is Goa";
$utterances = ["Goa", "Where is Goa"];
// prepare training phrases for intent
$trainingPhrases = [];
foreach ($utterances as $trainingPhrasePart) {
$part = new Intent_TrainingPhrase_Part();
$part->setText($trainingPhrasePart);
// create new training phrase for each provided part
$trainingPhrase = new Intent_TrainingPhrase();
$trainingPhrase->setParts([$part]);
$trainingPhrases[] = $trainingPhrase;
}
$messageTexts = 'Goa is in India.';
// prepare messages for intent
$text = new Intent_Message_Text();
$text->setText([$messageTexts]);
$message = new Intent_Message();
$message->setText($text);
$createIntentObject = $intentsClient->projectAgentName(env("DIALOG_FLOW_PROJECT_ID"));
// prepare intent
$intent = new Intent();
$intent->setDisplayName($disaplayName);
$intent->setTrainingPhrases($trainingPhrases);
$intent->setMessages([$message]);
$contexts = ['test'];
foreach($contexts as $con){
$contextObj = new Context();
$contextObj->setName($con);
$contextData[] = $contextObj;
$intent->setOutputContexts($contextData);
}
// $intent->getOutputContexts('test');
//dd($intent);
$response = $intentsClient->createIntent($createIntentObject, $intent);
printf('Intent created: %s' . PHP_EOL, $response->getName());
}
I am getting error message
{
"message": "com.google.apps.framework.request.BadRequestException: Resource name does not match format 'projects/{project_id}/agent/sessions/{session_id}/contexts/{context_id}' or 'projects/{project_id}/locations/{location_id}/agent/sessions/{session_id}/contexts/{context_id}'.",
"code": 3,
"status": "INVALID_ARGUMENT",
"details": []
}
I believe the issue is with the format of storing the output context. Please help me on this.
I had the same issue. The answer is to not put in the bare name, but to have a string of a certain format made
see http://googleapis.github.io/google-cloud-php/#/docs/google-cloud/v0.131.0/dialogflow/v2/intent?method=setOutputContexts
// $projectId your project id
// $sessionId can be anything, I use $sessionId = uniqid();
$uri = "projects/$projectId/agent/sessions/$sessionId/contexts/$con";
$contextObj->setName($uri);
I can see that there are any response about this topic.
I leave here my solution for the next generations
To create a output context you need to create the correct format, for this objetive you can use Context class Google\Cloud\Dialogflow\V2\Context
private function parseoOutputContexts($contexts, $project_id, $lifespan = 5)
{
$newContexts = array();
foreach ($contexts as $context) {
$newContexts[] = new Context(
[
'name' => 'projects/' . $project_id . '/agent/sessions/-/contexts/' . $context,
'lifespan_count' => $lifespan
]
);
}
return $newContexts;
}
And you can use this function to finnaly add the output context to the object Intent.
$dialogflow_intent = new Intent();
$output_contexts = ['output_context_1'. 'output_context_2'];
$output_contexts = $this->parseOutputContexts($output_contexts, '[YOUR_PROJECT_ID]');
$dialogflow_intent->setOutputContexts($output_contexts);

How to provide custom names for page view events in Azure App Insights?

By default App Insights use page title as event name. Having dynamic page names, like "Order 32424", creates insane amount of event types.
Documentation on the matter says to use trackEvent method, but there are no examples.
appInsights.trackEvent("Edit button clicked", { "Source URL": "http://www.contoso.com/index" })
What is the best approach? It would be perfect to have some sort of map/filter which would allow to modify event name for some pages to the shared name, like "Order 23424" => "Order", at the same time to leave most pages as they are.
You should be able to leverage telemetry initializer approach to replace certain pattern in the event name with the more "common" version of that name.
Here is the example from Application Insights JS SDK GitHub on how to modify pageView's data before it's sent out. With the slight modification you may use it to change event names based on their appearance:
window.appInsights = appInsights;
...
// Add telemetry initializer
appInsights.queue.push(function () {
appInsights.context.addTelemetryInitializer(function (envelope) {
var telemetryItem = envelope.data.baseData;
// To check the telemetry item’s type:
if (envelope.name === Microsoft.ApplicationInsights.Telemetry.PageView.envelopeType) {
// this statement removes url from all page view documents
telemetryItem.url = "URL CENSORED";
}
// To set custom properties:
telemetryItem.properties = telemetryItem.properties || {};
telemetryItem.properties["globalProperty"] = "boo";
// To set custom metrics:
telemetryItem.measurements = telemetryItem.measurements || {};
telemetryItem.measurements["globalMetric"] = 100;
});
});
// end
...
appInsights.trackPageView();
appInsights.trackEvent(...);
With help of Dmitry Matveev I've came with the following final code:
var appInsights = window.appInsights;
if (appInsights && appInsights.queue) {
function adjustPageName(item) {
var name = item.name.replace("AppName", "");
if (name.indexOf("Order") !== -1)
return "Order";
if (name.indexOf("Product") !== -1)
return "Shop";
// And so on...
return name;
}
// Add telemetry initializer
appInsights.queue.push(function () {
appInsights.context.addTelemetryInitializer(function (envelope) {
var telemetryItem = envelope.data.baseData;
// To check the telemetry item’s type:
if (envelope.name === Microsoft.ApplicationInsights.Telemetry.PageView.envelopeType || envelope.name === Microsoft.ApplicationInsights.Telemetry.PageViewPerformance.envelopeType) {
// Do not track admin pages
if (telemetryItem.name.indexOf("Admin") !== -1)
return false;
telemetryItem.name = adjustPageName(telemetryItem);
}
});
});
}
Why this code is important? Because App Insights use page titles by default as Name for PageView, so you would have hundreds and thousands of different events, like "Order 123132" which would make further analysis (funnel, flows, events) meaningless.
Key highlights:
var name = item.name.replace("AppName", ""); If you put your App/Product name in title, you probably want to remove it from you event name, because it would just repeat itself everywhere.
appInsights && appInsights.queue you should check for appInsights.queue because for some reason it can be not defined and it would cause an error.
if (telemetryItem.name.indexOf("Admin") !== -1) return false; returning false will cause event to be not recorded at all. There certain events/pages you most likely do not want to track, like admin part of website.
There are two types of events which use page title as event name: PageView
and PageViewPerformance. It makes sense to modify both of them.
Here's one work-around, if you're using templates to render your /orders/12345 pages:
appInsights.trackPageView({name: TEMPLATE_NAME });
Another option, perhaps better suited for a SPA with react-router:
const Tracker = () => {
let {pathname} = useLocation();
pathname = pathname.replace(/([/]orders[/])([^/]+), "$1*"); // handle /orders/NN/whatever
pathname = pathname.replace(/([/]foo[/]bar[/])([^/]+)(.*)/, "$1*"); // handle /foo/bar/NN/whatever
useEffect(() => {
appInsights.trackPageView({uri: pathname});
}, [pathname]);
return null;
}

Can I delete a file in Acumatica via the API?

I'm creating a file in Acumatica by calling an action from the API, so that I can retrieve the file in my application.
Is it possible to delete the file via API after I'm done with it? I'd rather not have it cluttering up my Acumatica database.
Failing this, is there a recommended cleanup approach for these files?
Found examples of how to delete a file from within Acumatica, as well as how to save a new version of an existing file! The below implementation saves a new version but has the deletion method commented out. Because I built this into my report generation process, I'm not later deleting the report via API, but it would be easy to translate a deletion into an action callable by the API.
private IEnumerable ExportReport(PXAdapter adapter, string reportID, Dictionary<String, String> parameters)
{
//Press save if the SO is not completed
if (Base.Document.Current.Completed == false)
{
Base.Save.Press();
}
PX.SM.FileInfo file = null;
using (Report report = PXReportTools.LoadReport(reportID, null))
{
if (report == null)
{
throw new Exception("Unable to access Acumatica report writer for specified report : " + reportID);
}
PXReportTools.InitReportParameters(report, parameters, PXSettingProvider.Instance.Default);
ReportNode reportNode = ReportProcessor.ProcessReport(report);
IRenderFilter renderFilter = ReportProcessor.GetRenderer(ReportProcessor.FilterPdf);
//Generate the PDF
byte[] data = PX.Reports.Mail.Message.GenerateReport(reportNode, ReportProcessor.FilterPdf).First();
file = new PX.SM.FileInfo(reportNode.ExportFileName + ".pdf", null, data);
//Save the PDF to the SO
UploadFileMaintenance graph = new UploadFileMaintenance();
//Check to see if a file with this name already exists
Guid[] files = PXNoteAttribute.GetFileNotes(Base.Document.Cache, Base.Document.Current);
foreach (Guid fileID in files)
{
FileInfo existingFile = graph.GetFileWithNoData(fileID);
if (existingFile.Name == reportNode.ExportFileName + ".pdf")
{
//If we later decide we want to delete previous versions instead of saving them, this can be changed to
//UploadFileMaintenance.DeleteFile(existingFile.UID);
//But in the meantime, for history purposes, set the UID of the new file to that of the existing file so we can save it as a new version.
file.UID = existingFile.UID;
}
}
//Save the file with the setting to create a new version if one already exists based on the UID
graph.SaveFile(file, FileExistsAction.CreateVersion);
//Save the note attribute so we can find it again.
PXNoteAttribute.AttachFile(Base.Document.Cache, Base.Document.Current, file);
}
//Return the info on the file
return adapter.Get();
}
The response from Acumatica:
S-b (Screen-base) API allows clean way of downloading report generated as file. C-b (Contract-base) simply does not have this feature added. I suggest you provided feedback here: feedback.acumatica.com (EDIT: Done! https://feedback.acumatica.com/ideas/ACU-I-1852)
I think couple of workaround are:
1) use s-b using login from c-b to generate report and get as file (see example below), or
2) create another method to delete the file once required report file is downloaded. For that, you will need to pass back FileID or something to identify for deletion.
example of #1
using (DefaultSoapClient sc = new DefaultSoapClient("DefaultSoap1"))
{
string sharedCookie;
using (new OperationContextScope(sc.InnerChannel))
{
sc.Login("admin", "123", "Company", null, null);
var responseMessageProperty = (HttpResponseMessageProperty)
OperationContext.Current.IncomingMessageProperties[HttpResponseMessageProperty.Name];
sharedCookie = responseMessageProperty.Headers.Get("Set-Cookie");
}
try
{
Screen scr = new Screen(); // add reference to report e.g. http://localhost/Demo2018R2/Soap/SO641010.asmx
scr.CookieContainer = new System.Net.CookieContainer();
scr.CookieContainer.SetCookies(new Uri(scr.Url), sharedCookie);
var schema = scr.GetSchema();
var commands = new Command[]
{
new Value { LinkedCommand = schema.Parameters.OrderType, Value = "SO" },
new Value { LinkedCommand = schema.Parameters.OrderNumber, Value = "SO004425" },
schema.ReportResults.PdfContent
};
var data = scr.Submit(commands);
if(data != null && data.Length > 0)
{
System.IO.File.WriteAllBytes(#"c:\Temp\SalesOrder.pdf",
Convert.FromBase64String(data[0].ReportResults.PdfContent.Value));
}
}
finally
{
sc.Logout();
}
}
Hope this helps. Also, it would be great if you update the stackover post based on these suggestions.
Thanks
Nayan Mansinha
Lead - Developer Support | Acumatica

Extending or modifying the SharePoint Datasheet view

Has anyone discovered a way to extend or modify the functionality of the SharePoint Datasheet view (the view used when you edit a list in Datasheet mode, the one that looks like a basic Excel worksheet)?
I need to do several things to it, if possible, but I have yet to find a decent non-hackish way to change any functionality in it.
EDIT: An example of what I wish to do is to enable cascading filtering on lookup fields - so a choice in one field limits the available choices in another. There is a method to do this in the standard view form, but the datasheet view is completely seperate.
Regards
Moo
I don't think you can modify it in any non-hackish way, but you can create a new datasheet view from scratch. You do this by creating a new ActiveX control, and exposing it as a COM object, and modifying the web.config file to make reference to the new ActiveX control.
There's an example here:
Creating a custom datasheet control.
Actually, you can do this. Here is a code snippet I stripped out of someplace where I am doing just what you asked. I tried to remove specifics.
var gridFieldOverrideExample = (function (){
function fieldView(ctx){
var val=ctx.CurrentItem[curFieldName];
var spanId=curFieldName+"span"+ctx.CurrentItem.ID;
if (ctx.inGridMode){
handleGridField(ctx, spanId);
}
return "<span id='"+spanId+"'>"+val+"</span>";
}
function handleGridField(ctx, spanID){
window.SP.SOD.executeOrDelayUntilScriptLoaded(function(){
window.SP.GanttControl.WaitForGanttCreation(function (ganttChart){
var gridColumn = null;
var editID = "EDIT_"+curFieldName+"_GRID_FIELD";
var columns = ganttChart.get_Columns();
for(var i=0;i<columns.length;i++){
if(columns[i].columnKey == curFieldName){
gridColumn = columns[i];
break;
}
}
if (gridColumn){
gridColumn.fnGetEditControlName = function(record, fieldKey){
return editID;
};
window.SP.JsGrid.PropertyType.Utils.RegisterEditControl(editID, function (ctx) {
editorInstance = new SP.JsGrid.EditControl.EditBoxEditControl(ctx, null);
editorInstance.NewValue = "";
editorInstance.SetValue = function (value) {
_cellContext = editorInstance.GetCellContext();
_cellContext.SetCurrentValue({ localized: value });
};
editorInstance.Unbind = function () {
//This happens when the grid cell loses focus - hide controls here, do cleanup, etc.
}
//Below I grabbed a reference to the original 'BindToCell' function so I can prepend to it by overwriting the event.
var origbtc = editorInstance.BindToCell;
editorInstance.BindToCell = function(cellContext){
if ((cellContext.record) &&
(cellContext.record.properties) &&
(cellContext.record.properties.ID) &&
(cellContext.record.properties.ID.dataValue)){
editorInstance.ItemID = cellContext.record.properties.ID.dataValue;
}
origbtc(cellContext);
};
//Below I grabbed a reference to the original 'OnBeginEdit' function so I can prepend to it by overwriting the event.
var origbte = editorInstance.OnBeginEdit;
editorInstance.TargetID;
editorInstance.OnBeginEdit = function (cellContext){
this.TargetID = cellContext.target.ID;
/*
. . .
Here is where you would include any custom rendering
. . .
*/
origbte(cellContext);
};
return editorInstance;
}, []);
}
});
},"spgantt.js");
}
return{
fieldView : fieldView
}
})();
(function () {
function OverrideFields(){
var overrideContext = {};
overrideContext.Templates = overrideContext.Templates || {};
overrideContext.Templates.Fields = {
'FieldToOverride' : {
'View': gridFieldOverrideExample.fieldView
}
};
SPClientTemplates.TemplateManager.RegisterTemplateOverrides(overrideContext);
}
ExecuteOrDelayUntilScriptLoaded(OverrideFields, 'clienttemplates.js');
})();
Also, there are a couple of other examples out there. Sorry, I don't have the links anymore:

Resources