-
Notifications
You must be signed in to change notification settings - Fork 430
[SOA]: Bugbash for releases 28.x - Contact unable to find due to Qasim map to Megan, different names #10036
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
[SOA]: Bugbash for releases 28.x - Contact unable to find due to Qasim map to Megan, different names #10036
Changes from all commits
b613f0f
2868cdf
b0c3487
47d11fb
4dc593f
2dd4ee7
87e956c
625ba05
4efb73e
36c2579
8d91b13
3be80eb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This filter is ANDed with the security filter (group 10) and with the agent search filter in the user filter group. If the agent searches by the sender name or email, which is exactly the failing scenario, the result is empty and the mapped contact is invisible. The fix then works only because the prompt now tells the agent not to search, which is not a guarantee. The group 11 filter is also never cleared when the current message has no valid override. Please clear group 11 on every call and neutralize the conflicting user filter when an override is pinned. |
||
| 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 |
|---|---|---|
|
|
@@ -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; | ||
|
|
@@ -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); | ||
|
|
@@ -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); | ||
|
|
||
| if MappedContactEmail <> '' then begin | ||
| ValidateMessageAccess(Rec, SOASetup); | ||
| ToRecipients.Add(MappedContactEmail); | ||
| GetOriginEmailCCRecipients(InputAgentTaskMessage, CCRecipients); | ||
| EmailMessage.CreateReply(ToRecipients, Subject, Body, true, InputAgentTaskMessage."External ID", CCRecipients, EmptyBCCRecipients); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| 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; | ||
|
|
@@ -53,6 +71,96 @@ 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."; | ||
|
tomasevicst marked this conversation as resolved.
|
||
| ContactCount: Integer; | ||
| begin | ||
| if SOATaskContactOverride.Get(InputAgentTaskMessage."Task ID", InputAgentTaskMessage.ID) then begin | ||
|
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. | ||
|
tomasevicst marked this conversation as resolved.
|
||
| if SOAFiltersImpl.FindContactByAlternateEmail(Contact, InputAgentTaskMessage.From, ContactCount) then begin | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| 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; | ||
|
|
||
| /// <summary> | ||
| /// Ensures that a mapped reply belongs to the selected SOA setup and is sent by its configured owner or agent. | ||
| /// Mapped replies redirect the original thread, so this check is enforced independently of the codeunit's internal access. | ||
| /// </summary> | ||
| 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 SOASetup.IsAuthorizedUserSecurityID(UserSecurityId()) then | ||
| Error(ReplyNotAuthorizedErr); | ||
| end; | ||
|
|
||
| local procedure ErrorMappedContact(ErrorMessage: Text; InputAgentTaskMessage: Record "Agent Task Message") | ||
|
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]) | ||
|
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"); | ||
|
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 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The mapped-contact reply path falls back to a plain Error when the original email, inbox row, or stored message cannot be reopened ( Suggested fix (apply manually — could not be anchored as a one-click suggestion): if not SOAEmail.FindFirst() then
ErrorMappedContact(OriginEmailUnavailableErr, InputAgentTaskMessage);
EmailInbox.SetLoadFields("Message Id");
if not EmailInbox.Get(SOAEmail."Email Inbox ID") then
ErrorMappedContact(OriginEmailUnavailableErr, InputAgentTaskMessage);
if not OriginEmailMessage.Get(EmailInbox."Message Id") then
ErrorMappedContact(OriginEmailUnavailableErr, InputAgentTaskMessage);Knowledge: 👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.32.4 |
||
| 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 | ||
|
|
||
| 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; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
Comment on lines
+15
to
+21
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The new Contact List field exposes Rec."E-Mail 2" as a normal editable field in agent sessions. Any user who already has Contact-modify permission can change this persistent sender mapping directly from the list, bypassing the ValidateContactMappingAccess() check that the codeunit path (SelectContactAndUpdateEmail) enforces, even though "E-Mail 2" now drives known-sender classification and mapped-contact reply routing. Make the list field read-only and keep alternate-email updates behind the validated SelectContactAndUpdateEmail(...) flow, or enforce the same authorization on direct edits.
Suggested change
Agent judgement — not directly backed by a BCQuality knowledge article. 👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.32.4 |
||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| 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 | ||||
|---|---|---|---|---|---|---|
|
|
@@ -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, | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The permission-set change from
Suggested change
Knowledge:
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.32.4 |
||||||
| page "Contact Card" = X, | ||||||
| page "Contact List" = X, | ||||||
| page "Customer Card" = X, | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new manual subscriber for OnBeforeFindRecord ignores an incoming IsHandled = true, so if another subscriber already handled the Contact List search, this code can still rewrite Rec and Found and change the final result. Add an early
if IsHandled then exit;guard so the Sales Order Agent override cooperates with other subscribers on the new base-app event.Suggested fix (apply manually — could not be anchored as a one-click suggestion):
Knowledge:
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.32.4