CRM 2013: Как я могу планировать одновременные встречи (используя сущности Appointment & RecurringAppointmentMaster)?
У нас есть плагин, который использует BookRequest
& RescheduleRequest
Методы для планирования Appointment
& RecurringAppointmentMaster
юридические лица. Недавно мне было поручено реализовать возможность планирования нескольких приложений в заданном временном интервале. Поэтому, исследуя это, я обнаружил несколько постов, касающихся емкости ресурсов (в рабочее время) и установки поля Усилие ActivityParty на 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?
1 ответ
Это то, что я усвоил нелегко, так что, возможно, это избавит кого-то от разочарования.
Я заглянул в базу данных и заметил, что для всех субъектов деятельности, связанных с назначениями, в поле усилия было установлено значение 2139095040, и мне стало интересно, откуда это число? В посте, не связанном с CRM, я узнал, что 2139095040 означает "положительная бесконечность".
Я повторно посетил посты, где они говорят об установке усилия на 1.0, и понял, что все они имеют в виду ServiceAppointment
сущность (не Appointment
) и тут я наконец наткнулся на список кодов ошибок Планирование кодов ошибок
DifferentEffort = Требуемая мощность этой службы не соответствует емкости ресурса {имя ресурса}.
Не совсем правда, но что угодно. Реальная проблема заключается в том, что объект назначения не ссылается на Сервис, поэтому емкость несуществующего сервиса - "Позитивная бесконечность" (2139095040). Установка Усилия, равной чему угодно, но 2139095040 выдает эту ошибку. Здесь на самом деле не проверяется емкость ресурса, а просто говорится, что несуществующая емкость службы должна быть равна = 2139095040.
В любом случае, чтобы обойти это, я удалил логику, которая устанавливает усилие = 1,0, и когда BookRequest
или же RescheduleRequest
возвращается ErrorCode.ResourceBusy
Я проверяю Capacity vs # appts, запланированную в этом временном интервале, и если осталась свободная емкость, я вынуждаю ее перезаписывать, используя Create или Update.
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 в любом случае, потому что теперь нам не нужен одноразовый рабочий процесс по требованию для обновления существующих данных.
Я надеюсь, что это поможет вам сэкономить время, если вам когда-нибудь понадобится это сделать.