Having REST API respect the segmented key for a field - acumatica

I want to do a GET request against the Default/18.200.001/StockItem endpoint (we are about to upgrade them so I would love to know if this is different in new versions)
They have an Inventory ID with the following segmented key AAA-AAA.##.##. When I do a GET the field is returned as AAAAAA####. Is there any way to get the API to respect the designated segmented key when querying data?

As commented on the original ticket, the web service will only ever return the value, not respecting segment. I implemented a cache extension on InventoryItem that manually calculates the segment I want, and returned it thru the web service api definition
#region UsrLSInventoryIDWithKey
[PXString]
[PXUIField(DisplayName = "DisplayName")]
public string UsrLSInventoryIDWithKey
{
[PXDependsOnFields(typeof(InventoryItem.inventoryCD))]
get
{
if ((Base.InventoryCD?.Length ?? 0) < 11) return Base.InventoryCD;
return Base.InventoryCD?.Substring(0, 6) + "-" + Base.InventoryCD?.Substring(5, 5);
}
}

Related

Add Files to Salesorder line item

I want to add files to salesorder line items in Acumatica using web services.
What endpoint should be used?
I want to add an image as shown in the screenshot above, using web service endpoint.
This is an old question, but I just came across this same issue while assisting a customer with a third-party integration. The third-party developers were adamant that they could only use the REST service, as they had already built the rest of their integration around it before realizing they couldn't attach files to sales order lines.
I was able to build a workaround using a customization. The issue at hand is that the way Acumatica's REST API attaches files is only accessible for Top-Level entities - which means there has to be a screen that uses the object as a primary DAC.
The workaround is to do just that, create a new custom screen that uses the SOLine object as it's primary DAC. In order to make the selectors available, I had to remove and replace a couple attributes on the key fields so that they could be visible and enabled. Here is the graph code - it's very simple, as this is basically just the bare minimum needed to be able to create a custom endpoint that uses the SOLine DAC as a top-level entity.
public class SOLineAttachmentEntry : PXGraph<SOLineAttachmentEntry, SOLine>
{
public PXSelect<SOLine> SOLineDetail;
[PXMergeAttributes(Method = MergeMethod.Append)]
[PXRemoveBaseAttribute(typeof(PXUIFieldAttribute))]
[PXUIField(DisplayName = "Order Type", Visible=true, Enabled = true)]
protected virtual void SOLine_OrderType_CacheAttached(PXCache sender) { }
[PXMergeAttributes(Method = MergeMethod.Append)]
[PXRemoveBaseAttribute(typeof(PXUIFieldAttribute))]
[PXUIField(DisplayName = "Order Nbr", Visible=true, Enabled = true)]
protected virtual void SOLine_OrderNbr_CacheAttached(PXCache sender) { }
[PXMergeAttributes(Method = MergeMethod.Append)]
[PXRemoveBaseAttribute(typeof(PXUIFieldAttribute))]
[PXRemoveBaseAttribute(typeof(PXLineNbrAttribute))]
[PXUIField(DisplayName = "Line Nbr", Visible=true, Enabled = true)]
protected virtual void SOLine_LineNbr_CacheAttached(PXCache sender) { }
}
The custom screen layout should be a simple Form with just these three key fields, OrderType, OrderNbr, LineNbr. In the Screen Editor of the customization, you'll want to set CommitChanges=true in the Layout Properties tab for each field.
Once the screen is published, you can use it to create a new custom endpoint and add a single entity by selecting the SOLine view from the custom screen. I named the endpoint "SalesOrderDetailAttach", assigned the endpoint version to be 1.0, and named the new entity "SalesOrderDetail". Using those names, the file attachment request should be a PUT request with the binary file data in the request body, using the url format:
[AcumaticaBaseUrl]/entity/SalesOrderDetailAttach/1.0/SalesOrderDetail/[OrderType]/[OrderNbr]/[LineNbr]/files/[Desired filename in Acumatica]
This worked for this one very specific case, attaching a file to the SOLine object. The screen and the endpoint should really never be used for anything else, and the custom screen should not be accessible to any users other than the administrator and the API user. Ultimately I would recommend using the Screen-Based method from the other answer, but if using the REST API is an absolute must-have, this is a potential workaround.
REST API needs to reference the detail line in the body. Since the body is used to pass the binary data of the attachment REST API can't be used to attach files to detail line.
Below is a screen based API snippet that creates a new master/detail document and attach images to the detail line. If you choose to use the screen based API you will need to adapt the snippet for sales order ASMX screen and fetch sales order with expanded details SOLine. The pattern to attach file will be the same:
string[] detailDescription = "Test";
List<string> filenames = "image.jpg";
List<byte[]> images = new byte[] { put_image_binary_data_here } ;
ServiceReference1.Screen context = new ServiceReference1.Screen();
context.CookieContainer = new System.Net.CookieContainer();
context.Url = "http://localhost/Demo/Soap/XYZ.asmx";
context.Login("admin#CompanyLoginName", "admin");
ServiceReference1.XY999999Content content = PX.Soap.Helper.GetSchema<ServiceReference1.XY999999Content>(context);
List<ServiceReference1.Command> cmds = new List<ServiceReference1.Command>
{
// Insert Master
new ServiceReference1.Value { Value="<NEW>", LinkedCommand = content.Document.KeyField},
new ServiceReference1.Value { Value="Description", LinkedCommand = content.Document.Description},
// Insert Detail
content.DataView.ServiceCommands.NewRow,
new ServiceReference1.Value { Value = noteDetail[0], LinkedCommand = content.DataView.Description },
// Attaching a file to detail
new ServiceReference1.Value
{
Value = Convert.ToBase64String(images[0]),
FieldName = filenames[0],
LinkedCommand = content.DataView.ServiceCommands.Attachment
},
content.Actions.Save,
content.Document.KeyField
};
var returnValue = context.PP301001Submit(cmds.ToArray());
context.Logout();

