Whats the best way to get Samba Entity Data onto a webserver with strict network policies

Ensure you update your PHP script to the version posted above. It is writing to a file named:

Ticket_<ticketId>.txt

… and it is set to append to the file…

$fileName = "ticket_".$ticketID.".txt";
// FILE_APPEND | LOCK_EX
$fwsuccess = file_put_contents ($fileName, $data, FILE_APPEND);

I have that updated PHP Script, and this is what i get when I put through an order of a Chocolate Milkshake and a Banana Milkshake

================================================================
JSON (15):
{"TicketId":"1127","Id":"1190","OrderNumber":"129","OrderUid":"Sno3VO9kK0Oy4DCTWmGfWg","MenuItemId":"36","MenuItemName":"Banana Milkshake","PortionName":"Normal","Price":"3.50","Quantity":"1.000","PortionCount":"1","PriceTag":"","Tag":"","Taxes":"[]","OrderTags":"","OrderStates":"[{\"D\":\"\\/Date(1461280619544+1000)\\/\",\"OK\":\"000000\",\"S\":\"Submitted\",\"SN\":\"Status\",\"SV\":\"\",\"U\":1}]"}

TicketId:1127
OrderID:1190
OrderNum:129
OrderUID:Sno3VO9kK0Oy4DCTWmGfWg
------------------
TicketId:1127
Id:1190
OrderNumber:129
OrderUid:Sno3VO9kK0Oy4DCTWmGfWg
MenuItemId:36
MenuItemName:Banana Milkshake
PortionName:Normal
Price:3.50
Quantity:1.000
PortionCount:1
PriceTag:
Tag:
Taxes:[]
OrderTags:
OrderStates:[{"D":"\/Date(1461280619544+1000)\/","OK":"000000","S":"Submitted","SN":"Status","SV":"","U":1}]
================================================================

================================================================
JSON (15):
{"TicketId":"1127","Id":"1190","OrderNumber":"129","OrderUid":"Sno3VO9kK0Oy4DCTWmGfWg","MenuItemId":"36","MenuItemName":"Banana Milkshake","PortionName":"Normal","Price":"3.50","Quantity":"1.000","PortionCount":"1","PriceTag":"","Tag":"","Taxes":"[]","OrderTags":"","OrderStates":"[{\"D\":\"\\/Date(1461280619544+1000)\\/\",\"OK\":\"000000\",\"S\":\"Submitted\",\"SN\":\"Status\",\"SV\":\"\",\"U\":1}]"}

TicketId:1127
OrderID:1190
OrderNum:129
OrderUID:Sno3VO9kK0Oy4DCTWmGfWg
------------------
TicketId:1127
Id:1190
OrderNumber:129
OrderUid:Sno3VO9kK0Oy4DCTWmGfWg
MenuItemId:36
MenuItemName:Banana Milkshake
PortionName:Normal
Price:3.50
Quantity:1.000
PortionCount:1
PriceTag:
Tag:
Taxes:[]
OrderTags:
OrderStates:[{"D":"\/Date(1461280619544+1000)\/","OK":"000000","S":"Submitted","SN":"Status","SV":"","U":1}]
================================================================

Ok, yes you are correct.

Need to modify some things, but I need to play with it for a bit… it has to do with the Actions / Rules.

2 Likes

Needles about this small bug, this really is so cool, i can see so many opportunties for this. like remote reporting, customer’s can see there receipt online.

1 Like

Ah @QMcKay - if I could just bottle your knowledge & skills (1 very big bottle!) then feed it into a clone bot army :robot: - I would take over the world …

2 Likes

The problem is caused by the speed at which things happen. So our stored values are being overwritten before the data can be posted. What seems to work is to set values to Setting Names that are more unique, so we will base this on the {ORDER_UID} since it is a very unique ID for an Order-line.

Delete both Rules (those prefixed by PD).

Modify the Action for PD Store Value (this isn’t really necessary, but it makes testing more flexible)

##PD Store Value [Update Program Setting] (Action)##

Action Name: PD Store Value
Action Type: Update Program Setting
###Parameters:###
Setting Name: [:settingName]
Setting Value: [:settingValue]
Update Type: [:updateType]
Is Local: [:isLocal]

Recreate your Rules, but this time, we are going to set the Setting Names to much more unique names so they don’t get over-written by subsequent/simultaneous Orders. You will notice that each settingName now has the {ORDER UID} as a suffix of the name, and when we fire the PD ExecAMC Action, we send the {ORDER UID} as the Command Value (AMCvalue) …

