Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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,59 @@
// ------------------------------------------------------------------------------------------------
// 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 4580 "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";
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;

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 @@ -419,12 +419,7 @@ page 4404 "SOA Email Message"
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";
BCCRecipients: List of [Text];
CCRecipients: 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,23 @@ 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
ToRecipients.Add(MappedContactEmail);
GetOriginEmailRecipients(InputAgentTaskMessage, CCRecipients, BCCRecipients);
Comment thread
tomasevicst marked this conversation as resolved.
Outdated
EmailMessage.CreateReply(ToRecipients, Subject, Body, true, InputAgentTaskMessage."External ID", CCRecipients, BCCRecipients);
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 +70,76 @@ 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.';
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.';

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 SOATaskContactOverride."Contact No." = '' then
ErrorMappedContact(InvalidMappedContactErr);

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

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);
if Contact."E-Mail" = '' then
ErrorMappedContact(StrSubstNo(MappedContactEmailMissingErr, Contact."No."));

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

if ContactCount > 0 then
Comment thread
tomasevicst marked this conversation as resolved.
Outdated
ErrorMappedContact(InvalidMappedContactErr);

exit('');
end;

local procedure ErrorMappedContact(ErrorMessage: Text)
Comment thread
tomasevicst marked this conversation as resolved.
Outdated
var
MappedContactErrorInfo: ErrorInfo;
begin
MappedContactErrorInfo.Message(ErrorMessage);
Error(MappedContactErrorInfo);
end;

local procedure GetOriginEmailRecipients(InputAgentTaskMessage: Record "Agent Task Message"; var CCRecipients: List of [Text]; var BCCRecipients: List of [Text])
Comment thread
tomasevicst marked this conversation as resolved.
Outdated
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
exit;

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

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

OriginEmailMessage.GetRecipients(Enum::"Email Recipient Type"::Cc, CCRecipients);
OriginEmailMessage.GetRecipients(Enum::"Email Recipient Type"::Bcc, BCCRecipients);
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 @@ -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 @@ -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