diff --git a/src/Apps/W1/Subscription Billing/App/Base/Codeunits/ProgressTracker.Codeunit.al b/src/Apps/W1/Subscription Billing/App/Base/Codeunits/ProgressTracker.Codeunit.al
new file mode 100644
index 0000000000..09e01da5d2
--- /dev/null
+++ b/src/Apps/W1/Subscription Billing/App/Base/Codeunits/ProgressTracker.Codeunit.al
@@ -0,0 +1,143 @@
+namespace Microsoft.SubscriptionBilling;
+
+///
+/// Reusable progress dialog helper for long-running batch processes.
+/// Shows elapsed time, progress (processed/total), estimated time remaining (ETA) and throughput.
+/// All dialog interaction is guarded by GuiAllowed, so callers are safe to use it from background
+/// Job Queue sessions without additional guards.
+///
+codeunit 8035 "Progress Tracker"
+{
+ var
+ Window: Dialog;
+ StartTime: DateTime;
+ LastUpdateTime: DateTime;
+ TotalCount: Integer;
+ LastDetail: Text;
+ IsOpen: Boolean;
+ UpdateIntervalMs: Integer;
+ DialogTxt: Label '#1#################################\Processing #2###############################\Progress #3###############################\Elapsed #4###############################\Est. remaining #5############################\Throughput #6###############################', Comment = '%1 = activity caption, %2 = current item detail, %3 = progress (processed of total), %4 = elapsed time, %5 = estimated time remaining, %6 = throughput';
+ ProgressLbl: Label '%1 of %2 (%3%)', Comment = '%1 = processed count, %2 = total count, %3 = percentage';
+ PerMinuteLbl: Label '%1 / min', Comment = '%1 = number of items processed per minute';
+
+ ///
+ /// Opens the progress dialog (only when GuiAllowed) and starts the elapsed-time clock.
+ ///
+ /// Caption describing the running activity.
+ /// Total number of items to process; used for ETA and percentage. Pass 0 if unknown.
+ procedure StartActivity(ActivityText: Text; NewTotalCount: Integer)
+ begin
+ StartTime := CurrentDateTime();
+ LastUpdateTime := StartTime;
+ TotalCount := NewTotalCount;
+ LastDetail := '';
+ UpdateIntervalMs := 1000; // Redraw the dialog at most once per second.
+ if not GuiAllowed() then
+ exit;
+ Window.Open(DialogTxt);
+ Window.Update(1, ActivityText);
+ IsOpen := true;
+ end;
+
+ ///
+ /// Updates the dialog. Redraws are throttled (at most once per second, plus whenever the detail
+ /// text changes or the last item is reached) to avoid excessive flicker.
+ ///
+ /// Number of items processed so far.
+ /// Detail about the current item (e.g. contract no., partner, step).
+ procedure UpdateProgress(ProcessedCount: Integer; DetailText: Text)
+ begin
+ if not IsOpen then
+ exit;
+ if not ShouldUpdate(ProcessedCount, DetailText) then
+ exit;
+ LastDetail := DetailText;
+ LastUpdateTime := CurrentDateTime();
+ Window.Update(2, DetailText);
+ Window.Update(3, FormatProgress(ProcessedCount));
+ Window.Update(4, FormatDurationText(CurrentDateTime() - StartTime));
+ Window.Update(5, FormatEta(ProcessedCount));
+ Window.Update(6, FormatThroughput(ProcessedCount));
+ end;
+
+ ///
+ /// Closes the progress dialog if it is open.
+ ///
+ procedure Finish()
+ begin
+ if IsOpen then
+ Window.Close();
+ IsOpen := false;
+ end;
+
+ local procedure ShouldUpdate(ProcessedCount: Integer; DetailText: Text): Boolean
+ begin
+ if DetailText <> LastDetail then
+ exit(true);
+ if (TotalCount > 0) and (ProcessedCount >= TotalCount) then
+ exit(true);
+ exit((CurrentDateTime() - LastUpdateTime) >= UpdateIntervalMs);
+ end;
+
+ local procedure FormatProgress(ProcessedCount: Integer): Text
+ var
+ Percentage: Integer;
+ begin
+ if TotalCount <= 0 then
+ exit(Format(ProcessedCount));
+ Percentage := Round(ProcessedCount / TotalCount * 100, 1);
+ exit(StrSubstNo(ProgressLbl, ProcessedCount, TotalCount, Percentage));
+ end;
+
+ local procedure FormatEta(ProcessedCount: Integer): Text
+ var
+ Elapsed: Duration;
+ begin
+ if (ProcessedCount <= 0) or (TotalCount <= 0) or (ProcessedCount >= TotalCount) then
+ exit('-');
+ Elapsed := CurrentDateTime() - StartTime;
+ exit(FormatDurationText(Round(Elapsed / ProcessedCount * (TotalCount - ProcessedCount), 1)));
+ end;
+
+ local procedure FormatThroughput(ProcessedCount: Integer): Text
+ var
+ Elapsed: Duration;
+ PerMinute: Decimal;
+ begin
+ Elapsed := CurrentDateTime() - StartTime;
+ if Elapsed <= 0 then
+ exit('-');
+ PerMinute := Round(ProcessedCount / (Elapsed / 60000), 0.1);
+ exit(StrSubstNo(PerMinuteLbl, Format(PerMinute, 0, '')));
+ end;
+
+ local procedure FormatDurationText(DurationValue: Duration): Text
+ var
+ TotalSeconds: Integer;
+ Days: Integer;
+ Hours: Integer;
+ Minutes: Integer;
+ Seconds: Integer;
+ HH: Text;
+ MM: Text;
+ SS: Text;
+ begin
+ if DurationValue <= 0 then
+ exit('0s');
+ if DurationValue < 1000 then
+ exit('<1s');
+ TotalSeconds := Round(DurationValue / 1000, 1);
+ Days := TotalSeconds div 86400;
+ TotalSeconds := TotalSeconds mod 86400;
+ Hours := TotalSeconds div 3600;
+ TotalSeconds := TotalSeconds mod 3600;
+ Minutes := TotalSeconds div 60;
+ Seconds := TotalSeconds mod 60;
+ HH := PadStr('', 2 - StrLen(Format(Hours)), '0') + Format(Hours);
+ MM := PadStr('', 2 - StrLen(Format(Minutes)), '0') + Format(Minutes);
+ SS := PadStr('', 2 - StrLen(Format(Seconds)), '0') + Format(Seconds);
+ if Days > 0 then
+ exit(Format(Days) + 'd ' + HH + ':' + MM + ':' + SS);
+ exit(HH + ':' + MM + ':' + SS);
+ end;
+}
diff --git a/src/Apps/W1/Subscription Billing/App/Billing/Codeunits/BillingPriceCalcSkip.Codeunit.al b/src/Apps/W1/Subscription Billing/App/Billing/Codeunits/BillingPriceCalcSkip.Codeunit.al
new file mode 100644
index 0000000000..d1bc6d424f
--- /dev/null
+++ b/src/Apps/W1/Subscription Billing/App/Billing/Codeunits/BillingPriceCalcSkip.Codeunit.al
@@ -0,0 +1,42 @@
+namespace Microsoft.SubscriptionBilling;
+
+using Microsoft.Purchases.Document;
+using Microsoft.Sales.Document;
+
+///
+/// Manual-binding helper used by "Create Billing Documents" to skip the redundant unit price / unit cost
+/// engine work that the platform runs while validating the created Sales and Purchase lines. The billing
+/// flow already assigns the known Unit Price and Unit Cost explicitly (the values come from the Billing
+/// Line), so the price-list and cost lookups inside those Validate calls are wasted work on every line.
+/// Only the price/cost calculation is skipped - all other validation side effects (dimensions, VAT,
+/// posting groups, item defaults) still run. Bind this instance only around the line field initialization
+/// via BindSubscription / UnbindSubscription.
+///
+codeunit 8036 "Billing Price Calc. Skip"
+{
+ EventSubscriberInstance = Manual;
+
+ [EventSubscriber(ObjectType::Table, Database::"Sales Line", OnBeforeUpdateUnitPrice, '', false, false)]
+ local procedure SkipSalesUpdateUnitPrice(var SalesLine: Record "Sales Line"; xSalesLine: Record "Sales Line"; CalledByFieldNo: Integer; CurrFieldNo: Integer; var Handled: Boolean)
+ begin
+ Handled := true;
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Sales Line", OnBeforeGetUnitCost, '', false, false)]
+ local procedure SkipSalesGetUnitCost(var SalesLine: Record "Sales Line"; var IsHandled: Boolean)
+ begin
+ IsHandled := true;
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Sales Line", OnBeforeUpdateQuantityFromUOMCode, '', false, false)]
+ local procedure SkipSalesUpdateQuantityFromUOMCode(var SalesLine: Record "Sales Line"; var IsHandled: Boolean)
+ begin
+ IsHandled := true;
+ end;
+
+ [EventSubscriber(ObjectType::Table, Database::"Purchase Line", OnBeforeUpdateUnitCost, '', false, false)]
+ local procedure SkipPurchaseUpdateUnitCost(var PurchaseLine: Record "Purchase Line"; xPurchaseLine: Record "Purchase Line"; CurrentFieldNo: Integer; var IsHandled: Boolean)
+ begin
+ IsHandled := true;
+ end;
+}
diff --git a/src/Apps/W1/Subscription Billing/App/Billing/Codeunits/BillingProposal.Codeunit.al b/src/Apps/W1/Subscription Billing/App/Billing/Codeunits/BillingProposal.Codeunit.al
index 81bfa4d6c0..92b9735f82 100644
--- a/src/Apps/W1/Subscription Billing/App/Billing/Codeunits/BillingProposal.Codeunit.al
+++ b/src/Apps/W1/Subscription Billing/App/Billing/Codeunits/BillingProposal.Codeunit.al
@@ -12,8 +12,11 @@ codeunit 8062 "Billing Proposal"
var
SalesHeaderGlobal: Record "Sales Header";
PurchaseHeaderGlobal: Record "Purchase Header";
+ ProgressTracker: Codeunit "Progress Tracker";
LastContractNo: Code[20];
LastPartnerNo: Code[20];
+ CreatingProposalLbl: Label 'Creating billing proposal...';
+ ContractDetailLbl: Label 'Contract %1', Comment = '%1 = Subscription Contract No.';
BillingToChangeNotAllowedErr: Label 'A change of Billing to field from %1 to %2 for %3 and %4 is not allowed because the Subscription Line has already been calculated up to %5.', Comment = '%1: Old Billing To Date, %2: New Billing To Date, %3: Subscription Contract No., %4: Subscription Contract Line No., %5: Last Billing To Date';
NoBillingDateErr: Label 'Please enter the Billing Date.';
BillingToChangeNotAllowedDocNoExistsErr: Label 'Billing to field is not allowed to change because an unposted invoice or credit memo exists.';
@@ -60,6 +63,87 @@ codeunit 8062 "Billing Proposal"
OnAfterInitTempTable(TempBillingLine, GroupBy);
end;
+ ///
+ /// Rebuilds only the temporary rows of the groups touched by a selection-scoped action (for example Delete
+ /// Billing Line or Change Billing To Date), instead of re-reading every Billing Line as InitTempTable does.
+ /// Each affected group is dropped and rebuilt from the database, so the result is identical to a full rebuild
+ /// for those groups - but the cost scales with the affected groups, not with the whole proposal. GroupKeys holds
+ /// the Subscription Contract No. of each affected group, or the Partner No. when grouping by "Contract Partner".
+ ///
+ internal procedure RefreshGroupsInTempTable(var TempBillingLine: Record "Billing Line" temporary; GroupBy: Enum "Contract Billing Grouping"; GroupKeys: List of [Code[20]])
+ var
+ PageFilterBillingLine: Record "Billing Line";
+ TempPageFilter: Record "Billing Line" temporary;
+ GroupKey: Code[20];
+ NextHeaderEntryNo: Integer;
+ begin
+ if GroupKeys.Count = 0 then
+ exit;
+
+ TempPageFilter.CopyFilters(TempBillingLine); // remember the page view filters so they can be restored
+ PageFilterBillingLine.CopyFilters(TempBillingLine); // page view filters (Partner, User ID) scope the DB read
+ NextHeaderEntryNo := SmallestTempBillingLineEntryNo(TempBillingLine);
+
+ foreach GroupKey in GroupKeys do begin
+ DeleteTempRowsForGroup(TempBillingLine, GroupBy, GroupKey);
+ RebuildTempRowsForGroup(TempBillingLine, PageFilterBillingLine, GroupBy, GroupKey, NextHeaderEntryNo);
+ end;
+
+ TempBillingLine.CopyFilters(TempPageFilter);
+ end;
+
+ local procedure SmallestTempBillingLineEntryNo(var TempBillingLine: Record "Billing Line" temporary): Integer
+ begin
+ // Group header rows use descending negative Entry No.; continue below the current minimum to avoid collisions.
+ TempBillingLine.Reset();
+ if TempBillingLine.FindFirst() then // primary key ascending -> smallest Entry No. is first
+ if TempBillingLine."Entry No." < 0 then
+ exit(TempBillingLine."Entry No.");
+ exit(0);
+ end;
+
+ local procedure DeleteTempRowsForGroup(var TempBillingLine: Record "Billing Line" temporary; GroupBy: Enum "Contract Billing Grouping"; GroupKey: Code[20])
+ begin
+ TempBillingLine.Reset();
+ case GroupBy of
+ GroupBy::"Contract Partner":
+ TempBillingLine.SetRange("Partner No.", GroupKey);
+ else
+ TempBillingLine.SetRange("Subscription Contract No.", GroupKey);
+ end;
+ TempBillingLine.DeleteAll(false);
+ TempBillingLine.Reset();
+ end;
+
+ local procedure RebuildTempRowsForGroup(var TempBillingLine: Record "Billing Line" temporary; var PageFilterBillingLine: Record "Billing Line"; GroupBy: Enum "Contract Billing Grouping"; GroupKey: Code[20]; var NextHeaderEntryNo: Integer)
+ var
+ BillingLine: Record "Billing Line";
+ TempGroupBillingLine: Record "Billing Line" temporary;
+ begin
+ BillingLine.CopyFilters(PageFilterBillingLine);
+ SetKeysForGrouping(BillingLine, TempBillingLine, GroupBy); // also resets Last* so the first row opens a new group
+ case GroupBy of
+ GroupBy::"Contract Partner":
+ BillingLine.SetRange("Partner No.", GroupKey);
+ else
+ BillingLine.SetRange("Subscription Contract No.", GroupKey);
+ end;
+ if BillingLine.FindSet() then
+ repeat
+ UpdateGroupingLine(TempGroupBillingLine, BillingLine, GroupBy);
+ TempBillingLine := BillingLine;
+ TempBillingLine.Indent := 1;
+ TempBillingLine.Insert(false);
+ until BillingLine.Next() = 0;
+ if TempGroupBillingLine.FindSet() then
+ repeat
+ TempBillingLine := TempGroupBillingLine;
+ NextHeaderEntryNo -= 1;
+ TempBillingLine."Entry No." := NextHeaderEntryNo;
+ TempBillingLine.Insert(false);
+ until TempGroupBillingLine.Next() = 0;
+ end;
+
local procedure SetKeysForGrouping(var BillingLine: Record "Billing Line"; var TempBillingLine: Record "Billing Line" temporary; GroupBy: Enum "Contract Billing Grouping")
begin
case GroupBy of
@@ -156,8 +240,11 @@ codeunit 8062 "Billing Proposal"
VendorContract: Record "Vendor Subscription Contract";
FilterText: Text;
BillingRhythmFilterText: Text;
+ ContractCounter: Integer;
+ CommitBatchSize: Integer;
begin
SalesHeaderGlobal.Reset();
+ CommitBatchSize := 50; // Contracts processed per transaction checkpoint
BillingTemplate.Get(BillingTemplateCode);
if BillingDate = 0D then
Error(NoBillingDateErr);
@@ -175,24 +262,38 @@ codeunit 8062 "Billing Proposal"
if FilterText <> '' then
CustomerContract.SetView(FilterText);
BillingRhythmFilterText := CustomerContract.GetFilter("Billing Rhythm Filter");
+ this.ProgressTracker.StartActivity(CreatingProposalLbl, CustomerContract.Count());
if CustomerContract.FindSet() then
repeat
+ ContractCounter += 1;
+ this.ProgressTracker.UpdateProgress(ContractCounter, StrSubstNo(ContractDetailLbl, CustomerContract."No."));
ProcessContractServiceCommitments(BillingTemplate, CustomerContract."No.", '', BillingDate, BillingToDate, BillingRhythmFilterText, AutomatedBilling);
+ if ContractCounter mod CommitBatchSize = 0 then
+ Commit(); // Checkpoint every CommitBatchSize contracts: bounds transaction size and releases locks during large runs, without committing per billing line (BCQuality: commit at a checkpoint boundary, not per row). The trailing contracts are committed when the codeunit returns.
until CustomerContract.Next() = 0;
+ this.ProgressTracker.Finish();
end;
"Service Partner"::Vendor:
begin
if FilterText <> '' then
VendorContract.SetView(FilterText);
BillingRhythmFilterText := VendorContract.GetFilter("Billing Rhythm Filter");
+ this.ProgressTracker.StartActivity(CreatingProposalLbl, VendorContract.Count());
if VendorContract.FindSet() then
repeat
+ ContractCounter += 1;
+ this.ProgressTracker.UpdateProgress(ContractCounter, StrSubstNo(ContractDetailLbl, VendorContract."No."));
ProcessContractServiceCommitments(BillingTemplate, VendorContract."No.", '', BillingDate, BillingToDate, BillingRhythmFilterText, AutomatedBilling);
+ if ContractCounter mod CommitBatchSize = 0 then
+ Commit(); // Checkpoint every CommitBatchSize contracts: bounds transaction size and releases locks during large runs, without committing per billing line (BCQuality: commit at a checkpoint boundary, not per row). The trailing contracts are committed when the codeunit returns.
until VendorContract.Next() = 0;
+ this.ProgressTracker.Finish();
end;
end;
- if AutomatedBilling then
+ // The remaining block only surfaces blocking credit memos to the user, so there is nothing to do
+ // on the automated/background path or when no UI is available.
+ if AutomatedBilling or not GuiAllowed() then
exit;
case BillingTemplate.Partner of
@@ -258,7 +359,7 @@ codeunit 8062 "Billing Proposal"
case BillingLine.Partner of
Enum::"Service Partner"::Customer:
if SalesHeaderGlobal.Get(SalesHeaderGlobal."Document Type"::"Credit Memo", BillingLine."Document No.") then
- if AutomatedBilling then
+ if AutomatedBilling or not GuiAllowed() then
ContractBillingErrLog.InsertLogFromSubscriptionLine(
BillingTemplate.Code,
ServiceCommitment,
@@ -267,7 +368,7 @@ codeunit 8062 "Billing Proposal"
SalesHeaderGlobal.Mark(true);
Enum::"Service Partner"::Vendor:
if PurchaseHeaderGlobal.Get(PurchaseHeaderGlobal."Document Type"::"Credit Memo", BillingLine."Document No.") then
- if AutomatedBilling then
+ if AutomatedBilling or not GuiAllowed() then
ContractBillingErrLog.InsertLogFromSubscriptionLine(
BillingTemplate.Code,
ServiceCommitment,
@@ -279,9 +380,9 @@ codeunit 8062 "Billing Proposal"
ServiceCommitment."Usage Based Billing":
begin
UsageDataBilling.Reset();
- UsageDataBilling.SetCurrentKey("Usage Data Import Entry No.", "Subscription Line Entry No.", Partner, "Document Type", "Charge End Date");
UsageDataBilling.FilterOnServiceCommitment(ServiceCommitment);
UsageDataBilling.SetRange("Document Type", "Usage Based Billing Doc. Type"::None);
+ OnProcessSubscriptionLineOnAfterFilterUsageDataBilling(UsageDataBilling, ServiceCommitment);
SkipServiceCommitment := UsageDataBilling.IsEmpty();
end;
else
@@ -447,6 +548,7 @@ codeunit 8062 "Billing Proposal"
case ServiceCommitment.Partner of
ServiceCommitment.Partner::Customer:
begin
+ CustomerContract.SetLoadFields("Sell-to Customer No.", "Detail Overview", "Currency Code");
CustomerContract.Get(ServiceCommitment."Subscription Contract No.");
BillingLine."Partner No." := CustomerContract."Sell-to Customer No.";
BillingLine."Detail Overview" := CustomerContract."Detail Overview";
@@ -454,6 +556,7 @@ codeunit 8062 "Billing Proposal"
end;
ServiceCommitment.Partner::Vendor:
begin
+ VendorContract.SetLoadFields("Pay-to Vendor No.", "Currency Code");
VendorContract.Get(ServiceCommitment."Subscription Contract No.");
BillingLine."Partner No." := VendorContract."Pay-to Vendor No.";
BillingLine."Currency Code" := VendorContract."Currency Code";
@@ -463,6 +566,7 @@ codeunit 8062 "Billing Proposal"
BillingLine."Subscription Contract Line No." := ServiceCommitment."Subscription Contract Line No.";
BillingLine."Discount %" := ServiceCommitment."Discount %";
BillingLine.Discount := ServiceCommitment.Discount;
+ ServiceObject.SetLoadFields(Quantity);
ServiceObject.Get(ServiceCommitment."Subscription Header No.");
BillingLine."Service Object Quantity" := BillingLine.GetSign() * ServiceObject.Quantity;
OnAfterUpdateBillingLineFromSubscriptionLine(BillingLine, ServiceCommitment);
@@ -552,7 +656,9 @@ codeunit 8062 "Billing Proposal"
ClearBillingProposalOptionsTxt: Label 'All billing proposals, Only current billing template proposal';
ClearBillingProposalOptionsMySuggestionsOnlyTxt: Label 'All billing proposals (user %1 only), Only current billing template proposal', Comment = '%1: User ID';
ClearBillingProposalQst: Label 'Which billing proposal(s) should be deleted?';
+ ClearingBillingProposalLbl: Label 'Clearing billing proposal...';
StrMenuResponse: Integer;
+ Counter: Integer;
begin
DisplayErrorIfNotAuthorizedToClearProposalOrDeleteDocuments();
BillingTemplate.Get(BillingTemplateCode);
@@ -570,24 +676,45 @@ codeunit 8062 "Billing Proposal"
BillingLine.SetRange(Partner, BillingTemplate.Partner);
if BillingTemplate."My Suggestions Only" then
BillingLine.SetRange("User ID", UserId());
- BillingLine.DeleteAll(true);
+ this.ProgressTracker.StartActivity(ClearingBillingProposalLbl, BillingLine.Count());
+ if BillingLine.FindSet() then
+ repeat
+ Counter += 1;
+ this.ProgressTracker.UpdateProgress(Counter, BillingLine."Subscription Contract No.");
+ BillingLine.Delete(true);
+ until BillingLine.Next() = 0;
+ this.ProgressTracker.Finish();
end;
2:
begin
BillingLine.SetRange("Billing Template Code", BillingTemplate.Code);
- BillingLine.DeleteAll(true);
+ this.ProgressTracker.StartActivity(ClearingBillingProposalLbl, BillingLine.Count());
+ if BillingLine.FindSet() then
+ repeat
+ Counter += 1;
+ this.ProgressTracker.UpdateProgress(Counter, BillingLine."Subscription Contract No.");
+ BillingLine.Delete(true);
+ until BillingLine.Next() = 0;
+ this.ProgressTracker.Finish();
end;
end;
end;
internal procedure DeleteBillingLines(var BillingLine: Record "Billing Line")
+ var
+ DeletingBillingLinesLbl: Label 'Deleting billing lines...';
+ Counter: Integer;
begin
BillingLine.SetCurrentKey("Subscription Header No.", "Subscription Line Entry No.", "Billing to");
BillingLine.SetAscending("Billing to", false);
+ this.ProgressTracker.StartActivity(DeletingBillingLinesLbl, BillingLine.Count());
if BillingLine.FindSet() then
repeat
- BillingLine.Delete(true)
+ Counter += 1;
+ this.ProgressTracker.UpdateProgress(Counter, BillingLine."Subscription Contract No.");
+ BillingLine.Delete(true);
until BillingLine.Next() = 0;
+ this.ProgressTracker.Finish();
end;
local procedure DeleteBillingLinesForServiceObject(var BillingLine: Record "Billing Line")
@@ -890,17 +1017,22 @@ codeunit 8062 "Billing Proposal"
DeleteBillingDocumentQst: Label 'Which contract billing documents should be deleted?';
DeleteBillingDocumentOptionsTxt: Label 'All Documents,Documents from current billing template only';
DeleteBillingDocumentOptionsMySuggestionsOnlyTxt: Label 'All Documents (user %1 only),Documents from current billing template only', Comment = '%1: User ID';
+ DeletingDocumentsLbl: Label 'Deleting billing documents...';
+ PreviousDocumentNo: Code[20];
StrMenuResponse: Integer;
+ Counter: Integer;
begin
+ PreviousDocumentNo := '';
DisplayErrorIfNotAuthorizedToClearProposalOrDeleteDocuments();
BillingTemplate.Get(BillingTemplateCode);
if BillingTemplate."My Suggestions Only" then
StrMenuResponse := Dialog.StrMenu(StrSubstNo(DeleteBillingDocumentOptionsMySuggestionsOnlyTxt, UserId()), 1, DeleteBillingDocumentQst)
else
StrMenuResponse := Dialog.StrMenu(DeleteBillingDocumentOptionsTxt, 1, DeleteBillingDocumentQst);
- BillingLine.SetLoadFields("Subscription Header No.", "Subscription Line Entry No.", "Billing to", "Document Type", "Document No.", "Partner", "User ID");
- BillingLine.SetCurrentKey("Subscription Header No.", "Subscription Line Entry No.", "Billing to");
- BillingLine.SetAscending("Billing to", false);
+ // Sort by document so same-document billing lines are adjacent; the loop skips duplicate document nos so each
+ // document is deleted exactly once and the progress counter reflects documents, not billing lines.
+ BillingLine.SetLoadFields("Document Type", "Document No.", "Partner", "User ID", "Billing Template Code");
+ BillingLine.SetCurrentKey("Document Type", "Document No.");
BillingLine.SetFilter("Document No.", '<>%1', '');
case StrMenuResponse of
0:
@@ -910,22 +1042,52 @@ codeunit 8062 "Billing Proposal"
BillingLine.SetRange(Partner, BillingTemplate.Partner);
if BillingTemplate."My Suggestions Only" then
BillingLine.SetRange("User ID", UserId());
+ this.ProgressTracker.StartActivity(DeletingDocumentsLbl, this.CountDistinctDocuments(BillingLine));
if BillingLine.FindSet() then
repeat
- DeleteBillingDocuments(BillingLine);
+ if BillingLine."Document No." <> PreviousDocumentNo then begin
+ Counter += 1;
+ this.ProgressTracker.UpdateProgress(Counter, BillingLine."Document No.");
+ this.DeleteBillingDocuments(BillingLine);
+ PreviousDocumentNo := BillingLine."Document No.";
+ end;
until BillingLine.Next() = 0;
+ this.ProgressTracker.Finish();
end;
2:
begin
BillingLine.SetRange("Billing Template Code", BillingTemplate.Code);
+ this.ProgressTracker.StartActivity(DeletingDocumentsLbl, this.CountDistinctDocuments(BillingLine));
if BillingLine.FindSet() then
repeat
- DeleteBillingDocuments(BillingLine);
+ if BillingLine."Document No." <> PreviousDocumentNo then begin
+ Counter += 1;
+ this.ProgressTracker.UpdateProgress(Counter, BillingLine."Document No.");
+ this.DeleteBillingDocuments(BillingLine);
+ PreviousDocumentNo := BillingLine."Document No.";
+ end;
until BillingLine.Next() = 0;
+ this.ProgressTracker.Finish();
end;
end;
end;
+ local procedure CountDistinctDocuments(var BillingLine: Record "Billing Line"): Integer
+ var
+ PrevDocumentNo: Code[20];
+ DocCount: Integer;
+ begin
+ PrevDocumentNo := '';
+ if BillingLine.FindSet() then
+ repeat
+ if BillingLine."Document No." <> PrevDocumentNo then begin
+ DocCount += 1;
+ PrevDocumentNo := BillingLine."Document No.";
+ end;
+ until BillingLine.Next() = 0;
+ exit(DocCount);
+ end;
+
local procedure DeleteBillingDocuments(BillingLine: Record "Billing Line")
var
SalesHeader: Record "Sales Header";
@@ -991,6 +1153,11 @@ codeunit 8062 "Billing Proposal"
begin
end;
+ [IntegrationEvent(false, false)]
+ local procedure OnProcessSubscriptionLineOnAfterFilterUsageDataBilling(var UsageDataBilling: Record "Usage Data Billing"; SubscriptionLine: Record "Subscription Line")
+ begin
+ end;
+
[IntegrationEvent(false, false)]
local procedure OnCreateBillingProposalBeforeApplyFilterToContract(var FilterText: Text; var BillingTemplate: Record "Billing Template"; BillingDate: Date; BillingToDate: Date)
begin
diff --git a/src/Apps/W1/Subscription Billing/App/Billing/Codeunits/CreateBillingDocuments.Codeunit.al b/src/Apps/W1/Subscription Billing/App/Billing/Codeunits/CreateBillingDocuments.Codeunit.al
index f3c64b4829..a83e934988 100644
--- a/src/Apps/W1/Subscription Billing/App/Billing/Codeunits/CreateBillingDocuments.Codeunit.al
+++ b/src/Apps/W1/Subscription Billing/App/Billing/Codeunits/CreateBillingDocuments.Codeunit.al
@@ -34,18 +34,15 @@ codeunit 8060 "Create Billing Documents"
if not RequestPageSelectionConfirmed() then
exit;
- Window.Open(ProgressTxt);
- Window.Update();
if AutomatedBilling then
BillingLine.SetRange("Billing Error Log Entry No.", 0)
else
BillingLine.ModifyAll("Billing Error Log Entry No.", 0);
ProcessBillingLines(BillingLine);
- Window.Close();
if PostDocuments then
PostCreatedDocuments();
- if not HideProcessingFinishedMessage then
+ if (not HideProcessingFinishedMessage) and GuiAllowed() then
ProcessingFinishedMessage();
end;
@@ -53,6 +50,9 @@ codeunit 8060 "Create Billing Documents"
begin
OnBeforeProcessBillingLines(BillingLine, DocumentDate, PostingDate, CustomerRecurringBillingGrouping, VendorRecurringBillingGrouping, PostDocuments);
CreateTempBillingLines(BillingLine);
+ TotalBillingLineCount := TempBillingLine.Count();
+ ProcessedBillingLineCount := 0;
+ this.ProgressTracker.StartActivity(CreatingDocumentsLbl, TotalBillingLineCount);
case BillingLine.Partner of
BillingLine.Partner::Customer:
case CustomerRecurringBillingGrouping of
@@ -71,6 +71,7 @@ codeunit 8060 "Create Billing Documents"
CreatePurchaseDocumentsPerVendor();
end;
end;
+ this.ProgressTracker.Finish();
OnAfterProcessBillingLines(BillingLine);
end;
@@ -89,17 +90,21 @@ codeunit 8060 "Create Billing Documents"
repeat
if IsNewHeaderNeededPerContract(PreviousContractNo) then begin
TestPreviousDocumentTotalInvoiceAmount(true, DiscountLineExists, PreviousContractNo);
+ CommitCreatedDocumentCheckpoint();
+ TrimTempBillingLinesForContract(PreviousContractNo);
CustomerContract.Get(TempBillingLine."Subscription Contract No.");
CreateSalesHeaderFromContract(CustomerContract);
InsertContractDescriptionSalesLines(TempBillingLine);
PreviousContractNo := TempBillingLine."Subscription Contract No.";
ContractsProcessedCount += 1;
- Window.Update(1, CustomerContract."Sell-to Customer No.");
- Window.Update(2, PreviousContractNo);
+ SetProgressPartner(CustomerContract."Sell-to Customer No.");
+ SetProgressContract(PreviousContractNo);
end;
InsertSalesLineFromTempBillingLine();
+ UpdateBillingProgress();
until TempBillingLine.Next() = 0;
TestPreviousDocumentTotalInvoiceAmount(true, DiscountLineExists, PreviousContractNo);
+ CommitCreatedDocumentCheckpoint();
end;
local procedure CreatePurchaseDocumentsPerContract()
@@ -117,17 +122,21 @@ codeunit 8060 "Create Billing Documents"
repeat
if IsNewHeaderNeededPerContract(PreviousContractNo) then begin
TestPreviousDocumentTotalInvoiceAmount(false, DiscountLineExists, PreviousContractNo);
+ CommitCreatedDocumentCheckpoint();
+ TrimTempBillingLinesForContract(PreviousContractNo);
VendorContract.Get(TempBillingLine."Subscription Contract No.");
CreatePurchaseHeaderFromContract(VendorContract);
InsertContractDescriptionPurchaseLines(TempBillingLine);
PreviousContractNo := TempBillingLine."Subscription Contract No.";
ContractsProcessedCount += 1;
- Window.Update(1, VendorContract."Pay-to Vendor No.");
- Window.Update(2, PreviousContractNo);
+ SetProgressPartner(VendorContract."Pay-to Vendor No.");
+ SetProgressContract(PreviousContractNo);
end;
InsertPurchaseLineFromTempBillingLine();
+ UpdateBillingProgress();
until TempBillingLine.Next() = 0;
TestPreviousDocumentTotalInvoiceAmount(false, DiscountLineExists, PreviousContractNo);
+ CommitCreatedDocumentCheckpoint();
end;
local procedure CreateSalesDocumentsPerCustomer()
@@ -149,13 +158,14 @@ codeunit 8060 "Create Billing Documents"
repeat
if IsNewSalesHeaderNeeded(PreviousCustomerNo, LastDetailOverview, PreviousCurrencyCode, PreviousContractNo) then begin
TestPreviousDocumentTotalInvoiceAmount(true, DiscountLineExists, PreviousContractNo);
+ CommitCreatedDocumentCheckpoint();
CreateSalesHeaderForCustomerNo(TempBillingLine."Partner No.");
SalesHeader."Sub. Contract Detail Overview" := TempBillingLine."Detail Overview";
SalesHeader.Modify(false);
PreviousCustomerNo := TempBillingLine."Partner No.";
LastDetailOverview := TempBillingLine."Detail Overview";
PreviousCurrencyCode := TempBillingLine."Currency Code";
- Window.Update(1, PreviousCustomerNo);
+ SetProgressPartner(PreviousCustomerNo);
FirstContractDescriptionLineInserted := false;
end;
if TempBillingLine."Subscription Contract No." <> PreviousContractNo then begin
@@ -166,13 +176,16 @@ codeunit 8060 "Create Billing Documents"
TranslationHelper.RestoreGlobalLanguage();
SalesHeader.Modify(false);
end;
+ TrimTempBillingLinesForContract(PreviousContractNo);
PreviousContractNo := TempBillingLine."Subscription Contract No.";
ContractsProcessedCount += 1;
- Window.Update(2, PreviousContractNo);
+ SetProgressContract(PreviousContractNo);
end;
InsertSalesLineFromTempBillingLine();
+ UpdateBillingProgress();
until TempBillingLine.Next() = 0;
TestPreviousDocumentTotalInvoiceAmount(true, DiscountLineExists, PreviousContractNo);
+ CommitCreatedDocumentCheckpoint();
end;
local procedure CreatePurchaseDocumentsPerVendor()
@@ -195,10 +208,11 @@ codeunit 8060 "Create Billing Documents"
(TempBillingLine."Currency Code" <> PreviousCurrencyCode)
then begin
TestPreviousDocumentTotalInvoiceAmount(false, DiscountLineExists, PreviousContractNo);
+ CommitCreatedDocumentCheckpoint();
CreatePurchaseHeaderForVendorNo(TempBillingLine."Partner No.");
PreviousVendorNo := TempBillingLine."Partner No.";
PreviousCurrencyCode := TempBillingLine."Currency Code";
- Window.Update(1, PreviousVendorNo);
+ SetProgressPartner(PreviousVendorNo);
FirstContractDescriptionLineInserted := false;
end;
if TempBillingLine."Subscription Contract No." <> PreviousContractNo then begin
@@ -209,13 +223,16 @@ codeunit 8060 "Create Billing Documents"
TranslationHelper.RestoreGlobalLanguage();
PurchaseHeader.Modify(false);
end;
+ TrimTempBillingLinesForContract(PreviousContractNo);
PreviousContractNo := TempBillingLine."Subscription Contract No.";
ContractsProcessedCount += 1;
- Window.Update(2, PreviousContractNo);
+ SetProgressContract(PreviousContractNo);
end;
InsertPurchaseLineFromTempBillingLine();
+ UpdateBillingProgress();
until TempBillingLine.Next() = 0;
TestPreviousDocumentTotalInvoiceAmount(false, DiscountLineExists, PreviousContractNo);
+ CommitCreatedDocumentCheckpoint();
end;
local procedure InsertSalesLineFromTempBillingLine()
@@ -237,6 +254,9 @@ codeunit 8060 "Create Billing Documents"
CustomerContractLine.Get(TempBillingLine."Subscription Contract No.", TempBillingLine."Subscription Contract Line No.");
OnAfterCustomerContractLineGetInInsertSalesLineFromTempBillingLine(CustomerContractLine, SalesHeader, TempBillingLine);
+ // Skip the redundant unit price/cost engine while validating the line - the values are taken from
+ // the Billing Line and assigned explicitly below. All other validation side effects still run.
+ BindSubscription(BillingPriceCalcSkip);
SalesLine.InitFromSalesHeader(SalesHeader);
SubContractsItemManagement.SetAllowInsertOfInvoicingItem(true);
if (ServiceCommitment."Invoicing Item No." <> '') and
@@ -247,15 +267,18 @@ codeunit 8060 "Create Billing Documents"
end else begin
SalesLine.Validate(Type, ServiceObject.GetSalesLineType());
SalesLine.Validate("No.", ServiceObject."Source No.");
- SalesLine.Validate("Variant Code", ServiceObject."Variant Code");
if Item.Get(ServiceObject."Source No.") then
if Item.IsVariantMandatory() then
ServiceObject.TestField("Variant Code");
+ if ServiceObject."Variant Code" <> '' then
+ SalesLine.Validate("Variant Code", ServiceObject."Variant Code");
end;
SubContractsItemManagement.SetAllowInsertOfInvoicingItem(false);
- SalesLine.Validate("Unit of Measure Code", ServiceObject."Unit of Measure");
+ if SalesLine."Unit of Measure Code" <> ServiceObject."Unit of Measure" then
+ SalesLine.Validate("Unit of Measure Code", ServiceObject."Unit of Measure");
SalesLine.Validate(Quantity, TempBillingLine.GetSign() * ServiceObject.Quantity);
- SalesLine.Validate("Unit Price", SalesLine.GetSalesDocumentSign() * TempBillingLine."Unit Price");
+ // Unit Price is assigned directly; the following Validate("Line Discount %") recalculates the line amount.
+ SalesLine."Unit Price" := SalesLine.GetSalesDocumentSign() * TempBillingLine."Unit Price";
SalesLine.Validate("Line Discount %", TempBillingLine."Discount %");
SalesLine.Validate("Unit Cost (LCY)", TempBillingLine."Unit Cost (LCY)");
SalesLine."Recurring Billing from" := TempBillingLine."Billing from";
@@ -272,6 +295,7 @@ codeunit 8060 "Create Billing Documents"
SalesLine."Description 2" := '';
SetInvoicePriceFromUsageDataBilling(SalesLine, TempBillingLine);
+ UnbindSubscription(BillingPriceCalcSkip);
OnBeforeInsertSalesLineFromContractLine(SalesLine, TempBillingLine);
SalesLine.Insert(false);
@@ -302,13 +326,16 @@ codeunit 8060 "Create Billing Documents"
UsageDataBilling.SetRange("Subscription Contract Line No.", CustomerContractLine."Line No.");
UsageDataBilling.SetRange("Document Type", Enum::"Usage Based Billing Doc. Type"::None);
UsageDataBilling.SetRange("Document No.", '');
- if UsageDataBilling.FindSet() then
+ if UsageDataBilling.FindSet() then begin
+ // TempBillingLine.Indent holds the source Billing Line "Entry No." captured during aggregation
+ // in CreateTempBillingLines; reused here as the Billing Line link for Usage Data Billing
+ // instead of re-querying Billing Line.
+ BillingLineNo := TempBillingLine.Indent;
repeat
- BillingLineNo := GetBillingLineNo(BillingLine.GetBillingDocumentTypeFromSalesDocumentType(SalesLine."Document Type"),
- "Service Partner"::Customer, SalesLine."Document No.", CustomerContractLine."Subscription Contract No.", CustomerContractLine."Line No.");
UsageDataBilling.SaveDocumentValues(UsageBasedDocTypeConv.ConvertSalesDocTypeToUsageBasedBillingDocType(SalesLine."Document Type"), SalesLine."Document No.",
SalesLine."Line No.", BillingLineNo);
until UsageDataBilling.Next() = 0;
+ end;
end;
OnAfterInsertSalesLineFromBillingLine(CustomerContractLine, SalesLine);
@@ -360,6 +387,9 @@ codeunit 8060 "Create Billing Documents"
ServiceObject.Get(TempBillingLine."Subscription Header No.");
ServiceCommitment.Get(TempBillingLine."Subscription Line Entry No.");
+ // Skip the redundant unit price/cost engine while validating the line - the values are taken from
+ // the Billing Line and assigned explicitly below. All other validation side effects still run.
+ BindSubscription(BillingPriceCalcSkip);
InitPurchaseLine(PurchaseLine);
SubContractsItemManagement.SetAllowInsertOfInvoicingItem(true);
if (ServiceCommitment."Invoicing Item No." <> '') and
@@ -370,12 +400,15 @@ codeunit 8060 "Create Billing Documents"
end else begin
PurchaseLine.Validate(Type, ServiceObject.GetPurchaseLineType());
PurchaseLine.Validate("No.", ServiceObject."Source No.");
- PurchaseLine.Validate("Variant Code", ServiceObject."Variant Code");
+ if ServiceObject."Variant Code" <> '' then
+ PurchaseLine.Validate("Variant Code", ServiceObject."Variant Code");
end;
SubContractsItemManagement.SetAllowInsertOfInvoicingItem(false);
- PurchaseLine.Validate("Unit of Measure Code", ServiceObject."Unit of Measure");
+ if PurchaseLine."Unit of Measure Code" <> ServiceObject."Unit of Measure" then
+ PurchaseLine.Validate("Unit of Measure Code", ServiceObject."Unit of Measure");
PurchaseLine.Validate(Quantity, TempBillingLine.GetSign() * ServiceObject.Quantity);
- PurchaseLine.Validate("Direct Unit Cost", PurchaseLine.GetPurchaseDocumentSign() * TempBillingLine."Unit Price");
+ // Direct Unit Cost is assigned directly; the following Validate("Line Discount %") recalculates the line amount.
+ PurchaseLine."Direct Unit Cost" := PurchaseLine.GetPurchaseDocumentSign() * TempBillingLine."Unit Price";
PurchaseLine.Validate("Line Discount %", TempBillingLine."Discount %");
PurchaseLine."Recurring Billing from" := TempBillingLine."Billing from";
PurchaseLine."Recurring Billing to" := TempBillingLine."Billing to";
@@ -384,6 +417,7 @@ codeunit 8060 "Create Billing Documents"
PurchaseLine.Description := ServiceCommitment.Description;
PurchaseLine."Description 2" := CopyStr(ServiceObject.Description, 1, MaxStrLen(PurchaseLine."Description 2"));
SetInvoicePriceFromUsageDataBilling(PurchaseLine, TempBillingLine);
+ UnbindSubscription(BillingPriceCalcSkip);
OnBeforeInsertPurchaseLineFromContractLine(PurchaseLine, TempBillingLine);
PurchaseLine.Insert(false);
@@ -411,13 +445,16 @@ codeunit 8060 "Create Billing Documents"
UsageDataBilling.SetRange("Subscription Contract Line No.", ServiceCommitment."Subscription Contract Line No.");
UsageDataBilling.SetRange("Document Type", Enum::"Usage Based Billing Doc. Type"::None);
UsageDataBilling.SetRange("Document No.", '');
- if UsageDataBilling.FindSet() then
+ if UsageDataBilling.FindSet() then begin
+ // TempBillingLine.Indent holds the source Billing Line "Entry No." captured during aggregation
+ // in CreateTempBillingLines; reused here as the Billing Line link for Usage Data Billing
+ // instead of re-querying Billing Line.
+ BillingLineNo := TempBillingLine.Indent;
repeat
- BillingLineNo := GetBillingLineNo(BillingLine.GetBillingDocumentTypeFromPurchaseDocumentType(PurchaseLine."Document Type"),
- "Service Partner"::Vendor, PurchaseLine."Document No.", ServiceCommitment."Subscription Contract No.", ServiceCommitment."Subscription Contract Line No.");
UsageDataBilling.SaveDocumentValues(UsageBasedDocTypeConv.ConvertPurchaseDocTypeToUsageBasedBillingDocType(PurchaseLine."Document Type"), PurchaseLine."Document No.",
PurchaseLine."Line No.", BillingLineNo);
until UsageDataBilling.Next() = 0;
+ end;
OnAfterInsertPurchaseLineFromBillingLine(ServiceCommitment, PurchaseLine);
end;
@@ -453,19 +490,6 @@ codeunit 8060 "Create Billing Documents"
PurchLine.Validate("Line Discount %", ServiceCommitment."Discount %");
end;
- local procedure GetBillingLineNo(BillingDocumentType: Enum "Rec. Billing Document Type"; ServicePartner: Enum "Service Partner"; DocumentNo: Code[20]; ContractNo: Code[20]; ContractLineNo: Integer): Integer
- var
- BillingLine: Record "Billing Line";
- begin
- BillingLine.FilterBillingLineOnContractLine(ServicePartner, ContractNo, ContractLineNo);
- BillingLine.SetRange("Document Type", BillingDocumentType);
- BillingLine.SetRange("Document No.", DocumentNo);
- if BillingLine.FindLast() then
- exit(BillingLine."Entry No.")
- else
- exit(0);
- end;
-
local procedure InitPurchaseLine(var PurchaseLine: Record "Purchase Line")
begin
PurchaseLine.Init();
@@ -724,16 +748,20 @@ codeunit 8060 "Create Billing Documents"
var
CustomerContract: Record "Customer Subscription Contract";
VendorContract: Record "Vendor Subscription Contract";
+ AggregatedLineByKey: Dictionary of [Text, Integer];
CurrencyCode: Code[20];
PartnerNo: Code[20];
+ LookupKey: Text;
LineNo: Integer;
+ ExistingEntryNo: Integer;
begin
if BillingLine.FindSet() then
repeat
case BillingLine.Partner of
BillingLine.Partner::Customer:
begin
- CustomerContract.Get(BillingLine."Subscription Contract No.");
+ if CustomerContract."No." <> BillingLine."Subscription Contract No." then
+ CustomerContract.Get(BillingLine."Subscription Contract No.");
case CustomerRecurringBillingGrouping of
CustomerRecurringBillingGrouping::"Sell-to Customer No.":
PartnerNo := CustomerContract."Sell-to Customer No.";
@@ -744,7 +772,8 @@ codeunit 8060 "Create Billing Documents"
end;
BillingLine.Partner::Vendor:
begin
- VendorContract.Get(BillingLine."Subscription Contract No.");
+ if VendorContract."No." <> BillingLine."Subscription Contract No." then
+ VendorContract.Get(BillingLine."Subscription Contract No.");
case VendorRecurringBillingGrouping of
VendorRecurringBillingGrouping::"Pay-to Vendor No.":
PartnerNo := VendorContract."Pay-to Vendor No.";
@@ -755,11 +784,13 @@ codeunit 8060 "Create Billing Documents"
end;
end;
- TempBillingLine.SetRange("Subscription Contract No.", BillingLine."Subscription Contract No.");
- TempBillingLine.SetRange("Subscription Header No.", BillingLine."Subscription Header No.");
- TempBillingLine.SetRange("Subscription Line Entry No.", BillingLine."Subscription Line Entry No.");
- TempBillingLine.SetRange(Rebilling, BillingLine.Rebilling);
- if not TempBillingLine.FindFirst() then begin
+ // Aggregate by Subscription Line + Rebilling. The Subscription Line (Entry No.) already determines
+ // its Contract and Subscription, so those are not part of the key. Regular and rebilling charges
+ // for the same line stay separate because they carry different document/sign semantics. The
+ // in-memory index maps each group key to its temp Entry No., so the matching aggregated line is
+ // fetched by primary key (Get) - fast even when the temp table holds millions of rows.
+ LookupKey := Format(BillingLine."Subscription Line Entry No.") + '|' + Format(BillingLine.Rebilling);
+ if not AggregatedLineByKey.Get(LookupKey, ExistingEntryNo) then begin
TempBillingLine.Init();
LineNo += 1;
TempBillingLine."Entry No." := LineNo;
@@ -777,7 +808,9 @@ codeunit 8060 "Create Billing Documents"
TempBillingLine.Rebilling := BillingLine.Rebilling;
OnBeforeInsertTempBillingLine(TempBillingLine, BillingLine);
TempBillingLine.Insert(false);
- end;
+ AggregatedLineByKey.Add(LookupKey, LineNo);
+ end else
+ TempBillingLine.Get(ExistingEntryNo);
TempBillingLine."Unit Price" += BillingLine."Unit Price";
TempBillingLine.Amount += BillingLine.Amount;
TempBillingLine.Discount := BillingLine.Discount;
@@ -789,6 +822,10 @@ codeunit 8060 "Create Billing Documents"
OnCreateTempBillingLinesBeforeSaveTempBillingLine(TempBillingLine, BillingLine);
TempBillingLine."Unit Cost" += BillingLine."Unit Cost";
TempBillingLine."Unit Cost (LCY)" += BillingLine."Unit Cost (LCY)";
+ // Carry the (last) source Billing Line "Entry No." on the aggregated temp line via the unused
+ // Indent field, so the usage data billing link can be stamped without re-querying Billing Line
+ // (replaces a per-line GetBillingLineNo FindLast).
+ TempBillingLine.Indent := BillingLine."Entry No.";
TempBillingLine.Modify(false);
until BillingLine.Next() = 0;
end;
@@ -829,27 +866,35 @@ codeunit 8060 "Create Billing Documents"
end;
local procedure CheckBillingLines(var BillingLine: Record "Billing Line"): Boolean
+ var
+ ShouldCheckDataConsistency: Boolean;
+ ShouldCheckItemUnitOfMeasures: Boolean;
begin
if not CheckOnlyOneServicePartnerType(BillingLine) then
exit(false);
if not CheckNoUpdateRequired(BillingLine) then
exit(false);
- CheckServiceCommitmentDataConsistency(BillingLine);
- CheckItemUnitOfMeasureForInvoicingItems(BillingLine);
+ ShouldCheckDataConsistency := true;
+ ShouldCheckItemUnitOfMeasures := true;
+ OnCheckBillingLinesOnBeforeDataChecks(BillingLine, ShouldCheckDataConsistency, ShouldCheckItemUnitOfMeasures);
+ if ShouldCheckDataConsistency or ShouldCheckItemUnitOfMeasures then begin
+ this.ProgressTracker.StartActivity(CheckingBillingLinesLbl, BillingLine.Count());
+ CheckBillingLineData(BillingLine, ShouldCheckDataConsistency, ShouldCheckItemUnitOfMeasures);
+ this.ProgressTracker.Finish();
+ end;
exit(true);
end;
local procedure CheckOnlyOneServicePartnerType(var BillingLine: Record "Billing Line"): Boolean
+ var
+ PartnerBillingLine: Record "Billing Line";
begin
- if BillingLine.FindSet() then
- repeat
- case BillingLine.Partner of
- BillingLine.Partner::Customer:
- CustomerBillingLinesFound := true;
- BillingLine.Partner::Vendor:
- VendorBillingLinesFound := true;
- end;
- until BillingLine.Next() = 0;
+ PartnerBillingLine.CopyFilters(BillingLine);
+ PartnerBillingLine.SetRange(Partner, Enum::"Service Partner"::Customer);
+ CustomerBillingLinesFound := not PartnerBillingLine.IsEmpty();
+
+ PartnerBillingLine.SetRange(Partner, Enum::"Service Partner"::Vendor);
+ VendorBillingLinesFound := not PartnerBillingLine.IsEmpty();
if (CustomerBillingLinesFound and VendorBillingLinesFound) then begin
DisplayOrLogUnspecificError(OnlyOneServicePartnerErr);
@@ -870,22 +915,48 @@ codeunit 8060 "Create Billing Documents"
exit(true);
end;
- local procedure CheckServiceCommitmentDataConsistency(var BillingLine: Record "Billing Line")
+ local procedure CheckBillingLineData(var BillingLine: Record "Billing Line"; CheckDataConsistency: Boolean; CheckItemUnitOfMeasures: Boolean)
var
- CheckedServiceCommitments: List of [Text];
- begin
- if BillingLine.FindSet() then
+ LoopBillingLine: Record "Billing Line";
+ SubscriptionHeader: Record "Subscription Header";
+ SubscriptionLine: Record "Subscription Line";
+ CheckedItemUnitsOfMeasure: Dictionary of [Text, Boolean];
+ InvoicingItemNo: Code[20];
+ ItemUnitOfMeasureKey: Text;
+ PreviousSubscriptionLineEntryNo: Integer;
+ Counter: Integer;
+ begin
+ LoopBillingLine.CopyFilters(BillingLine);
+ LoopBillingLine.SetCurrentKey("Subscription Header No.", "Subscription Line Entry No.", "Billing to");
+ LoopBillingLine.SetLoadFields("Subscription Header No.", "Subscription Line Entry No.");
+ SubscriptionLine.SetLoadFields("Invoicing Item No.");
+ if LoopBillingLine.FindSet() then
repeat
- ValidateServiceCommitmentConsistency(BillingLine, CheckedServiceCommitments);
- until BillingLine.Next() = 0;
- end;
-
- local procedure ValidateServiceCommitmentConsistency(var BillingLine: Record "Billing Line"; var CheckedServiceCommitments: List of [Text])
- begin
- if not CheckedServiceCommitments.Contains(Format(BillingLine."Subscription Line Entry No.")) then begin
- CheckedServiceCommitments.Add(Format(BillingLine."Subscription Line Entry No."));
- ValidateFilteredVsTotalBillingLineCount(BillingLine);
- end;
+ Counter += 1;
+ this.ProgressTracker.UpdateProgress(Counter, '');
+ if LoopBillingLine."Subscription Line Entry No." <> PreviousSubscriptionLineEntryNo then begin
+ PreviousSubscriptionLineEntryNo := LoopBillingLine."Subscription Line Entry No.";
+ if CheckDataConsistency then
+ ValidateFilteredVsTotalBillingLineCount(LoopBillingLine);
+ if CheckItemUnitOfMeasures then begin
+ if SubscriptionHeader."No." <> LoopBillingLine."Subscription Header No." then
+ SubscriptionHeader.Get(LoopBillingLine."Subscription Header No.");
+ if SubscriptionHeader.Type = SubscriptionHeader.Type::Item then begin
+ SubscriptionLine.Get(LoopBillingLine."Subscription Line Entry No.");
+ if SubscriptionLine."Invoicing Item No." = '' then
+ InvoicingItemNo := SubscriptionHeader."Source No."
+ else
+ InvoicingItemNo := SubscriptionLine."Invoicing Item No.";
+ ItemUnitOfMeasureKey := InvoicingItemNo + '|' + SubscriptionHeader."Unit of Measure";
+ if not CheckedItemUnitsOfMeasure.ContainsKey(ItemUnitOfMeasureKey) then begin
+ CheckedItemUnitsOfMeasure.Add(ItemUnitOfMeasureKey, true);
+ ErrorIfItemUnitOfMeasureCodeDoesNotExist(LoopBillingLine, InvoicingItemNo, SubscriptionHeader);
+ end;
+ end;
+ end;
+ end;
+ OnCheckBillingLineDataOnBeforeNextBillingLine(LoopBillingLine);
+ until LoopBillingLine.Next() = 0;
end;
local procedure ValidateFilteredVsTotalBillingLineCount(var BillingLine: Record "Billing Line")
@@ -929,30 +1000,6 @@ codeunit 8060 "Create Billing Documents"
DisplayOrLogErrorFromBillingLine(BillingLine, StrSubstNo(ConsistencyErr, BillingLine."Subscription Header No.", BillingLine."Subscription Line Entry No.", FilteredCount, TotalCount));
end;
- local procedure CheckItemUnitOfMeasureForInvoicingItems(var BillingLine: Record "Billing Line")
- var
- SubscriptionHeader: Record "Subscription Header";
- SubscriptionLine: Record "Subscription Line";
- CheckedServiceCommitments: List of [Text];
- InvoicingItemNo: Code[20];
- begin
- if BillingLine.FindSet() then
- repeat
- if not CheckedServiceCommitments.Contains(Format(BillingLine."Subscription Line Entry No.")) then begin
- CheckedServiceCommitments.Add(Format(BillingLine."Subscription Line Entry No."));
- SubscriptionHeader.Get(BillingLine."Subscription Header No.");
- if SubscriptionHeader.Type = SubscriptionHeader.Type::Item then begin
- SubscriptionLine.Get(BillingLine."Subscription Line Entry No.");
- if SubscriptionLine."Invoicing Item No." = '' then
- InvoicingItemNo := SubscriptionHeader."Source No."
- else
- InvoicingItemNo := SubscriptionLine."Invoicing Item No.";
- ErrorIfItemUnitOfMeasureCodeDoesNotExist(BillingLine, InvoicingItemNo, SubscriptionHeader);
- end;
- end;
- until BillingLine.Next() = 0;
- end;
-
internal procedure ErrorIfItemUnitOfMeasureCodeDoesNotExist(BillingLine: Record "Billing Line"; InvoicingItemNo: Code[20]; SubscriptionHeader: Record "Subscription Header")
var
ItemUnitOfMeasure: Record "Item Unit of Measure";
@@ -1249,6 +1296,55 @@ codeunit 8060 "Create Billing Documents"
OnAfterIsNewHeaderNeededPerContract(CreateNewHeader, TempBillingLine, PreviousSubContractNo);
end;
+ local procedure UpdateBillingProgress()
+ begin
+ ProcessedBillingLineCount += 1;
+ this.ProgressTracker.UpdateProgress(ProcessedBillingLineCount, CurrentDetailText);
+ end;
+
+ local procedure SetProgressPartner(PartnerNo: Code[20])
+ begin
+ CurrentPartnerNo := PartnerNo;
+ RefreshProgressDetail();
+ end;
+
+ local procedure SetProgressContract(ContractNo: Code[20])
+ begin
+ CurrentContractNo := ContractNo;
+ RefreshProgressDetail();
+ end;
+
+ local procedure RefreshProgressDetail()
+ begin
+ CurrentDetailText := StrSubstNo(ProgressDetailLbl, CurrentPartnerNo, CurrentContractNo);
+ end;
+
+ local procedure CommitCreatedDocumentCheckpoint()
+ begin
+ // Per-document checkpoint: makes each completed document durable on the non-posting path
+ // (incl. automated/background billing) without committing inside the per-line loop.
+ // When posting follows, creation and posting are kept atomic and PostCreatedDocuments
+ // manages its own commit/rollback.
+ if not PostDocuments then
+ Commit();
+ end;
+
+ local procedure TrimTempBillingLinesForContract(ContractNo: Code[20])
+ var
+ TempBillingLineToDelete: Record "Billing Line" temporary;
+ begin
+ // Release a processed contract's aggregated temp lines so the temporary table stays small; otherwise it
+ // spills to a per-session SQL temp table and every Get/Modify becomes a round-trip. The delete runs on a
+ // shared-table copy, so the caller's iteration cursor (position, key, filters) is left untouched - only
+ // lines of an already-processed contract (behind the cursor) are removed.
+ if ContractNo = '' then
+ exit;
+ TempBillingLineToDelete.Copy(TempBillingLine, true);
+ TempBillingLineToDelete.Reset();
+ TempBillingLineToDelete.SetRange("Subscription Contract No.", ContractNo);
+ TempBillingLineToDelete.DeleteAll(false);
+ end;
+
[IntegrationEvent(false, false)]
local procedure OnAfterCreateSalesHeaderFromContract(CustomerSubscriptionContract: Record "Customer Subscription Contract"; var SalesHeader: Record "Sales Header")
begin
@@ -1334,6 +1430,16 @@ codeunit 8060 "Create Billing Documents"
begin
end;
+ [IntegrationEvent(false, false)]
+ local procedure OnCheckBillingLinesOnBeforeDataChecks(var BillingLine: Record "Billing Line"; var ShouldCheckDataConsistency: Boolean; var ShouldCheckItemUnitOfMeasures: Boolean)
+ begin
+ end;
+
+ [IntegrationEvent(false, false)]
+ local procedure OnCheckBillingLineDataOnBeforeNextBillingLine(var BillingLine: Record "Billing Line")
+ begin
+ end;
+
[IntegrationEvent(false, false)]
local procedure OnAfterIsNewSalesHeaderNeeded(var CreateNewSalesHeader: Boolean; TempBillingLine: Record "Billing Line" temporary; PreviousCustomerNo: Code[20]; LastDetailOverview: Enum "Contract Detail Overview"; PreviousCurrencyCode: Code[20]; PreviousContractNo: Code[20])
begin
@@ -1393,19 +1499,27 @@ codeunit 8060 "Create Billing Documents"
TranslationHelper: Codeunit "Translation Helper";
DocumentChangeManagement: Codeunit "Document Change Management";
Language: Codeunit Language;
+ ProgressTracker: Codeunit "Progress Tracker";
+ BillingPriceCalcSkip: Codeunit "Billing Price Calc. Skip";
DocumentDate: Date;
PostingDate: Date;
CustomerRecurringBillingGrouping: Enum "Customer Rec. Billing Grouping";
VendorRecurringBillingGrouping: Enum "Vendor Rec. Billing Grouping";
DocumentsCreatedCount: Integer;
ContractsProcessedCount: Integer;
+ TotalBillingLineCount: Integer;
+ ProcessedBillingLineCount: Integer;
+ CurrentPartnerNo: Code[20];
+ CurrentContractNo: Code[20];
+ CurrentDetailText: Text;
CustomerBillingLinesFound: Boolean;
VendorBillingLinesFound: Boolean;
FirstContractDescriptionLineInserted: Boolean;
PostDocuments: Boolean;
HideProcessingFinishedMessage: Boolean;
- Window: Dialog;
- ProgressTxt: Label 'Creating documents...\Partner No. #1#################################\Contract No. #2#################################', Comment = '%1=Partner No., %2=Contract No.';
+ CreatingDocumentsLbl: Label 'Creating billing documents...';
+ ProgressDetailLbl: Label 'Partner %1 | Contract %2', Comment = '%1 = Partner No., %2 = Contract No.';
+ CheckingBillingLinesLbl: Label 'Checking billing lines...';
OnlyOneServicePartnerErr: Label 'You can create documents only for one type of partner at a time (Customer or Vendor). Please check your filters.';
UpdateRequiredErr: Label 'At least one Subscription Line was changed after billing proposal was created. Please check the lines marked with "Update Required" field and update the billing proposal before the billing documents can be created.';
BillingPeriodDescriptionTxt: Label 'Billing period: %1 to %2', Comment = '%1=Recurring Billing from, %2=Recurring Billing to';
diff --git a/src/Apps/W1/Subscription Billing/App/Billing/Codeunits/PurchaseDocuments.Codeunit.al b/src/Apps/W1/Subscription Billing/App/Billing/Codeunits/PurchaseDocuments.Codeunit.al
index 62ebb801c8..3d68e4483b 100644
--- a/src/Apps/W1/Subscription Billing/App/Billing/Codeunits/PurchaseDocuments.Codeunit.al
+++ b/src/Apps/W1/Subscription Billing/App/Billing/Codeunits/PurchaseDocuments.Codeunit.al
@@ -163,6 +163,7 @@ codeunit 8066 "Purchase Documents"
local procedure MoveBillingLineToBillingLineArchive(var BillingLine: Record "Billing Line"; var PurchaseHeader: Record "Purchase Header"; var PurchaseInvoiceHeader: Record "Purch. Inv. Header"; var PurchaseCrMemoHeader: Record "Purch. Cr. Memo Hdr.")
var
BillingLineArchive: Record "Billing Line Archive";
+ UsageDataBilling: Record "Usage Data Billing";
PostedDocumentNo: Code[20];
begin
case PurchaseHeader."Document Type" of
@@ -178,6 +179,8 @@ codeunit 8066 "Purchase Documents"
BillingLineArchive."Document No." := PostedDocumentNo;
BillingLineArchive."Entry No." := 0;
BillingLineArchive.Insert(false);
+ UsageDataBilling.SetRange("Billing Line Entry No.", BillingLine."Entry No.");
+ UsageDataBilling.ModifyAll("Billing Line Entry No.", BillingLineArchive."Entry No.", false);
OnAfterInsertBillingLineArchiveOnMoveBillingLineToBillingLineArchive(BillingLineArchive, BillingLine);
until BillingLine.Next() = 0;
end;
diff --git a/src/Apps/W1/Subscription Billing/App/Billing/Codeunits/SalesDocuments.Codeunit.al b/src/Apps/W1/Subscription Billing/App/Billing/Codeunits/SalesDocuments.Codeunit.al
index b9eae6e219..1bbeb71dcd 100644
--- a/src/Apps/W1/Subscription Billing/App/Billing/Codeunits/SalesDocuments.Codeunit.al
+++ b/src/Apps/W1/Subscription Billing/App/Billing/Codeunits/SalesDocuments.Codeunit.al
@@ -200,6 +200,7 @@ codeunit 8063 "Sales Documents"
local procedure MoveBillingLineToBillingLineArchive(var BillingLine: Record "Billing Line"; var SalesHeader: Record "Sales Header"; var SalesInvoiceHeader: Record "Sales Invoice Header"; var SalesCrMemoHeader: Record "Sales Cr.Memo Header")
var
BillingLineArchive: Record "Billing Line Archive";
+ UsageDataBilling: Record "Usage Data Billing";
PostedDocumentNo: Code[20];
begin
case SalesHeader."Document Type" of
@@ -215,6 +216,8 @@ codeunit 8063 "Sales Documents"
BillingLineArchive."Document No." := PostedDocumentNo;
BillingLineArchive."Entry No." := 0;
BillingLineArchive.Insert(false);
+ UsageDataBilling.SetRange("Billing Line Entry No.", BillingLine."Entry No.");
+ UsageDataBilling.ModifyAll("Billing Line Entry No.", BillingLineArchive."Entry No.", false);
OnAfterInsertBillingLineArchiveOnMoveBillingLineToBillingLineArchive(BillingLineArchive, BillingLine);
until BillingLine.Next() = 0;
end;
diff --git a/src/Apps/W1/Subscription Billing/App/Billing/Pages/CreateCustomerBillingDocs.Page.al b/src/Apps/W1/Subscription Billing/App/Billing/Pages/CreateCustomerBillingDocs.Page.al
index d47cea70b7..71b8322cff 100644
--- a/src/Apps/W1/Subscription Billing/App/Billing/Pages/CreateCustomerBillingDocs.Page.al
+++ b/src/Apps/W1/Subscription Billing/App/Billing/Pages/CreateCustomerBillingDocs.Page.al
@@ -55,7 +55,7 @@ page 8072 "Create Customer Billing Docs"
PostDocuments: Boolean;
Grouping: Enum "Customer Rec. Billing Grouping";
- internal procedure GetData(var NewDocumentDate: Date; var NewPostingDate: Date; var NewGroupingType: Enum "Customer Rec. Billing Grouping"; var NewPostDocuments: Boolean)
+ procedure GetData(var NewDocumentDate: Date; var NewPostingDate: Date; var NewGroupingType: Enum "Customer Rec. Billing Grouping"; var NewPostDocuments: Boolean)
begin
NewDocumentDate := DocumentDate;
NewPostingDate := PostingDate;
@@ -63,7 +63,7 @@ page 8072 "Create Customer Billing Docs"
NewPostDocuments := PostDocuments;
end;
- internal procedure SetData(NewDocumentDate: Date; NewPostingDate: Date; NewGroupingType: Enum "Customer Rec. Billing Grouping"; NewPostDocuments: Boolean)
+ procedure SetData(NewDocumentDate: Date; NewPostingDate: Date; NewGroupingType: Enum "Customer Rec. Billing Grouping"; NewPostDocuments: Boolean)
begin
DocumentDate := NewDocumentDate;
PostingDate := NewPostingDate;
diff --git a/src/Apps/W1/Subscription Billing/App/Billing/Pages/CreateVendorBillingDocs.Page.al b/src/Apps/W1/Subscription Billing/App/Billing/Pages/CreateVendorBillingDocs.Page.al
index a71b150937..aa6b2c89d0 100644
--- a/src/Apps/W1/Subscription Billing/App/Billing/Pages/CreateVendorBillingDocs.Page.al
+++ b/src/Apps/W1/Subscription Billing/App/Billing/Pages/CreateVendorBillingDocs.Page.al
@@ -50,14 +50,14 @@ page 8077 "Create Vendor Billing Docs"
PostingDate: Date;
Grouping: Enum "Vendor Rec. Billing Grouping";
- internal procedure GetData(var NewDocumentDate: Date; var NewPostingDate: Date; var NewGroupingType: Enum "Vendor Rec. Billing Grouping")
+ procedure GetData(var NewDocumentDate: Date; var NewPostingDate: Date; var NewGroupingType: Enum "Vendor Rec. Billing Grouping")
begin
NewDocumentDate := DocumentDate;
NewPostingDate := PostingDate;
NewGroupingType := Grouping;
end;
- internal procedure SetData(NewDocumentDate: Date; NewPostingDate: Date; NewGroupingType: Enum "Vendor Rec. Billing Grouping")
+ procedure SetData(NewDocumentDate: Date; NewPostingDate: Date; NewGroupingType: Enum "Vendor Rec. Billing Grouping")
begin
DocumentDate := NewDocumentDate;
PostingDate := NewPostingDate;
diff --git a/src/Apps/W1/Subscription Billing/App/Billing/Pages/RecurringBilling.Page.al b/src/Apps/W1/Subscription Billing/App/Billing/Pages/RecurringBilling.Page.al
index a0581d52a9..b230966283 100644
--- a/src/Apps/W1/Subscription Billing/App/Billing/Pages/RecurringBilling.Page.al
+++ b/src/Apps/W1/Subscription Billing/App/Billing/Pages/RecurringBilling.Page.al
@@ -70,7 +70,7 @@ page 8067 "Recurring Billing"
trigger OnDrillDown()
begin
ContractsGeneralMgt.OpenPartnerCard(Rec.Partner, Rec."Partner No.");
- InitTempTable();
+ RefreshCurrentRowGroup();
end;
}
field("Partner Name"; PartnerNameTxt)
@@ -83,7 +83,7 @@ page 8067 "Recurring Billing"
trigger OnDrillDown()
begin
ContractsGeneralMgt.OpenPartnerCard(Rec.Partner, Rec."Partner No.");
- InitTempTable();
+ RefreshCurrentRowGroup();
end;
}
field("Contract No."; Rec."Subscription Contract No.")
@@ -94,7 +94,7 @@ page 8067 "Recurring Billing"
trigger OnDrillDown()
begin
ContractsGeneralMgt.OpenContractCard(Rec.Partner, Rec."Subscription Contract No.");
- InitTempTable();
+ RefreshCurrentRowGroup();
end;
}
field(ContractDescriptionField; ContractDescriptionTxt)
@@ -107,7 +107,7 @@ page 8067 "Recurring Billing"
trigger OnDrillDown()
begin
ContractsGeneralMgt.OpenContractCard(Rec.Partner, Rec."Subscription Contract No.");
- InitTempTable();
+ RefreshCurrentRowGroup();
end;
}
field("Billing from"; Rec."Billing from")
@@ -182,8 +182,7 @@ page 8067 "Recurring Billing"
trigger OnDrillDown()
begin
- Rec.OpenDocumentCard();
- InitTempTable();
+ RefreshAfterDocumentDrillDown();
end;
}
field("Service Start Date"; Rec."Subscription Line Start Date")
@@ -204,7 +203,7 @@ page 8067 "Recurring Billing"
trigger OnDrillDown()
begin
ServiceObject.OpenServiceObjectCard(Rec."Subscription Header No.");
- InitTempTable();
+ RefreshCurrentRowGroup();
end;
}
field("Service Object Description"; Rec."Subscription Description")
@@ -335,6 +334,7 @@ page 8067 "Recurring Billing"
BillingLine: Record "Billing Line";
ChangeDate: Page "Change Date";
NewBillingToDate: Date;
+ AffectedGroupKeys: List of [Code[20]];
begin
if BillingDate <> 0D then
ChangeDate.SetDate(BillingDate);
@@ -343,8 +343,9 @@ page 8067 "Recurring Billing"
else
exit;
CurrPage.SetSelectionFilter(BillingLine);
+ CollectSelectionGroupKeys(BillingLine, AffectedGroupKeys);
BillingProposal.UpdateBillingToDate(BillingLine, NewBillingToDate);
- InitTempTable();
+ RefreshGroups(AffectedGroupKeys);
end;
}
action(DeleteBillingLineAction)
@@ -357,10 +358,12 @@ page 8067 "Recurring Billing"
trigger OnAction()
var
BillingLine: Record "Billing Line";
+ AffectedGroupKeys: List of [Code[20]];
begin
CurrPage.SetSelectionFilter(BillingLine);
+ CollectSelectionGroupKeys(BillingLine, AffectedGroupKeys);
BillingProposal.DeleteBillingLines(BillingLine);
- InitTempTable();
+ RefreshGroups(AffectedGroupKeys);
end;
}
action(Refresh)
@@ -390,7 +393,7 @@ page 8067 "Recurring Billing"
trigger OnAction()
begin
ContractsGeneralMgt.OpenPartnerCard(Rec.Partner, Rec."Partner No.");
- InitTempTable();
+ RefreshCurrentRowGroup();
end;
}
action(OpenContractAction)
@@ -403,7 +406,7 @@ page 8067 "Recurring Billing"
trigger OnAction()
begin
ContractsGeneralMgt.OpenContractCard(Rec.Partner, Rec."Subscription Contract No.");
- InitTempTable();
+ RefreshCurrentRowGroup();
end;
}
action(OpenServiceObjectAction)
@@ -416,7 +419,7 @@ page 8067 "Recurring Billing"
trigger OnAction()
begin
ServiceObject.OpenServiceObjectCard(Rec."Subscription Header No.");
- InitTempTable();
+ RefreshCurrentRowGroup();
end;
}
action(Dimensions)
@@ -466,8 +469,11 @@ page 8067 "Recurring Billing"
trigger OnAfterGetRecord()
begin
- ContractDescriptionTxt := ContractsGeneralMgt.GetContractDescription(Rec.Partner, Rec."Subscription Contract No.");
- PartnerNameTxt := ContractsGeneralMgt.GetPartnerName(Rec.Partner, Rec."Partner No.");
+ // Resolve the contract description and partner name from per-page caches: OnAfterGetRecord fires on every
+ // row of every render cycle, and the same contract/partner repeats across many rows, so an uncached lookup
+ // per row turns into thousands of redundant Get calls. The caches are reset whenever the lines are rebuilt.
+ ContractDescriptionTxt := GetCachedContractDescription(Rec.Partner, Rec."Subscription Contract No.");
+ PartnerNameTxt := GetCachedPartnerName(Rec.Partner, Rec."Partner No.");
SetLineStyleExpr();
end;
@@ -487,6 +493,8 @@ page 8067 "Recurring Billing"
PartnerNameTxt: Text;
GroupBy: Enum "Contract Billing Grouping";
UsageDataEnabled: Boolean;
+ ContractDescriptionCache: Dictionary of [Text, Text];
+ PartnerNameCache: Dictionary of [Text, Text];
protected var
BillingDate: Date;
@@ -553,11 +561,123 @@ page 8067 "Recurring Billing"
procedure InitTempTable()
begin
+ // The lines are about to be rebuilt, so drop the row-render caches; contract descriptions or partner names
+ // may have changed since they were last read.
+ Clear(ContractDescriptionCache);
+ Clear(PartnerNameCache);
BillingProposal.InitTempTable(Rec, GroupBy);
if Rec.FindFirst() then; //to enable CollapseAll
CurrPage.Update(false);
end;
+ // Rebuilds the temp table group that contains the current row. Called after any drilldown or navigate action
+ // that opens a card which may have been edited (partner name, contract discount, subscription quantity, etc.).
+ local procedure RefreshCurrentRowGroup()
+ var
+ GroupKeys: List of [Code[20]];
+ begin
+ GroupKeys.Add(GetGroupKey(Rec."Partner No.", Rec."Subscription Contract No."));
+ RefreshGroups(GroupKeys);
+ end;
+
+ // Special refresh for the Document No. drilldown: a document may be deleted inside the card, which clears
+ // Document No. on all billing lines it covered — potentially across multiple groups for collective documents.
+ // All affected groups are captured before the card opens, so the rebuild is correct regardless of what changes.
+ local procedure RefreshAfterDocumentDrillDown()
+ var
+ TempDocumentBillingLine: Record "Billing Line" temporary;
+ AffectedGroupKeys: List of [Code[20]];
+ GroupKey: Code[20];
+ begin
+ if Rec."Document No." = '' then
+ exit;
+ TempDocumentBillingLine.Copy(Rec, true); // shared-table copy — no data copy, cursor untouched
+ TempDocumentBillingLine.Reset();
+ TempDocumentBillingLine.SetRange("Document Type", Rec."Document Type");
+ TempDocumentBillingLine.SetRange("Document No.", Rec."Document No.");
+ if TempDocumentBillingLine.FindSet() then
+ repeat
+ GroupKey := GetGroupKey(TempDocumentBillingLine."Partner No.", TempDocumentBillingLine."Subscription Contract No.");
+ if not AffectedGroupKeys.Contains(GroupKey) then
+ AffectedGroupKeys.Add(GroupKey);
+ until TempDocumentBillingLine.Next() = 0;
+
+ Rec.OpenDocumentCard();
+
+ // RefreshGroups preserves Rec's position; if the document was deleted and the billing line no longer
+ // exists it falls back to the first record automatically.
+ RefreshGroups(AffectedGroupKeys);
+ end;
+
+ // Collects the group key for each line in the selection before a mutating action (Delete Billing Line,
+ // Change Billing To Date) runs, so only those groups need rebuilding afterwards.
+ local procedure CollectSelectionGroupKeys(var BillingLine: Record "Billing Line"; var GroupKeys: List of [Code[20]])
+ var
+ GroupKey: Code[20];
+ begin
+ if BillingLine.FindSet() then
+ repeat
+ GroupKey := GetGroupKey(BillingLine."Partner No.", BillingLine."Subscription Contract No.");
+ if not GroupKeys.Contains(GroupKey) then
+ GroupKeys.Add(GroupKey);
+ until BillingLine.Next() = 0;
+ end;
+
+ // Returns the grouping key for a billing line: the contract no. for Contract grouping, the partner no. for
+ // Contract Partner grouping. Centralizes the GroupBy decision so callers do not repeat it.
+ local procedure GetGroupKey(PartnerNo: Code[20]; ContractNo: Code[20]): Code[20]
+ begin
+ if GroupBy = GroupBy::"Contract Partner" then
+ exit(PartnerNo);
+ exit(ContractNo);
+ end;
+
+ // Common entry point for all targeted refreshes: clears the per-page render caches, rebuilds the specified
+ // groups in the temp table, and refreshes the page. The user's current row position is preserved; if the row
+ // no longer exists after the rebuild (e.g. it was deleted), the page falls back to the first record.
+ local procedure RefreshGroups(GroupKeys: List of [Code[20]])
+ var
+ CurrentEntryNo: Integer;
+ begin
+ CurrentEntryNo := Rec."Entry No.";
+ Clear(ContractDescriptionCache);
+ Clear(PartnerNameCache);
+ BillingProposal.RefreshGroupsInTempTable(Rec, GroupBy, GroupKeys);
+ if not Rec.Get(CurrentEntryNo) then
+ if Rec.FindFirst() then;
+ CurrPage.Update(false);
+ end;
+
+ local procedure GetCachedContractDescription(Partner: Enum "Service Partner"; ContractNo: Code[20]): Text
+ var
+ CacheKey: Text;
+ CachedValue: Text;
+ begin
+ if ContractNo = '' then
+ exit('');
+ CacheKey := Format(Partner) + '|' + ContractNo;
+ if ContractDescriptionCache.Get(CacheKey, CachedValue) then
+ exit(CachedValue);
+ CachedValue := ContractsGeneralMgt.GetContractDescription(Partner, ContractNo);
+ ContractDescriptionCache.Add(CacheKey, CachedValue);
+ exit(CachedValue);
+ end;
+
+ local procedure GetCachedPartnerName(Partner: Enum "Service Partner"; PartnerNo: Code[20]): Text
+ var
+ CacheKey: Text;
+ CachedValue: Text;
+ begin
+ if PartnerNo = '' then
+ exit('');
+ CacheKey := Format(Partner) + '|' + PartnerNo;
+ if PartnerNameCache.Get(CacheKey, CachedValue) then
+ exit(CachedValue);
+ CachedValue := ContractsGeneralMgt.GetPartnerName(Partner, PartnerNo);
+ PartnerNameCache.Add(CacheKey, CachedValue);
+ exit(CachedValue);
+ end;
+
local procedure UpdateServiceCommitmentDimension()
var
ServiceCommitment: Record "Subscription Line";
diff --git a/src/Apps/W1/Subscription Billing/App/Billing/Tables/BillingLine.Table.al b/src/Apps/W1/Subscription Billing/App/Billing/Tables/BillingLine.Table.al
index cc22098ed6..b2286f82d4 100644
--- a/src/Apps/W1/Subscription Billing/App/Billing/Tables/BillingLine.Table.al
+++ b/src/Apps/W1/Subscription Billing/App/Billing/Tables/BillingLine.Table.al
@@ -57,6 +57,7 @@ table 8061 "Billing Line"
field(31; "Subscription Line Entry No."; Integer)
{
Caption = 'Subscription Line Entry No.';
+ TableRelation = "Subscription Line"."Entry No.";
}
field(32; "Subscription Description"; Text[100])
{
@@ -243,11 +244,22 @@ table 8061 "Billing Line"
key(SK6; "Billing Template Code", Partner)
{
}
+ key(SK7; "Subscription Line Entry No.")
+ {
+ }
+ }
+
+ fieldgroups
+ {
+ fieldgroup(DropDown; "Subscription Header No.", "Billing from", "Billing to", Amount)
+ {
+ }
}
trigger OnDelete()
var
BillingLine2: Record "Billing Line";
+ UsageDataBilling: Record "Usage Data Billing";
begin
if "Document No." <> '' then
Error(CannotDeleteBillingLinesWithDocumentNoErr);
@@ -257,6 +269,8 @@ table 8061 "Billing Line"
else
Error(OnlyLastServiceLineCanBeDeletedErr, "Subscription Header No.");
RecalculateCustomerContractHarmonizedBillingFields();
+ UsageDataBilling.SetRange("Billing Line Entry No.", "Entry No.");
+ UsageDataBilling.ModifyAll("Billing Line Entry No.", 0, false);
end;
var
diff --git a/src/Apps/W1/Subscription Billing/App/Billing/Tables/BillingLineArchive.Table.al b/src/Apps/W1/Subscription Billing/App/Billing/Tables/BillingLineArchive.Table.al
index ea6f945c0d..81795965fb 100644
--- a/src/Apps/W1/Subscription Billing/App/Billing/Tables/BillingLineArchive.Table.al
+++ b/src/Apps/W1/Subscription Billing/App/Billing/Tables/BillingLineArchive.Table.al
@@ -200,6 +200,14 @@ table 8064 "Billing Line Archive"
{
}
}
+
+ fieldgroups
+ {
+ fieldgroup(DropDown; "Subscription Header No.", "Billing from", "Billing to", Amount)
+ {
+ }
+ }
+
internal procedure PostedDocumentExist(): Boolean
var
SalesInvoiceHeader: Record "Sales Invoice Header";
diff --git a/src/Apps/W1/Subscription Billing/App/Service Commitments/Tables/SubscriptionLine.Table.al b/src/Apps/W1/Subscription Billing/App/Service Commitments/Tables/SubscriptionLine.Table.al
index c9c071b8e6..2230a875f9 100644
--- a/src/Apps/W1/Subscription Billing/App/Service Commitments/Tables/SubscriptionLine.Table.al
+++ b/src/Apps/W1/Subscription Billing/App/Service Commitments/Tables/SubscriptionLine.Table.al
@@ -611,6 +611,9 @@ table 8059 "Subscription Line"
{
}
+ key(Key4; "Supplier Reference Entry No.", "Subscription Line Start Date")
+ {
+ }
}
trigger OnInsert()
@@ -895,7 +898,7 @@ table 8059 "Subscription Line"
until BillingLine.Next() = 0;
end;
- internal procedure UpdateNextBillingDate(LastBillingToDate: Date)
+ procedure UpdateNextBillingDate(LastBillingToDate: Date)
var
NewNextBillingDate: Date;
OriginalInvoicedToDate: Date;
@@ -1786,9 +1789,7 @@ table 8059 "Subscription Line"
internal procedure SetUsageDataBillingFilters(var UsageDataBilling: Record "Usage Data Billing"; BillingFromDate: Date; BillingToDate: Date)
begin
- UsageDataBilling.SetRange("Subscription Header No.", Rec."Subscription Header No.");
UsageDataBilling.SetRange("Subscription Line Entry No.", Rec."Entry No.");
- UsageDataBilling.SetRange(Partner, Rec.Partner);
UsageDataBilling.SetRange("Usage Base Pricing", Enum::"Usage Based Pricing"::"Usage Quantity", Enum::"Usage Based Pricing"::"Unit Cost Surcharge");
UsageDataBilling.SetRange("Document Type", "Usage Based Billing Doc. Type"::None);
UsageDataBilling.SetFilter("Charge Start Date", '>=%1', BillingFromDate);
diff --git a/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Codeunits/GenericConnectorProcessing.Codeunit.al b/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Codeunits/GenericConnectorProcessing.Codeunit.al
index 0e6a2a0da7..c2c04aa19a 100644
--- a/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Codeunits/GenericConnectorProcessing.Codeunit.al
+++ b/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Codeunits/GenericConnectorProcessing.Codeunit.al
@@ -8,6 +8,10 @@ codeunit 8033 "Generic Connector Processing" implements "Usage Data Processing"
var
ImportAndProcessUsageData: Codeunit "Import And Process Usage Data";
CreateUsageDataBilling: Codeunit "Create Usage Data Billing";
+ ProgressTracker: Codeunit "Progress Tracker";
+ ProcessImportedLinesLbl: Label 'Processing imported usage data lines...';
+ CreateBillingDataLbl: Label 'Creating usage data billing...';
+ ImportEntryDetailLbl: Label 'Import Entry No. %1', Comment = '%1 = Usage Data Import Entry No.';
ProcessingSetupErr: Label 'You must specify either a reading/writing XMLport or a reading/writing codeunit.';
UsageDataLinesProcessingErr: Label 'Errors were found while processing the Usage Data Lines.';
NoDataFoundErr: Label 'No data found for processing step %1.', Comment = '%1 = Name of the processing step';
@@ -84,12 +88,18 @@ codeunit 8033 "Generic Connector Processing" implements "Usage Data Processing"
GenericImportSettings: Record "Generic Import Settings";
UsageDataSupplierReference: Record "Usage Data Supplier Reference";
ErrorCount: Integer;
+ LineCounter: Integer;
+ DetailText: Text;
begin
UsageDataGenericImport.SetRange("Usage Data Import Entry No.", UsageDataImport."Entry No.");
+ this.ProgressTracker.StartActivity(ProcessImportedLinesLbl, UsageDataGenericImport.Count());
+ DetailText := StrSubstNo(ImportEntryDetailLbl, UsageDataImport."Entry No.");
+ GenericImportSettings.Get(UsageDataImport."Supplier No."); // Loop-invariant (constant Supplier No.) - read once instead of per row.
if UsageDataGenericImport.FindSet() then
repeat
+ LineCounter += 1;
+ this.ProgressTracker.UpdateProgress(LineCounter, DetailText);
UsageDataGenericImport.Validate("Processing Status", Enum::"Processing Status"::None);
- GenericImportSettings.Get(UsageDataImport."Supplier No.");
CreateUsageDataCustomers(GenericImportSettings, UsageDataGenericImport, UsageDataSupplierReference, UsageDataImport."Supplier No.");
CreateUsageDataSubscriptions(GenericImportSettings, UsageDataGenericImport, UsageDataSupplierReference, UsageDataImport);
@@ -109,6 +119,7 @@ codeunit 8033 "Generic Connector Processing" implements "Usage Data Processing"
UsageDataImport.SetErrorReason(StrSubstNo(NoDataFoundErr, UsageDataImport."Processing Step"));
UsageDataImport.Modify(false);
end;
+ this.ProgressTracker.Finish();
if ErrorCount <> 0 then
ImportAndProcessUsageData.SetError(UsageDataImport, UsageDataLinesProcessingErr);
@@ -203,6 +214,8 @@ codeunit 8033 "Generic Connector Processing" implements "Usage Data Processing"
UsageDataBilling: Record "Usage Data Billing";
UsageDataGenericImport: Record "Usage Data Generic Import";
ConfirmManagement: Codeunit "Confirm Management";
+ LineCounter: Integer;
+ DetailText: Text;
begin
UsageDataGenericImport.SetRange("Usage Data Import Entry No.", UsageDataImport."Entry No.");
UsageDataGenericImport.SetRange("Processing Status", "Processing Status"::Error);
@@ -216,18 +229,25 @@ codeunit 8033 "Generic Connector Processing" implements "Usage Data Processing"
UsageDataGenericImport.SetRange("Processing Status");
end;
+ this.ProgressTracker.StartActivity(CreateBillingDataLbl, UsageDataGenericImport.Count());
+ DetailText := StrSubstNo(ImportEntryDetailLbl, UsageDataImport."Entry No.");
if UsageDataGenericImport.FindSet() then
repeat
+ LineCounter += 1;
+ this.ProgressTracker.UpdateProgress(LineCounter, DetailText);
CreateUsageDataBilling.CollectServiceCommitments(TempServiceCommitment, UsageDataGenericImport."Subscription Header No.", UsageDataGenericImport."Supp. Subscription End Date");
SetUsageDataGenericImportError(UsageDataGenericImport, '');
- if not CheckServiceCommitments(UsageDataGenericImport, TempServiceCommitment) then
+ if not CheckServiceCommitments(UsageDataGenericImport, TempServiceCommitment) then begin
+ this.ProgressTracker.Finish();
exit;
+ end;
CreateUsageDataBilling.CreateUsageDataBillingFromTempServiceCommitments(TempServiceCommitment, UsageDataImport."Supplier No.", UsageDataGenericImport);
until UsageDataGenericImport.Next() = 0
else begin
UsageDataImport.SetErrorReason(StrSubstNo(NoDataFoundErr, UsageDataImport."Processing Step"));
UsageDataImport.Modify(false);
end;
+ this.ProgressTracker.Finish();
end;
procedure UpdateImportStatus(var UsageDataImport: Record "Usage Data Import")
diff --git a/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Codeunits/ProcessUsageDataBilling.Codeunit.al b/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Codeunits/ProcessUsageDataBilling.Codeunit.al
index 42d937ddf5..ff29371843 100644
--- a/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Codeunits/ProcessUsageDataBilling.Codeunit.al
+++ b/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Codeunits/ProcessUsageDataBilling.Codeunit.al
@@ -11,6 +11,9 @@ codeunit 8026 "Process Usage Data Billing"
UsageDataSupplier: Record "Usage Data Supplier";
DateTimeManagement: Codeunit "Date Time Management";
ContractItemMgt: Codeunit "Sub. Contracts Item Management";
+ ProgressTracker: Codeunit "Progress Tracker";
+ ProcessBillingLbl: Label 'Processing usage based billing...';
+ ImportEntryDetailLbl: Label 'Import Entry No. %1', Comment = '%1 = Usage Data Import Entry No.';
DoesNotExistErr: Label 'No data found for processing step %1.', Comment = '%1=Name of the processing step';
ProcessServiceCommitmentProcedureNameLbl: Label 'ProcessServiceCommitment', Locked = true;
UsageBasedPricingOptionNotImplementedErr: Label 'Unknown option %1 for %2.\\Object Type: %3 Object Name: %4, Procedure: %5', Comment = '%1=Format("Calculation Base Type"), %2 = Fieldcaption for "Calculation Base Type", %3 = Object Type, %4 = Object Name, %5 = Procedure Name';
@@ -30,6 +33,8 @@ codeunit 8026 "Process Usage Data Billing"
var
UsageDataBilling: Record "Usage Data Billing";
SubscriptionLineEntryNoList: List of [Integer];
+ Counter: Integer;
+ DetailText: Text;
begin
OnBeforeProcessUsageDataBilling(UsageDataImport);
if UsageDataImport."Processing Status" = Enum::"Processing Status"::Closed then
@@ -46,13 +51,18 @@ codeunit 8026 "Process Usage Data Billing"
until UsageDataBilling.Next() = 0;
UsageDataBilling.SetRange(Partner);
+ this.ProgressTracker.StartActivity(ProcessBillingLbl, UsageDataBilling.Count());
+ DetailText := StrSubstNo(ImportEntryDetailLbl, UsageDataImport."Entry No.");
if UsageDataBilling.FindSet() then
repeat
+ Counter += 1;
+ this.ProgressTracker.UpdateProgress(Counter, DetailText);
if SubscriptionLineEntryNoList.IndexOf(UsageDataBilling."Subscription Line Entry No.") = 0 then begin
SubscriptionLineEntryNoList.Add(UsageDataBilling."Subscription Line Entry No.");
ProcessServiceCommitment(UsageDataBilling);
end;
until UsageDataBilling.Next() = 0;
+ this.ProgressTracker.Finish();
end else begin
UsageDataBilling.SetRange("Subscription Contract No.");
if UsageDataBilling.FindFirst() then begin
@@ -197,8 +207,8 @@ codeunit 8026 "Process Usage Data Billing"
UnitPrice := UnitCost;
UpdateServiceObjectQuantity(ServiceCommitment."Subscription Header No.", NewServiceObjectQuantity);
- //Note: Service commitment will be recalculated if the quantity in service object changes
- Commit();
+ // Note: the Subscription Line is recalculated (within the same transaction) when the quantity
+ // in the Subscription Header changes, so it is re-read here to pick up the recalculated values.
ServiceCommitment.Get(ServiceCommitment."Entry No.");
UpdateServiceCommitment(LastUsageDataBilling, ServiceCommitment, UnitPrice, UnitCost, CurrencyCode);
if UsageDataBilling.Rebilling then begin
diff --git a/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Codeunits/UsageBasedContrSubscribers.Codeunit.al b/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Codeunits/UsageBasedContrSubscribers.Codeunit.al
index 96c800ef1a..224e311374 100644
--- a/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Codeunits/UsageBasedContrSubscribers.Codeunit.al
+++ b/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Codeunits/UsageBasedContrSubscribers.Codeunit.al
@@ -240,34 +240,6 @@ codeunit 8028 "Usage Based Contr. Subscribers"
until UsageDataBilling.Next() = 0;
end;
- [EventSubscriber(ObjectType::Codeunit, Codeunit::"Sales Documents", OnAfterInsertBillingLineArchiveOnMoveBillingLineToBillingLineArchive, '', false, false)]
- local procedure UpdateUsageDataBillingWithBillingArchiveLineSalesDocuments(var BillingLineArchive: Record "Billing Line Archive"; BillingLine: Record "Billing Line")
- var
- UsageDataBilling: Record "Usage Data Billing";
- ServiceCommitment: Record "Subscription Line";
- begin
- if not ServiceCommitment.Get(BillingLine."Subscription Line Entry No.") then
- exit;
- if not ServiceCommitment."Usage Based Billing" then
- exit;
- UsageDataBilling.SetRange("Billing Line Entry No.", BillingLine."Entry No.");
- UsageDataBilling.ModifyAll("Billing Line Entry No.", BillingLineArchive."Entry No.", false);
- end;
-
- [EventSubscriber(ObjectType::Codeunit, Codeunit::"Purchase Documents", OnAfterInsertBillingLineArchiveOnMoveBillingLineToBillingLineArchive, '', false, false)]
- local procedure UpdateUsageDataBillingWithBillingArchiveLinePurchaseDocuments(var BillingLineArchive: Record "Billing Line Archive"; BillingLine: Record "Billing Line")
- var
- UsageDataBilling: Record "Usage Data Billing";
- ServiceCommitment: Record "Subscription Line";
- begin
- if not ServiceCommitment.Get(BillingLine."Subscription Line Entry No.") then
- exit;
- if not ServiceCommitment."Usage Based Billing" then
- exit;
- UsageDataBilling.SetRange("Billing Line Entry No.", BillingLine."Entry No.");
- UsageDataBilling.ModifyAll("Billing Line Entry No.", BillingLineArchive."Entry No.", false);
- end;
-
[EventSubscriber(ObjectType::Table, Database::"Purchase Line", OnAfterDeleteEvent, '', false, false)]
local procedure DeleteOrResetRelatedUsageBillingLinesOnAfterDeletePurchaseLineEvent(Rec: Record "Purchase Line"; RunTrigger: Boolean)
var
@@ -330,18 +302,4 @@ codeunit 8028 "Usage Based Contr. Subscribers"
until UsageDataBilling.Next() = 0;
end;
- [EventSubscriber(ObjectType::Table, Database::"Billing Line", OnAfterDeleteEvent, '', false, false)]
- local procedure RemoveBillingLineNoFromRelatedUsageData(Rec: Record "Billing Line"; RunTrigger: Boolean)
- var
- UsageDataBilling: Record "Usage Data Billing";
- begin
- if not RunTrigger then
- exit;
- if Rec.IsTemporary then
- exit;
-
- UsageDataBilling.SetRange("Billing Line Entry No.", Rec."Entry No.");
- UsageDataBilling.ModifyAll("Billing Line Entry No.", 0, false);
- end;
-
}
diff --git a/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Tables/UsageDataBilling.Table.al b/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Tables/UsageDataBilling.Table.al
index 760ebec406..b845b800a0 100644
--- a/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Tables/UsageDataBilling.Table.al
+++ b/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Tables/UsageDataBilling.Table.al
@@ -205,6 +205,9 @@ table 8006 "Usage Data Billing"
{
Caption = 'Billing Line Entry No.';
TableRelation =
+ if (Partner = const(Customer), "Document Type" = filter(None)) "Billing Line"."Entry No."
+ where(Partner = const(Customer))
+ else
if (Partner = const(Customer), "Document Type" = filter(Invoice)) "Billing Line"."Entry No."
where(Partner = const(Customer), "Document Type" = const(Invoice), "Document No." = field("Document No."))
else
@@ -217,6 +220,9 @@ table 8006 "Usage Data Billing"
if (Partner = const(Customer), "Document Type" = filter("Posted Credit Memo")) "Billing Line Archive"."Entry No."
where(Partner = const(Customer), "Document Type" = const("Credit Memo"), "Document No." = field("Document No."))
else
+ if (Partner = const(Vendor), "Document Type" = filter(None)) "Billing Line"."Entry No."
+ where(Partner = const(Vendor))
+ else
if (Partner = const(Vendor), "Document Type" = filter(Invoice)) "Billing Line"."Entry No."
where(Partner = const(Vendor), "Document Type" = const(Invoice), "Document No." = field("Document No."))
else
@@ -300,6 +306,19 @@ table 8006 "Usage Data Billing"
SumIndexFields = Quantity, Amount;
MaintainSiftIndex = true;
}
+ key(key2; "Subscription Contract No.", "Subscription Contract Line No.")
+ {
+ }
+ key(key3; "Subscription Line Entry No.", "Document Type", "Charge End Date")
+ {
+ SumIndexFields = Quantity, Amount, "Cost Amount";
+ }
+ key(key4; "Document No.", "Document Type")
+ {
+ }
+ key(key5; "Billing Line Entry No.")
+ {
+ }
}
trigger OnInsert()
@@ -344,7 +363,7 @@ table 8006 "Usage Data Billing"
ServiceCommitment.Modify();
end;
- internal procedure SetReason(ReasonText: Text)
+ procedure SetReason(ReasonText: Text)
var
TextManagement: Codeunit "Text Management";
RRef: RecordRef;
@@ -360,7 +379,7 @@ table 8006 "Usage Data Billing"
end;
end;
- internal procedure InitFrom(UsageDataImportEntryNo: Integer; SubscriptionHeaderNo: Code[20]; ProductID: Text[80]; ProductName: Text[100];
+ procedure InitFrom(UsageDataImportEntryNo: Integer; SubscriptionHeaderNo: Code[20]; ProductID: Text[80]; ProductName: Text[100];
BillingPeriodStartDate: Date; BillingPeriodEndDate: Date; NewQuantity: Decimal)
begin
Rec.Init();
@@ -455,23 +474,23 @@ table 8006 "Usage Data Billing"
end;
end;
- internal procedure FilterOnUsageDataImportAndServiceCommitment(UsageDataImportEntryNo: Integer; ServiceCommitment: Record "Subscription Line")
+ procedure FilterOnUsageDataImportAndServiceCommitment(UsageDataImportEntryNo: Integer; ServiceCommitment: Record "Subscription Line")
begin
Rec.SetRange("Usage Data Import Entry No.", UsageDataImportEntryNo);
Rec.FilterOnServiceCommitment(ServiceCommitment);
end;
- internal procedure FilterOnServiceCommitment(ServiceCommitment: Record "Subscription Line")
+ procedure FilterOnServiceCommitment(ServiceCommitment: Record "Subscription Line")
begin
Rec.SetRange("Subscription Line Entry No.", ServiceCommitment."Entry No.");
end;
- internal procedure UpdateChargedPeriod()
+ procedure UpdateChargedPeriod()
begin
"Charged Period (Days)" := Rec."Charge End Date" - Rec."Charge Start Date" + 1;
end;
- internal procedure ShowReason()
+ procedure ShowReason()
var
TextManagement: Codeunit "Text Management";
RRef: RecordRef;
@@ -481,7 +500,7 @@ table 8006 "Usage Data Billing"
TextManagement.ShowFieldText(RRef, FieldNo(Reason));
end;
- internal procedure GetReason(): Text
+ procedure GetReason(): Text
var
TextManagement: Codeunit "Text Management";
RRef: RecordRef;
@@ -491,7 +510,7 @@ table 8006 "Usage Data Billing"
exit(TextManagement.ReadBlobText(RRef, FieldNo(Reason)));
end;
- internal procedure ShowRelatedDocuments(var UsageBasedBilling: Record "Usage Data Billing"; DocumentType: Option Contract,"Contract Invoices","Posted Contract Invoices"; ServicePartner: Enum "Service Partner")
+ procedure ShowRelatedDocuments(var UsageBasedBilling: Record "Usage Data Billing"; DocumentType: Option Contract,"Contract Invoices","Posted Contract Invoices"; ServicePartner: Enum "Service Partner")
begin
UsageBasedBilling.SetRange(Partner, ServicePartner);
case DocumentType of
@@ -579,7 +598,7 @@ table 8006 "Usage Data Billing"
Page.Run(Page::"Posted Purchase Invoices", PurchInvHeader);
end;
- internal procedure MarkPurchaseHeaderFromUsageDataBilling(var UsageDataBilling: Record "Usage Data Billing"; var PurchaseHeader: Record "Purchase Header")
+ procedure MarkPurchaseHeaderFromUsageDataBilling(var UsageDataBilling: Record "Usage Data Billing"; var PurchaseHeader: Record "Purchase Header")
begin
UsageDataBilling.SetRange("Document Type", "Usage Based Billing Doc. Type"::Invoice);
UsageDataBilling.SetFilter("Document No.", '<>%1', '');
@@ -627,14 +646,14 @@ table 8006 "Usage Data Billing"
SalesHeader.MarkedOnly(true);
end;
- internal procedure FilterOnDocumentTypeAndDocumentNo(ServicePartner: Enum "Service Partner"; UsageBasedBillingDocType: Enum "Usage Based Billing Doc. Type"; DocumentNo: Code[20])
+ procedure FilterOnDocumentTypeAndDocumentNo(ServicePartner: Enum "Service Partner"; UsageBasedBillingDocType: Enum "Usage Based Billing Doc. Type"; DocumentNo: Code[20])
begin
Rec.SetRange(Partner, ServicePartner);
Rec.SetRange("Document Type", UsageBasedBillingDocType);
Rec.SetRange("Document No.", DocumentNo);
end;
- internal procedure SaveDocumentValues(UsageBasedBillingDocType: Enum "Usage Based Billing Doc. Type"; DocumentNo: Code[20]; DocumentEntryNo: Integer; BillingLineEntryNo: Integer)
+ procedure SaveDocumentValues(UsageBasedBillingDocType: Enum "Usage Based Billing Doc. Type"; DocumentNo: Code[20]; DocumentEntryNo: Integer; BillingLineEntryNo: Integer)
begin
Rec."Document Type" := UsageBasedBillingDocType;
Rec."Document No." := DocumentNo;
@@ -644,12 +663,12 @@ table 8006 "Usage Data Billing"
OnAfterSaveDocumentValues(Rec);
end;
- internal procedure IsPartnerVendor(): Boolean
+ procedure IsPartnerVendor(): Boolean
begin
exit(Rec.Partner = Rec.Partner::Vendor);
end;
- internal procedure IsPartnerCustomer(): Boolean
+ procedure IsPartnerCustomer(): Boolean
begin
exit(Rec.Partner = Rec.Partner::Customer);
end;
@@ -661,7 +680,7 @@ table 8006 "Usage Data Billing"
Rec.SetRange("Subscription Contract Line No.", EntryNo);
end;
- internal procedure FilterDocumentWithLine(ServicePartner: Enum "Service Partner"; DocumentType: Enum "Usage Based Billing Doc. Type"; DocumentNo: Code[20]; EntryNo: Integer)
+ procedure FilterDocumentWithLine(ServicePartner: Enum "Service Partner"; DocumentType: Enum "Usage Based Billing Doc. Type"; DocumentNo: Code[20]; EntryNo: Integer)
begin
Rec.FilterOnDocumentTypeAndDocumentNo(ServicePartner, DocumentType, DocumentNo);
Rec.SetRange("Document Line No.", EntryNo);
@@ -682,75 +701,75 @@ table 8006 "Usage Data Billing"
Rec.SetRange("Subscription Line Entry No.", EntryNo);
end;
- internal procedure ShowForContractLine(ServicePartner: Enum "Service Partner"; ContractNo: Code[20]; EntryNo: Integer)
+ procedure ShowForContractLine(ServicePartner: Enum "Service Partner"; ContractNo: Code[20]; EntryNo: Integer)
begin
FilterContractLine(ServicePartner, ContractNo, EntryNo);
Page.RunModal(Page::"Usage Data Billings", Rec);
end;
- internal procedure ShowForDocuments(ServicePartner: Enum "Service Partner"; DocumentType: Enum "Usage Based Billing Doc. Type"; DocumentNo: Code[20]; EntryNo: Integer)
+ procedure ShowForDocuments(ServicePartner: Enum "Service Partner"; DocumentType: Enum "Usage Based Billing Doc. Type"; DocumentNo: Code[20]; EntryNo: Integer)
begin
FilterDocumentWithLine(ServicePartner, DocumentType, DocumentNo, EntryNo);
Page.RunModal(Page::"Usage Data Billings", Rec);
end;
- internal procedure ShowForSalesDocuments(DocumentType: Enum "Sales Document Type"; DocumentNo: Code[20]; EntryNo: Integer)
+ procedure ShowForSalesDocuments(DocumentType: Enum "Sales Document Type"; DocumentNo: Code[20]; EntryNo: Integer)
begin
ShowForDocuments(Enum::"Service Partner"::Customer, UsageBasedDocTypeConv.ConvertSalesDocTypeToUsageBasedBillingDocType(DocumentType), DocumentNo, EntryNo);
end;
- internal procedure ShowForPurchaseDocuments(DocumentType: Enum "Purchase Document Type"; DocumentNo: Code[20]; EntryNo: Integer)
+ procedure ShowForPurchaseDocuments(DocumentType: Enum "Purchase Document Type"; DocumentNo: Code[20]; EntryNo: Integer)
begin
ShowForDocuments(Enum::"Service Partner"::Vendor, UsageBasedDocTypeConv.ConvertPurchaseDocTypeToUsageBasedBillingDocType(DocumentType), DocumentNo, EntryNo);
end;
- internal procedure ShowForRecurringBilling(ServiceObjectNo: Code[20]; ServCommEntryNo: Integer; DocumentType: Enum "Rec. Billing Document Type"; DocumentNo: Code[20])
+ procedure ShowForRecurringBilling(ServiceObjectNo: Code[20]; ServCommEntryNo: Integer; DocumentType: Enum "Rec. Billing Document Type"; DocumentNo: Code[20])
begin
FilterBillingLine(ServiceObjectNo, ServCommEntryNo, UsageBasedDocTypeConv.ConvertRecurringBillingDocTypeToUsageBasedBillingDocType(DocumentType), DocumentNo);
Page.RunModal(Page::"Usage Data Billings", Rec);
end;
- internal procedure ShowForServiceCommitments(ServicePartner: Enum "Service Partner"; ServiceObjectNo: Code[20]; EntryNo: Integer)
+ procedure ShowForServiceCommitments(ServicePartner: Enum "Service Partner"; ServiceObjectNo: Code[20]; EntryNo: Integer)
begin
FilterServiceCommitmentLine(ServicePartner, ServiceObjectNo, EntryNo);
Page.RunModal(Page::"Usage Data Billings", Rec);
end;
- internal procedure ExistForContractLine(ServicePartner: Enum "Service Partner"; ContractNo: Code[20]; EntryNo: Integer): Boolean
+ procedure ExistForContractLine(ServicePartner: Enum "Service Partner"; ContractNo: Code[20]; EntryNo: Integer): Boolean
begin
FilterContractLine(ServicePartner, ContractNo, EntryNo);
exit(not Rec.IsEmpty());
end;
- internal procedure ExistForDocuments(ServicePartner: Enum "Service Partner"; DocumentType: Enum "Usage Based Billing Doc. Type"; DocumentNo: Code[20]; EntryNo: Integer): Boolean
+ procedure ExistForDocuments(ServicePartner: Enum "Service Partner"; DocumentType: Enum "Usage Based Billing Doc. Type"; DocumentNo: Code[20]; EntryNo: Integer): Boolean
begin
FilterDocumentWithLine(ServicePartner, DocumentType, DocumentNo, EntryNo);
exit(not Rec.IsEmpty());
end;
- internal procedure ExistForSalesDocuments(DocumentType: Enum "Sales Document Type"; DocumentNo: Code[20]; EntryNo: Integer): Boolean
+ procedure ExistForSalesDocuments(DocumentType: Enum "Sales Document Type"; DocumentNo: Code[20]; EntryNo: Integer): Boolean
begin
exit(ExistForDocuments(Enum::"Service Partner"::Customer, UsageBasedDocTypeConv.ConvertSalesDocTypeToUsageBasedBillingDocType(DocumentType), DocumentNo, EntryNo));
end;
- internal procedure ExistForPurchaseDocuments(DocumentType: Enum "Purchase Document Type"; DocumentNo: Code[20]; EntryNo: Integer): Boolean
+ procedure ExistForPurchaseDocuments(DocumentType: Enum "Purchase Document Type"; DocumentNo: Code[20]; EntryNo: Integer): Boolean
begin
exit(ExistForDocuments(Enum::"Service Partner"::Vendor, UsageBasedDocTypeConv.ConvertPurchaseDocTypeToUsageBasedBillingDocType(DocumentType), DocumentNo, EntryNo));
end;
- internal procedure ExistForRecurringBilling(ServiceObjectNo: Code[20]; ServCommEntryNo: Integer; DocumentType: Enum "Rec. Billing Document Type"; DocumentNo: Code[20]): Boolean
+ procedure ExistForRecurringBilling(ServiceObjectNo: Code[20]; ServCommEntryNo: Integer; DocumentType: Enum "Rec. Billing Document Type"; DocumentNo: Code[20]): Boolean
begin
FilterBillingLine(ServiceObjectNo, ServCommEntryNo, UsageBasedDocTypeConv.ConvertRecurringBillingDocTypeToUsageBasedBillingDocType(DocumentType), DocumentNo);
exit(not Rec.IsEmpty());
end;
- internal procedure ExistForServiceCommitments(ServicePartner: Enum "Service Partner"; ServiceObjectNo: Code[20]; EntryNo: Integer): Boolean
+ procedure ExistForServiceCommitments(ServicePartner: Enum "Service Partner"; ServiceObjectNo: Code[20]; EntryNo: Integer): Boolean
begin
FilterServiceCommitmentLine(ServicePartner, ServiceObjectNo, EntryNo);
exit(not Rec.IsEmpty());
end;
- internal procedure UpdateRebilling()
+ procedure UpdateRebilling()
var
UsageDataBillingMetadata: Record "Usage Data Billing Metadata";
begin
@@ -762,7 +781,7 @@ table 8006 "Usage Data Billing"
Rec.Rebilling := not UsageDataBillingMetadata.IsEmpty;
end;
- internal procedure InsertMetadata()
+ procedure InsertMetadata()
var
UsageDataBillingMetadata: Record "Usage Data Billing Metadata";
begin
@@ -773,7 +792,7 @@ table 8006 "Usage Data Billing"
UsageDataBillingMetadata.ModifyAll(Rebilling, Rec.Rebilling, false);
end;
- internal procedure SetMetadataAsInvoiced()
+ procedure SetMetadataAsInvoiced()
var
UsageDataBillingMetadata: Record "Usage Data Billing Metadata";
begin
@@ -785,12 +804,12 @@ table 8006 "Usage Data Billing"
UsageDataBillingMetadata.ModifyAll(Invoiced, true, false);
end;
- internal procedure IsInvoiced(): Boolean
+ procedure IsInvoiced(): Boolean
begin
exit((Rec."Document Type" <> "Usage Based Billing Doc. Type"::None) and (Rec."Document No." <> ''));
end;
- internal procedure GetPrintoutDescription() Description: Text[100]
+ procedure GetPrintoutDescription() Description: Text[100]
begin
if ShouldPrintProductName() then
Description := "Product Name"
@@ -799,7 +818,7 @@ table 8006 "Usage Data Billing"
OnAfterGetPrintoutDescription(Rec, Description);
end;
- internal procedure ShouldPrintProductName() Result: Boolean
+ procedure ShouldPrintProductName() Result: Boolean
var
ServiceContractSetup: Record "Subscription Contract Setup";
begin
diff --git a/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Tables/UsageDataBillingMetadata.Table.al b/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Tables/UsageDataBillingMetadata.Table.al
index 4b006806a0..8d42ea46f3 100644
--- a/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Tables/UsageDataBillingMetadata.Table.al
+++ b/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Tables/UsageDataBillingMetadata.Table.al
@@ -72,7 +72,7 @@ table 8021 "Usage Data Billing Metadata"
}
}
- internal procedure InsertFromUsageDataBilling(UsageDataBilling: Record "Usage Data Billing")
+ procedure InsertFromUsageDataBilling(UsageDataBilling: Record "Usage Data Billing")
var
ServiceCommitment: Record "Subscription Line";
NewOriginalInvoicedToDate: Date;
@@ -102,7 +102,7 @@ table 8021 "Usage Data Billing Metadata"
Insert(true);
end;
- internal procedure FilterOnServiceCommitment(ServiceCommitmentEntryNo: Integer)
+ procedure FilterOnServiceCommitment(ServiceCommitmentEntryNo: Integer)
begin
Rec.SetRange("Subscription Line Entry No.", ServiceCommitmentEntryNo);
end;
diff --git a/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Tables/UsageDataBlob.Table.al b/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Tables/UsageDataBlob.Table.al
index 74906a6fb1..f1418467a4 100644
--- a/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Tables/UsageDataBlob.Table.al
+++ b/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Tables/UsageDataBlob.Table.al
@@ -93,7 +93,7 @@ table 8011 "Usage Data Blob"
Rec.Insert(false);
end;
- internal procedure ImportFromFile(InStream: InStream; FileName: Text)
+ procedure ImportFromFile(InStream: InStream; FileName: Text)
var
OutStream: OutStream;
begin
@@ -106,7 +106,7 @@ table 8011 "Usage Data Blob"
Rec.Modify(false);
end;
- internal procedure ShowReason()
+ procedure ShowReason()
var
TextManagement: Codeunit "Text Management";
RRef: RecordRef;
@@ -116,7 +116,7 @@ table 8011 "Usage Data Blob"
TextManagement.ShowFieldText(RRef, FieldNo(Reason));
end;
- internal procedure ExportData()
+ procedure ExportData()
var
InStream: InStream;
FileName: Text;
diff --git a/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Tables/UsageDataGenericImport.Table.al b/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Tables/UsageDataGenericImport.Table.al
index d4c6732391..d738ba4abb 100644
--- a/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Tables/UsageDataGenericImport.Table.al
+++ b/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Tables/UsageDataGenericImport.Table.al
@@ -306,6 +306,9 @@ table 8018 "Usage Data Generic Import"
{
Clustered = true;
}
+ key(Key2; "Usage Data Import Entry No.", "Processing Status")
+ {
+ }
}
trigger OnInsert()
@@ -349,7 +352,7 @@ table 8018 "Usage Data Generic Import"
end;
end;
- internal procedure SetReason(ReasonText: Text)
+ procedure SetReason(ReasonText: Text)
var
TextManagement: Codeunit "Text Management";
RRef: RecordRef;
@@ -365,7 +368,7 @@ table 8018 "Usage Data Generic Import"
end;
end;
- internal procedure ShowReason()
+ procedure ShowReason()
var
TextManagement: Codeunit "Text Management";
RRef: RecordRef;
@@ -375,7 +378,7 @@ table 8018 "Usage Data Generic Import"
TextManagement.ShowFieldText(RRef, FieldNo(Reason));
end;
- internal procedure GetCurrencyCode(): Code[10]
+ procedure GetCurrencyCode(): Code[10]
var
GLSetup: Record "General Ledger Setup";
begin
diff --git a/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Tables/UsageDataImport.Table.al b/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Tables/UsageDataImport.Table.al
index 4e2d523fd0..43aa2b2db1 100644
--- a/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Tables/UsageDataImport.Table.al
+++ b/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Tables/UsageDataImport.Table.al
@@ -151,13 +151,13 @@ table 8013 "Usage Data Import"
UsageDataBlob.DeleteAll(false);
end;
- internal procedure SetStatus(ProcessingStatus: Enum Microsoft.SubscriptionBilling."Processing Status")
+ procedure SetStatus(ProcessingStatus: Enum Microsoft.SubscriptionBilling."Processing Status")
begin
Rec.Validate("Processing Status", ProcessingStatus);
Rec.Modify(true);
end;
- internal procedure SetReason(ReasonText: Text)
+ procedure SetReason(ReasonText: Text)
var
TextManagement: Codeunit "Text Management";
RRef: RecordRef;
@@ -173,7 +173,7 @@ table 8013 "Usage Data Import"
end;
end;
- internal procedure DeleteUsageDataBillingLines()
+ procedure DeleteUsageDataBillingLines()
var
UsageDataSupplier: Record "Usage Data Supplier";
UsageDataProcessing: Interface "Usage Data Processing";
@@ -206,13 +206,13 @@ table 8013 "Usage Data Import"
UsageDataBilling.DeleteAll(true);
end;
- internal procedure SetErrorReason(ErrorText: Text)
+ procedure SetErrorReason(ErrorText: Text)
begin
Rec.Validate("Processing Status", Enum::"Processing Status"::Error);
Rec.SetReason(ErrorText);
end;
- internal procedure ShowReason()
+ procedure ShowReason()
var
TextManagement: Codeunit "Text Management";
RRef: RecordRef;
@@ -255,7 +255,7 @@ table 8013 "Usage Data Import"
end;
end;
- internal procedure ProcessUsageDataImport(var UsageDataImport: Record "Usage Data Import"; ProcessingStep: Enum "Processing Step")
+ procedure ProcessUsageDataImport(var UsageDataImport: Record "Usage Data Import"; ProcessingStep: Enum "Processing Step")
var
UsageDataImport2: Record "Usage Data Import";
begin
@@ -282,7 +282,7 @@ table 8013 "Usage Data Import"
until UsageDataImport.Next() = 0;
end;
- internal procedure CollectCustomerContractsAndCreateInvoices(var UsageDataImport: Record "Usage Data Import")
+ procedure CollectCustomerContractsAndCreateInvoices(var UsageDataImport: Record "Usage Data Import")
var
CustomerContractFilter: Text;
CustomerContractLineFilter: Text;
@@ -295,7 +295,7 @@ table 8013 "Usage Data Import"
CreateCustomerInvoices(CustomerContractFilter, CustomerContractLineFilter);
end;
- internal procedure CollectVendorContractsAndCreateInvoices(var UsageDataImport: Record "Usage Data Import")
+ procedure CollectVendorContractsAndCreateInvoices(var UsageDataImport: Record "Usage Data Import")
var
VendorContractFilter: Text;
VendorContractLineFilter: Text;
@@ -358,7 +358,7 @@ table 8013 "Usage Data Import"
UsageBasedContrSubscribers.CreateContractInvoicesFromUsageDataImport(Enum::"Service Partner"::Vendor, VendorContractFilter, VendorContractLineFilter, '');
end;
- internal procedure ShowRelatedDocuments(var UsageDataImport: Record "Usage Data Import"; ServicePartner: Enum "Service Partner"; DocumentType: Option Contract,"Contract Invoices","Posted Contract Invoices")
+ procedure ShowRelatedDocuments(var UsageDataImport: Record "Usage Data Import"; ServicePartner: Enum "Service Partner"; DocumentType: Option Contract,"Contract Invoices","Posted Contract Invoices")
var
UsageBasedBilling: Record "Usage Data Billing";
begin
diff --git a/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Tables/UsageDataSuppSubscription.Table.al b/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Tables/UsageDataSuppSubscription.Table.al
index 9e0949c176..98f3e8026a 100644
--- a/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Tables/UsageDataSuppSubscription.Table.al
+++ b/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Tables/UsageDataSuppSubscription.Table.al
@@ -250,13 +250,13 @@ table 8016 "Usage Data Supp. Subscription"
Error(SubscriptionCannotBeConnectedErr);
end;
- internal procedure SetErrorReason(ErrorText: Text)
+ procedure SetErrorReason(ErrorText: Text)
begin
Rec.Validate("Processing Status", Enum::"Processing Status"::Error);
Rec.SetReason(ErrorText);
end;
- internal procedure ShowReason()
+ procedure ShowReason()
var
TextManagement: Codeunit "Text Management";
RRef: RecordRef;
@@ -266,7 +266,7 @@ table 8016 "Usage Data Supp. Subscription"
TextManagement.ShowFieldText(RRef, FieldNo(Reason));
end;
- internal procedure SetReason(ReasonText: Text)
+ procedure SetReason(ReasonText: Text)
var
TextManagement: Codeunit "Text Management";
RRef: RecordRef;
@@ -282,7 +282,7 @@ table 8016 "Usage Data Supp. Subscription"
end;
end;
- internal procedure ResetProcessingStatus(var UsageDataSubscription: Record "Usage Data Supp. Subscription")
+ procedure ResetProcessingStatus(var UsageDataSubscription: Record "Usage Data Supp. Subscription")
begin
if UsageDataSubscription.FindSet(true) then
repeat
@@ -291,7 +291,7 @@ table 8016 "Usage Data Supp. Subscription"
until UsageDataSubscription.Next() = 0;
end;
- internal procedure ResetServiceObjectAndServiceCommitment()
+ procedure ResetServiceObjectAndServiceCommitment()
begin
if Rec."Entry No." = 0 then
exit;
@@ -300,14 +300,14 @@ table 8016 "Usage Data Supp. Subscription"
Rec.Modify(true);
end;
- internal procedure FindForSupplierReference(SupplierNo: Code[20]; SupplierReference: Text[80]): Boolean
+ procedure FindForSupplierReference(SupplierNo: Code[20]; SupplierReference: Text[80]): Boolean
begin
Rec.SetRange("Supplier No.", SupplierNo);
Rec.SetRange("Supplier Reference", SupplierReference);
exit(Rec.FindFirst());
end;
- internal procedure UpdateSubscriptionHeaderNoInImportedData()
+ procedure UpdateSubscriptionHeaderNoInImportedData()
var
UsageDataSupplier: Record "Usage Data Supplier";
UsageDataProcessing: Interface "Usage Data Processing";
diff --git a/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Tables/UsageDataSupplier.Table.al b/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Tables/UsageDataSupplier.Table.al
index b321c0409f..3a2d8eb7f0 100644
--- a/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Tables/UsageDataSupplier.Table.al
+++ b/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Tables/UsageDataSupplier.Table.al
@@ -100,7 +100,7 @@ table 8014 "Usage Data Supplier"
UsageDataProcessing.DeleteSupplierData(Rec);
end;
- internal procedure OpenSupplierSettings()
+ procedure OpenSupplierSettings()
var
UsageDataProcessing: Interface "Usage Data Processing";
begin
diff --git a/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Tables/UsageDataSupplierReference.Table.al b/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Tables/UsageDataSupplierReference.Table.al
index f1ffe3be36..b304de0533 100644
--- a/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Tables/UsageDataSupplierReference.Table.al
+++ b/src/Apps/W1/Subscription Billing/App/Usage Based Billing/Tables/UsageDataSupplierReference.Table.al
@@ -58,7 +58,7 @@ table 8015 "Usage Data Supplier Reference"
}
}
- internal procedure CreateSupplierReference(SupplierNo: Code[20]; SupplierReference: Text[80]; ReferenceType: Enum "Usage Data Reference Type")
+ procedure CreateSupplierReference(SupplierNo: Code[20]; SupplierReference: Text[80]; ReferenceType: Enum "Usage Data Reference Type")
begin
if Rec.FindSupplierReference(SupplierNo, SupplierReference, ReferenceType) then begin
Rec.Reset();
@@ -73,13 +73,13 @@ table 8015 "Usage Data Supplier Reference"
Rec.Reset();
end;
- internal procedure FindSupplierReference(SupplierNo: Code[20]; SupplierReference: Text[80]; ReferenceType: Enum "Usage Data Reference Type"): Boolean
+ procedure FindSupplierReference(SupplierNo: Code[20]; SupplierReference: Text[80]; ReferenceType: Enum "Usage Data Reference Type"): Boolean
begin
Rec.FilterUsageDataSupplierReference(SupplierNo, SupplierReference, ReferenceType);
exit(Rec.FindFirst());
end;
- internal procedure FilterUsageDataSupplierReference(SupplierNo: Code[20]; SupplierReference: Text[80]; ReferenceType: Enum "Usage Data Reference Type")
+ procedure FilterUsageDataSupplierReference(SupplierNo: Code[20]; SupplierReference: Text[80]; ReferenceType: Enum "Usage Data Reference Type")
begin
Rec.SetCurrentKey("Supplier No.", "Supplier Reference", Type);
Rec.SetRange("Supplier No.", SupplierNo);