##PD Order Sumitted - Store Values [Order State Updated] (Rule)##

Rule Name: PD Order Sumitted - Store Values
Event Name: Order State Updated
Rule Tags:
Custom Constraint List (3):
Execute Rule if: Matches
State NameEqualsStatus
StateEqualsSubmitted
Previous StateEqualsNew

##Actions (5):##

PD Store Value

Constraint: (none)

settingName: PD_OrderUid_{ORDER UID}
settingValue: {ORDER UID}
updateType: Update
isLocal: True
PD Store Value

Constraint: (none)

settingName: PD_TicketId_{ORDER UID}
settingValue: [=TN('{TICKET ID}')]
updateType: Update
isLocal: True
PD Store Value

Constraint: (none)

settingName: PD_OrderId_{ORDER UID}
settingValue: [=TN('{ORDER ID}')]
updateType: Update
isLocal: True
PD Store Value

Constraint: (none)

settingName: PD_OrderNo_{ORDER UID}
settingValue: [=TN('{ORDER NO}')]
updateType: Update
isLocal: True
PD ExecAMC

Constraint: (none)

AMCname: PD Post Order Data
AMCvalue: {ORDER UID}
bg: True
delay: 0

##Mappings##

Mappings
Terminal User Role Department Ticket Type
****

The second Rule now references [:CommandValue] to pull the unique Setting Name in the {CALL:pd.postJSON()} method …

##PD Post Order Data [Automation Command Executed] (Rule)##

Rule Name: PD Post Order Data
Event Name: Automation Command Executed
Rule Tags:
Custom Constraint List (1):
Execute Rule if: Matches
Automation Command NameEqualsPD Post Order Data

##Actions (1):##

PD Store Value

Constraint: (none)

settingName: PD_PostResult_[:CommandValue]
settingValue: {CALL:pd.postJSON('{SETTING:PD_TicketId_[:CommandValue]}', '{SETTING:PD_OrderId_[:CommandValue]}', '{SETTING:PD_OrderNo_[:CommandValue]}', '{SETTING:PD_OrderUid_[:CommandValue]}')}
updateType: Update
isLocal: True

##Mappings##

Mappings
Terminal User Role Department Ticket Type
****

1 Like

Ill have a look at how this goes.

I agree. I had not thought of doing this before, and I didn’t really have a clear methodology in my mind on how to accomplish the task. But I like this type of challenge, so I thought I would give it a shot even though I won’t use this in the near future, if ever.

I too think it is very awesomely, amazingly cool!

LOL ;P, thanks for that @pauln … I didn’t understand how it could be done, so I had to do a little research to figure out how to process Post data.

Knowing PHP fairly well, I figured it would be somewhat simple and easy to do, but at first I had a hard time finding the right “thing”. While it is straight-forward to process Post data from a Form Submission using $_POST["form_element_name"], that wasn’t going to work in this case, because that is looking for a certain type of encoding, and we don’t have any of the form element names to reference, nor a “user” to click a Submit button ;).

What I was looking for is “sort of” like listening for something to happen behind the scenes, but with no user interaction like a Form Submission. And as I surmised earlier, we were not really “listening” for anything in the first place - we just needed to process the Post Data somehow. Given we have APIs all over the internet that allow for it, like @JTRTech’s PMI solution., I was confident there existed a method to do this.

I stumbled across the “key piece” a couple of times in my research without even realizing it and skimmed right over it. The “magic” is here:

file_get_contents('php://input');

That ^ thing is some sort of magical PHP thing that contains the Post data - in our case, the JSON string. I didn’t even read about what it was - I finally figured it must be something special when I saw it more than one time in different code-snippets and I just tried it. When I realized it worked, I did a bit more reading. You can read about it and other “streams” here …

http://php.net/manual/en/wrappers.php.php

… and here …

1 Like

Ive gone and got my entity data working :slight_smile: I think I did it a long way, but still it works great :slight_smile:

[code]
function postJSON(tid,oid,onm,ouid)
{
// override values for testing
//var tid = 3214;
//var oid = 7757;
//var onm = 347;
//var ouid = ‘YTYb4Ra7H0aN4ucamXrwDg’;
//return “TID:”+tid + " OID:“+oid + " ONM:”+onm + " OUID:"+ouid;

// webserver URL, Username, Password
var url = “mywebsite.com is available for purchase - Sedo.com”;
var usr = “q”;
var pwd = “nada”;

var datafields = new Array();
datafields.push(“TicketId”);
datafields.push(“Id”);
datafields.push(“MenuItemName”);
datafields.push(“Price”);
datafields.push(“Quantity”);

/*
qry += " ,[CalculatePrice]“;
qry += " ,[DecreaseInventory]”;
qry += " ,[IncreaseInventory]";

qry += " ,[CreatingUserName]“;
qry += " ,[CreatedDateTime]”;
qry += " ,[LastUpdateDateTime]“;
qry += " ,[AccountTransactionTypeId]”;
qry += " ,[ProductTimerValueId]";

qry += " ,[GroupTagName]“;
qry += " ,[GroupTagFormat]”;
qry += " ,[Separator]";

qry += " ,[Locked]“;
qry += " ,[WarehouseId]”;
qry += " ,[DepartmentId]“;
qry += " ,[TerminalId]”;
*/

var datafieldscount = datafields.length;

var qry = “”;

qry += “SELECT”;

for (var d=0; d < datafieldscount; d++)
{
qry += (d>0 ? " ," : " ");
qry += “[” + datafields[d] + “]”;
}

qry += " FROM [Orders]";

qry += " WHERE 1=1";
qry += " AND [TicketId] = " + tid;
//qry += " AND [Id] = " + oid;
qry += " AND [OrderNumber] = " + onm;
qry += " AND [OrderUid] = ‘" + ouid + "’“;
qry += " ORDER BY [Id]”;

//return qry;

var qryresult = sql.Query(qry).Delimit(‘~’).First;
var qrydata = qryresult.split(‘~’);
var qryfieldcount = qrydata.length;

var qry2 = “”;

qry2 += " SELECT [EntityName] ,[EntityCustomData] FROM [TicketEntities] WHERE 1=1";
qry2 += "AND [Ticket_Id] = " + tid;

var qryresult2 = sql.Query(qry2).Delimit(‘~’).First;

var qrydata2 = qryresult2.split(‘~’);

var entity = JSON.parse(qrydata2[1]);

var data = new Object();

for (var d=0; d < datafieldscount; d++)
{
data[datafields[d]] = qrydata[d];
}
data[“Student ID”] = qrydata2[0];
data[“Student Year”] = entity[0].Value;
data[“Student Name”] = entity[1].Value;
data[“Student Email”] = entity[2].Value;

var jsondata = JSON.stringify(data);
//return jsondata

var res = web.PostJson(url,jsondata,usr,pwd);
dlg.ShowMessage(res);
return res;

}[/code]

Now to try and work out in getting this in my datbase.

1 Like

Ok, spent a bit of time, but this is amazing this can be done in Samba…

I order what I want

And pay it

I do a quick query of my webserver mysql datbase 2 seconds later


And the order, and entity data is all there.

From that I can do so much!

3 Likes

Hey @QMcKay would you have any idea of what to do if the terminal loses network connection? I dont want to lose all the sales on the webserver, and therfore get bad reports. Im thinkin about sominng like storing the ticketid_{number} in the samba db if it doesnt get a response from the web server and from then whenever I next tender an order, if script receives a response, it sends that off and after that it checks if in the datbase there are any values that containt ticketID and if so upload them and remove the value. Another complication with that is, how would I get the current system time that an order was made?

There are a few Date/Time values stored in the DB along with Orders and with Tickets, like Created Date, Last Updated Date etc.

Tickets:

,[Date]
,[LastOrderDate]
,[LastPaymentDate]
,[LastUpdateTime]

Orders:

,[CreatedDateTime]
,[LastUpdateDateTime]

Yeah I saw that, Im confident with doing this, Im not so confident in adding a sort of list to make sure all the orders get onto the webserver

It really depends on your intent, and what you are using this for, because the whole thing thus far is based on Order Submission. That is, when the Order Status changes from New to Submitted.

So if you are using this for Reporting purposes on the external Web Server, you should probably change the flow of it all. So instead of capturing and posting “Submitted Orders”, it is probably better to capture and post “Settled Tickets” which are fully Paid. We would use a different event in the Automation to post the entire Ticket all at once, based on:

Event: Before Ticket Closing
Remaining Amount Equals 0

