Codeunit 85 Sales Post Batch via Job Queue, source in 29

Source29

src/Layers/W1/BaseApp/Sales/Posting/SalesPostBatchViaJobQueue.Codeunit.al322 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.Sales.Posting;

using Microsoft.EServices.EDocument;
using Microsoft.Foundation.Reporting;
using Microsoft.Sales.Document;
using Microsoft.Sales.History;
using Microsoft.Sales.Setup;
using System.Threading;
using System.Utilities;

/// <summary>
/// Posts multiple sales documents as a batch through the job queue for background processing.
/// </summary>
codeunit 85 "Sales Post Batch via Job Queue"
{
    TableNo = "Job Queue Entry";

    trigger OnRun()
    begin
        PostSalesBatch(Rec);
    end;

    var
        UnpostedDocumentsErr: Label '%1 sales documents out of %2 have errors during posting.', Comment = '%1 - number of documents with errors, %2 - total number of documents';
        UnprintedDocumentsErr: Label '%1 sales documents out of %2 have errors during printing.', Comment = '%1 - number of documents with errors, %2 - total number of documents';
        DefaultCategoryCodeLbl: Label 'SALESBCKGR', Locked = true;
        DefaultCategoryDescLbl: Label 'Def. Background Sales Posting', Locked = true;
        PostingDescriptionTxt: Label 'Post sales documents batch.';
        PostAndPrintDescriptionTxt: Label 'Post and print sales documents batch.';
        PrintingDescriptionTxt: Label 'Print Sales %1 No. %2', Comment = '%1 - document type, %2 - document no.';

    local procedure PostSalesBatch(var JobQueueEntry: Record "Job Queue Entry")
    var
        SalesReceivablesSetup: Record "Sales & Receivables Setup";
        SalesHeader: Record "Sales Header";
        ErrorMessageManagement: Codeunit "Error Message Management";
        SavedLockTimeout: Boolean;
        TotalDocumentsCount: Integer;
        ErrorPostDocumentsCount: Integer;
        ErrorPrintDocumentsCount: Integer;
    begin
        SalesReceivablesSetup.Get();
        SavedLockTimeout := LockTimeout;
        SalesHeader.SetRange("Job Queue Entry ID", JobQueueEntry.ID);
        SalesHeader.SetRange("Job Queue Status", SalesHeader."Job Queue Status"::"Scheduled for Posting");
        if SalesHeader.FindSet() then
            repeat
                TotalDocumentsCount += 1;
                SetJobQueueStatus(SalesHeader, SalesHeader."Job Queue Status"::Posting);
                OnPostSalesBatchOnBeforeRunSalesPost(SalesHeader);
                if not Codeunit.Run(Codeunit::"Sales-Post", SalesHeader) then begin
                    SetJobQueueStatus(SalesHeader, SalesHeader."Job Queue Status"::Error);
                    ErrorMessageManagement.LogLastError();
                    ErrorPostDocumentsCount += 1;
                end else begin
                    OnPostSalesBatchOnAfterRunSalesPost(SalesHeader);
                    SetJobQueueStatus(SalesHeader, SalesHeader."Job Queue Status"::" ");
                    if SalesReceivablesSetup."Post & Print with Job Queue" then
                        if SalesHeader."Print Posted Documents" then
                            if not PrintSalesDocument(SalesHeader, JobQueueEntry) then
                                ErrorPrintDocumentsCount += 1;
                end;
            until SalesHeader.Next() = 0;
        LockTimeout(SavedLockTimeout);

        if (ErrorPostDocumentsCount <> 0) or (ErrorPrintDocumentsCount <> 0) then
            ThrowErrorMessage(TotalDocumentsCount, ErrorPostDocumentsCount, ErrorPrintDocumentsCount);
    end;

    /// <summary>
    /// Enqueues a batch of sales documents for posting via the job queue.
    /// </summary>
    /// <param name="SalesHeader">Specifies the sales header records to be processed in the batch.</param>
    /// <param name="JobQueueEntry">Specifies the job queue entry to be used for batch processing.</param>
    procedure EnqueueSalesBatch(var SalesHeader: Record "Sales Header"; var JobQueueEntry: Record "Job Queue Entry")
    begin
        EnqueueJobQueueEntry(JobQueueEntry);
    end;

    local procedure EnqueueJobQueueEntry(var JobQueueEntry: Record "Job Queue Entry")
    var
        SalesReceivablesSetup: Record "Sales & Receivables Setup";
    begin
        SalesReceivablesSetup.Get();

        JobQueueEntry."Object Type to Run" := JobQueueEntry."Object Type to Run"::Codeunit;
        JobQueueEntry."Object ID to Run" := Codeunit::"Sales Post Batch via Job Queue";
        JobQueueEntry."Notify On Success" := SalesReceivablesSetup."Notify On Success";
        JobQueueEntry."Job Queue Category Code" := GetJobQueueCategoryCode();
        JobQueueEntry.Description := GetDescription();
        JobQueueEntry."User Session ID" := SessionId();
        Codeunit.Run(Codeunit::"Job Queue - Enqueue", JobQueueEntry);
    end;

    local procedure SetJobQueueStatus(var SalesHeader: Record "Sales Header"; NewStatus: Enum "Document Job Queue Status")
    begin
        SalesHeader.LockTable();
        if SalesHeader.Get(SalesHeader."Document Type", SalesHeader."No.") then begin
            SalesHeader."Job Queue Status" := NewStatus;
            SalesHeader.Modify();
            Commit();
        end;
    end;

    local procedure GetJobQueueCategoryCode(): Code[10]
    var
        SalesReceivablesSetup: Record "Sales & Receivables Setup";
        JobQueueCategory: Record "Job Queue Category";
    begin
        SalesReceivablesSetup.Get();
        if SalesReceivablesSetup."Job Queue Category Code" <> '' then
            exit(SalesReceivablesSetup."Job Queue Category Code");

        JobQueueCategory.InsertRec(
            CopyStr(DefaultCategoryCodeLbl, 1, MaxStrLen(JobQueueCategory.Code)),
            CopyStr(DefaultCategoryDescLbl, 1, MaxStrLen(JobQueueCategory.Description)));
        exit(JobQueueCategory.Code);
    end;

    local procedure GetDescription(): Text[250]
    var
        SalesReceivablesSetup: Record "Sales & Receivables Setup";
    begin
        SalesReceivablesSetup.Get();
        if SalesReceivablesSetup."Post & Print with Job Queue" then
            exit(PostAndPrintDescriptionTxt);
        exit(PostingDescriptionTxt);
    end;

    local procedure PrintSalesDocument(SalesHeader: Record "Sales Header"; JobQueueEntry: Record "Job Queue Entry") Result: Boolean
    begin
        case SalesHeader."Document Type" of
            SalesHeader."Document Type"::Order:
                begin
                    if SalesHeader.Ship then
                        Result := PrintShipmentDocument(SalesHeader, JobQueueEntry);
                    if SalesHeader.Invoice then
                        Result := Result or PrintInvoiceDocument(SalesHeader, JobQueueEntry);
                end;
            SalesHeader."Document Type"::Invoice:
                Result := PrintInvoiceDocument(SalesHeader, JobQueueEntry);
            SalesHeader."Document Type"::"Credit Memo":
                Result := PrintCrMemoDocument(SalesHeader, JobQueueEntry);
        end;
    end;

    local procedure PrintShipmentDocument(SalesHeader: Record "Sales Header"; JobQueueEntry: Record "Job Queue Entry") Result: Boolean
    var
        SalesShipmentHeader: Record "Sales Shipment Header";
        RecRef: RecordRef;
    begin
        SalesShipmentHeader."No." := SalesHeader."Last Shipping No.";
        SalesShipmentHeader.SetRecFilter();
        RecRef.GetTable(SalesShipmentHeader);
        Result := PrintDocument(Enum::"Report Selection Usage"::"S.Shipment", RecRef, SalesHeader, JobQueueEntry);
    end;

    local procedure PrintInvoiceDocument(SalesHeader: Record "Sales Header"; JobQueueEntry: Record "Job Queue Entry") Result: Boolean
    var
        SalesInvoiceHeader: Record "Sales Invoice Header";
        RecRef: RecordRef;
    begin
        if SalesHeader."Last Posting No." = '' then
            SalesInvoiceHeader."No." := SalesHeader."No."
        else
            SalesInvoiceHeader."No." := SalesHeader."Last Posting No.";
        SalesInvoiceHeader.SetRecFilter();
        RecRef.GetTable(SalesInvoiceHeader);
        Result := PrintDocument(Enum::"Report Selection Usage"::"S.Invoice", RecRef, SalesHeader, JobQueueEntry);
    end;

    local procedure PrintCrMemoDocument(SalesHeader: Record "Sales Header"; JobQueueEntry: Record "Job Queue Entry") Result: Boolean
    var
        SalesCrMemoHeader: Record "Sales Cr.Memo Header";
        RecRef: RecordRef;
    begin
        if SalesHeader."Last Posting No." = '' then
            SalesCrMemoHeader."No." := SalesHeader."No."
        else
            SalesCrMemoHeader."No." := SalesHeader."Last Posting No.";
        SalesCrMemoHeader.SetRecFilter();
        RecRef.GetTable(SalesCrMemoHeader);
        Result := PrintDocument(Enum::"Report Selection Usage"::"S.Cr.Memo", RecRef, SalesHeader, JobQueueEntry);
    end;

    local procedure PrintDocument(ReportUsage: Enum "Report Selection Usage"; RecRef: RecordRef; SalesHeader: Record "Sales Header"; JobQueueEntry: Record "Job Queue Entry") Result: Boolean
    var
        ReportSelections: Record "Report Selections";
        ErrorMessageManagement: Codeunit "Error Message Management";
        PrintingErrorExists: Boolean;
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforePrintDocument(ReportUsage, RecRef, SalesHeader, JobQueueEntry, Result, IsHandled);
        if IsHandled then
            exit(Result);

        ReportSelections.Reset();
        ReportSelections.SetRange(Usage, ReportUsage);
        ReportSelections.FindSet();
        repeat
            if CheckReportId(ReportSelections) then begin
                if not PrintToPDF(ReportSelections."Report ID", RecRef, SalesHeader, JobQueueEntry) then begin
                    ErrorMessageManagement.LogLastError();
                    PrintingErrorExists := true;
                end;
            end else begin
                ErrorMessageManagement.LogLastError();
                PrintingErrorExists := true;
            end;
        until ReportSelections.Next() = 0;

        Result := not PrintingErrorExists;
    end;

    local procedure PrintToPDF(ReportId: Integer; RecRef: RecordRef; SalesHeader: Record "Sales Header"; JobQueueEntry: Record "Job Queue Entry"): Boolean
    var
        ReportInbox: Record "Report Inbox";
        OStream: OutStream;
        IsSuccess: Boolean;
        IsHandled: Boolean;
    begin
        ReportInbox.Init();
        ReportInbox."User ID" := JobQueueEntry."User ID";
        ReportInbox."Job Queue Log Entry ID" := JobQueueEntry.ID;
        ReportInbox."Report ID" := ReportID;
        ReportInbox.Description := CopyStr(StrSubstNo(PrintingDescriptionTxt, SalesHeader."Document Type", SalesHeader."No."), 1, MaxStrLen(ReportInbox.Description));
        ReportInbox."Report Output".CreateOutStream(OStream);
        IsHandled := false;
        IsSuccess := false;
        OnPrintToPDFOnBeforeReportRun(ReportId, RecRef, OStream, IsSuccess, IsHandled);
        if not IsHandled then
            IsSuccess := Report.SaveAs(ReportId, '', ReportFormat::Pdf, OStream, RecRef);
        if not IsSuccess then
            exit(false);
        ReportInbox."Created Date-Time" := RoundDateTime(CurrentDateTime, 60000);
        ReportInbox.Insert(true);
        OnPrintToPDFOnAfterReportInboxInsert(ReportInbox, SalesHeader, RecRef);

        exit(true);
    end;

    [TryFunction]
    local procedure CheckReportId(ReportSelections: Record "Report Selections")
    begin
        ReportSelections.TestField("Report ID");
    end;

    local procedure ThrowErrorMessage(TotalDocumentsCount: Integer; ErrorPostDocumentsCount: Integer; ErrorPrintDocumentsCount: Integer)
    var
        ErrorMessage: Text;
    begin
        Commit();
        if ErrorPostDocumentsCount <> 0 then
            ErrorMessage := StrSubstNo(UnpostedDocumentsErr, ErrorPostDocumentsCount, TotalDocumentsCount) + ' ';
        if ErrorPrintDocumentsCount <> 0 then
            ErrorMessage += StrSubstNo(UnprintedDocumentsErr, ErrorPrintDocumentsCount, TotalDocumentsCount);
        ErrorMessage := DelChr(ErrorMessage, '>', ' ');
        Error(ErrorMessage);
    end;

    /// <summary>
    /// Raised after inserting the report into the report inbox after printing to PDF.
    /// </summary>
    /// <param name="ReportInbox">The report inbox record that was inserted.</param>
    /// <param name="SalesHeader">The sales header for which the report was generated.</param>
    /// <param name="RecRef">The record reference to the posted document.</param>
    [IntegrationEvent(false, false)]
    local procedure OnPrintToPDFOnAfterReportInboxInsert(var ReportInbox: Record "Report Inbox"; var SalesHeader: Record "Sales Header"; var RecRef: RecordRef)
    begin
    end;

    /// <summary>
    /// Raised before running the report to generate the PDF.
    /// </summary>
    /// <param name="ReportId">The ID of the report to run.</param>
    /// <param name="RecRef">The record reference to the posted document.</param>
    /// <param name="OStream">The output stream for the PDF content.</param>
    /// <param name="IsSuccess">Returns whether the report generation was successful.</param>
    /// <param name="IsHandled">Set to true to skip the default report generation logic.</param>
    [IntegrationEvent(false, false)]
    local procedure OnPrintToPDFOnBeforeReportRun(ReportId: Integer; RecRef: RecordRef; var OStream: OutStream; var IsSuccess: Boolean; var IsHandled: Boolean)
    begin
    end;

    /// <summary>
    /// Raised before the Sales-Post codeunit runs for each document in the batch.
    /// </summary>
    /// <param name="SalesHeader">The sales header to be posted.</param>
    [IntegrationEvent(false, false)]
    local procedure OnPostSalesBatchOnBeforeRunSalesPost(var SalesHeader: Record "Sales Header")
    begin
    end;

    /// <summary>
    /// Raised after the Sales-Post codeunit has successfully run for each document in the batch.
    /// </summary>
    /// <param name="SalesHeader">The sales header that was posted.</param>
    [IntegrationEvent(false, false)]
    local procedure OnPostSalesBatchOnAfterRunSalesPost(var SalesHeader: Record "Sales Header")
    begin
    end;

    /// <summary>
    /// Raised before printing a document in the batch.
    /// </summary>
    /// <param name="ReportUsage">The report usage type for the document.</param>
    /// <param name="RecRef">The record reference to the posted document.</param>
    /// <param name="SalesHeader">The sales header from which the document was posted.</param>
    /// <param name="JobQueueEntry">The job queue entry processing the batch.</param>
    /// <param name="Result">Returns whether printing was successful.</param>
    /// <param name="IsHandled">Set to true to skip the default print logic.</param>
    [IntegrationEvent(false, false)]
    local procedure OnBeforePrintDocument(ReportUsage: Enum "Report Selection Usage"; RecRef: RecordRef; SalesHeader: Record "Sales Header"; JobQueueEntry: Record "Job Queue Entry"; var Result: Boolean; var IsHandled: Boolean)
    begin
    end;
}