Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Keep current Sequence No. for batch operations until SaveState is called and align GetNextNo events in new facade with GetNextNo events in NoSeriesManagement #572

Merged
merged 5 commits into from
Feb 13, 2024
Merged
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 @@ -59,7 +59,7 @@ codeunit 309 "No. Series - Batch Impl."
var
NoSeries: Codeunit "No. Series";
begin
SetInitialState(TempNoSeriesLine);
SyncGlobalLineWithProvidedLine(TempNoSeriesLine, UsageDate);
exit(NoSeries.PeekNextNo(TempGlobalNoSeriesLine, UsageDate));
end;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -966,6 +966,12 @@ codeunit 396 NoSeriesManagement
begin
end;
#endif
[Obsolete('This is a temporary method for compatibility only. Please use the "No. Series" codeunit instead', '24.0')]
internal procedure RaiseObsoleteOnBeforeModifyNoSeriesLine(var NoSeriesLine: Record "No. Series Line"; var IsHandled: Boolean)
begin
OnBeforeModifyNoSeriesLine(NoSeriesLine, IsHandled);
end;

[IntegrationEvent(false, false)]
internal procedure OnBeforeModifyNoSeriesLine(var NoSeriesLine: Record "No. Series Line"; var IsHandled: Boolean)
begin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,12 @@ table 309 "No. Series Line"
DataClassification = SystemMetadata;

}
field(15; "Temp Current Sequence No."; Integer)
{
Caption = 'Temporary Sequence Number';
DataClassification = SystemMetadata;
Access = Internal;
}
}

keys
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@ codeunit 304 "No. Series - Impl."
NoSeriesRec: Record "No. Series";
NoSeries: Codeunit "No. Series";
NoSeriesErrorsImpl: Codeunit "No. Series - Errors Impl.";
#if not CLEAN24
#pragma warning disable AL0432
NoSeriesManagement: Codeunit NoSeriesManagement;
#pragma warning restore AL0432
#endif
LineFound: Boolean;
begin
if UsageDate = 0D then
Expand All @@ -165,12 +170,26 @@ codeunit 304 "No. Series - Impl."
NoSeriesLine.SetRange(Open, true);
if (NoSeriesLine."Line No." <> 0) and (NoSeriesLine."Series Code" = NoSeriesCode) then begin
NoSeriesLine.SetRange("Line No.", NoSeriesLine."Line No.");
#if not CLEAN24
#pragma warning disable AL0432
NoSeriesManagement.RaiseObsoleteOnNoSeriesLineFilterOnBeforeFindLast(NoSeriesLine);
#pragma warning restore AL0432
#endif
LineFound := NoSeriesLine.FindLast();
if not LineFound then
NoSeriesLine.SetRange("Line No.");
end;
#if not CLEAN24
#pragma warning disable AL0432
if not LineFound then begin
NoSeriesManagement.RaiseObsoleteOnNoSeriesLineFilterOnBeforeFindLast(NoSeriesLine);
LineFound := NoSeriesLine.FindLast();
end;
#pragma warning restore AL0432
#else
if not LineFound then
LineFound := NoSeriesLine.FindLast();
#endif

if LineFound and NoSeries.MayProduceGaps(NoSeriesLine) then begin
NoSeriesLine.Validate(Open);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,7 @@ codeunit 307 "No. Series - Sequence Impl." implements "No. Series - Single"
var
LastSeqNoUsed: BigInteger;
begin
if not TryGetCurrentSequenceNo(NoSeriesLine."Sequence Name", LastSeqNoUsed) then begin
if not NumberSequence.Exists(NoSeriesLine."Sequence Name") then
CreateNewSequence(NoSeriesLine);
TryGetCurrentSequenceNo(NoSeriesLine."Sequence Name", LastSeqNoUsed);
end;
LastSeqNoUsed := GetCurrentSequenceNo(NoSeriesLine);
if LastSeqNoUsed >= NoSeriesLine."Starting Sequence No." then
exit(GetFormattedNo(NoSeriesLine, LastSeqNoUsed));
exit(''); // No. Series has not been used yet, so there is no last no. used
Expand All @@ -46,6 +42,18 @@ codeunit 307 "No. Series - Sequence Impl." implements "No. Series - Single"
exit(true);
end;

