I see several are struggling with doing copy/paste of sales order lines, especially if the orders are large and there are complex logics for SCM and pricing. Based on how you set up D365, you will see examples where pasting a sales line can take from 1.5s and also beoynd 6s per line. And there are also much worse examples.
I have really gone into all code and SQL statements happening, and there are a LOT happening.
So I asked my-self, can I with AI do better ? Should I try a few minutes with “Vibe coding” ?
My idea is to not paste into the grid, but rather paste into a text field, and then have a single class create the sales lines within the same TTS.
So I asked ChatGPT for it. After a few iterations it actually created the class that does exactly that. I just needed to add a menu item, and then add it to the sales lines form.
Here are the solution it created:
We have a “QuickPaste” button on the sales order form, that brings up a dialog, where we can paste the item[tab]Qty. I also made him create an estimator to show how long it would take to create the 100 lines.

Next, when clicking “OK”, and 25s later, I have a sales order with 100 lines:

Nice 🙂 245ms in a UDE sandbox is not bad. In a production system, I hope to see it even further down towards 100ms per sales line.
As this is “Vibe coding” the code is not production ready, and should be considered as a alfa preview. There are tonns of possibilities to improve this, and is someone created a Github project on it, we could create something for those that hate waiting for copy/paste.
Here are the code for this demo:
/// Action menu item: Class = SalesOrderQuickPasteLines
/// Paste rows: ItemIdQty
/// Insert lines in ONE TTS (abort on first failing row), then run Retail full recalc OUTSIDE TTS,
/// refresh SalesLine DS, popup summary. Includes large paste dialog with live ETA (250ms/line).
///
/// Notes:
/// - GUPDelayPricingCalculation toggled during insert TTS only.
/// - SkipCreateMarkup set on insert; cleared OUTSIDE TTS before retail recalc (set-based).
/// - Uses MCRSalesTableController::recalculateRetailPricesDiscounts(SalesTable) outside TTS.
class SalesOrderQuickPasteLines extends RunBase
{
#define.ProgressEvery(20)
#define.MaxErr(4000)
#define.MsPerLine(250)
#define.AggregateSameItemId(false) // true can change behavior (merges same item into one line)
SalesId salesId;
FormRun callerFr;
str pasteText;
DialogField dfLines, dfEst;
FormStringControl cLines, cEst;
public static void main(Args _a)
{
SalesTable st = _a ? _a.record() : null;
if (!st || st.TableId != tableNum(SalesTable))
throw error("Run from SalesTable.");
SalesOrderQuickPasteLines o = new SalesOrderQuickPasteLines();
o.parmSalesId(st.SalesId);
o.parmCaller(_a ? _a.caller() as FormRun : null);
if (o.prompt())
o.runOperation();
}
public boolean canRunInNewSession()
{
return false; // needs caller form datasource refresh
}
public SalesId parmSalesId(SalesId _v = salesId)
{
salesId = _v; return salesId;
}
public FormRun parmCaller(FormRun _v = callerFr)
{
callerFr = _v; return callerFr;
}
// --- RunBase dialog ---
public Object dialog()
{
Dialog d = super();
d.caption("Paste lines (ItemIdQty)");
DialogGroup g = d.addGroup("Parameters");
dfLines = d.addField(extendedTypeStr(Notes), "Lines");
dfEst = d.addField(extendedTypeStr(Description), "Estimate");
dfLines.value("");
dfLines.displayLength(200);
dfEst.allowEdit(false);
dfEst.value("Lines: 0 | Est. time: 0 s (250 ms/line)");
return d;
}
public void dialogPostRun(DialogRunbase _d)
{
super(_d);
cLines = dfLines.control() as FormStringControl;
cEst = dfEst.control() as FormStringControl;
if (cLines)
{
cLines.height(420); // big paste area
cLines.registerOverrideMethod(
methodStr(FormStringControl, modified),
methodStr(SalesOrderQuickPasteLines, lines_modified),
this);
}
this.updateEstimate(cLines ? cLines.text() : "");
}
public boolean getFromDialog()
{
boolean ok = super();
pasteText = strLRTrim(dfLines.value());
return ok;
}
// --- RunBase execution ---
public void run()
{
if (!pasteText)
return;
List rows = this.parse(pasteText); // [lineNo,itemId,qty]
int inputCount = rows.elements();
if (!inputCount)
throw error("No valid rows. Expected: ItemIdQty.");
int64 t0 = WinAPIServer::getTickCount();
int created = this.insertLinesInOneTts(rows, inputCount); // COMMIT happens here
int64 insertMs = WinAPIServer::getTickCount() - t0;
// Post-commit: clear SkipCreateMarkup + full retail recalc OUTSIDE TTS to avoid update conflicts
int64 t1 = WinAPIServer::getTickCount();
this.postCommitRetailRecalc();
int64 recalcMs = WinAPIServer::getTickCount() - t1;
this.refreshSalesLineDs();
Box::info(strFmt("Import completed.\nLines created: %1\nInsert time: %2 ms\nRecalc time: %3 ms.",
created, insertMs, recalcMs), "QuickPaste");
}
// --- Live ETA in dialog ---
public boolean lines_modified(FormStringControl _ctrl)
{
this.updateEstimate(_ctrl ? _ctrl.text() : "");
return true;
}
private void updateEstimate(str _text)
{
int n = this.estimateLineCount(_text);
int64 ms = n * #MsPerLine;
int sec = any2int((ms + 999) / 1000);
str s = strFmt("Lines: %1 | Est. time: %2 s (%3 ms/line)", n, sec, #MsPerLine);
if (cEst) cEst.text(s); else if (dfEst) dfEst.value(s);
}
private int estimateLineCount(str _text)
{
if (!_text) return 0;
_text = strReplace(_text, "\r", "");
List raw = Global::strSplit(_text, "\n");
ListEnumerator e = raw.getEnumerator();
int n = 0;
while (e.moveNext())
if (strLRTrim(e.current())) n++;
return n;
}
// --- Parsing ---
private List parse(str _in)
{
List lines = new List(Types::Container);
if (!_in) return lines;
_in = strReplace(_in, "\r", "");
List raw = Global::strSplit(_in, "\n");
ListEnumerator e = raw.getEnumerator();
int n = 0;
while (e.moveNext())
{
n++;
str s = strLRTrim(e.current());
if (!s) continue;
int sep = strFind(s, "\t", 1, strLen(s));
if (sep <= 0) sep = strFind(s, " ", 1, strLen(s));
if (sep <= 0) continue;
str itemStr = strLRTrim(substr(s, 1, sep - 1));
str qtyStr = strLRTrim(substr(s, sep + 1, strLen(s)));
if (!itemStr || !qtyStr) continue;
ItemId itemId = itemStr;
Qty qty = any2real(qtyStr);
if (!itemId || qty InventTable
// Worklist: [lineNo,itemId,qty]
List work = new List(Types::Container);
if (#AggregateSameItemId)
{
Map qtyByItem = new Map(Types::String, Types::Real);
Map firstLine = new Map(Types::String, Types::Integer);
Set items = new Set(Types::String);
ListEnumerator e = _rows.getEnumerator();
while (e.moveNext())
{
container c = e.current();
int ln = conPeek(c, 1);
ItemId it = conPeek(c, 2);
Qty q = conPeek(c, 3);
items.add(it);
if (!firstLine.exists(it)) firstLine.insert(it, ln);
real prev = qtyByItem.exists(it) ? qtyByItem.lookup(it) : 0.0;
qtyByItem.insert(it, prev + q);
p.incCount(1);
}
SetEnumerator se = items.getEnumerator();
while (se.moveNext())
{
ItemId it = se.current();
int ln = firstLine.lookup(it);
Qty q = qtyByItem.lookup(it);
work.addEnd([ln, it, q]);
}
}
else
{
work = _rows;
}
int progressCounter = 0;
int64 t0 = WinAPIServer::getTickCount();
int workTotal = work.elements();
ttsBegin;
try
{
origDelay = st.GUPDelayPricingCalculation;
st.GUPDelayPricingCalculation = NoYes::Yes;
st.doUpdate();
ListEnumerator e2 = work.getEnumerator();
while (e2.moveNext())
{
container c2 = e2.current();
int lineNo = conPeek(c2, 1);
ItemId itemId = conPeek(c2, 2);
Qty qty = conPeek(c2, 3);
InventTable it;
if (inv.exists(itemId))
{
it = inv.lookup(itemId);
}
else
{
it = InventTable::find(itemId, true);
if (!it.RecId) throw error(this.err(lineNo, itemId, qty, "Item does not exist."));
inv.insert(itemId, it);
}
this.createFast(st, it, qty, lineNo);
created++;
progressCounter++;
if (progressCounter >= #ProgressEvery || created == workTotal)
{
progressCounter = 0;
int64 elapsed = WinAPIServer::getTickCount() - t0;
real avgMs = created ? (elapsed / created) : 0;
real remMs = avgMs * (workTotal - created);
p.setText(strFmt("Created %1/%2. Est. remaining: %3 s",
created, workTotal, any2int(remMs / 1000)));
}
if (!#AggregateSameItemId)
p.incCount(1);
}
st = SalesTable::find(this.salesId, true);
st.GUPDelayPricingCalculation = origDelay;
st.doUpdate();
ttsCommit;
}
catch (Exception::Error)
{
ttsAbort;
throw;
}
return created;
}
private void createFast(SalesTable _st, InventTable _it, Qty _qty, int _lineNo)
{
try
{
SalesLine sl;
sl.clear(); sl.initValue();
sl.SalesId = _st.SalesId;
sl.ItemId = _it.ItemId;
sl.SalesQty = _qty;
sl.SalesUnit = _it.salesUnitId();
sl.SkipCreateMarkup = NoYes::Yes;
// Fast: validate=false, calcInventQty=false, searchMarkup/Price=false
sl.createLine(false, true, true, false, false, false);
}
catch (Exception::Error)
{
throw error(this.err(_lineNo, _it.ItemId, _qty, this.lastInfo(#MaxErr)));
}
}
// --- Post-commit recalc OUTSIDE TTS ---
private void postCommitRetailRecalc()
{
SalesTable st = SalesTable::find(this.salesId, true);
SalesLine SalesLine;
// Clear markup suppression OUTSIDE TTS (prevents conflicts with recalculation pipeline)
update_recordset SalesLine
setting SkipCreateMarkup = NoYes::No
where SalesLine.SalesId == st.SalesId
&& SalesLine.SkipCreateMarkup == NoYes::Yes;
// Full retail recalc (same pipeline as the button)
MCRSalesTableController::recalculateRetailPricesDiscounts(st);
}
private void refreshSalesLineDs()
{
if (!callerFr) return;
FormDataSource ds = callerFr.dataSource(formDataSourceStr(SalesTable, SalesLine));
if (ds) ds.executeQuery();
}
private str err(int _n, ItemId _i, Qty _q, str _r)
{
_r = strLRTrim(_r); if (!_r) _r = "Unknown error.";
return strFmt("Import failed. Input line %1 (ItemId=%2, Qty=%3). Reason: %4", _n, _i, _q, _r);
}
private str lastInfo(int _max)
{
str t = "";
try
{
for (int i = infolog.num(); i > 0 && strLen(t) _max) t = substr(t, 1, _max);
return t;
}
}