Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -303,14 +303,21 @@ codeunit 8069 "Sales Subscription Line Mgmt."
end;

[EventSubscriber(ObjectType::Codeunit, Codeunit::"Sales-Post", OnBeforeSalesLineDeleteAll, '', false, false)]
local procedure DeleteSalesServiceCommitmentOnBeforeSalesLineDeleteAll(var SalesLine: Record "Sales Line")
var
SalesServiceCommitment: Record "Sales Subscription Line";
local procedure DeleteSalesServiceCommitmentOnBeforeSalesLineDeleteAll(var SalesLine: Record "Sales Line"; CommitIsSuppressed: Boolean; var SalesHeader: Record "Sales Header")
begin
if not SalesLine.FindFirst() then
exit;
SalesServiceCommitment.FilterOnDocument(SalesLine."Document Type", SalesLine."Document No.");
SalesServiceCommitment.DeleteAll(false);
SalesHeader.DeleteSalesServiceCommitments();
end;

[EventSubscriber(ObjectType::Report, Report::"Delete Invoiced Sales Orders", OnAfterDeleteSalesLinesLoop, '', false, false)]
local procedure DeleteSalesServiceCommitmentOnAfterDeleteSalesLinesLoop(var SalesHeader: Record "Sales Header")
begin
SalesHeader.DeleteSalesServiceCommitments();
end;

[EventSubscriber(ObjectType::Report, Report::"Delete Invd Blnkt Sales Orders", OnBeforeDeleteSalesHeader, '', false, false)]
local procedure DeleteSalesServiceCommitmentOnBeforeDeleteBlanketSalesHeader(var SalesHeader: Record "Sales Header")
begin
SalesHeader.DeleteSalesServiceCommitments();
end;

[EventSubscriber(ObjectType::Codeunit, Codeunit::"Sales-Quote to Order", OnAfterInsertSalesOrderLine, '', false, false)]
Expand All @@ -321,12 +328,8 @@ codeunit 8069 "Sales Subscription Line Mgmt."

[EventSubscriber(ObjectType::Codeunit, Codeunit::"Sales-Quote to Order", OnRunOnAfterSalesQuoteLineDeleteAll, '', false, false)]
local procedure DeleteSalesServiceCommitmentOnAfterSalesQuoteLineDeleteAll(var SalesHeaderRec: Record "Sales Header")
var
SalesServiceCommitment: Record "Sales Subscription Line";
begin
SalesServiceCommitment.SetRange("Document Type", SalesHeaderRec."Document Type");
SalesServiceCommitment.SetRange("Document No.", SalesHeaderRec."No.");
SalesServiceCommitment.DeleteAll(false);
SalesHeaderRec.DeleteSalesServiceCommitments();
end;

[EventSubscriber(ObjectType::Codeunit, Codeunit::"Blanket Sales Order to Order", OnAfterInsertSalesOrderLine, '', false, false)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,31 @@ tableextension 8053 "Sales Header" extends "Sales Header"
}
}

/// <summary>
/// Deletes the Sales Subscription Lines of every line of this sales document in one batch.
/// Use this instead of the Sales Line procedure DeleteSalesServiceCommitment() when all lines of the
/// document are removed without running their triggers, for example when the document is posted, when
/// a quote is converted into an order, or by the "Delete Invoiced Sales Orders" and
/// "Delete Invoiced Blanket Sales Orders" batch jobs.
/// Unlike the Sales Line procedure this does not skip document types outside Quote, Order and Blanket
/// Order, because Copy Document carries Sales Subscription Lines over to an Invoice or Credit Memo as
/// well, and those have to be cleaned up when the document is posted.
/// Temporary records are skipped, so calling this on a temporary Sales Header never deletes the real
/// Sales Subscription Lines of a document that happens to share its number.
/// </summary>
procedure DeleteSalesServiceCommitments()
var
SalesServiceCommitment: Record "Sales Subscription Line";
begin
if Rec.IsTemporary() then
exit;
SalesServiceCommitment.FilterOnDocument(Rec."Document Type", Rec."No.");
if SalesServiceCommitment.IsEmpty() then
exit;

SalesServiceCommitment.DeleteAll(false);
end;

local procedure GetLastLineNo(): Integer
var
SalesLine: Record "Sales Line";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,15 @@ tableextension 8054 "Sales Line" extends "Sales Line"
Rec."Sell-to Customer No." := SourceSalesHeader."Sell-to Customer No.";
end;

