Skip to content

Commit d2d2fee

Browse files
Merge pull request #6 from CMS-Enterprise/activity-log-plugins
Activity log plugins
2 parents d744c49 + ba717c1 commit d2d2fee

File tree

9 files changed

+795
-0
lines changed

9 files changed

+795
-0
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.14.36623.8 d17.14
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ActivityLogs.Plugins", "ActivityLogs.Plugins\ActivityLogs.Plugins.csproj", "{C39F5F21-4E07-45ED-972E-2F45962D62AC}"
7+
EndProject
8+
Global
9+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10+
Debug|Any CPU = Debug|Any CPU
11+
Release|Any CPU = Release|Any CPU
12+
EndGlobalSection
13+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
14+
{C39F5F21-4E07-45ED-972E-2F45962D62AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15+
{C39F5F21-4E07-45ED-972E-2F45962D62AC}.Debug|Any CPU.Build.0 = Debug|Any CPU
16+
{C39F5F21-4E07-45ED-972E-2F45962D62AC}.Release|Any CPU.ActiveCfg = Release|Any CPU
17+
{C39F5F21-4E07-45ED-972E-2F45962D62AC}.Release|Any CPU.Build.0 = Release|Any CPU
18+
EndGlobalSection
19+
GlobalSection(SolutionProperties) = preSolution
20+
HideSolutionNode = FALSE
21+
EndGlobalSection
22+
GlobalSection(ExtensibilityGlobals) = postSolution
23+
SolutionGuid = {CCFA3561-9AF0-4F5A-81C4-ACA08E7CB8B4}
24+
EndGlobalSection
25+
EndGlobal
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
using System;
2+
using Microsoft.Xrm.Sdk;
3+
4+
namespace SystemIntake.Plugins
5+
{
6+
public class ActivityLog_Create_SyncTargetStepToReviewAndRequest : IPlugin
7+
{
8+
private const string ActivityLogEntity = "new_activitylogs";
9+
10+
// Activity Log fields
11+
private const string TargetStepField = "new_process_target_step"; // Choice (global)
12+
private const string ReviewLookupField = "new_adminreview"; // Lookup -> cr69a_systemintakeadmin
13+
private const string RequestLookupField = "new_systemintake"; // Lookup -> new_systemintake (Request)
14+
15+
// Review fields
16+
private const string ReviewEntity = "cr69a_systemintakeadmin";
17+
private const string ReviewStepField = "new_admingovernancetasklist"; // Choice (same global)
18+
private const string ReviewReadyForReviewField = "cr69a_readyforreview"; // Two Options (Yes/No)
19+
20+
// Request fields
21+
private const string RequestEntity = "new_systemintake";
22+
private const string RequestStepField = "new_admingovernanceprocessstep"; // Choice (same global)
23+
private const string RequestReadyForReviewField = "cr69a_readyforreview"; // Two Options (Yes/No)
24+
25+
private const int PostOperationStage = 40;
26+
27+
//TODO: Consider shared PluginTracing helper to standardize Start/End/Exception tracing across plugins.
28+
//Keep it in same assembly initially to avoid dependency deployment complexity.
29+
30+
public void Execute(IServiceProvider serviceProvider)
31+
{
32+
var tracing = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
33+
var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
34+
35+
if (context == null)
36+
{
37+
tracing?.Trace("ActivityLog_Create_SyncTargetStepToReviewAndRequest: context is null. Exiting.");
38+
return;
39+
}
40+
41+
tracing?.Trace(
42+
"ActivityLog_Create_SyncTargetStepToReviewAndRequest: Start. Message={0}, PrimaryEntity={1}, Stage={2}, Mode={3}, Depth={4}, UserId={5}, InitiatingUserId={6}, CorrelationId={7}, OperationId={8}",
43+
context.MessageName,
44+
context.PrimaryEntityName,
45+
context.Stage,
46+
context.Mode,
47+
context.Depth,
48+
context.UserId,
49+
context.InitiatingUserId,
50+
context.CorrelationId,
51+
context.OperationId
52+
);
53+
54+
try
55+
{
56+
if (!string.Equals(context.PrimaryEntityName, ActivityLogEntity, StringComparison.OrdinalIgnoreCase))
57+
{
58+
tracing?.Trace("ActivityLog_Create_SyncTargetStepToReviewAndRequest: Not target entity ({0}). Exiting.", ActivityLogEntity);
59+
return;
60+
}
61+
62+
if (!string.Equals(context.MessageName, "Create", StringComparison.OrdinalIgnoreCase))
63+
{
64+
tracing?.Trace("ActivityLog_Create_SyncTargetStepToReviewAndRequest: Not Create message. Exiting.");
65+
return;
66+
}
67+
68+
// PostOperation only
69+
if (context.Stage != PostOperationStage)
70+
{
71+
tracing?.Trace("ActivityLog_Create_SyncTargetStepToReviewAndRequest: Not PostOperation (Stage {0}). Exiting.", PostOperationStage);
72+
return;
73+
}
74+
75+
object targetObj;
76+
if (!context.InputParameters.TryGetValue("Target", out targetObj))
77+
{
78+
tracing?.Trace("ActivityLog_Create_SyncTargetStepToReviewAndRequest: Missing Target in InputParameters. Exiting.");
79+
return;
80+
}
81+
82+
var target = targetObj as Entity;
83+
if (target == null)
84+
{
85+
tracing?.Trace("ActivityLog_Create_SyncTargetStepToReviewAndRequest: Target is not an Entity. Exiting.");
86+
return;
87+
}
88+
89+
tracing?.Trace(
90+
"ActivityLog_Create_SyncTargetStepToReviewAndRequest: Target received. LogicalName={0}, Id={1}",
91+
target.LogicalName,
92+
target.Id
93+
);
94+
95+
var reviewRef = target.GetAttributeValue<EntityReference>(ReviewLookupField);
96+
var requestRef = target.GetAttributeValue<EntityReference>(RequestLookupField);
97+
98+
// Step is optional now (we still want to clear Ready for Review even if step isn't set)
99+
var step = target.GetAttributeValue<OptionSetValue>(TargetStepField);
100+
101+
tracing?.Trace(
102+
"ActivityLog_Create_SyncTargetStepToReviewAndRequest: Parsed fields. ReviewRef={0}, RequestRef={1}, Step={2}",
103+
reviewRef != null ? $"{reviewRef.LogicalName}:{reviewRef.Id}" : "(null)",
104+
requestRef != null ? $"{requestRef.LogicalName}:{requestRef.Id}" : "(null)",
105+
step != null ? step.Value.ToString() : "(null)"
106+
);
107+
108+
var serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
109+
var service = serviceFactory.CreateOrganizationService(context.UserId);
110+
111+
// Update Review (if present)
112+
if (reviewRef != null)
113+
{
114+
tracing?.Trace("ActivityLog_Create_SyncTargetStepToReviewAndRequest: Updating Review {0}:{1}", reviewRef.LogicalName, reviewRef.Id);
115+
116+
var reviewUpdate = new Entity(ReviewEntity, reviewRef.Id);
117+
118+
// Sync step only if provided on the log
119+
if (step != null)
120+
{
121+
reviewUpdate[ReviewStepField] = new OptionSetValue(step.Value);
122+
tracing?.Trace("ActivityLog_Create_SyncTargetStepToReviewAndRequest: Review step set to {0}", step.Value);
123+
}
124+
else
125+
{
126+
tracing?.Trace("ActivityLog_Create_SyncTargetStepToReviewAndRequest: Review step not provided; leaving step unchanged.");
127+
}
128+
129+
// Always set Ready for Review = false when an Activity Log is created
130+
reviewUpdate[ReviewReadyForReviewField] = false;
131+
tracing?.Trace("ActivityLog_Create_SyncTargetStepToReviewAndRequest: Review ReadyForReview set to false.");
132+
133+
service.Update(reviewUpdate);
134+
tracing?.Trace("ActivityLog_Create_SyncTargetStepToReviewAndRequest: Review update succeeded.");
135+
}
136+
else
137+
{
138+
tracing?.Trace("ActivityLog_Create_SyncTargetStepToReviewAndRequest: Review lookup not present; skipping Review update.");
139+
}
140+
141+
// Update Request (if present)
142+
if (requestRef != null)
143+
{
144+
tracing?.Trace("ActivityLog_Create_SyncTargetStepToReviewAndRequest: Updating Request {0}:{1}", requestRef.LogicalName, requestRef.Id);
145+
146+
var requestUpdate = new Entity(RequestEntity, requestRef.Id);
147+
148+
// Sync step only if provided on the log
149+
if (step != null)
150+
{
151+
requestUpdate[RequestStepField] = new OptionSetValue(step.Value);
152+
tracing?.Trace("ActivityLog_Create_SyncTargetStepToReviewAndRequest: Request step set to {0}", step.Value);
153+
}
154+
else
155+
{
156+
tracing?.Trace("ActivityLog_Create_SyncTargetStepToReviewAndRequest: Request step not provided; leaving step unchanged.");
157+
}
158+
159+
// Always set Ready for Review = false when an Activity Log is created
160+
requestUpdate[RequestReadyForReviewField] = false;
161+
tracing?.Trace("ActivityLog_Create_SyncTargetStepToReviewAndRequest: Request ReadyForReview set to false.");
162+
163+
service.Update(requestUpdate);
164+
tracing?.Trace("ActivityLog_Create_SyncTargetStepToReviewAndRequest: Request update succeeded.");
165+
}
166+
else
167+
{
168+
tracing?.Trace("ActivityLog_Create_SyncTargetStepToReviewAndRequest: Request lookup not present; skipping Request update.");
169+
}
170+
}
171+
catch (Exception ex)
172+
{
173+
tracing?.Trace("ActivityLog_Create_SyncTargetStepToReviewAndRequest: Exception: {0}", ex);
174+
throw;
175+
}
176+
finally
177+
{
178+
tracing?.Trace("ActivityLog_Create_SyncTargetStepToReviewAndRequest: End.");
179+
}
180+
}
181+
}
182+
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
using System;
2+
using Microsoft.Xrm.Sdk;
3+
using Microsoft.Xrm.Sdk.Query;
4+
5+
namespace SystemIntake.Plugins
6+
{
7+
public class ActivityLog_Create_ValidateTargetStepNotCurrent : IPlugin
8+
{
9+
private const string ActivityLogEntity = "new_activitylogs";
10+
private const int PreOperationStage = 20;
11+
12+
// Activity Log fields
13+
private const string TargetStepField = "new_process_target_step"; // Choice (global)
14+
private const string ReviewLookupField = "new_adminreview"; // Lookup -> cr69a_systemintakeadmin
15+
16+
// Review fields
17+
private const string ReviewEntity = "cr69a_systemintakeadmin";
18+
private const string ReviewStepField = "new_admingovernancetasklist"; // Choice (same global)
19+
20+
public void Execute(IServiceProvider serviceProvider)
21+
{
22+
var tracing = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
23+
var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
24+
25+
if (context == null)
26+
{
27+
tracing?.Trace("ActivityLog_Create_ValidateTargetStepNotCurrent: context is null. Exiting.");
28+
return;
29+
}
30+
31+
tracing?.Trace(
32+
"ActivityLog_Create_ValidateTargetStepNotCurrent: Start. Message={0}, PrimaryEntity={1}, Stage={2}, Mode={3}, Depth={4}, UserId={5}, InitiatingUserId={6}, CorrelationId={7}, OperationId={8}",
33+
context.MessageName,
34+
context.PrimaryEntityName,
35+
context.Stage,
36+
context.Mode,
37+
context.Depth,
38+
context.UserId,
39+
context.InitiatingUserId,
40+
context.CorrelationId,
41+
context.OperationId
42+
);
43+
44+
try
45+
{
46+
if (!string.Equals(context.PrimaryEntityName, ActivityLogEntity, StringComparison.OrdinalIgnoreCase))
47+
{
48+
tracing?.Trace("ActivityLog_Create_ValidateTargetStepNotCurrent: Not target entity ({0}). Exiting.", ActivityLogEntity);
49+
return;
50+
}
51+
52+
if (!string.Equals(context.MessageName, "Create", StringComparison.OrdinalIgnoreCase))
53+
{
54+
tracing?.Trace("ActivityLog_Create_ValidateTargetStepNotCurrent: Not Create message. Exiting.");
55+
return;
56+
}
57+
58+
// PreOperation only
59+
if (context.Stage != PreOperationStage)
60+
{
61+
tracing?.Trace("ActivityLog_Create_ValidateTargetStepNotCurrent: Not PreOperation (Stage {0}). Exiting.", PreOperationStage);
62+
return;
63+
}
64+
65+
object targetObj;
66+
if (!context.InputParameters.TryGetValue("Target", out targetObj))
67+
{
68+
tracing?.Trace("ActivityLog_Create_ValidateTargetStepNotCurrent: Missing Target in InputParameters. Exiting.");
69+
return;
70+
}
71+
72+
var target = targetObj as Entity;
73+
if (target == null)
74+
{
75+
tracing?.Trace("ActivityLog_Create_ValidateTargetStepNotCurrent: Target is not an Entity. Exiting.");
76+
return;
77+
}
78+
79+
tracing?.Trace(
80+
"ActivityLog_Create_ValidateTargetStepNotCurrent: Target received. LogicalName={0}, Id={1}",
81+
target.LogicalName,
82+
target.Id
83+
);
84+
85+
var step = target.GetAttributeValue<OptionSetValue>(TargetStepField);
86+
if (step == null)
87+
{
88+
tracing?.Trace("ActivityLog_Create_ValidateTargetStepNotCurrent: No step provided ({0}); nothing to validate. Exiting.", TargetStepField);
89+
return;
90+
}
91+
92+
var reviewRef = target.GetAttributeValue<EntityReference>(ReviewLookupField);
93+
if (reviewRef == null)
94+
{
95+
tracing?.Trace("ActivityLog_Create_ValidateTargetStepNotCurrent: No review reference provided ({0}); nothing to validate. Exiting.", ReviewLookupField);
96+
return;
97+
}
98+
99+
tracing?.Trace(
100+
"ActivityLog_Create_ValidateTargetStepNotCurrent: Validating step. ProposedStep={0}, ReviewRef={1}:{2}",
101+
step.Value,
102+
reviewRef.LogicalName,
103+
reviewRef.Id
104+
);
105+
106+
var serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
107+
var service = serviceFactory.CreateOrganizationService(context.UserId);
108+
109+
tracing?.Trace(
110+
"ActivityLog_Create_ValidateTargetStepNotCurrent: Retrieving Review {0}:{1} (Column={2})",
111+
ReviewEntity,
112+
reviewRef.Id,
113+
ReviewStepField
114+
);
115+
116+
var review = service.Retrieve(ReviewEntity, reviewRef.Id, new ColumnSet(ReviewStepField));
117+
var current = review.GetAttributeValue<OptionSetValue>(ReviewStepField);
118+
119+
tracing?.Trace(
120+
"ActivityLog_Create_ValidateTargetStepNotCurrent: CurrentStep={0}, ProposedStep={1}",
121+
current != null ? current.Value.ToString() : "(null)",
122+
step.Value
123+
);
124+
125+
if (current != null && current.Value == step.Value)
126+
{
127+
tracing?.Trace("ActivityLog_Create_ValidateTargetStepNotCurrent: Validation failed (proposed step equals current). Throwing InvalidPluginExecutionException.");
128+
throw new InvalidPluginExecutionException(
129+
"That step is already the current step. Please choose a different target step."
130+
);
131+
}
132+
133+
tracing?.Trace("ActivityLog_Create_ValidateTargetStepNotCurrent: Validation passed.");
134+
}
135+
catch (InvalidPluginExecutionException)
136+
{
137+
// Expected validation failure path
138+
tracing?.Trace("ActivityLog_Create_ValidateTargetStepNotCurrent: InvalidPluginExecutionException thrown (validation).");
139+
throw;
140+
}
141+
catch (Exception ex)
142+
{
143+
tracing?.Trace("ActivityLog_Create_ValidateTargetStepNotCurrent: Unexpected exception: {0}", ex);
144+
throw;
145+
}
146+
finally
147+
{
148+
tracing?.Trace("ActivityLog_Create_ValidateTargetStepNotCurrent: End.");
149+
}
150+
}
151+
}
152+
}

0 commit comments

Comments
 (0)