Getting particular Case details along with all activities - acumatica

With CR306000SetSchema and CR306000Submit, I can create a new Case. But if I want to get details of the particular case with its activities then. Any one has any idea.

We can use CR306000Export to get the data. Below code sample will get you the activity of particular case ID. For instance "000001" in example below.
string[][] export = context.CR306000Export
(
new CaseAPI.Command[]
{
new CaseAPI.Value
{
Value = "000001",
LinkedCommand = CR306000.CaseSummary.CaseID
},
CR306000.Activities.Type,
CR306000.Activities.Summary,
CR306000.Details.Description,
CR306000.Activities.Status,
CR306000.Activities.StartDate,
CR306000.CaseSummary.Owner
},
new CaseAPI.Filter[]
{
new CaseAPI.Filter
{
Field = CR306000.Activities.StartDate,
Condition = CaseAPI.FilterCondition.GreaterOrEqual,
Value = DateTime.Today
}
},
0, true, true
);

Related

Conditionally add filter predicaments before returning data in Mongodb

since I cant word properly what I am trying to achieve as many topics can mistakenly fall under the terminology of conditionally filtering mongodb query. Here is what I want to achieve,
I have a users DB, I am trying to send a search query to my backend with the following data {"country":"all","category":"all","gender":"all","ageRange":[25, 40]}
And I am trying to add these parameters into my query programatically. Eg,
const search = db.collection("users").find();
if (params.country == "ALL") {
search.addFilter( { country: "..." } )
}
if (params.age < 18) {
search.addFilter( { minor : true } )
}
const data = search.limit(30).toArray();
Is something like this possible? As when country is equal to "all" I dont want to apply any filter but if country is equal to some country I want to apply that country as the filter and so on. Can I programatically add to the filter predicament?
Using Nodejs (Nextjs api), so javascript and mongodb library
var filters = {}
var andObj = []
if (params.country == "ALL") {
andObj.push( { country: {$regex:".*." }} )
}
if (params.age < 18) {
andObj.push( { minor : true } )
}
filters['$and'] = andObj
const data = await db.collection("users").find(filters).limit(30).toArray();
or
var filters = {}
var andObj = []
if (params.country != "ALL") {
andObj.push( { country: params.country} )
}
if (params.age < 18) {
andObj.push( { minor : true } )
}
filters['$and'] = andObj
const data = await db.collection("users").find(filters).limit(30).toArray();

How to execute multiple MERGE INTO query on Oracle DB from Node.js

I am trying to run MERGE INTO query on OracleDB in forEach loop on Node.js code. But only the first query executing and all other failings.
Code:
assignedDeals.forEach(async deal => {
let EXPECTED_DATE = null;
let STATUS = null;
let COMMENT = null;
let stmt = `MERGE INTO SP_ASSIGNMENT USING dual ON (ID = ${deal.ID}) WHEN MATCHED THEN UPDATE SET MODIFIED_DATE = current_timestamp `
if (deal.STATUS) {
stmt = stmt + `,STATUS = '${deal.STATUS}'`;
STATUS = `'${deal.STATUS}'`;
}
if (deal.COMMENT) {
stmt = stmt + `,COMMENT = '${deal.COMMENT}'`;
COMMENT = `'${deal.COMMENT}'`;
}
if (deal.EXPECTED_DATE) {
stmt = stmt + `,EXPECTED_DATE = '${deal.EXPECTED_DATE}'`;
EXPECTED_DATE = `'${deal.EXPECTED_DATE}'`
}
stmt = stmt + `WHEN NOT MATCHED THEN INSERT (ID,STATUS,ASSIGNED_USER_ID,MODIFIED_DATE,ASSIGNED_RID,ASSIGNED_BY,EXPECTED_DATE,STATUS,COMMENT)
VALUES (${deal.ID},'${deal.ISASSIGNED}','${deal.USERID}',current_timestamp,${parseInt(deal.RID)},'${deal.ASSIGNED_BY}',${EXPECTED_DATE},${STATUS},${COMMENT})`;
console.log(stmt);
await connection.execute(stmt,{},{
autoCommit: true
});
});
Is this the right way to execute multiple queries in for loop?
Any idea why it's not executing the next query?
Help me on this I am new to Node.js + Oracle DB.
Answering my own question so it can help other who have similar issue like this.
So after research on my issue and based on comments I can conclude that through bind variables and executeMany() method we can achieve our goal.
Step 1: Prepare your bind parameters like below:
let binds = [];
assignedDeals.forEach(deal => {
binds.push({
ID: deal.ID,
USER_ID: deal.USERID,
RID: parseInt(deal.ASSIGNED_ROLE_ID),
ASSIGNED_BY: deal.ASSIGNED_BY,
EXPECTED_DATE: deal.EXPECTED_DATE ? new Date(deal.EXPECTED_DATE) : null,
STATUS: deal.STATUS ? deal.STATUS : null,
COMMENT: deal.COMMENT ? deal.COMMENT : null,
SDC_STATUS: deal.SDC_STATUS ? deal.SDC_STATUS : null,
DELETED_ON: null
})
})
Step 2: Prepare ExecuteManyOptions:
autoCommit --> for autocommit your statement.
Boolean autoCommit
If this property is true, then the transaction in
the current connection is automatically committed at the end of
statement execution.
The default value is false.
This property may be overridden in an execute()/executeMany() call.
bindDefs --> Bind defs is for type and size specification as per your table defination.
Object bindDefs
The bindDefs object defines the bind variable types,
sizes, and directions. This object is optional in some cases but it is
more efficient to set it.
It should be an array or an object, depending on the structure of the
binds parameter.
Each value in the bindDefs array or object should be an object
containing the keys dir, maxSize, and type for one bind variable,
similar to how execute()/executeMany() bind parameters are identified.
Example:
const options = {
autoCommit: true,
bindDefs: {
ID: { type: oracledb.NUMBER },
USER_ID: { type: oracledb.DB_TYPE_VARCHAR, maxSize: 20 },
RID: { type: oracledb.DB_TYPE_NUMBER},
ASSIGNED_BY: { type: oracledb.DB_TYPE_VARCHAR, maxSize: 20 },
EXPECTED_DATE: { type: oracledb.DATE },
STATUS: { type: oracledb.DB_TYPE_VARCHAR, maxSize: 100 },
COMMENT: { type: oracledb.DB_TYPE_VARCHAR, maxSize: 200 },
SDC_STATUS: { type: oracledb.DB_TYPE_VARCHAR, maxSize: 20 },
DELETED_ON: { type: oracledb.DATE },
}
};
Step 3: Prepare your statement:
const sql = `MERGE INTO SP_ASSIGNMENT USING dual ON (DELETED_ON IS NULL AND ID = :ID)
WHEN MATCHED THEN UPDATE SET MODIFIED_DATE = current_timestamp ,STATUS = :STATUS, SDC_STATUS = :SDC_STATUS,COMMENT = :COMMENT,EXPECTED_DATE = :EXPECTED_DATE
WHEN NOT MATCHED THEN INSERT (ID,USER_ID,MODIFIED_DATE,RID,ASSIGNED_BY,EXPECTED_DATE,STATUS,COMMENT,SDC_STATUS,DELETED_ON)
VALUES (:ID,:USER_ID,current_timestamp,:RID,:ASSIGNED_BY,:EXPECTED_DATE,:STATUS,:COMMENT,:SDC_STATUS,:DELETED_ON)`;
Note: I prepare the above statement as per my requirements but it's
very in based on update & insert a condition. You can modify as per
yours.
Step 4: Final step to call executeMany() and pass your SQL statement and bind params and bind options.
result = await connection.executeMany(sql, binds, options);
So by following the above 4 steps you can achieve an update or insert based on USING ON condition in MERGE INTO statement.
Hope this will helps others.

Cosmos DB: range string index return query error

I'm trying to create custom index policy on CosmosDB documents collection for include only 1 field in index.
The index policy looks like :
new IndexingPolicy
{
Automatic = true,
IndexingMode = IndexingMode.Consistent,
ExcludedPaths = new Collection<ExcludedPath>(new[]
{
new ExcludedPath { Path = "/*" }
}),
IncludedPaths = new Collection<IncludedPath>(new[]
{
new IncludedPath
{
Path = "/Id/?",
Indexes = new Collection<Index>(new Index[] { new RangeIndex(DataType.String) {Precision = -1 } })
}
})
};
Then I do query on documents collection :
CosmosClient.CreateDocumentQuery<Entity>(
CollectionUri(docCollection),
"SELECT x from x where x.Id != \"\" ",
new FeedOptions
{
MaxItemCount = 100,
RequestContinuation = null,
EnableCrossPartitionQuery = false,
PartitionKey = new PartitionKey("entities"),
}).AsDocumentQuery();
Such request throws an error: An invalid query has been specified with filters against path(s) excluded from indexing. Consider adding allow scan header in the request.
While almost same one (check for equality instead of unequality) gives correct result.
Did I configure index policy wrong or I need to specify some additional args when querying? Thanks
Your partition key path should also be included in the included paths. It's implicitly included as a filter because you're setting it in PartitionKey = new PartitionKey("entities").

array manipulation in node js and lodash

I have two arrays
typeArr = [1010111,23342344]
infoArr={'name':'jon,'age':25}
I am expecting following
[{'name:'jone','age':25,'type':1010111,'default':'ok'},{'name:'jone','age':25,'type':23342344,'default':'nok'}]
Code :
updaterecord(infoArr,type)
{
infoArr.type=type;
response = calculate(age);
if(response)
infoArr.default = 'ok';
else
infoArr.default = 'nok';
return infoArr;
}
createRecord(infoArr,typeArr)
{
var data = _.map(typeArr, type => {
return updaterecord(infoArr,type);
});
return (data);
}
var myData = createRecord(infoArr,typeArr);
I am getting
[{'name:'jone,'age':25.'type':23342344,'default':nok},{'name:'jone,'age':25.'type':23342344,'default':nok}]
with some reason the last record updates the previous one. I have tried generating array using index var but not sure what's wrong it keep overriding the previous item.
how can I resolve this
You are passing the entire infoArr array to your updaterecord() function, but updaterecord() looks like it's expecting a single object. As a result it is adding those properties to the array rather than individual members of the array.
It's not really clear what is supposed to happen because typeArr has two elements and infoArr has one. Do you want to add another to infoArr or should infoArr have the same number of elements as typeArr.
Assuming it should have the same number you would need to use the index the _map gives you to send each item from infoArr:
function createRecord(infoArr,typeArr) {
var data = _.map(typeArr, (type, i) => {
// use infoArr[i] to send one element
return updaterecord(infoArr[i],type);
});
return (data);
}
Edit:
I'm not sure how you are calculating default since it's different in your expected output, but based on one number. To get an array of objects based on infoArray you need to copy the object and add the additional properties the you want. Object.assign() is good for this:
let typeArr = [1010111,23342344]
let infoArr={'name':'jon','age':25}
function updaterecord(infoArr,type){
var obj = Object.assign({}, infoArr)
return Object.assign(obj, {
type: type,
default: infoArr.age > 25 ? 'ok' : 'nok' //or however your figuring this out
})
}
function createRecord(infoArr,typeArr) {
return _.map(typeArr, type => updaterecord(infoArr,type));
}
Result:
[ { name: 'jon', age: 25, type: 1010111, default: 'nok' },
{ name: 'jon', age: 25, type: 23342344, default: 'nok' } ]

Updating and Deleting Payment Methods with Acumatica API

Thanks to the post on Creating customer payment method with api I am able to create a new Payment Method successfully using the API. But I can't figure out how to update or delete an existing payment method. There doesn't seem to be any type of key field. I've updated and deleted Contacts and Locations with the API, but Contacts have Contact IDs, and Locations have LocationIDs. There doesn't seem to be a PaymentMethodID. I thought maybe the "Card/Account No." field might function as that, but I wasn't able to make that work. And I've noticed that I can save two Payment Methods with identical card numbers, so that would make identical keys if that field was used anyway.
Here's the code I tried for updating. It's mostly Chris's code from the other post I linked to, but trying to add an extra key field for the update.
Public Function UpdateCreditCard(ByVal customerID As String, ByVal existingAcctNum As String, ByVal paymentMethodCode As String, ByVal CCNum As String, ByVal expiration As String, ByVal nameOnCard As String, ByVal active As Boolean)
Dim paymentMethod As AR303010Content = m_context.AR303010GetSchema()
m_context.AR303010Clear()
' main level fields
Dim customerVal As Value = CreateValue(paymentMethod.PaymentMethodSelection.Customer, customerID)
Dim paymentMethodCodeVal As Value = CreateValue(paymentMethod.PaymentMethodSelection.PaymentMethod, paymentMethodCode)
' inner level fields
Dim ccNumName As Value = CreateValue(paymentMethod.PaymentMethodDetails.Description, "CCDNUM")
Dim ccNumValue As Value = CreateValue(paymentMethod.PaymentMethodDetails.Value, CCNum, True)
Dim ccExpName As Value = CreateValue(paymentMethod.PaymentMethodDetails.Description, "EXPDATE")
Dim ccExpValue As Value = CreateValue(paymentMethod.PaymentMethodDetails.Value, expiration, True)
Dim ccNameName As Value = CreateValue(paymentMethod.PaymentMethodDetails.Description, "NAMEONCC")
Dim ccNameValue As Value = CreateValue(paymentMethod.PaymentMethodDetails.Value, nameOnCard, True)
Dim saveCommands() As Command
If existingAcctNum = "" Then
' new credit card
saveCommands = {customerVal, paymentMethod.Actions.Insert, paymentMethodCodeVal, ccNumName, ccNumValue, ccExpName, ccExpValue, ccNameName, ccNameValue, paymentMethod.Actions.Save}
Else
' existing credit card, only allow update of Active or expiration based on "description"
Dim descriptionVal As Value = CreateValue(paymentMethod.PaymentMethodDetails.Description, existingAcctNum)
Dim activeVal As Value = CreateValue(paymentMethod.PaymentMethodSelection.Active, active.ToString())
saveCommands = {customerVal, descriptionVal, ccExpName, ccExpValue, activeVal, paymentMethod.Actions.Save}
End If
Dim updateResult As AR303010Content() = m_context.AR303010Submit(saveCommands)
Return ""
End Function
Another confusing part of this is that the two fields I would really like to let the user update are either the expiration date, or whether the card is Active. But the Active flag is one of the fields in the list accessible from the Customer screen, and the expiration date is a field only accessible from the Payment Method screen. If those are two different API calls, then will I need a different key field in each one of them?
This screen is difficult to use via web services for a few different reasons:
There is no unique key exposed via the screen that you can use to locate payment methods. The internal key is a field named PMInstanceID, and although you can technically refer to it via web service commands, it is not obvious to use
In Acumatica, payment method details are locked once one transaction using this payment method has been recorded. This means that to make modifications to it, you need to create a new payment method, and set the old one inactive.
There's a bug in the screen that only manifests when using it via web services, which is related to item above. When loading a payment method, system will disable the payment method details if there has been any transactions, but will fail to re-enable it if not. This is never a problem when using Acumatica from a web browser, since the fields are re-enabled automatically between every round-trip. But when automating this screen through web services, you're actually performing all the operations in a single round-trip. This problem has been reported and will be fixed shortly (internal JIRA reference is AC-54456)
That being said, there's a few things at our disposal that we can use to make this screen as easy to use from web services as other screens, and work around its limitations. By creating a customization project, we can add the PMInstanceID field (hereby referred to as the Token ID field) to the screen. You can store this token on your system, and use it for future operations with the payment method. Within the same customization project, we can also make the payment method details always enabled, allowing you to update expiration date on existing cards. Doing so also resolves the bug mentioned above where system won't allow you to add any new payment method to the system. The customization has two parts:
Overriding CustomerPaymentMethod DAC to make the PMInstanceID field accessible from the UI. This is done by appending the PXUIField attribute to the PMInstanceID field: [PXUIField(DisplayName="Token ID", Visibility=PXUIVisibility.SelectorVisible)]. After that, the field may be added to the screen using the layout editor.
Handling the CustomerPaymentMethod_RowSelected event to force the payment methods details to always be enabled. We also use this event handler to hide the Token ID field when adding a new payment method, since this field would otherwise show int.MinValue until the payment method has been saved. The full code of the event looks like this:
protected void CustomerPaymentMethod_RowSelected(PXCache cache, PXRowSelectedEventArgs e, PXRowSelected InvokeBaseHandler)
{
if(InvokeBaseHandler != null)
InvokeBaseHandler(cache, e);
// Force the payment method details to always be enabled to facilitate working via web services
PXUIFieldAttribute.SetEnabled(Base.Details.Cache, null, true);
// When adding a new method, field will have a temporary value corresponding to int.MinValue - don't show it
PXUIFieldAttribute.SetVisible<CustomerPaymentMethod.pMInstanceID>(cache, e.Row, cache.GetStatus(e.Row) != PXEntryStatus.Inserted);
}
Once the customization is published, it becomes far easier to update existing payment methods. The code below shows how you can add a payment method, and retrieve the token ID that you will use later on to update the payment method:
public int AddCreditCard(string customerID, string paymentMethod, string cardNumber, string expirationDate, string cvv, string nameOnCard)
{
if(_AR303010 == null) _AR303010 = _context.AR303010GetSchema();
_context.AR303010Clear();
var commands = new Command[]
{
new Value { Value = customerID, LinkedCommand = _AR303010.PaymentMethodSelection.Customer },
new Value { Commit = true, LinkedCommand = _AR303010.Actions.Insert },
new Value { Value = paymentMethod, LinkedCommand = _AR303010.PaymentMethodSelection.PaymentMethod },
new Value { Value = "CCDNUM", LinkedCommand = _AR303010.PaymentMethodDetails.Description },
new Value { Value = cardNumber, LinkedCommand = _AR303010.PaymentMethodDetails.Value, Commit = true },
new Value { Value = "EXPDATE", LinkedCommand = _AR303010.PaymentMethodDetails.Description },
new Value { Value = expirationDate, LinkedCommand = _AR303010.PaymentMethodDetails.Value, Commit = true},
new Value { Value = "CVV", LinkedCommand = _AR303010.PaymentMethodDetails.Description },
new Value { Value = cvv, LinkedCommand = _AR303010.PaymentMethodDetails.Value, Commit = true },
new Value { Value = "NAMEONCC", LinkedCommand = _AR303010.PaymentMethodDetails.Description },
new Value { Value = nameOnCard, LinkedCommand = _AR303010.PaymentMethodDetails.Value, Commit = true },
_AR303010.Actions.Save,
_AR303010.PaymentMethodSelection.TokenID
};
var result = _context.AR303010Submit(commands.ToArray());
return int.Parse(result[0].PaymentMethodSelection.TokenID.Value);
}
public void UpdateCreditCardExpirationDate(string customerID, string paymentMethod, int tokenID, string expirationDate)
{
if (_AR303010 == null) _AR303010 = _context.AR303010GetSchema();
_context.AR303010Clear();
var commands = new Command[]
{
new Value { Value = customerID, LinkedCommand = _AR303010.PaymentMethodSelection.Customer },
new Value { Commit = true, LinkedCommand = _AR303010.Actions.Insert },
new Value { Value = paymentMethod, LinkedCommand = _AR303010.PaymentMethodSelection.PaymentMethod },
new Value { Value = tokenID.ToString(), LinkedCommand = _AR303010.PaymentMethodSelection.TokenID },
new Value { Value = "EXPDATE", LinkedCommand = _AR303010.PaymentMethodDetails.Description },
new Value { Value = expirationDate, LinkedCommand = _AR303010.PaymentMethodDetails.Value, Commit = true},
_AR303010.Actions.Save,
};
var result = _context.AR303010Submit(commands.ToArray());
}
public void MakeCardInactive(string customerID, string paymentMethod, int tokenID)
{
if (_AR303010 == null) _AR303010 = _context.AR303010GetSchema();
_context.AR303010Clear();
var commands = new Command[]
{
new Value { Value = customerID, LinkedCommand = _AR303010.PaymentMethodSelection.Customer },
new Value { Commit = true, LinkedCommand = _AR303010.Actions.Insert },
new Value { Value = paymentMethod, LinkedCommand = _AR303010.PaymentMethodSelection.PaymentMethod },
new Value { Value = tokenID.ToString(), LinkedCommand = _AR303010.PaymentMethodSelection.TokenID },
new Value { Value = "False", LinkedCommand = _AR303010.PaymentMethodSelection.Active },
_AR303010.Actions.Save,
};
var result = _context.AR303010Submit(commands.ToArray());
}
I wrapped these 3 functions into a class, and actual usage becomes quite simple:
var paymentMethodManager = new PaymentMethodManager(context);
int tokenID = paymentMethodManager.AddCreditCard("ABARTENDE", "MASTERCARD", "5111111111111118", "122016", "123", "John Doe");
paymentMethodManager.UpdateCreditCardExpirationDate("ABARTENDE", "MASTERCARD", tokenID, "032017");
paymentMethodManager.MakeCardInactive("ABARTENDE", "MASTERCARD", tokenID);
As of now, it is not possible to delete an existing payment method, and you must make it inactive instead. I've made the request for this enhancement, and it may come in the future.
Note: I have placed all the code used in this answer on GitHub at https://github.com/gmichaud/acumatica-paymentmethod-ws-extensions.

Resources