internal procedure DeleteSalesServiceCommitment()
/// <summary>
/// Deletes the Sales Subscription Lines that belong to this Sales Line.
/// Call this whenever a Sales Line is removed without running its triggers (Delete(false), DeleteAll(false)),
/// because the OnDelete() trigger of this table extension does not fire in that case and the
/// Sales Subscription Lines would be left behind as orphaned records.
/// Temporary records and document types that cannot carry Sales Subscription Lines are skipped,
/// so the call is safe for any Sales Line.
/// </summary>
procedure DeleteSalesServiceCommitment()
var
SalesServiceCommitment: Record "Sales Subscription Line";
begin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ codeunit 139915 "Sales Service Commitment Test"
NotCreatedProperlyErr: Label 'Subscription Lines are not created properly.', Locked = true;
SalesServiceCommitmentCannotBeDeletedErr: Label 'The Sales Subscription Line cannot be deleted, because it is the last line with Process Contract Renewal. Please delete the Sales line in order to delete the Sales Subscription Line.', Locked = true;
NaturalNumberRatioErr: Label 'The ratio of ''%1'' and ''%2'' or vice versa must give a natural number.', Comment = '%1=Field Caption, %2=Field Caption', Locked = true;
SalesOrderNotDeletedErr: Label 'The Sales Order was not deleted by the Delete Invoiced Sales Orders batch job.', Locked = true;
BlanketSalesOrderNotDeletedErr: Label 'The Blanket Sales Order was not deleted by the Delete Invoiced Blanket Sales Orders batch job.', Locked = true;

#region Tests

Expand Down Expand Up @@ -2109,10 +2111,180 @@ codeunit 139915 "Sales Service Commitment Test"
ServiceCommitment.TestField("Subscription Line End Date", 0D);
end;

[Test]
[TransactionModel(TransactionModel::AutoCommit)]
procedure CheckDeleteSalesServiceCommitmentOnDeleteInvoicedSalesOrders()
var
FetchSalesHeader: Record "Sales Header";
OtherSalesHeader: Record "Sales Header";
OtherSalesLine: Record "Sales Line";
SalesOrderNo: Code[20];
begin
// [SCENARIO] Report "Delete Invoiced Sales Orders" deletes the Sales Subscription Lines of the removed Sales Order
Initialize();

// [GIVEN] A Sales Order with a Subscription Item that has Sales Subscription Lines
ContractTestLibrary.SetupSalesServiceCommitmentItemAndAssignToServiceCommitmentPackage(Item, Enum::"Item Service Commitment Type"::"Service Commitment Item", ServiceCommitmentPackage.Code);
LibrarySales.CreateSalesHeader(SalesHeader, SalesHeader."Document Type"::Order, '');
LibrarySales.CreateSalesLineWithShipmentDate(SalesLine, SalesHeader, Enum::"Sales Line Type"::Item, Item."No.", WorkDate(), LibraryRandom.RandIntInRange(2, 10));
SalesOrderNo := SalesHeader."No.";
SalesServiceCommitment.FilterOnSalesLine(SalesLine);
Assert.RecordIsNotEmpty(SalesServiceCommitment);

// [GIVEN] A second, untouched Sales Order with Sales Subscription Lines
LibrarySales.CreateSalesHeader(OtherSalesHeader, OtherSalesHeader."Document Type"::Order, '');
LibrarySales.CreateSalesLineWithShipmentDate(OtherSalesLine, OtherSalesHeader, Enum::"Sales Line Type"::Item, Item."No.", WorkDate(), LibraryRandom.RandIntInRange(2, 10));
SalesServiceCommitment.Reset();
SalesServiceCommitment.FilterOnSalesLine(OtherSalesLine);
Assert.RecordIsNotEmpty(SalesServiceCommitment);

// [GIVEN] The Sales Order is fully shipped and invoiced, but not removed by posting
LibrarySales.PostSalesDocument(SalesHeader, true, true);
FetchSalesHeader.Get(SalesHeader."Document Type"::Order, SalesOrderNo);
SalesServiceCommitment.Reset();
SalesServiceCommitment.FilterOnDocument(SalesHeader."Document Type"::Order, SalesOrderNo);
Assert.RecordIsNotEmpty(SalesServiceCommitment);

// [WHEN] Running report "Delete Invoiced Sales Orders" for the Sales Order
RunDeleteInvoicedSalesOrders(SalesOrderNo);

// [THEN] The Sales Order is deleted
Assert.IsFalse(FetchSalesHeader.Get(SalesHeader."Document Type"::Order, SalesOrderNo), SalesOrderNotDeletedErr);

