Help debugging BPM Code

So, I’ve leaned that I need to sharpen my LINQ skills but, until then, can someone help me find out what’s wrong with my code? It works swimmingly when the source quote has all lines in sequence, but if the source quote has a deleted line it bombs.

My end goal is to duplicate the quantity fields from an existing quote when clicking “Actions” -> “Quote” -> “Duplicate Quote”.

So far I have created a Post-Processing BPM that sets a BPM Data Field, callContextBpmData.Number02, to Convert.ToDecimal(sourceLines.Split(’~’)[0]) and then runs the following custom code:

foreach (var tt in ttQuoteDtl)
{
for(int i = 0; i < callContextBpmData.Number02; i++ )
{
var dbqt = Db.QuoteDtl.Where(p => p.Company == tt.Company && p.QuoteNum == tt.QuoteNum && p.QuoteLine == i+1).FirstOrDefault();
dbqt.OrderQty = Db.QuoteDtl.Where(m => m.Company == tt.Company && m.QuoteNum == sourceQuote && m.QuoteLine == i+1).Select(p => p.OrderQty).FirstOrDefault();
dbqt.SellingExpectedQty = Db.QuoteDtl.Where(m => m.Company == tt.Company && m.QuoteNum == sourceQuote && m.QuoteLine == i+1).Select(p => p.SellingExpectedQty).FirstOrDefault();
}
Db.Validate();
}

Again, if the original quote has all of its lines in order my code works, but when Customer Service deletes a line and clicks “Duplicate Quote” the application throws an exception.

try:

