On the Acumatica Invoice page in the Project/Conract search field by default search result show in format: "ProjectID - ProjectDescription". May be somebody know - Can we change this to "ProjectID - CustomerName"
screenshot
The ProjectID field uses the attribute ActiveProjectOrContractBaseAttribute. So I created a an extension which assigns the Description field. Override the default attribute in ProjectID field with this custom attribute. See if this achieves your goal.
public class ActiveProjectOrContractBaseAttributeExtended : AcctSubAttribute, IPXFieldVerifyingSubscriber
{
protected Type customerField;
public ActiveProjectOrContractBaseAttributeExtended() : this(null) { }
public ActiveProjectOrContractBaseAttributeExtended(Type customerField)
{
this.customerField = customerField;
PXDimensionSelectorAttribute select;
if (PXAccess.FeatureInstalled<FeaturesSet.projectModule>() && customerField != null)
{
List<Type> command = new List<Type>();
command.AddRange(new[] {
typeof(Search2<,,>),
typeof(PMProject.contractID),
typeof(LeftJoin<,>),
typeof(Customer),
typeof(On<,>),
typeof(Customer.bAccountID),
typeof(Equal<>),
typeof(PMProject.customerID),
});
command.AddRange(
new[]
{
typeof(Where<,,>),
typeof (PMProject.nonProject),
typeof (Equal<>),
typeof (True),
typeof (Or2<,>),
typeof (Where2<,>),
typeof (Where<,,>),
typeof (PMProject.customerID),
typeof (Equal<>),
typeof (Current<>),
customerField,
typeof (And<,,>),
typeof (PMProject.restrictProjectSelect),
typeof (Equal<>),
typeof (PMRestrictOption.customerProjects),
typeof (Or<,,>),
typeof (PMProject.restrictProjectSelect),
typeof (Equal<>),
typeof (PMRestrictOption.allProjects),
typeof (Or<,>),
typeof(PMProject.baseType),
typeof(Equal<PX.Objects.CT.CTPRType.contract>),
typeof (Or<,>),
typeof (Current<>),
customerField,
typeof (IsNull),
typeof (And2<,>),
typeof (Match<Current<AccessInfo.userName>>),
typeof (Or<,>),
typeof (PMProject.nonProject),
typeof (Equal<>),
typeof (True)
});
select = new PXDimensionSelectorAttribute(ProjectAttribute.DimensionName,
BqlCommand.Compose(command.ToArray())
, typeof(PMProject.contractCD), typeof(PMProject.contractCD), typeof(PMProject.description),
typeof(PMProject.status), typeof(PMProject.customerID), typeof(Customer.acctName), typeof(PMProject.curyID));
}
else
{
select = new PXDimensionSelectorAttribute(ProjectAttribute.DimensionName,
typeof(Search2<PMProject.contractID,
LeftJoin<Customer, On<Customer.bAccountID, Equal<PMProject.customerID>>>,
Where<PMProject.nonProject, Equal<True>, Or<Match<Current<AccessInfo.userName>>>>>)
, typeof(PMProject.contractCD), typeof(PMProject.contractCD), typeof(PMProject.description),
typeof(PMProject.status), typeof(PMProject.customerID), typeof(Customer.acctName), typeof(PMProject.curyID)
);
}
select.DescriptionField = typeof(Customer.acctName);
select.ValidComboRequired = true;
select.CacheGlobal = true;
_Attributes.Add(select);
_SelAttrIndex = _Attributes.Count - 1;
Filterable = true;
DescriptionField = typeof(Customer.acctName);
}
public virtual void FieldVerifying(PXCache sender, PXFieldVerifyingEventArgs e)
{
PMProject project = PXSelect<PMProject>.Search<PMProject.contractID>(sender.Graph, e.NewValue);
if (customerField != null && project != null && project.NonProject != true)
{
int? customerID = (int?)sender.GetValue(e.Row, customerField.Name);
if (customerID != project.CustomerID)
{
sender.RaiseExceptionHandling(FieldName, e.Row, e.NewValue,
new PXSetPropertyException(Warnings.ProjectCustomerDontMatchTheDocument, PXErrorLevel.Warning));
}
}
}
}
Related
I am using Acumatica 2020 R1. I have a custom text field on the PMQuote DAC. This field is in a custom tab on the project quotes screen. I want that field to always be editable. I put the following in my RowSelected event:
protected virtual void PMQuote_RowSelected(PXCache cache, PXRowSelectedEventArgs e, PXRowSelected del)
{
del?.Invoke(cache, e);
PMQuote quote = e.Row as PMQuote;
cache.AllowUpdate = true;
PXUIFieldAttribute.SetEnabled(cache, e.Row, true);
PXUIFieldAttribute.SetEnabled<PMQuoteExt.usrMyCustomField>(cache, e.Row, true);
}
This didn't work, so I also looked in the automation steps. I didn't see any automation steps available to modify.
I then looked at Workflow. I didn't see any workflows to edit either. I tried creating a new one based on the status field. I added the user field for each status and made sure disabled was unchecked. This didn't work either.
Any ideas on how I can get that field to be enabled regardless of the document status?
Thanks for your help!
AllowUpdate is called only on the cache object, add it for the data view, example:
Base.Quote.AllowUpdate = true
Also, try to extend PXQuoteMaintExt graph extension:
public class PMQuoteMaintExtExtension : PXGraphExtension<PMQuoteMaintExt, PMQuoteMaint>
{
protected virtual void PMQuote_RowSelected(PXCache sender, PXRowSelectedEventArgs e, PXRowSelected sel)
{
sel?.Invoke(sender, e);
}
}
It manages visibility with RowSelected event:
namespace PX.Objects.PM
{
public class PMQuoteMaintExt : PXGraphExtension<PMDiscount, PMQuoteMaint>
{
protected virtual void PMQuote_RowSelected(PXCache sender, PXRowSelectedEventArgs e, PXRowSelected sel)
{
sel?.Invoke(sender, e);
var row = e.Row as PMQuote;
if (row == null) return;
VisibilityHandler(sender, row);
}
private void VisibilityHandler(PXCache sender, PMQuote row)
{
CR.Standalone.CROpportunityRevision revisionInDb = PXSelectReadonly<CR.Standalone.CROpportunityRevision,
Where<CR.Standalone.CROpportunityRevision.noteID, Equal<Required<CR.Standalone.CROpportunityRevision.noteID>>>>.Select(Base, row.QuoteID).FirstOrDefault();
CR.Standalone.CROpportunity opportunityInDb = (revisionInDb == null) ? null : PXSelectReadonly<CR.Standalone.CROpportunity,
Where<CR.Standalone.CROpportunity.opportunityID, Equal<Required<CR.Standalone.CROpportunity.opportunityID>>>>.Select(Base, revisionInDb.OpportunityID).FirstOrDefault();
CR.Standalone.CROpportunity opportunity = PXSelect<CR.Standalone.CROpportunity,
Where<CR.Standalone.CROpportunity.opportunityID, Equal<Required<CR.Standalone.CROpportunity.opportunityID>>>>.Select(Base, row.OpportunityID).FirstOrDefault();
var opportunityIsClosed = opportunity?.IsActive == false;
bool allowUpdate = row.IsDisabled != true && !opportunityIsClosed && row.Status != PMQuoteStatusAttribute.Closed;
if (opportunityInDb?.OpportunityID == opportunity?.OpportunityID)
Base.Caches[typeof(PMQuote)].AllowUpdate = allowUpdate;
else
{
var quoteCache = Base.Caches[typeof(PMQuote)];
foreach (var field in quoteCache.Fields)
{
if (!quoteCache.Keys.Contains(field) &&
field != quoteCache.GetField(typeof(PMQuote.opportunityID)) &&
field != quoteCache.GetField(typeof(PMQuote.isPrimary)))
PXUIFieldAttribute.SetEnabled(sender, row, field, allowUpdate);
}
}
PXUIFieldAttribute.SetEnabled<PMQuote.bAccountID>(sender, row, row.OpportunityID == null);
Base.Caches[typeof(PMQuote)].AllowDelete = !opportunityIsClosed;
foreach (var type in new[]
{
typeof(CR.CROpportunityDiscountDetail),
typeof(CR.CROpportunityProducts),
typeof(CR.CRTaxTran),
typeof(CR.CRAddress),
typeof(CR.CRContact),
typeof(CR.CRPMTimeActivity),
typeof(PM.PMQuoteTask)
})
{
Base.Caches[type].AllowInsert = Base.Caches[type].AllowUpdate = Base.Caches[type].AllowDelete = allowUpdate;
}
Base.Caches[typeof(CopyQuoteFilter)].AllowUpdate = true;
Base.Caches[typeof(RecalcDiscountsParamFilter)].AllowUpdate = true;
Base.Actions[nameof(Base.Approval.Submit)]
.SetVisible(row.Status == PMQuoteStatusAttribute.Draft);
Base.actionsFolder
.SetVisible(nameof(Base.Approval.Approve), Base.Actions[nameof(Base.Approval.Approve)].GetVisible());
Base.actionsFolder
.SetVisible(nameof(Base.Approval.Reject), Base.Actions[nameof(Base.Approval.Reject)].GetVisible());
Base.Actions[nameof(EditQuote)]
.SetVisible(row.Status != PMQuoteStatusAttribute.Draft);
Base.Actions[nameof(Base.Approval.Submit)]
.SetEnabled(row.Status == PMQuoteStatusAttribute.Draft && !opportunityIsClosed);
Base.Actions[nameof(Base.Approval.Approve)]
.SetEnabled(row.Status == PMQuoteStatusAttribute.PendingApproval);
Base.Actions[nameof(Base.Approval.Reject)]
.SetEnabled(row.Status == PMQuoteStatusAttribute.PendingApproval);
Base.Actions[nameof(EditQuote)]
.SetEnabled(row.Status != PMQuoteStatusAttribute.Draft && row.Status != PMQuoteStatusAttribute.Closed);
Base.Actions[nameof(Base.CopyQuote)]
.SetEnabled(Base.Caches[typeof(PMQuote)].AllowInsert);
Base.Actions[nameof(PMDiscount.GraphRecalculateDiscountsAction)]
.SetEnabled((row.Status == PMQuoteStatusAttribute.Draft));
Base.Actions[nameof(Base.PrimaryQuote)].SetEnabled(!String.IsNullOrEmpty(row.OpportunityID) && row.IsPrimary == false && row.Status != PMQuoteStatusAttribute.Closed);
Base.Actions[nameof(Base.SendQuote)].SetEnabled(row.Status.IsIn<string>(PMQuoteStatusAttribute.Approved, PMQuoteStatusAttribute.Sent, PMQuoteStatusAttribute.Closed));
Base.Actions[nameof(Base.PrintQuote)].SetEnabled(true);
Base.convertToProject.SetEnabled( (row.OpportunityID == null || row.IsPrimary == true) && row.QuoteProjectID == null && row.Status.IsIn<string>(PMQuoteStatusAttribute.Approved, PMQuoteStatusAttribute.Sent));
PXUIFieldAttribute.SetEnabled<PMQuote.subject>(sender, row, true);
PXUIFieldAttribute.SetEnabled<PMQuote.status>(sender, row, false);
}
}
}
Is it possible to change the default option all column filters? I'm pretty sure I can accomplish this with some JavaScript, but I'd like to know if there's any way within the Acumatica framework to change this.
The answer will be no. The filter is inside the PX.Web.UI.dll in the PXGridFilter class which is an internal class. The property that you are interested in is the Condition.
The value is set inside one of the private methods of the PXGrid class. The code of the method is below:
private IEnumerable<PXGridFilter> ab()
{
List<PXGridFilter> list = new List<PXGridFilter>();
if (this.FilterID != null)
{
Guid? filterID = this.FilterID;
Guid guid = PXGrid.k;
if (filterID == null || (filterID != null && filterID.GetValueOrDefault() != guid))
{
using (IEnumerator<PXResult<FilterRow>> enumerator = PXSelectBase<FilterRow, PXSelect<FilterRow, Where<FilterRow.filterID, Equal<Required<FilterRow.filterID>>, And<FilterRow.isUsed, Equal<True>>>>.Config>.Select(this.DataGraph, new object[]
{
this.FilterID.Value
}).GetEnumerator())
{
while (enumerator.MoveNext())
{
FilterRow row = enumerator.Current;
string dataField = row.DataField;
PXCache pxcache = PXFilterDetailView.TargetCache(this.DataGraph, new Guid?(this.FilterID.Value), ref dataField);
if (this.Columns[row.DataField] != null)
{
List<PXGridFilter> list2 = list;
int valueOrDefault = row.OpenBrackets.GetValueOrDefault();
string dataField2 = row.DataField;
string dataField3 = row.DataField;
int value = (int)row.Condition.Value;
object value2 = pxcache.ValueFromString(dataField, row.ValueSt);
string valueText = row.ValueSt.With((string _) => this.Columns[row.DataField].FormatValue(_));
object value3 = pxcache.ValueFromString(dataField, row.ValueSt2);
string value2Text = row.ValueSt2.With((string _) => this.Columns[row.DataField].FormatValue(_));
int valueOrDefault2 = row.CloseBrackets.GetValueOrDefault();
int? #operator = row.Operator;
int num = 1;
list2.Add(new PXGridFilter(valueOrDefault, dataField2, dataField3, value, value2, valueText, value3, value2Text, valueOrDefault2, #operator.GetValueOrDefault() == num & #operator != null));
}
}
return list;
}
}
}
if (this.FilterRows != null && this.FilterRows.Count > 0)
{
for (int i = 0; i < this.FilterRows.Count; i++)
{
PXFilterRow row = this.FilterRows[i];
list.Add(new PXGridFilter(row.OpenBrackets, row.DataField, row.DataField, (int)row.Condition, row.Value, row.Value.With(delegate(object _)
{
if (this.Columns[row.DataField] == null)
{
return _.ToString();
}
return this.Columns[row.DataField].FormatValue(_);
}), row.Value2, row.Value2.With(delegate(object _)
{
if (this.Columns[row.DataField] == null)
{
return _.ToString();
}
return this.Columns[row.DataField].FormatValue(_);
}), row.CloseBrackets, row.OrOperator));
}
}
return list;
}
UPDATE
The below JS code allows you to change the condition of the last set filter:
px_all.ctl00_phG_grid_fd.show();
px_all.ctl00_phG_grid_fd_cond.items.items[7].setChecked(true);
px_all.ctl00_phG_grid_fd_ok.element.click();
px_all.ctl00_phG_grid_fd_cond.items.items[7].value "EQ"
px_all.ctl00_phG_grid_fd_cond.items.items[8].value "NE"
px_all.ctl00_phG_grid_fd_cond.items.items[9].value "GT"
px_all.ctl00_phG_grid_fd_cond.items.items[13].value "LIKE"
px_all.ctl00_phG_grid_fd_cond.items.items[14].value "LLIKE"
px_all.ctl00_phG_grid_fd_cond.items.items[15].value "RLIKE"
px_all.ctl00_phG_grid_fd_cond.items.items[16].value "NOTLIKE"
px_all.ctl00_phG_grid_fd_cond.items.items[18].value "ISNULL"
px_all.ctl00_phG_grid_fd_cond.items.items[19].value "ISNOTNULL"
After I customized the code below and I want to update SLA by AssignDateTime. But with the Severity changed then my SLA has changed to get datetime from createdDateTime also. I think it should have other event that need to customize.
protected virtual void CRCase_RowUpdated(PXCache cache, PXRowUpdatedEventArgs e, PXRowUpdated InvokeBaseHandler)
{
if (InvokeBaseHandler != null)
InvokeBaseHandler(cache, e);
var row = e.Row as CRCase;
var oldRow = e.OldRow as CRCase;
CRCaseExt rowExt = PXCache<CRCase>.GetExtension<CRCaseExt>(row);
if (row == null || oldRow == null) return;
if (row.OwnerID == null)
{
row.AssignDate = null;
row.SLAETA = null;
}
else if (oldRow.OwnerID == null)
{
row.AssignDate = PXTimeZoneInfo.Now;
if (row == null || row.AssignDate == null) return;
if (row.ClassID != null && row.Severity != null)
{
var severity = (CRClassSeverityTime)PXSelect<CRClassSeverityTime,
Where<CRClassSeverityTime.caseClassID, Equal<Required<CRClassSeverityTime.caseClassID>>,
And<CRClassSeverityTime.severity, Equal<Required<CRClassSeverityTime.severity>>>>>
.Select(Base, row.ClassID, row.Severity);
if (severity != null && severity.TimeReaction != null)
{
row.SLAETA = ((DateTime)row.AssignDate).AddMinutes((int)severity.TimeReaction);
}
}
if (row.Severity != null && row.ContractID != null)
{
var template = (Contract)PXSelect<Contract, Where<Contract.contractID, Equal<Required<CRCase.contractID>>>>.Select(Base, row.ContractID);
if (template == null) return;
var sla = (ContractSLAMapping)PXSelect<ContractSLAMapping,
Where<ContractSLAMapping.severity, Equal<Required<CRCase.severity>>,
And<ContractSLAMapping.contractID, Equal<Required<CRCase.contractID>>>>>
.Select(Base, row.Severity, template.TemplateID);
if (sla != null && sla.Period != null)
{
row.SLAETA = ((DateTime)row.AssignDate).AddMinutes((int)sla.Period);
}
}
}
}
SLAETA field is decorated with PXFormulaAttribute to raise FieldDefaulting event every time change is made to one of the following fields:
CRCase.contractID
CRCase.severity
CRCase.caseClassID
public partial class CRCase : IBqlTable, IAssign, IAttributeSupport, IPXSelectable
{
...
#region SLAETA
public abstract class sLAETA : IBqlField { }
[PXDBDate(PreserveTime = true, DisplayMask = "g")]
[PXUIField(DisplayName = "SLA")]
[PXFormula(typeof(Default<CRCase.contractID, CRCase.severity, CRCase.caseClassID>))]
public virtual DateTime? SLAETA { get; set; }
#endregion
...
}
It’s a way better to only customize CRCase_SLAETA_FieldDefaulting handler in the CRCaseMaint BLC extension instead of implementing CRCase_RowUpdated:
public class CRCaseMaint : PXGraph<CRCaseMaint, CRCase>
{
...
protected virtual void CRCase_SLAETA_FieldDefaulting(PXCache sender, PXFieldDefaultingEventArgs e)
{
CRCase row = e.Row as CRCase;
if (row == null || row.CreatedDateTime == null) return;
if (row.ClassID != null && row.Severity != null)
{
var severity = (CRClassSeverityTime)PXSelect<CRClassSeverityTime,
Where<CRClassSeverityTime.caseClassID, Equal<Required<CRClassSeverityTime.caseClassID>>,
And<CRClassSeverityTime.severity, Equal<Required<CRClassSeverityTime.severity>>>>>.
Select(this, row.ClassID, row.Severity);
if (severity != null && severity.TimeReaction != null)
{
e.NewValue = ((DateTime)row.CreatedDateTime).AddMinutes((int)severity.TimeReaction);
e.Cancel = true;
}
}
if (row.Severity != null && row.ContractID != null)
{
var template = (Contract)PXSelect<Contract, Where<Contract.contractID, Equal<Required<CRCase.contractID>>>>.Select(this, row.ContractID);
if (template == null) return;
var sla = (ContractSLAMapping)PXSelect<ContractSLAMapping,
Where<ContractSLAMapping.severity, Equal<Required<CRCase.severity>>,
And<ContractSLAMapping.contractID, Equal<Required<CRCase.contractID>>>>>.
Select(this, row.Severity, template.TemplateID);
if (sla != null && sla.Period != null)
{
e.NewValue = ((DateTime)row.CreatedDateTime).AddMinutes((int)sla.Period);
e.Cancel = true;
}
}
}
...
}
You can either use the FieldUpdated Event or in the row updated event you can look for the change of your field.
Eg: row.Severity != oldRow.Severity
I am working on a method that compares two objects using reflection. The object types are objects created from entity framework. When I use GetProperties() I am getting EntityCollection and EntityReference properties. I only want the properties that belong to the object and not any associated properties or references from foreign keys.
I've tried the following How to get all names of properties in an Entity?.
I thought about passing an array of properties to compare but I don't want to have to type them in for each object type. I am open to some suggestions even those that don't use reflection.
public bool CompareEntities<T>(T oldEntity, T newEntity)
{
bool same = true;
PropertyInfo[] properties = oldEntity.GetType().GetProperties();
foreach (PropertyInfo property in properties)
{
var oldValue = property.GetValue(oldEntity, null);
var newValue = property.GetValue(newEntity, null);
if (oldValue != null && newValue != null)
{
if (!oldValue.Equals(newValue))
{
same = false;
break;
}
}
else if ((oldValue == null && newValue != null) || (oldValue != null && newValue == null))
{
same = false;
break;
}
}
return same;
}
Using the suggestions from #Eranga and https://stackoverflow.com/a/5381986/1129035 I was able to come up with a workable solution.
Since some of the properties in the root object are a GenericType there needs to be two different if statements. Only if the current property is an EntityCollection skip over it.
public bool CompareEntities<T>(T oldEntity, T newEntity)
{
bool same = true;
PropertyInfo[] properties = oldEntity.GetType().GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance)
.Where(pi => !(pi.PropertyType.IsSubclassOf(typeof(EntityObject)))
&& !(pi.PropertyType.IsSubclassOf(typeof(EntityReference)))
).ToArray();
foreach (PropertyInfo property in properties)
{
if (property.PropertyType.IsGenericType)
{
if (property.PropertyType.GetGenericTypeDefinition() == typeof(EntityCollection<>))
{
continue;
}
}
var oldValue = property.GetValue(oldEntity, null);
var newValue = property.GetValue(newEntity, null);
if (oldValue != null && newValue != null)
{
if (!oldValue.Equals(newValue))
{
same = false;
break;
}
}
else if ((oldValue == null && newValue != null) || (oldValue != null && newValue == null))
{
same = false;
break;
}
}
return same;
}
Try filtering out EntityObject type and EntityCollection properties.
var properties = oldEntity.GetType().GetProperties().
Where(pi => !(pi.PropertyType.IsSubclassOf(typeof(EntityObject))
|| pi.PropertyType.IsSubclassOf(typeof(EntityCollection));
make it easy for yourself and do this instead
PropertyInfo[] properties = oldEntity.GetType().GetProperties(pi=> pi.PropertyType.NameSpace=="System").ToArray();
Scenario:
Lets say we got to check for address lines. which includes addressline1, addressline2,Town,Country,Postcode
If any one of the property is entered, all other fields are mandatory.
If none of it is entered, the validation doesnt have to get trigged.
To achieve it, I ended up with two lines of If statement.
Like
if(AddressLine1 != null || AddressLine2 != null || Town != null || Country != null)
{
if(AddressLine1 != null && AddressLine2 != null && Town != null && Country != null) == false
{
return false;
}
}
Note: I am using c#. Are there any language constructs i can make use of.
private bool IsAddressValid(params string[] addressParts)
{
return addressParts.Any(p => p != null) ? addressParts.All(p => p != null) : true;
}
To be called like so:
var addressValid = IsAddressValid(AddressLine1, AddressLine2, Town, County);
Well, the null-coalescing operator can help with the first:
if (AddressLine1 ?? AddressLine2 ?? Town ?? Country != null)
{
if (AddressLine1 == null || AddressLine2 == null ||
Town == null || Country == null)
{
return false;
}
// Presumably there's more here
}
You might want to write some helper methods though:
if (IsAnyNonNull(AddressLine1, AddressLine2, Town, Country))
{
if (IsAnyNull(AddressLine1, AddressLine2, Town, Country))
{
return false;
}
}
Where the utility methods would be something like:
public static bool IsAnyNonNull(params object[] values)
{
return values.Any(x => x != null);
}
public static bool IsAnyNull(params object[] values)
{
return values.Any(x => x == null);
}
Of course, you've still got two if statements - but I think that's basically necessary here anyway.
If you make an array of the fields in the group, then you can do:
var fields = new object[] {AddressLine1, AddressLine2, Town, Country};
return fields.All(f => f == null) || fields.All(f => f != null);
Define this:
public static bool SameNullness(params object[] values)
{
int nullCount = 0;
foreach (var value in values)
{
if (value == null) nullCount++;
}
return nullCount == values.Length;
}
Then use it like:
SameNullness(AddressLine1, AddressLine2, Town, Country);