local procedure GetCurrentSequenceNo(var NoSeriesLine: Record "No. Series Line") LastSeqNoUsed: BigInteger
begin
if NoSeriesLine."Temp Current Sequence No." <> 0 then
exit(NoSeriesLine."Temp Current Sequence No.");

if not TryGetCurrentSequenceNo(NoSeriesLine."Sequence Name", LastSeqNoUsed) then begin
if not NumberSequence.Exists(NoSeriesLine."Sequence Name") then
CreateNewSequence(NoSeriesLine);
TryGetCurrentSequenceNo(NoSeriesLine."Sequence Name", LastSeqNoUsed);
end;
end;

[TryFunction]
local procedure TryGetCurrentSequenceNo(SequenceName: Code[40]; var LastSeqNoUsed: BigInteger)
begin
Expand All @@ -57,13 +65,28 @@ codeunit 307 "No. Series - Sequence Impl." implements "No. Series - Single"
var
NoSeriesLine2: Record "No. Series Line";
NoSeriesStatelessImpl: Codeunit "No. Series - Stateless Impl.";
#if not CLEAN24
#pragma warning disable AL0432
NoSeriesManagement: Codeunit NoSeriesManagement;
IsHandled: Boolean;
#pragma warning restore AL0432
#endif
NewNo: BigInteger;
begin
if not TryGetNextSequenceNo(NoSeriesLine, ModifySeries, NewNo) then begin
if not NumberSequence.Exists(NoSeriesLine."Sequence Name") then
CreateNewSequence(NoSeriesLine);
TryGetNextSequenceNo(NoSeriesLine, ModifySeries, NewNo);
end;
if NoSeriesLine.IsTemporary() or (NoSeriesLine."Temp Current Sequence No." <> 0) then begin // Do not update the database for temporary records, if Temp Current Sequence No. is set that means we are emulating the next numbers
if NoSeriesLine."Temp Current Sequence No." = 0 then
NoSeriesLine."Temp Current Sequence No." := GetCurrentSequenceNo(NoSeriesLine);

NewNo := NoSeriesLine."Temp Current Sequence No." + NoSeriesLine."Increment-by No.";

if ModifySeries then
NoSeriesLine."Temp Current Sequence No." := NewNo;
end else
if not TryGetNextSequenceNo(NoSeriesLine, ModifySeries, NewNo) then begin
if not NumberSequence.Exists(NoSeriesLine."Sequence Name") then
CreateNewSequence(NoSeriesLine);
TryGetNextSequenceNo(NoSeriesLine, ModifySeries, NewNo);
end;

NoSeriesLine2 := NoSeriesLine;
NoSeriesLine2."Last No. Used" := GetFormattedNo(NoSeriesLine, NewNo);
Expand All @@ -72,9 +95,15 @@ codeunit 307 "No. Series - Sequence Impl." implements "No. Series - Single"
if not NoSeriesStatelessImpl.EnsureLastNoUsedIsWithinValidRange(NoSeriesLine2, HideErrorsAndWarnings) then
exit('');

if ModifySeries and ((NoSeriesLine."Last Date Used" <> UsageDate) or (NoSeriesLine.Open <> NoSeriesLine2.Open)) then begin // Only modify the series if either the date or the open status has changed. Otherwise avoid locking the record.
if ModifySeries and ((NoSeriesLine."Last Date Used" <> UsageDate) or (NoSeriesLine.Open <> NoSeriesLine2.Open) or NoSeriesLine.IsTemporary()) then begin // Only modify the series if either the date or the open status has changed. Otherwise avoid locking the record.
NoSeriesLine."Last Date Used" := UsageDate;
NoSeriesLine.Open := NoSeriesLine2.Open;
#if not CLEAN24
#pragma warning disable AL0432
NoSeriesManagement.RaiseObsoleteOnBeforeModifyNoSeriesLine(NoSeriesLine, IsHandled);
if not IsHandled then
#pragma warning restore AL0432
#endif
NoSeriesLine.Modify(true);
end;

Expand Down Expand Up @@ -268,4 +297,33 @@ codeunit 307 "No. Series - Sequence Impl." implements "No. Series - Single"
if NumberSequence.Exists(Rec."Sequence Name") then
NumberSequence.Delete(Rec."Sequence Name");
end;

