Codeunit 1248 Process Bank Acc. Rec Lines

App
Base Application
Namespace
Microsoft.Bank.Reconciliation
Versions
17-28

Procedures, 6Events, 3

Versions171819202122232425262728

Source29

Source in 29

src/Layers/W1/BaseApp/Bank/Reconciliation/ProcessBankAccRecLines.Codeunit.al252 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.Bank.Reconciliation;

using Microsoft.Bank.BankAccount;
using System.IO;
using System.Telemetry;

/// <summary>
/// Processes bank account reconciliation lines during bank statement import and data exchange operations.
/// This codeunit handles the transformation of imported bank statement data into Business Central reconciliation
/// lines, managing data validation, field mapping, and integration with the data exchange framework.
/// Supports both bank reconciliation and payment application workflows with comprehensive error handling
/// and progress tracking for large statement imports.
/// </summary>
/// <remarks>
/// Key features include bank statement import processing, data exchange framework integration, field mapping
/// validation, progress reporting, and telemetry logging. The codeunit manages the technical aspects of
/// converting external bank statement formats into standardized reconciliation line records through
/// configurable mapping definitions. Provides error handling for format validation, data quality issues,
/// and import failures while maintaining audit trails and processing statistics.
/// </remarks>
codeunit 1248 "Process Bank Acc. Rec Lines"
{
    Permissions = TableData "Data Exch." = rimd;
    TableNo = "Bank Acc. Reconciliation Line";
    EventSubscriberInstance = Manual;

    trigger OnRun()
    var
        DataExch: Record "Data Exch.";
        ProcessDataExch: Codeunit "Process Data Exch.";
        RecRef: RecordRef;
    begin
        DataExch.Get(Rec."Data Exch. Entry No.");
        RecRef.GetTable(Rec);
        ProcessDataExch.ProcessAllLinesColumnMapping(DataExch, RecRef);
    end;

    var
        ProgressWindowMsg: Label 'Please wait while the operation is being completed.';
        BankAccountRecImportedBankStatementLinesCountMsg: Label 'Number of imported lines in bank statement: %1', Locked = true;
        BankAccountRecCategoryLbl: Label 'AL Bank Account Rec', Locked = true;
        PaymentRecCategoryLbl: Label 'AL Payment Reconciliation', Locked = true;
        InvalidFileFormatErr: Label 'The format of the chosen file is incompatible with the bank statement import format %2 specified on bank account %1. You must choose another file or change the bank statement import format on bank account %1.', Comment = '%1 - bank account code, %2 - name of a bank statement import format';
        BankAccountNo: Code[20];

    /// <summary>
    /// Imports bank statement data into a bank account reconciliation document.
    /// Processes imported data through the data exchange framework, creating reconciliation lines
    /// with proper field mapping and validation. Handles both bank reconciliation and payment
    /// application statement types with progress tracking and telemetry logging.
    /// </summary>
    /// <param name="BankAccRecon">Bank account reconciliation document to receive the imported lines.</param>
    /// <param name="DataExch">Data exchange record containing the imported bank statement data.</param>
    /// <returns>True if import completed successfully; false if errors occurred during processing.</returns>
    procedure ImportBankStatement(BankAccRecon: Record "Bank Acc. Reconciliation"; DataExch: Record "Data Exch."): Boolean
    var
        DataExchDef: Record "Data Exch. Def";
        DataExchMapping: Record "Data Exch. Mapping";
        DataExchLineDef: Record "Data Exch. Line Def";
        TempBankAccReconLine: Record "Bank Acc. Reconciliation Line" temporary;
        FeatureTelemetry: Codeunit "Feature Telemetry";
        ProgressWindow: Dialog;
        NumberOfLinesImported: Integer;
        StartDateTime: DateTime;
        FinishDateTime: DateTime;
    begin
        case BankAccRecon."Statement Type" of
            BankAccRecon."Statement Type"::"Bank Reconciliation":
                FeatureTelemetry.LogUptake('0000JLJ', BankAccRecon.GetBankReconciliationTelemetryFeatureName(), Enum::"Feature Uptake Status"::Used);
            BankAccRecon."Statement Type"::"Payment Application":
                FeatureTelemetry.LogUptake('0000KMD', BankAccRecon.GetPaymentRecJournalTelemetryFeatureName(), Enum::"Feature Uptake Status"::Used);
        end;
        PrepareDataExch(BankAccRecon, DataExch, DataExchDef);

        if not DataExch.ImportToDataExch(DataExchDef) then
            exit(false);

        StartDateTime := CurrentDateTime();
        ProgressWindow.Open(ProgressWindowMsg);

        CreateBankAccRecLineTemplate(TempBankAccReconLine, BankAccRecon, DataExch);
        DataExchLineDef.SetRange("Data Exch. Def Code", DataExchDef.Code);
        DataExchLineDef.FindFirst();

        DataExchMapping.Get(DataExchDef.Code, DataExchLineDef.Code, DATABASE::"Bank Acc. Reconciliation Line");

        if DataExchMapping."Pre-Mapping Codeunit" <> 0 then
            CODEUNIT.Run(DataExchMapping."Pre-Mapping Codeunit", TempBankAccReconLine);

        DataExchMapping.TestField("Mapping Codeunit");
        CODEUNIT.Run(DataExchMapping."Mapping Codeunit", TempBankAccReconLine);

        if DataExchMapping."Post-Mapping Codeunit" <> 0 then
            CODEUNIT.Run(DataExchMapping."Post-Mapping Codeunit", TempBankAccReconLine);

        NumberOfLinesImported := 0;
        InsertNonReconciledOrImportedLines(TempBankAccReconLine, GetLastStatementLineNo(BankAccRecon), NumberOfLinesImported);

        ProgressWindow.Close();
        FinishDateTime := CurrentDateTime();
        OnLogTelemetryAfterImportBankStatement(BankAccRecon, NumberOfLinesImported);
        LogTelemetryOnBankAccRecOnAfterImportBankStatement(BankAccRecon, NumberOfLinesImported, StartDateTime, FinishDateTime);
        case BankAccRecon."Statement Type" of
            BankAccRecon."Statement Type"::"Bank Reconciliation":
                FeatureTelemetry.LogUptake('0000JLK', BankAccRecon.GetBankReconciliationTelemetryFeatureName(), Enum::"Feature Uptake Status"::Used);
            BankAccRecon."Statement Type"::"Payment Application":
                FeatureTelemetry.LogUptake('0000KME', BankAccRecon.GetPaymentRecJournalTelemetryFeatureName(), Enum::"Feature Uptake Status"::Used);
        end;
        OnAfterImportBankStatement(TempBankAccReconLine, DataExch);
        exit(true);
    end;

    local procedure PrepareDataExch(BankAccRecon: Record "Bank Acc. Reconciliation"; var DataExch: Record "Data Exch."; var DataExchDef: Record "Data Exch. Def")
    var
        BankAcc: Record "Bank Account";
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforePrepareDataExch(BankAccRecon, DataExch, DataExchDef, IsHandled);
        if IsHandled then
            exit;

        BankAcc.Get(BankAccRecon."Bank Account No.");
        BankAcc.GetDataExchDef(DataExchDef);

        DataExch."Related Record" := BankAcc.RecordId;
        DataExch."Data Exch. Def Code" := DataExchDef.Code;
    end;

    procedure CreateBankAccRecLineTemplate(var BankAccReconLine: Record "Bank Acc. Reconciliation Line"; BankAccRecon: Record "Bank Acc. Reconciliation"; DataExch: Record "Data Exch.")
    begin
        BankAccReconLine.Init();
        BankAccReconLine."Statement Type" := BankAccRecon."Statement Type";
        BankAccReconLine."Statement No." := BankAccRecon."Statement No.";
        BankAccReconLine."Bank Account No." := BankAccRecon."Bank Account No.";
        BankAccReconLine."Data Exch. Entry No." := DataExch."Entry No.";
    end;

    procedure InsertNonReconciledOrImportedLines(var TempBankAccReconLine: Record "Bank Acc. Reconciliation Line" temporary; StatementLineNoOffset: Integer)
    var
        NumberOfLinesImported: Integer;
    begin
        InsertNonReconciledOrImportedLines(TempBankAccReconLine, StatementLineNoOffset, NumberOfLinesImported)
    end;

    procedure InsertNonReconciledOrImportedLines(var TempBankAccReconLine: Record "Bank Acc. Reconciliation Line" temporary; StatementLineNoOffset: Integer; var NumberOfLinesImported: Integer)
    var
        BankAccReconciliationLine: Record "Bank Acc. Reconciliation Line";
    begin
        if TempBankAccReconLine.FindSet() then
            repeat
                if TempBankAccReconLine.CanImport() then begin
                    BankAccReconciliationLine := TempBankAccReconLine;
                    BankAccReconciliationLine."Statement Line No." += StatementLineNoOffset;
                    BankAccReconciliationLine.Insert();
                    NumberOfLinesImported += 1;
                end;
            until TempBankAccReconLine.Next() = 0;
    end;

    procedure GetLastStatementLineNo(BankAccRecon: Record "Bank Acc. Reconciliation"): Integer
    var
        BankAccReconLine: Record "Bank Acc. Reconciliation Line";
    begin
        BankAccReconLine.SetRange("Statement Type", BankAccRecon."Statement Type");
        BankAccReconLine.SetRange("Statement No.", BankAccRecon."Statement No.");
        BankAccReconLine.SetRange("Bank Account No.", BankAccRecon."Bank Account No.");
        if BankAccReconLine.FindLast() then
            exit(BankAccReconLine."Statement Line No.");
        exit(0)
    end;

    local procedure LogTelemetryOnBankAccRecOnAfterImportBankStatement(var BankAccReconciliation: Record "Bank Acc. Reconciliation"; NumberOfLinesImported: Integer; StartDateTime: DateTime; FinishDateTime: DateTime)
    var
        Dimensions: Dictionary of [Text, Text];
        ImportDuration: BigInteger;
    begin
        ImportDuration := FinishDateTime - StartDateTime;
        Dimensions.Add('ImportStartTime', Format(StartDateTime, 0, 9));
        Dimensions.Add('ImportFinishTime', Format(FinishDateTime, 0, 9));
        Dimensions.Add('ImportDuration', Format(ImportDuration));
        Dimensions.Add('NumberOfLines', Format(NumberOfLinesImported));
        case BankAccReconciliation."Statement Type" of
            BankAccReconciliation."Statement Type"::"Bank Reconciliation":
                begin
                    Dimensions.Add('Category', BankAccountRecCategoryLbl);
                    Session.LogMessage('0000FJW', StrSubstNo(BankAccountRecImportedBankStatementLinesCountMsg, NumberOfLinesImported), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, Dimensions);
                end;
            BankAccReconciliation."Statement Type"::"Payment Application":
                begin
                    Dimensions.Add('Category', PaymentRecCategoryLbl);
                    Session.LogMessage('0000KMF', StrSubstNo(BankAccountRecImportedBankStatementLinesCountMsg, NumberOfLinesImported), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, Dimensions);
                end;
        end;
    end;

    internal procedure SetBankAccountNo(NewValue: Code[20])
    begin
        BankAccountNo := NewValue;
    end;

    local procedure GetBankAccountNo(): Code[20]
    begin
        exit(BankAccountNo);
    end;

    local procedure InvalidFileFormatError(var ErrorMessage: Text)
    var
        BankAccount: Record "Bank Account";
    begin
        if GetBankAccountNo() = '' then
            exit;

        if not BankAccount.Get(GetBankAccountNo()) then
            exit;

        ErrorMessage := StrSubstNo(InvalidFileFormatErr, BankAccount."No.", BankAccount."Bank Statement Import Format");
    end;

    [EventSubscriber(ObjectType::XmlPort, XmlPort::"Data Exch. Import - CSV", 'OnInvalidHeaderSetErrorMessage', '', false, false)]
    local procedure HandleOnInvalidHeaderSetErrorMessage(var ErrorMessage: Text);
    begin
        InvalidFileFormatError(ErrorMessage);
    end;

    [EventSubscriber(ObjectType::XmlPort, XmlPort::"Data Exch. Import - CSV", 'OnNoLinesFoundSetErrorMessage', '', false, false)]
    local procedure HandleOnNoLinesFoundSetErrorMessage(var ErrorMessage: Text);
    begin
        InvalidFileFormatError(ErrorMessage);
    end;

    [IntegrationEvent(false, false)]
    local procedure OnLogTelemetryAfterImportBankStatement(var BankAccReconciliation: Record "Bank Acc. Reconciliation"; NumberOfLinesImported: Integer)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnAfterImportBankStatement(BankAccReconciliationLine: Record "Bank Acc. Reconciliation Line"; DataExch: Record "Data Exch.")
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnBeforePrepareDataExch(BankAccRecon: Record "Bank Acc. Reconciliation"; var DataExch: Record "Data Exch."; var DataExchDef: Record "Data Exch. Def"; var IsHandled: Boolean)
    begin
    end;
}