I have been trying to override the Process Delegate for the page "Update Contract Price".
public ContractPriceUpdate()
{
Items.SetSelected<ContractDetail.selected>();
Items.SetProcessDelegate<ContractMaint>(UpdatePrices);
Items.SetProcessCaption(Messages.Update);
Items.SetProcessAllCaption(Messages.UpdateAll);
PXUIFieldAttribute.SetDisplayName<Contract.contractCD>(Caches[typeof(Contract)], Common.Messages.Identifier);
}
protected static void UpdatePrices(ContractMaint graph, ContractDetail item)
{
Contract contract = PXSelect<Contract, Where<Contract.contractID, Equal<Required<Contract.contractID>>>>.Select(graph, item.ContractID);
using (PXTransactionScope ts = new PXTransactionScope())
{
if (contract.IsTemplate != true)
{
ContractMaintExt contractMaintExt = CreateInstance<ContractMaintExt>();
contractMaintExt.Contracts.Current = contract;
if (contract.IsActive == true &&
contract.IsPendingUpdate != true &&
contract.Status != Contract.status.PendingActivation)
{
CTBillEngine engine = CreateInstance<CTBillEngine>();
engine.Upgrade(contract.ContractID);
engine.Clear();
contractMaintExt.Contracts.Current = PXSelect<Contract, Where<Contract.contractID, Equal<Required<Contract.contractID>>>>.Select(engine, item.ContractID);
item = PXSelect<ContractDetail,
Where<ContractDetail.contractID, Equal<Required<ContractDetail.contractID>>,
And<ContractDetail.lineNbr, Equal<Required<ContractDetail.lineNbr>>>>>.Select(engine, item.ContractID, item.LineNbr);
}
contractMaintExt.ContractDetails.Cache.SetDefaultExt<ContractDetail.basePriceOption>(item);
contractMaintExt.ContractDetails.Cache.SetDefaultExt<ContractDetail.basePrice>(item);
contractMaintExt.ContractDetails.Cache.SetDefaultExt<ContractDetail.renewalPriceOption>(item);
contractMaintExt.ContractDetails.Cache.SetDefaultExt<ContractDetail.renewalPrice>(item);
contractMaintExt.ContractDetails.Cache.SetDefaultExt<ContractDetail.fixedRecurringPriceOption>(item);
contractMaintExt.ContractDetails.Cache.SetDefaultExt<ContractDetail.fixedRecurringPrice>(item);
contractMaintExt.ContractDetails.Cache.SetDefaultExt<ContractDetail.usagePriceOption>(item);
contractMaintExt.ContractDetails.Cache.SetDefaultExt<ContractDetail.usagePrice>(item);
contractMaintExt.ContractDetails.Update(item);
contractMaintExt.Actions.PressSave();
}
else
{
TemplateMaint templateMaint = CreateInstance<TemplateMaint>();
templateMaint.Templates.Current = PXSelect<ContractTemplate, Where<ContractTemplate.contractID, Equal<Required<ContractTemplate.contractID>>>>.Select(graph, item.ContractID);
templateMaint.ContractDetails.Cache.SetDefaultExt<ContractDetail.basePriceOption>(item);
templateMaint.ContractDetails.Cache.SetDefaultExt<ContractDetail.basePrice>(item);
templateMaint.ContractDetails.Cache.SetDefaultExt<ContractDetail.renewalPriceOption>(item);
templateMaint.ContractDetails.Cache.SetDefaultExt<ContractDetail.renewalPrice>(item);
templateMaint.ContractDetails.Cache.SetDefaultExt<ContractDetail.fixedRecurringPriceOption>(item);
templateMaint.ContractDetails.Cache.SetDefaultExt<ContractDetail.fixedRecurringPrice>(item);
templateMaint.ContractDetails.Cache.SetDefaultExt<ContractDetail.usagePriceOption>(item);
templateMaint.ContractDetails.Cache.SetDefaultExt<ContractDetail.usagePrice>(item);
templateMaint.ContractDetails.Update(item);
templateMaint.Actions.PressSave();
}
ts.Complete();
}
}
Here I need to customize this condition:
if (contract.IsActive == true &&
contract.IsPendingUpdate != true &&
contract.Status != Contract.status.PendingActivation)
I tried to override the constructor to with my own custom static method where I would rewrite all the code but change the condition like this, but it is not working:
public override void Initialize()
{
var processDelegate = (PXProcessingBase<ContractDetail>.ProcessListDelegate)Base.Items.GetProcessDelegate();
Base.Items.SetProcessDelegate<ContractMaint>(UpdatePricesProc);
}
#region Event Handlers
public static void UpdatePricesProc(ContractMaint graph, ContractDetail item)
{
Contract contract = PXSelect<Contract, Where<Contract.contractID, Equal<Required<Contract.contractID>>>>.Select(graph, item.ContractID);
}
Need help to figure this out. Thanks.
Related
When I was working with EF (System.Data.Entity) I successfully used an interceptor to automatically trim all existing strings in the database.
The IDbCommandTreeInterceptor is described in this post: EF6.1–Workaround Trailing Blanks Issue in String Joins.
public class StringTrimmerInterceptor : IDbCommandTreeInterceptor
{
public void TreeCreated(DbCommandTreeInterceptionContext interceptionContext)
{
if (interceptionContext.OriginalResult.DataSpace == DataSpace.SSpace)
{
var queryCommand = interceptionContext.Result as DbQueryCommandTree;
if (queryCommand != null)
{
var newQuery = queryCommand.Query.Accept(new StringTrimmerQueryVisitor());
interceptionContext.Result = new DbQueryCommandTree(
queryCommand.MetadataWorkspace,
queryCommand.DataSpace,
newQuery);
}
}
}
private class StringTrimmerQueryVisitor : DefaultExpressionVisitor
{
private static readonly string[] _typesToTrim = { "nvarchar", "varchar", "char", "nchar" };
public override DbExpression Visit(DbNewInstanceExpression expression)
{
var arguments = expression.Arguments.Select(a =>
{
var propertyArg = a as DbPropertyExpression;
if (propertyArg != null && _typesToTrim.Contains(propertyArg.Property.TypeUsage.EdmType.Name))
return EdmFunctions.Trim(a);
return a;
});
return DbExpressionBuilder.New(expression.ResultType, arguments);
}
}
}
I need some help to implement the same functionality by EntityFrameworkCore DbCommandInterceptor.
I am working on customization to add some filters to the existing data view.
The Activity data view on the Employee Time Activities page.
I have added the Customer property to PMTimeActivity and OwnedFilter.
Now I need to modify the activity method to take into consideration the Customer filter.
The only way to do this is to override the method with one of the following scenarios:
without calling the base method and copying the code and adding the
filter part
with calling the base method and checking the condition on each
returned record.
The first scenario is making this part of customization very problematic because it will require to review this code every time the customization is being upgraded to any other build.
The second scenario is not good from a performance view.
Has anybody faced this issue and how can this be done in an acceptable way?
Below is the code of the activity method:
protected virtual IEnumerable activity()
{
List<object> args = new List<object>();
EmployeeActivitiesEntry.PMTimeActivityFilter filterRow = this.Filter.Current;
if (filterRow == null)
{
return null;
}
BqlCommand cmd = BqlCommand.CreateInstance(new Type[]
{
typeof(Select2<EPActivityApprove, LeftJoin<EPEarningType, On<EPEarningType.typeCD, Equal<PMTimeActivity.earningTypeID>>, LeftJoin<CRActivityLink, On<CRActivityLink.noteID, Equal<PMTimeActivity.refNoteID>>, LeftJoin<CRCase, On<CRCase.noteID, Equal<CRActivityLink.refNoteID>>, LeftJoin<ContractEx, On<CRCase.contractID, Equal<ContractEx.contractID>>>>>>, Where<EPActivityApprove.ownerID, Equal<Current<EmployeeActivitiesEntry.PMTimeActivityFilter.ownerID>>, And<EPActivityApprove.trackTime, Equal<True>, And<PMTimeActivity.isCorrected, Equal<False>>>>, OrderBy<Desc<EPActivityApprove.date>>>)
});
if (filterRow.ProjectID != null)
{
cmd = cmd.WhereAnd<Where<EPActivityApprove.projectID, Equal<Current<EmployeeActivitiesEntry.PMTimeActivityFilter.projectID>>>>();
}
if (filterRow.ProjectTaskID != null)
{
cmd = cmd.WhereAnd<Where<EPActivityApprove.projectTaskID, Equal<Current<EmployeeActivitiesEntry.PMTimeActivityFilter.projectTaskID>>>>();
}
if (filterRow.FromWeek != null || filterRow.TillWeek != null)
{
List<Type> cmdList = new List<Type>();
bool? includeReject = filterRow.IncludeReject;
bool flag = true;
if (includeReject.GetValueOrDefault() == flag & includeReject != null)
{
cmdList.Add(typeof(Where<, , >));
cmdList.Add(typeof(EPActivityApprove.approvalStatus));
cmdList.Add(typeof(Equal<ActivityStatusListAttribute.rejected>));
cmdList.Add(typeof(Or<>));
}
if (filterRow.FromWeek != null)
{
if (filterRow.TillWeek != null)
{
cmdList.Add(typeof(Where<, , >));
}
else
{
cmdList.Add(typeof(Where<, >));
}
cmdList.Add(typeof(EPActivityApprove.weekID));
cmdList.Add(typeof(GreaterEqual<Required<EmployeeActivitiesEntry.PMTimeActivityFilter.fromWeek>>));
args.Add(filterRow.FromWeek);
if (filterRow.TillWeek != null)
{
cmdList.Add(typeof(And<>));
}
}
if (filterRow.TillWeek != null)
{
cmdList.Add(typeof(Where<EPActivityApprove.weekID, LessEqual<Required<EmployeeActivitiesEntry.PMTimeActivityFilter.tillWeek>>>));
args.Add(filterRow.TillWeek);
}
cmd = cmd.WhereAnd(BqlCommand.Compose(cmdList.ToArray()));
}
return new PXView(this, false, cmd).SelectMultiBound(new object[]
{
this.Filter.Current
}, args.ToArray());
}
I would consider to try work with PXView, which was described by Sergey here.
In case of Employee activities time card it may look like this:
public class EmployeeExt : PXGraphExtension<EmployeeActivitiesEntry>
{
protected virtual IEnumerable activity()
{
var sel = new PXView(Base, true, Base.Activity.View.BqlSelect);
if(true)
{
sel.WhereAnd<Where<EPActivityApprove.projectID, Equal<Current<EmployeeActivitiesEntry.PMTimeActivityFilter.projectID>>>>();
}
int totalRow = 0;
int startRow = PXView.StartRow;
return sel.Select(PXView.Currents, PXView.Parameters,
PXView.Searches, PXView.SortColumns, PXView.Descendings,
PXView.Filters, ref startRow, PXView.MaximumRows, ref totalRow);
}
}
I have a customization where I have added three user fields to the Sales Order (SO301000) screen transactions grid. I would like to set fields on the 'Create Purchase Order' screen (PO505000). I had used the POFixedDemand's 'RowSelected' event, which works fine - but that causes a problem when anyone tries to modify anything in a row - which re-triggers that event - not a desired outcome.
I've tried the 'RowInserting' and 'RowInserted' events - but they're never triggered. I'm assuming at this point that I'll have to intercept some code in the 'POCreate' BLC that creates the POFixedDemand records in the Create Purchase Order screen - but I don't really know where to start. Would I put it somewhere in the EnumerateAndPrepareFixedDemands method?
Here's the code I created which works for the RowSelected event, but is no good for when a row is modified by a user. Any help is appreciated. Thank you.
protected virtual void POFixedDemand_RowSelected(PXCache sender, PXRowSelectedEventArgs e)
{
var pofd = (POFixedDemand)e.Row;
if (pofd == null) return;
var filter = Base.Filter.Current;
var ordernbr = filter.OrderNbr;
var ordertype = filter.OrderType;
var solinesplit = (SOLineSplit)PXSelect<SOLineSplit, Where<SOLineSplit.planID, Equal<Required<SOLineSplit.planID>>>>.Select(Base, pofd.PlanID);
if (solinesplit != null)
{
var soline = (SOLine)PXSelect<SOLine,
Where<SOLine.orderNbr, Equal<Required<SOLine.orderNbr>>,
And<SOLine.orderType, Equal<Required<SOLine.orderType>>,
And<SOLine.lineNbr, Equal<Required<SOLine.lineNbr>>>>>>.Select(Base, solinesplit.OrderNbr, solinesplit.OrderType, solinesplit.LineNbr);
if (soline != null)
{
var solineext = PXCache<SOLine>.GetExtension<SOLineExt>(soline);
pofd.VendorID = solineext.UsrVendor;
pofd.EffPrice = solineext.UsrVendorUnitCost;
pofd.ExtCost = solineext.UsrVendorExtendedCost;
//Now set the Vendor location...
var location = (Location)PXSelect<Location,
Where<Location.bAccountID, Equal<Required<Location.bAccountID>>>>.Select(Base, pofd.VendorID);
if (location != null)
{
pofd.LocationID = location.LocationID;
}
}
}
}
I'm assuming at this point that I'll have to intercept some code in
the 'POCreate' BLC
Yes you need to do something along those lines.
There's a similar answer here for initializing the POLine instead of POFixedDemand:
https://stackoverflow.com/a/37255340/7376238
With some minor adjustments, the general pattern would be:
public class POCreateExt : PXGraphExtension<POCreate>
{
public override void Initialize()
{
PXGraph.InstanceCreated.AddHandler<POOrderEntry>((graph) =>
{
graph.RowInserting.AddHandler<POFixedDemand>((sender, e) =>
{
// Initialize fields when row is inserted
POFixedDemand demand = e.Row as POFixedDemand;
demand.DACField = [initialization value];
});
graph.RowUpdating.AddHandler<POFixedDemand>((sender, e) =>
{
// Sometimes fields are updated so you need to
// hook RowUpdating too and re-initialize
POFixedDemand demand = e.NewRow as POFixedDemand;
demand.DACField = [initialization value];
});
});
}
}
What I came up with after some investigation is to override the 'EnumerateAndPrepareFixedDemands' method to set the values. Code is as follows:
public delegate IEnumerable EnumerateAndPrepareFixedDemandsDelegate(PXResultset<POFixedDemand> fixedDemands);
[PXOverride]
public IEnumerable EnumerateAndPrepareFixedDemands(PXResultset<POFixedDemand> fixedDemands, EnumerateAndPrepareFixedDemandsDelegate baseMethod)
{
foreach (PXResult<POFixedDemand> rec in fixedDemands)
{
POFixedDemand demand = rec;
var solinesplit = (SOLineSplit)PXSelect<SOLineSplit, Where<SOLineSplit.planID, Equal<Required<SOLineSplit.planID>>>>.Select(Base, demand.PlanID);
if (solinesplit != null)
{
var soline = (SOLine)PXSelect<SOLine,
Where<SOLine.orderNbr, Equal<Required<SOLine.orderNbr>>,
And<SOLine.orderType, Equal<Required<SOLine.orderType>>,
And<SOLine.lineNbr, Equal<Required<SOLine.lineNbr>>>>>>.Select(Base, solinesplit.OrderNbr, solinesplit.OrderType, solinesplit.LineNbr);
if (soline != null)
{
var solineext = PXCache<SOLine>.GetExtension<SOLineExt>(soline);
demand.VendorID = solineext.UsrVendor;
demand.EffPrice = solineext.UsrVendorUnitCost;
demand.ExtCost = solineext.UsrVendorExtendedCost;
//Now set the Vendor location...
var location = (Location)PXSelect<Location,
Where<Location.bAccountID, Equal<Required<Location.bAccountID>>>>.Select(Base, solineext.UsrVendor);
if (location != null)
{
demand.VendorLocationID = location.LocationID;
}
}
}
}
return baseMethod(fixedDemands);
}
When I release a Cash Sales document, after everything verifies successfully and the GL Batch is created, I execute code which created a second GL Batch that handles other transactions related to the cash sale. After that batch is created, I want to save the second GL Batch's Ref Nbr on the Cash Sales document as well. When I try to save it, I get an error:
Here is my code overriding the normal Release process:
public delegate IEnumerable ReleaseDelegate(PXAdapter adapter);
[PXOverride]
public IEnumerable Release(PXAdapter adapter, ReleaseDelegate baseMethod)
{
ARCashSale cashSale = Base.Document.Current;
PXGraph.InstanceCreated.AddHandler<JournalEntry>(delegate (JournalEntry oldJournalEntry)
{
oldJournalEntry.RowPersisted.AddHandler<Batch>(delegate (PXCache sender, PXRowPersistedEventArgs e)
{
Batch oldBatch = oldJournalEntry.BatchModule.Current;
if (oldBatch != null && isCreated == false && e.Operation == PXDBOperation.Insert && e.TranStatus == PXTranStatus.Completed)
{
isCreated = true;
if (CFBSAdjustments.Select().Count > 0)
{
JournalEntry newJournalEntry = PXGraph.CreateInstance<JournalEntry>();
Batch newBatch = new Batch();
newBatch = newJournalEntry.BatchModule.Insert(newBatch);
Customer customer = PXSelect<Customer, Where<Customer.bAccountID, Equal<Required<Customer.bAccountID>>>>.Select(Base, cashSale.CustomerID);
newBatch.Description = "Fund Entry for Cash Sales Reference " + cashSale.RefNbr;
newBatch.FinPeriodID = oldBatch.FinPeriodID;
newBatch.LedgerID = oldBatch.LedgerID;
newBatch.DateEntered = oldBatch.DateEntered;
decimal? debit = 0;
decimal? credit = 0;
foreach (CFBSCashSalesAdjustment row in CFBSAdjustments.Select())
{
GLTran tran = newJournalEntry.GLTranModuleBatNbr.Insert();
tran.SummPost = true;
tran.BranchID = cashSale.BranchID;
tran.TranType = GLTran.tranClass.Normal;
tran.TranClass = GL.Messages.RoundingDiff;
tran.LedgerID = newBatch.LedgerID;
tran.FinPeriodID = newBatch.FinPeriodID;
tran.TranDate = newBatch.DateEntered;
tran.CuryInfoID = Base.currencyinfo.Current.CuryInfoID;
tran.AccountID = row.Account;
tran.SubID = row.Subaccount;
tran.DebitAmt = row.DebitAmt;
tran.CuryDebitAmt = row.DebitAmt;
debit += row.DebitAmt;
tran.CreditAmt = row.CreditAmt;
tran.CuryCreditAmt = row.CreditAmt;
credit += row.CreditAmt;
tran.RefNbr = row.CashSalesRefNbr;
tran.TranDesc = customer.AcctCD + " - " + customer.AcctName;
newJournalEntry.GLTranModuleBatNbr.Update(tran);
}
newBatch = newJournalEntry.BatchModule.Update(newBatch);
if (GLSetup.Current.GetExtension<GLSetupExt>().UsrAutoRelease == true)
{
newJournalEntry.BatchModule.Current.Hold = false;
newJournalEntry.release.Press();
}
newJournalEntry.Save.Press();
if (isCreated)
isCreated = false;
cashSale.GetExtension<ARRegisterExt>().UsrFundBatch = newJournalEntry.BatchModule.Current.BatchNbr;
//Base.Document.Current.GetExtension<ARRegisterExt>().UsrFundBatch = newJournalEntry.BatchModule.Current.BatchNbr;
//Base.dummy_CATran.View.RequestRefresh();
Base.Document.Update(cashSale);
//TODO - Figure out why the fund batch isn't saving to the Cash Sale
Base.Actions.PressSave();
//Base.dummy_CATran.Cache.ClearQueryCache();
//Base.Persist(typeof(ARCashSale), PXDBOperation.Update);
}
}
});
});
return baseMethod(adapter);
}
I left in all of the different methods I've tried to get the ref nbr to save. I've ever tried to add a field updated handler for the BatchNbr field and force the new number in that way, but it did not work.
EDIT: I also noticed that I cannot successfully set an extended field on the Batch DAC. For example, I have the line newBatch.GetExtension<BatchExt>().ExtRefNbr = cashSale.RefNbr; to set the ref nbr in that document as well, but any time I run 'NewJournal.Press.Save()' it changes the set value of the extended field to null. If anyone knows how to set the extension field, I may be able to work with that instead to go down a different path that may do what I need.
I believe the appropriate place to perform your update is in graph ARDocumentRelease. Try something like this....
public class ARDocumentReleaseExtension : PXGraphExtension<ARDocumentRelease>
{
public override void Initialize()
{
ARSetup setup = Base.arsetup.Current;
Base.ARDocumentList.SetProcessDelegate(
delegate (List<BalancedARDocument> list)
{
List<ARRegister> newlist = new List<ARRegister>(list.Count);
foreach (BalancedARDocument doc in list)
{
newlist.Add(doc);
}
ARDocumentRelease.ReleaseDoc(newlist, true);
CreateSecondBatch(newlist);
}
);
Base.ARDocumentList.SetProcessCaption(PX.Objects.AR.Messages.Release);
Base.ARDocumentList.SetProcessAllCaption(PX.Objects.AR.Messages.ReleaseAll);
}
private void CreateSecondBatch(List<ARRegister> docs)
{
foreach(BalancedARDocument register in docs)
{
if (register.DocType == ARDocType.CashSale)
{
//create the second batch and assign the 2nd ref nbr to the cash sale user field
}
}
}
}
When writing a sequence in an IYamlTypeConverter you might use some code like this:
public class MyObjectConverter : IYamlTypeConverter {
public MyObjectConverter() {}
public bool Accepts(Type type) { return typeof(IMyObject) == type || typeof(IMyObject[]) == type; }
public object ReadYaml(IParser parser, Type type) { return null; }
public void WriteYaml(IEmitter emitter, object value, Type type) {
var itemVal = value as IMyObject;
if (itemVal != null)
emitter.Emit(new Scalar(itemVal.GetID()));
else {
var arrayVal = value as IMyObject[];
emitter.Emit(new SequenceStart(null, null, true, SequenceStyle.Block));
if (arrayVal != null) {
foreach (var item in arrayVal)
if (item != null) emitter.Emit(new Scalar(item.GetID()));
else emitter.Emit(new Scalar("null"));
}
emitter.Emit(new SequenceEnd());
}
}
}
By calling emitter.Emit(new Scalar("null")) you would get a 'null' entry in the sequence, but if you leave the serialization up to YamlDotNet, it would be serialized as '' (empty string).
How do you output a null value in a sequence as an empty string when writing a custom IYamlTypeConverter?
One way to achieve this is to create a custom IEventEmitter that will add this logic:
public class NullStringsAsEmptyEventEmitter : ChainedEventEmitter
{
public NullStringsAsEmptyEventEmitter(IEventEmitter nextEmitter)
: base(nextEmitter)
{
}
public override void Emit(ScalarEventInfo eventInfo, IEmitter emitter)
{
if (eventInfo.Source.Type == typeof(string) && eventInfo.Source.Value == null)
{
emitter.Emit(new Scalar(string.Empty));
}
else
{
base.Emit(eventInfo, emitter);
}
}
}
You then register it like this:
var serializer = new SerializerBuilder()
.WithEventEmitter(nextEmitter => new NullStringsAsEmptyEventEmitter(nextEmitter))
.Build();
Here's a fiddle with this code
It seems you can represent a null value simply with '~', according to http://www.yaml.org/refcard.html