// [THEN] No Sales Subscription Line of the deleted Sales Order is left behind
SalesServiceCommitment.Reset();
SalesServiceCommitment.FilterOnDocument(SalesHeader."Document Type"::Order, SalesOrderNo);
Assert.RecordIsEmpty(SalesServiceCommitment);

// [THEN] The Sales Subscription Lines of the second Sales Order are untouched
SalesServiceCommitment.Reset();
SalesServiceCommitment.FilterOnSalesLine(OtherSalesLine);
Assert.RecordIsNotEmpty(SalesServiceCommitment);
end;

[Test]
[TransactionModel(TransactionModel::AutoCommit)]
procedure CheckDeleteSalesServiceCommitmentOnDeleteInvdBlnktSalesOrders()
var
FetchSalesHeader: Record "Sales Header";
OtherSalesHeader: Record "Sales Header";
OtherSalesLine: Record "Sales Line";
SalesOrder: Record "Sales Header";
SecondSalesLine: Record "Sales Line";
BlanketSalesOrderToOrder: Codeunit "Blanket Sales Order to Order";
BlanketOrderNo: Code[20];
begin
// [SCENARIO] Report "Delete Invd Blnkt Sales Orders" deletes the Sales Subscription Lines of the removed Blanket Sales Order
Initialize();

// [GIVEN] A Blanket Sales Order with two lines that both have Sales Subscription Lines
ContractTestLibrary.SetupSalesServiceCommitmentItemAndAssignToServiceCommitmentPackage(Item, Enum::"Item Service Commitment Type"::"Sales with Service Commitment", ServiceCommitmentPackage.Code);
LibrarySales.CreateSalesHeader(SalesHeader, SalesHeader."Document Type"::"Blanket Order", '');
LibrarySales.CreateSalesLineWithShipmentDate(SalesLine, SalesHeader, Enum::"Sales Line Type"::Item, Item."No.", WorkDate(), LibraryRandom.RandIntInRange(2, 10));
LibrarySales.CreateSalesLineWithShipmentDate(SecondSalesLine, SalesHeader, Enum::"Sales Line Type"::Item, Item."No.", WorkDate(), LibraryRandom.RandIntInRange(2, 10));
BlanketOrderNo := SalesHeader."No.";
SalesServiceCommitment.FilterOnSalesLine(SalesLine);
Assert.RecordIsNotEmpty(SalesServiceCommitment);
SalesServiceCommitment.Reset();
SalesServiceCommitment.FilterOnSalesLine(SecondSalesLine);
Assert.RecordIsNotEmpty(SalesServiceCommitment);

// [GIVEN] A second, untouched Blanket Sales Order with Sales Subscription Lines
LibrarySales.CreateSalesHeader(OtherSalesHeader, OtherSalesHeader."Document Type"::"Blanket Order", '');
LibrarySales.CreateSalesLineWithShipmentDate(OtherSalesLine, OtherSalesHeader, Enum::"Sales Line Type"::Item, Item."No.", WorkDate(), LibraryRandom.RandIntInRange(2, 10));
SalesServiceCommitment.Reset();
SalesServiceCommitment.FilterOnSalesLine(OtherSalesLine);
Assert.RecordIsNotEmpty(SalesServiceCommitment);

// [GIVEN] The Blanket Sales Order is fully converted into a Sales Order, which is then fully shipped and invoiced
Clear(BlanketSalesOrderToOrder);
BlanketSalesOrderToOrder.SetHideValidationDialog(true);
BlanketSalesOrderToOrder.Run(SalesHeader);
BlanketSalesOrderToOrder.GetSalesOrderHeader(SalesOrder);
LibrarySales.PostSalesDocument(SalesOrder, true, true);

FetchSalesHeader.Get(SalesHeader."Document Type"::"Blanket Order", BlanketOrderNo);
SalesServiceCommitment.Reset();
SalesServiceCommitment.FilterOnDocument(SalesHeader."Document Type"::"Blanket Order", BlanketOrderNo);
Assert.RecordIsNotEmpty(SalesServiceCommitment);

// [WHEN] Running report "Delete Invd Blnkt Sales Orders" for the Blanket Sales Order
RunDeleteInvdBlnktSalesOrders(BlanketOrderNo);

// [THEN] The Blanket Sales Order is deleted
Assert.IsFalse(FetchSalesHeader.Get(SalesHeader."Document Type"::"Blanket Order", BlanketOrderNo), BlanketSalesOrderNotDeletedErr);

