Codeunit 110 Consolidate Business Units, source in 29
Source29
src/Layers/W1/BaseApp/Finance/Consolidation/ConsolidateBusinessUnits.Codeunit.al270 lines, Copyright (c) Microsoft Corporation. MIT
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
namespace Microsoft.Finance.Consolidation;
using Microsoft.Finance.GeneralLedger.Journal;
using Microsoft.Finance.GeneralLedger.Setup;
using Microsoft.Foundation.Period;
using System.Threading;
using System.Utilities;
/// <summary>
/// Manages the consolidation workflow for multiple business units with validation, process orchestration, and job queue integration.
/// Coordinates the consolidation process across multiple business units with proper validation and error handling.
/// </summary>
/// <remarks>
/// Primary orchestrator for multi-business unit consolidation operations supporting both interactive and background processing.
/// Integrates with Job Queue system for scheduled consolidation operations and provides comprehensive validation.
/// Extensibility: Integration events for custom validation, pre/post consolidation processing, and business unit selection.
/// </remarks>
codeunit 110 "Consolidate Business Units"
{
Permissions = tabledata "Job Queue Entry" = rid;
var
EmptyDateRangeErr: Label 'You must specify the starting date and the ending date for the consolidation.';
ClosingDateErr: Label 'The starting date or the ending date is a closing date, and they are not the same.';
YouMustCreateFiscalYearErr: Label 'You must create a new fiscal year for the consolidation company.';
ConsolidationPeriodOutsideFiscalYearErr: Label 'The consolidation period %1 .. %2 is outside the fiscal year %3 .. %4 in the consolidation company. Do you want to continue?', Comment = '%1 - Starting date, %2 - Ending date, %3 - Starting date, %4 - Ending date';
PleaseSpecifyErr: Label '"%1" is mandatory in General Ledger Setup, but it is not specified.', Comment = '%1 - Field name';
PleaseSpecifyNoSeriesErr: Label 'Specify a No. Series in the General Journal Batch %1.', Comment = '%1 - The code of the general journal batch';
BusinessUnitStartingDateLaterErr: Label 'The ending date is earlier than the starting date for the business unit %1.', Comment = '%1 - The code of the business unit';
PleaseSpecifyDocNoErr: Label 'Please specify a document number for the consolidation journal.';
ConfirmConsolidationDatesForBusinessUnitMsg: Label 'The business unit %1 has the date range %2 .. %3 configured. Do you want to consolidate the period %4 .. %5?', Comment = '%1 - Code of the business unit, %2 - starting date, %3 - ending date, %4 - starting date, %5 - ending date';
SelectOneBusinessUnitErr: Label 'Select at least one business unit to consolidate.';
MaxNumberOfDaysInConsolidationErr: Label 'Maximum number of days in consolidation is %1.', Comment = '%1 - The maximum number of days in consolidation';
FollowingCompaniesHaveNoAccessErr: Label 'The business units %1 have not been granted access. Select them and use the action "Grant Access" to authenticate into these companies.', Comment = '%1 comma separated names of the business units'' codes';
LogRequestsOnlyForTroubleshootingMsg: Label 'A business unit to consolidate has "Log requests" enabled. This is recommended only for troubleshooting purposes and should be disabled to avoid data corruption. Do you want to continue?';
ConsolidationJQECodeTok: Label 'CONSOLID', Locked = true;
ConsolidationJQECategoryDescriptionLbl: Label 'Importing Consolidation Data';
internal procedure ValidateAndRunConsolidation(var ConsolidationProcess: Record "Consolidation Process" temporary; var BusinessUnit: Record "Business Unit" temporary): Boolean
var
ConsolidationSetup: Record "Consolidation Setup";
begin
ConsolidationSetup.GetOrCreateWithDefaults();
ValidateConsolidationParameters(ConsolidationProcess, BusinessUnit, false);
exit(RunConsolidation(ConsolidationProcess, BusinessUnit));
end;
internal procedure ValidateDatesForConsolidation(StartingDate: Date; EndingDate: Date; AskConfirmation: Boolean)
var
Consolidate: Codeunit Consolidate;
begin
if (StartingDate = 0D) or (EndingDate = 0D) then
Error(EmptyDateRangeErr);
if (StartingDate = ClosingDate(StartingDate)) or (EndingDate = ClosingDate(EndingDate)) then
if StartingDate <> EndingDate then
Error(ClosingDateErr);
if not Consolidate.ValidateMaxNumberOfDaysInConsolidation(StartingDate, EndingDate) then
Error(MaxNumberOfDaysInConsolidationErr, Consolidate.MaxNumberOfDaysInConsolidation());
ValidateDatesToBeInSameFiscalYear(StartingDate, EndingDate, AskConfirmation);
end;
internal procedure ValidateConsolidationParameters(var ConsolidationProcess: Record "Consolidation Process" temporary; var BusinessUnit: Record "Business Unit" temporary; AskConfirmation: Boolean)
var
GeneralLedgerSetup: Record "General Ledger Setup";
begin
GeneralLedgerSetup.Get();
ValidateDatesForConsolidation(ConsolidationProcess."Starting Date", ConsolidationProcess."Ending Date", AskConfirmation);
ValidateJournalForConsolidation(GeneralLedgerSetup."Journal Templ. Name Mandatory", ConsolidationProcess."Journal Template Name", ConsolidationProcess."Journal Batch Name", ConsolidationProcess."Document No.");
ValidateBusinessUnitsToConsolidate(BusinessUnit, AskConfirmation);
ValidateDatesForBusinessUnits(BusinessUnit, ConsolidationProcess."Starting Date", ConsolidationProcess."Ending Date", AskConfirmation);
end;
internal procedure GetLastConsolidationEndingDate(BusinessUnit: Record "Business Unit"): Date
var
BusUnitInConsProcess: Record "Bus. Unit In Cons. Process";
begin
BusUnitInConsProcess.SetRange(Status, BusUnitInConsProcess.Status::Finished);
BusUnitInConsProcess.SetRange("Business Unit Code", BusinessUnit.Code);
BusUnitInConsProcess.SetCurrentKey(SystemCreatedAt);
BusUnitInConsProcess.SetAscending(SystemCreatedAt, false);
BusUnitInConsProcess.SetAutoCalcFields("Ending Date");
if not BusUnitInConsProcess.FindFirst() then
exit(0D);
exit(BusUnitInConsProcess."Ending Date");
end;
internal procedure BusinessUnitConsolidationProcessesInDateRange(BusinessUnit: Record "Business Unit"; StartingDate: Date; EndingDate: Date): Boolean
var
BusUnitConsProcess: Record "Bus. Unit In Cons. Process";
begin
BusUnitConsProcess.SetAutoCalcFields("Starting Date", "Ending Date");
BusUnitConsProcess.SetRange("Business Unit Code", BusinessUnit.Code);
BusUnitConsProcess.SetRange(Status, BusUnitConsProcess.Status::Finished);
BusUnitConsProcess.SetRange("Ending Date", StartingDate, EndingDate);
if not BusUnitConsProcess.IsEmpty() then
exit(true);
BusUnitConsProcess.SetRange("Ending Date");
BusUnitConsProcess.SetRange("Starting Date", StartingDate, EndingDate);
if not BusUnitConsProcess.IsEmpty() then
exit(true);
BusUnitConsProcess.SetRange("Starting Date", 0D, StartingDate);
BusUnitConsProcess.SetFilter("Ending Date", '>%1', EndingDate);
if not BusUnitConsProcess.IsEmpty() then
exit(true);
exit(false);
end;
internal procedure ValidateBusinessUnitsToConsolidate(var BusinessUnit: Record "Business Unit" temporary; AskConfirmation: Boolean)
var
ImportConsolidationFromAPI: Codeunit "Import Consolidation from API";
CompaniesWithNoAccess: Text;
LogRequestsEnabled: Boolean;
begin
BusinessUnit.SetRange(Consolidate, true);
if not BusinessUnit.FindSet() then
Error(SelectOneBusinessUnitErr);
repeat
if BusinessUnit."Default Data Import Method" = BusinessUnit."Default Data Import Method"::API then
if not ImportConsolidationFromAPI.IsStoredTokenValidForBusinessUnit(BusinessUnit) then begin
if CompaniesWithNoAccess <> '' then
CompaniesWithNoAccess += ', ';
CompaniesWithNoAccess += BusinessUnit.Code;
end;
LogRequestsEnabled := LogRequestsEnabled or BusinessUnit."Log Requests";
until BusinessUnit.Next() = 0;
if CompaniesWithNoAccess <> '' then
Error(FollowingCompaniesHaveNoAccessErr, CompaniesWithNoAccess);
if LogRequestsEnabled and AskConfirmation then
if not Confirm(LogRequestsOnlyForTroubleshootingMsg) then
Error('');
end;
local procedure RunConsolidation(var TempConsolidationProcess: Record "Consolidation Process" temporary; var BusinessUnit: Record "Business Unit" temporary): Boolean
var
ConsolidationProcess: Record "Consolidation Process";
BusUnitInConsProcess: Record "Bus. Unit In Cons. Process";
JobQueueEntry: Record "Job Queue Entry";
Success: Boolean;
begin
BusinessUnit.SetRange(Consolidate, true);
if not BusinessUnit.FindSet() then
exit;
ConsolidationProcess.TransferFields(TempConsolidationProcess);
ConsolidationProcess.Status := ConsolidationProcess.Status::NotStarted;
ConsolidationProcess.Insert();
repeat
BusUnitInConsProcess."Consolidation Process Id" := ConsolidationProcess.Id;
BusUnitInConsProcess."Business Unit Code" := BusinessUnit.Code;
BusUnitInConsProcess."Average Exchange Rate" := BusinessUnit."Income Currency Factor";
BusUnitInConsProcess."Closing Exchange Rate" := BusinessUnit."Balance Currency Factor";
BusUnitInConsProcess."Last Closing Exchange Rate" := BusinessUnit."Last Balance Currency Factor";
BusUnitInConsProcess."Currency Exchange Rate Table" := BusinessUnit."Currency Exchange Rate Table";
BusUnitInConsProcess."Currency Code" := BusinessUnit."Currency Code";
BusUnitInConsProcess.Insert();
until BusinessUnit.Next() = 0;
JobQueueEntry.ID := CreateGuid();
JobQueueEntry."Object Type to Run" := JobQueueEntry."Object Type to Run"::Codeunit;
JobQueueEntry."Object ID to Run" := Codeunit::"Import and Consolidate";
JobQueueEntry."Maximum No. of Attempts to Run" := 1;
JobQueueEntry."Recurring Job" := false;
EnsureJobQueueCategoryExists(ConsolidationJQECodeTok, ConsolidationJQECategoryDescriptionLbl);
JobQueueEntry."Job Queue Category Code" := ConsolidationJQECodeTok;
JobQueueEntry."Record ID to Process" := ConsolidationProcess.RecordId;
JobQueueEntry.Status := JobQueueEntry.Status::Ready;
JobQueueEntry.Insert();
Commit();
Success := Codeunit.Run(Codeunit::"Job Queue Dispatcher", JobQueueEntry);
if not Success then begin
ConsolidationProcess.SetRecFilter();
ConsolidationProcess.FindFirst();
ConsolidationProcess.Error := CopyStr(GetLastErrorText(), 1, MaxStrLen(ConsolidationProcess.Error));
ConsolidationProcess.Status := ConsolidationProcess.Status::Failed;
ConsolidationProcess.Modify();
BusUnitInConsProcess.Reset();
BusUnitInConsProcess.SetRange("Consolidation Process Id", ConsolidationProcess.Id);
if BusUnitInConsProcess.FindSet() then
repeat
if BusUnitInConsProcess.Status = BusUnitInConsProcess.Status::ImportingData then begin
BusUnitInConsProcess.Status := BusUnitInConsProcess.Status::Error;
BusUnitInConsProcess.Modify();
end;
until BusUnitInConsProcess.Next() = 0;
end;
JobQueueEntry.SetRecFilter();
if JobQueueEntry.FindFirst() then
JobQueueEntry.Delete();
Commit();
exit(Success);
end;
local procedure EnsureJobQueueCategoryExists(JobQueueCategoryCode: Code[10]; Description: Text)
var
JobQueueCategory: Record "Job Queue Category";
begin
if JobQueueCategory.Get(JobQueueCategoryCode) then
exit;
JobQueueCategory.Code := JobQueueCategoryCode;
JobQueueCategory.Description := CopyStr(Description, 1, MaxStrLen(JobQueueCategory.Description));
JobQueueCategory.Insert();
end;
local procedure ValidateDatesForBusinessUnits(var BusinessUnit: Record "Business Unit" temporary; StartingDate: Date; EndingDate: Date; AskConfirmation: Boolean)
var
ConfirmManagement: Codeunit "Confirm Management";
begin
if (StartingDate <> NormalDate(StartingDate)) and (EndingDate <> NormalDate(EndingDate)) then
exit;
BusinessUnit.SetRange(Consolidate, true);
BusinessUnit.FindSet();
repeat
if (BusinessUnit."Starting Date" <> 0D) or (BusinessUnit."Ending Date" <> 0D) then begin
BusinessUnit.TestField("Starting Date");
BusinessUnit.TestField("Ending Date");
if BusinessUnit."Starting Date" > BusinessUnit."Ending Date" then
Error(BusinessUnitStartingDateLaterErr, BusinessUnit.Code);
if AskConfirmation and ((StartingDate < BusinessUnit."Starting Date") or (EndingDate > BusinessUnit."Ending Date")) then
if not ConfirmManagement.GetResponseOrDefault(StrSubstNo(ConfirmConsolidationDatesForBusinessUnitMsg, BusinessUnit.Code, BusinessUnit."Starting Date", BusinessUnit."Ending Date", StartingDate, EndingDate), true) then
Error('');
end
until BusinessUnit.Next() = 0;
end;
local procedure ValidateDatesToBeInSameFiscalYear(StartingDate: Date; EndingDate: Date; AskConfirmation: Boolean)
var
AccountingPeriod: Record "Accounting Period";
ConfirmManagement: Codeunit "Confirm Management";
FiscalYearStartDate: Date;
FiscalYearEndDate: Date;
begin
AccountingPeriod.SetRange(Closed, false);
AccountingPeriod.SetRange("New Fiscal Year", true);
AccountingPeriod.SetCurrentKey("Starting Date");
AccountingPeriod.Ascending(true);
if not AccountingPeriod.FindSet() then
exit;
FiscalYearStartDate := AccountingPeriod."Starting Date";
if AccountingPeriod.Next() = 0 then
Error(YouMustCreateFiscalYearErr);
FiscalYearEndDate := CalcDate('<-1D>', AccountingPeriod."Starting Date");
if AskConfirmation and ((StartingDate < FiscalYearStartDate) or (EndingDate > FiscalYearEndDate)) then
if not ConfirmManagement.GetResponseOrDefault(StrSubstNo(ConsolidationPeriodOutsideFiscalYearErr, StartingDate, EndingDate, FiscalYearStartDate, FiscalYearEndDate), true) then
Error('');
end;
internal procedure ValidateJournalForConsolidation(JournalTemplateNameMandatory: Boolean; JournalTemplateName: Code[10]; JournalBatchName: Code[10]; DocumentNo: Code[20])
var
GenJournalTemplate: Record "Gen. Journal Template";
GenJournalBatch: Record "Gen. Journal Batch";
begin
if JournalTemplateNameMandatory then begin
if JournalTemplateName = '' then
Error(PleaseSpecifyErr, GenJournalTemplate.TableCaption);
if JournalBatchName = '' then
Error(PleaseSpecifyErr, GenJournalBatch.TableCaption);
GenJournalBatch.Get(JournalTemplateName, JournalBatchName);
if GenJournalBatch."No. Series" = '' then
Error(PleaseSpecifyNoSeriesErr, JournalTemplateName + '-' + JournalBatchName);
exit;
end;
if DocumentNo = '' then
Error(PleaseSpecifyDocNoErr);
end;
}