If we do that ^ then we will get more accurate reports because the Ticket is finalized. The problem with posting Order by Order is that we might Void something for example, and the current method is not set up to handle that.

Since we would be dealing with entire finalized Tickets, we could more easily track which have been posted and which have not.

So if we get no response from the server, we simply store the Ticket ID in SambaPOS DB. Then next time we try to post, we look at the Ticket ID(s) which have failed to post, then gather all their information (Ticket Details, Order Details, etc), post the data, then erase the ID from the DB.

My order flow, is so that all orders must wither be settled or have all ites from it deleted, I do not allow unpaid orders to be submitted. Would I still need to use what you suggest?

Well, I don’t know what your flow is, but if it is based on what was designed in this Topic, then the answer is YES, you should revamp the flow to be Ticket-based rather than Order-based.

There isn’t such a thing as a “Paid/Settled Order”, only “Paid/Settled Tickets”. Orders are either “New” or “Submitted”, but it does not mean the Orders are “Paid/Settled”.

Hmm… stand by … need to figure a way to get a return from webserver back to SambaPOS. Anyone know how to do that in PHP ?

EDIT: nevermind, found it. PHP never ceases to amaze me.

Instead of this (which does not work):

return "SUCCESS";

We do this (to my surprise):

echo "SUCCESS";

So in the PHP on the server, at the end when we are finished:

$data .= "================================================================\r\n\r\n";

$fileName = "ticket_".$ticketID.".txt";
// FILE_APPEND | LOCK_EX
$fwsuccess = file_put_contents ($fileName, $data, FILE_APPEND);

echo "SUCCESS";   // sends data (SUCCESS) back to caller
return "SUCCESS"; // does nothing - sends nothing back
?>

Ok let’s give this a try.

After modification of the PHP to return “SUCCESS” via the echo statement, we modify the JScript to give us a better idea whether or not the Post succeeded …

  var res = web.PostJson(url,jsondata,usr,pwd);
  
  if (res != "SUCCESS")
  {
    //dlg.ShowMessage("Failed to Post Order Data:\r\n"+jsondata);
    return qrydata[0]+"~"+qrydata[1];
  }
  
  processFailedPosts(); // we will build a function for this
  return 0;

So with that ^ when the Post fails, we return <ticketId>~<orderId>
When the Post succeeds, we will process missed posts, then return 0.


Now a modification to our Rule that calls the Post Jscript to add another Execute Automation Command Action…

##PD Post Order Data [Automation Command Executed] (Rule)##

Rule Name: PD Post Order Data
Event Name: Automation Command Executed
Rule Tags:
Custom Constraint List (1):
Execute Rule if: Matches
Automation Command NameEqualsPD Post Order Data

##Actions (2):##

PD Store Value

Constraint: (none)

settingName: PD_PostResult_[:CommandValue]
settingValue: {CALL:pd.postJSON('{SETTING:PD_TicketId_[:CommandValue]}', '{SETTING:PD_OrderId_[:CommandValue]}', '{SETTING:PD_OrderNo_[:CommandValue]}', '{SETTING:PD_OrderUid_[:CommandValue]}')}
updateType: Update
isLocal: True
PD ExecAMC

Constraint: (none)

AMCname: PD Update Failed Post ID
AMCvalue: {SETTING:PD_PostResult_[:CommandValue]}
bg: False
delay: 0

##Mappings##

Mappings
Terminal User Role Department Ticket Type
****

And we need a Rule to handle our new Automation Command from the above Rule, which will be responsible for storing failed Post Data …

##PD Update Failed Post ID [Automation Command Executed] (Rule)##

Rule Name: PD Update Failed Post ID
Event Name: Automation Command Executed
Rule Tags:
Custom Constraint List (2):
Execute Rule if: Matches
Automation Command NameEqualsPD Update Failed Post ID
Command ValueNot Equals0

##Actions (1):##

PD Store Value

Constraint: (none)

settingName: PD_Failed_Postings
settingValue: [='{SETTING:PD_Failed_Postings}' != '' ? '{SETTING:PD_Failed_Postings}' + ',' + '[:CommandValue]' : '[:CommandValue]']
updateType: Update
isLocal: False

##Mappings##

Mappings
Terminal User Role Department Ticket Type
****

So now we have in SambaPOS DB, some orders that failed to Post as a comma-separated list using the format <ticketId>~<orderId>


