I have a Rich Text field attached to the PMQuote and CRQuote DACs. On the CRQuote, the user field is on the standalone class and the regular class, and that is working as desired. However, Acumatica's reporting does not show the HTML formatting so I am creating an unbound Plain Text field to put on the reports and in GI's. This field works fine on the PMQuote, but not on the CRQuote. On the CRQuote, the field is there, but it is always blank. I'm sure it is something to do with the projection, but I'm not sure how it should be created.
Here is the PMQuote field (working properly)
[PXString(IsUnicode = true)]
[PXUIField(Visible = false, DisplayName = "Scope Text")]
public virtual String UsrScopePlainText
{
get
{
return PX.Data.Search.SearchService.Html2PlainText(UsrScope);
}
}
public abstract class usrScopePlainText : PX.Data.BQL.BqlString.Field<usrScopePlainText> { }
And here is what I have on the CRQuote (not working):
public class CRQuoteExt : PXCacheExtension<CRQuote>
{
#region UsrScope
[PXDBText(IsUnicode = true, BqlField = typeof(CRQuoteStandaloneExt.usrScope))]
[PXUIField(DisplayName = "Scope")]
public virtual string UsrScope { get; set; }
public abstract class usrScope : IBqlField { }
#endregion
#region UsrScopePlainText
[PXString(IsUnicode = true)]
[PXUIField(Visible = false, DisplayName = "Scope Text")]
public virtual String UsrScopePlainText
{
get
{
return PX.Data.Search.SearchService.Html2PlainText(UsrScope);
}
}
public abstract class usrScopePlainText : IBqlField { }
#endregion
}
public class CRQuoteStandaloneExt : PXCacheExtension<PX.Objects.CR.Standalone.CRQuote>
{
#region UsrScope
[PXDBText(IsUnicode = true)]
[PXUIField(DisplayName = "Scope")]
public virtual string UsrScope { get; set; }
public abstract class usrScope : IBqlField { }
#endregion
#region UsrScopePlainText
[PXString(IsUnicode = true)]
[PXUIField(Visible = false, DisplayName = "Scope Text")]
public virtual String UsrScopePlainText
{
get
{
return PX.Data.Search.SearchService.Html2PlainText(UsrScope);
}
}
public abstract class usrScopePlainText : IBqlField { }
#endregion
}
Thanks!
An alternative solution could be to have a reusable attribute that you can attach to any field. I think this is cleaner than adding logic directly in the Data Access class...
using PX.Objects.IN;
using PX.Data;
using System;
namespace PX.Objects.IN
{
public class InventoryItemPlainTextBodyExt : InventoryItem
{
[HtmlToText(typeof(InventoryItem.body))]
[PXDependsOnFields(typeof(InventoryItem.body))]
[PXUIField(DisplayName="Body (Plain Text)")]
public virtual string BodyPlainText { get; set; }
public abstract class bodyPlainText : PX.Data.BQL.BqlString.Field<bodyPlainText> { }
}
[PXString]
public class HtmlToTextAttribute : PXEventSubscriberAttribute, IPXFieldSelectingSubscriber
{
protected Type _htmlField;
public HtmlToTextAttribute(Type htmlField) :base()
{
_htmlField = htmlField;
}
public void FieldSelecting(PXCache sender, PXFieldSelectingEventArgs e)
{
e.ReturnValue = PX.Data.Search.SearchService.Html2PlainText(sender.GetValue(e.Row, _htmlField.Name) as string);
}
}
}
I'm not sure what happened, but I put the original code I posted above back into the customization project and it started working as expected.
Related
I am trying to update the dropdown values for Reason field in Cases if Status is set to Open.
{
if (InvokeBaseHandler != null)
InvokeBaseHandler(cache, e);
var row = (CRCase)e.Row;
if (row == null) return;
if (row.Status == CRCaseStatusesAttribute._OPEN)
{
PXStringListAttribute.SetList<CRCase.resolution>(cache, row,
new string[] { "IP", "AD", "ES", "QQ", "CC" },
new string[] { "In Process", "Updated", "In Escalation", "Pending Quote", "Pending Closure"}
);
}
}
But it doesn't seem to be reflecting on the UI.
I checked if anything else is overriding my code in CRCaseMaint graph but couldn't find any which seem to be affecting the List.
I couldn't see anything off in Field definition though.
#region Resolution
public abstract class resolution : PX.Data.BQL.BqlString.Field<resolution> { }
[PXDBString(2, IsFixed = true)]
[CRCaseResolutions]
[PXUIField(DisplayName = "Reason")]
[PXChildUpdatable]
[PXMassUpdatableField]
public virtual String Resolution { get; set; }
#endregion
public sealed class CRCaseResolutionsAttribute : PXStringListAttribute
{
public const string _CUSTOMER_PRECLOSED = "CC";
public const string _CUSTOMER_REPLIED = "AD";
public const string _RESOLVED = "RD";
public const string _ASSIGNED = "AS";
public const string _UNASSIGNED = "UA";
public const string _UPDATED = "AD";
public CRCaseResolutionsAttribute();
public sealed class CustomerPreclosed : BqlType<IBqlString, string>.Constant<CustomerPreclosed>
{
public CustomerPreclosed();
}
public sealed class CustomerReplied : BqlType<IBqlString, string>.Constant<CustomerReplied>
{
public CustomerReplied();
}
public sealed class Resolved : BqlType<IBqlString, string>.Constant<Resolved>
{
public Resolved();
}
public sealed class Assigned : BqlType<IBqlString, string>.Constant<Assigned>
{
public Assigned();
}
public sealed class Unassigned : BqlType<IBqlString, string>.Constant<Unassigned>
{
public Unassigned();
}
public sealed class Updated : BqlType<IBqlString, string>.Constant<Updated>
{
public Updated();
}
}
I think I can definitely use different labels and values than the one listed above. Is there anything else that I am missing and/or need to look into?
Note: Acumatica 2019 R1 (Build 19.112.0045) is the version that I am working on.
Good morning, I want to add a new field on this screen Project Quotes but in doing so I get this message, that the table does not exist.
How should or what is the way to achieve it.
Thanks in advance
Imagen 01
The added field in the database
enter image description here
He added the field in the database and then I generated my extension.
namespace PX.Objects.CR
{
public class PMQuoteExt : PXCacheExtension<PX.Objects.CR.CRQuote>
{
#region UsrNota
[PXDBString(-1, InputMask = "", BqlField = typeof(PMQuoteStandaloneExt.usrNotaText))]
[PXUIField(DisplayName = "Nota ")]
public virtual string UsrNotaText { get; set; }
public abstract class usrNotaText : IBqlField { }
#endregion
}
public class PMQuoteStandaloneExt : PXCacheExtension<PX.Objects.CR.Standalone.CRQuote>
{
#region UsrNota
[PXDBString(-1, InputMask = "")]
[PXUIField(DisplayName = "Nota ")]
public virtual string UsrNotaText { get; set; }
public abstract class usrNotaText : IBqlField { }
#endregion
}
}
public class PMQuoteMaint_Extension : PXGraphExtension<PMQuoteMaint>
{
public PXSelect<PX.Objects.CR.Standalone.CRQuote> Test;
}
However, when I record, it does not fill the field.
that I am making a mistake or doing wrong.
Can you tell me please.
Thank you
PMQuote is not an actual DB table, but a BQL projection between tables:
CR.Standalone.CRQuote
CROpportunityRevision
CR.Standalone.CROpportunity
The way that I would tackle this is:
Add the field in table CRQuote
Extend the graph and override the Projection with the inclusion of the new CRQuote field.
UPDATE:
Based on #HB_Acumatica suggestion, step 2 would get simplified to a DAC extension (no need for the Graph extension). Much simpler to maintain in subsequent Acumatica versions!
UPDATE 2:
The extended DACs do not look correct in your question. Keep in mind that you should extend the original table (CRQuote), and the projection in order to have the value persisted.
The following definition worked correctly on my end:
//Projection extension
public class PMQuoteExt : PXCacheExtension<PMQuote>
{
#region UsrCustomField
[PXDBString(100, BqlField = typeof(CRQuoteExt.usrCustomField))]
[PXUIField(DisplayName="Custom Field")]
public virtual string UsrCustomField { get; set; }
public abstract class usrCustomField : IBqlField { }
#endregion
}
//Actual Table extension
public class CRQuoteExt : PXCacheExtension<PX.Objects.CR.Standalone.CRQuote>
{
#region UsrCustomField
[PXDBString(100)]
[PXUIField(DisplayName="Custom Field")]
public virtual string UsrCustomField { get; set; }
public abstract class usrCustomField : IBqlField { }
#endregion
}
I am having trouble to find a solution in how to save a char type field depending on the Tab that the user is positioned. I have "Fringe", "Overhead" and "G&A" tabs where the 3 of them share the same database table. The problem is that I want to save this Type field ("F","O" or "G") depending on the Tab that I am in.
Tabs and Type field in the grid
The solution that I am trying, was to create 3 different DAC Unbounded for each tab and save the information on the datamembers bounded to these tabs (the type field is included on each tab). After doing this, I used the Event Handler RowPersisting to insert manually on database (which it worked correctly) and also I needed to implement the RowDeleting to delete a row if needed. This is where this solution is not working.
Basically the RowDeleting EventHandler is not showing the correct information of the current row in which this event was triggered.
IU Row vs What the event is showing as the current row
I would look into using projections (PXProjection) in Acumatica so you can create additional DACs representing your different types (as they are located on different Tabs but stored in the same table). This way you don not have to use any events to do any complex/tricky logic against the framework as you have described you are doing.
I put together a simple example below. Note the 3 projection DACs that point to the same main dac. This allows the records to be all stored inMyTable in the database but each will have their own cache.
Here I define my different types...
public class MyDacType
{
public const string TypeA = "A";
public const string TypeB = "B";
public const string TypeC = "B";
public class typeA : Constant<string>
{
public typeA() : base(TypeA) { }
}
public class typeB : Constant<string>
{
public typeB() : base(TypeB) { }
}
public class typeC : Constant<string>
{
public typeC() : base(TypeC) { }
}
/// <summary>
/// List attribute for display of user friendly value
/// </summary>
public class ListAttribute : PXStringListAttribute
{
public ListAttribute()
: base(new string[]
{
TypeA,
TypeB,
TypeC
}, new string[]
{
"Type A",
"Type B",
"Type C"
})
{
}
}
}
This is my main table...
/// <summary>
/// Main database DAC
/// </summary>
[Serializable]
public class MyTable : IBqlTable
{
#region SomeKeyField
public abstract class someKeyField : PX.Data.IBqlField
{
}
protected string _SomeKeyField;
[PXDBString(10, IsKey = true, IsUnicode = true)]
[PXDefault]
[PXUIField(DisplayName = "My Key Field", Enabled = false)]
public virtual string SomeKeyField
{
get
{
return this._SomeKeyField;
}
set
{
this._SomeKeyField = value;
}
}
#endregion
#region DacType
public abstract class dacType : PX.Data.IBqlField
{
}
protected string _DacType;
[PXDBString(1)]
[PXDefault]
[MyDacType.List]
[PXUIField(DisplayName = "Dac Type")]
public virtual string DacType
{
get
{
return this._DacType;
}
set
{
this._DacType = value;
}
}
#endregion
#region Description
public abstract class description : PX.Data.IBqlField
{
}
protected string _Description;
[PXDBString(256, IsUnicode = true)]
[PXUIField(DisplayName = "Description")]
public virtual string Description
{
get
{
return this._Description;
}
set
{
this._Description = value;
}
}
#endregion
}
I created 3 projections (one for each tab)...
/// <summary>
/// DAC representing Type A (data stored in MyTable)
/// </summary>
[Serializable]
[PXProjection(typeof(Select<MyTable, Where<MyTable.dacType, Equal<MyDacType.typeA>>>), Persistent = true)]
public class MyTableTypeA : IBqlTable
{
#region SomeKeyField
public abstract class someKeyField : PX.Data.IBqlField
{
}
protected string _SomeKeyField;
[PXDBString(10, IsKey = true, IsUnicode = true, BqlField = typeof(MyTable.someKeyField))]
[PXDefault]
[PXUIField(DisplayName = "My Key Field", Enabled = false)]
public virtual string SomeKeyField
{
get
{
return this._SomeKeyField;
}
set
{
this._SomeKeyField = value;
}
}
#endregion
#region DacType
public abstract class dacType : PX.Data.IBqlField
{
}
protected string _DacType;
[PXDBString(1, BqlField = typeof(MyTable.dacType))]
[PXDefault(MyDacType.TypeA)]
[MyDacType.List]
[PXUIField(DisplayName = "Dac Type", Enabled = false, Visible = false)]
public virtual string DacType
{
get
{
return this._DacType;
}
set
{
this._DacType = value;
}
}
#endregion
#region Description
public abstract class description : PX.Data.IBqlField
{
}
protected string _Description;
[PXDBString(256, IsUnicode = true, BqlField = typeof(MyTable.description))]
[PXUIField(DisplayName = "Description")]
public virtual string Description
{
get
{
return this._Description;
}
set
{
this._Description = value;
}
}
#endregion
}
/// <summary>
/// DAC representing Type B (data stored in MyTable)
/// </summary>
[Serializable]
[PXProjection(typeof(Select<MyTable, Where<MyTable.dacType, Equal<MyDacType.typeB>>>), Persistent = true)]
public class MyTableTypeB : IBqlTable
{
#region SomeKeyField
public abstract class someKeyField : PX.Data.IBqlField
{
}
protected string _SomeKeyField;
[PXDBString(10, IsKey = true, IsUnicode = true, BqlField = typeof(MyTable.someKeyField))]
[PXDefault]
[PXUIField(DisplayName = "My Key Field", Enabled = false)]
public virtual string SomeKeyField
{
get
{
return this._SomeKeyField;
}
set
{
this._SomeKeyField = value;
}
}
#endregion
#region DacType
public abstract class dacType : PX.Data.IBqlField
{
}
protected string _DacType;
[PXDBString(1, BqlField = typeof(MyTable.dacType))]
[PXDefault(MyDacType.TypeB)]
[MyDacType.List]
[PXUIField(DisplayName = "Dac Type", Enabled = false, Visible = false)]
public virtual string DacType
{
get
{
return this._DacType;
}
set
{
this._DacType = value;
}
}
#endregion
#region Description
public abstract class description : PX.Data.IBqlField
{
}
protected string _Description;
[PXDBString(256, IsUnicode = true, BqlField = typeof(MyTable.description))]
[PXUIField(DisplayName = "Description")]
public virtual string Description
{
get
{
return this._Description;
}
set
{
this._Description = value;
}
}
#endregion
}
/// <summary>
/// DAC representing Type C (data stored in MyTable)
/// </summary>
[Serializable]
[PXProjection(typeof(Select<MyTable, Where<MyTable.dacType, Equal<MyDacType.typeC>>>), Persistent = true)]
public class MyTableTypeC : IBqlTable
{
#region SomeKeyField
public abstract class someKeyField : PX.Data.IBqlField
{
}
protected string _SomeKeyField;
[PXDBString(10, IsKey = true, IsUnicode = true, BqlField = typeof(MyTable.someKeyField))]
[PXDefault]
[PXUIField(DisplayName = "My Key Field", Enabled = false)]
public virtual string SomeKeyField
{
get
{
return this._SomeKeyField;
}
set
{
this._SomeKeyField = value;
}
}
#endregion
#region DacType
public abstract class dacType : PX.Data.IBqlField
{
}
protected string _DacType;
[PXDBString(1, BqlField = typeof(MyTable.dacType))]
[PXDefault(MyDacType.TypeC)]
[MyDacType.List]
[PXUIField(DisplayName = "Dac Type", Enabled = false, Visible = false)]
public virtual string DacType
{
get
{
return this._DacType;
}
set
{
this._DacType = value;
}
}
#endregion
#region Description
public abstract class description : PX.Data.IBqlField
{
}
protected string _Description;
[PXDBString(256, IsUnicode = true, BqlField = typeof(MyTable.description))]
[PXUIField(DisplayName = "Description")]
public virtual string Description
{
get
{
return this._Description;
}
set
{
this._Description = value;
}
}
#endregion
}
Now in my graph I create 3 views (1 for each PXProjection dac). I also do not need to display the DacType field since it will automatically default to the correct value.
public class MyGraph : PXGraph<MyGraph>
{
// Some other views...
/// <summary>
/// View for tab 1
/// </summary>
public PXSelect<MyTableTypeA> TypeA;
/// <summary>
/// View for tab 2
/// </summary>
public PXSelect<MyTableTypeB> TypeB;
/// <summary>
/// View for tab 3
/// </summary>
public PXSelect<MyTableTypeC> TypeC;
// When using events and cache attached you use the DAC name. Ex:
// protected virtual void MyTableTypeB_RowDeleting(PXCache cache, RowDeletingEvents e) { }
}
I'm trying to populate this field with the value of a customer attribute.
public class CustomerExt : PXCacheExtension<Customer>
{
#region OtherID
[PXString]
[PXUIField(DisplayName = "Other ID")]
[PXDBScalar(typeof(Search<CSAnswers.value,
Where<CSAnswers.refNoteID, Equal<Current<Customer.noteID>>,
And<CSAnswers.attributeID, Like<OtherIDAttr>>>>))]
public virtual string UsrOtherID { get; set; }
public abstract class usrOtherID : IBqlField { }
#endregion
public class OtherIDAttr: Constant<string>
{
public OtherIDAttr() : base("OTHERID") { }
}
}
It causes the above error when the field is added to a screen. If I remove the second condition from the Search<>, the field populates, so I'm sure its the comparison between the CSAnswers AttributeID and the constant string.
If someone could point me in the right direction, that would be awesome.
Looks like you get this error due to the Current operator (Equal<Current<Customer.noteID>>) used within PXDBScalarAttribute.
Attempt to simply remove the Current operator led to a different error Invalid column name 'NoteID'., which can be resolved by the replacement of Customer.noteID with PX.Objects.CR.BAccount.noteID:
public class CustomerExt : PXCacheExtension<Customer>
{
#region OtherID
public abstract class usrOtherID : IBqlField { }
[PXString]
[PXUIField(DisplayName = "Other ID")]
[PXDBScalar(typeof(Search<CSAnswers.value,
Where<CSAnswers.refNoteID, Equal<BAccount.noteID>,
And<CSAnswers.attributeID, Like<OtherIDAttr>>>>))]
public virtual string UsrOtherID { get; set; }
#endregion
public class OtherIDAttr : Constant<string>
{
public OtherIDAttr() : base("OTHERID") { }
}
}
We have a custom processing screen that is updating a custom field called UsrDateNotified in the ARTran table where the UsrDateNotified is prior to the RevisionDateReceived in a custom table ItemBaseDocument. We also need to update the UsrDateNotified field in table ItemBaseDocument which is linked to the InventoryID in ARTran. Our current code below validates for updating the ARTran table, but we are struggling with how to also update the related ItemBaseDocument for the selected ARTran records. What is the right approach for this scenario?
using System;
using System.Collections;
using System.Linq;
using PX.Data;
using PX.SM;
using PX.Objects.AR;
using PX.Objects.CR;
using PX.Objects.IN;
namespace DocCenter
{
public class UpdateLastNotified : PXGraph<UpdateLastNotified>
{
public PXFilter<UpdateLastNotifiedFilter> MasterView;
public PXCancel<UpdateLastNotifiedFilter> Cancel;
[PXFilterable]
public PXSelect<ARTran> DetailsView;
public UpdateLastNotified()
{
Cancel.SetCaption("Clear Filter");
this.DetailsView.Cache.AllowInsert = false;
this.DetailsView.Cache.AllowDelete = false;
this.DetailsView.Cache.AllowUpdate = true;
}
protected virtual IEnumerable detailsView()
{
UpdateLastNotifiedFilter filter = MasterView.Current as UpdateLastNotifiedFilter;
PXSelectBase<ARTran> cmd = new PXSelectJoinOrderBy<ARTran,
InnerJoin<InventoryItem, On<ARTran.inventoryID, Equal <InventoryItem.inventoryID>>,
InnerJoin<ItemBaseDocument, On<InventoryItemExt.usrDocumentNumber, Equal<ItemBaseDocument.baseDocumentCode>>,
InnerJoin<Contact, On<ARTranExt.usrContactID, Equal<Contact.contactID>>>>>,
OrderBy<Asc<ARTran.tranDate>>>(this);
cmd.WhereAnd<Where<ContactExt.usrNotificationPriority,
Equal<Current<UpdateLastNotifiedFilter.notificationPriority>>>>();
cmd.WhereAnd<Where<ARTranExt.usrDateNotified,
Less<ItemBaseDocument.revisionDateReceived>>>();
if (filter.BaseDocumentCode != null)
{
cmd.WhereAnd<Where<InventoryItemExt.usrDocumentNumber,
Equal<Current<UpdateLastNotifiedFilter.baseDocumentCode>>>>();
}
return cmd.Select();
}
public PXAction<UpdateLastNotifiedFilter> Process;
[PXProcessButton]
[PXButton(CommitChanges=true)]
[PXUIField(DisplayName = "Process")]
protected virtual IEnumerable process(PXAdapter adapter)
{
PXLongOperation.StartOperation(this, delegate()
{
foreach(ARTran tran in DetailsView.Select())
{
if (tran.Selected==true)
{
ARTranExt tranExt = tran.GetExtension<ARTranExt>();
ARInvoiceEntry tranEntry = new ARInvoiceEntry();
tranExt.UsrDateNotified = MasterView.Current.DateNotified;
tranEntry.Transactions.Update(tran);
tranEntry.Save.PressButton();
}
}
}
);
return adapter.Get();
}
[Serializable]
public class UpdateLastNotifiedFilter : IBqlTable
{
public static class NotificationPriority
{
public const string None = "N";
public const string Alert = "A";
public const string Express = "E";
public const string Shipment = "P";
public const string Subscription = "S";
}
#region NotificationPriority
public abstract class notificationPriority : PX.Data.IBqlField
{
}
[PXDBString(1, IsFixed = true)]
[PXDefault(NotificationPriority.None)]
[PXUIField(DisplayName = "Notification Type")]
[PXStringList(
new string[]
{
NotificationPriority.None,
NotificationPriority.Alert,
NotificationPriority.Express,
NotificationPriority.Shipment,
NotificationPriority.Subscription
},
new string[]
{
"None",
"Alert",
"Express",
"Shipment",
"Subscription"
})]
#endregion
#region BaseDocumentID
public abstract class baseDocumentCode : PX.Data.IBqlField
{
}
[PXString(50)]
[PXUIField(DisplayName="Document Number")]
[PXSelector(typeof(DocCenter.ItemBaseDocument.baseDocumentCode))]
public virtual String BaseDocumentCode
{
get;
set;
}
#endregion
#region DateNotified
public abstract class dateNotified : PX.Data.IBqlField
{
}
[PXDBDate()]
[PXDefault(typeof(AccessInfo.businessDate))]
[PXUIField(DisplayName = "Date Notified")]
public DateTime? DateNotified { get; set; }
#endregion
}
}
}
Your best bet would be to create a view that selects ItemBaseDocument.
PXSelect<ItemBaseDocument> ViewName;
In your for loop of your action button, you will want to create a new ItemBaseDocument object and set it equal to the corresponding ItemBaseDocument row entry. You can then update the date of this object, and when you execute your Save.PressButton() action, that should save the updates to that entry as well.
foreach(ARTran tran in DetailsView.Select())
{
if (tran.Selected==true)
{
ARTranExt tranExt = tran.GetExtension<ARTranExt>();
ARInvoiceEntry tranEntry = new ARInvoiceEntry();
tranExt.UsrDateNotified = MasterView.Current.DateNotified;
tranEntry.Transactions.Update(tran);
tranEntry.Save.PressButton();
//Target Added Code
ItemBaseDocument doc = PXSelect<ItemBaseDocument, Where<ItemBaseDocument.inventoryID,
Equal<Required<ARTran.inventoryID>>>>.Select(tran.InventoryID);
doc.UsrDateNotified = MasterView.Current.DateNotified;
}
}
Disclaimer: There may be a syntax error in the added code above. If there is, please let me know and I will fix it.