I have 2 scenarios, I am facing issue.
I have 3 DAC, One is Parent and other 2 are child DAC.
I have tab view, and showing data accordingly.
First tab showing Parent DAC data.
Second and third are showing child DAC data.
I am creating new item, Now i press Add(+) button and fill, Information in Parent tab, but not filled any data in 2nd and 3rd tab, when i press SAVE then only parent DAC saved, 2nd and 3rd tab rows not created in database, I want to save 2nd and 3rd Tab DAC also, what should i do?
Another case:
i am not allowing DAC to update, I am setting DAC.Cahce.AllowUpdated = false; all controls are disabled excepting Checkbox, why?
Also when we are on 3rd tab and click on toolbar Navigation button, then 1st tab data doesn't refresh, it is showing previous selected item data. How to fix this issue?
We are not using PXGrid for any child data, there is one to one relation ship between all 3 DAC.
/// Parent View
public SelectFrom<Parent>.View parentTran;
/// Child view
public SelectFrom<Child> .Where<Child.tranId.IsEqual<Parent.nCTranId.AsOptional>>.View
childAnalysis;
#region TranId
// Child DAC Field.
public abstract class tranId:PX.Data.BQL.BqlInt.Field<tranId> {
}
[PXDBInt(IsKey = true)]
[PXDBDefault(typeof(Parent.nCTranId))]
[PXParent(typeof(Select<Parent, Where<Parent.nCTranId, Equal<Current<tranId>>>>))]
public virtual int? TranId {
get; set;
}
#endregion
Take a look at T210 regarding Master-Detail Relationships. It contains a section on inserting a default record, which sounds like what you are trying to do with your child DAC. The training can be found at https://openuni.acumatica.com/courses/development/t210-development-customized-forms-and-master-detail-relationship/ on the Acumatica website. Specifically look at pages 103 and 104.
In step 3 of the section, you will see use of the RowInserted event to insert the child record. Since the RowInserted event fires only when creating a record, making it safe to insert the child record.
See the following code sample. I tried to use as much of your sample as possible, but I merged it with the technique shown in the training guide. Disclaimer: It will compile, but I have not built the prerequisites to fully test the code.
using PX.Data;
using PX.Data.BQL.Fluent;
namespace SSCS.CodeSample
{
public class CodeSampleEntry : PXGraph<CodeSampleEntry, DACParent>
{
#region Data Views
/// Parent View
[PXViewName("Parent View - Primary")]
public SelectFrom<DACParent>.View ParentTran;
/// Child view
[PXViewName("Child View")]
public SelectFrom<DACChild>
.Where<DACChild.tranId.IsEqual<DACParent.nCTranId.FromCurrent>>
.View ChildAnalysis;
#endregion
// RowInserted Event to Create a Default Child Record
// See T210 Master-Detail training guide from Acumatica
protected virtual void _(Events.RowInserted<DACParent> e)
{
// Ensure there isn't a child record already
if (ChildAnalysis.Select().Count == 0)
{
// Save state of IsDirty so that we can restore it
// after inserting the default child record
// so that we don't force the user to save
// unless they actually enter data somewhere
bool oldDirty = ChildAnalysis.Cache.IsDirty;
// Create an object for the child
// and fill in any data that should be
// populated by default
DACChild newChild = new DACChild();
newChild.MyData = "My Value";
// Insert the new child record into the cache
ChildAnalysis.Insert(newChild);
// Restore the IsDirty flag
// The insert above will have marked the view as
// dirty. If it wasn't dirty before, it should be
// treated as if it still isn't
ChildAnalysis.Cache.IsDirty = oldDirty;
}
}
}
[PXCacheName("Parent DAC")]
public class DACParent : IBqlTable
{
#region NCTranId
[PXDBIdentity]
public virtual int? NCTranId { get; set; }
public abstract class nCTranId
: PX.Data.BQL.BqlInt.Field<nCTranId> { }
#endregion
}
[PXCacheName("Child DAC")]
public class DACChild : IBqlTable
{
#region TranId
[PXDBInt(IsKey = true)]
[PXDBDefault(typeof(DACParent.nCTranId))]
[PXParent(typeof(SelectFrom<DACParent>
.Where<DACParent.nCTranId.IsEqual<DACChild.tranId.FromCurrent>>))]
public virtual int? TranId { get; set; }
public abstract class tranId
: PX.Data.BQL.BqlInt.Field<tranId> { }
#endregion
#region MyData
[PXDBString()]
[PXUIField(DisplayName = "My Data Field")]
public virtual string MyData { get; set; }
public abstract class myData
: PX.Data.BQL.BqlString.Field<myData> { }
#endregion
}
}
Related
I have a number of non persistent fields that get its value via FieldSelecting events. The events do the same queries and calculations so I decided to move them into an attribute that implements IPXFieldSelectingSubscriber interface. This works fine and it minimises duplicate codes. However, the event fires every single time the DAC is accessed. Be that in GIs or called in some other graphs. Is there such a flag to verify if the event is being fired only from a specific screen ?
The easiest way to do that would be to add the validation of which screen is accessing the DAC, you can do that by simply putting an if statement like below into your event handler.
if(graph.Accessinfo.ScreenID == "SO.30.10.00")
{
//execute the logic
}
else
{
//skip
}
An alternative solution would be to have the base field type declared on your DAC and then within a graph extension specific to the page you want the logic on, you would declare a CacheAttached event with the custom attribute.
DAC field :
public sealed class APInvoiceExtension : PXCacheExtension<APInvoice>
{
#region UsrTotal
public abstract class usrTotal : BqlDecimal.Field<usrTotal>
{
}
[PXDBDecimal]
[PXDefault(TypeCode.Decimal, "0.0", PersistingCheck = PXPersistingCheck.Nothing)]
[PXUIField(DisplayName = "Total")]
public decimal? UsrTotal { get; set; }
#endregion
}
Graph Extension :
public class APInvoiceEntryExtension : PXGraphExtension<APInvoiceEntry>
{
[PXMergeAttributes(Method = MergeMethod.Append)]
[CustomCalculationAttribute]
protected virtual void __(Events.CacheAttached<APInvoiceExtension.usrTotal> e)
{
}
}
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.