How to Pass Value from QuoteDtl_ud field to OrderDtl_ud field

Hi Everyone,
I have created a field called Quotefabric_c in QuoteDtl_ud and Orderfabric_c in OrderDtl_ud. I want to pass value from Quotefabric_c to Orderfabric_c, when i click on “create Sales Order” in Quote Entry Screen.

You will nbeed a post processing BPM where you will do in code something like

foreach statement
{
var quotefabric = UDField(“Quotefabric_c”);
SetUDField(“Orderfabric_c”,quotefabric);
}

@Alex
Thank you for response

Erp.Tables.QuoteDtl QuoteDtl;

foreach(var i in ttOrderDtl.Where(id => id.Updated()))
{
var myQuote = Db.QuoteDtl.Where(qd => qd.Company == i.Company &&
qd.QuoteNum == i.QuoteNum &&
qd.QuoteLine == i.QuoteLine ).FirstOrDefault();
if(myQuote != null)
{
var quotefabric = UDField(“Quotefabric_c”);
SetUDField(“Orderfabric_c”,quotefabric);

}
}

I tried above code ,but am getting error

This needs to be on the Quote BO, not the SalesOrder BO

But do make another similira BPM for SalesOrder, for when the Make Order from Quote is run in Order Entry.

@ckrusen… I got partial solution by using the following method.

  1. On Quote BO(CreateOrder | Pre-Processing ) I took CallContext BPM datasets for Number01= QuoteNum, Number02=CustNum, Number03=OrderNum and ShortChar01=CustomField_c

  2. Then I took Data Directive (In-Transaction) for QuoteDtl and gave condition if QuoteNum,CustNum,OrderNum are greater than 0 then set QuoteDtl.CustomField_c to ShortChar01.

I can only pass one value from QuoteDlt to OrderDtl. But i wanted to pass multiple line values

On QuoteDtl I have a custom field on line level. and i have the same field on orderDlt line level also.

I believe that you can do this with ONE DATA BPM as an IN-TRANS BPM… with code something like this (typed from memory, so it may have a typo)… You will need to replace the field names with your custom fields.
The way this is designed, it will ONLY do it the FIRST time (when ADDED) so it will not slow Order Entry down… also it will only retrieve the fields necessary from the Quote.

foreach (oDtl in ttOrderDtl.Where(x => x.RowMod == "A" && x.QuoteNum != 0)) {
    foreach (qDtl = Db.QuoteDtl.Where(x => x.Company == CompanyID && x.QuoteNum == oDtl.QuoteNum && x.QuoteLine == oDtl.QuoteLine)
        .Select(x => new {
            x.quoteFabric_c,
                x.Field2_c,
                x.Field3_c
        }).FirstOrDefault()) {

        oDtl.OrderFabric_c = qDtl.quoteFabric;
        oDtl.Field2_c = qDtl.Field2_c;
        oDtl.Field3_c = qDtl.Field3_c;
    }
}
3 Likes

I am using @timshuwy 's code here (modified for my UD field). I am trying to apply it to a Data Directive In-Trans BPM (on Erp.OrderDtl.Update). I get an error regarding an anonymous type (see attachment).
CustomCodeError
I am a novice, trying to learn. Can someone help me identify the error?

Your mistake is the “first or default” on the second “foreach”… you are stating to process for each one found, but when you say “FirstOrDefault” you are also saying only return one record… You can fix this three different ways… each one will work, and each one will probably perform about the same. I enhanced the first two versions to return more than one field showing how this differs from the third option below which is the best in your situation.

Method 1: like you did above, but without the firstordefault. This one supports more than one field from the quote to Order

foreach (var oDtl in ttOrderDtl.Where(x => x.RowMod == "A" && x.QuoteNum != 0)) {
    
    foreach (var qDtl in Db.QuoteDtl.Where(x => 
        x.Company == CompanyID && 
        x.QuoteNum == oDtl.QuoteNum && 
        x.QuoteLine == oDtl.QuoteLine).Select(x => new { x.Industry_c, x.Field2_c })) {

            oDtl.Industry_c = qDtl.Industry_c;
            oDtl.Field2_c = qDtl.Field2_c;

    }
}

or Better, eliminate the ForEach since there is only one record to be returned… check to make sure it is found before using it… note that this works with multiple fields too:

foreach (var oDtl in ttOrderDtl.Where(x => x.RowMod == "A" && x.QuoteNum != 0)) {

    var qDtl in Db.QuoteDtl.Where(x => 
        x.Company == CompanyID && 
        x.QuoteNum == oDtl.QuoteNum && 
        x.QuoteLine == oDtl.QuoteLine).Select(x => new { x.Industry_c,x.Field2 }).FirstOrDefault();

    if (oDtl != null)
        oDtl.Industry_c = qDtl.Industry_c;
        oDtl.Field2_c = qDtl.Field2_c
    }
}

OR… BEST YET, do a simplified query… since you are only returning one string value, you can simply return that one value directly into oDtl.Industry_c and eliminate the “foreach”… we can also default the string to a blank string using the coalesce (??) command. Just make sure that you dont do this type of query when you only want one value… if you want two or more values from the table, the method above is better because you can select more than one “new” value from the table.

foreach (var oDtl in ttOrderDtl.Where(x => x.RowMod == "A" && x.QuoteNum != 0)) {

    oDtl.Industry_c = Db.QuoteDtl.Where(x =>
        x.Company == CompanyID &&
        x.QuoteNum == oDtl.QuoteNum &&
        x.QuoteLine == oDtl.QuoteLine).Select(x => x.Industry_c).FirstOrDefault() ?? "";

}
4 Likes

