Skip to content

[main]- Negative Shipped and Invoiced Quantities appear on Sales Order Lines after using the Copy Document from a Sales Credit Memo. - #10083

Open
MSNehaNawal wants to merge 2 commits into
mainfrom
bugs/Bug-646375-main-NegQtyShipped_CopyDocSalesCM
Open

[main]- Negative Shipped and Invoiced Quantities appear on Sales Order Lines after using the Copy Document from a Sales Credit Memo.#10083
MSNehaNawal wants to merge 2 commits into
mainfrom
bugs/Bug-646375-main-NegQtyShipped_CopyDocSalesCM

Conversation

@MSNehaNawal

@MSNehaNawal MSNehaNawal commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

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.

@github-actions github-actions Bot added the SCM GitHub request for SCM area label Aug 10, 2026
@github-actions github-actions Bot added this to the Version 29.0 milestone Aug 10, 2026
@MSNehaNawal
MSNehaNawal marked this pull request as ready for review August 10, 2026 16:38
@MSNehaNawal
MSNehaNawal requested a review from a team August 10, 2026 16:38
if SalesInvoiceLine."Line No." <> 0 then begin
UpdateSalesOrderLinesFromCreditMemo(SalesInvoiceLine, SalesCrMemoLine);
TempUsedSalesInvoiceLine := SalesInvoiceLine;
if TempUsedSalesInvoiceLine.Insert() then;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Error\ Handling}$

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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Error\ Handling}$

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟠\ High\ Severity\ —\ Performance}$

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟠\ High\ Severity\ —\ Performance}$

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟠\ High\ Severity\ —\ Performance}$

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟠\ High\ Severity\ —\ Performance}$

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟠\ High\ Severity\ —\ Performance}$

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟠\ High\ Severity\ —\ Performance}$

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟠\ High\ Severity\ —\ Performance}$

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟠\ High\ Severity\ —\ Performance}$

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟠\ High\ Severity\ —\ Performance}$

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟠\ High\ Severity\ —\ Performance}$

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

SCM GitHub request for SCM area

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant