Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -143,25 +143,26 @@
"steps_include_numbering": "true",
"steps": [
{
"value": "Navigate to the contact list page and use the search function to find the contact.",
"value": "Navigate to the contact list page. **If there is only one contact record displayed, select it directly without using the search function. If there are multiple contacts, use the search function to find the correct contact.**",
"steps_include_numbering": "true",
"steps": [
"{% if page.id == 5052 -%}",
"Use information available to you from the conversation history one by one, starting with the email address, sender's name, company name, phone number, etc.",
"Do not select a contact without performing a search first.",
"If there are multiple contacts, do not select a contact without performing a search first.",
"{% endif -%}"
]
},
{
"value": "If the contact is not found, navigate to the customer list page and use the search function to find the customer.",
"value": "If the contact is not found, navigate to the customer list page. **If there is only one customer record displayed, select it directly without using the search function. If there are multiple customers, use the search function to find the correct customer.**",
"steps_include_numbering": "true",
"steps": [
"{% if page.id == 22 -%}",
"Use information available to you from the conversation history one by one, starting with the email address, sender's name, company name, phone number, etc.",
"Do not select a customer without performing a search first.",
"If there are multiple customers, do not select a customer without performing a search first.",
"{% endif -%}"
]
},
"**Important:** Once you have selected a contact or customer record from the search results, proceed with sales quote creation even if the contact's or customer's name or email address does not exactly match the conversation history. Incoming emails can be mapped to another contact for response routing. The selected record is authoritative; do not request assistance only because of this mismatch.",
"If neither the contact nor the customer is found, then request for assistance."
]
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
namespace Microsoft.Agent.SalesOrderAgent;

using Microsoft.CRM.Contact;
using System.Agents;

codeunit 4411 "SOA Contact Search Impl"
{
Access = Internal;
EventSubscriberInstance = Manual;
InherentEntitlements = X;
InherentPermissions = X;

var
AgentTaskID: BigInteger;

internal procedure SetAgentTaskID(NewAgentTaskID: BigInteger)
begin
AgentTaskID := NewAgentTaskID;
end;

[EventSubscriber(ObjectType::Page, Page::"Contact List", OnBeforeFindRecord, '', false, false)]
local procedure FindRecordContactFromList(var Rec: Record Contact; Which: Text; var Found: Boolean; var IsHandled: Boolean)
begin
FindRecordContact(Rec, Which, Found, IsHandled);
end;

local procedure FindRecordContact(var Rec: Record Contact; Which: Text; var Found: Boolean; var IsHandled: Boolean)
var
AgentTaskMessage: Record "Agent Task Message";
SOATaskContactOverride: Record "SOA Task Contact Override";
SOAFiltersImpl: Codeunit "SOA Filters Impl.";
OriginalFilterGroup: Integer;
begin
if AgentTaskID = 0 then
exit;

AgentTaskMessage.SetLoadFields(ID);
AgentTaskMessage.SetRange("Task ID", AgentTaskID);
AgentTaskMessage.SetRange(Type, AgentTaskMessage.Type::Input);
AgentTaskMessage.SetFilter(Status, '<>%1&<>%2', AgentTaskMessage.Status::Discarded, AgentTaskMessage.Status::Rejected);
AgentTaskMessage.SetCurrentKey("Task ID", SystemCreatedAt);
AgentTaskMessage.Ascending(false);
if not AgentTaskMessage.FindFirst() then
exit;

if not SOATaskContactOverride.Get(AgentTaskID, AgentTaskMessage.ID) then
exit;
if not SOAFiltersImpl.IsContactOverrideTrusted(SOATaskContactOverride) then
exit;

OriginalFilterGroup := Rec.FilterGroup();
Rec.FilterGroup(11);
Rec.SetRange("No.", SOATaskContactOverride."Contact No.");
Rec.FilterGroup(OriginalFilterGroup);
Found := Rec.Find(Which);
IsHandled := true;
end;
}
Original file line number Diff line number Diff line change
Expand Up @@ -412,19 +412,14 @@ page 4404 "SOA Email Message"
else
TaskMessageID := Rec.ID;

if SOATaskContactOverride.Get(Rec."Task ID", TaskMessageID) then
if SOATaskContactOverride.Get(Rec."Task ID", TaskMessageID) and SOAFiltersImpl.IsContactOverrideTrusted(SOATaskContactOverride) then
if SOATaskContactOverride."Contact No." <> '' then
if Contact.Get(SOATaskContactOverride."Contact No.") then begin
ContactCount := 1;
exit(true);
end;

