Acumatica: how can i customize page IN501000 (Release IN Document) - acumatica

As title, i don't know how can customize this page (processing page). I have extension with override Initialize as below
Base.INDocumentList.SetProcessDelegate(delegate(List<INRegister> list){ ReleaseDocExt(list); });
But ReleaseDocExt not run when i process item.

I was able to create an extension and override the release process. If I include this extension, system will show "Hello, World!" when trying to release any IN document from the batch processing screen:
namespace PX.Objects.IN
{
public class INDocumentRelease_Extension:PXGraphExtension<INDocumentRelease>
{
public override void Initialize()
{
Base.INDocumentList.SetProcessDelegate(delegate(List<INRegister> list){ ReleaseDocExt(list); });
}
public static void ReleaseDocExt(List<INRegister> list)
{
throw new PXException("Hello, World!!");
}
}
}
This code is not getting invoked when releasing a document from one of the inventory screens, like the Receipts (IN.30.10.00) screen. The reason is because these screens directly invoke a static method in the INDocumentRelease class, and don't create a graph to do it:
public PXAction<INRegister> release;
[PXUIField(DisplayName = Messages.Release, MapEnableRights = PXCacheRights.Update, MapViewRights = PXCacheRights.Update)]
[PXProcessButton]
public virtual IEnumerable Release(PXAdapter adapter)
{
PXCache cache = receipt.Cache;
List<INRegister> list = new List<INRegister>();
foreach (INRegister indoc in adapter.Get<INRegister>())
{
if (indoc.Hold == false && indoc.Released == false)
{
cache.Update(indoc);
list.Add(indoc);
}
}
if (list.Count == 0)
{
throw new PXException(Messages.Document_Status_Invalid);
}
Save.Press();
PXLongOperation.StartOperation(this, delegate() { INDocumentRelease.ReleaseDoc(list, false); });
return list;
}
There's therefore no opportunity for system to include your extension in this process.
If you absolutely need to customize this process, you'll also need to override the Release actions in the individual screens. This code could also be modified by Acumatica to avoid the use of static functions, and instead instantiate a INDocumentRelease instance that can be customized.
Finally, I would like to warn you about customizing the transaction release processes - make sure you know what you're doing!

Related

Enable UDF in Transfer screen (IN304000) on release Status

I am trying to enable an UDF in transfer screen on release status, but the UDF is not getting enabled.
May anyone help me on this issue. I have debugged the code as well, while debugging, the code is getting executed but the result is not taking any effect on the screen. I can see the cursor blinking on that field as well. Thanks in advance. Following is my Code :
namespace PX.Objects.IN
{
public class INTransferEntry_Extension : PXGraphExtension<INTransferEntry>
{
#region Event Handlers
protected void INRegister_RowSelected(PXCache cache, PXRowSelectedEventArgs e)
{
if (e.Row == null)
{
return;
}
bool shouldDisable = ((INRegister)e.Row).Hold == false && ((INRegister)e.Row).Released == true;
if (shouldDisable)
{
PXUIFieldAttribute.SetEnabled<INRegisterExt.usrExpReturnDate>(cache, e.Row, true);
}
}
#endregion
}
}
DAC FIELD
namespace PX.Objects.IN
{
public class INRegisterExt : PXCacheExtension<PX.Objects.IN.INRegister>
{
#region UsrExpReturnDate
[PXDBDate]
[PXUIField(DisplayName="Expected Return Date")]
public virtual DateTime? UsrExpReturnDate { get; set; }
public abstract class usrExpReturnDate : PX.Data.BQL.BqlDateTime.Field<usrExpReturnDate> { }
#endregion
}
}
The document is in released status. There are mechanisms to prevent field editing when a document is in a closed state. It is not recommended to enable fields in this situation.
To enable the fields you need to revert the mechanism blocking editing. It is often the AllowUpdate property of the data view that is used to do so but it can also be automation steps or the workflow system.
When re-enabling AllowUpdate, you need to disable and re-enable the fields:
Base.transfer.AllowUpdate = true;
PXUIFieldAttribute.SetEnabled(sender, e.Row, false);
PXUIFieldAttribute.SetEnabled<INRegisterExt.usrExpReturnDate>(cache, e.Row, true);

Running a long operation within an event handler