// [THEN] No Sales Subscription Line of the deleted Blanket Sales Order is left behind
SalesServiceCommitment.Reset();
SalesServiceCommitment.FilterOnDocument(SalesHeader."Document Type"::"Blanket Order", BlanketOrderNo);
Assert.RecordIsEmpty(SalesServiceCommitment);

// [THEN] The Sales Subscription Lines of the second Blanket Sales Order are untouched
SalesServiceCommitment.Reset();
SalesServiceCommitment.FilterOnSalesLine(OtherSalesLine);
Assert.RecordIsNotEmpty(SalesServiceCommitment);
end;

[Test]
procedure CheckDeleteSalesServiceCommitmentOnPostSalesInvoiceCopiedFromSalesOrder()
var
SalesInvoiceHeader2: Record "Sales Header";
CopyDocMgt: Codeunit "Copy Document Mgt.";
SalesInvoiceNo: Code[20];
begin
// [SCENARIO] Posting a Sales Invoice that carries Sales Subscription Lines copied from a Sales Order deletes them
Initialize();

// [GIVEN] A Sales Order with an Item with Subscription Lines
ContractTestLibrary.SetupSalesServiceCommitmentItemAndAssignToServiceCommitmentPackage(Item, Enum::"Item Service Commitment Type"::"Sales with Service Commitment", ServiceCommitmentPackage.Code);
LibrarySales.CreateSalesHeader(SalesHeader, SalesHeader."Document Type"::Order, '');
LibrarySales.CreateSalesLineWithShipmentDate(SalesLine, SalesHeader, Enum::"Sales Line Type"::Item, Item."No.", WorkDate(), LibraryRandom.RandIntInRange(2, 10));

// [GIVEN] A Sales Invoice copied from that Sales Order, which carries over the Sales Subscription Lines
LibrarySales.CreateSalesHeader(SalesInvoiceHeader2, SalesInvoiceHeader2."Document Type"::Invoice, SalesHeader."Sell-to Customer No.");
SalesInvoiceNo := SalesInvoiceHeader2."No.";
CopyDocMgt.CopySalesDoc(Enum::"Sales Document Type From"::Order, SalesHeader."No.", SalesInvoiceHeader2);
SalesServiceCommitment.Reset();
SalesServiceCommitment.FilterOnDocument(SalesInvoiceHeader2."Document Type"::Invoice, SalesInvoiceNo);
Assert.RecordIsNotEmpty(SalesServiceCommitment);

// [WHEN] Posting the Sales Invoice
LibrarySales.PostSalesDocument(SalesInvoiceHeader2, true, true);

// [THEN] No Sales Subscription Line of the posted Sales Invoice is left behind
SalesServiceCommitment.Reset();
SalesServiceCommitment.FilterOnDocument(SalesInvoiceHeader2."Document Type"::Invoice, SalesInvoiceNo);
Assert.RecordIsEmpty(SalesServiceCommitment);
end;

#endregion Tests

#region Procedures

local procedure RunDeleteInvdBlnktSalesOrders(BlanketOrderNo: Code[20])
var
FilterSalesHeader: Record "Sales Header";
DeleteInvdBlnktSalesOrders: Report "Delete Invd Blnkt Sales Orders";
begin
FilterSalesHeader.SetRange("Document Type", FilterSalesHeader."Document Type"::"Blanket Order");
FilterSalesHeader.SetRange("No.", BlanketOrderNo);
DeleteInvdBlnktSalesOrders.SetTableView(FilterSalesHeader);
DeleteInvdBlnktSalesOrders.UseRequestPage(false);
DeleteInvdBlnktSalesOrders.Run();
end;

local procedure RunDeleteInvoicedSalesOrders(SalesOrderNo: Code[20])
var
FilterSalesHeader: Record "Sales Header";
DeleteInvoicedSalesOrders: Report "Delete Invoiced Sales Orders";
begin
FilterSalesHeader.SetRange("Document Type", FilterSalesHeader."Document Type"::Order);
FilterSalesHeader.SetRange("No.", SalesOrderNo);
DeleteInvoicedSalesOrders.SetTableView(FilterSalesHeader);
DeleteInvoicedSalesOrders.UseRequestPage(false);
DeleteInvoicedSalesOrders.Run();
end;

local procedure Initialize()
begin
LibraryTestInitialize.OnTestInitialize(Codeunit::"Sales Service Commitment Test");
Expand Down
Loading