Contact.SetFilter("E-Mail", SOAFiltersImpl.GetSafeFromEmailFilter(EmailAddress));
ContactCount := Contact.Count();
if not Contact.FindFirst() then
exit(false);

exit(true);
exit(SOAFiltersImpl.FindContactByEmail(Contact, EmailAddress, ContactCount));
end;

local procedure GetSOAEmail(var AgentTaskMessage: Record "Agent Task Message"): Boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#pragma warning disable AS0007
namespace Microsoft.Agent.SalesOrderAgent;

using Microsoft.CRM.Contact;
using System.Agents;
using System.Email;
using System.Telemetry;
Expand All @@ -24,7 +25,11 @@ codeunit 4419 "SOA Send Reply"
AgentMessage: Codeunit "Agent Message";
Email: Codeunit Email;
EmailMessage: Codeunit "Email Message";
CCRecipients: List of [Text];
EmptyBCCRecipients: List of [Text];
ToRecipients: List of [Text];
Body: Text;
MappedContactEmail: Text;
Subject: Text;
begin
Rec.Get(Rec."Task ID", Rec.ID);
Expand All @@ -36,11 +41,24 @@ codeunit 4419 "SOA Send Reply"

Subject := StrSubstNo(EmailSubjectTxt, InputAgentTaskMessage."Task ID");
Body := AgentMessage.GetText(Rec);
EmailMessage.CreateReplyAll(Subject, Body, true, InputAgentTaskMessage."External ID");
MappedContactEmail := GetMappedContactEmail(InputAgentTaskMessage);
Comment thread
tomasevicst marked this conversation as resolved.

if MappedContactEmail <> '' then begin
ValidateMessageAccess(Rec, SOASetup);
ToRecipients.Add(MappedContactEmail);
GetOriginEmailCCRecipients(InputAgentTaskMessage, CCRecipients);
EmailMessage.CreateReply(ToRecipients, Subject, Body, true, InputAgentTaskMessage."External ID", CCRecipients, EmptyBCCRecipients);
end else
EmailMessage.CreateReplyAll(Subject, Body, true, InputAgentTaskMessage."External ID");

AddMessageAttachments(EmailMessage, Rec);

if not Email.ReplyAll(EmailMessage, SOASetup."Email Account ID", SOASetup."Email Connector") then
Error(EmailReplyFailedErr);
if MappedContactEmail <> '' then begin
if not Email.Reply(EmailMessage, SOASetup."Email Account ID", SOASetup."Email Connector") then
Error(EmailReplyFailedErr);
end else
if not Email.ReplyAll(EmailMessage, SOASetup."Email Account ID", SOASetup."Email Connector") then
Error(EmailReplyFailedErr);

AgentMessage.SetStatusToSent(Rec."Task ID", Rec.ID);
end;
Expand All @@ -53,6 +71,103 @@ codeunit 4419 "SOA Send Reply"
EmailSubjectTxt: Label 'Sales order agent reply to task %1', Comment = '%1 = Agent Task id';
EmailReplyFailedErr: Label 'The email reply could not be sent.';
InvalidReplyMessageErr: Label 'Only reviewed output messages can be sent as replies.';
ReplyNotAuthorizedErr: Label 'You are not authorized to send this reply.';
InvalidMappedContactErr: Label 'The contact mapping for this message is no longer valid. Choose another contact before sending the reply.';
MappedContactEmailMissingErr: Label 'The mapped contact %1 does not have a primary email address. Add an email address to the contact or choose another contact before sending the reply.', Comment = '%1 = Contact No.';
MultipleAlternateEmailMappingsErr: Label 'The sender''s alternate email address is assigned to more than one contact. Remove the duplicate alternate email mappings before sending the reply.';
MappedContactErrorTitleErr: Label 'Contact mapping requires attention';
MappedContactErrorDetailedMessageErr: Label 'Open the source email message and correct its contact mapping or the mapped contact''s primary email address, then retry the reply.';
ShowSourceEmailMessageLbl: Label 'Show source email message';
OriginEmailUnavailableErr: Label 'The original email could not be opened, so the mapped-contact reply was not sent.';

