We have requirement to make Ext. Price editable even after the invoice is released, we wrote logic to make field editable but when we update value we are getting "Project cannot be empty error". We are using Acumatica 2020 R2 Build - 20.207.0012 with Sales Demo database and without any customizations.
Here is a code sample:
private bool IsDisabled(ARInvoice doc)
{
return doc.Released == true
|| doc.Voided == true
|| doc.DocType == ARDocType.SmallCreditWO
|| doc.PendingPPD == true
|| doc.DocType == ARDocType.FinCharge
&& !Base.IsProcessingMode
&& Base.Document.Cache.GetStatus(doc) == PXEntryStatus.Inserted;
}
protected void ARInvoice_RowSelected(PXCache cache, PXRowSelectedEventArgs e, PXRowSelected InvokeBaseHandler)
{
if(InvokeBaseHandler != null)
InvokeBaseHandler(cache, e);
var rows= (ARInvoice)e.Row;
if (rows == null)
return;
if (IsDisabled(rows))
{
PXUIFieldAttribute.SetEnabled<ARTran.curyExtPrice>(cache, rows, true);
Base.Document.Cache.AllowUpdate = true;
Base.Transactions.Cache.AllowUpdate = true;
}
}
Trace:
3/16/2021 12:30:41 PM Error:
Error: Updating 'SO Invoice' record raised at least one error. Please review the errors.
Error: 'Project' cannot be empty.
at PX.Data.PXUIFieldAttribute.CommandPreparing(PXCache sender, PXCommandPreparingEventArgs e)
at PX.Data.PXCache.OnCommandPreparing(String name, Object row, Object value, PXDBOperation operation, Type table, FieldDescription& description)
at PX.Data.PXProjectionAttribute.PersistUpdated(PXCache sender, Object row)
at PX.Data.PXCache`1.PersistUpdated(Object row, Boolean bypassInterceptor)
at PX.Data.PXCache`1.Persist(PXDBOperation operation)
at PX.Data.PXGraph.Persist(Type cacheType, PXDBOperation operation)
at PX.Data.PXGraph.Persist()
at PX.Objects.AR.ARInvoiceEntry.Persist()
at PX.Objects.AR.ARInvoiceEntryExternalTax.Persist(Action persist)
at PX.Data.PXSave`1.d__2.MoveNext()
at PX.Data.PXAction`1.d__30.MoveNext()
at PX.Data.PXAction`1.d__30.MoveNext()
at PX.Web.UI.PXBaseDataSource.tryExecutePendingCommand(String viewName, String[] sortcolumns, Boolean[] descendings, Object[] searches, Object[] parameters, PXFilterRow[] filters, DataSourceSelectArguments arguments, Boolean& closeWindowRequired, Int32& adapterStartRow, Int32& adapterTotalRows)
at PX.Web.UI.PXBaseDataSource.ExecuteSelect(String viewName, DataSourceSelectArguments arguments, PXDSSelectArguments pxarguments
I think your best bet is to add that field to your screen and check what the value is:
Then can also try to suppress the error like this:
namespace PX.Objects.SO
{
public class SOInvoiceEntry_Extension : PXGraphExtension<SOInvoiceEntry>
{
#region Event Handlers
protected void ARTran_ProjectID_FieldVerifying(PXCache cache, PXFieldVerifyingEventArgs e)
{
e.Cancel = true;
}
#endregion
}
}
Related
How can I copy the note from a SalesOrder to a Shipment as the shipment is created?
I am trying to use the PXNoteAttribute.GetNote()/PXNoteAttribute.SetNote() functions, but GetNote keeps turning up blank.
#region Event Handlers
string notetext;
protected void SOShipLine_RowInserted(PXCache cache, PXRowInsertedEventArgs e, PXRowInserted InvokeBaseHandler)
{
if(InvokeBaseHandler != null)
InvokeBaseHandler(cache, e);
var row = (SOShipLine)e.Row;
SOOrder SalesOrder = (SOOrder)PXSelectorAttribute.Select<SOShipLine.origOrderNbr>(cache, e.Row);
string note = PXNoteAttribute.GetNote(cache, SalesOrder);
notetext = note;
}
protected void SOShipment_RowUpdated(PXCache cache, PXRowUpdatedEventArgs e, PXRowUpdated InvokeBaseHandler)
{
if(InvokeBaseHandler != null)
InvokeBaseHandler(cache, e);
var row = (SOShipment)e.Row;
PXNoteAttribute.SetNote(cache, cache.Current, notetext);
}
The cache you are referencing in your code is the cache for the ShipLine. You need to reference the SalesOrder cache for GetNote() to function properly. You can use Base.Caches[typeof(SOOrder)].
Like so:
#region Event Handlers
string notetext;
protected void SOShipLine_RowInserted(PXCache cache, PXRowInsertedEventArgs e, PXRowInserted InvokeBaseHandler)
{
if(InvokeBaseHandler != null)
InvokeBaseHandler(cache, e);
var row = (SOShipLine)e.Row;
SOOrder SalesOrder = (SOOrder)PXSelectorAttribute.Select<SOShipLine.origOrderNbr>(cache, e.Row);
string note = PXNoteAttribute.GetNote(Base.Caches[typeof(SOOrder)], SalesOrder);
notetext = note;
}
protected void SOShipment_RowUpdated(PXCache cache, PXRowUpdatedEventArgs e, PXRowUpdated InvokeBaseHandler)
{
if(InvokeBaseHandler != null)
InvokeBaseHandler(cache, e);
var row = (SOShipment)e.Row;
PXNoteAttribute.SetNote(cache, cache.Current, notetext);
}
Anyone know if it's possible to combine a field like Item Class (we use a 3 letter code for each one) with a sequential number to get a unique Inventory ID?
e.g. we need all hardware to be HWR-00988 but all Shipping Supplies to be SUP-00989 and so on.
My workaround right now is to create an Attribute called ITEMCLASS and basically mirror the item class codes (HWR, SUP, etc) then add attributes to each Item Class and use the Inventory ID Segment Settings to make it look like it's pulling the actual Item Class.
Seems like this should exist though? I know the data exists in INITEMMENU already.
If you mean having a unique InventoryCD key that switches between constants HWR-XXXXX and SUP-XXXXX based on custom business rules (like appending Item Class); I don't think that's possible out-of-box without programming.
Usually this is accomplished with a custom attribute. Here's an example for [CuryID] attribute which computes key value based on arbitrary logic.
Usage, decorate key fields with custom attribute [CuryID]:
[PXDBString(5, IsUnicode = true)]
[PXDefault(typeof(AccessInfo.baseCuryID))]
[PXUIField(DisplayName = "Currency", ErrorHandling = PXErrorHandling.Never)]
[PXSelector(typeof(Currency.curyID))]
[CuryID]
public virtual String CuryID { get; set; }
For your scenario you would replace InventoryRaw with your custom attribute:
[PXDefault]
[InventoryRaw(IsKey = true, DisplayName = "Inventory ID")]
public virtual String InventoryCD { get; set; }
Custom attribute [CuryID], if key is autogenerated consider adding FieldDefaulting event:
public sealed class CuryIDAttribute : PXEventSubscriberAttribute, IPXFieldUpdatedSubscriber, IPXRowSelectedSubscriber, IPXRowUpdatingSubscriber, IPXRowUpdatedSubscriber, IPXFieldVerifyingSubscriber
{
public void FieldUpdated(PXCache sender, PXFieldUpdatedEventArgs e)
{
CurrencyInfo info = e.Row as CurrencyInfo;
if (info != null)
{
//reset effective date to document date first
info.SetDefaultEffDate(sender);
try
{
info.defaultCuryRate(sender);
}
catch (PXSetPropertyException ex)
{
sender.RaiseExceptionHandling(_FieldName, e.Row, sender.GetValue(e.Row, _FieldOrdinal), ex);
}
info.CuryPrecision = null;
}
}
public void RowSelected(PXCache sender, PXRowSelectedEventArgs e)
{
CurrencyInfo info = e.Row as CurrencyInfo;
if (info != null)
{
bool disabled = info.IsReadOnly == true || (info.CuryID == info.BaseCuryID);
PXUIFieldAttribute.SetEnabled<CurrencyInfo.curyMultDiv>(sender, info, !disabled);
PXUIFieldAttribute.SetEnabled<CurrencyInfo.sampleCuryRate>(sender, info, !disabled);
PXUIFieldAttribute.SetEnabled<CurrencyInfo.sampleRecipRate>(sender, info, !disabled);
PXUIFieldAttribute.SetEnabled<CurrencyInfo.curyRateTypeID>(sender, info, !disabled);
PXUIFieldAttribute.SetEnabled<CurrencyInfo.curyEffDate>(sender, info, !disabled);
PXUIFieldAttribute.SetEnabled<CurrencyInfo.baseCuryID>(sender, info, false);
PXUIFieldAttribute.SetEnabled<CurrencyInfo.displayCuryID>(sender, info, false);
PXUIFieldAttribute.SetEnabled<CurrencyInfo.curyID>(sender, info, true);
}
}
private bool? currencyInfoDirty = null;
public void RowUpdating(PXCache sender, PXRowUpdatingEventArgs e)
{
CurrencyInfo info = e.Row as CurrencyInfo;
if (info != null && info._IsReadOnly == true)
{
e.Cancel = true;
}
else
{
currencyInfoDirty = sender.IsDirty;
}
}
public void RowUpdated(PXCache sender, PXRowUpdatedEventArgs e)
{
CurrencyInfo info = e.Row as CurrencyInfo;
if (info != null)
{
CurrencyInfo old = e.OldRow as CurrencyInfo;
if (old != null && (String.IsNullOrEmpty(info.CuryID) || String.IsNullOrEmpty(info.BaseCuryID)))
{
info.BaseCuryID = old.BaseCuryID;
info.CuryID = old.CuryID;
}
if (currencyInfoDirty == false
&& info.CuryID == old.CuryID
&& info.CuryRateTypeID == old.CuryRateTypeID
&& info.CuryEffDate == old.CuryEffDate
&& info.CuryMultDiv == old.CuryMultDiv
&& info.CuryRate == old.CuryRate)
{
sender.IsDirty = false;
currencyInfoDirty = null;
}
}
}
public void FieldVerifying(PXCache sender, PXFieldVerifyingEventArgs e)
{
CurrencyInfo info = e.Row as CurrencyInfo;
if (info != null)
{
CMSetup CMSetup = info.getCMSetup(sender);
}
}
}
Application Date to default Document Date:
protected void ARPayment_RowSelected(PXCache cache, PXRowSelectedEventArgs e, PXRowSelected InvokeBaseHandler)
{
if(InvokeBaseHandler != null)
InvokeBaseHandler(cache, e);
var row = (ARPayment)e.Row;
if (row != null)
{
row.AdjDate = row.DocDate;
row.AdjFinPeriodID = row.FinPeriodID;
}
}
The code defaults the fields as required but Application date cannot be edited hence backdating cannot be done
protected void ARPayment_AdjFinPeriodID_FieldDefaulting(PXCache cache, PXFieldDefaultingEventArgs e)
{
var row = (ARPayment)e.Row;
row.AdjFinPeriodID = row.FinPeriodID;
}
protected void ARPayment_AdjDate_FieldDefaulting(PXCache cache, PXFieldDefaultingEventArgs e)
{
var row = (ARPayment)e.Row;
row.AdjDate = row.DocDate;
}
Using field defaulting gives index out of range error
By Adding a condition to check the status of the document it seizes to work
protected void ARPayment_RowSelected(PXCache cache, PXRowSelectedEventArgs e, PXRowSelected InvokeBaseHandler)
{
if(InvokeBaseHandler != null)
InvokeBaseHandler(cache, e);
var row = (ARPayment)e.Row;
if (row != null && row.Status == "Open")
{
row.AdjDate = row.DocDate;
row.AdjFinPeriodID = row.FinPeriodID;
}
}
The RowSelected event is not an initialization event. It is run on every callback to the server.
The FieldDefaulting event should assign the default value using the NewValue property of PXFieldDefaultingEventArgs argument parameter.
Using field defaulting gives index out of range error
That should not happen in your FieldDefaulting handler because there are no index access operations. I can't reproduce this issue. Remove all other customization code, keep only the FieldDefaulting event and this error should disappear.
There is another issue though. The DocDate value is always null when I debug the FieldDefaulting event. When I pass a valid Date value for initialization it does work as expected on new payments. Example:
using PX.Data;
using System;
namespace PX.Objects.AR
{
public class ARPaymentEntry_Extension : PXGraphExtension<ARPaymentEntry>
{
public void ARPayment_AdjDate_FieldDefaulting(PXCache cache, PXFieldDefaultingEventArgs e)
{
ARPayment row = e.Row as ARPayment;
if (row != null)
e.NewValue = new DateTime(2077, 01, 01);
}
}
}
I have added a DAC Extension to ARTran with a field to provide the Discount Unit Price (displayed to the user on the Invoices screen)
public class ARTran_Extension : PXCacheExtension<ARTran>
{
[PXDecimal]
[PXUIField(DisplayName = "Disc Unit Price", Enabled = false)]
[PXDefault(TypeCode.Decimal, "0")]
[PXDBCalced(typeof(Div<Mult<IsNull<ARTran.curyUnitPrice, Zero>, Sub<_100Percents, IsNull<ARTran.discPct, Zero>>>, _100Percents>), typeof(decimal))]
public virtual decimal UsrDiscUnitPrice { get; set; }
public abstract class usrDiscUnitPrice : IBqlField { }
}
Now I'm trying to compose a simple Generic Inquiry that has SOOrder Inner Join SOLine and SOLine Left Join ARTran (and a couple parameters to specify a date range of orders). When I view the inquiry and select a date range that returns records it returns an error: "Error #111: An error occurred while processing the field Disc Unit Price : Object reference not set to an instance of an object."
The Results Grid does not currently reference any fields from the ARTran table. This occurs even when Inner joining ARTran (ensuring there are records from the table).
I systematically eliminated elements and identified when the PXDBCalced attribute is removed the Inquiry runs successfully. I then tried changing it to use a Custom attribute instead and found even when a custom attribute is added, even if it has no functional code within it, the inquiry again fails with that error. Even when there is FieldSelecting event code via the custom attribute, setting a breakpoint within it that event is never reached.
public class ARTranDiscUnitPriceAttribute : PXEventSubscriberAttribute, IPXFieldSelectingSubscriber
{
public virtual void FieldSelecting(PXCache sender, PXFieldSelectingEventArgs e)
{
/*
ARTran artran = (ARTran)e.Row;
if (artran == null) return;
e.ReturnValue = (artran.CuryUnitPrice ?? 0) * (100 - (artran.DiscPct ?? 0)) / 100;
*/
}
}
public class ARTran_Extension : PXCacheExtension<ARTran>
{
[PXDecimal]
[PXUIField(DisplayName = "Disc Unit Price")]
[PXDefault(TypeCode.Decimal, "0")]
[ARTranDiscUnitPrice]
public virtual decimal UsrDiscUnitPrice { get; set; }
public abstract class usrDiscUnitPrice : IBqlField { }
}
Any details or suggestions how to resolve this would be greatly appreciated.
The Trace:
Error #111: An error occurred while processing the field Disc Unit Price : Object reference not set to an instance of an object..
System.NullReferenceException: Object reference not set to an instance of an object.
at _SetValueByOrdinal(ARTran , Int32 , Object , PXCacheExtension[] )
at PX.Data.PXCache`1.SetValueByOrdinal(TNode data, Int32 ordinal, Object value, PXCacheExtension[] extensions)
at PX.Data.PXCache`1.SetValue(Object data, Int32 ordinal, Object value)
at PX.Data.PXDBCalcedAttribute.RowSelecting(PXCache sender, PXRowSelectingEventArgs e)
at PX.Data.PXCache.OnRowSelecting(Object item, PXDataRecord record, Int32& position, Boolean isReadOnly)
at PX.Data.PXCache.OnRowSelecting(Object item, PXDataRecord record, Int32& position, Boolean isReadOnly)
at PX.Data.PXGenericInqGrph.d__9.MoveNext()
at _CustomMethod(Object , Object[] )
at PX.Data.PXView.InvokeDelegate(Object[] parameters)
at PX.Data.PXView.Select(Object[] currents, Object[] parameters, Object[] searches, String[] sortcolumns, Boolean[] descendings, PXFilterRow[] filters, Int32& startRow, Int32 maximumRows, Int32& totalRows)
at PX.Data.PXProcessingBase`1._SelectRecords(Int32 startRow, Int32 maxRows)
at PX.Data.Maintenance.GI.GIFilteredProcessing._List()
at _CustomMethod(Object , Object[] )
at PX.Data.PXView.InvokeDelegate(Object[] parameters)
at PX.Data.PXView.Select(Object[] currents, Object[] parameters, Object[] searches, String[] sortcolumns, Boolean[] descendings, PXFilterRow[] filters, Int32& startRow, Int32 maximumRows, Int32& totalRows)
at PX.Data.PXGraph.ExecuteSelect(String viewName, Object[] parameters, Object[] searches, String[] sortcolumns, Boolean[] descendings, PXFilterRow[] filters, Int32& startRow, Int32 maximumRows, Int32& totalRows)
A super easy fix for this issue: in Acumatica all DAC fields must be of a nullable type, so once you declare your UsrDiscUnitPrice field of the Nullable<decimal> type or the decimal? type, you should be good to go:
public class ARTran_Extension : PXCacheExtension<ARTran>
{
public abstract class usrDiscUnitPrice : IBqlField { }
[PXDecimal]
[PXUIField(DisplayName = "Disc Unit Price")]
[PXDefault(TypeCode.Decimal, "0")]
[ARTranDiscUnitPrice]
public virtual decimal? UsrDiscUnitPrice { get; set; }
}
As title, i want to auto confirm shipment when I Create shipment from Sales Order screen by automation step.
Thanks all.
SOShipmentEntry docgraph = PXGraph.CreateInstance<SOShipmentEntry>();
docgraph.Document.Current = docgraph.Document.Search<SOShipment.shipmentNbr>(ShipmentNbr);
foreach (var action in (docgraph.action.GetState(null) as PXButtonState).Menus)
{
if (action.Command == "Confirm Shipment")
{
PXAdapter adapter2 = new PXAdapter(new DummyView(docgraph, docgraph.Document.View.BqlSelect, new List<object> { docgraph.Document.Current }));
adapter2.Menu = action.Command;
docgraph.action.PressButton(adapter2);
TimeSpan timespan;
Exception ex;
while (PXLongOperation.GetStatus(docgraph.UID, out timespan, out ex) == PXLongRunStatus.InProcess)
{ }
break;
}
}
internal class DummyView : PXView
{
List<object> _Records;
internal DummyView(PXGraph graph, BqlCommand command, List<object> records)
: base(graph, true, command)
{
_Records = records;
}
public override List<object> Select(object[] currents, object[] parameters, object[] searches, string[] sortcolumns, bool[] descendings, PXFilterRow[] filters, ref int startRow, int maximumRows, ref int totalRows)
{
return _Records;
}
}
We had issues with 'confirm shipment' , the above code helped to do that. It loads the shipment document from the shipment number and finds the menu of the graph for 'confirm shipment' and clicks it.
Best option to schedule process Confirm Shipment instead of using Automation Steps functionality.