Is there a way to refresh the weight that appear in the Totals tab of a Sales Order in Acumatica? if you create a Sales Order and add an item with Weight say 2KG and save it, the Totals tab will correctly display 2KG. But then I changed the weight in the Inventory Item section to 5KG. Is there a way to get the Sales Order to update that weight (apart from deleting the item and adding it back)?
Thanks,
G
Weight is stored at SO Line in the database and calculated automatically on selecting Inventory item.
Acumatica will automatically refresh default value when you update InvendoryItemID or UOM.
Not sure that this is best approach, but i can suggest 2 ways:
1) If you need it with non-programmatic way, you can use Export Scenarios to update UOM (and than change it back) for all Open/Hold orders.
2) Another is way with customization - create an action that will update wheigt.
You can click this action automatically with using the same import scenarios or GI mass actions.
public class SOOrderEntry_Extension:PXGraphExtension<SOOrderEntry>
{
public PXAction<SOOrder> recalculateWeight;
[PXUIField(DisplayName = "Recalculate Weight", MapEnableRights = PXCacheRights.Update, MapViewRights = PXCacheRights.Update, Visible = false)]
[PXButton(SpecialType = PXSpecialButtonType.Process)]
public virtual void RecalculateWeight()
{
foreach(SOLine line in Base.Transactions.Select())
{
Base.Transactions.Cache.SetDefaultExt<SOLine.unitWeigth>(line);
Base.Transactions.Update(line);
}
}
}
Related
I have a process that creates records in the Sales Orders screen's Details grid based on two Header user fields: SiteID (Warehouse) and LocationID.
When the 'Create Shipment' process is initiated, the shipment is created which contains the SiteID in the Sales Orders grid - but since there is no locationID in the grid, this 'Create Shipment' process uses some default(?) LocationID, where I'd like to use the Header User field's LocationID.
My question is, how would I intercept this process to set the LocationID to something other than what it's defaulting to?
Thanks...
Update:
Using the virtual method:
SetShipmentFieldsFromOrder(SOOrder order, SOShipment shipment, Nullable<Int32> siteID, Nullable<DateTime> shipDate, String operation, SOOrderTypeOperation orderOperation, Boolean newlyCreated, SetShipmentFieldsFromOrderDelegate baseMethod)
I don't see any way to set the grid value for LocationID (i.e., there is no SOShipLine record to set a value in the virtual method. How would I do this?
There's a virtual method on the SOShipmentEntry graph called SetShipmentFieldsFromOrder, you can override that to update the CustomerLocationID as needed. The create shipment action calls SOShipmentEntry.CreateShipment which inserts the shipment and then calls the SetShipmentFieldsFromOrder method.
The system should be pulling the SOShipment.CustomerLocationID from the SOOrder.CustomerLocationID field by default though.
I believe the question is about defaulting of warehouse locations into the shipment lines and allocations rather than customer locations
Currently, shipment line selects location(s) by the following way
Originally (SelectLocationStatus), it selects location based on their pick priority (smaller value means higher priority)
After this method, the ResortStockForShipmentByDefaultItemLocation is executed. This method puts the default issue location for the item-warehouse combination (InItemSite) at the top of this list regardless of its pick priority.
I believe you should override this method to put the needed location to the top of the list instead of (or ahead of) the default issue location. Here is the code of this method of the SOShipmentEntry class for reference:
protected virtual void ResortStockForShipmentByDefaultItemLocation(SOShipLine newline, List<PXResult> resultset)
if (INSite.PK.Find(this, newline.SiteID)?.UseItemDefaultLocationForPicking != true)
return;
var dfltShipLocationID = INItemSite.PK.Find(this, newline.InventoryID, newline.SiteID)?.DfltShipLocationID;
if (dfltShipLocationID == null)
return;
var listOrderedByDfltShipLocationID = resultset.OrderByDescending(
r => PXResult.Unwrap<INLocation>(r).LocationID == dfltShipLocationID).ToList();
resultset.Clear();
resultset.AddRange(listOrderedByDfltShipLocationID);
}
Important! If we are talking about 21R2 version, there is the "Project-Specific Inventory" (materialManagement) feature which has its own extension of the SOShipmentEntry where some of the shipment creation methods (including the SelectLocationStatus) are overridden. The ResortStockForShipmentByDefaultItemLocation is not overridden, but if the customer uses this feature, I suggest to extend this extension rather than base SOSHipmentEntry:
namespace PX.Objects.PM.MaterialManagement
{
public class SOShipmentEntryMaterialExt : PXGraphExtension<SOShipmentEntry>
For example, if I change the "Original Budgeted Quantity" field to 9.00 (see first image below), I would like that to also change in the Revenue tab (see second image) without having to change the same thing twice.
Is there a way I can achieve this?
Cost Tab
Revenue Tab
Here are the details of the two fields I have highlighted in the images above:
Cost Tab Field
Revenue Tab Field
Let me know if I need to clarify anything or provide more information :)
Unfortunately I'm not very familiar with the Project Entry screen, and after a quick glance I couldn't find an easy way to tie the records from the Cost Budget tab to the Revenue Budget tab.
If you are planning on tying these two records together you may need some additional customization work to create the link that you are looking for if it doesn't already exist.
As far as the update itself, that is pretty straightforward and can be accomplished with a simple event handler, as demonstrated below.
namespace MyCompany.MyCustomization
{
public class ProjectEntryExtMyCustomization : PXGraphExtension<ProjectEntry>
{
public static bool IsActive() => true;
#region Actions
#endregion
#region Events
protected virtual void _(Events.FieldUpdated<PMCostBudget, PMCostBudget.qty> eventHandler)
{
PMCostBudget row = eventHandler.Row;
if (row is null) return;
// Replace the stub below with your PMRevenueBudget lookup
// using the link that you have defined.
PMRevenueBudget revenueDetail = new PMRevenueBudget();
// Assigns the PMRevenueBudget Qty field to match the PMCostBudget new value.
revenueDetail.Qty = eventHandler.NewValue as decimal?;
// Updates the PMRevenueBudget record in the cache.
Base.RevenueBudget.Update(revenueDetail);
}
#endregion
}
}
Don't forget to replace the line where I create a new PMRevenueBudget with the logic that you need for looking up the linked PMRevenueBudget record that you need. Let me know if you have any questions.
I'm new to Acumatica, could you please help me? I have too screens IN202500 (stock items) and SO301000(sales orders). I added a field to stock items and now I need to show a value from that field in grid column of sale orders for each stock items. I suppose that I need to use PXDefault attribute for this?
There are a number of ways you can do this. I'll provide 3 possibilities.
If your View used by the grid contains InventoryItem, you may be able simply to select your custom field from InventoryItem and add it directly to the screen. I'll assume this is not an option or you likely would have found it already.
Create a custom field in a DAC extension on SOLine where you add your custom field as unbound (PXString, not PXDBString) and then use PXDBScalar or PXFormula to populate it. I haven't used PXDBScalar or PXFormula to retrieve a value from a DAC Extension, so I'll leave it to you to research. I do know this is super easy if you were pulling a value directly from InventoryItem, so worth doing the research.
Create as an unbound field as in #2, but populate it in the SOLine_RowSelecting event. This is similar to JvD's suggestion, but I'd go with RowSelecting because it is the point where the cache data is being built. RowSelected should be reserved, in general, for controlling the UI experience once the record is already in the cache. Keep in mind that this will require using a new PXConnectionScope, as Acuminator will advise and help you add. (Shown in example.) In a pinch, this is how I would do it if I don't have time to sort out the generally simpler solution provided as option 2.
Code for Option 3:
#region SOLine_RowSelecting
protected virtual void _(Events.RowSelecting<SOLine> e)
{
SOLine row = (SOLine)e.Row;
if (row == null)
{
return;
}
using (new PXConnectionScope())
{
SOLineExt rowExt = row.GetExtension<SOLineExt>();
InventoryItem item = SelectFrom<InventoryItem>
.Where<InventoryItem.inventoryID.IsEqual<#P.AsInt>>
.View.Select(Base, row.InventoryID);
InventoryItemExt itemExt = item.GetExtension<InventoryItemExt>();
rowExt.UsrSSMyDatAField = itemExt.UsrSSMyDataField;
}
}
#endregion
i would like to Hide the column(except Inventory ID and Description) in the "Expense Item" Lookup screen on the Expense Receipt screen(EP301020) for all the users.
How can i set that some columns (which are not needed) in the "Available column" section in "Column Configuration" by default for all the users and only the required ones are available in the lookup screen.By default, all the columns are included in the selected column list in the Column Configuration.
Please advise.
Thanks
Below is code to hide the columns from Available and Selected list box. If all you need is to initialize the columns in Available and Selected list box consider using Acumatica Default Table Layout feature. Note that column configuration is a user configuration so you can initialize the columns but you can't override the user choices after initialization.
To completely remove the columns from the selector you need to redefine the InventoryID selector and explicitly declare the columns you want to see in the second parameter of PXSelector.
You can do so by creating a graph extension on ExpenseClaimDetailEntry and using CacheAttached method to redefine the selector:
using PX.Data;
using PX.Objects.IN;
namespace PX.Objects.EP
{
public class ExpenseClaimDetailEntry_Extension : PXGraphExtension<ExpenseClaimDetailEntry>
{
[PXMergeAttributes(Method = MergeMethod.Replace)]
[PXDefault]
[PXUIField(DisplayName = "Expense Item")]
[PXSelector(typeof(InventoryItem.inventoryID),
/* List of available/visible columns go here */
new Type[] { typeof(InventoryItem.inventoryCD),
typeof(InventoryItem.descr) },
SubstituteKey = typeof(InventoryItem.inventoryCD),
DescriptionField = typeof(InventoryItem.descr))]
[PXRestrictor(typeof(Where<InventoryItem.itemType, Equal<INItemTypes.expenseItem>>), Messages.InventoryItemIsNotAnExpenseType)]
protected virtual void EPExpenseClaimDetails_InventoryID_CacheAttached(PXCache sender)
{
}
}
}
Current customization project I'm working on has the requirement of displaying / editing a grid with a "Sort Order" for records. The "SortOrder" field is read only with up/down buttons to allow the user to re-order the items in the grid.
The "SortOrder" column in the DAC is a simple Int field.
The PXSelect statement for the grid is using a OrderBy>> to display the records.
The Grid in the ASPX is a defined with "SyncPosition= true"
I've added an Up/Down button that increments/decrements the "SortOrder" value for the current selected record.
The issue that I'm running into is that the first time "Up" or "Down" is clicked, the "SortOrder" field is updated however the rows do not move. Once I click Save to persist the update, the grid then refreshes with the right order.
I've looked through the the rest of the code but all other situations where this is used is for treeviews, not grids.
I've tried adding a View.RequestRefresh() at the end of my Action but this doesn't cause the reorder.
What would be the best way without a Persist after each move to get the Grid to update and reflect the current order from the cache values? As usual I'm assuming I'm overlooking something simple.
Any advice would be appreciated.
I had a look at the generic inquiry designer source code - it has an up/down button in the grid to reorder the fields. The views don't have an OrderBy clause:
public PXSelect<GIFilter, Where<GIFilter.designID, Equal<Current<GIDesign.designID>>>> Parameters;
OrderBy is not necessary because the LineNbr field is a key field - system automatically orders the records by the key fields.
public abstract class lineNbr : IBqlField { }
[PXDBInt(IsKey = true)]
[PXDefault]
[PXLineNbr(typeof(GIDesign))]
[PXParent(typeof(Select<GIDesign,
Where<GIDesign.designID, Equal<Current<GIFilter.designID>>>>))]
public virtual int? LineNbr { get; set; }
The code for the button looks like this:
[PXButton(ImageKey = Sprite.Main.ArrowUp, Tooltip = ActionsMessages.ttipRowUp)]
[PXUIField(DisplayName = ActionsMessages.RowUp, MapEnableRights = PXCacheRights.Update)]
protected void moveUpFilter()
{
if (this.Parameters.Current == null)
return;
GIFilter prev = PXSelect<GIFilter, Where<GIFilter.designID, Equal<Current<GIDesign.designID>>, And<GIFilter.lineNbr, Less<Current<GIFilter.lineNbr>>>>, OrderBy<Desc<GIFilter.lineNbr>>>.Select(this);
if (prev != null)
this.SwapItems(this.Parameters.Cache, prev, this.Parameters.Current);
}
[PXButton(ImageKey = Sprite.Main.ArrowDown, Tooltip = ActionsMessages.ttipRowDown)]
[PXUIField(DisplayName = ActionsMessages.RowDown, MapEnableRights = PXCacheRights.Update)]
protected void moveDownFilter()
{
if (this.Parameters.Current == null)
return;
GIFilter next = PXSelect<GIFilter, Where<GIFilter.designID, Equal<Current<GIDesign.designID>>, And<GIFilter.lineNbr, Greater<Current<GIFilter.lineNbr>>>>, OrderBy<Asc<GIFilter.lineNbr>>>.Select(this);
if (next != null)
this.SwapItems(this.Parameters.Cache, next, this.Parameters.Current);
}
The SwapItems function is shared between all the move up / move down actions:
private void SwapItems(PXCache cache, object first, object second)
{
object temp = cache.CreateCopy(first);
foreach (Type field in cache.BqlFields)
if (!cache.BqlKeys.Contains(field))
cache.SetValue(first, field.Name, cache.GetValue(second, field.Name));
foreach (Type field in cache.BqlFields)
if (!cache.BqlKeys.Contains(field))
cache.SetValue(second, field.Name, cache.GetValue(temp, field.Name));
cache.Update(first);
cache.Update(second);
}
Finally, there's a bit of JavaScript code in the ASPX code - it may or may not be what you're missing to get the feature to work correctly; i'm not exactly sure what it's doing but would encourage you to open SM208000.aspx in an editor and look for commandResult. Also check out the CallbackCommands that are defined on the grids which support up/down - it may have something to do with it.