How to add a new business object to Universal Search in Acumatica - acumatica

I have been able to add additional search fields to existing entities in the universal search of Acumatica. However, adding a similar definition to NoteID on a custom table does not add the object to the Rebuild Full-Text Entity Index screen and does not make the custom data searchable.
I added a one-to-many list of selectable attributes to my Stock Items screen, and the values need to be included in the universal search. The syntax on the NoteID field looks identical to other source code, and the code compiles cleanly.
It appears the entities to be included are returned by PXSearchableAttribute.GetAllSearchableEntities(), but it does not appear to be in source code to see how this works. Are the entities fixed, or can I add my own for universal search?
How do I get data from my custom table to be searchable?

Full-Text Entity Index requires the entity to have a NoteID field, decorated with the usual PXNote attribute as well as PXSearchable. Here's an example coming from the APInvoice DAC:
[PXSearchable(SM.SearchCategory.AP, "AP {0}: {1} - {3}", new Type[] { typeof(APInvoice.docType), typeof(APInvoice.refNbr), typeof(APInvoice.vendorID), typeof(Vendor.acctName) },
new Type[] { typeof(APInvoice.invoiceNbr), typeof(APInvoice.docDesc)},
NumberFields = new Type[] { typeof(APInvoice.refNbr) },
Line1Format = "{0:d}{1}{2}", Line1Fields = new Type[] { typeof(APInvoice.docDate), typeof(APInvoice.status), typeof(APInvoice.invoiceNbr) },
Line2Format = "{0}", Line2Fields = new Type[] { typeof(APInvoice.docDesc) },
MatchWithJoin = typeof(InnerJoin<Vendor, On<Vendor.bAccountID, Equal<APInvoice.vendorID>>>),
SelectForFastIndexing = typeof(Select2<APInvoice, InnerJoin<Vendor, On<APInvoice.vendorID, Equal<Vendor.bAccountID>>>>)
)]
[PXNote(ShowInReferenceSelector = true)]
public override Guid? NoteID
The GetAllSearchableEntities function does nothing more than looping through every table using ServiceManager.TableList, and looking for note fields that are decorated with the expected attributes. The table list is cached on application startup, so depending on how you published your changes a reload may not have been completed. If you have already configured your field as described above, you could try doing iisreset from the command-line to see if it solves your problem.

Related

Why I'm getting Muti-part identifier error in view query(after added the newly created extension field into the query?)

Multi-part identifier error
I newly created one extension field in Contacts screen (UsrLocationCD int field). After creatimg that field I added that field into the view query and I got the above error.
The concept is the "Customer and Location ID" (Customer Location screen) should match in Contacts screen "Business Account and Location ID" (Location ID, newly added). After this condition is satisfied that related Contact ID should display in Customer Location screen under the Contacts Tab.
Full concept
This the query what I wrote:
[PXViewName(Messages.Contacts)]
[PXFilterable]
[PXViewSavedDetailsButton(typeof(Location))]
public PXSelectJoin<Contact,
LeftJoin<Address, On<Address.addressID, Equal<Contact.defAddressID>>>,
Where<Contact.bAccountID, Equal<Current<Location.bAccountID>>,
And<Where<ContactExt.usrLocationCD, Equal<Location.locationID>,
And<Where<Contact.contactType, Equal<ContactTypesAttribute.person>,
Or<Contact.contactType, Equal<ContactTypesAttribute.lead>>>>>>>> Contacts;
here is the newly created extension field:
public class ContactExt : PXCacheExtension<PX.Objects.CR.Contact> /*, IBqlTable*/
{
#region UsrLocationCD
[PXDBInt()]
[PXUIField(DisplayName = "Location ID")]
[PXSelector(
typeof(Search<Location.locationID, Where<Location.bAccountID,
Equal<Current<Contact.bAccountID>>>>),
SubstituteKey = typeof(Location.locationCD), ValidateValue = false)]
public virtual int? UsrLocationCD { get; set; }
public abstract class usrLocationCD : PX.Data.BQL.BqlInt.Field<usrLocationCD> { }
#endregion
}
I'm sharing one point here that newly created extension field is not creating any problem in the Contacts screen, successfully I'm able to saving the record you can see the below imgs.
Before saving the record
After saving the record
In the contacts screen location id field is "Int".
Where is the mistake and how to overcome this issue?
Your PXSelect is missing a Current<> on where you added in your usrLocationCD.
Original line with missing Current<>:
And<Where<ContactExt.usrLocationCD, Equal<Location.locationID>,
After adding the missing Current<> back in:
[PXViewName(Messages.Contacts)]
[PXFilterable]
[PXViewSavedDetailsButton(typeof(Location))]
public PXSelectJoin<Contact,
LeftJoin<Address, On<Address.addressID, Equal<Contact.defAddressID>>>,
Where<Contact.bAccountID, Equal<Current<Location.bAccountID>>,
And<Where<ContactExt.usrLocationCD, Equal<Current<Location.locationID>>,
And<Where<Contact.contactType, Equal<ContactTypesAttribute.person>,
Or<Contact.contactType, Equal<ContactTypesAttribute.lead>>>>>>>> Contacts;
When selecting data, you always must connect the referenced DAC's in some way... either by joining directly to another table selected in the join, by joining the field to a Current value (such as a field in a parent view), or by supplying a parameter that you pass in.
Also, for consistency, I'd recommend changing the name of your new field from usrLocationCD to usrLocationID. ID means "identifier" and CD means "code". LocationID (the identifier) is the integer field used to identify the Location record, in this case. LocationCD is the field of the Location record that contains the Location Code that we normally see in the display. When another Acumatica developer looks at the code above, the first impression is that you are trying to relate a string field to an integer field. Technically, as long as your field type matches on both sides of the equals then it will work, but consistency is important in coding standards.
In the above code, join is missed for location DAC. I hope this may help you.
[PXViewName(PX.Objects.CR.Messages.Contacts)]
[PXFilterable]
[PXViewSavedDetailsButton(typeof(Location))]
public PXSelectJoin<Contact,
LeftJoin<Address, On<Address.addressID, Equal<Contact.defAddressID>>,
LeftJoin<Location, On <Location.bAccountID,Equal<Contact.bAccountID>>>>,
Where<Contact.bAccountID, Equal<Current<Location.bAccountID>>,
And<Where<ContactExt.usrLocationCD, Equal<Location.locationID>,
And<Where<Contact.contactType, Equal<ContactTypesAttribute.person>,
Or<Contact.contactType, Equal<ContactTypesAttribute.lead>>>>>>>> Contacts;

