[main]- Negative Shipped and Invoiced Quantities appear on Sales Order Lines after using the Copy Document from a Sales Credit Memo. - #10083
Conversation
| if SalesInvoiceLine."Line No." <> 0 then begin | ||
| UpdateSalesOrderLinesFromCreditMemo(SalesInvoiceLine, SalesCrMemoLine); | ||
| TempUsedSalesInvoiceLine := SalesInvoiceLine; | ||
| if TempUsedSalesInvoiceLine.Insert() then; |
There was a problem hiding this comment.
if TempUsedSalesInvoiceLine.Insert() then; suppresses the duplicate-key failure from the temporary tracking table and continues as if the Sales Invoice Line had been recorded. That hides a broken uniqueness assumption in the new matching logic and can leave the credit memo correction mapped inconsistently. Call Insert() without consuming the Boolean result, or raise an error when it returns false.
Suggested fix (apply manually — could not be anchored as a one-click suggestion):
TempUsedSalesInvoiceLine.Insert();👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.31.4
| if SalesInvoiceLine."Line No." <> 0 then begin | ||
| UpdateSalesOrderLinesFromCreditMemo(SalesInvoiceLine, SalesCrMemoLine); | ||
| TempUsedSalesInvoiceLine := SalesInvoiceLine; | ||
| if TempUsedSalesInvoiceLine.Insert() then; |
There was a problem hiding this comment.
if TempUsedSalesInvoiceLine.Insert() then; suppresses the duplicate-key failure from the temporary tracking table and continues as if the Sales Invoice Line had been recorded. That hides a broken uniqueness assumption in the new matching logic and can leave the credit memo correction mapped inconsistently. Call Insert() without consuming the Boolean result, or raise an error when it returns false.
Suggested fix (apply manually — could not be anchored as a one-click suggestion):
TempUsedSalesInvoiceLine.Insert();The same issue exists in these regional copies — apply the equivalent fix in each:
src/Layers/ES/BaseApp/Sales/History/CorrectPostedSalesInvoice.Codeunit.al:1043(ES)
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.31.4
|
|
||
| local procedure FindUnusedSalesInvoiceLine(var SalesInvoiceLine: Record "Sales Invoice Line"; var TempUsedSalesInvoiceLine: Record "Sales Invoice Line" temporary): Boolean | ||
| begin | ||
| if SalesInvoiceLine.FindSet() then |
There was a problem hiding this comment.
FindUnusedSalesInvoiceLine scans filtered "Sales Invoice Line" records but only reads "Document No." and "Line No." before probing the temporary used-lines set. Because GetSalesInvoiceLine can call this helper up to three times per credit-memo line, each pass now loads the full Sales Invoice Line row for every candidate match. Add SetLoadFields("Document No.", "Line No.") before FindSet and re-Get the winning row once an unused key is found.
Knowledge:
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.31.4
|
|
||
| local procedure FindUnusedSalesInvoiceLine(var SalesInvoiceLine: Record "Sales Invoice Line"; var TempUsedSalesInvoiceLine: Record "Sales Invoice Line" temporary): Boolean | ||
| begin | ||
| if SalesInvoiceLine.FindSet() then |
There was a problem hiding this comment.
FindUnusedSalesInvoiceLine scans filtered "Sales Invoice Line" records but only reads "Document No." and "Line No." before probing the temporary used-lines set. Because GetSalesInvoiceLine can call this helper up to three times per credit-memo line, each pass now loads the full Sales Invoice Line row for every candidate match. Add SetLoadFields("Document No.", "Line No.") before FindSet and re-Get the winning row once an unused key is found.
Knowledge:
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.31.4
|
|
||
| local procedure FindUnusedSalesInvoiceLine(var SalesInvoiceLine: Record "Sales Invoice Line"; var TempUsedSalesInvoiceLine: Record "Sales Invoice Line" temporary): Boolean | ||
| begin | ||
| if SalesInvoiceLine.FindSet() then |
There was a problem hiding this comment.
FindUnusedSalesInvoiceLine scans filtered "Sales Invoice Line" records but only reads "Document No." and "Line No." before probing the temporary used-lines set. Because GetSalesInvoiceLine can call this helper up to three times per credit-memo line, each pass now loads the full Sales Invoice Line row for every candidate match. Add SetLoadFields("Document No.", "Line No.") before FindSet and re-Get the winning row once an unused key is found.
Knowledge:
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.31.4
|
|
||
| local procedure FindUnusedSalesInvoiceLine(var SalesInvoiceLine: Record "Sales Invoice Line"; var TempUsedSalesInvoiceLine: Record "Sales Invoice Line" temporary): Boolean | ||
| begin | ||
| if SalesInvoiceLine.FindSet() then |
There was a problem hiding this comment.
FindUnusedSalesInvoiceLine scans filtered "Sales Invoice Line" records but only reads "Document No." and "Line No." before probing the temporary used-lines set. Because GetSalesInvoiceLine can call this helper up to three times per credit-memo line, each pass now loads the full Sales Invoice Line row for every candidate match. Add SetLoadFields("Document No.", "Line No.") before FindSet and re-Get the winning row once an unused key is found.
Knowledge:
The same issue exists in these regional copies — apply the equivalent fix in each:
src/Layers/SE/BaseApp/Sales/History/SalesCrMemoLine.Table.al:1243(SE)
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.31.4
|
|
||
| local procedure FindUnusedSalesInvoiceLine(var SalesInvoiceLine: Record "Sales Invoice Line"; var TempUsedSalesInvoiceLine: Record "Sales Invoice Line" temporary): Boolean | ||
| begin | ||
| if SalesInvoiceLine.FindSet() then |
There was a problem hiding this comment.
FindUnusedSalesInvoiceLine scans filtered "Sales Invoice Line" records but only reads "Document No." and "Line No." before probing the temporary used-lines set. Because GetSalesInvoiceLine can call this helper up to three times per credit-memo line, each pass now loads the full Sales Invoice Line row for every candidate match. Add SetLoadFields("Document No.", "Line No.") before FindSet and re-Get the winning row once an unused key is found.
Knowledge:
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.31.4
|
|
||
| local procedure FindUnusedSalesInvoiceLine(var SalesInvoiceLine: Record "Sales Invoice Line"; var TempUsedSalesInvoiceLine: Record "Sales Invoice Line" temporary): Boolean | ||
| begin | ||
| if SalesInvoiceLine.FindSet() then |
There was a problem hiding this comment.
FindUnusedSalesInvoiceLine scans filtered "Sales Invoice Line" records but only reads "Document No." and "Line No." before probing the temporary used-lines set. Because GetSalesInvoiceLine can call this helper up to three times per credit-memo line, each pass now loads the full Sales Invoice Line row for every candidate match. Add SetLoadFields("Document No.", "Line No.") before FindSet and re-Get the winning row once an unused key is found.
Knowledge:
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.31.4
|
|
||
| local procedure FindUnusedSalesInvoiceLine(var SalesInvoiceLine: Record "Sales Invoice Line"; var TempUsedSalesInvoiceLine: Record "Sales Invoice Line" temporary): Boolean | ||
| begin | ||
| if SalesInvoiceLine.FindSet() then |
There was a problem hiding this comment.
FindUnusedSalesInvoiceLine scans filtered "Sales Invoice Line" records but only reads "Document No." and "Line No." before probing the temporary used-lines set. Because GetSalesInvoiceLine can call this helper up to three times per credit-memo line, each pass now loads the full Sales Invoice Line row for every candidate match. Add SetLoadFields("Document No.", "Line No.") before FindSet and re-Get the winning row once an unused key is found.
Knowledge:
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.31.4
|
|
||
| local procedure FindUnusedSalesInvoiceLine(var SalesInvoiceLine: Record "Sales Invoice Line"; var TempUsedSalesInvoiceLine: Record "Sales Invoice Line" temporary): Boolean | ||
| begin | ||
| if SalesInvoiceLine.FindSet() then |
There was a problem hiding this comment.
FindUnusedSalesInvoiceLine scans filtered "Sales Invoice Line" records but only reads "Document No." and "Line No." before probing the temporary used-lines set. Because GetSalesInvoiceLine can call this helper up to three times per credit-memo line, each pass now loads the full Sales Invoice Line row for every candidate match. Add SetLoadFields("Document No.", "Line No.") before FindSet and re-Get the winning row once an unused key is found.
Knowledge:
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.31.4
|
|
||
| local procedure FindUnusedSalesInvoiceLine(var SalesInvoiceLine: Record "Sales Invoice Line"; var TempUsedSalesInvoiceLine: Record "Sales Invoice Line" temporary): Boolean | ||
| begin | ||
| if SalesInvoiceLine.FindSet() then |
There was a problem hiding this comment.
FindUnusedSalesInvoiceLine scans filtered "Sales Invoice Line" records but only reads "Document No." and "Line No." before probing the temporary used-lines set. Because GetSalesInvoiceLine can call this helper up to three times per credit-memo line, each pass now loads the full Sales Invoice Line row for every candidate match. Add SetLoadFields("Document No.", "Line No.") before FindSet and re-Get the winning row once an unused key is found.
Knowledge:
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.31.4
|
|
||
| local procedure FindUnusedSalesInvoiceLine(var SalesInvoiceLine: Record "Sales Invoice Line"; var TempUsedSalesInvoiceLine: Record "Sales Invoice Line" temporary): Boolean | ||
| begin | ||
| if SalesInvoiceLine.FindSet() then |
There was a problem hiding this comment.
FindUnusedSalesInvoiceLine scans filtered "Sales Invoice Line" records but only reads "Document No." and "Line No." before probing the temporary used-lines set. Because GetSalesInvoiceLine can call this helper up to three times per credit-memo line, each pass now loads the full Sales Invoice Line row for every candidate match. Add SetLoadFields("Document No.", "Line No.") before FindSet and re-Get the winning row once an unused key is found.
Knowledge:
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.31.4
Bug 646375: [ALL-E] Negative Shipped and Invoiced Quantities appear on Sales Order Lines after using the Copy Document from a Sales Credit Memo.
Fixes AB#646375
Issue-
When a corrective Sales Credit Memo is created via Copy Document from a Posted Sales Invoice, and that invoice contains the same item on multiple lines (e.g. shipped from several sales orders and invoiced together via Get Shipment Lines), posting the credit memo produces negative Quantity Shipped / Quantity Invoiced on one sales order line while another order line is left unchanged.
Root cause-
Copy Document does not populate Appl.-from Item Entry on the credit memo lines, so SalesCrMemoLine.GetSalesInvoiceLine falls back to matching the source invoice line only by Item No. + Quantity >= X and takes FindFirst(). With duplicate items, every credit memo line resolves to the same first invoice line, so the corresponding order line is decremented repeatedly (going negative) and the other order line is never corrected.
Solution-
GetSalesInvoiceLine:
Added an overload that accepts a temporary "used invoice lines" buffer. The new FindUnusedSalesInvoiceLine helper skips invoice lines already consumed by earlier credit memo lines. Returns a blank line when no unused candidate remains.
UpdateSalesOrderLineIfExist now keeps a temporary buffer of resolved invoice lines, passes it into the resolver, and records each successfully consumed invoice line so it cannot be reused for a subsequent credit memo line.