@timshuwy it worked great (I used your Best Yet solution)! I tested it from Sales Order Entry ( Actions - Get Oppurtunity/Quote) and from Opportunity/Quote Entry (Actions - Quote - Create Sales Order).
Would it be wise to use a Condition statement for this (or does that occur on the first line Where statement x.QuoteNum != 0)? If so, I am unsure of the best one to use.

When I have played around with Method Directives you can specify what directive will trigger the action (like Quote.CreateOrder). But with Data Directives, it seems more general as the one used in this example was OrderDtl.Update, making me feel that I should be using some sort of condition (so as not to slow down standard order entry; when an order is being created without pulling or pushing details from a Quote).

Thank you very much for providing multiple examples as I am sure I will encounter situations where I will need to bring in multiple fields in the future.

You actually have a great condition right in the C# code… it first does a check against the TT table (which is a memory query, not a database query), and you are only running the code IF it is an added row, and IF the quote is not zero… so in essence, this is the only condition you need.

Hi Tim,

Sorry to resurrect this old thread, but I’m trying to do something similar here, and was wondering if you could help me figure out how to get this working. I’ve got the below In-Trans BPM working (or at least I don’t get syntax errors:

foreach ( var od in ttOrderDtl.Where(x => x.RowMod == "A" && x.QuoteNum != 0) ) {

	var qd = Db.QuoteDtl.Where(x => x.Company == CompanyID
		&& x.QuoteNum  == od.QuoteNum
		&& x.QuoteLine == od.QuoteLine)
	.FirstOrDefault();

	if ( od != null && qd != null ) {

		od["udField0_c"] = qd["udField0_c"];
		od["udField1_c"] = qd["udField1_c"];
		od["udField2_c"] = qd["udField2_c"];
		od["udField3_c"] = qd["udField3_c"];
		od["udField4_c"] = qd["udField4_c"];

		// etc
	}
}

BUT I have several dozen UD fields. I’m trying to figure out how to loop through all of them. All of them have the exact same field names and formatting in QuoteDtl and OrderDtl. I tried the code below, but get the error

foreach statement cannot operate on variables of type ‘QuoteDtl’ because ‘QuoteDtl’ does not contain a public instance definition for ‘GetEnumerator’

foreach ( var od in ttOrderDtl.Where(x => x.RowMod == "A" && x.QuoteNum != 0) ) {

  var qd = Db.QuoteDtl.Where(x => x.Company == CompanyID
    && x.QuoteNum  == od.QuoteNum
    && x.QuoteLine == od.QuoteLine)
  .FirstOrDefault();

  if ( od != null && qd != null ) {

    foreach ( var udField in qd ) {
      if ( (string)udField.Contains("_c") ) od[udField] = qd[udField];
    }
  }
}

I’ve tried a couple of things. Replacing FirstOrDefault() with Select(s => s) seems to get me part of the way there, except then I can’t use indexing, so I can’t compare the field names. I feel like I’m right there, but can’t quite figure out how to get it done.

You’re not comparing what you thing you are there.
That’s comparing the value of the column, not the name of column.

Off the cuff, but this is more what you want:

foreach ( var orderDetail in ttOrderDtl.Where(odRow => odRow.RowMod == "A" && odRow.QuoteNum != 0) )
{
    var quoteDetail = Db.QuoteDtl.Where(qdRow =>
        qdRow.Company   == CompanyID &&
        qdRow.QuoteNum  == orderDetail.QuoteNum &&
        qdRow.QuoteLine == orderDetail.QuoteLine).FirstOrDefault();

    if ( orderDetail != null && quoteDetail != null )
    {
        foreach( var udColumn in quoteDetail.Columns)
        {
            if(udColumn.ColumnName.EndsWith("_c") == true)
                {
                orderDetail[udColumn.ColumnName] = quoteDetail[udColumn.ColumnName];
            }
        }
    }
}
1 Like

Is there a Using or Reference I need tp add for this? I’m getting “QuoteDtl does not contain a definitions for ‘Columns’”

it might be quoteDetail. [someTable] .Columns

I coded it in the reply form, not 100% sure on the syntax

Thanks! I appreciate the help. I knew I was doing something wrong there. I’ll update when I get it working.

Hey I was finally able to get it working and test it properly. The code below does the job. I really appreciate your help on this!

(for anyone who finds this in the future, this is an In-Transaction Data Directive on the OrderDtl Table)

foreach ( var od in ttOrderDtl.Where(x => x.RowMod == "A" && x.QuoteNum != 0) ) {

  var qd = Db.QuoteDtl.Where(x => x.Company == CompanyID
    && x.QuoteNum  == od.QuoteNum
    && x.QuoteLine == od.QuoteLine)
  .FirstOrDefault();

  if ( od != null && qd != null ) {

    Action<string> udCopy = col => {
      if ( col.EndsWith("_c") ) od[col] = qd[col];
    };

    qd.GetType().GetProperties().Select(s => s.Name).ToList().ForEach(udCopy);
  }
}

Only issue I have yet is handling when there is a QuoteDtl UD Field that doesn’t have a matching field in OrderDtl. It isn’t a big deal since I always make sure they match, but it would be nice to have that handled in case someone else takes this over in the future.

2 Likes