Acumatica自定义字段计算存储及订单总毛利显示异常求助
Hey Krunal, let's break down and fix the two issues you're facing with your Acumatica customization:
Issue 1:
UsrGrossProfit not saving to the database The core problems here are event selection, incorrect currency field configuration, and unsafe persistence calls:
- You’re using
FieldSelectingto set the field value—this event only controls what’s displayed, not what gets persisted to the database. Changes made here won’t be marked as modified in the cache. - Your
PXDBCurrencyattribute referencesSOOrderfields instead ofSOLine(where the custom field lives), and you’re missing a required base currency field for proper multi-currency handling. - There’s a typo in your calculation (
invc.AvgCostshould beinv.AvgCost), and callingBase.Save.Press()/Base.Persist()directly in an event risks infinite loops and unintended data saves.
Issue 2:
UsrTotalGrossProfit not displaying on the order summary The main issues here are incorrect initialization of the total value and misconfigured currency attributes:
- You never reset the total to 0 before summing lines, so each time
RowSelectedfires, the total keeps accumulating old values. - The
PXCurrencyattribute uses unrelated fields (orderWeight), and the total field should either be bound with a base currency pair or marked as unbound (since it’s a calculated summary).
Fixed Full Code
using PX.Data; using PX.Objects.IN; using PX.Objects.SO; public class SOOrderExtension : PXCacheExtension<SOOrder> { #region UsrTotalGrossProfit public abstract class usrTotalGrossProfit : PX.Data.IBqlField { } // Use SOOrder's currency info and link to base currency field [PXCurrency(typeof(SOOrder.curyInfoID), typeof(usrTotalGrossProfitBase))] [PXDefault(TypeCode.Decimal, "0.0")] [PXUIField(DisplayName = "Total Gross Profit")] // Auto-sum line-level gross profit (cleaner than manual looping) [PXUnboundFormula(typeof(Sum<SOLineExtension.usrGrossProfit>), typeof(SumCalc<usrTotalGrossProfit>))] public virtual Decimal? UsrTotalGrossProfit { get; set; } // Required base currency field for total gross profit public abstract class usrTotalGrossProfitBase : PX.Data.IBqlField { } [PXDBDecimal(19, 4)] [PXDefault(TypeCode.Decimal, "0.0")] public virtual Decimal? UsrTotalGrossProfitBase { get; set; } #endregion } public class SOLineExtension : PXCacheExtension<SOLine> { #region UsrGrossProfit public abstract class usrGrossProfit : PX.Data.IBqlField { } // Correct currency binding: use SOLine's curyInfoID and base currency field [PXDBCurrency(typeof(SOLine.curyInfoID), typeof(usrGrossProfitBase))] [PXDefault(TypeCode.Decimal, "0.0")] [PXUIField(DisplayName = "Gross Profit")] public virtual Decimal? UsrGrossProfit { get; set; } // Required base currency field for line-level gross profit public abstract class usrGrossProfitBase : PX.Data.IBqlField { } [PXDBDecimal(19, 4)] [PXDefault(TypeCode.Decimal, "0.0")] public virtual Decimal? UsrGrossProfitBase { get; set; } #endregion } public class SOOrderEntryExtension : PXGraphExtension<SOOrderEntry> { // Recalculate when key line fields change protected virtual void SOLine_CuryUnitPrice_FieldUpdated(PXCache cache, PXFieldUpdatedEventArgs e) { CalculateGrossProfit(cache, e.Row as SOLine); } protected virtual void SOLine_Qty_FieldUpdated(PXCache cache, PXFieldUpdatedEventArgs e) { CalculateGrossProfit(cache, e.Row as SOLine); } protected virtual void SOLine_InventoryID_FieldUpdated(PXCache cache, PXFieldUpdatedEventArgs e) { CalculateGrossProfit(cache, e.Row as SOLine); } protected virtual void SOLine_RowInserted(PXCache cache, PXRowInsertedEventArgs e) { CalculateGrossProfit(cache, e.Row as SOLine); } private void CalculateGrossProfit(PXCache cache, SOLine row) { if (row == null || Base.Document.Current?.Status == "C") return; SOLineExtension lineExt = cache.GetExtension<SOLineExtension>(row); InventoryItem inv = PXSelect<InventoryItem, Where<InventoryItem.inventoryID, Equal<Required<InventoryItem.inventoryID>>>>.Select(Base, row.InventoryID); if (inv?.ItemType == "F") // Stock Item { // Use CuryUnitPrice for currency-aware calculation lineExt.UsrGrossProfit = (row.CuryUnitPrice - inv.AvgCost) * row.Qty; } else { lineExt.UsrGrossProfit = 0; // Reset for non-stock items (adjust as needed) } // Mark field as modified to ensure persistence cache.SetValueExt<SOLineExtension.usrGrossProfit>(row, lineExt.UsrGrossProfit); } // Optional: Manual total calculation (if you don't want to use PXUnboundFormula) protected virtual void SOOrder_RowSelected(PXCache cache, PXRowSelectedEventArgs e) { SOOrder row = e.Row as SOOrder; if (row == null) return; SOOrderExtension orderExt = cache.GetExtension<SOOrderExtension>(row); decimal total = 0; foreach (SOLine line in Base.Transactions.Select()) { SOLineExtension lineExt = PXCache<SOLine>.GetExtension<SOLineExtension>(line); total += lineExt.UsrGrossProfit ?? 0; } orderExt.UsrTotalGrossProfit = total; cache.SetValueExt<SOOrderExtension.usrTotalGrossProfit>(row, total); } }
Key Changes Explained
Gross Profit Persistence Fixes:
- Switched to
FieldUpdated(for relevant fields) andRowInsertedevents—these mark fields as modified, ensuring changes are saved to the database. - Added base currency fields (
UsrGrossProfitBaseandUsrTotalGrossProfitBase) to comply with Acumatica’s multi-currency requirements forPXDBCurrency. - Fixed the
invctypo and removed unsafeSave/Persistcalls—Acumatica handles persistence automatically when fields are marked as modified.
- Switched to
Total Gross Profit Display Fixes:
- Used
PXUnboundFormulato auto-sum line values (cleaner than manual looping), or included a corrected manual calculation inRowSelectedthat resets the total to 0 before summing. - Updated currency attributes to reference the correct parent fields for proper UI rendering.
- Used
内容的提问来源于stack exchange,提问作者Krunal
相关产品推荐
相关产品推荐