Finally, we need a method to process our list of failed Posts, which we will do in JScript…

function processFailedPosts()
{
  var tid=0;
  var oid=0;
  
  var lqry = "SELECT [Value] FROM [ProgramSettingValues] WHERE [Name]='PD_Failed_Postings'";
  var failedPosts = sql.Query(lqry).First;
  if (failedPosts=='')
  {
    return 0;
  }
  
  failedPosts = failedPosts.split(',');
  var failedPostsCount = failedPosts.length;
  //return failedPostsCount;
  
  var datafields = getDataFields();
  var datafieldscount = datafields.length;
  
  for (var f=0; f<failedPostsCount; f++)
  {
    var pair = failedPosts[f].split('~');
    tid = pair[0];
    oid = pair[1];
    
    var qry = "";
    qry += "SELECT";
    for (var d=0; d < datafieldscount; d++)
    {
      qry += (d>0 ? " ," : " ");
      qry += "[" + datafields[d] + "]";
    }
    qry += " FROM [Orders]";
    qry += " WHERE 1=1";
    qry += " AND [TicketId] = " + tid;
    qry += " AND [Id] = " + oid;
    //qry += " AND [OrderNumber] = " + onm;
    //qry += " AND [OrderUid] = '" + ouid + "'";
    //qry += " ORDER BY [Id]";
  
    var qryresult = sql.Query(qry).Delimit('~').First;
    var qrydata = qryresult.split('~');
    var qryfieldcount = qrydata.length;  

    var data = new Object();
  
    for (var d=0; d < datafieldscount; d++)
    {
      data[datafields[d]] = qrydata[d];
    }
  
    var jsondata = JSON.stringify(data);
    //return jsondata;

    // Post missed Data to server
    var res = web.PostJson(url,jsondata,usr,pwd);

    if (res == "SUCCESS")
    {
      // remove missed Post data from DB
      var rval = tid+"~"+oid+",";
      var uqry = "UPDATE [ProgramSettingValues] SET [Value]=(REPLACE([Value],'"+rval+"','')) WHERE [Name]='PD_Failed_Postings'";
      sql.ExecSql(uqry);
      var rval = tid+"~"+oid;
      uqry = "UPDATE [ProgramSettingValues] SET [Value]=(REPLACE([Value],'"+rval+"','')) WHERE [Name]='PD_Failed_Postings'";
      sql.ExecSql(uqry);
    }
  }
  return 0;
}

You will notice that ^ will not work by itself because it calls another function. I re-arranged some of the master script to make it more modular, so here it is in its entirety …

// webserver URL, Username, Password
var url = "http://localhost:82/posthandler.php";
var usr = "q";
var pwd = "nada";
  
function getDataFields()
{
  var datafields = new Array();
  datafields.push("TicketId");
  datafields.push("Id");
  datafields.push("OrderNumber");
  datafields.push("OrderUid");
  
  datafields.push("MenuItemId");
  datafields.push("MenuItemName");
  datafields.push("PortionName");
  datafields.push("Price");
  datafields.push("Quantity");
  datafields.push("PortionCount");

  datafields.push("PriceTag");
  datafields.push("Tag");
  datafields.push("Taxes");
  datafields.push("OrderTags");
  datafields.push("OrderStates");
  
/*
  qry += " ,[CalculatePrice]";
  qry += " ,[DecreaseInventory]";
  qry += " ,[IncreaseInventory]";

  qry += " ,[CreatingUserName]";
  qry += " ,[CreatedDateTime]";
  qry += " ,[LastUpdateDateTime]";
  qry += " ,[AccountTransactionTypeId]";
  qry += " ,[ProductTimerValueId]";

  qry += " ,[GroupTagName]";
  qry += " ,[GroupTagFormat]";
  qry += " ,[Separator]";
  
  qry += " ,[Locked]";
  qry += " ,[WarehouseId]";
  qry += " ,[DepartmentId]";
  qry += " ,[TerminalId]";
*/
  return datafields;
}

