I am trying to do a few simple calculations on both the SOLine level
(document details grid) and the SOOrder Level (Order Summary Area).
Thanks to another stackoverflow user, I got the first SOLine
calculation figured out (Field called Total profit (Ext Price - Ext
Cost). Now, I am trying to calculate the percentage of the Total
profit divided by the Ext price (on the SOLine level) but am having
issues. Is this something that’s possible? The function doesn’t seem
to recognize the new field. So I did it like this and it’s giving a
“cannot divide by zero” error…. Is this wrong?
Here’s my first try with the new custom field (the TotalProfit field is already defined and
confirmed working - but I am having issues with this GP% one):
[PXUIField(DisplayName = "GP %", Enabled = false)]
[PXFormula(typeof(Div<SOLine.curyTotalProfit, SOLine.curyLineAmt>))]
[PXDefault(TypeCode.Decimal, "0.0")]
And here is the second one I tried that takes the Total profit formula
and divides the result by the Ext price (curylineamt)
[PXUIField(DisplayName = "GP %", Enabled = false)]
[PXFormula(typeof(Div<Sub<SOLine.curyLineAmt, SOLine.curyExtCost>,SOLine.curyLineAmt>))]
[PXDefault(TypeCode.Decimal, "0.0")]
The second thing I need to do is to display (on the Order Summary
level) 1. The sum of all the SOLines TotalProfit and 2. The percentage
of each line's total profit divided by the total ext Cost.
- For this one, I know that I have to define the pxparent attribute, but all of my attempts have failed. Here is what I tried:
I created two new fields -( one for the currency consideration):
**(FIRST FIELD):**
[PXParent ( typeof(Select<SOOrder,Where<SOLine.OrderNbr, Equal<Current<SOOrder.OrderNbr>>>>))]
[PXDBCurrency(typeof(SOOrder.curyInfoID), typeof(SOOrder.usrOrderTotalProfit))]
[PXUIField(DisplayName = "Total Profit", Enabled = false)]
[PXFormula(null, typeof(SumCalc<SOLine.usrCuryTotalProfit>))]
[PXDefault(TypeCode.Decimal, "0.0")]
**(SECOND FIELD):**
[PXDBDecimal(4)]
[PXDefault(TypeCode.Decimal, "0.0")]
When I try to publish this, I get errors saying that the type name
does not exist and one that says it is a property but is used like a
type.
These seem like they should be built into Acumatica, but they are not.
Any help is greatly appreciated.
Update:
Hi Dmitry, Thanks so much for your response. I am still stuck on the GP% one...I am trying to calculate my custom field (UsrCuryTotalProfit) divided by curyLineAmt. This is my attribute for the UsrCuryTotalProfit field:
[PXDBCurrency(typeof(SOLine.curyInfoID), typeof(SOLineExt.usrTotalProfit))]
[PXUIField(DisplayName = "Total Profit", Enabled = false)]
[PXFormula(typeof(Sub<SOLine.curyLineAmt, SOLine.curyExtCost>))]
[PXDefault(TypeCode.Decimal, "0.0")]
So, I used this for my GP% field:
[PXUIField(DisplayName = "GP %", Enabled = false)]
[PXFormula(typeof(Div<SOLineExt.usrTotalProfit, SOLine.curyLineAmt>))]
[PXDefault(TypeCode.Decimal, "0.0")]
And it published fine, but when I go to the Sales Order screen and try to add a line in the grid, it gives me an error: "Attempted to Divide by Zero". Am I still doing something wrong here?
I wonder if this has something to do with the row not having any cost or price info yet, but the formula tries to divide before I enter anything in...Not sure how to fix.
[PXUIField(DisplayName = "GP %", Enabled = false)]
[PXFormula(typeof(Div<SOLineTotalProfit, SOLine.curyLineAmt>))]
[PXDefault(TypeCode.Decimal, "0.0")]
Is this actual code you are trying to publish?
I think there is a typo in this code. Should be something like this:
[PXFormula(typeof(Div<SOLine.curyTotalProfit, SOLine.curyLineAmt>))]
You also have an incorrect parent attribute. You should select parent table that matches to the current record. Like that:
[PXParent(typeof(Select<SOOrder,Where<SOOrder.orderNbr, Equal<Current<SOLine.orderNbr>>>>))]
However, such attribute is already present in SOLine class, you don't need to place it again.
Your Formula
[PXFormula(null, typeof(SumCalc<SOLine.usrCuryTotalProfit>))]
should be placed on the SOLine.usrCuryTotalProfit field. In the second parameter you should specify the field of the parent DAC where result should be stored.
Parent and formula attributes are covered well in T200 training Lesson 7.
So there are several errors in your code. You should also pay more attention to the case of the first letters.
Here is the quote from T100 training:
The abstract class and property have the same name that differ by the case of the first letter. By
convention, the abstract class name starts with a lowercase letter, while the property name starts with
the same uppercase letter. In BQL statements, the data field is referred to by the abstract class name,
Visual studio is nice tool to fix typos and case of the letters.
You can easily open your customization in visual studio:
Go to the Customization Projects
Click on your project
Click Extension Library -> Create new
Browser will download *.bat file that will open your customization in visual studio.
You will be able to see and fix compilation errors, use autocomplete and syntax highlighting.
Update:
I wonder if this has something to do with the row not having any cost or price info yet, but the formula tries to divide before I enter anything in...Not sure how to fix.
Looks like you are right. You can fix it like that:
[PXFormula(typeof(Switch<Case<Where<SOLine.curyLineAmt, Equal<decimal0>>, decimal0>, Div<SOLineExt.usrTotalProfit, SOLine.curyLineAmt>>))]
Also I think you should use SOLineExt.curyUsrTotalProfit in this case.
Related
I’m trying to get the Attributes per ItemClassId to place them on a grid in the look up dialog box (Sales Order Entry).
enter image description here
I’m using the CSAttributeGroupList method:
public PXFilter<SOSiteStatusFilter> ItemSearchClass;
{
[PXViewName("Attributes")]
CSAttributeGroupList<INItemClass, SOSiteStatusFilter> Attributes;
public PXFilter<SOSiteStatusFilter> ItemSearchClass;
[PXViewName("Attributes")]
CSAttributeGroupList<INItemClass, SOSiteStatusFilter> Attributes;
}
I can't find the proper way to bring the attributes from each ItemClassID.
I have tryed:
public CSAttributeGroupList<INItemClass, SOSiteStatusFilter> Attributes;
But it doesn't work either.
I would first put SyncPosition=True on the grid to the right, to ensure that the current inventory item is selected. From there, you would want to use the code that is found in InventoryItemMaintBase:
public CRAttributeList<InventoryItem> Answers;
This would pull the current inventory item's attributes, and should automatically link to the items answers. You may need to mess with getting the grid on the right to refresh after the row is selected for the inventory item, if it does not do it automatically.
I'm using Acumatica 2020R1 and I'm adding side panels. Is there anyway, for example on the PO form (PO301000), that I could link a side panel to update when clicking on the Document Details?
I actually discovered a way to do this on Acumatica 2020R1. Here's how I did it:
Edit: I did this for the SOOrderEntry screen, creating a side panel that looks at the SOOrder but uses parameters from the SOLine. This method will work for any two tables with a master-detail relationship by replacing SOOrder & SOLine accordingly.
Part A:
Create virtual fields on the SOOrder DAC that correlate to the SOLine Parameters you need
Update these as lines are selecting/updated using event handlers
pass these fields as the params to your side panel actions as you normally would
While It seems like this alone would work, the side panel actually doesn’t refresh when a new SOLine is selected. We’re going to have to get creative to resolve this issue.
Part B:
Create an additional virtual field on the SOOrder DAC to keep track of which side panel was last opened (I will explain how to track this later). I use a string field to do so. This is especially important if you have more than one side panel on your screen.
Next you want to define a standard graph action that will update this field. Make it Visible = false, since you don’t actually want this button to show. We will call if from behind the scenes later. This is the method I use:
public PXAction<SOOrder> SidePanelCallback;
[PXButton]
[PXUIField(DisplayName = "SidePanelCallback", Visible = false)]
public virtual IEnumerable sidePanelCallback(PXAdapter adapter)
{
SOOrder doc = Base.Document.Current;
if (doc == null) return adapter.Get();
var docExt = doc.GetExtension<SOOrderExt>();
docExt.UsrSidePanelDetailParamsRefresh = adapter.CommandArguments;
return adapter.Get();
}
*Note: docExt.UsrSidePanelDetailParamRefresh is the variable I for the previous step
Now you need a way to trigger your code when your side panel actions are called. To do this we’ll use the following JavaScript
function commandResult(ds, context) {
var ds = px_alls['ds'];
switch (context.command)
{
case ('JustAction'):
ds.executeCallback('GraphAction', 'parameter value');
break;
}
}
You want to replace JustAction with the internal name of your side panel action. it should look like this: NavigateToLayer$DB000028
where DB000028 is the target ScreenID for your sidepanel. If you want to make sure your actions follow the same naming convention, you can check it by adding breakpoints and checking with the debugger.
You should replace GraphAction with the name of the graph action you created in the previous step. In our case SidePanelCallback
Then finally for the parameter value pass whatever you’d like to use to identify the opened side panel. This will be stored in UsrSidePanelDetailParamRefresh, or your equivalent.
You can create a case statement for each side panel you want this functionality for, so you only have to do all this once per graph!
Then finally, attach your script to the client event ‘commandPerformed’ on your data source.
Next we will define another graph action to do the actual refresh of the side panel. Mine looks like this:
public PXAction<SOOrder> SidePanelRefresh;
[PXButton]
[PXUIField(DisplayName = "SidePanelRefresh", Visible = false)]
public virtual IEnumerable sidePanelRefresh(PXAdapter adapter)
{
SOOrder doc = Base.Document.Current;
if (doc == null) return adapter.Get();
var docExt = doc.GetExtension<SOOrderExt>();
Dictionary<string, string> parameters = new Dictionary<string, string>();
string[] args = adapter.CommandArguments.Split('*');
switch (docExt.UsrSidePanelDetailParamsRefresh)
{
case ("Component Inventory"):
parameters.Add("Kit", args[0]);
parameters.Add("SiteID", args[1]);
throw new PXRedirectToGIRequiredException("Component Inventory", parameters) { Mode = PXBaseRedirectException.WindowMode.Layer };
}
return adapter.Get();
}
Note that for my use case the parameters I am passing cannot contain the character ‘*’, so I use it as a delimiter
And again, you can define as many case statements as you need for each of your dynamic side panels.
And now finally we need javascript again to call this action. Using event handlers are too slow for this type of refresh, they will always pass the old SOLine parameters. If you know a way to avoid javascript here, I’d love to hear it, as doing callbacks like this does have a heavy overhead. Because of this, be careful where you attach this script to, I actually attach it to the grid’s RowSelectorClick client event rather than AfterRowChange for this reason, and train my users to select rows in this way (by clicking the arrow to the left of each row) so we don’t impact the rest of the company for the minority that use this feature. I’d recommend this if you're working on a high traffic screen such as SO301000.
Anyway, here’s the javascript:
function Side_Panel_Refresh() {
px_alls["ds"].executeCallback("SidePanelRefresh", this.func.caller.arguments[2].row.getCell("InventoryID").getValue() + "*" + this.func.caller.arguments[2].row.getCell("SiteID").getValue());
}
SidePanelRefresh: Name of our graph action from the previous step.
InventoryID & SiteID are my parameters.
And again, using ‘*’ as a delimiter.
Anyway, I hope this helps anyone looking to make dynamic side panels. If anyone has improvements on this method I’d love to hear constructive feedback.
Happy development!
It is a new feature. Side panel were restricted to generic inquiry. In version 2020 you still need a generic inquiry to configure the side panel but they can be viewed in data entry forms too.
The feature is implemented with wizards so it's pretty limited in what it can do. Currently you are limited to the choices available in navigation parameter. It seems like it's not possible to add details entity there. Maybe there's a way but I'm not aware of it.
Check documentation reference link for User Interface: Side Panels for Data Entry Forms (content copied below):
https://help-2020r1.acumatica.com/Help?ScreenId=ShowWiki&pageid=a11aa71b-5f18-4c8f-9c69-350f2c7a89f4
Adding a Side Panel to the Data Entry Form
A customizer can add multiple side panel navigation paths to a data entry form.
To add a side panel, the customizer navigates to the Customization Projects (SM204505) form and creates a new project.
In the Customization Project Editor, the customizer selects the screen for which the side panel is planned to be added.
On the Screens > Form ID > Actions, the customizer adds an action of the Navigation:
Side panel type (1), specifies general settings (2), adds destination screen (3), and specifies parameters (4), if needed (for instance, in the screenshot below, Business Account was selected as a parameter so that the side panel displayed the cases of the specific customer selected on the data entry form).
Example of a side panel on the Customers form
To apply the changes, the customizer must publish the created customization project.
I would like to filter the records shown on the Projects screen. I've been given the directive to see if limiting the Selector for projects (re-writing the PXSelector for the ProjectID?) would also limit the records that show up on the screen, i.e., a user would not be able to navigate to records that aren't displayed by the Selector. I don't think that's the case, as the screen's view is not limited by what is chosen by the Selector - but I wanted to verify this.
Also - as far as limiting the records that show up in the Selector (possibly re-writing the Selector/BQL using a where clause?) - I looked at the source DAC, and for the life of me I can't figure it out. There is a PXSelector on the ContractID, which doesn't use the SubstituteKey that I'm familiar with, and the ContractCD also has several attributes with which I'm unfamiliar - namely the PXRestrictor AND the PXDimensionSelector.
Bottom line:
1.) What's the best way to limit the records for Project shown in the screen's Selector? Can I just add to the PXRestrictor attribute?
2.) Would limiting the Selector's results also limit what the user can navigate to on the screen using the navigation buttons?
Whenever you need to restrict access to primary records on a data entry screen, it's always required to customize both the lookup DAC key field and the primary data view. By design, in Acumatica key fields in DAC and the primary data view are completely independent, that is why it's required to modify both pieces to achieve the desired result.
For example, to deny access to canceled projects on the Projects screen, you should add PXRestrictorAttribute to PMProject's ContractCD field and also re-declare Project primary data view:
public class ProjectEntryExt : PXGraphExtension<ProjectEntry>
{
public class cancelled : Constant<string>
{
public cancelled() : base(ProjectStatus.Cancelled) {; }
}
[PXViewName(Messages.Project)]
public PXSelect<PMProject, Where<PMProject.baseType, Equal<PMProject.ProjectBaseType>,
And<PMProject.nonProject, Equal<False>, And<PMProject.isTemplate, Equal<False>,
And<PMProject.status, NotEqual<cancelled>,
And<Match<Current<AccessInfo.userName>>>>>>>> Project;
[PXMergeAttributes(Method = MergeMethod.Append)]
[PXRestrictor(typeof(Where<PMProject.status, NotEqual<cancelled>>),
"Given Project/Contract '{0}' is cancelled", typeof(PMProject.contractCD))]
public void PMProject_ContractCD_CacheAttached(PXCache sender) { }
}
I have a custom table for historical data. It's a one-time data dump directly into SQL so I'd prefer not to have to create a screen for it.
I have three columns in my table:
CompanyID INT
InvoiceNbr NVARCHAR(40)
Amount DECIMAL(19,4)
I create a new Customization Project and added a new DAC to the Code area that is linked to my custom table:
https://i.imgur.com/6mNjdou.png (Screenshot #1)
https://i.imgur.com/IdNLJkR.png (Screenshot #2)
Then I create a Generic Inquiry, but I don't get the Paper Clip and Note icons. I was hoping to use the Paper Clip to upload documents and attach them to the records in my custom table.
So, I added another column to my custom table:
NoteID UNIQUEIDENTIFIER
And I re-added the new DAC which now generates this code:
https://i.imgur.com/QvpWB5X.png (Screenshot #3)
Now I get the Paper clip and Note icons in my Generic Inquiry:
https://i.imgur.com/olCglBB.png (Screenshot #4)
I can add a note or attach a document and the icons change color which makes it seem like the notes and documents got attached to the records. But when I refresh the page, everything goes away. Also, I can tell that nothing is getting stored in the database.
So the Paper Clip and Note icons don't work.
I'm wondering if it's possible to get the Paper Clip and Note icons to work in my Generic Inquiry without building a custom screen. Is this possible?
Tim, for Notes and Attachments to work properly, your NoteID field should be decorated with the PXNote attribute, instead of the default combination of PXDBGuid- and PXUIFieldAttribute. Will everything work as expected after you replace NoteID field declaration with the code snippet below and republish the customization?
public abstract class noteID : PX.Data.IBqlField
{
}
[PXNote()]
public virtual Guid? NoteID { get; set; }
Currently I work with screen ap303000.aspx. I want to add new attribute to tab "Attributes". This tab is binded to view "Answers" which is declared in following way:
[PXViewName(CR.Messages.Answers)]
public CRAttributeList<Vendor> Answers;
little bit digging with metadata viewer in CRAttributeList shows that CRAttributeList is inherited from PXSelectBase and definitely reads records from CSAnswers table:
public class CRAttributeList<TReference> : PXSelectBase<CSAnswers> where TReference : IBqlTable
which gives me hint, that I need to insert something into table CSAnswers. Table CSAnswers by it's structure also doesn't give me enough information what should I put in table CSAnswers in order to have some attribute as bool and available to all Vendors and to turn it on by default?
You need to specify list of Attributes at Vendor Class (AP201000) level first.
Once Vendor class is specified, Attributes specified at Class level will be listed for which value can be assigned.