foreach (var tt in ttQuoteDtl.Where(d => d.Added() || d.Updated())

2 Likes

agree with @Chris_Conn… but also it appears that you are attempting to update your current record based on the value of some other similar record in the quote (based pm Number02?)…
It seems like what you are trying to do here could be done 100% with widgets and and the “Fill table” setter widget, so that you would not need the link statement.
Below is how i reorganized the linq so i could better see it (i can’t read it if all one one)

foreach (var tt in ttQuoteDtl.Where(d => d.Added() || d.Updated()))
{
    for(int i = 0; i < callContextBpmData.Number02; i++ )
    {
        var dbqt = Db.QuoteDtl.Where(p => 
            p.Company == tt.Company && 
            p.QuoteNum == tt.QuoteNum && 
            p.QuoteLine == i+1).FirstOrDefault();

        dbqt.OrderQty = Db.QuoteDtl.Where(m => 
            m.Company == tt.Company && 
            m.QuoteNum == sourceQuote && 
            m.QuoteLine == i+1).Select(p => p.OrderQty).FirstOrDefault();

        dbqt.SellingExpectedQty = Db.QuoteDtl.Where(m => 
            m.Company == tt.Company && 
            m.QuoteNum == sourceQuote && 
            m.QuoteLine == i+1).Select(p => p.SellingExpectedQty).FirstOrDefault();
    }
Db.Validate();
}
2 Likes

I added “.Where(d => d.Added() || d.Updated())” and now none of my source quote quantities are copied over to the new quote.

I am open to using widgets for this, if possible, but everything I’ve tried fails when there is a missing line in the source quote.

can you explain what exactly you are copying from/to? it is hard to tell since this has things outside this code (sourceQuote, callContextBpmData.Number02 are two that I can see here).

Untested - hand written

    foreach (var tt in ttQuoteDtl.Where(d => d.Added() || d.Updated()))
        {
            //now we get the quote dtls 
            var q = Db.QuoteDtl.Where(p => p.Company == tt.Company && p.QuoteNum == tt.QuoteNum && p.QuoteLine == tt.QuoteLine && p.QuoteLine <= callContextBpmData.Number02).FirstOrDefault(); //this quoteLine condition is not needed me thinks (unless you are trying to limit some details)
            if (q != null) //if null, then either that dtl doesnt exist, or was excluded because of your callContext condition above
            {
                q.OrderQty = tt.OrderQty;
                q.SellingExpectedQty = tt.SellingExpectedQty;
                //q.RowMod = "U"; //i dont think this is used in a db record...
            }
                        
        } //only update once all records are modified
        Db.Validate();

Our CSRs have a lot of quotes that they want to be able to completely duplicate. Using the Actions -> Quote -> Duplicate Quote menu item, from Opportunity/Quote Entry, they can duplicate the lines but the quantities in the new quote are all zero. I am trying to copy the quantities from the old quote to the new quote so they don’t have to manually re-enter the line quantities.

Now, the code…the sourceLines parameter is a tilde separated list of the lines that are selected for duplication, which I am splitting into the callContextBpmData.Number02 field, and the sourceQuote is a parameter containing the original quote number.

Let me know if those are the details you were looking for or if I need to be more clear…

maybe try something like this:

//first split
var lines = sourceLines.Split(’~’);

//only iterate over details that are in source lines
    foreach (var tt in ttQuoteDtl.Where(d => d.Added() || d.Updated() &&  lines.Contains(p.QuoteLine.ToString()))
        {
            //now we get the related quote dtls 
            var q = Db.QuoteDtl.Where(p => p.Company == tt.Company && p.QuoteNum == tt.QuoteNum && p.QuoteLine == tt.QuoteLine).FirstOrDefault(); 
            if (q != null) //if null, then either that dtl doesnt exist, or was excluded because of your callContext condition above
            {
                q.OrderQty = tt.OrderQty;
                q.SellingExpectedQty = tt.SellingExpectedQty;
                //q.RowMod = "U"; //i dont think this is used in a db record...
            }
                        
        } //only update once all records are modified
        Db.Validate();

Note that when you are duplicating, you should only have a quote head for the quote you are duplicating, no need to specify source quote. Also, the ttQuoteDtls are all of the lines of the duplicated quote. In the code above, we are going to iterate only details which have lines that exist within your sourcelines list

Btw - I wonder if you couldnt just handle all of this in the Pre-Process. Just modify the ttValues directly before they are saved. Then you wont have to do this lookup in the Db.

This code will duplicate the lines but, again, the line quantities are all zero…

Ah yes… because I am duping from the tt values…which are 0. Boing! (That source quote is making more sense now)

Well we could move it around to fix that but I sure hate to do 2 db lookups when we could do it with one in the pre-process. Def give it a try.

in the post-process

//first split
var lines = sourceLines.Split(’~’);

//only iterate over details that are in source lines
    foreach (var tt in ttQuoteDtl.Where(d => d.Added() || d.Updated() &&  lines.Contains(p.QuoteLine.ToString()))
        {
            //now we get the related quote dtls 
            var q = Db.QuoteDtl.Where(p => p.Company == tt.Company && p.QuoteNum == tt.QuoteNum && p.QuoteLine == tt.QuoteLine).FirstOrDefault(); 
            var oq = Db.QuoteDtl.Where(p => p.Company == tt.Company && p.QuoteNum ==SourceQuote && p.QuoteLine == tt.QuoteLine).FirstOrDefault(); 

            if (q != null && oq != null) //if null, then either that dtl doesnt exist
            {
                q.OrderQty = oq.OrderQty;
                q.SellingExpectedQty = oq.SellingExpectedQty;
                //q.RowMod = "U"; //i dont think this is used in a db record...
            }
                        
        } //only update once all records are modified
        Db.Validate();

Still no dice…

I’m not committed to using a post-processing directive; if there’s an easier/more efficient way to accomplish this, I’m all for it.

try this in pre-process

//first split
var lines = sourceLines.Split(’~’);
string msg = lines.Count().ToString() + " : "+ sourceLines;
this.PublishInfoMessage(msg, Ice.Common.BusinessObjectMessageType.Information, Ice.Bpm.InfoMessageDisplayMode.Individual, "", "");
//only iterate over details that are in source lines
    foreach (var tt in ttQuoteDtl.Where(d => d.Added() || d.Updated() &&  lines.Contains(d.QuoteLine.ToString()))
        {
            //now we get the related quote dtls 
          msg = string.Format("Working on Q{0} L{1}",tt.QuoteNum,tt.QuoteLine);
          this.PublishInfoMessage(msg, Ice.Common.BusinessObjectMessageType.Information, Ice.Bpm.InfoMessageDisplayMode.Individual, "", ""); 
            var oq = Db.QuoteDtl.Where(p => p.Company == tt.Company && p.QuoteNum ==SourceQuote && p.QuoteLine == tt.QuoteLine).FirstOrDefault(); 

            if (oq != null) //if null, then either that dtl doesnt exist
            {
                tt.OrderQty = oq.OrderQty;
                tt.SellingExpectedQty = oq.SellingExpectedQty;
                msg = string.Format("Updating Q{0} L{1} -> EQ{2}  SQ{3}",tt.QuoteNum,tt.QuoteLine,tt.OrderQty,tt.SellingExpectedQty);
                this.PublishInfoMessage(msg, Ice.Common.BusinessObjectMessageType.Information, Ice.Bpm.InfoMessageDisplayMode.Individual, "", ""); 

            }
                        
        } //only update once all records are modified
        Db.Validate();
1 Like

Still not working :confused:

added some debugging lines above to trace execution

I get one message box…it has the correct line count and all of the selected lines (tilde separated), but the quantities are still zero.

I still have to convert one of my CopyLines BPMs… I think the source contains more than just line(s)…

See My Old ABL


// NEW TODO:
NewSourceQuoteLines = ((i == 1) ? sourceQuoteLines.Entry(i - 1, Ice.Constants.ListSeparator) : NewSourceQuoteLines + Ice.Constants.ListSeparator + sourceQuoteLines.Entry(i - 1, Ice.Constants.ListSeparator));
sourceQuote = Compatibility.Convert.ToInt32(sourceQuoteLines.Entry(i - 1, Ice.Constants.ListSeparator).Entry(1, '`'));
sourceLine = Compatibility.Convert.ToInt32(sourceQuoteLines.Entry(i - 1, Ice.Constants.ListSeparator).Entry(0, '`'));
// END TODO
                
DEFINE VARIABLE myArray AS CHARACTER EXTENT 30 FORMAT "X(30)".
DEFINE VARIABLE myLoopIndex AS INTEGER INIT 1 NO-UNDO.

DEFINE VARIABLE iNumEntries AS INTEGER NO-UNDO.
DEFINE VARIABLE iLoop AS INTEGER NO-UNDO.
DEF BUFFER bNewUD06 FOR UD06.

DEF VARIABLE sCurrentQuoteNum AS CHARACTER NO-UNDO.
DEF VARIABLE sCurrentQuoteLine AS CHARACTER NO-UNDO.

// Count How Many We are Copying
ASSIGN iNumEntries = NUM-ENTRIES(sourceQuoteLines, '~~').

message "sourceQuoteLines: " + sourceQuoteLines.

// Lets Determine All the Lines we have to work with, so we can find the NEWEST Lines
// since Epicors Line Numbering is not Sequential we cant just guess
// we have to count the QuoteDtl because the tt only has the newly added lines
for first ttQuoteDtl, each QuoteDtl WHERE QuoteDtl.QuoteNum = ttQuoteDtl.QuoteNum no-lock by QuoteDtl.QuoteLine.
	message "- QUOTEDTL FOUND: " + string(QuoteDtl.QuoteLine).
	myArray[myLoopIndex] = string(QuoteDtl.QuoteLine).
	myLoopIndex = myLoopIndex + 1.
end.

// Lets Loop through our Lines to be copied
DO iLoop = 1 TO iNumEntries:

		// Assign Some Vars
		assign sCurrentQuoteNum = ENTRY(2, ENTRY(iLoop, sourceQuoteLines, '~~'), '`').
		assign sCurrentQuoteLine = ENTRY(1, ENTRY(iLoop, sourceQuoteLines, '~~'), '`').

		// Check if the Line we are copying has a UD06
		FIND FIRST UD06 WHERE UD06.Key1 = sCurrentQuoteNum AND UD06.Key2 = sCurrentQuoteLine no-error.
		message "Checking for UD06".
		IF AVAILABLE UD06 THEN DO:
			CREATE bNewUD06.
			BUFFER-COPY UD06 EXCEPT sysrevid sysrowid to bNewUD06
			ASSIGN bNewUD06.Key1 = string(targetQuoteNum)
						 bNewUD06.Key2 = string( myArray[ (myLoopIndex - iNumEntries - 1) + iLoop] ).

		END.

END.

Hmm so we arent matching records. Remove:

&& lines.Contains(d.QuoteLine.ToString())

It should dupe all the lines.

Are you saying that Original Line 1 may not equal Duplicate Line 1?

Just to verify we are using this method right?
Erp.Quote.DuplicateQuote

That’s the one.