Codeunit 1034 Job Planning Line - Calendar, source in 29

Source29

src/Layers/W1/BaseApp/Projects/Project/Planning/JobPlanningLineCalendar.Codeunit.al358 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.Projects.Project.Planning;

using Microsoft.CRM.Contact;
using Microsoft.CRM.Outlook;
using Microsoft.Projects.Project.Job;
using Microsoft.Projects.Resources.Resource;
using Microsoft.Sales.Customer;
using System;
using System.Email;
using System.Security.AccessControl;
using System.Utilities;

codeunit 1034 "Job Planning Line - Calendar"
{
    TableNo = "Job Planning Line";

    trigger OnRun()
    var
        LocalJobPlanningLine: Record "Job Planning Line";
    begin
        LocalJobPlanningLine.SetRange("Job No.", Rec."Job No.");
        LocalJobPlanningLine.SetRange("Job Task No.", Rec."Job Task No.");
        LocalJobPlanningLine.SetFilter("Line Type", '%1|%2', Rec."Line Type"::Budget, Rec."Line Type"::"Both Budget and Billable");
        LocalJobPlanningLine.SetRange(Type, Rec.Type::Resource);
        LocalJobPlanningLine.SetFilter("No.", '<>''''');
        if LocalJobPlanningLine.FindSet() then
            repeat
                SetPlanningLine(LocalJobPlanningLine);
                CreateAndSend();
            until LocalJobPlanningLine.Next() = 0
        else
            Message(NoPlanningLinesMsg);
    end;

    var
        JobPlanningLineCalendar: Record "Job Planning Line - Calendar";
        JobPlanningLine: Record "Job Planning Line";
        Job: Record Job;
        JobTask: Record "Job Task";
        Contact: Record Contact;
        Customer: Record Customer;
        ProjectManagerResource: Record Resource;
        Resource: Record Resource;

        AdditionalResourcesTxt: Label 'Additional Resources';
        SetPlanningLineErr: Label 'You must specify a project planning line before you can send the appointment.';
        DateTimeFormatTxt: Label '<Year4><Month,2><Day,2>T<Hours24,2><Minutes,2><Seconds,2>', Locked = true;
        ProdIDTxt: Label '//Microsoft Corporation//Dynamics 365//EN', Locked = true;
        NoPlanningLinesMsg: Label 'There are no applicable planning lines for this action.';
        SendToCalendarTelemetryTxt: Label 'Sending project planning line to calendar.', Locked = true;

    procedure SetPlanningLine(NewJobPlanningLine: Record "Job Planning Line")
    begin
        JobPlanningLine := NewJobPlanningLine;
        UpdateJob();
        UpdateJobTask();
        UpdateResource();

        OnAfterSetPlanningLine(NewJobPlanningLine, ProjectManagerResource);
    end;

    [Scope('OnPrem')]
    procedure CreateAndSend()
    var
        TempEmailItem: Record "Email Item" temporary;
        OfficeMgt: Codeunit "Office Management";
    begin
        if JobPlanningLine."No." = '' then
            Error(SetPlanningLineErr);

        if JobPlanningLineCalendar.ShouldSendCancellation(JobPlanningLine) then
            if CreateCancellation(TempEmailItem) then
                TempEmailItem.Send(true, Enum::"Email Scenario"::"Job Planning Line Calendar");

        if JobPlanningLineCalendar.ShouldSendRequest(JobPlanningLine) then
            if CreateRequest(TempEmailItem) then begin
                Session.LogMessage('0000ACX', SendToCalendarTelemetryTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', OfficeMgt.GetOfficeAddinTelemetryCategory());
                TempEmailItem.Send(true, Enum::"Email Scenario"::"Job Planning Line Calendar");
            end;
    end;

    [Scope('OnPrem')]
    procedure CreateRequest(var TempEmailItem: Record "Email Item" temporary): Boolean
    var
        Email: Text[80];
    begin
        if JobPlanningLine."No." = '' then
            Error(SetPlanningLineErr);

        Email := GetResourceEmail(JobPlanningLine."No.");

        if Email <> '' then begin
            JobPlanningLineCalendar.InsertOrUpdate(JobPlanningLine);
            GenerateEmail(TempEmailItem, Email, false);
            exit(true);
        end;
    end;

    [Scope('OnPrem')]
    procedure CreateCancellation(var TempEmailItem: Record "Email Item" temporary): Boolean
    var
        Email: Text[80];
    begin
        if JobPlanningLine."No." = '' then
            Error(SetPlanningLineErr);

        if not JobPlanningLineCalendar.HasBeenSent(JobPlanningLine) then
            exit(false);

        Email := GetResourceEmail(JobPlanningLineCalendar."Resource No.");
        if Email <> '' then begin
            GenerateEmail(TempEmailItem, Email, true);
            JobPlanningLineCalendar.Delete();
            exit(true);
        end;
    end;

    local procedure GenerateEmail(var TempEmailItem: Record "Email Item" temporary; RecipientEmail: Text[80]; Cancel: Boolean)
    var
        TempBlob: Codeunit "Temp Blob";
        Stream: OutStream;
        InStream: Instream;
        ICS: Text;
    begin
        ICS := GenerateICS(Cancel);
        TempBlob.CreateOutStream(Stream, TextEncoding::UTF8);
        Stream.Write(ICS);
        TempBlob.CreateInStream(InStream);

        TempEmailItem.Initialize();
        TempEmailItem.Subject := JobTask.Description;
        TempEmailItem.AddAttachment(InStream, StrSubstNo('%1.ics', JobTask.TableCaption()));
        TempEmailItem."Send to" := RecipientEmail;
    end;

    local procedure GenerateICS(Cancel: Boolean) ICS: Text
    var
        StringBuilder: DotNet StringBuilder;
        Location: Text;
        Summary: Text;
        Status: Text;
        Method: Text;
        Description: Text;
    begin
        Location := StrSubstNo('%1, %2, %3', Customer.Address, Customer.City, Customer."Country/Region Code");
        Summary := StrSubstNo('%1:%2:%3', JobPlanningLine."Job No.", JobPlanningLine."Job Task No.", JobPlanningLine."Line No.");

        if Cancel then begin
            Method := 'CANCEL';
            Status := 'CANCELLED';
        end else begin
            Method := 'REQUEST';
            Status := 'CONFIRMED';
        end;
        Description := GetDescription();

        StringBuilder := StringBuilder.StringBuilder();
        StringBuilder.AppendLine('BEGIN:VCALENDAR');
        StringBuilder.AppendLine('VERSION:2.0');
        StringBuilder.AppendLine('PRODID:-' + ProdIDTxt);
        StringBuilder.AppendLine('METHOD:' + Method);
        StringBuilder.AppendLine('BEGIN:VEVENT');
        StringBuilder.AppendLine('UID:' + DelChr(JobPlanningLineCalendar.UID, '<>', '{}'));
        StringBuilder.AppendLine('ORGANIZER:' + GetOrganizer());
        StringBuilder.AppendLine('LOCATION:' + Location);
        StringBuilder.AppendLine('DTSTART:' + GetStartDate());
        StringBuilder.AppendLine('DTEND:' + GetEndDate());
        StringBuilder.AppendLine('SUMMARY:' + Summary);
        StringBuilder.AppendLine('DESCRIPTION:' + Description);
        StringBuilder.AppendLine('X-ALT-DESC;FMTTYPE=' + GetHtmlDescription(Description));
        StringBuilder.AppendLine('SEQUENCE:' + Format(JobPlanningLineCalendar.Sequence));
        StringBuilder.AppendLine('STATUS:' + Status);
        StringBuilder.AppendLine('END:VEVENT');
        StringBuilder.AppendLine('END:VCALENDAR');

        ICS := StringBuilder.ToString();
    end;

    local procedure GetAdditionalResources() AdditionalResources: Text
    var
        LocalJobPlanningLine: Record "Job Planning Line";
        LocalResource: Record Resource;
    begin
        // Get all resources for the same job task.
        LocalJobPlanningLine.SetRange("Job No.", JobPlanningLine."Job No.");
        LocalJobPlanningLine.SetRange("Job Task No.", JobPlanningLine."Job Task No.");
        LocalJobPlanningLine.SetRange(Type, LocalJobPlanningLine.Type::Resource);
        LocalJobPlanningLine.SetFilter("Line Type", '%1|%2', LocalJobPlanningLine."Line Type"::Budget, LocalJobPlanningLine."Line Type"::"Both Budget and Billable");
        LocalJobPlanningLine.SetFilter("No.", '<>%1&<>''''', Resource."No.");
        if LocalJobPlanningLine.FindSet() then begin
            AdditionalResources += '\n\n' + AdditionalResourcesTxt + ':';
            repeat
                LocalResource.Get(LocalJobPlanningLine."No.");
                AdditionalResources +=
                    StrSubstNo('\n    (%1) %2 - %3', LocalJobPlanningLine."Line Type", LocalResource.Name, LocalJobPlanningLine.Description);
            until LocalJobPlanningLine.Next() = 0;
        end;
    end;

    local procedure GetContactPhone(): Text[30]
    begin
        if Contact."No." <> '' then
            exit(Contact."Phone No.");

        exit(Customer."Phone No.");
    end;

    local procedure GetDescription() AppointmentDescription: Text
    var
        AppointmentFormat: Text;
    begin
        AppointmentFormat := Job.TableCaption + ': %1 - %2\r\n';
        AppointmentFormat += JobTask.TableCaption + ': %3 - %4\n\n';
        if Customer.Name <> '' then
            AppointmentFormat += StrSubstNo('%1: %2\n', Customer.TableCaption(), Customer.Name);
        AppointmentFormat += Contact.TableCaption + ': %5\n';
        AppointmentFormat += Contact.FieldCaption("Phone No.") + ': %6\n\n';
        AppointmentFormat += Resource.TableCaption + ': (%7) %8 - %9';
        AppointmentDescription := StrSubstNo(AppointmentFormat,
            Job."No.", Job.Description,
            JobTask."Job Task No.", JobTask.Description,
            Customer.Contact, GetContactPhone(),
            JobPlanningLine."Line Type", Resource.Name, JobPlanningLine.Description);

        AppointmentDescription += GetAdditionalResources();
        if ProjectManagerResource.Name <> '' then
            AppointmentDescription += StrSubstNo('\n\n%1: %2',
                Job.FieldCaption("Project Manager"), ProjectManagerResource.Name);
    end;

    local procedure GetHtmlDescription(Description: Text) HtmlAppointDescription: Text
    var
        Regex: Codeunit Regex;
    begin
        HtmlAppointDescription := Regex.Replace(Description, '\\r', '');
        HtmlAppointDescription := Regex.Replace(HtmlAppointDescription, '\\n', '<br>');
        HtmlAppointDescription := 'text/html:<html><body>' + HtmlAppointDescription + '</html></body>';
    end;

    local procedure GetOrganizer(): Text
    var
        ProjectManagerUser: Record User;
        TempEmailAccount: Record "Email Account";
        EmailScenario: Codeunit "Email Scenario";
    begin
        ProjectManagerUser.SetRange("User Name", ProjectManagerResource."Time Sheet Owner User ID");
        if ProjectManagerUser.FindFirst() then
            if ProjectManagerUser."Authentication Email" <> '' then
                exit(ProjectManagerUser."Authentication Email");

        EmailScenario.GetEmailAccount(Enum::"Email Scenario"::Default, TempEmailAccount);
        exit(TempEmailAccount."Email Address");

    end;

    local procedure GetStartDate() StartDateTime: Text
    var
        StartDate: Date;
        StartTime: Time;
    begin
        StartDate := JobPlanningLine."Planning Date";
        if JobPlanningLine.Quantity < 12 then
            Evaluate(StartTime, Format(8));

        StartDateTime := Format(CreateDateTime(StartDate, StartTime), 0, DateTimeFormatTxt);

        OnAfterGetStartDate(JobPlanningLine, StartDateTime);
    end;

    local procedure GetEndDate() EndDateTime: Text
    var
        StartDate: Date;
        EndTime: Time;
        Duration: Decimal;
        Days: Integer;
    begin
        Duration := JobPlanningLine.Quantity;
        StartDate := JobPlanningLine."Planning Date";
        if Duration < 12 then
            Evaluate(EndTime, Format(8 + Duration))
        else
            Days := Round(Duration / 24, 1, '>');

        EndDateTime := Format(CreateDateTime(StartDate + Days, EndTime), 0, DateTimeFormatTxt);

        OnAfterGetEndDate(JobPlanningLine, EndDateTime);
    end;

    local procedure GetResourceEmail(ResourceNo: Code[20]): Text[80]
    var
        LocalResource: Record Resource;
        LocalUser: Record User;
    begin
        LocalResource.Get(ResourceNo);
        LocalUser.SetRange("User Name", LocalResource."Time Sheet Owner User ID");
        if LocalUser.FindFirst() then
            exit(LocalUser."Authentication Email");
    end;

    local procedure UpdateJob()
    begin
        if Job."No." <> JobPlanningLine."Job No." then begin
            Job.Get(JobPlanningLine."Job No.");
            Customer.Get(Job."Bill-to Customer No.");
            if Customer."Primary Contact No." <> '' then
                Contact.Get(Customer."Primary Contact No.");
            if Job."Project Manager" <> '' then
                ProjectManagerResource.Get(Job."Project Manager");
        end;
    end;

    local procedure UpdateJobTask()
    begin
        if (JobTask."Job Task No." <> JobPlanningLine."Job Task No.") or (JobTask."Job No." <> JobPlanningLine."Job No.") then
            JobTask.Get(JobPlanningLine."Job No.", JobPlanningLine."Job Task No.");
    end;

    local procedure UpdateResource()
    begin
        if JobPlanningLine."No." <> Resource."No." then
            if JobPlanningLine."No." <> '' then
                Resource.Get(JobPlanningLine."No.");
    end;

    [EventSubscriber(ObjectType::Table, Database::"Job Planning Line", 'OnAfterDeleteEvent', '', false, false)]
    local procedure OnDeletePlanningLine(var Rec: Record "Job Planning Line"; RunTrigger: Boolean)
    var
        LocalJobPlanningLineCalendar: Record "Job Planning Line - Calendar";
    begin
        if not RunTrigger or Rec.IsTemporary then
            exit;
        if LocalJobPlanningLineCalendar.HasBeenSent(Rec) then begin
            SetPlanningLine(Rec);
            CreateAndSend();
        end;
    end;

    [IntegrationEvent(false, false)]
    local procedure OnAfterSetPlanningLine(NewJobPlanningLine: Record "Job Planning Line"; var ProjectManagerResource: Record Resource)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnAfterGetStartDate(JobPlanningLine: Record "Job Planning Line"; var StartDateTime: Text)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnAfterGetEndDate(JobPlanningLine: Record "Job Planning Line"; var EndDateTime: Text)
    begin
    end;
}