I need to run some address validation on Customer Location addresses using a 3rd party API to determine if the address is residential or commercial. This validation should run whenever an address field is changed. In other words, the validation should be run in the Address_RowUpdated event handler.
Because the function is calling a 3rd party API, I believe that it should be done in a separate thread, using PXLongOperation so that it does not hold up address saving and fails gracefully if the API is unavailable or returns an error.
However, I am not sure if the architecture of running a long operation within an event handler is supported or if a different approach would be better.
Here is my code.
public class CustomerLocationMaint_Extension : PXGraphExtension<CustomerLocationMaint>
{
protected virtual void Address_RowUpdated(PXCache sender, PXRowUpdatedEventArgs e)
{
PX.Objects.CR.Address row = (PX.Objects.CR.Address)e.Row;
if (row != null)
{
Location location = this.Base.Location.Current;
PXCache locationCache = Base.LocationCurrent.Cache;
PXLongOperation.StartOperation(Base, delegate
{
RunCheckResidential(location, locationCache);
});
this.Base.LocationCurrent.Cache.IsDirty = true;
}
}
protected void RunCheckResidential(Location location, PXCache locationCache)
{
string messages = "";
PX.Objects.CR.Address defAddress = PXSelect<PX.Objects.CR.Address,
Where<PX.Objects.CR.Address.addressID, Equal<Required<Location.defAddressID>>>>.Select(Base, location.DefAddressID);
FValidator validator = new FValidator();
AddressValidationReply reply = validator.Validate(defAddress);
AddressValidationResult result = reply.AddressResults[0];
bool isResidential = location.CResedential ?? false;
if (result.Classification == FClassificationType.RESIDENTIAL)
{
isResidential = true;
} else if (result.Classification == FClassificationType.BUSINESS)
{
isResidential = false;
} else
{
messages += "Residential classification is: " + result.Classification + "\r\n";
}
location.CResedential = isResidential;
locationCache.Update(location);
Base.LocationCurrent.Update(location);
Base.Actions.PressSave();
// Display relevant messages
if (reply.HighestSeverity == NotificationSeverityType.SUCCESS)
String addressCorrection = validator.AddressCompare(result.EffectiveAddress, defAddress);
if (!string.IsNullOrEmpty(addressCorrection))
messages += addressCorrection;
}
PXSetPropertyException message = new PXSetPropertyException(messages, PXErrorLevel.Warning);
PXLongOperation.SetCustomInfo(new LocationMessageDisplay(message));
//throw new PXOperationCompletedException(messages); // Shows message if you hover over the success checkmark, but you have to hover to see it so not ideal
}
public class LocationMessageDisplay : IPXCustomInfo
{
public void Complete(PXLongRunStatus status, PXGraph graph)
{
if (status == PXLongRunStatus.Completed && graph is CustomerLocationMaint)
{
((CustomerLocationMaint)graph).RowSelected.AddHandler<Location>((sender, e) =>
{
Location location = e.Row as Location;
if (location != null)
{
sender.RaiseExceptionHandling<Location.cResedential>(location, location.CResedential, _message);
}
});
}
}
private PXSetPropertyException _message;
public LocationMessageDisplay(PXSetPropertyException message)
{
_message = message;
}
}
}
UPDATE - New Approach
As suggested, this code now calls the LongOperation within the Persist method.
protected virtual void Address_RowUpdated(PXCache sender, PXRowUpdatedEventArgs e)
{
PX.Objects.CR.Address row = (PX.Objects.CR.Address)e.Row;
if (row != null)
{
Location location = Base.Location.Current;
LocationExt locationExt = PXCache<Location>.GetExtension<LocationExt>(location);
locationExt.UsrResidentialValidated = false;
Base.LocationCurrent.Cache.IsDirty = true;
}
}
public delegate void PersistDelegate();
[PXOverride]
public virtual void Persist(PersistDelegate baseMethod)
{
baseMethod();
var location = Base.Location.Current;
PXCache locationCache = Base.LocationCurrent.Cache;
LocationExt locationExt = PXCache<Location>.GetExtension<LocationExt>(location);
if (locationExt.UsrResidentialValidated == false)
{
PXLongOperation.StartOperation(Base, delegate
{
CheckResidential(location);
});
}
}
public void CheckResidential(Location location)
{
CustomerLocationMaint graph = PXGraph.CreateInstance<CustomerLocationMaint>();
graph.Clear();
graph.Location.Current = location;
LocationExt locationExt = location.GetExtension<LocationExt>();
locationExt.UsrResidentialValidated = true;
try
{
// Residential code using API (this will change the value of the location.CResedential field)
} catch (Exception e)
{
throw new PXOperationCompletedWithErrorException(e.Message);
}
graph.Location.Update(location);
graph.Persist();
}
PXLongOperation is meant to be used in the context of a PXAction callback. This is typically initiated by a menu item or button control, including built-in actions like Save.
It is an anti-pattern to use it anytime a value changes in the web page. It should be used only when a value is persisted (by Save action) or by another PXAction event handler. You should handle long running validation when user clicks on a button or menu item not when he changes the value.
For example, the built in Validate Address feature is run only when the user clicks on the Validate Address button and if validated requests are required it is also run in a Persist event called in the context of the Save action to cancel saving if validation fails.
This is done to ensure user expectation that a simple change in a form/grid value field doesn't incur a long validation wait time that would lead the user to believe the web page is unresponsive. When the user clicks on Save or a specific Action button it is deemed more reasonable to expect a longer wait time.
That being said, it is not recommended but possible to wrap your PXLongOperation call in a dummy Action and asynchronously click on the invisible Action button to get the long operation running in the proper context from any event handler (except Initialize):
using PX.Data;
using System.Collections;
namespace PX.Objects.SO
{
public class SOOrderEntry_Extension : PXGraphExtension<SOOrderEntry>
{
public PXAction<SOOrder> TestLongOperation;
[PXUIField(DisplayName = "Test Long Operation", Visible = false, Visibility = PXUIVisibility.Invisible)]
[PXButton]
public virtual IEnumerable testLongOperation(PXAdapter adapter)
{
PXLongOperation.StartOperation(Base, delegate ()
{
System.Threading.Thread.Sleep(2000);
Base.Document.Ask("Operation Done", MessageButtons.OK);
});
return adapter.Get();
}
public void SOOrder_OrderDesc_FieldUpdated(PXCache sender, PXFieldUpdatedEventArgs e)
{
if (!PXLongOperation.Exists(Base.UID))
{
// Calling Action Button asynchronously so it can run in the context of a PXAction callback
Base.Actions["TestLongOperation"].PressButton();
}
}
}
}