Acumatica GetList error: Optimization cannot be performed.The following fields cause the error: Attributes.AttributeID

Developer's version of Acumatica 2020R1 is installed locally. Data for sample tenant MyTenant from training for I-300 were loaded, and WSDL connection established.
DefaultSoapClient is created fine.
However, attempts to export any data by using Getlist cause errors:
using (Default.DefaultSoapClient soapClient =
new Default.DefaultSoapClient())
{
//Sign in to Acumatica ERP
soapClient.Login
(
"Admin",
"*",
"MyTenant",
"Yogifon",
null
);
try
{
//Retrieving the list of customers with contacts
//InitialDataRetrieval.RetrieveListOfCustomers(soapClient);
//Retrieving the list of stock items modified within the past day
// RetrievalOfDelta.ExportStockItems(soapClient);
RetrievalOfDelta.ExportItemClass(soapClient);
}
public static void ExportItemClass(DefaultSoapClient soapClient)
{
Console.WriteLine("Retrieving the list of item classes...");
ItemClass ItemClassToBeFound = new ItemClass
{
ReturnBehavior = ReturnBehavior.All,
};
Entity[] ItemClasses = soapClient.GetList(ItemClassToBeFound);
string lcItemType = "", lcValuationMethod = "";
int lnCustomFieldsCount;
using (StreamWriter file = new StreamWriter("ItemClass.csv"))
{
//Write the values for each item
foreach (ItemClass loItemClass in ItemClasses)
{
file.WriteLine(loItemClass.Note);
}
}
The Acumatica instance was modified by adding a custom field to Stock Items using DAC, and by adding several Attributes to Customer and Stock Items.
Interesting enough, this code used to work until something broke it.
What is wrong here?
Thank you.
Alexander
In the request you have the following line: ReturnBehavior = ReturnBehavior.All
That means that you try to retrieve all linked/detail entities of the object. Unfortunately, some object are not optimized enough to not affect query performance in GetList scenarios.
So, you have to options:
Replace ReturnBehavior=All by explicitly specifying linked/detail entities that you want to retrieve and not include Attributes into the list.
Retrieve StockItem with attributes one by one using Get operation instead of GetList.
P.S. The problem with attributes will most likely be fixed in the next version of API endpoint.
Edit:
Code sample for Get:
public static void ExportItemClass(DefaultSoapClient soapClient)
{
Console.WriteLine("Retrieving the list of item classes...");
ItemClass ItemClassToBeFound = new ItemClass
{
ReturnBehavior = ReturnBehavior.Default //retrieve only default fields (without attributes and other linked/detailed entities)
};
Entity[] ItemClasses = soapClient.GetList(ItemClassToBeFound);
foreach(var entity in ItemClasses)
{
ItemClass itemClass= entity as ItemClass;
ItemClass.ReturnBehavior=ReturnBehavior.All;
// retrieve each ItemClass with all the details/linked entities individually
ItemClass retrievedItemCLass = soapClient.Get(itemClass);
}

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();

Adding Excel upload feature to Employee Timecard Entry doesn't show ProjectTask field

I have a customization to the Employee Timecard Entry screen (EP305000) which enables the Excel upload functionality into the Details tab grid. I did this by adding the attribute [PXImport(typeof(EPTimeCard))] to the 'Activities' view re-declaration in a TimeCardMaint BLC extension as follows:
[PXImport(typeof(EPTimeCard))]
[PXViewName(PX.Objects.EP.Messages.TimeCardDetail)]
public PXSelectJoin<EPTimecardDetail,
InnerJoin<CREmployee,
On<CREmployee.userID, Equal<EPTimecardDetail.ownerID>>,
LeftJoin<CRActivityLink,
On<CRActivityLink.noteID, Equal<EPTimecardDetail.refNoteID>>,
LeftJoin<CRCase,
On<CRCase.noteID, Equal<CRActivityLink.refNoteID>>,
LeftJoin<PX.Objects.AR.Customer,
On<PX.Objects.AR.Customer.bAccountID, Equal<CRCase.customerID>>,
LeftJoin<PX.Objects.EP.TimeCardMaint.ContractEx,
On<PX.Objects.EP.TimeCardMaint.ContractEx.contractID, Equal<CRCase.contractID>>,
LeftJoin<PMProject,
On<PMProject.contractID, Equal<EPTimecardDetail.projectID>>>>>>>>,
Where<CREmployee.bAccountID, Equal<Current<EPTimeCard.employeeID>>,
And<EPTimecardDetail.weekID, Equal<Current<EPTimeCard.weekId>>,
And<EPTimecardDetail.trackTime, Equal<True>,
And<EPTimecardDetail.approvalStatus, NotEqual<ActivityStatusListAttribute.canceled>,
And<Where<EPTimecardDetail.timeCardCD, IsNull, Or<EPTimecardDetail.timeCardCD, Equal<Current<EPTimeCard.timeCardCD>>>>>>>>>,
OrderBy<Asc<EPTimecardDetail.date>>> Activities;
I also set the 'AllowImport' property of the grid to 'True'. This seems to work ok, except that the 'ProjectTask' field of the upload does not allow mapping - i.e., if you go through the import process, when you get to the field mapping part, you can't map the Excel field for ProjectTask to the grid's ProjectTask. It just doesn't show up.
Would this be because the source BLC has as delegate method for 'activities' that I didn't reproduce in my extension?
What could be the reason for not allowing mapping to the ProjectTask field?
Since the ProjectTask field is disabled by default, this was solved by adding a parameter to the [ProjectTask] attribute, called "AlwaysEnabled" via the CacheAttached event, as shown below:
public class TimeCardMaint_Extension : PXGraphExtension<TimeCardMaint>
{
[PXDefault(typeof(Search<PMTask.taskID, Where<PMTask.projectID, Equal<Current<TimeCardMaint.EPTimecardDetail.projectID>>, And<PMTask.isDefault, Equal<True>>>>), PersistingCheck = PXPersistingCheck.Nothing)]
[ProjectTask(typeof(TimeCardMaint.EPTimecardDetail.projectID),
BatchModule.TA,
DisplayName = "Project Task",
BqlField = typeof(PMTimeActivity.projectTaskID),
AlwaysEnabled = true)]
protected virtual void EPTimecardDetail_ProjectTaskID_CacheAttached(PXCache cache)
{
}

How to automatically create Note record in Acumatica?

I've noticed that whenever an AR Invoice gets saved, a record gets created in the Note table with the new invoice's note ID. Can you tell me how that is being accomplished? I'd like to get one of my screens to do the same thing. I guess there must be some kind of attribute on the either the DAC or the graph but I can't find it. I have the PXNote attribute on the NoteID column in my DAC but it does not cause a Note record to be automatically created.
Thanks for your help.
To have Note record automatically created when a new parent record gets saved, one should invoke the static PXNoteAttribute.GetNoteID<Field>(PXCache cache, object data) method when the parent record is inserted in the cache.
For example, to have Note record automatically created when a new Stock Item gets saved, you should subscribe to RowInserted handler for the InventoryItem DAC and call PXNoteAttribute.GetNoteID<Field>(...):
public class InventoryItemMaintExt : PXGraphExtension<InventoryItemMaint>
{
public void InventoryItem_RowInserted(PXCache sender, PXRowInsertedEventArgs e)
{
var noteCache = Base.Caches[typeof(Note)];
var oldDirty = noteCache.IsDirty;
PXNoteAttribute.GetNoteID<InventoryItem.noteID>(sender, e.Row);
noteCache.IsDirty = oldDirty;
}
}
The code snippet above can be incorporated into almost any custom BLC with a couple simple changes to replace InventoryItem with a custom DAC.

Resources