我们有一个使用BookRequest
&的插件RescheduleRequest
安排Appointment
&的方法RecurringAppointmentMaster
个实体。
最近,我的任务是实现在给定时间段内安排多个appts的能力。
因此,在研究这个问题时,我发现一些帖子涉及资源容量(工作时间)和在约会中将ActivityParty的Effort字段设置为1.0。
我想,很高兴这很容易。
所以我更改了插件以存储工作:
activityParty = new Entity("activityparty");
activityParty["partyid"] = new EntityReference("systemuser", apptCaregiverId);
activityParty["effort"] = (Double)1.0;
但是当我运行代码时,BookRequest返回了这个让我困惑的错误:ErrorCode.DifferentEffort
我搜索了ErrorCode.DifferentEffort,2139095040,BookRequest,你发现它没有任何用处。
这个错误究竟是什么意思? 为什么在CRM中安排并发约会如此困难?
答案 0 :(得分:2)
这就是我学到的难点,也许这会让别人感到沮丧。
我查看了数据库&注意到与约会相关联的activityparty实体都将努力字段设置为2139095040,我想知道这个数字来自哪里?在与CRM无关的帖子中,我发现2139095040意味着“正无限”。
我重新审视了他们谈论将工作量设置为1.0&的帖子。意识到他们都指的是ServiceAppointment
实体(不是Appointment
)然后我最终偶然发现了错误代码列表
Scheduling Error Codes
DifferentEffort =此服务所需的容量与资源{资源名称}的容量不匹配。
不完全是事实,但无论如何。 真正的问题是约会实体没有引用服务,因此不存在的服务的容量是'正无限'(2139095040)。 设置Effort等于除2139095040之外的任何内容都会引发此错误。 这里并没有真正检查资源的容量,它只是说不存在的服务容量必须是= 2139095040
无论如何要解决这个问题,我删除了设置工作量= 1.0的逻辑,当BookRequest
或RescheduleRequest
返回ErrorCode.ResourceBusy
时,我检查容量与该时间段中安排的#appts &安培;如果剩余容量,我会通过使用创建或更新强制它超量预订。
private Guid BookAppointment(Entity appointment, bool setState, out List<string> errors)
{
Guid apptId = Guid.Empty;
try
{
BookRequest request = new BookRequest
{
Target = appointment
};
BookResponse booked = (BookResponse)this.orgService.Execute(request);
apptId = ParseValidationResult(booked.ValidationResult, setState, appointment, true, out errors);
}
catch (Exception ex)
{
errors = new List<string> { ex.GetBaseException().Message };
}
return apptId;
}
private Guid RescheduleAppointment(Entity appointment, out List<string> errors)
{ // used to reschedule non-recurring appt or appt in recurrence
Guid apptId = Guid.Empty;
try
{
RescheduleRequest request = new RescheduleRequest
{
Target = appointment
};
RescheduleResponse rescheduled = (RescheduleResponse)this.orgService.Execute(request);
apptId = ParseValidationResult(rescheduled.ValidationResult, false, appointment, false, out errors);
}
catch (Exception ex)
{
errors = new List<string> { ex.GetBaseException().Message };
}
return apptId;
}
private Guid ParseValidationResult(ValidationResult result, bool setState, Entity appointment, Boolean addNew, out List<string> errors)
{
Guid apptId = result.ActivityId;
errors = new List<string>();
if (result.ValidationSuccess == true)
{
if (setState == true)
{
SetStateRequest state = new SetStateRequest();
state.State = new OptionSetValue(3); // Scheduled
state.Status = new OptionSetValue(5); // Busy
state.EntityMoniker = new EntityReference("appointment", apptId);
SetStateResponse stateSet = (SetStateResponse)this.orgService.Execute(state);
}
}
else
{
String error;
String errortxt;
Boolean overbookAppt = true;
foreach (var errorInfo in result.TraceInfo.ErrorInfoList)
{
bool unavailable = false;
if (errorInfo.ErrorCode == "ErrorCode.ResourceNonBusinessHours")
{
errortxt = "{0} is being scheduled outside work hours";
}
else if (errorInfo.ErrorCode == "ErrorCode.ResourceBusy")
{
errortxt = "{0} is unavailable at this time";
unavailable = true;
}
else
{
errortxt = "failed to schedule {0}, error code = " + errorInfo.ErrorCode;
}
Dictionary<Guid, String> providers;
Dictionary<Guid, String> resources;
DateTime start = DateTime.Now, end = DateTime.Now;
Guid[] resourceIds = errorInfo.ResourceList.Where(r => r.EntityName == "equipment").Select(r => r.Id).ToList().ToArray();
if (unavailable == true)
{
if (appointment.LogicalName == "recurringappointmentmaster")
{
start = (DateTime)appointment["starttime"];
end = (DateTime)appointment["endtime"];
}
else
{
start = (DateTime)appointment["scheduledstart"];
end = (DateTime)appointment["scheduledend"];
}
Dictionary<Guid, Boolean> availability = GetAvailabilityOfResources(resourceIds, start, end);
resourceIds = availability.Where(a => a.Value == false).Select(t => t.Key).ToArray(); // get ids of all unavailable resources
if (resourceIds.Count() == 0)
{ // all resources still have capacity left at this timeslot - overbook appt timeslot
overbookAppt = true;
} // otherwise at least some resources are booked up in this timeslot - return error
}
if (errortxt.Contains("{0}"))
{ // include resource name in error msg
if (resourceIds.Count() > 0)
{
LoadProviderAndResourceInfo(resourceIds, out providers, out resources);
foreach (var resource in providers)
{
error = String.Format(errortxt, resource.Value);
errors.Add(error);
}
foreach (var resource in resources)
{
error = String.Format(errortxt, resource.Value);
errors.Add(error);
}
}
}
else
{ // no place for name in msg - just store it
errors.Add(errortxt);
break;
}
}
if (overbookAppt == true && errors.Count() == 0)
{ // all resources still have capacity left at this timeslot & no other errors have been returned - create appt anyway
if (addNew)
{
appointment.Attributes.Remove("owner"); // Create message does not like when owner field is specified
apptId = this.orgService.Create(appointment);
if (setState == true)
{
SetStateRequest state = new SetStateRequest();
state.State = new OptionSetValue(3); // Scheduled
state.Status = new OptionSetValue(5); // Busy
state.EntityMoniker = new EntityReference("appointment", apptId);
SetStateResponse stateSet = (SetStateResponse)this.orgService.Execute(state);
}
}
else
{
this.orgService.Update(appointment);
}
}
}
return apptId;
}
private Dictionary<Guid, Boolean> GetAvailabilityOfResources(Guid[] resourceIds, DateTime start, DateTime end)
{
Dictionary<Guid, Boolean> availability = new Dictionary<Guid, Boolean>();
QueryMultipleSchedulesRequest scheduleRequest = new QueryMultipleSchedulesRequest();
scheduleRequest.ResourceIds = resourceIds;
scheduleRequest.Start = start;
scheduleRequest.End = end;
// TimeCode.Unavailable - gets appointments
// TimeCode.Filter - gets resource capacity
scheduleRequest.TimeCodes = new TimeCode[] { TimeCode.Unavailable, TimeCode.Filter };
QueryMultipleSchedulesResponse scheduleResponse = (QueryMultipleSchedulesResponse)this.orgService.Execute(scheduleRequest);
int index = 0;
TimeInfo[][] timeInfo = new TimeInfo[scheduleResponse.TimeInfos.Count()][];
foreach (var schedule in scheduleResponse.TimeInfos)
{
TimeInfo resourceCapacity = schedule.Where(s => s.SubCode == SubCode.ResourceCapacity).FirstOrDefault();
Int32 capacity = (resourceCapacity != null) ? (Int32)resourceCapacity.Effort : 1;
Int32 numAppts = schedule.Where(s => s.SubCode == SubCode.Appointment).Count();
// resource is available if capacity is more than # appts in timeslot
availability.Add(resourceIds[index++], (capacity > numAppts) ? true : false);
}
return availability;
}
这确实是比设置Effort = 1更好的解决方案,因为现在我们不需要一次性按需工作流来更新现有数据。
我希望如果您需要这样做,这有助于节省您的时间。