[EventSubscriber(ObjectType::Table, Database::"No. Series Line", 'OnBeforeModifyEvent', '', false, false)]
local procedure OnModifyNoSeriesLine(var Rec: Record "No. Series Line"; RunTrigger: Boolean)
begin
if Rec.IsTemporary() then
exit;

EnsureTempCurrentSequenceNoIsReset(Rec);
end;

[EventSubscriber(ObjectType::Table, Database::"No. Series Line", 'OnBeforeInsertEvent', '', false, false)]
local procedure OnInsertNoSeriesLine(var Rec: Record "No. Series Line"; RunTrigger: Boolean)
begin
if Rec.IsTemporary() then
exit;

EnsureTempCurrentSequenceNoIsReset(Rec);
end;

local procedure EnsureTempCurrentSequenceNoIsReset(var NoSeriesLine: Record "No. Series Line")
begin
if NoSeriesLine."Temp Current Sequence No." = 0 then
exit;

if NoSeriesLine.Implementation = "No. Series Implementation"::Sequence then
RecreateNoSeriesWithLastUsedNo(NoSeriesLine, NoSeriesLine."Temp Current Sequence No.");

NoSeriesLine."Temp Current Sequence No." := 0; // Always reset the temporary sequence number!
end;
}
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,7 @@ codeunit 134531 "No. Series Batch Tests"
NoSeriesBatch: Codeunit "No. Series - Batch";
PermissionsMock: Codeunit "Permissions Mock";
NoSeriesBatch2: Codeunit "No. Series - Batch";
NoSeriesBatch3: Codeunit "No. Series - Batch";
NoSeriesCode: Code[20];
i: Integer;
begin
Expand Down Expand Up @@ -601,10 +602,17 @@ codeunit 134531 "No. Series Batch Tests"
LibraryAssert.AreEqual('B3', NoSeriesBatch.GetNextNo(NoSeriesCode, WorkDate()), 'Getting Next No. should continue the simulation');

// [WHEN] We get the next number from the No. Series using a different batch
// [THEN] The numbers from line 1 are exhausted so we continue from line 2
LibraryAssert.AreEqual('B4', NoSeriesBatch2.GetNextNo(NoSeriesCode, WorkDate()), 'No numbers from the sequence should have been used');
LibraryAssert.AreEqual('B5', NoSeriesBatch2.GetNextNo(NoSeriesCode, WorkDate()), 'No numbers from the sequence should have been used');
LibraryAssert.AreEqual('B6', NoSeriesBatch2.GetNextNo(NoSeriesCode, WorkDate()), 'No numbers from the sequence should have been used');
// [THEN] The numbers start again from A1
LibraryAssert.AreEqual('A1', NoSeriesBatch2.GetNextNo(NoSeriesCode, WorkDate()), 'No numbers from the sequence should have been used');
LibraryAssert.AreEqual('A2', NoSeriesBatch2.GetNextNo(NoSeriesCode, WorkDate()), 'No numbers from the sequence should have been used');
LibraryAssert.AreEqual('A3', NoSeriesBatch2.GetNextNo(NoSeriesCode, WorkDate()), 'No numbers from the sequence should have been used');

// [WHEN] We save the original batch
NoSeriesBatch.SaveState();
// [THEN] The numbers from another batch will continue from line 2
LibraryAssert.AreEqual('B4', NoSeriesBatch3.GetNextNo(NoSeriesCode, WorkDate()), 'No numbers from the sequence should have been used');
LibraryAssert.AreEqual('B5', NoSeriesBatch3.GetNextNo(NoSeriesCode, WorkDate()), 'No numbers from the sequence should have been used');
LibraryAssert.AreEqual('B6', NoSeriesBatch3.GetNextNo(NoSeriesCode, WorkDate()), 'No numbers from the sequence should have been used');
end;

[Test]
Expand Down Expand Up @@ -735,9 +743,9 @@ codeunit 134531 "No. Series Batch Tests"
// [THEN] No number is returned
LibraryAssert.AreEqual('', NoSeriesBatch.GetNextNo(NoSeriesCode, WorkDate(), true), 'A number was returned even though the sequence has run out');