local procedure GetMappedContactEmail(InputAgentTaskMessage: Record "Agent Task Message"): Text
var
SOATaskContactOverride: Record "SOA Task Contact Override";
Contact: Record Contact;
SOAFiltersImpl: Codeunit "SOA Filters Impl.";
Comment thread
tomasevicst marked this conversation as resolved.
ContactCount: Integer;
begin
if SOATaskContactOverride.Get(InputAgentTaskMessage."Task ID", InputAgentTaskMessage.ID) then begin
Comment thread
tomasevicst marked this conversation as resolved.
if not SOAFiltersImpl.IsContactOverrideTrusted(SOATaskContactOverride) then
ErrorMappedContact(InvalidMappedContactErr, InputAgentTaskMessage);
if SOATaskContactOverride."Contact No." = '' then
ErrorMappedContact(InvalidMappedContactErr, InputAgentTaskMessage);

Contact.SetLoadFields("E-Mail");
if not Contact.Get(SOATaskContactOverride."Contact No.") then
ErrorMappedContact(InvalidMappedContactErr, InputAgentTaskMessage);
if Contact."E-Mail" = '' then
ErrorMappedContact(StrSubstNo(MappedContactEmailMissingErr, Contact."No."), InputAgentTaskMessage);

exit(Contact."E-Mail");
end;

// Only the alternate email represents a persistent mapping; primary email matches keep the existing Reply All behavior.
Comment thread
tomasevicst marked this conversation as resolved.
if SOAFiltersImpl.FindContactByAlternateEmail(Contact, InputAgentTaskMessage.From, ContactCount) then begin
if ContactCount > 1 then
ErrorMappedContact(MultipleAlternateEmailMappingsErr, InputAgentTaskMessage);
if Contact."E-Mail" = '' then
ErrorMappedContact(StrSubstNo(MappedContactEmailMissingErr, Contact."No."), InputAgentTaskMessage);

exit(Contact."E-Mail");
end;

exit('');
end;

local procedure ValidateMessageAccess(AgentTaskMessage: Record "Agent Task Message"; SOASetup: Record "SOA Setup")
begin
if AgentTaskMessage."Agent User Security ID" <> SOASetup."User Security ID" then
Error(ReplyNotAuthorizedErr);
if not IsAuthorizedUserSecurityID(UserSecurityId(), SOASetup) then
Error(ReplyNotAuthorizedErr);
end;

local procedure IsAuthorizedUserSecurityID(UserSecurityID: Guid; SOASetup: Record "SOA Setup"): Boolean
var
OwnerUserSecurityID: Guid;
begin
OwnerUserSecurityID := SOASetup."Owner User Security ID";
if IsNullGuid(OwnerUserSecurityID) then
OwnerUserSecurityID := SOASetup."User Security ID";

exit((UserSecurityID = OwnerUserSecurityID) or (UserSecurityID = SOASetup."User Security ID"));
end;

local procedure ErrorMappedContact(ErrorMessage: Text; InputAgentTaskMessage: Record "Agent Task Message")
Comment thread
tomasevicst marked this conversation as resolved.
var
MappedContactErrorInfo: ErrorInfo;
begin
MappedContactErrorInfo.Title := MappedContactErrorTitleErr;
MappedContactErrorInfo.Message := ErrorMessage;
MappedContactErrorInfo.DetailedMessage := MappedContactErrorDetailedMessageErr;
MappedContactErrorInfo.PageNo := Page::"SOA Email Message";
MappedContactErrorInfo.RecordId := InputAgentTaskMessage.RecordId();
MappedContactErrorInfo.AddNavigationAction(ShowSourceEmailMessageLbl);
Error(MappedContactErrorInfo);
end;

local procedure GetOriginEmailCCRecipients(InputAgentTaskMessage: Record "Agent Task Message"; var CCRecipients: List of [Text])
Comment thread
tomasevicst marked this conversation as resolved.
var
SOAEmail: Record "SOA Email";
EmailInbox: Record "Email Inbox";
OriginEmailMessage: Codeunit "Email Message";
begin
SOAEmail.SetLoadFields("Email Inbox ID");
Comment thread
tomasevicst marked this conversation as resolved.
SOAEmail.SetRange("Task ID", InputAgentTaskMessage."Task ID");
SOAEmail.SetRange("Task Message ID", InputAgentTaskMessage.ID);
if not SOAEmail.FindFirst() then
Error(OriginEmailUnavailableErr);

EmailInbox.SetLoadFields("Message Id");
if not EmailInbox.Get(SOAEmail."Email Inbox ID") then
Error(OriginEmailUnavailableErr);

if not OriginEmailMessage.Get(EmailInbox."Message Id") then
Error(OriginEmailUnavailableErr);

OriginEmailMessage.GetRecipients(Enum::"Email Recipient Type"::Cc, CCRecipients);
end;