How do I save value in user field added to INItemPlan?

After using a custom "Tag" table to create a Sales Order destined to be fulfilled via PO, I need to attach my custom "Tag ID" to the INItemPlan record for future use in the purchasing process. While I was able to do this successfully in other places, repeating the same methodology is not working now.
In the code, the INItemPlanExt object is retrieved for the INItemPlan object. A value is assigned to the UsrTagID field, and the data can be retrieved from the cache following the Persist.
The code below shows the lookup from the SOLine to the SOLineSplit(s) to the INItemPlan record that is intended to handle purchase via the PO Create graph later. For testing purposes, it sets the value, persists the data, and then retrieves it again. The trace shows that the value appears to be written and retrievable, but the database does not show the value in the UsrTagID field when looking directly into SQL after the fact. I don't see where the code might reset the record in the database as this code is executed as the last call in the "Create SO" action that was added to the menu.
(A) Is there an error in the first section shown in how the UsrTagID field is retrieved and set/saved?
(B) Is there a better way to store the Tag ID on the INItemPlan (database table) record?
(C) Is there something I should look for that might be resetting the data elsewhere? (Although I may have missed something, I didn't find anything unexpected in event handlers.)
Version is: Acumatica 2018R1 Build 18.114.0018
public static void StoreSoTagID(SOOrderEntry graph, int? tagID, string orderType, string orderNbr, int? lineNbr)
{
PXResultset<SOLine> Results = PXSelectJoin<SOLine,
InnerJoin<SOOrder, On<SOOrder.orderNbr, Equal<SOLine.orderNbr>,
And<SOOrder.orderType, Equal<SOLine.orderType>>>,
InnerJoin<SOLineSplit, On<SOLineSplit.orderType, Equal<SOLine.orderType>,
And<SOLineSplit.orderNbr, Equal<SOLine.orderNbr>,
And<SOLineSplit.lineNbr, Equal<SOLine.lineNbr>>>>,
InnerJoin<INItemPlan, On<INItemPlan.planID, Equal<SOLineSplit.planID>>
>>>,
Where<SOLine.orderType, Equal<Required<SOLine.orderType>>,
And<SOLine.orderNbr, Equal<Required<SOLine.orderNbr>>,
And<SOLine.lineNbr, Equal<Required<SOLine.lineNbr>>>>>>
.Select(graph, orderType, orderNbr, lineNbr);
foreach (PXResult<SOLine, SOOrder, SOLineSplit, INItemPlan> result in Results)
{
INItemPlan plan = result;
INItemPlanExt planExt = PXCache<INItemPlan>.GetExtension<INItemPlanExt>(plan);
planExt.UsrTagID = tagID;
graph.Caches[typeof(INItemPlanExt)].Update(planExt);
graph.Caches[typeof(INItemPlanExt)].Persist(PXDBOperation.Update);
PXTrace.WriteInformation("Setting: {0} {1}", plan.PlanID, planExt.UsrTagID);
}
Results = PXSelectJoin<SOLine,
InnerJoin<SOOrder, On<SOOrder.orderNbr, Equal<SOLine.orderNbr>,
And<SOOrder.orderType, Equal<SOLine.orderType>>>,
InnerJoin<SOLineSplit, On<SOLineSplit.orderType, Equal<SOLine.orderType>,
And<SOLineSplit.orderNbr, Equal<SOLine.orderNbr>,
And<SOLineSplit.lineNbr, Equal<SOLine.lineNbr>>>>,
InnerJoin<INItemPlan, On<INItemPlan.planID, Equal<SOLineSplit.planID>>
>>>,
Where<SOLine.orderType, Equal<Required<SOLine.orderType>>,
And<SOLine.orderNbr, Equal<Required<SOLine.orderNbr>>,
And<SOLine.lineNbr, Equal<Required<SOLine.lineNbr>>>>>>
.Select(graph, orderType, orderNbr, lineNbr);
foreach (PXResult<SOLine, SOOrder, SOLineSplit, INItemPlan> result in Results)
{
INItemPlan plan = result;
INItemPlanExt planExt = PXCache<INItemPlan>.GetExtension<INItemPlanExt>(plan);
PXTrace.WriteInformation("Poll DB: {0} {1}", plan.PlanID, planExt.UsrTagID);
}
}
While I have had a problem in the past where I made the DAC definition PXString instead of PXDBString, I did verifiy the DAC to ensure that I didn't repeat the error this time.
The solution is in the comments, so pulling the comments from HB_ACUMATICA out into a solution for others to find easily...
graph.Caches[typeof(INItemPlan)].Update(plan);
graph.Caches[typeof(INItemPlan)].Persist(plan, PXDBOperation.Update);
When working with the cache, do not reference the Ext DAC. Work with the base cache. Acumatica knows how to connect the extended cache data for you. A combination of referencing the plan object in the Persist and also referencing the base DAC instead of the Ext DAC resulted in my data being written to the database table as desired.

"Order By" When Retrieving From Acumatica Web Service API

I was wondering if there was a way to add an "Order By" clause when retrieving data from Acumatica through the Web Service API?
IN202500Content IN202500 = oScreen.IN202500GetSchema();
oScreen.IN202500Clear();
Command[] oCmd = new Command[] {IN202500.StockItemSummary.ServiceCommands.EveryInventoryID,
IN202500.StockItemSummary.InventoryID,
IN202500.StockItemSummary.Description,
IN202500.StockItemSummary.ItemStatus,
IN202500.GeneralSettingsItemDefaults.ItemClass,
IN202500.GeneralSettingsItemDefaults.LotSerialClass,
IN202500.PriceCostInfoPriceManagement.DefaultPrice,
};
Filter[] oFilter = new Filter[] {new Filter
{
Field = new Field {ObjectName = IN202500.StockItemSummary.InventoryID.ObjectName,
FieldName = "LastModifiedDateTime"},
Condition = FilterCondition.GreaterOrEqual,
Value = SyncDate
}
};
String[][] sReturn = oScreen.IN202500Export(oCmd, oFilter, iMaxRecords, true, false);
I would like to sort the results for example by DefaultPrice, so that I can retrieve the Top 200 most expensive items in my list (using iMaxRecords = 200 in this case)
I haven't seen any parameters that allows me to do the sorting yet.
I ran into this when I developed a round robin assignment system and the short answer is using the Acumatica API you cant do a sort on the results you have to do it outside of the API (This info came from a friend closely tied to the Acumatica product).
I came up with two options:
Query your DB directly... There are always reasons not to do this but it is much faster than pulling the result from the API and will allow you to bypass the BQL Acumatica uses and write an SQL statement that does EXACTLY what you want providing a result that is easier to work with than the jagged array Acumatica sends.
You can use some Linq and build a second array[][] that is sorted by price and then trim it to the top 200 (You would need all results from Acumatica first).
// This is rough but should get you there.
string[][] MaxPriceList = sReturn.OrderBy(innerArray =>
{
if () // This is a test to make sure the element is not null
{
decimal price;
if (//test decimal is not null))
return price;
}
return price.MaxValue;
}).Take(200).ToArray(); //Take 200 is a shot but might work

Document not available in query direct after store

I'm trying to store a "Role" object and then get a list of Roles, as shown here:
public class Role
{
public Guid RoleId { get; set; }
public string RoleName { get; set; }
public string RoleDescription { get; set; }
}
//Function store:
private void StoreRole(Role role)
{
using (var docSession = docStore.OpenSession())
{
docSession.Store(role);
docSession.SaveChanges();
}
}
// then it return and a function calls this
public List<Role> GetRoles()
{
using (var docSession = docStore.OpenSession())
{
var Roles = from roles in docSession.Query<Role>() select roles;
return Roles.ToList();
}
}
However, in the GetRoles I am missing the last inserted record/document. If I wait 200ms and then call this function the item is there.
So I am not in sync. ?!
How can I solve this, or alternately how could I know when the result is in the document store for querying?
I've used transactions, but cannot figure this out. Update and delete are just fine, but when inserting I need to delay my 'List' call.
You are treating RavenDB as if it is a relational database, and it isn't. Load and Store are ACID operations in RavenDB, Query is not. Indexes (necessary for queries) are updated asynchronously, and in fact, temporary indexes may have to be built from scratch when you do a session.Query<T>() without a durable index specified. So, if you are trying to query for information you JUST stored, or if you are doing the FIRST query that requires a temporary index to be created, you probably won't get the data you expect.
There are methods of customizing your query to wait for non-stale results but you shouldn't lean on these too much because they're indicative of a bad design - it is better to figure out a better way to do the same thing in a way that embraces eventual consistency, either changing your model (so you get consistency via Load/Store - perhaps you could have one document that defines ALL of the roles in a list?) or by changing the application flow so you don't need to Store and then immediately Query.
An additional way of solving this is to query the index with WaitForNonStaleResultsAsOfLastWrite() turned on inside the save function. That way when the save is completed the index will be updated to at least include the change you just made.
You can read more about this here

RavenDB: Raven Query not returning correct count with document authorization

public class EngineInfo
{
public int Id{get;set;}
public int? AircraftId { get; set; }
public string SerialNumber { get; set; }
public int Position { get; set; }
public string RegNumber { get; set; }
}
// Here is the code which uses the above model. I have 17,000 documents with this model
ravenSession.Store(new AuthorizationUser
{
Id = "Authorization/Users/1",
Name = "user-1",
Permissions =
{
new OperationPermission
{
Allow = true,
Operation = "EngineInfos/View",
Tags = "Company/100"
}
}
});
1. var query = ravenSession.Query<EngineInfo>();
// When I log query.Count(), I see all the documents count ie., 17000, This is ignoring the authorization I set in the before statement. If I add where clause to the above statement it is working and I could see the correct count. But I want to get all the documents for which the user has authorization to.
2. var query = ravenSession.Query<EngineInfo>().ToList();
Now, I get the correct count considering authorization. But the problem is unless I mention Take(x), it will not return all the results.
I tried with
RavenQueryStatistics queryStats;
query.Statistics(out queryStats);
queryStats.TotalResults
I still could not get the authorizes results. I get all the count.
Could you please help me figuring out in finding TotalCount of the query results without loading all records?
My requirement is to display all engines in an searchable ExtJS paging grid. I need to know the total count of the records to display calculate and display the number of pages(page count is fixed).
This is by design, see http://ravendb.net/docs/intro/safe-by-default.
session.Query<Post>().Count() will give you the count of all the posts on the server, while session.Query<Post>().ToList().Count() will give the count of the posts that was fetched to the client.
By default, RavenDB apply .Take(128) to the query, in order to encourage you to do paging and be safe by default. If you want to get more then that you need to specify how much to take, like .Take(1024), but by default the server will not return more then 1024 items at once. You can configure the server to do so, but this is not recommended. You much better use paging as the user cannot handle that much on info at once anyway.
What you're seeing is that Raven's QueryStatistics ignore the Authorization bundle. This has been reported in the Raven Google Group.
As far as I can tell, there isn't, at the time of this writing, a reliable way to get the total count of authorized documents for a query. It seems to me that the Authorization bundle should include some support for this.
I'll look into it and update this answer as I find out more.

Resources