Ended up figuring it out - I wasn’t creating the sales order correctly from the quote.
Instead of simply hitting the ‘Create’, we have to go through the job wizard screen. This properly pulls our engineered job from the quote. This is still within “Create Sales Order”:

Then, since pricing wasn’t carrying over to the order since we didn’t use any configuration rules etc - I created a function on the order entry side that fires if it finds the specific part that we are creating this functionality for. I probably could have done this slightly better but here is the code:
string companyID = "*****"; //redacted for privacy :)
PartFound = false;
FoundPrice = 0m;
foundOrderLine = 0;
// Try to find a line with partnum = "5865000"
var orderLine = Db.OrderDtl
.FirstOrDefault(d => d.Company == companyID &&
d.OrderNum == SalesOrderNum &&
d.PartNum == "5865000");
if (orderLine != null)
{
PartFound = true;
foundOrderLine = orderLine.OrderLine;
// Only continue if we have a valid QuoteNum
if (orderLine.QuoteNum != 0)
{
var quoteLine = Db.QuoteDtl
.FirstOrDefault(qd => qd.Company == companyID &&
qd.QuoteNum == orderLine.QuoteNum &&
qd.QuoteLine == orderLine.QuoteLine);
if (quoteLine != null)
{
FoundPrice = quoteLine.DocExpUnitPrice;
}
}
}
After retrieving the price, I simply update the price within the order entry application based on our FoundPrice response param.
If anyone is interested in the overall use-case here, we wanted a generic part for generic kits which can be configured. You are able to enter all the parts ordered into the engineering for that part, then this function will divide all the qty/parent for all materials based off of a given division factor (we are assuming all parts are evenly divisable here… specific to our use case - though you could automatically look for the highest common denominator if anyone does something similar). Then, this function updates the qty of the parent part within the quote.
Basically, it will divide all your materials, update your quote parent part qty, and also calculate the price based on the customer on the quote → customer’s price list → part prices from that specific price list.
// Declare and initialize
var quoteMtlTS = new Erp.Tablesets.QuoteAsmTableset();
List < Erp.Tablesets.QuoteMtlRow > quoteMtlList = new List < Erp.Tablesets.QuoteMtlRow > ();
// Clear outputs
outJson = "";
outMessage = "";
debug = "";
innerdebug = "";
totalPrice = 0;
// Additional structures
var outJsonList = new List < Dictionary < string,
object >> ();
var mtlLookup = new Dictionary < int,
Dictionary < string, object >> (); // Using MtlSeq as the key
try {
this.CallService < Erp.Contracts.QuoteAsmSvcContract > (QuoteAsmSvc => {
try {
// Fetch dataset
QuoteAsmSvc.GetDatasetForTreeByRef(
ref quoteMtlTS,
QuoteNum,
QuoteLine,
0, // AssemblySeq
0, // CopyNum
false // GetLatestRev
);
// Confirm rows returned
debug = "quoteMtlTS.QuoteMtl count: " + quoteMtlTS.QuoteMtl.Count.ToString();
if (quoteMtlTS.QuoteMtl.Count == 0) {
innerdebug = "<b>ERROR</b><br><br>Please enter materials";
}
// Store rows in local list and populate mtlLookup using MtlSeq as the key
foreach(var row in quoteMtlTS.QuoteMtl) {
quoteMtlList.Add(row);
var dict = new Dictionary < string,
object > {
{
"AddrList",
row.AddrList
},
{
"AnalysisCdDescription",
row.AnalysisCdDescription
},
{
"AnalysisCode",
row.AnalysisCode
},
{
"APSAddResType",
row.APSAddResType
},
{
"AssemblySeq",
row.AssemblySeq
},
{
"AssemblySeqDescription",
row.AssemblySeqDescription
},
{
"AttributeSetID",
row.AttributeSetID
},
{
"AutoSetChkDirect",
row.AutoSetChkDirect
},
{
"BasePartNum",
row.BasePartNum
},
{
"BaseRevisionNum",
row.BaseRevisionNum
},
{
"BaseUOM",
row.BaseUOM
},
{
"BitFlag",
row.BitFlag
},
{
"BuyIt",
row.BuyIt
},
{
"Class",
row.Class
},
{
"ClassDescription",
row.ClassDescription
},
{
"ClassInactive",
row.ClassInactive
},
{
"Company",
row.Company
},
{
"ContractID",
row.ContractID
},
{
"Description",
row.Description
},
{
"Direct",
row.Direct
},
{
"DispPlanningNumberOfPieces",
row.DispPlanningNumberOfPieces
},
{
"DispSalvagePlanningNumberOfPieces",
row.DispSalvagePlanningNumberOfPieces
},
{
"DynAttrValueSetDescription",
row.DynAttrValueSetDescription
},
{
"DynAttrValueSetShortDescription",
row.DynAttrValueSetShortDescription
},
{
"EnableFixedQty",
row.EnableFixedQty
},
{
"EstMtlBurUnitCost",
row.EstMtlBurUnitCost
},
{
"EstMtlUnitCost",
row.EstMtlUnitCost
},
{
"EstScrap",
row.EstScrap
},
{
"EstScrapType",
row.EstScrapType
},
{
"EstUnitCost",
row.EstUnitCost
},
{
"FindNum",
row.FindNum
},
{
"FixedQty",
row.FixedQty
},
{
"GlbRFQ",
row.GlbRFQ
},
{
"GroupSeq",
row.GroupSeq
},
{
"IsMtlConfigurationOn",
row.IsMtlConfigurationOn
},
{
"IsMtlConfigureOn",
row.IsMtlConfigureOn
},
{
"IsMtlExtConfig",
row.IsMtlExtConfig
},
{
"IsMtlRevisionApproved",
row.IsMtlRevisionApproved
},
{
"IUM",
row.IUM
},
{
"LeadTime",
row.LeadTime
},
{
"LinkToContract",
row.LinkToContract
},
{
"LocationView",
row.LocationView
},
{
"MfgComment",
row.MfgComment
},
{
"MinimumCost",
row.MinimumCost
},
{
"MiscCharge",
row.MiscCharge
},
{
"MiscCode",
row.MiscCode
},
{
"MtlBurRate",
row.MtlBurRate
},
{
"MtlSeq",
row.MtlSeq
}, // Using MtlSeq as the key
{
"OrigGroupSeq",
row.OrigGroupSeq
},
{
"OrigStructTag",
row.OrigStructTag
},
{
"PartExists",
row.PartExists
},
{
"PartNum",
row.PartNum
},
{
"PartNumAttrClassID",
row.PartNumAttrClassID
},
{
"PartNumIUM",
row.PartNumIUM
},
{
"PartNumPartDescription",
row.PartNumPartDescription
},
{
"PartNumPricePerCode",
row.PartNumPricePerCode
},
{
"PartNumSalesUM",
row.PartNumSalesUM
},
{
"PartNumSellingFactor",
row.PartNumSellingFactor
},
{
"PartNumTrackDimension",
row.PartNumTrackDimension
},
{
"PartNumTrackInventoryAttributes",
row.PartNumTrackInventoryAttributes
},
{
"PartNumTrackInventoryByRevision",
row.PartNumTrackInventoryByRevision
},
{
"PartNumTrackLots",
row.PartNumTrackLots
},
{
"PartNumTrackSerialNum",
row.PartNumTrackSerialNum
},
{
"PBImage",
row.PBImage
},
{
"PBrkCost01",
row.PBrkCost01
},
{
"PBrkCost02",
row.PBrkCost02
},
{
"PBrkCost03",
row.PBrkCost03
},
{
"PBrkCost04",
row.PBrkCost04
},
{
"PBrkCost05",
row.PBrkCost05
},
{
"PBrkCost06",
row.PBrkCost06
},
{
"PBrkCost07",
row.PBrkCost07
},
{
"PBrkCost08",
row.PBrkCost08
},
{
"PBrkCost09",
row.PBrkCost09
},
{
"PBrkCost10",
row.PBrkCost10
},
{
"PBrkQty01",
row.PBrkQty01
},
{
"PBrkQty02",
row.PBrkQty02
},
{
"PBrkQty03",
row.PBrkQty03
},
{
"PBrkQty04",
row.PBrkQty04
},
{
"PBrkQty05",
row.PBrkQty05
},
{
"PBrkQty06",
row.PBrkQty06
},
{
"PBrkQty07",
row.PBrkQty07
},
{
"PBrkQty08",
row.PBrkQty08
},
{
"PBrkQty09",
row.PBrkQty09
},
{
"PBrkQty10",
row.PBrkQty10
},
{
"PCLinkRemoved",
row.PCLinkRemoved
},
{
"PlanningNumberOfPieces",
row.PlanningNumberOfPieces
},
{
"PLMMtlSeq",
row.PLMMtlSeq
},
{
"PurComment",
row.PurComment
},
{
"PurMiscDescription",
row.PurMiscDescription
},
{
"PurPoint",
row.PurPoint
},
{
"PurPointAddress1",
row.PurPointAddress1
},
{
"PurPointAddress2",
row.PurPointAddress2
},
{
"PurPointAddress3",
row.PurPointAddress3
},
{
"PurPointCity",
row.PurPointCity
},
{
"PurPointCountry",
row.PurPointCountry
},
{
"PurPointName",
row.PurPointName
},
{
"PurPointPrimPCon",
row.PurPointPrimPCon
},
{
"PurPointState",
row.PurPointState
},
{
"PurPointZip",
row.PurPointZip
},
{
"QtyBearing",
row.QtyBearing
},
{
"QtyPer",
row.QtyPer
},
{
"QuoteLine",
row.QuoteLine
},
{
"QuoteLineLineDesc",
row.QuoteLineLineDesc
},
{
"QuoteNum",
row.QuoteNum
},
{
"QuoteNumCurrencyCode",
row.QuoteNumCurrencyCode
},
{
"RDEndNum",
row.RDEndNum
},
{
"RDPrefix",
row.RDPrefix
},
{
"RDStartNum",
row.RDStartNum
},
{
"RDSuffix",
row.RDSuffix
},
{
"RelatedOperation",
row.RelatedOperation
},
{
"RelatedOperationDescription",
row.RelatedOperationDescription
},
{
"ReqRefDes",
row.ReqRefDes
},
{
"RequiredQty",
row.RequiredQty
},
{
"RevisionNum",
row.RevisionNum
},
{
"RFQLine",
row.RFQLine
},
{
"RFQLineLineDesc",
row.RFQLineLineDesc
},
{
"RFQNeeded",
row.RFQNeeded
},
{
"RFQNum",
row.RFQNum
},
{
"RFQStat",
row.RFQStat
},
{
"RFQVendQuotes",
row.RFQVendQuotes
},
{
"RowMod",
row.RowMod
},
{
"SalvageAttrClassID",
row.SalvageAttrClassID
},
{
"SalvageAttributeSetID",
row.SalvageAttributeSetID
},
{
"SalvageBaseUOM",
row.SalvageBaseUOM
},
{
"SalvageDescription",
row.SalvageDescription
},
{
"SalvageEstMtlBurUnitCredit",
row.SalvageEstMtlBurUnitCredit
},
{
"SalvageMtlBurRate",
row.SalvageMtlBurRate
},
{
"SalvageQtyPer",
row.SalvageQtyPer
},
{
"SalvageUM",
row.SalvageUM
}
};
mtlLookup.Add(row.MtlSeq, dict); // Use MtlSeq as the key
}
// Modify fields as required
foreach(var mtl in mtlLookup.Values) {
decimal qtyPer = Convert.ToDecimal(mtl["QtyPer"]);
decimal factor = Convert.ToDecimal(divisionFactor);
// Check if the division is clean (no remainder)
if (qtyPer % factor == 0) {
int dividedQty = Convert.ToInt32(qtyPer / factor);
// Assign divided value
mtl["QtyPer"] = dividedQty;
mtl["RDEndNum"] = dividedQty;
mtl["ReqRefDes"] = dividedQty;
mtl["RequiredQty"] = dividedQty;
// Mark for update
mtl["RowMod"] = "U";
// Retrieve the customer number from QuoteNum
int custNum = 0; // Default value for CustNum
this.CallService < Erp.Contracts.QuoteSvcContract > (QuoteService => {
try {
// Retrieve the quote using QuoteNum
var retrievedQuote = QuoteService.GetByID((int) mtl["QuoteNum"]);
// Check if QuoteHed is not null and contains elements
var quoteHed = retrievedQuote?.QuoteHed?.FirstOrDefault(q => q.QuoteNum == (int) mtl["QuoteNum"]);
if (quoteHed != null) {
custNum = quoteHed.CustNum;
}
} catch (Exception ex) {
// Handle exception and return default value
custNum = 0;
}
});
//int custNum = this.EfxLib.LCIQuoteLibrary.GetCustNumFromQuote((int)mtl["QuoteNum"]);
// Retrieve the customer price list based on the customer number
string priceList = (string) this.EfxLib.LCIPriceLst.GetCustomerPriceListByCustNum(custNum);
// Retrieve the part price based on the PartNum and the price list
decimal partPrice = 0;
try {
var partNumTrimmed = ((string) mtl["PartNum"])?.Trim().ToUpper();
var listCodeTrimmed = priceList?.Trim().ToUpper();
if (!string.IsNullOrEmpty(partNumTrimmed) && !string.IsNullOrEmpty(listCodeTrimmed)) {
var priceLstPart = Db.PriceLstParts
.FirstOrDefault(plp =>
plp.PartNum.Trim().ToUpper() == partNumTrimmed &&
plp.ListCode.Trim().ToUpper() == listCodeTrimmed);
if (priceLstPart != null) {
partPrice = priceLstPart.BasePrice;
}
}
} catch (Exception ex) {
partPrice = 0;
innerdebug = "<b>Error</b><br>err retrieving part price - are all parts in the price list?<br>";
}
//decimal partPrice = (decimal)this.EfxLib.LCIPriceLst.GetPartPrice((string)mtl["PartNum"], priceList);
totalPrice += partPrice * dividedQty;
} else {
// Round both values to 2 decimal places for the debug message
decimal roundedQtyPer = decimal.Round(qtyPer, 2);
decimal roundedFactor = decimal.Round(factor, 2);
innerdebug = $"<b>Error</b> - <br><br>not clean division: <b>{roundedQtyPer} / {roundedFactor}</b><br><br><b>Please ensure all division is clean without remainders</b>";
return;
}
}
// Set the modified data back into the QuoteMtl dataset for update
foreach(var row in quoteMtlTS.QuoteMtl) {
var updatedRow = mtlLookup.ContainsKey(row.MtlSeq) ? mtlLookup[row.MtlSeq] : null;
if (updatedRow != null) {
row.QtyPer = Convert.ToInt32(updatedRow["QtyPer"]);
row.RDEndNum = Convert.ToInt32(updatedRow["RDEndNum"]);
row.ReqRefDes = Convert.ToInt32(updatedRow["ReqRefDes"]);
row.RequiredQty = Convert.ToDecimal(updatedRow["RequiredQty"]);
row.RowMod = updatedRow["RowMod"].ToString();
}
}
// Update the service with modified data
QuoteAsmSvc.Update(ref quoteMtlTS);
// Serialize and order by MtlSeq before sending the result
outJson = Newtonsoft.Json.JsonConvert.SerializeObject(
outJsonList.OrderBy(d => (int) d["MtlSeq"]), // Optional: Ensure MtlSeq order
Newtonsoft.Json.Formatting.Indented
);
} catch (Exception ex) {
outMessage = ex.Message;
debug = "Error while processing QuoteAsm: " + ex.Message;
}
});
} catch (Exception ex) {
outMessage = ex.Message;
debug = "Error in service call: " + ex.Message;
}
There are a lot of extra response params not even being used here in the end as they were helpful in debugging while writing this function, and innerdebug turned into an error message that appears on the application if innerdebug != “”. But wanted to share in-case anyone found this useful for any reason 