Button not showing in the UI

I'm extending the AccountByPeriodEnq logic, I just want to add a button on top of my screen to modify the selected GL records but it just doesnt want to show up and I can't figure out why.
Here's my code :
namespace PX.Objects.GL
{
class AccountByPeriodEnqExtensions : PXGraphExtension<AccountByPeriodEnq>
{
#region Actions
public PXAction<AccountByPeriodFilter> Letter;
[PXUIField(Visible = true, DisplayName = "Lettrer")]
[PXButton(CommitChanges = true)]
protected virtual IEnumerable letter(PXAdapter adapter)
{
IReadOnlyCollection<GLTranR> selectedTrans = GetSelectedTrans();
if (selectedTrans.Any())
{
PXLongOperation.StartOperation(this, delegate ()
{
foreach(GLTranR line in selectedTrans)
{
// UpdateSomeFieldsAndPersists
}
});
}
else
{
throw new PXException("Error");
}
return Base.Filter.Select();
}
#endregion
#region Utility
private IReadOnlyCollection<GLTranR> GetSelectedTrans()
{
return Base.GLTranEnq.Cache.Updated
.Cast<GLTranR>()
.Where(tran => tran.Selected == true)
.ToArray();
}
#endregion
}
}
Is there anything I'm missing here ?
Regards,
Edit:
To clarify i'm trying to customize the GL404000, Account Details. And using the inspector I saw the Business logic is in the AccountByPeriodEnq Graph
Using Acumatica Inspect Element feature notice that the 'Account by Period' screen (GL402000) is not using the 'AccountByPeriodEnq' graph.
It is using the 'AccountHistoryByYearEnq' graph instead so that's the graph you want to target:
You also need to declare Actions on the primary DAC of that Graph.
The one for 'AccountHistoryByYearEnq' is a little bit harder to find than usual.
You can use Acumatica Source Code page and search for 'PXPrimaryGraph(typeof(AccountHistoryByYearEnq)':
In this case it is AccountByYearFilter DAC that you should use:
[System.SerializableAttribute()]
[PXCacheName(Messages.Account)]
[PXPrimaryGraph(typeof(AccountHistoryByYearEnq), Filter = typeof(AccountByYearFilter))]
public partial class Account : PX.Data.IBqlTable, PX.SM.IIncludable
{
[…]
}
I think this is a special case for Filter, when there's no filter Account would have been the DAC to use for the Actions.
Now that you have identified the Primary Graph of the Screen (AccountHistoryByYearEnq) and the Primary DAC of the Graph (AccountByYearFilter) it should work as expected:
public class AccountByPeriodEnq_Extension : PXGraphExtension<AccountHistoryByYearEnq>
{
public PXAction<AccountByYearFilter> letter;
[PXUIField(DisplayName = "Letter")]
[PXButton]
protected virtual IEnumerable Letter(PXAdapter adapter)
{
return adapter.Get();
}
}
UI:
EDIT:
For Account detail page (GL404000), use the same code with different DAC and Graph:
using PX.Data;
using System.Collections;
namespace PX.Objects.GL
{
public class AccountByPeriodEnq_Extension : PXGraphExtension<AccountByPeriodEnq>
{
#region Actions
public PXAction<AccountByPeriodFilter> letter;
[PXUIField(DisplayName = "Letter")]
[PXButton]
protected virtual IEnumerable Letter(PXAdapter adapter)
{
return adapter.Get();
}
#endregion
}
}
With a base Acumatica install that's all that is needed for the Action to appear:
Note that you can specify explicit state and view rights for the button control, though I don't think your issue is related to access rights if you're working in a developer instance:
[PXUIField(DisplayName = "Letter",
MapEnableRights = PXCacheRights.Select,
MapViewRights = PXCacheRights.Select)]
I finally found my problem, I missed the public member for my class.
namespace PX.Objects.GL
{
public class AccountByPeriodEnqExtensions : PXGraphExtension<AccountByPeriodEnq>
{
#region Actions
public PXAction<AccountByPeriodFilter> Letter;
Thanks for your answer HB, i'll reuse it.

Overriding Persist() in Custom BLC

I've found information on how to override the Persist() function for a customization extension for an out-of-the-box BLC, but I cannot find anything regarding a custom built BLC. I need to insert an item into the cache right before RowPersisting() begins, but using public void Persist(PersistDelegate baseMethod) is not working since I do not have a PersistDelegate object defined. The error I receive is:
PX.Data.PXException: An invalid type has been specified for the data source.
Is there a way to override Persist() in a custom BLC? If so, how do I do it?
Overriding the Persist method in a custom BLC is not different then overriding any virtual method in C#:
public class ARSalesPriceMaint : PXGraph<ARSalesPriceMaint>
{
...
public override void Persist()
{
foreach (ARSalesPrice price in Records.Cache.Inserted)
{
ARSalesPrice lastPrice = FindLastPrice(this, price);
if (lastPrice?.EffectiveDate > price.EffectiveDate && price.ExpirationDate == null)
{
Records.Cache.RaiseExceptionHandling<ARSalesPrice.expirationDate>(price, price.ExpirationDate, new PXSetPropertyException(ErrorMessages.FieldIsEmpty, PXUIFieldAttribute.GetDisplayName<ARSalesPrice.expirationDate>(Records.Cache)));
throw new PXSetPropertyException(ErrorMessages.FieldIsEmpty, PXUIFieldAttribute.GetDisplayName<ARSalesPrice.expirationDate>(Records.Cache));
}
ValidateDuplicate(this, Records.Cache, price);
}
foreach (ARSalesPrice price in Records.Cache.Updated)
{
ARSalesPrice lastPrice = FindLastPrice(this, price);
if (lastPrice?.EffectiveDate > price.EffectiveDate && price.ExpirationDate == null)
{
Records.Cache.RaiseExceptionHandling<ARSalesPrice.expirationDate>(price, price.ExpirationDate, new PXSetPropertyException(ErrorMessages.FieldIsEmpty, PXUIFieldAttribute.GetDisplayName<ARSalesPrice.expirationDate>(Records.Cache)));
throw new PXSetPropertyException(ErrorMessages.FieldIsEmpty, PXUIFieldAttribute.GetDisplayName<ARSalesPrice.expirationDate>(Records.Cache));
}
ValidateDuplicate(this, Records.Cache, price);
}
base.Persist();
}
...
}

Print button on CR306000 to print case data

I have a custom button on CR306000 to try to print current loaded case information by calling one customized report. Navigation URL current set to:
~/Frames/ReportLauncher.aspx?ID=Inquirycase.rpx&CASEID=####
I will need to have custom programming to assign current case ID to replace "####", but don't know where and how to reference that custom button and modify its property. Please help. Thanks.
You could add a report parameter called 'CaseID' to your report and call it using the following code using an AEF extension like this:
public class CRCaseMaintExtension : PXGraphExtension<CRCaseMaint>
{
public override void Initialize()
{
base.Initialize();
//if adding to an existing menu button do that here...
// Example:
//Base.Inquiry.AddMenuAction(this.CustomReportButton);
}
public PXAction<CRCase> CustomReportButton;
[PXButton]
[PXUIField(DisplayName = "Custom Report", MapEnableRights = PXCacheRights.Select, MapViewRights = PXCacheRights.Select)]
public virtual IEnumerable customReportButton(PXAdapter adapter)
{
if (Base.Case.Current != null)
{
Dictionary<string, string> parameters = new Dictionary<string, string>();
parameters["CaseID"] = Base.Case.Current.CaseID.ToString();
//enter in your report id/number here
string reportNumber = "Inquirycase";
//opens the report using the defined parameters
throw new PXReportRequiredException(parameters, reportNumber, "Custom Report");
}
return adapter.Get();
}
}
I have not tested the above but this should get you most of the way there.

Resources