I'm upgrading an older version (6.1) to 2019 R1 (19.100.0022) and an old section of code is giving me an error that I cannot understand. Here's the relevant code - which is an attribute:
[ActiveProjectTask(typeof(EPExpenseClaimSummary.projectID), BatchModule.EP, DisplayName = "Project Task")]
[PXDefault]
public virtual int? TaskID
{
The problem is that there seems to be something wrong with the 'BatchModule.EP' parameter. Here's the error I get:
ProjectTaskAttribute does not support the given module.
Parameter name: Module
Actual value was EP.
The stack trace is as follows:
[ArgumentOutOfRangeException: ProjectTaskAttribute does not support the given module.
Parameter name: Module
Actual value was EP.]
PX.Data.PXGraph.CreateInstance(Type graphType, String prefix) +1017
PX.Web.UI.PXBaseDataSource.f(Type A_0) +560
PX.Web.UI.PXBaseDataSource.g(Type A_0) +195
PX.Web.UI.PXBaseDataSource.get_DataGraph() +396
User_PageTitle.InitAuditMenu() +591
User_PageTitle.Page_InitComplete(Object sender, EventArgs e) +71
System.EventHandler.Invoke(Object sender, EventArgs e) +0
System.Web.UI.Page.OnInitComplete(EventArgs e) +141
System.Web.UI.Page.ProcessRequestMain(Boolean
includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +2203
Does anyone know what's happening here?
I couldn't locate the relevant release note but I'm almost certain this is a breaking change introduced by newer versions. Only BatchModule.EA will be allowed in that context, for all practical purpose it seems to have replaced BatchModule.EP.
More concerning is EPExpenseClaimSummary which was removed so I think some refactoring will be necessary here because the module was re-implemented. You can get inspiration from the new DAC EPExpenseClaim (summary) and EPExpenseClaimDetails (details). I think the ActiveProjectTask attribute should be replaced by EPExpenseAllowProjectTask attribute (exemple in source code below).
The TaskID field has been moved from Summary to Details so you can't re-use your current logic. This means task are now assigned per detail records instead of being assigned to a single header record changing the relationship from 1-1 to 1-x. Also project was renamed to contract. The most relevant fields for you would be EPExpenseClaimDetails.ContractID and EPExpenseClaimDetails.TaskID:
#region ContractID
public abstract class contractID : PX.Data.IBqlField
{
}
/// <summary>
/// The <see cref = "Contract.ContractID"/> project or contract</see>, which should be specified if the
/// employee incurred the expenses while working on a particular project or contract.
/// You can select a project or contract only if the Project Management or Contract Management feature,
/// respectively, is enabled on the Enable/Disable Features (CS100000) form.
/// </summary>
[PXDBInt]
[PXUIField(DisplayName = "Project/Contract")]
[PXDimensionSelector(ContractAttribute.DimensionName,
typeof(Search2<Contract.contractID,
LeftJoin<EPEmployeeContract,
On<EPEmployeeContract.contractID, Equal<Contract.contractID>,
And<EPEmployeeContract.employeeID, Equal<Current2<employeeID>>>>>,
Where<Contract.isActive, Equal<True>,
And<Contract.isCompleted, Equal<False>,
And<Where<Contract.nonProject, Equal<True>,
Or2<Where<Contract.baseType, Equal<CTPRType.contract>,
And<FeatureInstalled<FeaturesSet.contractManagement>>>,
Or<Contract.baseType, Equal<CTPRType.project>,
And2<Where<Contract.visibleInEA, Equal<True>>,
And2<FeatureInstalled<FeaturesSet.projectModule>,
And2<Match<Current<AccessInfo.userName>>,
And<Where<Contract.restrictToEmployeeList, Equal<False>,
Or<EPEmployeeContract.employeeID, IsNotNull>>>>>>>>>>>>,
OrderBy<Desc<Contract.contractCD>>>),
typeof(Contract.contractCD),
typeof(Contract.contractCD),
typeof(Contract.description),
typeof(Contract.customerID),
typeof(Contract.status),
Filterable = true,
ValidComboRequired = true,
CacheGlobal = true,
DescriptionField = typeof(Contract.description))]
[ProjectDefault(BatchModule.EA, AccountType = typeof(expenseAccountID))]
public virtual int? ContractID
{
get;
set;
}
#endregion
#region TaskID
public abstract class taskID : PX.Data.IBqlField
{
}
/// <summary>
/// The <see cref="PMTask">project task</see> to which the expenses are related.
/// This box is available only if the Project Management feature is enabled on the Enable/Disable Features (CS100000) form.
/// </summary>
///
[PXDefault(typeof(Search<PMTask.taskID, Where<PMTask.projectID, Equal<Current<contractID>>, And<PMTask.isDefault, Equal<True>>>>), PersistingCheck = PXPersistingCheck.Nothing)]
[EPExpenseAllowProjectTaskAttribute(typeof(EPExpenseClaimDetails.contractID), BatchModule.EA, DisplayName = "Project Task")]
[PXUIEnabled(typeof(Where<contractID, IsNotNull, And<Selector<contractID, Contract.baseType>, Equal<CTPRType.project>>>))]
[PXFormula(typeof(Switch<Case<Where<contractID, IsNull, Or<Selector<contractID, Contract.baseType>, NotEqual<CTPRType.project>>>, Null>, taskID>))]
[PXForeignReference(typeof(Field<taskID>.IsRelatedTo<PMTask.taskID>))]
public virtual int? TaskID
{
get;
set;
}
#endregion
Related
We have a graph extension that manages the tab we added to a screen. The tab displays data from our own DAC. We do not use a DAC extension, not going to go into the reasons right now.
When a user opens the main screen I want to create a record of our data if it does not exist, with some defaulting business logic.
I added a RowSelected event handler on the main DAC and it fires as I expect it to. When I add the code to create our missing record in the event handler Acuminator gives me error "PX1044 Changes to PXCache cannot be performed in event handlers."
I understand the error that Acuminator is raising, but I'm not sure where else to create our record. I cannot remember a section of the university specifically addressing this scenario.
Can anyone tell me how I would handle this scenario? And if possible, point me at the learning material that covers this scenario for broader information.
Unfortunatly there are only a few OK ways to do this and make Acuminator happy. You will risk data inconsistency.
https://github.com/Acumatica/Acuminator/blob/master/docs/diagnostics/PX1044.md
I would reccomend putting your data insertion code in Row Updated and hope the user updates something
I got a reply from Acumatica support, you can do this in a view select delegate.
Here's some example code, I haven't tested it though:
public class InventoryItemMaintExtension : PXGraphExtension<InventoryItemMaint>
{
public SelectFrom<MyCustomTable>.
Where<MyCustomTable.inventoryID.IsEqual<
InventoryItem.inventoryID.FromCurrent>>.
View AdditionalItemData;
public static bool IsActive() { return true; }
public IEnumerable additionalItemData()
{
var data = AdditionalItemData.SelectSingle();
if (data is null)
{
data = new MyCustomTable();
AdditionalItemData.Insert(data);
}
return AdditionalItemData.Select();
}
}
[PXCacheName("Additional Item Data")]
[Serializable]
public class MyCustomTable : IBqlTable
{
#region CashAccountID
[Inventory]
[PXDefault]
public virtual int? InventoryID { get; set; }
public abstract class inventoryID : PX.Data.BQL.BqlInt.Field<inventoryID> { }
#endregion
#region Required
[PXDBBool()]
[PXDefault(false)]
[PXUIField(DisplayName = "Required")]
public virtual bool? Required { get; set; }
public abstract class required : PX.Data.BQL.BqlBool.Field<required> { }
#endregion
}
I have implemented CRM activity on the Custom page where the Key Field is SOOrder Type, SOOrder Nbr & Job Code which are stored in custom DAC. I have tried to add the Entity Type listed on Related Entity and I am not able to figure out how to do it. Pls let me know where to add or override the method to Implement the functionality
The following cod used to implement the CRM Activity
public sealed class SOOrderJobActivities : CRActivityList<PSSOOrderJob>
{
public SOOrderJobActivities(PXGraph graph)
: base(graph) { }
protected override RecipientList GetRecipientsFromContext(NotificationUtility utility, string type, object row, NotificationSource source)
{
var recipients = new RecipientList();
var order = _Graph.Caches[typeof(PSSOOrderJob)].Current as PSSOOrderJob;
if (order == null || source == null)
return null;
SOOrder ord = SOOrder.PK.Find(_Graph, order.OrderType, order.OrderNbr);
var contact = SOOrder.FK.Contact.FindParent(_Graph, ord);
if (contact == null || contact.EMail == null)
return null;
recipients.Add(new NotificationRecipient()
{
Active = true,
AddTo = RecipientAddToAttribute.To,
Email = contact.EMail
});
source.RecipientsBehavior = RecipientsBehaviorAttribute.Override;
return recipients;
}
}
Update
After going through the Acumatica code, I have done the following changes
#region Noteid
[PXNote(ShowInReferenceSelector =true,Selector =typeof(Search2<PSSOOrderJob.jobCode,
InnerJoin<SOOrder,On<PSSOOrderJob.orderType,Equal<SOOrder.orderType>,And<PSSOOrderJob.orderNbr, Equal<SOOrder.orderNbr>>>>,
Where<SOOrder.orderType,Equal<Current<PSSOOrderJob.orderType>>,And<SOOrder.orderNbr, Equal<Current<PSSOOrderJob.orderNbr>>>>>))]
public virtual Guid? Noteid { get; set; }
public abstract class noteid : PX.Data.BQL.BqlGuid.Field<noteid> { }
#endregion
The entity comes into selection, But I am not able to select the relative entity document and the value is not getting updated in the related entity field.
The above screenshot the select is missing and not able to select the document
The following steps I have done to add relative Entity for any Activity using custom DAC
1. Added ShowInReferenceSelector = true in PXNoteID field.
2. Added Selector in PXNoteID field
3. Decorated [PX.Data.EP.PXFieldDescription] attribute for Key fields
#region NoteID
[PXNote(ShowInReferenceSelector = true, Selector = typeof(Search2<PSSOOrderJob.jobCode,
InnerJoin<SOOrder, On<PSSOOrderJob.orderType, Equal<SOOrder.orderType>, And<PSSOOrderJob.orderNbr, Equal<SOOrder.orderNbr>>>>,
Where<SOOrder.orderType, Equal<Current<PSSOOrderJob.orderType>>, And<SOOrder.orderNbr, Equal<Current<PSSOOrderJob.orderNbr>>,And<PSSOOrderJob.jobType,Equal<Current<PSSOOrderJob.jobType>>>>>>), DescriptionField = typeof(PSSOOrderJob.jobCode))]
//[PXNote(ShowInReferenceSelector = true)]
public virtual Guid? NoteID { get; set; }
public abstract class noteID : PX.Data.BQL.BqlGuid.Field<noteID> { }
#endregion
#region JobCode
[PXDBString(15, IsKey = true, IsUnicode = true, InputMask = ">CCCCCCCCCCCCCCC")]
[PXUIField(DisplayName = "Job Code")]
[PXDefault()]
[PXSelector(typeof(Search<PSSOOrderJob.jobCode, Where<PSSOOrderJob.orderType, Equal<Current<PSSOOrderJob.orderType>>, And<PSSOOrderJob.orderNbr, Equal<Current<PSSOOrderJob.orderNbr>>,And<PSSOOrderJob.jobType, Equal<Current<PSSOOrderJob.jobType>>>>>>), typeof(PSSOOrderJob.jobCode), ValidateValue = false)]
[PSSOOrderJobNbr.Numbering()]
[PX.Data.EP.PXFieldDescription]
public virtual string JobCode { get; set; }
public abstract class jobCode : PX.Data.BQL.BqlString.Field<jobCode> { }
#endregion
This automatically fills the related entity field with Jobcode.
There still one issue I am facing is not able to access the selector due to Entity field width is more than the popup window and I do not know how to fix it.
This answer is to address the popup size only.
First, give browser zoom a try 'control' + 'minus' key. It might work as a quick workaround.
Otherwise, use the browser debugger feature. Open it with F12 key. Then use the browser debugger inspect element feature (1). Click on the smart panel (2). Go up a bit in html control hierarchy until you reach and select the smart panel root which is a table element (3). Change the width of the smart panel popup using the debugger CSS properties editor (4).
If selector control size increases automatically with window size; change the selector control width instead of popup width using browser debugger CSS property editor.
I have a customization to the Release Time Activities screen (EP507020) where I add a user field. This user field will contain the result of fetching the Appointment status from the Appointments screen (FS300200) based on the Appointment ID that I've also added to the Release Time Activities screen grid.
This was done so that the process grid could be filtered for appointment status that were a certain value. The Appointment Status User field I've added contains the same attributes that the Status field contains on the Appointments screen, with the Cache extension looking as follows:
#region UsrApptStatus
public abstract class usrApptStatus : IBqlField
{
}
[PXDBString(1)]
[FSAppointment.status.ListAtrribute]
[PXUIField(DisplayName = "Appt Status",Enabled = false)]
public virtual string UsrApptStatus { get; set; }
#endregion
This works fine when I fetch the status as follows in a Graph extension:
protected virtual void EPActivityApprove_RowSelected(PXCache sender, PXRowSelectedEventArgs e)
{
var epactivityapprove = (EPActivityApprove)e.Row;
if (epactivityapprove != null)
{
FSxPMTimeActivity rowExt = epactivityapprove.GetExtension<FSxPMTimeActivity>();
if (rowExt != null)
{
var appointmentID = rowExt.AppointmentID;
var fsappt = (FSAppointment)PXSelect<FSAppointment,
Where<FSAppointment.appointmentID, Equal<Required<FSAppointment.appointmentID>>>>.Select(Base, appointmentID);
var epactivityapproveext = PXCache<EPActivityApprove>.GetExtension<EPActivityApproveExt>(epactivityapprove);
epactivityapproveext.UsrApptStatus = fsappt.Status;
}
}
}
The problem is that when I go to filter the Status column, no matter what I choose from the list of options, it clears all rows. I have no idea why this wouldn't work, but I'm sure I'm missing something.
Grid before filtering:
Filter:
Grid after filter:
It seems to me that changing the value in the RowUpdated event is interfering with the filter. If you wrote both extensions, it would help to have both custom fields in the same extension and use the PXFormula attribute to set the value of your status based on the AppointmentID field. This way you wouldn't have to rely on the event:
#region UsrApptStatus
public abstract class usrApptStatus : IBqlField
{
}
[PXDBString(1)]
[FSAppointment.status.ListAtrribute]
[PXFormula(typeof(Selector<appointmentID, FSAppointment.status>))]
[PXUIField(DisplayName = "Appt Status",Enabled = false)]
public virtual string UsrApptStatus { get; set; }
#endregion
I'm customizing the Release Time Activities screen (EP507020) in the following ways:
First, I'm putting the Appointment Number in the grid via the 'Add Data Fields' option in the screen customization.
Second, I want to tie back to the Appointments screen (FS300200) via the Appointment Nbr to get the status.
Third, I want to add a user field to the Time Activities grid to hold this obtained status so that the grid can be filtered by the status.
I've run into several problems, the largest of which is that even though I can add the Appointment Number to the Release Time Activities screen grid - and upon inspection it shows that it belongs to the same DAC as all the other fields in that grid - it show NO WHERE in the DAC when I bring up the source code. It isn't in any table I can find either. This is a complete mystery - how can a field show on the inspection window as part of a DAC when it really isn't?
Next - there are two fields in the DAC for the Appointments screen DAC (FSAppointment) - RefNbr (which is the Appointment Number) and the AppointmentID (which is an auto-incrementing identity field). Which one would I use to tie back to the Appointments screen to link the Appointment Nbr (if that's even possible)?
The main issue is that I cannot find the Appointment Nbr in the DAC (EPActivityApprove) to even tie back to the Appointments screen.
Is this something doable?
The Activity dataview bound to Release Time Activities screen (EP507020) grid contains multiple DACs:
public PXFilteredProcessingJoin<
EPActivityApprove,
EPActivityFilter,
LeftJoin<EPEarningType,
On<EPEarningType.typeCD, Equal<EPActivityApprove.earningTypeID>>,
InnerJoin<EPEmployee,
On<EPEmployee.userID, Equal<EPActivityApprove.ownerID>,
And<Where<EPEmployee.timeCardRequired, NotEqual<True>, Or<EPEmployee.timeCardRequired, IsNull>>>>,
LeftJoin<CRActivityLink,
On<CRActivityLink.noteID, Equal<EPActivityApprove.refNoteID>>,
LeftJoin<CRCase,
On<CRCase.noteID, Equal<CRActivityLink.refNoteID>>,
LeftJoin<CRCaseClass,
On<CRCaseClass.caseClassID, Equal<CRCase.caseClassID>>,
LeftJoin<ContractEx,
On<CRCase.contractID, Equal<ContractEx.contractID>>>>>>>>
[...]
Looking at the main DAC we see it extends PMTimeActivity so it will contain all fields of that DAC too:
public class EPActivityApprove : PMTimeActivity
PMTimeActivity doesn't contain AppointmentID either but another DAC named FSxPMTimeActivity does and it also extends PMTimeActivity. That's the reason why you're seeing that field available in EPActivityApprove:
public class FSxPMTimeActivity : PXCacheExtension<PMTimeActivity>
{
#region AppointmentID
public abstract class appointmentID : PX.Data.IBqlField
{
}
[PXDBInt]
[PXUIField(DisplayName = "Appointment Nbr.")]
[PXSelector(typeof(Search<FSAppointment.appointmentID>),
SubstituteKey = typeof(FSAppointment.refNbr))]
public virtual int? AppointmentID { get; set; }
#endregion
}
Given an EPActivityApprove object I think you can get a reference to the extension containing AppointmentID like this:
EPActivityApprove row = ???;
FSxPMTimeActivity rowExt = row.GetExtension<FSxPMTimeActivity>();
int? appointmentID = rowExt.AppointmentID;
As for the joins, you must specify each key field (IsKey = true) from the DAC.
For FSAppointment these would be SrvOrdType and RefNbr (appointmentID is not a key field in that DAC):
#region SrvOrdType
public abstract class srvOrdType : PX.Data.IBqlField
{
}
[PXDBString(4, IsFixed = true, IsKey = true, InputMask = ">AAAA")]
[PXDefault(typeof(FSSetup.dfltSrvOrdType))]
[PXUIField(DisplayName = "Service Order Type")]
[FSSelectorSrvOrdTypeNOTQuote]
[PX.Data.EP.PXFieldDescription]
public virtual string SrvOrdType { get; set; }
#endregion
#region RefNbr
public abstract class refNbr : PX.Data.IBqlField
{
}
[PXDBString(20, IsKey = true, IsUnicode = true, InputMask = "CCCCCCCCCCCCCCCCCCCC")]
[PXDefault(PersistingCheck = PXPersistingCheck.Nothing)]
[PXUIField(DisplayName = "Appointment Nbr.", Visibility = PXUIVisibility.SelectorVisible, Visible = true, Enabled = true)]
[PXSelector(typeof(
Search2<FSAppointment.refNbr,
LeftJoin<FSServiceOrder,
On<FSServiceOrder.sOID, Equal<FSAppointment.sOID>>,
LeftJoin<Customer,
On<Customer.bAccountID, Equal<FSServiceOrder.customerID>>,
LeftJoin<Location,
On<Location.locationID, Equal<FSServiceOrder.locationID>>>>>,
Where<
FSAppointment.srvOrdType, Equal<Optional<FSAppointment.srvOrdType>>>,
OrderBy<Desc<FSAppointment.refNbr>>>),
new Type[] {
typeof(FSAppointment.refNbr),
typeof(Customer.acctCD),
typeof(Customer.acctName),
typeof(Location.locationCD),
typeof(FSAppointment.docDesc),
typeof(FSAppointment.status),
typeof(FSAppointment.scheduledDateTimeBegin)
})]
[AppointmentAutoNumber(typeof(
Search<FSSrvOrdType.srvOrdNumberingID,
Where<
FSSrvOrdType.srvOrdType, Equal<Optional<FSAppointment.srvOrdType>>>>),
typeof(AccessInfo.businessDate))]
[PX.Data.EP.PXFieldDescription]
public virtual string RefNbr { get; set; }
#endregion
EDIT:
Took a better look at it, I don't think you'll be able to join on the key fields. Because AppointmentID is a Unique Identifier field you can make an exception in this case and join only on that field because it will behave as a single key field.
I want to hide or update a field on the UI based on conditions of another field.
For example, if I have a field called Color:
[PXUIField(DisplayName="Color")]
[PXStringList("Red,Blue,Other")]
[PXDefault("Red")]
And text field for comments only shown when "Other" is selected, how is this accomplished?
The requested behavior can either be accomplished either with a series of event handlers or with a bunch of attributes. You can find several examples on how to subscribe to the RowSelected and FieldUpdated events in the T200 training course, available at Acumatica University and Acumatica Open University
Going with field attributes is a more convenient and way easier option for your particular scenario. I would recommend setting CommitChanges to True for the drop-down, so the Comments field is cleared and disabled/enabled immediately after the user updates Color. Also, it's very to have your Color declared after Comments, so the framework will process Comments field first and always clear the current Comments value after the Color field got updated.
public class Other : Constant<string>
{
public Other() : base("Other") { }
}
public abstract class comments : IBqlField { }
[PXDBString(255, IsUnicode = true)]
[PXUIField(DisplayName = "Comments")]
[PXUIEnabled(typeof(Where<color, Equal<Other>>))]
[PXFormula(typeof(Default<color>))]
[PXDefault(PersistingCheck = PXPersistingCheck.Nothing)]
public string Comments { get; set; }
public abstract class color : IBqlField { }
[PXDBString(10, IsUnicode = true)]
[PXUIField(DisplayName = "Color")]
[PXStringList("Red,Blue,Other")]
[PXDefault("Red")]
public string Color { get; set; }
The only way to conditionally hide/show editor on a form is though the RowSelected event handler:
public void YourDAC_RowSelected(PXCache sender, PXRowSelectedEventArgs e)
{
YourDAC row = e.Row as YourDAC;
if (row == null) return;
PXUIFieldAttribute.SetVisible<YourDAC.comments>(sender, row, row.Color == "Other");
}
I believe, in the T200 training course, there are several examples on the PXUIFieldAttribute.SetVisible method.