local procedure AddMessageAttachments(var EmailMessage: Codeunit "Email Message"; var AgentTaskMessage: Record "Agent Task Message")
var
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ codeunit 4398 "SOA Task Message"
SentAgentTaskMessage: Record "Agent Task Message";
SOATaskContactOverride: Record "SOA Task Contact Override";
OverrideContact: Record Contact;
SOAFiltersImpl: Codeunit "SOA Filters Impl.";
ContactCount: Integer;
begin
Clear(ToAddress);
if OutputAgentTaskMessage.Type <> OutputAgentTaskMessage.Type::Output then
Expand All @@ -100,7 +102,7 @@ codeunit 4398 "SOA Task Message"
if SentAgentTaskMessage.From = '' then
exit(false);

if SOATaskContactOverride.Get(OutputAgentTaskMessage."Task ID", OutputAgentTaskMessage."Input Message ID") then
if SOATaskContactOverride.Get(OutputAgentTaskMessage."Task ID", OutputAgentTaskMessage."Input Message ID") and SOAFiltersImpl.IsContactOverrideTrusted(SOATaskContactOverride) then
if SOATaskContactOverride."Contact No." <> '' then begin
OverrideContact.SetLoadFields("E-Mail");
if OverrideContact.Get(SOATaskContactOverride."Contact No.") then
Expand All @@ -110,29 +112,31 @@ codeunit 4398 "SOA Task Message"
end;
end;

if SOAFiltersImpl.FindContactByEmail(OverrideContact, SentAgentTaskMessage.From, ContactCount) and (ContactCount = 1) then
if OverrideContact."E-Mail" <> '' then begin
ToAddress := OverrideContact."E-Mail";
exit(true);
end;

ToAddress := SentAgentTaskMessage.From;
exit(true);
end;

internal procedure MessageRequiresReview(SOASetup: Record "SOA Setup"; EmailInbox: Record "Email Inbox"; IsFirstMessageInTask: Boolean): Boolean
var
Contact: Record Contact;
SOAFiltersImpl: Codeunit "SOA Filters Impl.";
SOAInputMessageReview: Enum "SOA Input Message Review";
begin
// If we have the same review setting for both registered and unregistered senders,
// then we can skip trying to find the contact.
if SOASetup."Known Sender In. Msg. Review" = SOASetup."Unknown Sender In. Msg. Review" then
SOAInputMessageReview := SOASetup."Known Sender In. Msg. Review"
else begin
else
// Check if the sender is a registered contact
Contact.SetFilter("E-Mail", SOAFiltersImpl.GetSafeFromEmailFilter(EmailInbox."Sender Address"));
Contact.ReadIsolation := IsolationLevel::ReadCommitted;
if Contact.IsEmpty() then
if not SOAFiltersImpl.ContactExistsByEmail(EmailInbox."Sender Address") then
SOAInputMessageReview := SOASetup."Unknown Sender In. Msg. Review"
else
SOAInputMessageReview := SOASetup."Known Sender In. Msg. Review";
end;

case SOAInputMessageReview of
SOAInputMessageReview::"All Messages":
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
namespace Microsoft.Agent.SalesOrderAgent;

using Microsoft.CRM.Contact;

pageextension 4411 "SOA Contact List Ext" extends "Contact List"
{
layout
{
addafter("E-Mail")
{
field("SOA E-Mail 2"; Rec."E-Mail 2")
{
ApplicationArea = Basic, Suite;
Caption = 'Email 2';
ToolTip = 'Specifies an alternative email address for the contact.';
Visible = IsAgentSession;
}
}
}

trigger OnOpenPage()
var
SOAKPITrackAll: Codeunit "SOA - KPI Track All";
AgentTaskID: BigInteger;
begin
IsAgentSession := SOAKPITrackAll.IsOrderTakerAgentSession(AgentTaskID);
end;

var
IsAgentSession: Boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ permissionset 4406 "SOA - Objects"
tabledata "Contact" = R,
tabledata "SOA Email" = RIM,
tabledata "SOA Reply Attempt" = rimd,
tabledata "SOA Task Contact Override" = RIM,
tabledata "SOA Task Contact Override" = R,
page "Contact Card" = X,
page "Contact List" = X,
page "Customer Card" = X,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ pagecustomization "SOA Contact List" customizes "Contact List"
{
Visible = true;
}
modify("SOA E-Mail 2")
{
Visible = true;
}
modify("Fax No.")
{
Visible = true;
Expand Down
Loading
Loading