diff --git a/src/Layers/APAC/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al b/src/Layers/APAC/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al
index 2b7a225aea..ac7900d6fa 100644
--- a/src/Layers/APAC/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al
+++ b/src/Layers/APAC/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al
@@ -2157,6 +2157,24 @@ codeunit 12 "Gen. Jnl.-Post Line"
SequenceNoMgt.ValidateSeqNo(TableNo);
end;
+ ///
+ /// Invalidates the transaction-scoped caches in this codeunit so the next call to RunWithCheck
+ /// is forced through StartPosting (which re-takes the G/L Entry table lock and re-reads the
+ /// last entry number from disk) instead of taking the ContinuePosting fast path with a stale
+ /// NextEntryNo.
+ ///
+ procedure ResetTransactionState()
+ begin
+ NextEntryNo := 0;
+ NextTransactionNo := 0;
+ NextVATEntryNo := 0;
+ FirstEntryNo := 0;
+ FirstNewVATEntryNo := 0;
+ IsGLRegInserted := false;
+ TempGLEntryBuf.Reset();
+ TempGLEntryBuf.DeleteAll();
+ end;
+
///
/// Checks if transaction is balanced for both local and additional currencies, inserts all G/L Entries that were created for the Gen. Journal Line.
/// If posting is performed for application purpose, original Customer and Vendor Ledger Entries are updated to reflect that.
diff --git a/src/Layers/APAC/BaseApp/Inventory/Costing/InventoryAdjustment.Codeunit.al b/src/Layers/APAC/BaseApp/Inventory/Costing/InventoryAdjustment.Codeunit.al
index d941cf125e..d76789243f 100644
--- a/src/Layers/APAC/BaseApp/Inventory/Costing/InventoryAdjustment.Codeunit.al
+++ b/src/Layers/APAC/BaseApp/Inventory/Costing/InventoryAdjustment.Codeunit.al
@@ -84,9 +84,12 @@ codeunit 5895 "Inventory Adjustment" implements "Inventory Adjustment", "Cost Ad
AdjustTillDate: Date;
StartDateTime: DateTime;
MaxDuration: Duration;
+ ItemsSinceLastCommit: Integer;
AutomaticCostAdjustmentTok: Label 'Automatic cost adjustment', Locked = true;
AutomaticCostAdjustmentEnabledTok: Label 'Automatic cost adjustment was used.', Locked = true;
CostAdjustmentReachedMaxDurationErr: Label 'The cost adjustment process has reached the maximum duration of %1.', Comment = '%1=Max Duration';
+ CostAdjmtCommitFeatureNameTok: Label 'Cost Adjustment Item-by-Item Commit', Locked = true;
+ CostAdjmtCommitEventNameTok: Label 'CheckAndCommit invoked', Locked = true;
#pragma warning disable AA0074
Text009: Label 'WIP';
Text010: Label 'Assembly';
@@ -189,6 +192,8 @@ codeunit 5895 "Inventory Adjustment" implements "Inventory Adjustment", "Cost Ad
Clear(ItemJnlPostLine);
ItemJnlPostLine.SetCalledFromAdjustment(true, PostToGL);
+ ItemsSinceLastCommit := 0;
+
InvtSetup.SetLoadFields("Automatic Cost Adjustment");
InvtSetup.Get();
if InvtSetup.AutomaticCostAdjmtRequired() then
@@ -2971,13 +2976,30 @@ codeunit 5895 "Inventory Adjustment" implements "Inventory Adjustment", "Cost Ad
local procedure CheckAndCommit()
begin
- if ItemsBeingAdjusted.Count() = 0 then begin
- if CommitAdjustedItems then
- Commit();
+ ItemsSinceLastCommit += 1;
+ if ItemsBeingAdjusted.Count() > 0 then
+ exit;
- ItemApplicationTrace.Reset();
- ItemApplicationTrace.DeleteAll();
+ if CommitAdjustedItems then begin
+ Commit();
+ ItemJnlPostLine.ResetGLPostingState();
+ EmitCheckAndCommitTelemetry();
+ ItemsSinceLastCommit := 0;
end;
+
+ ItemApplicationTrace.Reset();
+ ItemApplicationTrace.DeleteAll();
+ end;
+
+ local procedure EmitCheckAndCommitTelemetry()
+ var
+ TelemetryDimensions: Dictionary of [Text, Text];
+ begin
+ TelemetryDimensions.Add('ItemsSinceLastCommit', Format(ItemsSinceLastCommit));
+ TelemetryDimensions.Add('IsOnlineAdjmt', Format(IsOnlineAdjmt, 0, 9));
+ TelemetryDimensions.Add('PostToGL', Format(PostToGL, 0, 9));
+ TelemetryDimensions.Add('ItemByItemCommit', Format(CommitAdjustedItems, 0, 9));
+ FeatureTelemetry.LogUsage('0000MEQ', CostAdjmtCommitFeatureNameTok, CostAdjmtCommitEventNameTok, TelemetryDimensions);
end;
local procedure FindLastDate(DateFilter: Text): Date
diff --git a/src/Layers/APAC/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al b/src/Layers/APAC/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al
index 4302a209ae..45f175927c 100644
--- a/src/Layers/APAC/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al
+++ b/src/Layers/APAC/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al
@@ -4977,6 +4977,20 @@ codeunit 22 "Item Jnl.-Post Line"
PostToGL := NewPostToGL;
end;
+ ///
+ /// Resets the transaction-scoped state of the downstream G/L posting codeunits
+ /// (Codeunit "Inventory Posting To G/L" -> Codeunit "Gen. Jnl.-Post Line") so the
+ /// next G/L posting call re-takes the G/L Entry table lock and re-reads the last
+ /// entry number from disk. Must be called by any caller that issues an explicit
+ /// Commit() while keeping a long-lived reference to this codeunit (for example
+ /// Codeunit 5895 "Inventory Adjustment".CheckAndCommit). The configuration set via
+ /// SetCalledFromAdjustment / SetCalledFromInvtPutawayPick / etc. is preserved.
+ ///
+ procedure ResetGLPostingState()
+ begin
+ InventoryPostingToGL.ResetGLPostingState();
+ end;
+
internal procedure GetPostToGL(): Boolean
begin
exit(PostToGL);
diff --git a/src/Layers/BE/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al b/src/Layers/BE/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al
index fc01e1d44d..b5d43e7bde 100644
--- a/src/Layers/BE/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al
+++ b/src/Layers/BE/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al
@@ -2033,6 +2033,24 @@ codeunit 12 "Gen. Jnl.-Post Line"
SequenceNoMgt.ValidateSeqNo(TableNo);
end;
+ ///
+ /// Invalidates the transaction-scoped caches in this codeunit so the next call to RunWithCheck
+ /// is forced through StartPosting (which re-takes the G/L Entry table lock and re-reads the
+ /// last entry number from disk) instead of taking the ContinuePosting fast path with a stale
+ /// NextEntryNo.
+ ///
+ procedure ResetTransactionState()
+ begin
+ NextEntryNo := 0;
+ NextTransactionNo := 0;
+ NextVATEntryNo := 0;
+ FirstEntryNo := 0;
+ FirstNewVATEntryNo := 0;
+ IsGLRegInserted := false;
+ TempGLEntryBuf.Reset();
+ TempGLEntryBuf.DeleteAll();
+ end;
+
///
/// Checks if transaction is balanced for both local and additional currencies, inserts all G/L Entries that were created for the Gen. Journal Line.
/// If posting is performed for application purpose, original Customer and Vendor Ledger Entries are updated to reflect that.
diff --git a/src/Layers/BE/BaseApp/Inventory/Costing/InventoryPostingToGL.Codeunit.al b/src/Layers/BE/BaseApp/Inventory/Costing/InventoryPostingToGL.Codeunit.al
index 39a3e254cf..402110e66d 100644
--- a/src/Layers/BE/BaseApp/Inventory/Costing/InventoryPostingToGL.Codeunit.al
+++ b/src/Layers/BE/BaseApp/Inventory/Costing/InventoryPostingToGL.Codeunit.al
@@ -137,6 +137,18 @@ codeunit 5802 "Inventory Posting To G/L"
NextTransactionNo := GenJnlPostLine.GetNextTransactionNo();
end;
+ ///
+ /// Resets the transaction-scoped state of the underlying Codeunit "Gen. Jnl.-Post Line"
+ /// instance held by this codeunit so the next G/L posting call re-takes the G/L Entry
+ /// table lock and re-reads the last entry number from disk. Must be called by any caller
+ /// that issues an explicit Commit() while keeping a long-lived reference to this codeunit.
+ /// See Codeunit "Gen. Jnl.-Post Line".ResetTransactionState for details.
+ ///
+ procedure ResetGLPostingState()
+ begin
+ GenJnlPostLine.ResetTransactionState();
+ end;
+
procedure BufferInvtPosting(var ValueEntry: Record "Value Entry"): Boolean
var
CostToPost: Decimal;
diff --git a/src/Layers/CH/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al b/src/Layers/CH/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al
index 20826749d9..a2186d6c0e 100644
--- a/src/Layers/CH/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al
+++ b/src/Layers/CH/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al
@@ -2028,6 +2028,24 @@ codeunit 12 "Gen. Jnl.-Post Line"
SequenceNoMgt.ValidateSeqNo(TableNo);
end;
+ ///
+ /// Invalidates the transaction-scoped caches in this codeunit so the next call to RunWithCheck
+ /// is forced through StartPosting (which re-takes the G/L Entry table lock and re-reads the
+ /// last entry number from disk) instead of taking the ContinuePosting fast path with a stale
+ /// NextEntryNo.
+ ///
+ procedure ResetTransactionState()
+ begin
+ NextEntryNo := 0;
+ NextTransactionNo := 0;
+ NextVATEntryNo := 0;
+ FirstEntryNo := 0;
+ FirstNewVATEntryNo := 0;
+ IsGLRegInserted := false;
+ TempGLEntryBuf.Reset();
+ TempGLEntryBuf.DeleteAll();
+ end;
+
///
/// Checks if transaction is balanced for both local and additional currencies, inserts all G/L Entries that were created for the Gen. Journal Line.
/// If posting is performed for application purpose, original Customer and Vendor Ledger Entries are updated to reflect that.
diff --git a/src/Layers/CH/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al b/src/Layers/CH/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al
index cc5905f866..28203de98c 100644
--- a/src/Layers/CH/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al
+++ b/src/Layers/CH/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al
@@ -4949,6 +4949,20 @@ codeunit 22 "Item Jnl.-Post Line"
PostToGL := NewPostToGL;
end;
+ ///
+ /// Resets the transaction-scoped state of the downstream G/L posting codeunits
+ /// (Codeunit "Inventory Posting To G/L" -> Codeunit "Gen. Jnl.-Post Line") so the
+ /// next G/L posting call re-takes the G/L Entry table lock and re-reads the last
+ /// entry number from disk. Must be called by any caller that issues an explicit
+ /// Commit() while keeping a long-lived reference to this codeunit (for example
+ /// Codeunit 5895 "Inventory Adjustment".CheckAndCommit). The configuration set via
+ /// SetCalledFromAdjustment / SetCalledFromInvtPutawayPick / etc. is preserved.
+ ///
+ procedure ResetGLPostingState()
+ begin
+ InventoryPostingToGL.ResetGLPostingState();
+ end;
+
internal procedure GetPostToGL(): Boolean
begin
exit(PostToGL);
diff --git a/src/Layers/ES/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al b/src/Layers/ES/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al
index befcec4b41..e4dbdf0864 100644
--- a/src/Layers/ES/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al
+++ b/src/Layers/ES/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al
@@ -2146,6 +2146,24 @@ codeunit 12 "Gen. Jnl.-Post Line"
exit(NewTransaction);
end;
+ ///
+ /// Invalidates the transaction-scoped caches in this codeunit so the next call to RunWithCheck
+ /// is forced through StartPosting (which re-takes the G/L Entry table lock and re-reads the
+ /// last entry number from disk) instead of taking the ContinuePosting fast path with a stale
+ /// NextEntryNo.
+ ///
+ procedure ResetTransactionState()
+ begin
+ NextEntryNo := 0;
+ NextTransactionNo := 0;
+ NextVATEntryNo := 0;
+ FirstEntryNo := 0;
+ FirstNewVATEntryNo := 0;
+ IsGLRegInserted := false;
+ TempGLEntryBuf.Reset();
+ TempGLEntryBuf.DeleteAll();
+ end;
+
///
/// Checks if transaction is balanced for both local and additional currencies, inserts all G/L Entries that were created for the Gen. Journal Line.
/// If posting is performed for application purpose, original Customer and Vendor Ledger Entries are updated to reflect that.
diff --git a/src/Layers/ES/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al b/src/Layers/ES/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al
index e7eda4fa2f..7cc5231e6f 100644
--- a/src/Layers/ES/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al
+++ b/src/Layers/ES/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al
@@ -4958,6 +4958,20 @@ codeunit 22 "Item Jnl.-Post Line"
PostToGL := NewPostToGL;
end;
+ ///
+ /// Resets the transaction-scoped state of the downstream G/L posting codeunits
+ /// (Codeunit "Inventory Posting To G/L" -> Codeunit "Gen. Jnl.-Post Line") so the
+ /// next G/L posting call re-takes the G/L Entry table lock and re-reads the last
+ /// entry number from disk. Must be called by any caller that issues an explicit
+ /// Commit() while keeping a long-lived reference to this codeunit (for example
+ /// Codeunit 5895 "Inventory Adjustment".CheckAndCommit). The configuration set via
+ /// SetCalledFromAdjustment / SetCalledFromInvtPutawayPick / etc. is preserved.
+ ///
+ procedure ResetGLPostingState()
+ begin
+ InventoryPostingToGL.ResetGLPostingState();
+ end;
+
internal procedure GetPostToGL(): Boolean
begin
exit(PostToGL);
diff --git a/src/Layers/FI/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al b/src/Layers/FI/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al
index fbe8c6eda3..d33b355668 100644
--- a/src/Layers/FI/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al
+++ b/src/Layers/FI/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al
@@ -2018,6 +2018,24 @@ codeunit 12 "Gen. Jnl.-Post Line"
SequenceNoMgt.ValidateSeqNo(TableNo);
end;
+ ///
+ /// Invalidates the transaction-scoped caches in this codeunit so the next call to RunWithCheck
+ /// is forced through StartPosting (which re-takes the G/L Entry table lock and re-reads the
+ /// last entry number from disk) instead of taking the ContinuePosting fast path with a stale
+ /// NextEntryNo.
+ ///
+ procedure ResetTransactionState()
+ begin
+ NextEntryNo := 0;
+ NextTransactionNo := 0;
+ NextVATEntryNo := 0;
+ FirstEntryNo := 0;
+ FirstNewVATEntryNo := 0;
+ IsGLRegInserted := false;
+ TempGLEntryBuf.Reset();
+ TempGLEntryBuf.DeleteAll();
+ end;
+
///
/// Checks if transaction is balanced for both local and additional currencies, inserts all G/L Entries that were created for the Gen. Journal Line.
/// If posting is performed for application purpose, original Customer and Vendor Ledger Entries are updated to reflect that.
diff --git a/src/Layers/FR/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al b/src/Layers/FR/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al
index b666e639cb..640474c6f0 100644
--- a/src/Layers/FR/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al
+++ b/src/Layers/FR/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al
@@ -2027,6 +2027,24 @@ codeunit 12 "Gen. Jnl.-Post Line"
SequenceNoMgt.ValidateSeqNo(TableNo);
end;
+ ///
+ /// Invalidates the transaction-scoped caches in this codeunit so the next call to RunWithCheck
+ /// is forced through StartPosting (which re-takes the G/L Entry table lock and re-reads the
+ /// last entry number from disk) instead of taking the ContinuePosting fast path with a stale
+ /// NextEntryNo.
+ ///
+ procedure ResetTransactionState()
+ begin
+ NextEntryNo := 0;
+ NextTransactionNo := 0;
+ NextVATEntryNo := 0;
+ FirstEntryNo := 0;
+ FirstNewVATEntryNo := 0;
+ IsGLRegInserted := false;
+ TempGLEntryBuf.Reset();
+ TempGLEntryBuf.DeleteAll();
+ end;
+
///
/// Checks if transaction is balanced for both local and additional currencies, inserts all G/L Entries that were created for the Gen. Journal Line.
/// If posting is performed for application purpose, original Customer and Vendor Ledger Entries are updated to reflect that.
diff --git a/src/Layers/IT/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al b/src/Layers/IT/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al
index 4ae77b19be..3c370e6255 100644
--- a/src/Layers/IT/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al
+++ b/src/Layers/IT/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al
@@ -2335,6 +2335,24 @@ codeunit 12 "Gen. Jnl.-Post Line"
SequenceNoMgt.ValidateSeqNo(TableNo);
end;
+ ///
+ /// Invalidates the transaction-scoped caches in this codeunit so the next call to RunWithCheck
+ /// is forced through StartPosting (which re-takes the G/L Entry table lock and re-reads the
+ /// last entry number from disk) instead of taking the ContinuePosting fast path with a stale
+ /// NextEntryNo.
+ ///
+ procedure ResetTransactionState()
+ begin
+ NextEntryNo := 0;
+ NextTransactionNo := 0;
+ NextVATEntryNo := 0;
+ FirstEntryNo := 0;
+ FirstNewVATEntryNo := 0;
+ IsGLRegInserted := false;
+ TempGLEntryBuf.Reset();
+ TempGLEntryBuf.DeleteAll();
+ end;
+
///
/// Checks if transaction is balanced for both local and additional currencies, inserts all G/L Entries that were created for the Gen. Journal Line.
/// If posting is performed for application purpose, original Customer and Vendor Ledger Entries are updated to reflect that.
diff --git a/src/Layers/IT/BaseApp/Inventory/Costing/InventoryPostingToGL.Codeunit.al b/src/Layers/IT/BaseApp/Inventory/Costing/InventoryPostingToGL.Codeunit.al
index ad3dae435d..687481d49c 100644
--- a/src/Layers/IT/BaseApp/Inventory/Costing/InventoryPostingToGL.Codeunit.al
+++ b/src/Layers/IT/BaseApp/Inventory/Costing/InventoryPostingToGL.Codeunit.al
@@ -137,6 +137,18 @@ codeunit 5802 "Inventory Posting To G/L"
NextTransactionNo := GenJnlPostLine.GetNextTransactionNo();
end;
+ ///
+ /// Resets the transaction-scoped state of the underlying Codeunit "Gen. Jnl.-Post Line"
+ /// instance held by this codeunit so the next G/L posting call re-takes the G/L Entry
+ /// table lock and re-reads the last entry number from disk. Must be called by any caller
+ /// that issues an explicit Commit() while keeping a long-lived reference to this codeunit.
+ /// See Codeunit "Gen. Jnl.-Post Line".ResetTransactionState for details.
+ ///
+ procedure ResetGLPostingState()
+ begin
+ GenJnlPostLine.ResetTransactionState();
+ end;
+
procedure BufferInvtPosting(var ValueEntry: Record "Value Entry"): Boolean
var
CostToPost: Decimal;
diff --git a/src/Layers/IT/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al b/src/Layers/IT/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al
index 367f70b51a..b7153c4ea4 100644
--- a/src/Layers/IT/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al
+++ b/src/Layers/IT/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al
@@ -4976,6 +4976,20 @@ codeunit 22 "Item Jnl.-Post Line"
PostToGL := NewPostToGL;
end;
+ ///
+ /// Resets the transaction-scoped state of the downstream G/L posting codeunits
+ /// (Codeunit "Inventory Posting To G/L" -> Codeunit "Gen. Jnl.-Post Line") so the
+ /// next G/L posting call re-takes the G/L Entry table lock and re-reads the last
+ /// entry number from disk. Must be called by any caller that issues an explicit
+ /// Commit() while keeping a long-lived reference to this codeunit (for example
+ /// Codeunit 5895 "Inventory Adjustment".CheckAndCommit). The configuration set via
+ /// SetCalledFromAdjustment / SetCalledFromInvtPutawayPick / etc. is preserved.
+ ///
+ procedure ResetGLPostingState()
+ begin
+ InventoryPostingToGL.ResetGLPostingState();
+ end;
+
internal procedure GetPostToGL(): Boolean
begin
exit(PostToGL);
diff --git a/src/Layers/NA/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al b/src/Layers/NA/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al
index 4c5356aa7a..d93fec1e70 100644
--- a/src/Layers/NA/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al
+++ b/src/Layers/NA/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al
@@ -2123,6 +2123,24 @@ codeunit 12 "Gen. Jnl.-Post Line"
SequenceNoMgt.ValidateSeqNo(TableNo);
end;
+ ///
+ /// Invalidates the transaction-scoped caches in this codeunit so the next call to RunWithCheck
+ /// is forced through StartPosting (which re-takes the G/L Entry table lock and re-reads the
+ /// last entry number from disk) instead of taking the ContinuePosting fast path with a stale
+ /// NextEntryNo.
+ ///
+ procedure ResetTransactionState()
+ begin
+ NextEntryNo := 0;
+ NextTransactionNo := 0;
+ NextVATEntryNo := 0;
+ FirstEntryNo := 0;
+ FirstNewVATEntryNo := 0;
+ IsGLRegInserted := false;
+ TempGLEntryBuf.Reset();
+ TempGLEntryBuf.DeleteAll();
+ end;
+
///
/// Checks if transaction is balanced for both local and additional currencies, inserts all G/L Entries that were created for the Gen. Journal Line.
/// If posting is performed for application purpose, original Customer and Vendor Ledger Entries are updated to reflect that.
diff --git a/src/Layers/NO/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al b/src/Layers/NO/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al
index 67a1b6f901..280d425cb3 100644
--- a/src/Layers/NO/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al
+++ b/src/Layers/NO/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al
@@ -2032,6 +2032,24 @@ codeunit 12 "Gen. Jnl.-Post Line"
SequenceNoMgt.ValidateSeqNo(TableNo);
end;
+ ///
+ /// Invalidates the transaction-scoped caches in this codeunit so the next call to RunWithCheck
+ /// is forced through StartPosting (which re-takes the G/L Entry table lock and re-reads the
+ /// last entry number from disk) instead of taking the ContinuePosting fast path with a stale
+ /// NextEntryNo.
+ ///
+ procedure ResetTransactionState()
+ begin
+ NextEntryNo := 0;
+ NextTransactionNo := 0;
+ NextVATEntryNo := 0;
+ FirstEntryNo := 0;
+ FirstNewVATEntryNo := 0;
+ IsGLRegInserted := false;
+ TempGLEntryBuf.Reset();
+ TempGLEntryBuf.DeleteAll();
+ end;
+
///
/// Checks if transaction is balanced for both local and additional currencies, inserts all G/L Entries that were created for the Gen. Journal Line.
/// If posting is performed for application purpose, original Customer and Vendor Ledger Entries are updated to reflect that.
diff --git a/src/Layers/RU/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al b/src/Layers/RU/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al
index 16a6c109a1..b0ef163bf4 100644
--- a/src/Layers/RU/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al
+++ b/src/Layers/RU/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al
@@ -2450,6 +2450,24 @@ codeunit 12 "Gen. Jnl.-Post Line"
SequenceNoMgt.ValidateSeqNo(TableNo);
end;
+ ///
+ /// Invalidates the transaction-scoped caches in this codeunit so the next call to RunWithCheck
+ /// is forced through StartPosting (which re-takes the G/L Entry table lock and re-reads the
+ /// last entry number from disk) instead of taking the ContinuePosting fast path with a stale
+ /// NextEntryNo.
+ ///
+ procedure ResetTransactionState()
+ begin
+ NextEntryNo := 0;
+ NextTransactionNo := 0;
+ NextVATEntryNo := 0;
+ FirstEntryNo := 0;
+ FirstNewVATEntryNo := 0;
+ IsGLRegInserted := false;
+ TempGLEntryBuf.Reset();
+ TempGLEntryBuf.DeleteAll();
+ end;
+
///
/// Checks if transaction is balanced for both local and additional currencies, inserts all G/L Entries that were created for the Gen. Journal Line.
/// If posting is performed for application purpose, original Customer and Vendor Ledger Entries are updated to reflect that.
diff --git a/src/Layers/RU/BaseApp/Inventory/Costing/InventoryAdjustment.Codeunit.al b/src/Layers/RU/BaseApp/Inventory/Costing/InventoryAdjustment.Codeunit.al
index 2616b5643c..2a862ec5cb 100644
--- a/src/Layers/RU/BaseApp/Inventory/Costing/InventoryAdjustment.Codeunit.al
+++ b/src/Layers/RU/BaseApp/Inventory/Costing/InventoryAdjustment.Codeunit.al
@@ -86,9 +86,12 @@ codeunit 5895 "Inventory Adjustment" implements "Inventory Adjustment", "Cost Ad
AdjustTillDate: Date;
StartDateTime: DateTime;
MaxDuration: Duration;
+ ItemsSinceLastCommit: Integer;
AutomaticCostAdjustmentTok: Label 'Automatic cost adjustment', Locked = true;
AutomaticCostAdjustmentEnabledTok: Label 'Automatic cost adjustment was used.', Locked = true;
CostAdjustmentReachedMaxDurationErr: Label 'The cost adjustment process has reached the maximum duration of %1.', Comment = '%1=Max Duration';
+ CostAdjmtCommitFeatureNameTok: Label 'Cost Adjustment Item-by-Item Commit', Locked = true;
+ CostAdjmtCommitEventNameTok: Label 'CheckAndCommit invoked', Locked = true;
#pragma warning disable AA0074
Text009: Label 'WIP';
#pragma warning restore AA0074
@@ -221,6 +224,8 @@ codeunit 5895 "Inventory Adjustment" implements "Inventory Adjustment", "Cost Ad
Clear(ItemJnlPostLine);
ItemJnlPostLine.SetCalledFromAdjustment(true, PostToGL);
+ ItemsSinceLastCommit := 0;
+
InvtSetup.SetLoadFields("Automatic Cost Adjustment");
InvtSetup.Get();
if InvtSetup.AutomaticCostAdjmtRequired() then
@@ -3045,13 +3050,30 @@ codeunit 5895 "Inventory Adjustment" implements "Inventory Adjustment", "Cost Ad
local procedure CheckAndCommit()
begin
- if ItemsBeingAdjusted.Count() = 0 then begin
- if CommitAdjustedItems then
- Commit();
+ ItemsSinceLastCommit += 1;
+ if ItemsBeingAdjusted.Count() > 0 then
+ exit;
- ItemApplicationTrace.Reset();
- ItemApplicationTrace.DeleteAll();
+ if CommitAdjustedItems then begin
+ Commit();
+ ItemJnlPostLine.ResetGLPostingState();
+ EmitCheckAndCommitTelemetry();
+ ItemsSinceLastCommit := 0;
end;
+
+ ItemApplicationTrace.Reset();
+ ItemApplicationTrace.DeleteAll();
+ end;
+
+ local procedure EmitCheckAndCommitTelemetry()
+ var
+ TelemetryDimensions: Dictionary of [Text, Text];
+ begin
+ TelemetryDimensions.Add('ItemsSinceLastCommit', Format(ItemsSinceLastCommit));
+ TelemetryDimensions.Add('IsOnlineAdjmt', Format(IsOnlineAdjmt, 0, 9));
+ TelemetryDimensions.Add('PostToGL', Format(PostToGL, 0, 9));
+ TelemetryDimensions.Add('ItemByItemCommit', Format(CommitAdjustedItems, 0, 9));
+ FeatureTelemetry.LogUsage('0000MEQ', CostAdjmtCommitFeatureNameTok, CostAdjmtCommitEventNameTok, TelemetryDimensions);
end;
local procedure FindLastDate(DateFilter: Text): Date
diff --git a/src/Layers/RU/BaseApp/Inventory/Costing/InventoryPostingToGL.Codeunit.al b/src/Layers/RU/BaseApp/Inventory/Costing/InventoryPostingToGL.Codeunit.al
index b761fb7484..8e5f10e231 100644
--- a/src/Layers/RU/BaseApp/Inventory/Costing/InventoryPostingToGL.Codeunit.al
+++ b/src/Layers/RU/BaseApp/Inventory/Costing/InventoryPostingToGL.Codeunit.al
@@ -160,6 +160,18 @@ codeunit 5802 "Inventory Posting To G/L"
NextTransactionNo := GenJnlPostLine.GetNextTransactionNo();
end;
+ ///
+ /// Resets the transaction-scoped state of the underlying Codeunit "Gen. Jnl.-Post Line"
+ /// instance held by this codeunit so the next G/L posting call re-takes the G/L Entry
+ /// table lock and re-reads the last entry number from disk. Must be called by any caller
+ /// that issues an explicit Commit() while keeping a long-lived reference to this codeunit.
+ /// See Codeunit "Gen. Jnl.-Post Line".ResetTransactionState for details.
+ ///
+ procedure ResetGLPostingState()
+ begin
+ GenJnlPostLine.ResetTransactionState();
+ end;
+
procedure BufferInvtPosting(var ValueEntry: Record "Value Entry"): Boolean
var
CostToPost: Decimal;
diff --git a/src/Layers/RU/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al b/src/Layers/RU/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al
index dcd14584c0..672ad1dd34 100644
--- a/src/Layers/RU/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al
+++ b/src/Layers/RU/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al
@@ -4992,6 +4992,20 @@ codeunit 22 "Item Jnl.-Post Line"
PostToGL := NewPostToGL;
end;
+ ///
+ /// Resets the transaction-scoped state of the downstream G/L posting codeunits
+ /// (Codeunit "Inventory Posting To G/L" -> Codeunit "Gen. Jnl.-Post Line") so the
+ /// next G/L posting call re-takes the G/L Entry table lock and re-reads the last
+ /// entry number from disk. Must be called by any caller that issues an explicit
+ /// Commit() while keeping a long-lived reference to this codeunit (for example
+ /// Codeunit 5895 "Inventory Adjustment".CheckAndCommit). The configuration set via
+ /// SetCalledFromAdjustment / SetCalledFromInvtPutawayPick / etc. is preserved.
+ ///
+ procedure ResetGLPostingState()
+ begin
+ InventoryPostingToGL.ResetGLPostingState();
+ end;
+
internal procedure GetPostToGL(): Boolean
begin
exit(PostToGL);
diff --git a/src/Layers/W1/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al b/src/Layers/W1/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al
index 85589d60ea..d9bb0f53c9 100644
--- a/src/Layers/W1/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al
+++ b/src/Layers/W1/BaseApp/Finance/GeneralLedger/Posting/GenJnlPostLine.Codeunit.al
@@ -2004,6 +2004,24 @@ codeunit 12 "Gen. Jnl.-Post Line"
SequenceNoMgt.ValidateSeqNo(TableNo);
end;
+ ///
+ /// Invalidates the transaction-scoped caches in this codeunit so the next call to RunWithCheck
+ /// is forced through StartPosting (which re-takes the G/L Entry table lock and re-reads the
+ /// last entry number from disk) instead of taking the ContinuePosting fast path with a stale
+ /// NextEntryNo.
+ ///
+ procedure ResetTransactionState()
+ begin
+ NextEntryNo := 0;
+ NextTransactionNo := 0;
+ NextVATEntryNo := 0;
+ FirstEntryNo := 0;
+ FirstNewVATEntryNo := 0;
+ IsGLRegInserted := false;
+ TempGLEntryBuf.Reset();
+ TempGLEntryBuf.DeleteAll();
+ end;
+
///
/// Checks if transaction is balanced for both local and additional currencies, inserts all G/L Entries that were created for the Gen. Journal Line.
/// If posting is performed for application purpose, original Customer and Vendor Ledger Entries are updated to reflect that.
diff --git a/src/Layers/W1/BaseApp/Inventory/Costing/InventoryAdjustment.Codeunit.al b/src/Layers/W1/BaseApp/Inventory/Costing/InventoryAdjustment.Codeunit.al
index d2359f1fd6..2609a3dc52 100644
--- a/src/Layers/W1/BaseApp/Inventory/Costing/InventoryAdjustment.Codeunit.al
+++ b/src/Layers/W1/BaseApp/Inventory/Costing/InventoryAdjustment.Codeunit.al
@@ -84,9 +84,12 @@ codeunit 5895 "Inventory Adjustment" implements "Inventory Adjustment", "Cost Ad
AdjustTillDate: Date;
StartDateTime: DateTime;
MaxDuration: Duration;
+ ItemsSinceLastCommit: Integer;
AutomaticCostAdjustmentTok: Label 'Automatic cost adjustment', Locked = true;
AutomaticCostAdjustmentEnabledTok: Label 'Automatic cost adjustment was used.', Locked = true;
CostAdjustmentReachedMaxDurationErr: Label 'The cost adjustment process has reached the maximum duration of %1.', Comment = '%1=Max Duration';
+ CostAdjmtCommitFeatureNameTok: Label 'Cost Adjustment Item-by-Item Commit', Locked = true;
+ CostAdjmtCommitEventNameTok: Label 'CheckAndCommit invoked', Locked = true;
#pragma warning disable AA0074
Text009: Label 'WIP';
Text010: Label 'Assembly';
@@ -189,6 +192,8 @@ codeunit 5895 "Inventory Adjustment" implements "Inventory Adjustment", "Cost Ad
Clear(ItemJnlPostLine);
ItemJnlPostLine.SetCalledFromAdjustment(true, PostToGL);
+ ItemsSinceLastCommit := 0;
+
InvtSetup.SetLoadFields("Automatic Cost Adjustment");
InvtSetup.Get();
if InvtSetup.AutomaticCostAdjmtRequired() then
@@ -2967,13 +2972,30 @@ codeunit 5895 "Inventory Adjustment" implements "Inventory Adjustment", "Cost Ad
local procedure CheckAndCommit()
begin
- if ItemsBeingAdjusted.Count() = 0 then begin
- if CommitAdjustedItems then
- Commit();
+ ItemsSinceLastCommit += 1;
+ if ItemsBeingAdjusted.Count() > 0 then
+ exit;
- ItemApplicationTrace.Reset();
- ItemApplicationTrace.DeleteAll();
+ if CommitAdjustedItems then begin
+ Commit();
+ ItemJnlPostLine.ResetGLPostingState();
+ EmitCheckAndCommitTelemetry();
+ ItemsSinceLastCommit := 0;
end;
+
+ ItemApplicationTrace.Reset();
+ ItemApplicationTrace.DeleteAll();
+ end;
+
+ local procedure EmitCheckAndCommitTelemetry()
+ var
+ TelemetryDimensions: Dictionary of [Text, Text];
+ begin
+ TelemetryDimensions.Add('ItemsSinceLastCommit', Format(ItemsSinceLastCommit));
+ TelemetryDimensions.Add('IsOnlineAdjmt', Format(IsOnlineAdjmt, 0, 9));
+ TelemetryDimensions.Add('PostToGL', Format(PostToGL, 0, 9));
+ TelemetryDimensions.Add('ItemByItemCommit', Format(CommitAdjustedItems, 0, 9));
+ FeatureTelemetry.LogUsage('0000MEQ', CostAdjmtCommitFeatureNameTok, CostAdjmtCommitEventNameTok, TelemetryDimensions);
end;
local procedure FindLastDate(DateFilter: Text): Date
diff --git a/src/Layers/W1/BaseApp/Inventory/Costing/InventoryPostingToGL.Codeunit.al b/src/Layers/W1/BaseApp/Inventory/Costing/InventoryPostingToGL.Codeunit.al
index 653dd1fa57..56102ff7ca 100644
--- a/src/Layers/W1/BaseApp/Inventory/Costing/InventoryPostingToGL.Codeunit.al
+++ b/src/Layers/W1/BaseApp/Inventory/Costing/InventoryPostingToGL.Codeunit.al
@@ -137,6 +137,18 @@ codeunit 5802 "Inventory Posting To G/L"
NextTransactionNo := GenJnlPostLine.GetNextTransactionNo();
end;
+ ///
+ /// Resets the transaction-scoped state of the underlying Codeunit "Gen. Jnl.-Post Line"
+ /// instance held by this codeunit so the next G/L posting call re-takes the G/L Entry
+ /// table lock and re-reads the last entry number from disk. Must be called by any caller
+ /// that issues an explicit Commit() while keeping a long-lived reference to this codeunit.
+ /// See Codeunit "Gen. Jnl.-Post Line".ResetTransactionState for details.
+ ///
+ procedure ResetGLPostingState()
+ begin
+ GenJnlPostLine.ResetTransactionState();
+ end;
+
procedure BufferInvtPosting(var ValueEntry: Record "Value Entry"): Boolean
var
CostToPost: Decimal;
diff --git a/src/Layers/W1/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al b/src/Layers/W1/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al
index 3ec23a8f0d..f6e8c398a2 100644
--- a/src/Layers/W1/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al
+++ b/src/Layers/W1/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al
@@ -4945,6 +4945,20 @@ codeunit 22 "Item Jnl.-Post Line"
PostToGL := NewPostToGL;
end;
+ ///
+ /// Resets the transaction-scoped state of the downstream G/L posting codeunits
+ /// (Codeunit "Inventory Posting To G/L" -> Codeunit "Gen. Jnl.-Post Line") so the
+ /// next G/L posting call re-takes the G/L Entry table lock and re-reads the last
+ /// entry number from disk. Must be called by any caller that issues an explicit
+ /// Commit() while keeping a long-lived reference to this codeunit (for example
+ /// Codeunit 5895 "Inventory Adjustment".CheckAndCommit). The configuration set via
+ /// SetCalledFromAdjustment / SetCalledFromInvtPutawayPick / etc. is preserved.
+ ///
+ procedure ResetGLPostingState()
+ begin
+ InventoryPostingToGL.ResetGLPostingState();
+ end;
+
internal procedure GetPostToGL(): Boolean
begin
exit(PostToGL);
diff --git a/src/Layers/W1/Tests/SCM/CostAdjustmentParallelRun.Codeunit.al b/src/Layers/W1/Tests/SCM/CostAdjustmentParallelRun.Codeunit.al
index 108a149740..36dc74161d 100644
--- a/src/Layers/W1/Tests/SCM/CostAdjustmentParallelRun.Codeunit.al
+++ b/src/Layers/W1/Tests/SCM/CostAdjustmentParallelRun.Codeunit.al
@@ -20,7 +20,10 @@ codeunit 137103 "Cost Adjustment Parallel Run"
LibraryManufacturing: Codeunit "Library - Manufacturing";
LibraryAssembly: Codeunit "Library - Assembly";
LibraryWarehouse: Codeunit "Library - Warehouse";
+ Assert: Codeunit Assert;
Initialized: Boolean;
+ TrackGLStartPosting: Boolean;
+ GLStartPostingCount: Integer;
local procedure Initialize()
var
@@ -1293,6 +1296,70 @@ codeunit 137103 "Cost Adjustment Parallel Run"
UnbindSubscription(this);
end;
+ [Test]
+ procedure ItemByItemCommit_GLPostingRestartsAfterEachCommit()
+ var
+ Item1, Item2 : Record Item;
+ FilterItem: Record Item;
+ TempCostAdjustmentParameter: Record "Cost Adjustment Parameter" temporary;
+ InventoryAdjustmentHandler: Codeunit "Inventory Adjustment Handler";
+ CostAdjustmentParamsMgt: Codeunit "Cost Adjustment Params Mgt.";
+ begin
+ // [SCENARIO 637264] Offline cost adjustment with Item-by-Item commit must re-take the
+ // G/L Entry lock per item. After each per-item Commit() the cached NextEntryNo is reset, so the
+ // next item goes through StartPosting (re-lock + re-read) instead of the stale ContinuePosting
+ // fast path - which is what removes the duplicate-key race on table 17 under concurrency.
+ Initialize();
+
+ // [GIVEN] Automatic adjustment off (so the offline run does the work).
+ SetAutomaticCostAdjustment(false);
+
+ // [GIVEN] Two FIFO items with inbound + outbound entries that need cost adjustment.
+ CreateItem(Item1, Item1."Costing Method"::FIFO, 0);
+ CreateItem(Item2, Item2."Costing Method"::FIFO, 0);
+ PostItemJournalLine(Item1."No.", 10, 12.5, WorkDate());
+ PostItemJournalLine(Item1."No.", -10, 0, WorkDate());
+ PostItemJournalLine(Item2."No.", 10, 17.5, WorkDate());
+ PostItemJournalLine(Item2."No.", -10, 0, WorkDate());
+
+ TrackGLStartPosting := true;
+ GLStartPostingCount := 0;
+
+ // [GIVEN] Offline cost adjustment parameters with Item-by-Item commit enabled, posting to G/L.
+ TempCostAdjustmentParameter.Init();
+ TempCostAdjustmentParameter."Online Adjustment" := false;
+ TempCostAdjustmentParameter."Post to G/L" := true;
+ TempCostAdjustmentParameter."Item-By-Item Commit" := true;
+ CostAdjustmentParamsMgt.SetParameters(TempCostAdjustmentParameter);
+
+ FilterItem.SetFilter("No.", '%1|%2', Item1."No.", Item2."No.");
+ InventoryAdjustmentHandler.SetFilterItem(FilterItem);
+
+ // [WHEN] The cost adjustment runs over both items, committing after each.
+ BindSubscription(this);
+ Commit();
+ InventoryAdjustmentHandler.MakeInventoryAdjustment(CostAdjustmentParamsMgt);
+ UnbindSubscription(this);
+ TrackGLStartPosting := false;
+
+ // [THEN] Each adjusted item re-entered StartPosting (NextEntryNo reset to 0) instead of the
+ // stale ContinuePosting fast path, so the lock was re-taken at least once per item.
+ Assert.IsTrue(GLStartPostingCount >= 2, 'CU12 should re-take the G/L lock after each per-item commit.');
+
+ // [THEN] Both items are adjusted without a duplicate-key error.
+ Item1.Get(Item1."No.");
+ Item1.TestField("Cost is Adjusted", true);
+ Item2.Get(Item2."No.");
+ Item2.TestField("Cost is Adjusted", true);
+ end;
+
+ [EventSubscriber(ObjectType::Codeunit, Codeunit::"Gen. Jnl.-Post Line", OnBeforeStartOrContinuePosting, '', false, false)]
+ local procedure CountStartPostingOnBeforeStartOrContinuePosting(NextEntryNo: Integer)
+ begin
+ if TrackGLStartPosting and (NextEntryNo = 0) then
+ GLStartPostingCount += 1;
+ end;
+
local procedure FindProdOrderLine(var ProdOrderLine: Record "Prod. Order Line"; ProductionOrder: Record "Production Order")
begin
ProdOrderLine.SetRange(Status, ProductionOrder.Status);