function postJSON(tid,oid,onm,ouid)
{
  // override values for testing
  //var tid = 3214;
  //var oid = 7757;
  //var onm = 347;
  //var ouid = 'YTYb4Ra7H0aN4ucamXrwDg';
  //return "TID:"+tid + " OID:"+oid + " ONM:"+onm + " OUID:"+ouid;
  
  var datafields = getDataFields();
  var datafieldscount = datafields.length;
  
  var qry = "";

  qry += "SELECT";
  
  for (var d=0; d < datafieldscount; d++)
  {
    qry += (d>0 ? " ," : " ");
    qry += "[" + datafields[d] + "]";
  }
  
  qry += " FROM [Orders]";

  qry += " WHERE 1=1";
  qry += " AND [TicketId] = " + tid;
  //qry += " AND [Id] = " + oid;
  qry += " AND [OrderNumber] = " + onm;
  qry += " AND [OrderUid] = '" + ouid + "'";
  qry += " ORDER BY [Id]";
  
  //return qry;
  
  var qryresult = sql.Query(qry).Delimit('~').First;
  var qrydata = qryresult.split('~');
  var qryfieldcount = qrydata.length;  

  //return qryfieldcount;
  
  var data = new Object();
  
  for (var d=0; d < datafieldscount; d++)
  {
    data[datafields[d]] = qrydata[d];
  }
  
  var jsondata = JSON.stringify(data);
  //return jsondata;

  var res = web.PostJson(url,jsondata,usr,pwd);
  
  if (res != "SUCCESS")
  {
    //dlg.ShowMessage("Failed to Post Order Data:\r\n"+jsondata);
    return qrydata[0]+"~"+qrydata[1];
  }
  
  processFailedPosts();
  return 0;
}

function processFailedPosts()
{
  var tid=0;
  var oid=0;
  
  var lqry = "SELECT [Value] FROM [ProgramSettingValues] WHERE [Name]='PD_Failed_Postings'";
  var failedPosts = sql.Query(lqry).First;
  if (failedPosts=='')
  {
    return 0;
  }
  
  failedPosts = failedPosts.split(',');
  var failedPostsCount = failedPosts.length;
  //return failedPostsCount;
  
  var datafields = getDataFields();
  var datafieldscount = datafields.length;
  
  for (var f=0; f<failedPostsCount; f++)
  {
    var pair = failedPosts[f].split('~');
    tid = pair[0];
    oid = pair[1];
    
    var qry = "";
    qry += "SELECT";
    for (var d=0; d < datafieldscount; d++)
    {
      qry += (d>0 ? " ," : " ");
      qry += "[" + datafields[d] + "]";
    }
    qry += " FROM [Orders]";
    qry += " WHERE 1=1";
    qry += " AND [TicketId] = " + tid;
    qry += " AND [Id] = " + oid;
    //qry += " AND [OrderNumber] = " + onm;
    //qry += " AND [OrderUid] = '" + ouid + "'";
    //qry += " ORDER BY [Id]";
  
    var qryresult = sql.Query(qry).Delimit('~').First;
    var qrydata = qryresult.split('~');
    var qryfieldcount = qrydata.length;  

    var data = new Object();
  
    for (var d=0; d < datafieldscount; d++)
    {
      data[datafields[d]] = qrydata[d];
    }
  
    var jsondata = JSON.stringify(data);
    //return jsondata;

    // Post missed Data to server
    var res = web.PostJson(url,jsondata,usr,pwd);

    if (res == "SUCCESS")
    {
      // remove missed Post data from DB
      var rval = tid+"~"+oid+",";
      var uqry = "UPDATE [ProgramSettingValues] SET [Value]=(REPLACE([Value],'"+rval+"','')) WHERE [Name]='PD_Failed_Postings'";
      sql.ExecSql(uqry);
      var rval = tid+"~"+oid;
      uqry = "UPDATE [ProgramSettingValues] SET [Value]=(REPLACE([Value],'"+rval+"','')) WHERE [Name]='PD_Failed_Postings'";
      sql.ExecSql(uqry);
    }
  }
  return 0;
}

That should do it. Next time we Submit an Order, and the Post is successful, we will also process the failed-Post list and post that data as well, then clear the list.

2 Likes

I am trying to follow this but am getting stuck. When I test the script I am getting ‘tid’ is undefined. What action type should the PD ExecAMC action be?

2

Your test expression doesn’t seem right to me, the call variables should surely have values in them but textual strings matching the variables.
Your terminal Id is not going to be tid surely?

You need to supply actual real parameter values in the Test facility. You can’t just put letters in there - you need real values, usually enclosed in 'single quotes'.

So tid needs to be a valid Ticket Id, onm needs to be an Order Number (?), and ouid needs to be an Order UID.


ExecAMC is “Execute Automation Command