// [THEN] GetLastNoUsed returns blank, however new batch references will return A9 until save since the Line is not yet closed but the sequence is updated in the database.
// [THEN] GetLastNoUsed returns A9, however new batch references will return blank since no number has been saved.
LibraryAssert.AreEqual('A9', NoSeriesBatch.GetLastNoUsed(NoSeriesLine), 'GetLastNoUsed Number was not as expected after getting invalid number');
LibraryAssert.AreEqual('A9', NoSeriesBatch2.GetLastNoUsed(NoSeriesLine), 'GetLastNoUsed Number was not as expected');
LibraryAssert.AreEqual('', NoSeriesBatch2.GetLastNoUsed(NoSeriesLine), 'GetLastNoUsed Number was not as expected');

// [GIVEN] The No. Series is saved
NoSeriesBatch.SaveState();
Expand Down Expand Up @@ -859,6 +867,58 @@ codeunit 134531 "No. Series Batch Tests"
end;
#endregion

[Test]
procedure TestSequenceTempCurrentSequenceNoField()
var
NoSeriesLine: Record "No. Series Line";
TempNoSeriesLine: Record "No. Series Line" temporary;
NoSeriesBatch: Codeunit "No. Series - Batch";
NoSeriesBatch2: Codeunit "No. Series - Batch";
PermissionsMock: Codeunit "Permissions Mock";
RecordRef: RecordRef;
TempCurrentSequenceNoField: FieldRef;
NoSeriesCode: Code[20];
begin
// [Scenario] Make sure the Temp Current Sequence No. field cannot be abused and is always set to 0 upon database modify
// Note: These scenarios are not supported, this is simply to make sure the Temp Current Sequence No. field works as expected behind the scenes.

Initialize();
PermissionsMock.Set('No. Series - Admin');

// [GIVEN] A No. Series with 10 numbers
NoSeriesCode := CopyStr(UpperCase(Any.AlphabeticText(MaxStrLen(NoSeriesCode))), 1, MaxStrLen(NoSeriesCode));
LibraryNoSeries.CreateNoSeries(NoSeriesCode);
LibraryNoSeries.CreateSequenceNoSeriesLine(NoSeriesCode, 1, '1', '9');
NoSeriesLine.SetRange("Series Code", NoSeriesCode);
NoSeriesLine.FindFirst();
TempNoSeriesLine := NoSeriesLine;
TempNoSeriesLine.Insert();

// [GIVEN] A No. Series Line and Temporary No. Series Line with Current Sequence No. set to 5
PermissionsMock.SetExactPermissionSet('No. Series Test');
RecordRef.GetTable(NoSeriesLine);
TempCurrentSequenceNoField := RecordRef.Field(15); // Field "Temp Current Sequence No."
TempCurrentSequenceNoField.Value := 5;
RecordRef.SetTable(NoSeriesLine);
TempNoSeriesLine := NoSeriesLine;

// [WHEN] Fetching the Last No. Used, both implementations return blank (batch will fetch the correct line from the database which does not contain the Temp Current Sequence No.)
LibraryAssert.AreEqual('', NoSeriesBatch.GetLastNoUsed(NoSeriesLine), 'GetLastNoUsed returned wrong value');
LibraryAssert.AreEqual('', NoSeriesBatch2.GetLastNoUsed(TempNoSeriesLine), 'GetLastNoUsed with temporary record returned wrong value');

// [WHEN] We peek the next number from both No. Series, they both return 1 since no number has been used
LibraryAssert.AreEqual('1', NoSeriesBatch.PeekNextNo(NoSeriesLine, WorkDate()), 'PeekNextNo returned wrong value');
LibraryAssert.AreEqual('1', NoSeriesBatch2.PeekNextNo(TempNoSeriesLine, WorkDate()), 'PeekNextNo with temporary record returned wrong value');

// [WHEN] We get the next number from both No. Series, they both return 1
LibraryAssert.AreEqual('1', NoSeriesBatch.GetNextNo(NoSeriesLine, WorkDate()), 'GetNextNo returned wrong value');
LibraryAssert.AreEqual('1', NoSeriesBatch2.GetNextNo(TempNoSeriesLine, WorkDate()), 'GetNextNo with temporary record returned wrong value');

// [THEN] Getting the next number, they again both return 7
LibraryAssert.AreEqual('2', NoSeriesBatch.GetNextNo(NoSeriesLine, WorkDate()), 'GetNextNo returned wrong value');
LibraryAssert.AreEqual('2', NoSeriesBatch2.GetNextNo(TempNoSeriesLine, WorkDate()), 'GetNextNo with temporary record returned wrong value');
end;

