So I’m trying to use a method directive to set a UD field in the QuoteQty table. Currently I am using the Update method, and it is working fine for the most part.
The problem I am facing is I need to populate this field for quotes that have already been quoted. So to do this, we are planning on having someone go into to every old quote, un-quote, un-engineer, save in order to trigger the BPM, then re-engineer and re-quote.
I noticed a problem while testing this process where unless the costs have changed on the quote, the BPM does not trigger to set the UD field. Is there a different method I can use that will circumvent this issue? Or perhaps I need to use a data directive instead of a method directive?
This is my first time utilizing BPMs, so if anyone has some insight on these issues that would be greatly appreciated!
I think what you are seeing is caused by the fact there is no change in the record as you mention. One (temporary) option might be to write the BPM to look at actual records (not just ttRecords coming across) and make the changes. You’d only want to run this once before disabling. Being new to BPM’s I dont necessarily know if this is a safe option for you unsupervised. Perhaps a somewhat safer approach is to put this logic in an updatable BAQ where you can more easily control the execution, but even this comes with it’s own learning curve. Please dont take this as a cop-out, I just wouldnt want to be responsible for giving you bad advice to mess up your data.
Perhaps someone else will have some friendlier alternatives.
John,
I can’t say that I’ve done a BPM to update the QuoteQty table (and adjust costs or something) but I have run into the same issue with updating UD fields on QuoteHed and QuoteDtl - when the quote is marked quoted.
The update method is the same used when the UI attempts the update, and you get a message saying something like “No Update - Quote is marked Quoted”. There is a way around this, but it may be dangerous with respect to your overall process.
In company config, Quotes tab, there is a checkbox that allows changes to quoted quotes. This allows the BPM to update quotes that are quoted, but it also allows users to update quotes that have been marked quoted - and this could be dangerous, so try at your discretion.
// Function to Toggle Quote Changes
Action<bool> PreventQuoteChanges = (allowChanges) =>
{
// System.Transactions.TransactionScope
using (var txScope = IceDataContext.CreateDefaultTransactionScope())
{
var eqsyst =
(from eqs in Db.EQSyst.With(LockHint.UpdLock)
where eqs.Company == Session.CompanyID
select eqs
).FirstOrDefault();
if (eqsyst != null)
{
eqsyst.PreventQQChange = allowChanges;
}
Db.Validate(eqsyst);
txScope.Complete();
}
};
Add to your BPM on the top and then you can do
// Enable Quote Changes
PreventQuoteChanges(false);
// Place your normal Logic Here
// Disable Quote Changes
PreventQuoteChanges(true);
If you trace Epicors Method via Reflector all it really checks is the Prevent Quote Changes checkbox in your company config, nothing else, nothing more.
That seems pretty ingenious. Not sure where this would go if we are only editing BPMs via the graphical/widget editor. Are you saying that we need to directly edit the C# BPM code that gets created on the server in order to inject the function definition and the two calls - as sort of a wrapper for the code Epicor automatically creates?
Quote is a subset of a larger issue though. If you add UD fields to PartTran or ProjCstHistory or BookDtl,… You can’t update these field because there is not BO/Adapter that allows to update these records once written. I have submitted a suggestion to the DMT folks that this would be a nice enhancement for their tool. You would need to provide the key of the main file, DMT would now know the Record ID and then it could update the associated _UD table entry. I’d hate to muddy up the BO but an .UpdateUD method would be cool for these kinds of operations too.
Thanks everyone for your input. This is turning out to be a much bigger issue than I had hoped.
Long story short, all I want is to be able to report quoted margin in a BAQ, so to do that we made UD field that would store the total cost shown on the Worksheet for each line item. But I am realizing that the BPM I wrote to do this is not even updating the UD field in the way that I had hoped. I assumed that using the Update method would set the field whenever the costs changed, but it seems like it only works when a field in the QuoteQty table is changed. I’m guessing this is because the costs on the worksheet are not stored in the database.
So now I’m starting to wonder if this is even at all possible. Anyone out there have a solution to do this?
Just a thought but the Quote Analysis table stores all that for you, so you could just join that table to the BAQ and calc the margin in one step without any additional UD fields or BPMS. That is how we’ve done it in the past.
Just a follow up on @ckrusen recommendation to use DMT.
If you have DMT you can update all these records without changing your BPM.
Or you could change your BPM or add another Data Directive BPM to trigger your field set on update.
But what I prefer is to create a BAQ that will pull in all the UD Quote fields that are blank and need to be set and then use a Calculated field to recalc what the UD field should be and give it the appropriate DMT label. Once you confirm the data is correct and that you have all the columns that DMT needs for that load, right click on the results and send to Excel, doublecheck column headings, save and load with DMT.
If you dont have DMT, do what @Rick_Bird said about making a BAQ, but make it a uBAQ. Then just use copy and paste to get the values from the calculated field to the updatable UD field.
Via DMT you should get the same error that its already been Quoted. Due to the Company Config Setting. Atleast in E9 I did and I have to use SQL to update UD Fields. So aslong as you go and uncheck the Company Config Setting eqsyst.PreventQQChange you should be good.
i agree with @Chris_Conn you have to be a bit careful when updating live historical data, however you can use the code that @hkeric.wci suggested to force the update and i only use it when i update my UD fields nothing else within Epicor BO, now to solve your method trigger issue, you should invoke it either by the code or useing the BPM Widget, and pass quoteQty as a parameter through callcontext variable x 1… i.e let the BO method think it is new/change QuoteQty to run thereby update your UD fields.
By suggesting to place your Action on the top, it implies that Epicor is not re-writing the whole bpm on each save. I would have thought the contrary. Never notice it…
I am not sure what you guys are talking about Historic Data. I’ve spent about 2yrs in the Quote tables, BPMs, BAQs and the Quote is pretty much isolated, make all the changes you want, anytime it’s pretty much its own bubble; sure if you Convert it to a Sales Order it might populate the QuoteNum as a reference field, but thats about it - it clones itself to the SO. =)
Epicor even allows you to make changes, if you disable the Prevent Quote Changes after Quoted checkbox in Company Config. It’s just makes the form read-only if Quoted = true.
If you make Quoted = false, make changes and set it back to Quote = true; thats when the Quoted Date will change, and your dashboards/stats will be off. So I find the EQSyst tweak to be silent/passive/works =)
The Action that I posted above changes the EQSyst.PreventQQChange to false, you do your changes and then you change it back to true.
Doing so will not re-calculated the Quoted Date, Due Date, Expiration Date, Follow Up Date. Which is perfect, because continuously bumping the Quote Date and if you have a dashboard comparing “Quoted Date” to “Created Date” you will think your sales-team is slow.
By the way if anyone ever DOES have the need to apply better logic to the Erp.Quote.GetQuotedInfo.BASE.ManageQuoteDates method. I found that it merely sets the date’s and I added some better logic to not bump the Quoted Date if a “User” un-quotes it, to make a minor tweak.
// Initialize Actions
Action<string> show = message => {
PublishInfoMessage(message, Ice.Common.BusinessObjectMessageType.Information, Ice.Bpm.InfoMessageDisplayMode.Individual, string.Empty, string.Empty);
};
// Main Query
var quotes =
from tqh in ttQuoteHed
join qh in Db.QuoteHed.With(LockHint.NoLock) on tqh.QuoteNum equals qh.QuoteNum
where tqh.Updated()
select new { tqh, qh };
foreach (var quoteRow in quotes)
{
/* DateQuoted we will try to use our initial Date */
if (quoteRow.tqh.Quoted == true)
{
if ( ((DateTime?)quoteRow.tqh["Date01"]).HasValue )
{
quoteRow.tqh.DateQuoted = (DateTime?)quoteRow.tqh["Date01"];
}
else
{
quoteRow.tqh.DateQuoted = DateTime.Today;
}
}
else
{
quoteRow.tqh.DateQuoted = null;
}
/* Expiration Date Based on SOP */
if (quoteRow.tqh.Quoted == true)
{
if (quoteRow.tqh.ExpectedClose.HasValue)
{
// 90 days from ExpectedClose
quoteRow.tqh.ExpirationDate = quoteRow.tqh.ExpectedClose.Value.AddDays(90);
}
else
{
// 30 days from DateQuoted
quoteRow.tqh.ExpirationDate = quoteRow.tqh.DateQuoted.Value.AddDays(30);
}
}
else
{
quoteRow.tqh.ExpirationDate = null;
}
/* Follow Up Date */
if ( !quoteRow.qh.FollowUpDate.HasValue && quoteRow.tqh.Quoted == true)
{
// 10 days from Today
quoteRow.tqh.FollowUpDate = DateTime.Now.AddDays(10);
}
else
{
// Preserve whatever the Quote has
quoteRow.tqh.FollowUpDate = quoteRow.qh.FollowUpDate;
}
}
Once I create an Order, which usually happens also if you use the Tasks and have a workflow, I would like to mark the quote as Won in my own way - The Quote at this time is Quoted. I use:
const string bpmName = "Quote.CreateOrder.POST.MarkQuotesAsWon";
Ice.Diagnostics.Log.WriteEntry(String.Format("[ {0} ] START", bpmName));
// Function to Toggle Quote Changes
Action<bool> PreventQuoteChanges = (allowChanges) =>
{
// System.Transactions.TransactionScope
using (var txScope = IceDataContext.CreateDefaultTransactionScope())
{
var eqsyst =
(from eqs in Db.EQSyst.With(LockHint.UpdLock)
where eqs.Company == Session.CompanyID
select eqs
).FirstOrDefault();
if (eqsyst != null)
{
eqsyst.PreventQQChange = allowChanges;
}
Db.Validate(eqsyst);
txScope.Complete();
}
};
// Quote Number passed from the PRE Event
int quoteNum = Convert.ToInt32(callContextBpmData.Number01);
Ice.Diagnostics.Log.WriteEntry(String.Format("[ {0} ] quoteNum: {1}", bpmName, quoteNum));
// Enable Quote Changes
PreventQuoteChanges(false);
// Main Query
var quotes =
(from qh in Db.QuoteHed.With(LockHint.UpdLock)
where qh.QuoteNum == quoteNum
select qh
).FirstOrDefault();
if (quotes != null)
{
Ice.Diagnostics.Log.WriteEntry(String.Format("[ {0} ] Quote Found marking as Won: {1}", bpmName, quoteNum.ToString()));
quotes["ShortChar02"] = "Won";
quotes["Date03"] = DateTime.Today;
Db.Validate(quotes);
}
// Disable Quote Changes
PreventQuoteChanges(true);
Ice.Diagnostics.Log.WriteEntry(String.Format("[ {0} ] END", bpmName));
// Why dont we need Db.Validate?
// PreSOCreate
/*
// Enable us to Make Changes to the Quote
for first EQSyst where EQSyst.Company = cur-comp.
EQSyst.PreventQQChange = false.
end.
for first ttQuoteHed, each QuoteHed WHERE QuoteHed.QuoteNum = ttQuoteHed.QuoteNum.
QuoteHed.ShortChar02 = "Won".
QuoteHed.Date03 = TODAY.
end.
// Block User from Making Changes Again
for first EQSyst where EQSyst.Company = cur-comp.
EQSyst.PreventQQChange = true.
EQSyst.LogChanges = true.
end.
*/