local procedure Initialize()
begin
Any.SetDefaultSeed();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,60 @@ codeunit 134530 "No. Series Tests"
end;
#endregion

[Test]
procedure TestSequenceTempCurrentSequenceNoField()
var
NoSeriesLine: Record "No. Series Line";
TempNoSeriesLine: Record "No. Series Line" temporary;
NoSeries: Codeunit "No. Series";
PermissionsMock: Codeunit "Permissions Mock";
RecordRef: RecordRef;
TempCurrentSequenceNoField: FieldRef;
NoSeriesCode: Code[20];
begin
// [Scenario] Make sure the Temp Current Sequence No. field cannot be abused and is always set to 0 upon database modify
// Note: These scenarios are not supported, this is simply to make sure the Temp Current Sequence No. field works as expected behind the scenes.

Initialize();
PermissionsMock.Set('No. Series - Admin');

// [GIVEN] A No. Series with 10 numbers
NoSeriesCode := CopyStr(UpperCase(Any.AlphabeticText(MaxStrLen(NoSeriesCode))), 1, MaxStrLen(NoSeriesCode));
LibraryNoSeries.CreateNoSeries(NoSeriesCode);
LibraryNoSeries.CreateSequenceNoSeriesLine(NoSeriesCode, 1, '1', '9');
NoSeriesLine.SetRange("Series Code", NoSeriesCode);
NoSeriesLine.FindFirst();
TempNoSeriesLine := NoSeriesLine;
TempNoSeriesLine.Insert();

// [GIVEN] A No. Series Line and Temporary No. Series Line with Current Sequence No. set to 5
PermissionsMock.SetExactPermissionSet('No. Series Test');
RecordRef.GetTable(NoSeriesLine);
TempCurrentSequenceNoField := RecordRef.Field(15); // Field "Temp Current Sequence No."
TempCurrentSequenceNoField.Value := 5;
RecordRef.SetTable(NoSeriesLine);
TempNoSeriesLine := NoSeriesLine;

// [WHEN] Fetching the Last No. Used, both implementations return 5
LibraryAssert.AreEqual('5', NoSeries.GetLastNoUsed(NoSeriesLine), 'GetLastNoUsed returned wrong value');
LibraryAssert.AreEqual('5', NoSeries.GetLastNoUsed(TempNoSeriesLine), 'GetLastNoUsed with temporary record returned wrong value');

// [WHEN] We peek the next number from both No. Series, they both return 6
LibraryAssert.AreEqual('6', NoSeries.PeekNextNo(NoSeriesLine, WorkDate()), 'PeekNextNo returned wrong value');
LibraryAssert.AreEqual('6', NoSeries.PeekNextNo(TempNoSeriesLine, WorkDate()), 'PeekNextNo with temporary record returned wrong value');

// [WHEN] We get the next number from both No. Series, they both return 6 since that's the number set to be next number based on Temp Current Sequence No., furthermore the Temp Current Sequence No. in the normal No. Series has been saved into the sequence due to modify on Get
LibraryAssert.AreEqual('6', NoSeries.GetNextNo(NoSeriesLine, WorkDate()), 'GetNextNo returned wrong value');
LibraryAssert.AreEqual('6', NoSeries.GetNextNo(TempNoSeriesLine, WorkDate()), 'GetNextNo with temporary record returned wrong value');
RecordRef.GetTable(NoSeriesLine);
TempCurrentSequenceNoField := RecordRef.Field(15); // Field "Temp Current Sequence No."
LibraryAssert.AreEqual(0, TempCurrentSequenceNoField.Value, 'Temp Current Sequence No. was not as expected');

// [THEN] Getting the next number, they again both return 7
LibraryAssert.AreEqual('7', NoSeries.GetNextNo(NoSeriesLine, WorkDate()), 'GetNextNo returned wrong value');
LibraryAssert.AreEqual('7', NoSeries.GetNextNo(TempNoSeriesLine, WorkDate()), 'GetNextNo with temporary record returned wrong value');
end;

local procedure Initialize()
begin
Any.SetDefaultSeed();
Expand Down
Loading