· 6 years ago · Sep 04, 2019, 07:00 PM
1global class Appointments {
2
3 static Integer slotMinsDuration = 5;
4 private AppointmentsHelper appointmentsHelper;
5 public static Map<Id, Map<String, AppointmentType>> appointmentTypesById;
6
7 public class Config {
8 public Integer calStartHour, calEndHour, unitpx, calendarDays, calendarInterval;
9 public Map<String, Map<String, List<String>>> cancellationReasons;
10 }
11
12 @RemoteAction
13 public static Config getConfig() {
14 AppointmentsHelper appointmentsHelper = AppointmentsHelper.getInstance();
15 return appointmentsHelper.getConfig();
16 }
17
18 public class MapsConfig {
19 public String url, token;
20 }
21
22 public class AppointmentException extends Exception {}
23
24 @RemoteAction
25 public static MapsConfig getMapsConfig() {
26 AppointmentsHelper appointmentsHelper = AppointmentsHelper.getInstance();
27 return appointmentsHelper.getMapsConfig();
28 }
29
30 @RemoteAction
31 public static PicklistSchema getReschedulingReasons() {
32 return new PicklistSchema(sked__Job__c.Rescheduling_Reason__c);
33 }
34
35 @RemoteAction
36 public static PicklistSchema getPhoneVisitTypes() {
37 return new PicklistSchema(sked__Job__c.Phone_Visit_Type__c);
38 }
39
40 @RemoteAction
41 public static Map<String, Map<String, Map<String, List<String>>>> getCancellationReasons() {
42 AppointmentsHelper appointmentsHelper = AppointmentsHelper.getInstance();
43 return appointmentsHelper.getCancellationReasons();
44 }
45
46 public static String findObjectNameFromRecordIdPrefix(String recordIdOrPrefix) {
47 String objectName = '';
48 try {
49 //Get prefix from record ID
50 //This assumes that you have passed at least 3 characters
51 String myIdPrefix = String.valueOf(recordIdOrPrefix).substring(0,3);
52
53 //Get schema information
54 Map<String, Schema.SObjectType> gd = Schema.getGlobalDescribe();
55
56 //Loop through all the sObject types returned by Schema
57 for(Schema.SObjectType stype : gd.values()){
58 Schema.DescribeSObjectResult r = stype.getDescribe();
59 String prefix = r.getKeyPrefix();
60
61 //Check if the prefix matches with requested prefix
62 if(prefix!=null && prefix.equals(myIdPrefix)){
63 objectName = r.getName();
64 break;
65 }
66 }
67 } catch(Exception e) {
68 System.debug(e);
69 }
70 return objectName;
71 }
72
73 //Gets all enabled appointment types without filtering(only used in suggest feature(deprecated))
74 @RemoteAction
75 public static Map<String,AppointmentType> getAllAppointmentTypes() {
76 AppointmentsHelper appointmentsHelper = AppointmentsHelper.getInstance();
77 return appointmentsHelper.getAllAppointmentTypes();
78 }
79
80 //Returns available apointment types for a new appointment according to workflow conditions from a patientId
81 @RemoteAction
82 public static Map<String, AppointmentType> getAppointmentTypes(Id patientId) {
83 if (appointmentTypesById != null && appointmentTypesById.containsKey(patientId)) {
84 return appointmentTypesById.get(patientId);
85 } else {
86 Set<String> types1A = get1ATypes();
87 Account a = [SELECT Id,
88 Status__c,
89 Parent.Name,
90 Sub_Status__c,
91 RecordType.Name,
92 Parent.SNF_Enabled__c,
93 Last_Entry_Appointment_Category__c,
94 Primary_LVN__r.sked__Is_Active__c,
95 Secondary_LVN__r.sked__Is_Active__c,
96 Primary_Care_Provider__r.sked__Is_Active__c,
97 SNF__c,
98 (SELECT Program_Name__c, Status__c, Substatus__c, Program__r.Appointment_Categories__c FROM Enrollments__r WHERE End__c = null),
99 (SELECT Id
100 FROM sked__Jobs__r
101 WHERE sked__Job_Status__c != :Constants.JOB_STATUS_COMPLETE
102 AND sked__Job_Status__c != :Constants.JOB_STATUS_CANCELLED
103 AND Type_Code__c IN :types1A)
104 FROM Account
105 WHERE Id = :patientId LIMIT 1];
106 return getAppointmentTypes(a);
107 }
108 }
109
110 @RemoteAction
111 webservice static String isPossibleCreateAppointments(Id patientId) {
112 return isPossibleGoToCalendar(patientId);
113 }
114
115 public static String isPossibleGoToCalendar(Id patientId) {
116 try {
117 if(!(appointmentTypesById != null && appointmentTypesById.containsKey(patientId))) {
118 Set<String> types1A = get1ATypes();
119 Account a = [SELECT Id,
120 Status__c,
121 Parent.Name,
122 Sub_Status__c,
123 RecordType.Name,
124 Parent.SNF_Enabled__c,
125 Last_Entry_Appointment_Category__c,
126 Primary_LVN__r.sked__Is_Active__c,
127 Secondary_LVN__r.sked__Is_Active__c,
128 Primary_Care_Provider__r.sked__Is_Active__c,
129 SNF__c,
130 (SELECT Program_Name__c, Status__c, Substatus__c, Program__r.Appointment_Categories__c FROM Enrollments__r WHERE End__c = null),
131 (SELECT Id
132 FROM sked__Jobs__r
133 WHERE sked__Job_Status__c != :Constants.JOB_STATUS_COMPLETE
134 AND sked__Job_Status__c != :Constants.JOB_STATUS_CANCELLED
135 AND Type_Code__c IN :types1A)
136 FROM Account
137 WHERE Id = :patientId LIMIT 1];
138
139 if (a.Primary_Care_Provider__c != null && !a.Primary_Care_Provider__r.sked__Is_Active__c) {
140 throw new AppointmentException(Constants.MSG_PRIMARY_CCP_INACTIVE);
141 } else if (a.Primary_LVN__c != null && !a.Primary_LVN__r.sked__Is_Active__c) {
142 throw new AppointmentException(Constants.MSG_PRIMARY_LVN_INACTIVE);
143 } else if (a.Secondary_LVN__c != null && !a.Secondary_LVN__r.sked__Is_Active__c) {
144 throw new AppointmentException(Constants.MSG_SECONDARY_LVN_INACTIVE);
145 }
146 Map<String, AppointmentType> appointments = getAppointmentTypes(a);
147 }
148 return '{"response":true}';
149 } catch(Exception e) {
150 return '{"response":false, "message": "'+e.getMessage()+'"}';
151 }
152 }
153
154 @RemoteAction
155 public static boolean isTOCIAvailable(Id patientId) {
156 Account patient = [Select Id,Pro_TOC_latest_status__c FROM Account WHERE Id =:patientId LIMIT 1];
157 return patient.Pro_TOC_latest_status__c == Constants.STATUS_NEW_PATIENT;
158 }
159
160 //Returns available apointment types for a new appointment according to workflow conditions from a patient's account
161 private static Map<String, AppointmentType> getAppointmentTypes(Account a) {
162 Map<String, AppointmentType> types;
163 Set<String> programs = new Set<String>();
164 Set<String> appCategoriesAccordingEnrollment = new Set<String>();
165 Map<String, List<String>> programStSubSt = new Map<String, List<String>>();
166 if(!a.Enrollments__r.isEmpty()) {
167 for (Enrollment__c en :a.Enrollments__r) {
168 programs.add(en.Program_Name__c);
169 programStSubSt.put(en.Program_Name__c, new List<String>{en.Status__c,en.Substatus__c});
170 if (en.Program__r.Appointment_Categories__c != null) {
171 appCategoriesAccordingEnrollment.addAll(en.Program__r.Appointment_Categories__c.split(';'));
172 }
173 }
174 }
175
176 if (appointmentTypesById != null && appointmentTypesById.containsKey(a.Id)) {
177 types = appointmentTypesById.get(a.Id);
178 } else {
179 types = getAllAppointmentTypes();
180 for (String code :types.keySet()) {
181 AppointmentType appType = types.get(code);
182 if (appType.disable) {
183 types.remove(code);
184 } else if (a.RecordType.Name == Constants.HOSPITAL_TYPE && appType.categoryCode != Constants.CATEGORY_OUTREACH) {
185 types.remove(code);
186 } else if (a.RecordType.Name == Constants.HOSPITAL_TYPE) {
187 // no need for additional checks. It's hospital + Outreach
188 continue;
189 } else if (programs.isEmpty()) {
190 throw new AppointmentException('This patient is not enrolled in any program.');
191 } else if (appCategoriesAccordingEnrollment.isEmpty()) {
192 throw new AppointmentException('The programs to which this patient is enrolled have not configured the allowed appt categories. Consult the Tapestry administrators.');
193 } else if (!appCategoriesAccordingEnrollment.contains(appType.categoryCode) && !appCategoriesAccordingEnrollment.contains(appType.code)) {
194 types.remove(code);
195
196 /**
197 Rule to HC20 and PC updated in assembla card #1567
198 1. The first appt is an lvn_only_1 or a 1a
199 2. If the first appt is an lvn_only_1a the next appt can be the 2a but it can't be the fa, i.e. 2a's are only used when lvn_only_1a's are used
200 3. If the first appt is an 1a, the next appt can be an fa but it can't be a 2a
201 4. If a 2a has been scheduled, another 2a can't be schedule, it has to be an fa
202 */
203
204 // LVN Only
205 } else if (appType.categoryCode == Constants.CATEGORY_LVN_ONLY
206 && ((!programs.contains(Constants.PROGRAM_HC20)
207 || (!((programStSubSt.get(Constants.PROGRAM_HC20).get(0) == Constants.STATUS_NEW_PATIENT && programStSubSt.get(Constants.PROGRAM_HC20).get(1) == Constants.SUBSTATUS_APPT_COMPLETED) || programStSubSt.get(Constants.PROGRAM_HC20).get(0) == Constants.STATUS_ENROLLED || programStSubSt.get(Constants.PROGRAM_HC20).get(0) == Constants.STATUS_ENROLLED_INACTIVE_PRO) &&
208 !(programStSubSt.get(Constants.PROGRAM_HC20).get(0) == Constants.STATUS_NEW_PATIENT && programStSubSt.get(Constants.PROGRAM_HC20).get(1) == Constants.SUBSTATUS_SCHEDULED))
209 || (programStSubSt.get(Constants.PROGRAM_HC20).get(0) == Constants.STATUS_NEW_PATIENT && programStSubSt.get(Constants.PROGRAM_HC20).get(1) == Constants.SUBSTATUS_2A_SCHEDULED)
210 || (programStSubSt.get(Constants.PROGRAM_HC20).get(0) == Constants.STATUS_NEW_PATIENT && programStSubSt.get(Constants.PROGRAM_HC20).get(1) == Constants.SUBSTATUS_2A_COMPLETED)
211 || !(a.Last_Entry_Appointment_Category__c == Constants.CATEGORY_LVN_ONLY_2A))
212 && (!programs.contains(Constants.PROGRAM_PC)
213 || (!((programStSubSt.get(Constants.PROGRAM_PC).get(0) == Constants.STATUS_NEW_PATIENT && programStSubSt.get(Constants.PROGRAM_PC).get(1) == Constants.SUBSTATUS_APPT_COMPLETED) || programStSubSt.get(Constants.PROGRAM_PC).get(0) == Constants.STATUS_ENROLLED || programStSubSt.get(Constants.PROGRAM_PC).get(0) == Constants.STATUS_ENROLLED_INACTIVE_PRO) &&
214 !(programStSubSt.get(Constants.PROGRAM_PC).get(0) == Constants.STATUS_NEW_PATIENT && programStSubSt.get(Constants.PROGRAM_PC).get(1) == Constants.SUBSTATUS_SCHEDULED))))) {
215 types.remove(code);
216
217 // 1A and LVN Only - 1A
218 } else if ((appType.categoryCode == Constants.CATEGORY_1A || appType.categoryCode == Constants.CATEGORY_LVN_ONLY_1A)
219 && (!programs.contains(Constants.PROGRAM_HC20)
220 || (!(programStSubSt.get(Constants.PROGRAM_HC20).get(0) == Constants.STATUS_NEW_PATIENT && programStSubSt.get(Constants.PROGRAM_HC20).get(1) == Constants.SUBSTATUS_UNSCHEDULED
221 || programStSubSt.get(Constants.PROGRAM_HC20).get(0) == Constants.STATUS_OPEN_LEAD_FIELD || programStSubSt.get(Constants.PROGRAM_HC20).get(0) == Constants.STATUS_OPEN_LEAD_REMOTE)))) {
222 types.remove(code);
223
224 // LVN Only - 2A
225 } else if ((appType.categoryCode == Constants.CATEGORY_LVN_ONLY_2A)
226 && !(programs.contains(Constants.PROGRAM_HC20)
227 && ((programStSubSt.get(Constants.PROGRAM_HC20).get(0) == Constants.STATUS_NEW_PATIENT && programStSubSt.get(Constants.PROGRAM_HC20).get(1) == Constants.SUBSTATUS_SCHEDULED)
228 || (programStSubSt.get(Constants.PROGRAM_HC20).get(0) == Constants.STATUS_NEW_PATIENT && programStSubSt.get(Constants.PROGRAM_HC20).get(1) == Constants.SUBSTATUS_APPT_COMPLETED))
229 && (a.Last_Entry_Appointment_Category__c == Constants.CATEGORY_1A
230 && a.Last_Entry_Appointment_Category__c != Constants.CATEGORY_LVN_ONLY_2A))) {
231 types.remove(code);
232
233 // 1A PC and LVN Only - PC 1A
234 } else if ((appType.categoryCode == Constants.CATEGORY_PC_1A || appType.categoryCode == Constants.CATEGORY_LVN_ONLY_PC_1A)
235 && (!programs.contains(Constants.PROGRAM_PC)
236 || (!(programStSubSt.get(Constants.PROGRAM_PC).get(0) == Constants.STATUS_NEW_PATIENT && programStSubSt.get(Constants.PROGRAM_PC).get(1) == Constants.SUBSTATUS_UNSCHEDULED
237 || programStSubSt.get(Constants.PROGRAM_PC).get(0) == Constants.STATUS_OPEN_LEAD_FIELD || programStSubSt.get(Constants.PROGRAM_PC).get(0) == Constants.STATUS_OPEN_LEAD_REMOTE)))) {
238 types.remove(code);
239
240 // HC20 FA and BH
241 } else if ((appType.categoryCode == Constants.CATEGORY_FA || appType.categoryCode == Constants.CATEGORY_BH_1A || appType.categoryCode == Constants.CATEGORY_BH_FA)
242 && !(programs.contains(Constants.PROGRAM_HC20)
243 && ((programStSubSt.get(Constants.PROGRAM_HC20).get(0) == Constants.STATUS_NEW_PATIENT && programStSubSt.get(Constants.PROGRAM_HC20).get(1) == Constants.SUBSTATUS_SCHEDULED)
244 || (programStSubSt.get(Constants.PROGRAM_HC20).get(0) == Constants.STATUS_NEW_PATIENT && programStSubSt.get(Constants.PROGRAM_HC20).get(1) == Constants.SUBSTATUS_APPT_COMPLETED)
245 || (programStSubSt.get(Constants.PROGRAM_HC20).get(0) == Constants.STATUS_NEW_PATIENT && programStSubSt.get(Constants.PROGRAM_HC20).get(1) == Constants.SUBSTATUS_2A_SCHEDULED)
246 || programStSubSt.get(Constants.PROGRAM_HC20).get(0) == Constants.STATUS_ENROLLED
247 || programStSubSt.get(Constants.PROGRAM_HC20).get(0) == Constants.STATUS_ENROLLED_INACTIVE_PRO)
248 && (a.Last_Entry_Appointment_Category__c == Constants.CATEGORY_1A
249 || a.Last_Entry_Appointment_Category__c == Constants.CATEGORY_2A
250 || a.Last_Entry_Appointment_Category__c == Constants.CATEGORY_FA
251 || a.Last_Entry_Appointment_Category__c == null))) {
252 types.remove(code);
253
254 // PC FA
255 } else if (appType.categoryCode == Constants.CATEGORY_PC_FA
256 && !(programs.contains(Constants.PROGRAM_PC)
257 && ((programStSubSt.get(Constants.PROGRAM_PC).get(0) == Constants.STATUS_NEW_PATIENT && programStSubSt.get(Constants.PROGRAM_PC).get(1) == Constants.SUBSTATUS_SCHEDULED)
258 || (programStSubSt.get(Constants.PROGRAM_PC).get(0) == Constants.STATUS_NEW_PATIENT && programStSubSt.get(Constants.PROGRAM_PC).get(1) == Constants.SUBSTATUS_APPT_COMPLETED)
259 || (programStSubSt.get(Constants.PROGRAM_PC).get(0) == Constants.STATUS_NEW_PATIENT && programStSubSt.get(Constants.PROGRAM_PC).get(1) == Constants.SUBSTATUS_2A_SCHEDULED)
260 || programStSubSt.get(Constants.PROGRAM_PC).get(0) == Constants.STATUS_ENROLLED
261 || programStSubSt.get(Constants.PROGRAM_PC).get(0) == Constants.STATUS_ENROLLED_INACTIVE_PRO)
262 && (a.Last_Entry_Appointment_Category__c == Constants.CATEGORY_PC_1A
263 || a.Last_Entry_Appointment_Category__c == Constants.CATEGORY_PC_2A
264 || a.Last_Entry_Appointment_Category__c == Constants.CATEGORY_PC_FA
265 || a.Last_Entry_Appointment_Category__c == null))) {
266 types.remove(code);
267
268 // HC20 2A
269 } else if ((appType.categoryCode == Constants.CATEGORY_2A)
270 && !(programs.contains(Constants.PROGRAM_HC20)
271 && ((programStSubSt.get(Constants.PROGRAM_HC20).get(0) == Constants.STATUS_NEW_PATIENT && programStSubSt.get(Constants.PROGRAM_HC20).get(1) == Constants.SUBSTATUS_SCHEDULED)
272 || (programStSubSt.get(Constants.PROGRAM_HC20).get(0) == Constants.STATUS_NEW_PATIENT && programStSubSt.get(Constants.PROGRAM_HC20).get(1) == Constants.SUBSTATUS_APPT_COMPLETED)
273 || programStSubSt.get(Constants.PROGRAM_HC20).get(0) == Constants.STATUS_ENROLLED
274 || programStSubSt.get(Constants.PROGRAM_HC20).get(0) == Constants.STATUS_ENROLLED_INACTIVE_PRO)
275 && a.Last_Entry_Appointment_Category__c == Constants.CATEGORY_LVN_ONLY_1A)) {
276 types.remove(code);
277
278 // PC 2A
279 } else if ((appType.categoryCode == Constants.CATEGORY_PC_2A)
280 && !(programs.contains(Constants.PROGRAM_PC)
281 && ((programStSubSt.get(Constants.PROGRAM_PC).get(0) == Constants.STATUS_NEW_PATIENT && programStSubSt.get(Constants.PROGRAM_PC).get(1) == Constants.SUBSTATUS_SCHEDULED)
282 || (programStSubSt.get(Constants.PROGRAM_PC).get(0) == Constants.STATUS_NEW_PATIENT && programStSubSt.get(Constants.PROGRAM_PC).get(1) == Constants.SUBSTATUS_APPT_COMPLETED)
283 || programStSubSt.get(Constants.PROGRAM_PC).get(0) == Constants.STATUS_ENROLLED
284 || programStSubSt.get(Constants.PROGRAM_PC).get(0) == Constants.STATUS_ENROLLED_INACTIVE_PRO)
285 && a.Last_Entry_Appointment_Category__c == Constants.CATEGORY_LVN_ONLY_PC_1A)) {
286 types.remove(code);
287
288 // TOC
289 } else if (appType.categoryCode == Constants.CATEGORY_TOC
290 && (!programs.contains(Constants.PROGRAM_TOC)
291 || (programStSubSt.get(Constants.PROGRAM_TOC).get(0) != Constants.STATUS_NEW_PATIENT && programStSubSt.get(Constants.PROGRAM_TOC).get(1) != Constants.SUBSTATUS_TOC_MEDZED))) {
292 types.remove(code);
293
294 // HRA and HRA_AWE
295 } else if ((appType.categoryCode == Constants.CATEGORY_HRA || appType.categoryCode == Constants.CATEGORY_HRA_AWE)
296 && (!programs.contains(Constants.PROGRAM_HRA)
297 || !((programStSubSt.get(Constants.PROGRAM_HRA).get(0) == Constants.STATUS_OPEN_LEAD_REMOTE
298 || (programStSubSt.get(Constants.PROGRAM_HRA).get(0) == Constants.STATUS_NEW_PATIENT && programStSubSt.get(Constants.PROGRAM_HRA).get(1) == Constants.SUBSTATUS_UNSCHEDULED))))) {
299 types.remove(code);
300
301 // AWE and HRA_AWE
302 } else if ((appType.categoryCode == Constants.CATEGORY_AWE || appType.categoryCode == Constants.CATEGORY_HRA_AWE)
303 && (!programs.contains(Constants.PROGRAM_AWE)
304 || !(programStSubSt.get(Constants.PROGRAM_AWE).get(0) == Constants.STATUS_OPEN_LEAD_REMOTE
305 || (programStSubSt.get(Constants.PROGRAM_AWE).get(0) == Constants.STATUS_NEW_PATIENT && programStSubSt.get(Constants.PROGRAM_AWE).get(1) == Constants.SUBSTATUS_UNSCHEDULED)))) {
306 types.remove(code);
307
308 // HC20 => Specimen Collections
309 } else if ((appType.categoryCode == Constants.CATEGORY_SC || appType.code == Constants.CATEGORY_SC )
310 && (programs.contains(Constants.PROGRAM_HC20) && programStSubSt.get(Constants.PROGRAM_HC20).get(0) != Constants.STATUS_ENROLLED && programStSubSt.get(Constants.PROGRAM_HC20).get(0) != Constants.STATUS_ENROLLED_INACTIVE_PRO
311 && (programStSubSt.get(Constants.PROGRAM_HC20).get(0) == Constants.STATUS_NEW_PATIENT && programStSubSt.get(Constants.PROGRAM_HC20).get(1) == Constants.SUBSTATUS_APPT_COMPLETED))){
312 types.remove(code);
313
314 // HEDIS => Specimen Collections
315 } else if((appType.categoryCode == Constants.CATEGORY_SC || appType.code == Constants.CATEGORY_SC )
316 && ((programs.contains(Constants.PROGRAM_HEDIS) && programs.contains(Constants.PROGRAM_HC20))
317 || (programs.contains(Constants.PROGRAM_HEDIS) && programStSubSt.get(Constants.PROGRAM_HEDIS).get(0) != Constants.STATUS_OPEN_LEAD_REMOTE) )){
318 types.remove(code);
319
320 // OUTREACH
321 } else if (appType.categoryCode == Constants.CATEGORY_OUTREACH
322 && (!programs.contains(Constants.PROGRAM_HC20) || programStSubSt.get(Constants.PROGRAM_HC20).get(0) == Constants.STATUS_DISENROLLED || programStSubSt.get(Constants.PROGRAM_HC20).get(0) == Constants.STATUS_CLOSED_LEAD || programStSubSt.get(Constants.PROGRAM_HC20).get(0) == Constants.STATUS_OPEN_LEAD_REMOTE)
323 && (!programs.contains(Constants.PROGRAM_PC) || programStSubSt.get(Constants.PROGRAM_PC).get(0) == Constants.STATUS_DISENROLLED || programStSubSt.get(Constants.PROGRAM_PC).get(0) == Constants.STATUS_CLOSED_LEAD || programStSubSt.get(Constants.PROGRAM_PC).get(0) == Constants.STATUS_OPEN_LEAD_REMOTE)
324 && (!programs.contains(Constants.PROGRAM_TOC) || programStSubSt.get(Constants.PROGRAM_TOC).get(0) == Constants.STATUS_DISENROLLED || programStSubSt.get(Constants.PROGRAM_TOC).get(0) == Constants.STATUS_CLOSED_LEAD || programStSubSt.get(Constants.PROGRAM_TOC).get(0) == Constants.STATUS_OPEN_LEAD_REMOTE)) {
325 types.remove(code);
326
327 // PHONE VISIT
328 } else if ((appType.categoryCode == Constants.CATEGORY_PHONE_VISIT || appType.category == Constants.CATEGORY_PHONE_VISIT_LABEL)
329 && (!programs.contains(Constants.PROGRAM_HC20) || (programStSubSt.get(Constants.PROGRAM_HC20).get(0) != Constants.STATUS_ENROLLED && programStSubSt.get(Constants.PROGRAM_HC20).get(0) != Constants.STATUS_NEW_PATIENT && programStSubSt.get(Constants.PROGRAM_HC20).get(0) != Constants.STATUS_ENROLLED_INACTIVE_PRO))
330 && (!programs.contains(Constants.PROGRAM_PC) || (programStSubSt.get(Constants.PROGRAM_PC).get(0) != Constants.STATUS_ENROLLED && programStSubSt.get(Constants.PROGRAM_PC).get(0) != Constants.STATUS_NEW_PATIENT && programStSubSt.get(Constants.PROGRAM_PC).get(0) != Constants.STATUS_ENROLLED_INACTIVE_PRO))) {
331 types.remove(code);
332 }
333 appType.snfEnabled = (a.Parent == null || (a.Parent.SNF_Enabled__c != null && !a.Parent.SNF_Enabled__c)) ? null : a.SNF__c != null;
334 }
335
336 if (appointmentTypesById == null) {
337 appointmentTypesById = new Map<Id, Map<String, AppointmentType>>();
338 }
339 appointmentTypesById.put(a.Id, types);
340 }
341 if (types.isEmpty()) {
342 throw new AppointmentException('This patient does not have a valid status to schedule appointments. Check enrollment in programs.');
343 }
344 return types;
345 }
346
347 @RemoteAction
348 public static List<Market> getAllMarkets() {
349 List<Market> returnList = new List<Market>();
350 List<Market__c> markets = [SELECT Id, Name, Region__c FROM Market__c];
351 List<ResourceMarket__c> marketResources = [SELECT Id, resource__c, market__c, Map_Color__c FROM ResourceMarket__c];
352 List<sked__Resource__c> FAResources = [SELECT Id, PrimaryMarket__c, Map_Color__c FROM sked__Resource__c WHERE RecordType.Name IN :Constants.FA_TYPES];
353
354 for (Market__c market :markets) {
355 Market m = new Market();
356 m.regionId = market.Region__c;
357 m.name = market.Name;
358 m.id = market.Id;
359 m.resources = new Set<String>();
360 m.colorByResource = new Map<Id, String>();
361 if (!marketResources.isEmpty()) {
362 for (ResourceMarket__c rm : marketResources) {
363 if (market.Id.equals(rm.market__c)) {
364 m.resources.add(rm.resource__c);
365 m.colorByResource.put(rm.resource__c,rm.Map_Color__c);
366 }
367 }
368 }
369
370 for(sked__Resource__c resource :FAResources) {
371 if(resource.primaryMarket__c == m.id) {
372 m.resources.add(resource.Id);
373 m.colorByResource.put(resource.Id,resource.Map_Color__c);
374 }
375 }
376 returnList.add(m);
377 }
378 return returnList;
379 }
380
381 @RemoteAction
382 public static List<Zone> getZonesByMarket(String marketId) {
383 Map<Id, Zone__c> zonesMap = new Map<Id, Zone__c>([SELECT Id,Name,Market__c FROM Zone__c WHERE Market__c = :marketId]);
384 Map<Id, Subzone__c> subZones = new Map<Id, Subzone__c>([SELECT Id,Name,Zone__c FROM Subzone__c WHERE Zone__c IN : zonesMap.keySet()]);
385 List <Zip__c> Zips = [SELECT Id,Name, Subzone__c,Primary_LVN__c, Secondary_LVN__c FROM Zip__c WHERE Subzone__c IN :subZones.keySet() ];
386 List<Zone> returnList = new List<Zone>();
387 for(Zone__c zone : zonesMap.values()) {
388 Zone wZone = new Zone();
389 wZone.id = zone.Id;
390 wZone.name = zone.Name;
391 wZone.relatedLvnIds = new Set<String>();
392 for(Subzone__c subzone : subZones.values()) {
393 if(subzone.Zone__c == zone.Id) {
394 for(Zip__c zip : Zips) {
395 if(zip.Subzone__c == subzone.Id) {
396 if(zip.Primary_LVN__c!=null) wZone.relatedLvnIds.add(zip.Primary_LVN__c);
397 if(zip.Secondary_LVN__c!=null) wZone.relatedLvnIds.add(zip.Secondary_LVN__c);
398 }
399 }
400 }
401 }
402 returnList.add(wZone);
403 }
404 return returnList;
405 }
406
407 @RemoteAction
408 public static String getMarketByPatient(String patientId) {
409 return [SELECT Market__c FROM Account WHERE Id = :patientId Limit 1].Market__c;
410 }
411
412 @RemoteAction
413 public static String getFirstAppointmentId(String patientId) {
414 String ret;
415 List<sked__job__c> jobs = [SELECT Id FROM sked__Job__c
416 WHERE sked__Account__c = :patientId
417 AND sked__Start__c > TODAY
418 AND sked__Job_Status__c != :Constants.JOB_STATUS_COMPLETE
419 AND sked__Job_Status__c != :Constants.JOB_STATUS_CANCELLED
420 ORDER BY sked__Start__c
421 LIMIT 1];
422 if (!jobs.isEmpty()) ret = jobs.get(0).Id;
423 return ret;
424 }
425
426 @RemoteAction
427 public static String createPatient(String patientId) {
428 return Patients.createPatient(patientId, false, true, true);
429 }
430
431 @RemoteAction
432 public static AppointmentType getDefaultAppointmentType(Id patientId) {
433 sked__Job__c[] lastFollowUp = [SELECT Type_Code__c
434 FROM sked__Job__c
435 WHERE sked__Account__c = :patientId AND Type_Code__c LIKE 'FA%'
436 AND sked__Job_Status__c = :Constants.JOB_STATUS_COMPLETE
437 ORDER BY sked__Start__c DESC
438 LIMIT 1];
439 Map<String, AppointmentType> patientTypes = getAppointmentTypes(patientId);
440 if (!lastFollowUp.isEmpty() && patientTypes.containsKey(lastFollowUp[0].Type_Code__c)) {
441 return patientTypes.get(lastFollowUp[0].Type_Code__c);
442 } else {
443 List<AppointmentType> types = patientTypes.values();
444 types.sort();
445 return types.remove(0);
446 }
447 }
448
449 private static Appointment prepareAppointment(String typeCode) {
450 Appointment appt = new Appointment();
451 appt.type = getAllAppointmentTypes().get(typeCode);
452 appt.resources = new List<Resource>();
453 return appt;
454 }
455
456 private static Appointment setAppointmentResources(Appointment appt, Map<String, List<sked__Resource__c>> resourcesByType) {
457 Integer rCount, rLimit;
458 List<sked__Resource__c> resources, missingResources;
459 for (String rType : appt.type.requiredTypes.keySet()) {
460 Set<String> rTypes = new Set<String>();
461 if (rType.contains(',')){
462 String[] splittedrTypes = String.valueOf(rType).trim().split(',');
463 for (String s : splittedrTypes) {
464 rTypes.add(s);
465 }
466 } else {
467 rTypes.add(rType);
468 }
469 rCount = appt.type.requiredTypes.get(rType);
470 if (Constants.CATEGORIES_FIELD_AGENT.contains(appt.type.categoryCode)) {
471 // The required type is the actual category
472 rType = appt.type.categoryCode;
473 }
474 if (!resourcesByType.containsKey(rType)) {
475 // add an empty list, we'll use it later
476 resourcesByType.put(rType, new List<sked__Resource__c>());
477 }
478 resources = resourcesByType.get(rType);
479 if (rCount > resources.size()) {
480 rLimit = rCount - resources.size();
481 // Ugly but won't be executing this a lot
482 missingResources = [SELECT Id, Name, sked__Category__c ,sked__User__c, PrimaryMarket__c,
483 sked__User__r.smallphotourl, Timezone__c, Rate_Factor__c,
484 sked__Home_Address__c, sked__User__r.TimeZoneSidKey,
485 sked__User__r.Name, RecordTypeId, RecordType.Name, show_in_calendar__c, Map_Color__c, sked__Is_Active__c
486 FROM sked__Resource__c
487 WHERE RecordType.Name in :rTypes
488 AND (NOT Name LIKE '%Test%')
489 AND Id NOT IN :resources
490 LIMIT :rLimit];
491 resources.addAll(missingResources);
492 }
493 }
494
495 // Add resources in order
496 Map<Boolean, List<Resource>> rObjectsByType = new Map<Boolean, List<Resource>>();
497 for (Resource r : appt.type.resources) {
498 String[] splittedTypes = String.valueOf(r.type).trim().split(',');
499 if (rObjectsByType.get(r.remote) == null){
500 rObjectsByType.put(r.remote, new List<Resource>());
501 }
502 boolean added = false;
503 if (Constants.CATEGORIES_FIELD_AGENT.contains(appt.type.categoryCode)) {
504 List<String> faTypes = new List<String>(Constants.CATEGORIES_FIELD_AGENT);
505 faTypes.addAll(splittedTypes);
506 splittedTypes = faTypes;
507 }
508 for (String s : splittedTypes) {
509 if ( !added && resourcesByType.get(s) != null && resourcesByType.get(s).size() > 0){
510 rObjectsByType.get(r.remote).add(new Resource(resourcesByType.get(s).remove(0), r.remote));
511 added = true;
512 }
513 }
514 if (!added && resourcesByType.get(String.valueOf(r.type)) != null && resourcesByType.get(String.valueOf(r.type)).size() > 0) {
515 rObjectsByType.get(r.remote).add(new Resource(resourcesByType.get(String.valueOf(r.type)).remove(0), r.remote));
516 added = true;
517 }
518 }
519
520 if (rObjectsByType.get(true) != null){
521 appt.resources.addAll(rObjectsByType.get(true));
522 }
523
524 if (rObjectsByType.get(false) != null){
525 appt.resources.addAll(rObjectsByType.get(false));
526 }
527
528 //Pleasing requests of the frontend, the entries are added to each resource.
529 Set<Id> resourceId = new Set<Id>();
530 for(Resource r : appt.resources) { resourceId.add(r.Id); }
531 Map<Id, Map<String,List<sked__Availability_Template_Entry__c>>> entriesByResource = getEntriesByResource(resourceId);
532
533 for(Integer i = 0; i < appt.resources.size(); i++) {
534 Resource re = appt.resources[i];
535 re.entries = entriesByResource.containsKey(re.Id) ? entriesByResource.get(re.Id) : null;
536 appt.resources[i] = re;
537 }
538 return appt;
539 }
540
541 @RemoteAction
542 public static Appointment newAppointmentWithoutPatient(String typeCode) {
543 Appointment appt = prepareAppointment(typeCode);
544 return setAppointmentResources(appt, new Map<String, List<sked__Resource__c>>());
545 }
546
547 @RemoteAction
548 public static Appointment newAppointment(String patientId, String typeCode) {
549 Appointment appt = prepareAppointment(typeCode);
550 //Get patient info
551 Account patient = [SELECT Id,
552 Name,
553 PRS__c,
554 Color__c,
555 Region__c,
556 Status__c,
557 Gender__c,
558 EHR_MRN__c,
559 Language__c,
560 Color_Dark__c,
561 Touch_Count__c,
562 RecordType.Name,
563 Date_of_Birth__c,
564 Lead_Suborigin__c,
565 Patient_Unique_ID__c,
566 Subzone__c,
567 BillingCity,
568 BillingState,
569 BillingStreet,
570 BillingCountry,
571 BillingPostalCode,
572 Pro_TOC_latest_status__c,
573 Pro_TOC_latest_substatus__c,
574 Insurance_Plan_Name__c,
575 Primary_Physician__r.Id,
576 Primary_Physician__r.Name,
577 Primary_Physician__r.Timezone__c,
578 Primary_Physician__r.Map_Color__c,
579 Primary_Physician__r.RecordTypeId,
580 Primary_Physician__r.sked__User__c,
581 Primary_Physician__r.Rate_Factor__c,
582 Primary_Physician__r.RecordType.Name,
583 Primary_Physician__r.PrimaryMarket__c,
584 Primary_Physician__r.sked__Category__c,
585 Primary_Physician__r.sked__User__r.Name,
586 Primary_Physician__r.show_in_calendar__c,
587 Primary_Physician__r.sked__Home_Address__c,
588 Primary_Physician__r.sked__User__r.smallphotourl,
589 Primary_Physician__r.sked__User__r.TimeZoneSidKey,
590 Primary_Physician__r.sked__Is_Active__c,
591 Primary_LVN__r.Id,
592 Primary_LVN__r.Name,
593 Primary_LVN__r.Timezone__c,
594 Primary_LVN__r.Map_Color__c,
595 Primary_LVN__r.RecordTypeId,
596 Primary_LVN__r.sked__User__c,
597 Primary_LVN__r.Rate_Factor__c,
598 Primary_LVN__r.RecordType.Name,
599 Primary_LVN__r.PrimaryMarket__c,
600 Primary_LVN__r.sked__Category__c,
601 Primary_LVN__r.sked__User__r.Name,
602 Primary_LVN__r.show_in_calendar__c,
603 Primary_LVN__r.sked__Home_Address__c,
604 Primary_LVN__r.sked__User__r.smallphotourl,
605 Primary_LVN__r.sked__User__r.TimeZoneSidKey,
606 Primary_LVN__r.sked__Is_Active__c,
607 Secondary_LVN__r.Id,
608 Secondary_LVN__r.Name,
609 Secondary_LVN__r.Timezone__c,
610 Secondary_LVN__r.Map_Color__c,
611 Secondary_LVN__r.RecordTypeId,
612 Secondary_LVN__r.sked__User__c,
613 Secondary_LVN__r.Rate_Factor__c,
614 Secondary_LVN__r.RecordType.Name,
615 Secondary_LVN__r.PrimaryMarket__c,
616 Secondary_LVN__r.sked__Category__c,
617 Secondary_LVN__r.sked__User__r.Name,
618 Secondary_LVN__r.show_in_calendar__c,
619 Secondary_LVN__r.sked__Home_Address__c,
620 Secondary_LVN__r.sked__User__r.smallphotourl,
621 Secondary_LVN__r.sked__User__r.TimeZoneSidKey,
622 Secondary_LVN__r.sked__Is_Active__c,
623 Primary_BH__r.Id,
624 Primary_BH__r.Name,
625 Primary_BH__r.Timezone__c,
626 Primary_BH__r.Map_Color__c,
627 Primary_BH__r.RecordTypeId,
628 Primary_BH__r.sked__User__c,
629 Primary_BH__r.Rate_Factor__c,
630 Primary_BH__r.RecordType.Name,
631 Primary_BH__r.PrimaryMarket__c,
632 Primary_BH__r.sked__Category__c,
633 Primary_BH__r.sked__User__r.Name,
634 Primary_BH__r.show_in_calendar__c,
635 Primary_BH__r.sked__Home_Address__c,
636 Primary_BH__r.sked__User__r.smallphotourl,
637 Primary_BH__r.sked__User__r.TimeZoneSidKey,
638 Primary_BH__r.sked__Is_Active__c,
639 Primary_Care_Provider__r.Id,
640 Primary_Care_Provider__r.Name,
641 Primary_Care_Provider__r.Timezone__c,
642 Primary_Care_Provider__r.Map_Color__c,
643 Primary_Care_Provider__r.RecordTypeId,
644 Primary_Care_Provider__r.sked__User__c,
645 Primary_Care_Provider__r.Rate_Factor__c,
646 Primary_Care_Provider__r.RecordType.Name,
647 Primary_Care_Provider__r.PrimaryMarket__c,
648 Primary_Care_Provider__r.sked__Category__c,
649 Primary_Care_Provider__r.sked__User__r.Name,
650 Primary_Care_Provider__r.show_in_calendar__c,
651 Primary_Care_Provider__r.sked__Home_Address__c,
652 Primary_Care_Provider__r.sked__User__r.smallphotourl,
653 Primary_Care_Provider__r.sked__User__r.TimeZoneSidKey,
654 Primary_Care_Provider__r.sked__Is_Active__c,
655 Primary_ROC__r.Id,
656 Primary_ROC__r.Name,
657 Primary_ROC__r.Timezone__c,
658 Primary_ROC__r.RecordTypeId,
659 Primary_ROC__r.Map_Color__c,
660 Primary_ROC__r.sked__User__c,
661 Primary_ROC__r.Rate_Factor__c,
662 Primary_ROC__r.RecordType.Name,
663 Primary_ROC__r.PrimaryMarket__c,
664 Primary_ROC__r.sked__Category__c,
665 Primary_ROC__r.sked__User__r.Name,
666 Primary_ROC__r.show_in_calendar__c,
667 Primary_ROC__r.sked__Home_Address__c,
668 Primary_ROC__r.sked__User__r.smallphotourl,
669 Primary_ROC__r.sked__User__r.TimeZoneSidKey,
670 Primary_ROC__r.sked__Is_Active__c,
671 Parent.Name,
672 Parent.BillingCity,
673 Parent.BillingState,
674 Parent.BillingStreet,
675 Parent.BillingCountry,
676 Parent.SNF_Enabled__c,
677 Parent.BillingPostalCode,
678 Hospital__c,
679 Completed_Appointments__c,
680 Cancelled_Appointments__c,
681 SNF__c,
682 In_SNF__c,
683 SNF__r.Name,
684 SNF__r.RecordType.Name,
685 SNF__r.Subzone__c,
686 SNF__r.BillingCity,
687 SNF__r.BillingState,
688 SNF__r.BillingStreet,
689 SNF__r.BillingCountry,
690 SNF__r.BillingPostalCode,
691 (SELECT Id, Name FROM Contacts WHERE RecordType.Name = :Constants.CONTACT_PATIENT_TYPE),
692 (SELECT Id, Date_Time__c, Facility__r.Phone, Facility__r.Name, Facility__r.Subzone__c, Facility__r.BillingCity, Facility__r.BillingState, Facility__r.BillingStreet, Facility__r.BillingCountry, Facility__r.BillingPostalCode, Facility__r.RecordType.Name
693 FROM ADT_records__r WHERE Facility__r.RecordType.Name = :Constants.HOSPITAL_TYPE AND Type__c = :Constants.ADT_TYPE_ADMISSION ORDER BY Date_Time__c DESC LIMIT 1)
694 FROM Account
695 WHERE Id = :patientId];
696 appt.patient = new Patient(patient);
697 appt.patient.locations.put(Constants.PATIENT_HOME_TYPE, new Location(patient));
698 appt.address = appt.patient.address;
699
700 if(patient.Hospital__c != null) {
701 if(!patient.ADT_records__r.isEmpty()) {
702 appt.patient.locations.put(Constants.HOSPITAL_TYPE, new Hospital(patient.ADT_records__r[0]));
703 }
704 }
705
706 if(patient.SNF__r!=null) {
707 appt.patient.locations.put(Constants.SNF_TYPE, new SNF(patient.SNF__r));
708 }
709
710 appt.patient.parent = patient.Parent.Name;
711 appt.patient.hospital = patient.Hospital__c;
712 appt.patient.enrolledPrograms = AppointmentsHelper.getEnrollmentList([
713 SELECT Id, Program_Name__c, Status__c, Program__c
714 FROM Enrollment__c
715 WHERE Status__c NOT IN :Constants.STATUS_NOT_ENROLLED_SET AND End__c = null AND AccountId__c = :patientId]
716 );
717
718 Map<String, List<sked__Resource__c>> resourcesByType = new Map<String, List<sked__Resource__c>>();
719 List<sked__Resource__c> resources = new List<sked__Resource__c>();
720 // Add primary resources to map
721 if (patient.Primary_BH__r != null) {
722 resources = new List<sked__Resource__c>{patient.Primary_BH__r};
723 resourcesByType.put(patient.Primary_BH__r.RecordType.Name, resources);
724 }
725 if (patient.Primary_Physician__r != null) {
726 resources = new List<sked__Resource__c>{patient.Primary_Physician__r};
727 resourcesByType.put(patient.Primary_Physician__r.RecordType.Name, resources);
728 }
729 if (patient.Primary_LVN__r != null) {
730 resources = new List<sked__Resource__c>{patient.Primary_LVN__r};
731 resourcesByType.put(patient.Primary_LVN__r.RecordType.Name, resources);
732 }
733 if (patient.Secondary_LVN__r != null) {
734 resources = new List<sked__Resource__c>{patient.Secondary_LVN__r};
735 if (resourcesByType.containsKey(patient.Secondary_LVN__r.RecordType.Name)) {
736 resourcesByType.get(patient.Secondary_LVN__r.RecordType.Name).addAll(resources);
737 } else {
738 resourcesByType.put(patient.Secondary_LVN__r.RecordType.Name, resources);
739 }
740 }
741 if (patient.Primary_Care_Provider__r != null) {
742 resources = new List<sked__Resource__c>{patient.Primary_Care_Provider__r};
743 resourcesByType.put(patient.Primary_Care_Provider__r.RecordType.Name, resources);
744 }
745 if (patient.Primary_ROC__r != null) {
746 resources = new List<sked__Resource__c>{patient.Primary_ROC__r};
747 for (String cat : Constants.CATEGORIES_FIELD_AGENT) {
748 resourcesByType.put(cat, resources);
749 }
750 }
751 appt.address = appt.patient.address;
752 appt.Patient.addresses = new Map<String, Address> ();
753 setSNFDetails(appt, patient);
754 setHospitalDetails(appt, patient);
755 return setAppointmentResources(appt, resourcesByType);
756 }
757
758 public static String getFacilityAddress(Account a){
759 String address = a.SNF__r.BillingStreet + ', ' + a.SNF__r.BillingCity + ', ' + a.SNF__r.BillingState + ' ' + ((a.SNF__r.BillingPostalCode == null) ? '' : a.SNF__r.BillingPostalCode) + ', ' + a.SNF__r.BillingCountry;
760 return address;
761 }
762
763 @RemoteAction
764 public static List<Account> getPatient(String param, String pType) {
765 List<Account> acct = new List<Account>();
766 String accountType = pType == 'hospital' ? Constants.HOSPITAL_TYPE : Constants.PATIENT_TYPE;
767 if (param.contains('-') || param.contains('/')) {
768 try {
769 param = param.replaceAll('-', '/');
770 Date d = Date.parse(param);
771 acct = [SELECT Id, Name, BillingStreet, BillingCity, BillingState, BillingPostalCode, HCO_Member_ID__c, Date_of_Birth__c,
772 Phone, Home_Phone__c,RecordType.Name,Zip__r.Subzone__r.Zone__r.Market__c,Zip_Secondary__r.Subzone__r.Zone__r.Market__c, (Select Id From Enrollments__r)
773 FROM Account
774 WHERE RecordType.Name = :accountType
775 AND Date_of_Birth__c = :d LIMIT 20];
776 } catch (Exception e){
777 System.debug(e.getMessage());
778 }
779 } else {
780 acct = [SELECT Id, Name, BillingStreet, BillingCity, BillingState, BillingPostalCode, HCO_Member_ID__c, Date_of_Birth__c, EHR_MRN__c,
781 Phone, Home_Phone__c,RecordType.Name,Zip__r.Subzone__r.Zone__r.Market__c,Zip_Secondary__r.Subzone__r.Zone__r.Market__c, (Select Id From Enrollments__r)
782 FROM Account
783 WHERE RecordType.Name = :accountType
784 AND (Name LIKE :('%' + param + '%') OR HCO_Member_ID__c LIKE :('%' + param + '%') OR EHR_MRN__c LIKE :('%' + param + '%')) LIMIT 20];
785 }
786 return acct;
787 }
788
789 @RemoteAction
790 public static List<Account> getRecentPatients(String pType) {
791 List<Account> acct = new List<Account>();
792 String accountType = pType == 'Hospital' ? Constants.HOSPITAL_TYPE : Constants.PATIENT_TYPE;
793 acct = [SELECT Id, Name, BillingStreet, BillingCity, BillingState, BillingPostalCode, HCO_Member_ID__c, Date_of_Birth__c,
794 Phone, Home_Phone__c,RecordType.Name,Zip__r.Subzone__r.Zone__r.Market__c,Zip_Secondary__r.Subzone__r.Zone__r.Market__c, (Select Id From Enrollments__r)
795 FROM Account
796 WHERE RecordType.Name = :accountType
797 ORDER BY LastModifiedDate DESC LIMIT 10];
798 return acct;
799 }
800
801 @RemoteAction
802 public static Resource getPersonById(Id id) {
803 Resource ret = null;
804 String type = findObjectNameFromRecordIdPrefix(id);
805
806 if (type == 'sked__Resource__c') {
807 List<sked__Resource__c> resources = [SELECT Id, Name, sked__Is_Active__c, PrimaryMarket__c, sked__Category__c ,sked__User__c, sked__User__r.smallphotourl, Timezone__c, sked__Home_Address__c, Rate_Factor__c,sked__User__r.TimeZoneSidKey , sked__User__r.Name, RecordTypeId, RecordType.Name, show_in_calendar__c,Map_Color__c FROM sked__Resource__c WHERE Id = :id];
808
809 if(resources.size() > 0 ) {
810 Set<Id> resourceId = new Set<Id>();
811 resourceId.add(resources[0].Id);
812 Map<Id, Map<String,List<sked__Availability_Template_Entry__c>>> entriesByResource = getEntriesByResource(resourceId);
813 ret = new Resource(resources[0], false);
814 ret.entries = entriesByResource.containsKey(resources[0].Id) ? entriesByResource.get(resources[0].Id) : null;
815 }
816 } else {
817 List<Account> a = [SELECT Id, Name, Color__c, Color_Dark__c, TimeZone__c, BillingStreet, BillingCity, BillingState, BillingPostalCode, BillingCountry, Region__c FROM Account WHERE Id = :id];
818 if (a.size() >0) {
819 ret = new Resource(a[0]);
820 }
821 }
822 return ret;
823 }
824
825 // @RemoteAction
826 // public static Map<String, List<Patient>> getClosestPatients(Map<Id, Appointment> previous, Map<Id, Appointment> next, Appointment slot) {
827 // // Define the static part of the query.
828 // String query = 'SELECT Id, Name, Insurance_Plan_Name__c, Color__c, Color_Dark__c, Lead_Suborigin__c,Pro_TOC_latest_status__c, Pro_TOC_latest_substatus__c, Language__c, BillingStreet, BillingCity, BillingState, BillingPostalCode, BillingCountry, Subzone__c, Status__c, Region__c, Patient_Unique_ID__c, (SELECT Id FROM Contacts), RecordType.Name '
829 // + 'FROM Account WHERE RecordType.Name = \'' + Constants.PATIENT_TYPE + '\' '
830 // + 'AND Status__c = \'' + Constants.STATUS_OPEN_LEAD + '\' '
831 // + 'AND Sub_Status__c = \'' + Constants.SUBSTATUS_LEAD_RECEIVED + '\' ';
832 // String dailyCensusCondition = 'AND Lead_Origin__c = \'' + Constants.LEAD_ORIGIN_DAILY + '\' ';
833 // String newlyCappedCondition = 'AND Lead_Origin__c = \'' + Constants.LEAD_ORIGIN_NEW + '\' ';
834 // String orderBy = 'ORDER BY ';
835 // String queryLimit = ' LIMIT 3';
836
837 // Map<Id, sked__Resource__c> resources = new Map<Id, sked__Resource__c>([SELECT Id, Name, sked__GeoLocation__c,
838 // sked__Resource__c.sked__GeoLocation__Latitude__s,
839 // sked__Resource__c.sked__GeoLocation__Longitude__s,
840 // RecordType.Name
841 // FROM sked__Resource__c
842 // WHERE Id IN :previous.keySet()]);
843 // // Get prev/next patient addresses
844 // List<Appointment> allAppts = new List<Appointment>(previous.values());
845 // allAppts.addAll(next.values());
846 // Set<Id> patientIds = new Set<Id>();
847 // for (Appointment appt : allAppts) {
848 // if (appt != null) {
849 // patientIds.add(appt.patient.Id);
850 // }
851 // }
852 // Map<Id, Account> apptPatients = new Map<Id, Account>([SELECT Id, BillingLatitude, BillingLongitude
853 // FROM Account
854 // WHERE Id IN : patientIds]);
855 // String[] orderConditions = new String[6];
856 // String[] cleanConditions = new List<String>();
857 // Appointment p, n;
858 // Integer index;
859 // String fromCoordinates, toCoordinates;
860 // sked__Resource__c res;
861 // Account patient;
862 // for (Resource r : slot.resources) {
863 // p = previous.get(r.Id);
864 // n = next.get(r.Id);
865 // index = r.type == Constants.MD_TYPE? 0 : r.type == Constants.NP_TYPE? 2 : 4;
866 // res = resources.get(r.Id);
867 // if (!r.remote) {
868 // if (p == null || p.resource.remote) {
869 // fromCoordinates = res.sked__GeoLocation__Latitude__s + ',' + res.sked__GeoLocation__Longitude__s;
870 // } else {
871 // patient = apptPatients.get(p.patient.Id);
872 // fromCoordinates = patient.BillingLatitude + ',' + patient.BillingLongitude;
873 // }
874 // if (n == null || n.resource.remote) {
875 // toCoordinates = res.sked__GeoLocation__Latitude__s + ',' + res.sked__GeoLocation__Longitude__s;
876 // } else {
877 // patient = apptPatients.get(n.patient.Id);
878 // toCoordinates = patient.BillingLatitude + ',' + patient.BillingLongitude;
879 // }
880 // orderConditions[index] = 'DISTANCE(BillingAddress, GEOLOCATION(' + fromCoordinates + '), \'mi\')';
881 // orderConditions[index + 1] = 'DISTANCE(BillingAddress, GEOLOCATION(' + toCoordinates + '), \'mi\')';
882 // }
883 // }
884 // for (String cond : orderConditions) {
885 // if (cond != null) {
886 // cleanConditions.add(cond);
887 // }
888 // }
889 // orderBy += String.join(cleanConditions, ', ');
890 // List<Account> closestDailyCensus = Database.query(query + dailyCensusCondition + orderBy + queryLimit);
891 // List<Account> closestNewlyCapped = Database.query(query + newlyCappedCondition + orderBy + queryLimit);
892 // Map<String, List<Patient>> result = new Map<String, List<Patient>>();
893 // List<Patient> dailyList = new List<Patient>();
894 // List<Patient> cappedList = new List<Patient>();
895 // for (Account a : closestDailyCensus) {
896 // dailyList.add(new Patient(a));
897 // }
898 // result.put(Constants.LEAD_ORIGIN_DAILY, dailyList);
899 // for (Account a : closestNewlyCapped) {
900 // cappedList.add(new Patient(a));
901 // }
902 // result.put(Constants.LEAD_ORIGIN_NEW, cappedList);
903 // return result;
904 // }
905
906 @Remoteaction
907 public static List<Resource> getAllResources() {
908 AppointmentsHelper appointmentsHelper = AppointmentsHelper.getInstance();
909 return appointmentsHelper.getAllResources();
910 }
911
912 @RemoteAction
913 public static List<Resource> getResourceByType(String type) {
914 List<Resource> ret = new List<Resource>();
915 Map<Id,sked__Resource__c> resources = new Map<Id, sked__Resource__c>([SELECT Id, Name,
916 TimeZone__c,
917 RecordTypeId,
918 Map_Color__c,
919 sked__User__c,
920 Rate_Factor__c,
921 RecordType.Name,
922 PrimaryMarket__c,
923 sked__Category__c,
924 sked__User__r.Name,
925 show_in_calendar__c,
926 sked__Home_Address__c,
927 sked__User__r.smallphotourl,
928 sked__User__r.TimeZoneSidKey,
929 sked__Is_Active__c
930 FROM sked__Resource__c
931 WHERE RecordType.Name = :type AND sked__Is_Active__c = true AND IsDeleted = false AND show_in_calendar__c = true]);
932
933 Map<Id, Map<String,List<sked__Availability_Template_Entry__c>>> entriesByResource = getEntriesByResource(resources.keySet());
934
935 for(Id rid : resources.keySet()) {
936 sked__Resource__c resource = resources.get(rid);
937 Resource re = new Resource(resource, false);
938 re.entries = entriesByResource.containsKey(resource.Id) ? entriesByResource.get(resource.Id) : null;
939 ret.add(re);
940 }
941 return ret;
942 }
943
944 @RemoteAction
945 public static List<Resource> getResource(String name, String searchType) {
946 List<Resource> ret = new List<Resource>();
947 List<sked__Resource__c> resources = new List<sked__Resource__c>();
948 if (searchType.equals('name')) {
949 resources = [select Id, Name, sked__Category__c, sked__User__c, sked__User__r.smallphotourl, TimeZone__c, sked__Home_Address__c, PrimaryMarket__c, sked__Is_Active__c,
950 Rate_Factor__c, sked__User__r.TimeZoneSidKey, sked__User__r.Name, RecordTypeId, RecordType.Name, show_in_calendar__c, Map_Color__c
951 FROM sked__Resource__c WHERE Name LIKE :('%' + name + '%') AND sked__Is_Active__c = true AND IsDeleted = false AND show_in_calendar__c = true];
952 } else if (searchType.equals('market')) {
953 List<ResourceMarket__c> rMarkets = [SELECT Resource__c FROM ResourceMarket__c WHERE Market__c = :name];
954 Set<Id> resourceIds = new Set<Id>();
955 for (ResourceMarket__c rm : rMarkets) {
956 resourceIds.add(rm.Resource__c);
957 }
958 resources = [select Id, Name, sked__Category__c ,sked__User__c, sked__User__r.smallphotourl, TimeZone__c, sked__Home_Address__c, PrimaryMarket__c,
959 Rate_Factor__c, sked__User__r.TimeZoneSidKey, sked__User__r.Name, RecordTypeId, RecordType.Name, show_in_calendar__c, Map_Color__c, sked__Is_Active__c
960 FROM sked__Resource__c
961 WHERE (PrimaryMarket__c = :name OR Id in :resourceIds)
962 AND sked__Is_Active__c = true
963 AND IsDeleted = false];
964 }
965 for (sked__Resource__c resource : resources) {
966 Resource re = new Resource(resource, false);
967 ret.add(re);
968 }
969 return ret;
970 }
971
972 public static Map<Id, Map<String,List<sked__Availability_Template_Entry__c>>> getAvailabilityByResource(Set<Id> resources, Boolean sortByTimeAsc) {
973 Set<Id> availailityTemplateIds = new Set<Id>();
974 Map<Id, Map<String,List<sked__Availability_Template_Entry__c>>> mapEntriesByResource = new Map<Id, Map<String,List<sked__Availability_Template_Entry__c>>>();
975
976 // Create Map to store the resource's Id for each template resorce's Id: (idResource, Set<idTemplateResource> )
977 Map<Id, Set<Id>> mapResourceTemplate = new Map<Id, Set<Id>>();
978 String templatesFilter = '(';
979 for(sked__Availability_Template_Resource__c atr : [SELECT sked__Availability_Template__c, sked__Resource__c FROM sked__Availability_Template_Resource__c WHERE sked__Resource__c IN :resources]) {
980 if(!mapResourceTemplate.containsKey(atr.sked__Resource__c)) {
981 mapResourceTemplate.put(atr.sked__Resource__c, new Set<Id>());
982 }
983 mapResourceTemplate.get(atr.sked__Resource__c).add(atr.sked__Availability_Template__c);
984 availailityTemplateIds.add(atr.sked__Availability_Template__c);
985 templatesFilter += '\'' +atr.sked__Availability_Template__c + '\', ';
986 }
987 templatesFilter = templatesFilter.removeEnd(', ') + ')';
988 if(templatesFilter!='()'){
989 // Create Map<Id templates, Lis<entries>>
990 Map <Id, List<sked__Availability_Template_Entry__c>> entriesByTemplates = new Map <Id, List<sked__Availability_Template_Entry__c>>();
991 String query = 'SELECT sked__Weekday__c, sked__Start_Time__c, sked__Finish_Time__c, sked__Availability_Template__c, sked__Is_Available__c, desired__c, Note__c FROM sked__Availability_Template_Entry__c WHERE sked__Availability_Template__r.Id IN '+templatesFilter;
992 if(sortByTimeAsc){
993 query += ' ORDER BY sked__Start_Time__c ASC';
994 }
995 for(sked__Availability_Template_Entry__c entrie : Database.query(query)) {
996 if(!entriesByTemplates.containsKey(entrie.sked__Availability_Template__c)) {
997 entriesByTemplates.put(entrie.sked__Availability_Template__c, new List<sked__Availability_Template_Entry__c>());
998 }
999 entriesByTemplates.get(entrie.sked__Availability_Template__c).add(entrie);
1000 }
1001
1002 for( Id idR : mapResourceTemplate.keySet()) {
1003 for(Id idT : mapResourceTemplate.get(idR)) {
1004 if(entriesByTemplates.containsKey(idT)) {
1005 for(sked__Availability_Template_Entry__c entrie : entriesByTemplates.get(idT)) {
1006 if(!mapEntriesByResource.containsKey(idR)) {
1007 mapEntriesByResource.put(idR, new Map<String,List<sked__Availability_Template_Entry__c>>());
1008 }
1009 String weekday = entrie.sked__Weekday__c;
1010 if(!mapEntriesByResource.get(idR).containsKey(weekday)) {
1011 mapEntriesByResource.get(idR).put(weekday, new List<sked__Availability_Template_Entry__c>());
1012 }
1013 mapEntriesByResource.get(idR).get(weekday).add(entrie);
1014 }
1015 }
1016 }
1017 }
1018 }
1019
1020 return mapEntriesByResource;
1021 }
1022
1023 public static Map<Id, Map<String,List<sked__Availability_Template_Entry__c>>> getEntriesByResource(Set<Id> resources) {
1024 return getAvailabilityByResource(resources, false);
1025 }
1026
1027
1028 /*******************************************************************************************************
1029 * @description Get available work time periods for specified resources
1030 * @paramresources Map<Id, String> Map of resource Id, and Timezone__c id
1031 * @param respectDesired Boolean Respect 'desired' time blocks
1032 * @param startDate Date
1033 * @param endDate Date
1034 * @return Map<Id, List<Map<String, Datetime>>> Map of resourceIds, abd a list of maps which contain start and end times TODO: Refactor into strong typed class
1035 */
1036 public static Map<Id, List<Map<String, Datetime>>> getActualWorkingTime(Map<Id, String> resources, Boolean respectDesired, Datetime startDate, Datetime endDate) {
1037 Map<Id, Map<String,List<sked__Availability_Template_Entry__c>>> entriesByResource = getAvailabilityByResource(resources.keySet(), true);
1038 List<sked__Availability_Template_Entry__c> availabilityEntries;
1039 sked__Availability_Template_Entry__c actual;
1040 Iterator<sked__Availability_Template_Entry__c> ei;
1041 Map<Id, Map<String,List<sked__Availability_Template_Entry__c>>> availability = new Map<Id, Map<String,List<sked__Availability_Template_Entry__c>>>();
1042 Map<Id, Map<String,List<sked__Availability_Template_Entry__c>>> unavailability = new Map<Id, Map<String,List<sked__Availability_Template_Entry__c>>>();
1043
1044 if(!entriesByResource.isEmpty()){
1045 for (Id resource: resources.keySet()){
1046 availability.put(resource, new Map<String,List<sked__Availability_Template_Entry__c>>());
1047 unavailability.put(resource, new Map<String,List<sked__Availability_Template_Entry__c>>());
1048 for(String weekday: Constants.SKEDULO_WEEKDAYS){
1049 availability.get(resource).put(weekday,new List<sked__Availability_Template_Entry__c>());
1050 unavailability.get(resource).put(weekday,new List<sked__Availability_Template_Entry__c>());
1051 }
1052 if(entriesByResource.get(resource)!=null && !entriesByResource.get(resource).isEmpty()){
1053 for(String weekday: Constants.SKEDULO_WEEKDAYS){
1054 if(entriesByResource.get(resource).keySet().contains(weekday)){
1055 for(sked__Availability_Template_Entry__c e: entriesByResource.get(resource).get(weekday)){
1056 if(e.sked__Is_Available__c){
1057 availability.get(resource).get(weekday).add(e);
1058 } else{
1059 if((e.desired__c!=null && e.desired__c == true) && respectDesired==true){
1060 unavailability.get(resource).get(weekday).add(e);
1061 }
1062 }
1063 }
1064 }
1065 }
1066 }
1067 }
1068 }
1069 return calculateTrueAvailability(availability, unavailability, startDate, endDate, resources);
1070 }
1071
1072 public static Map<Id, List<Map<String, Datetime>>> calculateTrueAvailability(
1073 Map<Id, Map<String,List<sked__Availability_Template_Entry__c>>> availabilityByResource,
1074 Map<Id, Map<String,List<sked__Availability_Template_Entry__c>>> unavailabilityByResource,
1075 Datetime startDate,
1076 Datetime endDate,
1077 Map<Id, String> resourcesTz) {
1078 Integer av_index = 0, uav_index = 0;
1079 Decimal newEntryStartTime, newEntryFinishTime, currentEntryStartTime, currentEntryFinishTime;
1080 sked__Availability_Template_Entry__c av_current_entry, uav_current_entry;
1081 List<sked__Availability_Template_Entry__c> availability, unavailability;
1082 Map<Id, Map<String,List<sked__Availability_Template_Entry__c>>> actualAvailabilityMap = new Map<Id, Map<String,List<sked__Availability_Template_Entry__c>>> ();
1083 Map<Id, List<Map<String, Datetime>>> actualAvailabilityLists = new Map<Id, List<Map<String, Datetime>>>();
1084
1085 for (Id resource: availabilityByResource.keySet()) {
1086 actualAvailabilityMap.put(resource, new Map<String,List<sked__Availability_Template_Entry__c>>());
1087 for (String weekday: availabilityByResource.get(resource).keySet()) {
1088 actualAvailabilityMap.get(resource).put(weekday, new List<sked__Availability_Template_Entry__c>());
1089 availability = availabilityByResource.get(resource).get(weekday);
1090 unavailability = unavailabilityByResource.get(resource).get(weekday);
1091
1092 if (availability!=null && availability.size()>0) {
1093 av_index = 0;
1094 uav_index = 0;
1095 av_current_entry = null;
1096 uav_current_entry = null;
1097 if (unavailability!=null && unavailability.size()>0) {
1098 currentEntryStartTime = availability[av_index].sked__Start_Time__c;
1099 while (av_index< availability.size()){
1100 if (uav_index<unavailability.size()){
1101 if (availability[av_index].sked__Finish_Time__c <= unavailability[uav_index].sked__Start_Time__c) {
1102 newEntryStartTime = currentEntryStartTime;
1103 newEntryFinishTime = availability[uav_index].sked__Finish_Time__c;
1104 actualAvailabilityMap.get(resource).get(weekday).add(
1105 new sked__Availability_Template_Entry__c(
1106 Note__c = 'WorkingTime',
1107 sked__Is_Available__c = true,
1108 sked__Weekday__c = weekday,
1109 sked__Start_Time__c = newEntryStartTime,
1110 sked__Finish_Time__c = newEntryFinishTime,
1111 sked__Availability_Template__c = null
1112 )
1113 );
1114 av_index++;
1115 } else {
1116 actualAvailabilityMap.get(resource).get(weekday).add(
1117 new sked__Availability_Template_Entry__c(
1118 Note__c = 'WorkingTime',
1119 sked__Is_Available__c = true,
1120 sked__Weekday__c = weekday,
1121 sked__Start_Time__c = currentEntryStartTime,
1122 sked__Finish_Time__c = unavailability[uav_index].sked__Start_Time__c,
1123 sked__Availability_Template__c = null
1124 )
1125 );
1126 currentEntryStartTime = unavailability[uav_index].sked__Finish_Time__c;
1127 uav_index++;
1128 }
1129 } else {
1130 if (av_index< availability.size()) {
1131 actualAvailabilityMap.get(resource).get(weekday).add(new sked__Availability_Template_Entry__c(
1132 Note__c = 'WorkingTime',
1133 sked__Is_Available__c = true,
1134 sked__Weekday__c = weekday,
1135 sked__Start_Time__c = currentEntryStartTime,
1136 sked__Finish_Time__c = availability[av_index].sked__Finish_Time__c,
1137 sked__Availability_Template__c = null
1138 ));
1139 av_index++;
1140 }
1141 }
1142 }
1143
1144 } else {
1145 actualAvailabilityMap.get(resource).get(weekday).addAll(availability);
1146 }
1147 }
1148 }
1149 actualAvailabilityLists.put(resource, availability_to_gmt(actualAvailabilityMap.get(resource), startDate, endDate, resourcesTz.get(resource)));
1150 }
1151 return actualAvailabilityLists;
1152 }
1153
1154 public static Datetime createDatetimeFromDecimal(Date d, Decimal hm, String tz) {
1155 Datetime returnValue = Datetime.newInstance(d.year(), d.month(), d.day(), (Integer)Math.Floor(hm/100),
1156 (Integer)Math.Floor(hm-Math.Floor(hm/100)*100), 0);
1157 return returnValue;
1158 }
1159
1160 public static List<Map<String, Datetime>> availability_to_gmt(Map<String,List<sked__Availability_Template_Entry__c>> availability_naive, Datetime startDate, Datetime endDate, String tz) {
1161 Datetime startDateLocal = startDate;
1162 Datetime endDateLocal = endDate;
1163 String weekday;
1164 Date day;
1165 Integer days = startDateLocal.date().daysBetween(endDateLocal.date());
1166 List<Datetime> startsAndEnds = new List<Datetime>();
1167 for (Integer d=0; d<=days; d++) {
1168 day = startDate.addDays(d).date();
1169 weekday = startDateLocal.addDays(d).format('E').toUppercase();
1170 for(sked__Availability_Template_Entry__c e: availability_naive.get(weekday)){
1171 startsAndEnds.add(createDatetimeFromDecimal(day, e.sked__Start_Time__c, tz));
1172 startsAndEnds.add(createDatetimeFromDecimal(day, e.sked__Finish_Time__c, tz));
1173 }
1174 }
1175 startsAndEnds.sort();
1176 List<Map<String, Datetime>> availabilityList = new List<Map<String, Datetime>>();
1177 for (Integer i=0; i<startsAndEnds.size(); i=i+2) {
1178 availabilityList.add(new Map<String, Datetime>{
1179 'start' => startsAndEnds[i],
1180 'end' => startsAndEnds[i+1]
1181 });
1182 }
1183 return availabilityList;
1184 }
1185
1186 static List<TimeSlot> processWorkDays(List<Map<String, Datetime>> availability, List<Appointment> unavailability, Id res, Integer allocatedTime, DateTime endDate, String timezone) {
1187 Integer av_index = 0,
1188 uav_index = 0;
1189 DateTime newEntryStartTime,
1190 newEntryFinishTime,
1191 currentEntryStartTime,
1192 currentEntryFinishTime;
1193 List<TimeSlot> weekdayEntries = new List<TimeSlot>();
1194 av_index = 0;
1195 uav_index = 0;
1196 if(availability!=null && !availability.isEmpty()){
1197 currentEntryStartTime = availability[av_index].get('start');
1198 if(availability[availability.size()-1].get('end') > Utils.convertTzToGmt(endDate,timezone)){
1199 availability[availability.size()-1].put('end', Utils.convertTzToGmt(endDate,timezone));
1200 }
1201 if(unavailability!=null&& unavailability.size()>0){
1202 currentEntryStartTime = availability[av_index].get('start');
1203 while(av_index< availability.size() && uav_index<unavailability.size() ){
1204 if(currentEntryStartTime <= availability[av_index].get('start')){
1205 currentEntryStartTime = availability[av_index].get('start');
1206 }
1207 if(availability[av_index].get('end')<= unavailability[uav_index].startTimeAsDate){ //if A entirely before UAV
1208 newEntryStartTime = currentEntryStartTime;
1209 newEntryFinishTime = availability[av_index].get('end');
1210 weekdayEntries.add(
1211 new TimeSlot (
1212 newEntryStartTime,
1213 newEntryFinishTime,
1214 res,
1215 allocatedTime
1216 )
1217 );
1218 av_index++;
1219 currentEntryStartTime = newEntryFinishTime;
1220 } else if(currentEntryStartTime >= unavailability[uav_index].startTimeAsDate ||
1221 availability[av_index].get('end') <= unavailability[uav_index].endTimeAsDate)
1222 { //if A part inside uav(starts or ends in uav)
1223 if(currentEntryStartTime >= unavailability[uav_index].startTimeAsDate){ //if A starts inside uav
1224 if(availability[av_index].get('start') <= unavailability[uav_index].endTimeAsDate){ //if A ends inside UAV
1225 currentEntryStartTime = unavailability[uav_index].endTimeAsDate;
1226 av_index++;
1227 } else { //if A continues after UAV
1228 currentEntryStartTime = unavailability[uav_index].endTimeAsDate;
1229 uav_index++;
1230 }
1231 } else {
1232 currentEntryStartTime = availability[av_index].get('end');
1233 av_index++;
1234 }
1235 } else if (currentEntryStartTime < unavailability[uav_index].startTimeAsDate){ //if A starts before uav's start if(currentEntryStartTime < unavailability[uav_index].startTimeAsDate)
1236 newEntryStartTime = currentEntryStartTime;
1237 newEntryFinishTime = unavailability[uav_index].startTimeAsDate;
1238 weekdayEntries.add(
1239 new TimeSlot (
1240 newEntryStartTime,
1241 newEntryFinishTime,
1242 res,
1243 allocatedTime
1244 )
1245 );
1246 currentEntryStartTime = unavailability[uav_index].endTimeAsDate;
1247 uav_index++;
1248 } else { //if A start after UAV
1249 uav_index++;
1250 }
1251 }
1252
1253 while(av_index< availability.size()) //if extra availability exists add it all
1254 {
1255 newEntryStartTime = currentEntryStartTime;
1256 newEntryFinishTime = availability[av_index].get('end');
1257 weekdayEntries.add(
1258 new TimeSlot (
1259 newEntryStartTime,
1260 newEntryFinishTime,
1261 res,
1262 allocatedTime
1263 )
1264 );
1265 if(av_index+1< availability.size()){
1266 currentEntryStartTime = availability[av_index+1].get('start');
1267 }
1268 av_index++;
1269 }
1270
1271 } else { //if only have availability
1272 while(av_index< availability.size())
1273 {
1274 newEntryStartTime = availability[av_index].get('start');
1275 newEntryFinishTime = availability[av_index].get('end');
1276 weekdayEntries.add(
1277 new TimeSlot (
1278 newEntryStartTime,
1279 newEntryFinishTime,
1280 res,
1281 allocatedTime
1282 )
1283 );
1284 av_index++;
1285 }
1286 }
1287 }
1288 weekdayEntries.sort();
1289 return weekdayEntries;
1290
1291 }
1292
1293 public class TimeSlot implements Comparable {
1294 public Datetime startTime {get;set;}
1295 public Datetime endTime {get;set;}
1296 public Integer availableMinutes {get;set;}
1297 public Id resource {get;set;}
1298 public Integer availableSpaces {get;set;}
1299
1300 TimeSlot(Datetime startDateTime, Datetime endDatetime, Id res, Integer slotTimePeriod){
1301 startTime = startDateTime;
1302 endTime = endDatetime;
1303 resource = res;
1304 availableSpaces = (Integer) Math.floor((endDatetime.getTime() - startDateTime.getTime())/(1000*60)/slotTimePeriod);
1305 }
1306 public Integer compareTo(Object t) {
1307 TimeSlot compareToSlot = (TimeSlot)t;
1308 // The return value of 0 indicates that both elements are equal.
1309 Integer returnValue = 0;
1310 if (this.startTime > compareToSlot.startTime) {
1311 // Set return value to a positive value.
1312 returnValue = 1;
1313 } else if (this.startTime < compareToSlot.startTime) {
1314 // Set return value to a negative value.
1315 returnValue = -1;
1316 }
1317
1318 return returnValue;
1319 }
1320 }
1321
1322 /*******************************************************************************************************
1323 * @description Groups an appointment list by resource, relative order can be altered
1324 * @param appointments List<Appointment>
1325 * @param resources List<Id>
1326 * @return Map<Id, List<Appointment>> Map of resourceIds and appointments is sorted (by appointment startDate)
1327 */
1328 public static Map<Id, List<Appointment>> classifyAppointmentsPerResource(List<Appointment> appointments, List<Id> resources) {
1329 Map<Id, List<Appointment>> clasified = new Map<Id, List<Appointment>>();
1330 List<Appointments> apps;
1331 for(Id r: resources){
1332 clasified.put(r, new List<Appointment>());
1333 }
1334
1335 for(Appointment a: appointments){
1336 clasified.get(a.resource.id).add(a);
1337 }
1338
1339 for(Id rid: clasified.keySet()){
1340 clasified.get(rid).sort();
1341 }
1342 return clasified;
1343 }
1344
1345 /*******************************************************************************************************
1346 * @description Returns available interval for appointments
1347 * @param startDate Datetime
1348 * @param endDate Datetime
1349 * @param resources Map<Id, String> Map of resource Id, and Timezone__c id
1350 * @param respectDesired Boolean
1351 * @param allocatedTime Integer
1352 */
1353 public static List<TimeSlot> getAvailableResourceInterval(DateTime startDate, DateTime endDate, Map<Id, String> resources, Boolean respectDesired, Integer allocatedTime) {
1354 String weekday;
1355 Date dat;
1356 List<TimeSlot> availableSlotsPerResource = new List<TimeSlot>();
1357 List<Id> resourcesList = new List<ID>(resources.keySet());
1358 Appointment[] appts = getSFAppointments(startDate, endDate, resourcesList);
1359 appts.addAll(getResourceUnAvailability(startDate,endDate, resourcesList));
1360 appts.addAll(getResourceActivity(startDate,endDate, resourcesList));
1361 appts.sort();
1362 Map<Id, List<Appointment>> appointments = classifyAppointmentsPerResource(appts, resourcesList);
1363 Map<Id, List<Map<String, Datetime>>> availability_by_resource = getActualWorkingTime(resources, respectDesired, startDate, endDate);
1364 for(Id r: resources.keySet()){
1365 // Integer days = startDate.dateGMT().daysBetween(endDate.dateGMT());
1366 // Datetime day = startDate;
1367 if(appointments.containsKey(r) && appointments.get(r)!=null && !appointments.get(r).isEmpty())
1368 {
1369 availableSlotsPerResource.addAll(processWorkDays(availability_by_resource.get(r), appointments.get(r), r, allocatedTime, endDate, resources.get(r)));
1370 } else {
1371 availableSlotsPerResource.addAll(processWorkDays(availability_by_resource.get(r), null , r, allocatedTime, endDate, resources.get(r)));
1372 }
1373 }
1374 availableSlotsPerResource.sort();
1375 return availableSlotsPerResource;
1376 }
1377
1378 public static Map<Id, Map<string,String>> getMarketColorsByLVNResource(Set<Id> resources) {
1379 Map<Id, Map<string,String>> result = new Map<Id, Map<string,String>>();
1380 for (ResourceMarket__c rm :[SELECT Id, Resource__c, Market__c, Map_Color__c FROM ResourceMarket__c WHERE Resource__c IN :resources AND Resource__r.RecordType.Name = :Constants.LVN_TYPE]) {
1381 if (!result.containsKey(rm.Resource__c)) {
1382 result.put(rm.Resource__c, new Map<String,String>());
1383 }
1384 result.get(rm.Resource__c).put(rm.Market__c,rm.Map_Color__c);
1385 }
1386 return result;
1387 }
1388
1389 @RemoteAction
1390 public static Appointment editAppointment(Appointment app) {
1391 Map<Id, sked__Job__c> jobs = new Map<Id, sked__Job__c>(
1392 [SELECT Id,
1393 Name,
1394 Category__c,
1395 Flexible__c,
1396 TimeAdded__c,
1397 CCP_Parent__c,
1398 sked__Start__c,
1399 Interpreter__c,
1400 sked__Parent__c,
1401 sked__Finish__c,
1402 sked__Urgency__c,
1403 Post_Discharge__c,
1404 sked__Duration__c,
1405 Updated_Address__c,
1406 Phone_Visit_Type__c,
1407 sked__Job_Status__c,
1408 sked__Notes_Comments__c,
1409 Job_confirmation_status__c,
1410 sked__Region__r.sked__TimeZone__c,
1411 sked__Account__r.Parent.Name,
1412 sked__Account__r.Parent.SNF_Enabled__c,
1413 sked__Account__r.ParentId,
1414 sked__Account__r.Hospital__c,
1415 sked__Account__r.In_SNF__c,
1416 sked__Account__r.Snf__c,
1417 sked__Account__r.SNF__r.BillingStreet,
1418 sked__Account__r.SNF__r.BillingCity,
1419 sked__Account__r.SNF__r.BillingCountry,
1420 sked__Account__r.SNF__r.BillingState,
1421 sked__Account__r.SNF__r.BillingPostalCode,
1422 sked__Account__r.SNF__r.Subzone__c,
1423 Snf__c,
1424 (SELECT Id,
1425 Remote__c,
1426 Distance_To__c,
1427 sked__Status__c,
1428 Distance_From__c,
1429 EHR_Appointment_ID__c,
1430 patient_primary_CCP_Id__c,
1431 sked__Estimated_Travel_Time__c,
1432 sked__Resource__r.Id,
1433 sked__Resource__r.Name,
1434 sked__Resource__r.TimeZone__c,
1435 sked__Resource__r.Doctor_ID__c,
1436 sked__Resource__r.Map_Color__c,
1437 sked__Resource__r.Rate_Factor__c,
1438 sked__Resource__r.RecordType.Name,
1439 sked__Resource__r.sked__Is_Active__c,
1440 sked__Resource__r.sked__Mobile_Phone__c,
1441 sked__Resource__r.EHR__r.Id,
1442 sked__Resource__r.EHR__r.Name,
1443 sked__Resource__r.EHR__r.Token__c,
1444 sked__Resource__r.EHR__r.CallBack__c,
1445 sked__Resource__r.EHR__r.Exam_Room__c,
1446 sked__Resource__r.EHR__r.Client_Id__c,
1447 sked__Resource__r.EHR__r.EHR_System__c,
1448 sked__Resource__r.EHR__r.Expiration__c,
1449 sked__Resource__r.EHR__r.Refresh_Token__c,
1450 sked__Resource__r.EHR__r.Client_Secret__c,
1451 sked__Resource__r.EHR__r.Token_Endpoint__c,
1452 sked__Resource__r.EHR__r.Token_Last_Reset__c,
1453 sked__Resource__r.EHR__r.Offices_Endpoint__c,
1454 sked__Resource__r.EHR__r.Doctors_Endpoint__c,
1455 sked__Resource__r.EHR__r.Appointment_Endpoint__c,
1456 sked__Resource__r.Doctor_s_Practice_Account__r.EHR_Office_Id__c
1457 FROM sked__Job_Allocations__r
1458 ORDER BY sked__Job__r.sked__Start__c)
1459 FROM sked__Job__c
1460 WHERE Id =: app.jobID OR sked__Parent__c =: app.jobID
1461 ORDER BY sked__Start__c]);
1462
1463 Datetime oldStar = jobs.get(app.jobID).sked__Start__c;
1464 Datetime newStar = DateTime.valueOfGmt(app.startTime);
1465
1466 Map<Id, String> mapJobOldTime = new Map<Id, String>();
1467 for(Id jId : jobs.keySet()) {
1468 Datetime dt = jobs.get(jId).sked__Start__c;
1469 String appTold = dt.format('h:mm a',jobs.get(app.jobID).sked__Region__r.sked__TimeZone__c);
1470 mapJobOldTime.put(jId, appTold);
1471 }
1472
1473 List<Resource> resources = app.allResources.isEmpty() ? app.resources : app.allResources;
1474 Set<String> resourcesName = new Set<String>();
1475 for (Integer i = 0; i < resources.size(); i++) {
1476 resourcesName.add(resources.get(i).contact.Name);
1477 }
1478 Id idJobChangeCCP_parent = null;
1479
1480 List<sked__Job_Allocation__c> allocations = new List<sked__Job_Allocation__c>();
1481
1482 // process parent appointment
1483 allocations.addAll(processAppointmentForEdit(app, jobs.get(app.jobID)));
1484
1485 // process children
1486 if (!app.children.isEmpty()) {
1487 for (Appointment child : app.children) {
1488 allocations.addAll(processAppointmentForEdit(child, jobs.get(child.jobID)));
1489
1490 if (jobs.get(child.jobID).CCP_Parent__c != null
1491 && !resourcesName.contains(jobs.get(child.jobID).CCP_Parent__c)) {
1492 idJobChangeCCP_parent = child.jobID;
1493 }
1494
1495 jobs.get(child.jobID).Job_confirmation_status__c = app.confirmationStatus;
1496 }
1497 } else {
1498 if (jobs.get(app.jobID).CCP_Parent__c != null
1499 && !resourcesName.contains(jobs.get(app.jobID).CCP_Parent__c)) {
1500 idJobChangeCCP_parent = app.jobID;
1501 }
1502 }
1503
1504 if (idJobChangeCCP_parent != null) {
1505 for (Integer i = 0; i < resources.size(); i++) {
1506 if (Constants.CCP_TYPES.contains(resources.get(i).type)) {
1507 jobs.get(idJobChangeCCP_parent).CCP_Parent__c = resources.get(i).contact.Name;
1508 break;
1509 }
1510 }
1511 }
1512
1513 for(Id jId : jobs.keySet()) {
1514 Datetime dt = jobs.get(jId).sked__Start__c;
1515 String appTime = dt.format('h:mm a',jobs.get(app.jobID).sked__Region__r.sked__TimeZone__c);
1516 String appDate = dt.format('EEE, d-MMM-yyyy');
1517 String appTold = mapJobOldTime.get(jId);
1518 //ufi
1519 jobs.get(jId).sked__Urgency__c = app.urgent == null || !app.urgent ? Constants.URGENCY_NORMAL : Constants.URGENCY_URGENT;
1520 jobs.get(jId).Flexible__c = app.flexible == null ? false : app.flexible;
1521 jobs.get(jId).Interpreter__c = app.interpreter == null ? false : app.interpreter;
1522 jobs.get(jId).TimeAdded__c = app.timeadded == null ? false : app.timeadded;
1523 jobs.get(jId).Job_confirmation_status__c = app.confirmationStatus;
1524 jobs.get(jId).Post_Discharge__c = app.postDischarge;
1525 jobs.get(jId).Snf__c = app.snf;
1526 jobs.get(jId).Eligible_value__c = app.jobEligibility;
1527 if(app.reschedulingReason != null) {
1528 jobs.get(jId).Rescheduling_Reason__c = app.reschedulingReason;
1529 }
1530 jobs.get(jId).Phone_Visit_Type__c = app.phoneVisitType;
1531 List<sked__Job_Allocation__c> aList = jobs.get(jId).sked__Job_Allocations__r;
1532 String appointmentAddress = app.address == null ? app.patient.address : app.address; //just in case old jobs have no address
1533
1534 for(sked__Job_Allocation__c a :aList) {
1535 String phone = a.sked__Resource__r.sked__Mobile_Phone__c;
1536 ResourceNotification re = new ResourceNotification(appTime, appDate, appTold, phone);
1537 if(oldStar.date().isSameDay(Date.today()) && newStar.date() > Date.today()) {
1538
1539 //Notify all CareProvider that the appointment scheduled for today has been rescheduled
1540 notificationsResourceUp(re,'upToday', app.patient.Name, appointmentAddress);
1541
1542 } else if(newStar.date().isSameDay(Date.today()) && newStar.date() < oldStar.date()) {
1543
1544 //Notify all CareProvider that the appointment scheduled for other day has been rescheduled for today
1545 notificationsResourceUp(re,'inToday', app.patient.Name, appointmentAddress);
1546
1547 } else if(newStar.date().isSameDay(Date.today()) && oldStar.date().isSameDay(Date.today())
1548 && newStar.timeGmt() != oldStar.time()) {
1549
1550 //Notify all CareProvider that the appointment scheduled for today has been rescheduled for other time of today
1551 notificationsResourceUp(re,'upTodayTime', app.patient.Name, appointmentAddress);
1552 }
1553 }
1554 }
1555
1556 upsert allocations;
1557 update jobs.values();
1558
1559 /*if(newStar.date().isSameDay(Date.today()) && newStar.date() < oldStar.date()) {
1560 sendNotificationPatient(app);
1561 }*/
1562
1563 processPatientPrimaryResource(app, Constants.DML_UPDATE);
1564 dispatchAsync(app.jobID);
1565
1566 return app;
1567 }
1568
1569 @RemoteAction
1570 public static Appointment updateEstimatedTimeOfAnAppointment(Appointment app) {
1571 sked__Job__c job = [SELECT Id, Updated_Address__c,
1572 (SELECT Id, sked__Estimated_Travel_Time__c, Distance_From__c, Distance_To__c FROM sked__Job_Allocations__r)
1573 FROM sked__Job__c WHERE Id =: app.jobID LIMIT 1 FOR UPDATE];
1574 if (job != null) {
1575 job.Updated_Address__c = false;
1576 update job;
1577
1578 if(!job.sked__Job_Allocations__r.isEmpty()) {
1579 List<sked__Job_Allocation__c> jobAs = job.sked__Job_Allocations__r;
1580 if (jobAs.size() > 0) {
1581 jobAs[0].sked__Estimated_Travel_Time__c = app.resource.drivingTimeFrom;
1582 jobAs[0].Distance_From__c = app.resource.distanceFrom;
1583 jobAs[0].Distance_To__c = app.resource.distanceTo;
1584 update jobAs;
1585 }
1586 }
1587 }
1588 app.updatedAddress = false;
1589 return app;
1590 }
1591
1592 private static List<sked__Job_Allocation__c> processAppointmentForEdit(Appointment appt, sked__Job__c job) {
1593 List<Resource> resources = appt.allResources.isEmpty() ? appt.resources : appt.allResources;
1594 String defaultProviderDoctorId;
1595 String doctorId;
1596 job.sked__Start__c = DateTime.valueOfGmt(appt.startTime);
1597 job.sked__Finish__c = DateTime.valueOfGmt(appt.endTime);
1598 job.sked__Job_Status__c = 'Pending Dispatch';
1599 //In the normal gui address cannot be changed unless snf is toggled
1600 job.Updated_Address__c = (appt.address != null && appt.address != job.sked__Address__c) || appt.patient.address != job.sked__Address__c;
1601 job.sked__Address__c = appt.address != null ? appt.address : appt.patient.address;
1602 DateTime start = job.sked__Start__c;
1603 Integer duration = (Integer)job.sked__Duration__c, examId;
1604 String resourceTimezone = null;
1605 //notes
1606 if (!String.isEmpty(appt.notes)){
1607 job.sked__Notes_Comments__c = appt.notes;
1608 }
1609 //ufi
1610 job.sked__Urgency__c = appt.urgent == null || !appt.urgent ? Constants.URGENCY_NORMAL : Constants.URGENCY_URGENT;
1611 job.Flexible__c = appt.flexible == null ? false : appt.flexible;
1612 job.Interpreter__c = appt.interpreter == null ? false : appt.interpreter;
1613 job.TimeAdded__c = appt.timeadded == null ? false : appt.timeadded;
1614 Job_Type__mdt type = [SELECT Id, Name__c,
1615 Force_EHR__c,
1616 EHR_Profile__c,
1617 EHR_profile_Medi_Cal_Urgent__c,
1618 EHR_profile_Medi_Cal_Post_Discharge__c,
1619 EHR_profile_Medicare_Advantage__c,
1620 EHR_profile_Medicare_Adv_Urgent__c,
1621 EHR_profile_Medicare_Adv_Post_Discharge__c,
1622 EHR_profile_CCHP__c,
1623 EHR_profile_CCHP_Urgent__c,
1624 EHR_profile_CCHP_Post_Discharge__c,
1625 EHR_profile_CCHP_SNF__c
1626 FROM Job_Type__mdt WHERE DeveloperName = :appt.type.code LIMIT 1];
1627
1628 Decimal ehrProfile = Utils.getEHRProfile(appt, type, job);
1629
1630 List<sked__Job_Allocation__c> allocationsToUpsert = new List<sked__Job_Allocation__c>();
1631 List<sked__Job_Allocation__c> allocationToDelete = new List<sked__Job_Allocation__c>();
1632 Integer count = 0;
1633 List<String> resourcesName = new List<String>();
1634 for (Integer i = 0; i < resources.size(); i++) {
1635 if (!resources.get(i).remote) { // If it is not remote(LVN, OR...)
1636 resourcesName.add(resources.get(i).contact.Name);
1637 }
1638 }
1639 String notes = AppointmentsHelper.getNotes(resourcesName, job.Category__c);
1640 Map<Id, Id> jobRes = new Map<Id, Id>();
1641 Set<Id> resId = new Set<Id>();
1642
1643 for (sked__Job_Allocation__c a : job.sked__Job_Allocations__r) {
1644 for (Integer i = 0; i < resources.size(); i++) {
1645 if (resources.get(i).Id == a.sked__Resource__r.Id) {
1646 a.sked__Estimated_Travel_Time__c = resources.get(i).drivingTimeFrom;
1647 a.Distance_From__c = resources.get(i).distanceFrom;
1648 a.Distance_To__c = resources.get(i).distanceTo;
1649 }
1650 if(resources.get(i).changed != null && a.sked__Resource__r.Id == resources.get(i).changed) {
1651 allocationToDelete.add(a);
1652 sked__Job_Allocation__c aNew = a.clone(false, false, false, false);
1653 aNew.sked__Resource__c = resources.get(i).Id;
1654 aNew.sked__Status__c = 'Pending Dispatch';
1655 allocationsToUpsert.add(aNew);
1656 jobRes.put(a.Id, resources.get(i).Id);
1657 resId.add(resources.get(i).Id);
1658 }
1659 resId.add(a.patient_primary_CCP_Id__c);
1660 }
1661 }
1662
1663 Map<Id, sked__Resource__c> resMap = new Map<Id, sked__Resource__c> ([SELECT Id,
1664 Name,
1665 EHR__r.Id,
1666 EHR__r.Name,
1667 Doctor_ID__c,
1668 EHR__r.Token__c,
1669 EHR__r.CallBack__c,
1670 EHR__r.Exam_Room__c,
1671 EHR__r.Client_Id__c,
1672 EHR__r.Expiration__c,
1673 EHR__r.EHR_System__c,
1674 EHR__r.Refresh_Token__c,
1675 EHR__r.Client_Secret__c,
1676 EHR__r.Token_Endpoint__c,
1677 EHR__r.Offices_Endpoint__c,
1678 EHR__r.Doctors_Endpoint__c,
1679 EHR__r.Token_Last_Reset__c,
1680 EHR__r.Appointment_Endpoint__c,
1681 Doctor_s_Practice_Account__r.EHR_Office_Id__c
1682 FROM sked__Resource__c
1683 WHERE Id IN :resId OR Name = :Constants.DEFAULT_PROVIDER]);
1684
1685 for(sked__Resource__c r: resMap.values()) {
1686 if(r.Name == Constants.DEFAULT_PROVIDER) {
1687 defaultProviderDoctorId = r.Doctor_ID__c;
1688 break;
1689 }
1690 }
1691
1692 Map<Id, QueuableSendAppointmentsToEHR.AppointmentBookingData> sendToEHR = new Map<Id, QueuableSendAppointmentsToEHR.AppointmentBookingData>();
1693 for (sked__Job_Allocation__c a : job.sked__Job_Allocations__r) {
1694 if (a.EHR_Appointment_ID__c != null) {
1695 examId = a.sked__Resource__r.EHR__r.Exam_Room__c == null ? Constants.DEFAULT_EXAM_ROOM : Integer.valueOf(a.sked__Resource__r.EHR__r.Exam_Room__c);
1696 sked__Resource__c aResource = a.sked__Resource__r;
1697 if (jobRes.containsKey(a.Id)) {
1698 aResource = resMap.get(jobRes.get(a.Id));
1699 }
1700
1701 if(type.Force_EHR__c) {
1702 doctorId = a.patient_primary_CCP_Id__c == null ? defaultProviderDoctorId : resMap.get(a.patient_primary_CCP_Id__c).Doctor_ID__c;
1703 } else {
1704 doctorId = aResource.Doctor_ID__c;
1705 }
1706 sendToEHR.put(a.Id, new QueuableSendAppointmentsToEHR.AppointmentBookingData(
1707 a.EHR_Appointment_ID__c,
1708 start,
1709 duration,
1710 Integer.valueOf(appt.patient.account.Patient_Unique_ID__c),
1711 examId,
1712 a.Id,
1713 aResource,
1714 ehrProfile,
1715 notes,
1716 doctorId
1717 ));
1718 if (resourceTimezone == null && a.sked__Resource__r.TimeZone__c != null && a.sked__Resource__r.TimeZone__c != '') {
1719 // @TODO in the future we may book resources in different zones. This will need to be enhanced
1720 resourceTimezone = a.sked__Resource__r.TimeZone__c;
1721 }
1722 }
1723 // reset status
1724 a.sked__Status__c = 'Pending Dispatch';
1725
1726 for (Integer i = 0; i < resources.size(); i++) {
1727 if (resources.get(i).Id == a.sked__Resource__r.Id) {
1728 a.sked__Estimated_Travel_Time__c = resources.get(i).drivingTimeFrom;
1729 a.Distance_From__c = resources.get(i).distanceFrom;
1730 a.Distance_To__c = resources.get(i).distanceTo;
1731 }
1732 }
1733 }
1734
1735 if(!sendToEHR.isEmpty()) {
1736 System.enqueueJob(new QueuableSendAppointmentsToEHR(
1737 sendToEHR
1738 ));
1739 }
1740
1741 // apply timezone offset
1742 if (resourceTimezone == null) { resourceTimezone = Constants.DEFAULT_TIME_ZONE; }
1743 job.sked__Duration__c = (Integer)(job.sked__Finish__c.getTime() - job.sked__Start__c.getTime())/60000;
1744 Integer startOffset = -getOffsetInMinutes(resourceTimezone, job.sked__Start__c);
1745 job.sked__Start__c = job.sked__Start__c.addMinutes(startOffset);
1746 Integer endOffset = -getOffsetInMinutes(resourceTimezone, job.sked__Finish__c);
1747 job.sked__Finish__c = job.sked__Finish__c.addMinutes(endOffset);
1748
1749 if(!allocationToDelete.isEmpty()){
1750 delete allocationToDelete;
1751 }
1752 return allocationsToUpsert.isEmpty() ? job.sked__Job_Allocations__r : allocationsToUpsert;
1753 }
1754
1755
1756 public static PatientEnrollment getNewAppointmentPatientEnrollment(Appointments.Appointment appointment, Map<String, PatientEnrollment> patientEnrollmentMap) {
1757 ProgramHelper programs = new ProgramHelper();
1758 PatientEnrollment patientEnrollment;
1759 List<String> programsSortedByPriority = new List<String> ();
1760 if(patientEnrollmentMap.containsKey(Constants.Program_PC)) {
1761 programsSortedByPriority.add(Constants.Program_PC);
1762 }
1763 if(patientEnrollmentMap.containsKey(Constants.Program_HC20)) {
1764 programsSortedByPriority.add(Constants.Program_HC20);
1765 }
1766 if (appointment.type != null && appointment.type.code != null) {
1767 for (String programName :patientEnrollmentMap.keySet()) {
1768 if(!programsSortedByPriority.contains(programName)) {
1769 programsSortedByPriority.add(programName);
1770 }
1771 }
1772 for (String pName :programsSortedByPriority) {
1773 if(programs.get(pName).hasJobType(appointment.type.code)) {
1774 patientEnrollment = patientEnrollmentMap.get(pName);
1775 break;
1776 }
1777 }
1778 }
1779 if (patientEnrollment == null) {
1780 throw new Utils.MedzedException('No program matches this appointment type.');
1781 }
1782 return patientEnrollment;
1783 }
1784
1785 /*
1786 All dates are assumed to be in GMT time zone.
1787 */
1788 @RemoteAction
1789 public static Appointment createAppointment(Appointment app) {
1790 Job_Type__mdt type;
1791 Set<String> setCodes_1A = new Set<String>();
1792 Map<Id, List<String>> jobsResourceMap = new Map<Id, List<String>>();
1793 for (Job_Type__mdt jt :[SELECT Id, Name__c,
1794 DeveloperName,
1795 Force_EHR__c,
1796 EHR_Profile__c,
1797 EHR_profile_Medi_Cal_Urgent__c,
1798 EHR_profile_Medi_Cal_Post_Discharge__c,
1799 EHR_profile_Medicare_Advantage__c,
1800 EHR_profile_Medicare_Adv_Urgent__c,
1801 EHR_profile_Medicare_Adv_Post_Discharge__c,
1802 EHR_profile_CCHP__c,
1803 EHR_profile_CCHP_SNF__c,
1804 EHR_profile_CCHP_Urgent__c,
1805 EHR_profile_CCHP_Post_Discharge__c,
1806 Category__r.DeveloperName
1807 FROM Job_Type__mdt]) {
1808 if (jt.Category__r.DeveloperName == Constants.CATEGORY_1A) {
1809 setCodes_1A.add(jt.DeveloperName);
1810 }
1811 if (jt.DeveloperName == app.type.code) {
1812 type = jt;
1813 }
1814 }
1815
1816 if (setCodes_1A.contains(app.type.code)) {
1817 Boolean hasOpen1A = [SELECT count()
1818 FROM sked__Job__c
1819 WHERE (sked__Account__c = :app.patient.Id OR sked__Account__c = :app.patient.account.Id)
1820 AND (sked__Job_Status__c != :Constants.JOB_STATUS_COMPLETE AND sked__Job_Status__c != :Constants.JOB_STATUS_CANCELLED)
1821 AND (Type_Code__c IN :setCodes_1A OR sked__Type__c IN :setCodes_1A)] > 0;
1822
1823 if (hasOpen1A) {
1824 throw new AppointmentException ('This patient already has a First Appointment scheduled, please select a different appointment type');
1825 }
1826 }
1827
1828 sked__Resource__c rObject;
1829 app.startTimeAsDate = DateTime.valueOfGmt(app.startTime);
1830 app.endTimeAsDate = DateTime.valueOfGmt(app.endTime);
1831
1832 // search for the cp & lvn
1833 Id cpId, ccpId;
1834 Boolean haveRemote = false;
1835 Set<Id> fieldsResources = new Set<Id>();
1836 Id[] resourceIds = new List<Id>();
1837 for (Resource r :app.resources) {
1838 if (r.remote) {
1839 cpId = r.Id;
1840 haveRemote = true;
1841 if (Constants.CCP_TYPES.contains(r.type)) {
1842 ccpId = r.Id;
1843 }
1844 } else {
1845 fieldsResources.add(r.Id);
1846 }
1847 resourceIds.add(r.Id);
1848 }
1849
1850 // if haven't remote resource and it has more than one resource then it's an Appt of type 2 FIELD
1851 if(!haveRemote && app.resources.size() > 1) {
1852 for (Resource r : app.resources) {
1853 if (Constants.CCP_TYPES.contains(r.type)) {
1854 ccpId = r.Id;
1855 break;
1856 }
1857 }
1858 }
1859
1860 Map<Id, sked__Resource__c> resources = new Map<Id, sked__Resource__c>([SELECT Id, Name,
1861 TimeZone__c,
1862 RecordTypeId,
1863 Doctor_ID__c,
1864 Rate_Factor__c,
1865 RecordType.Name,
1866 PrimaryMarket__c,
1867 EHR__r.Id,
1868 EHR__r.Name,
1869 EHR__r.Token__c,
1870 EHR__r.CallBack__c,
1871 EHR__r.Client_Id__c,
1872 EHR__r.Exam_Room__c,
1873 EHR__r.Expiration__c,
1874 EHR__r.EHR_System__c,
1875 EHR__r.Refresh_Token__c,
1876 EHR__r.Client_Secret__c,
1877 EHR__r.Token_Endpoint__c,
1878 EHR__r.Token_Last_Reset__c,
1879 EHR__r.Offices_Endpoint__c,
1880 EHR__r.Doctors_Endpoint__c,
1881 EHR__r.Appointment_Endpoint__c,
1882 Assigned_Patient_Advocate__r.Id,
1883 Assigned_Patient_Advocate__r.Name,
1884 Assigned_Patient_Advocate__r.Email,
1885 Doctor_s_Practice_Account__r.EHR_Office_Id__c
1886 FROM sked__Resource__c
1887 WHERE Id IN : resourceIds]);
1888 // get the cp's time zone
1889 String timeZoneId = cpId == null ? Constants.DEFAULT_TIME_ZONE :resources.get(cpId).TimeZone__c;
1890 Id patientProgram;
1891 PatientEnrollment currentEnrollment;
1892 if(!app.patient.enrolledPrograms.isEmpty()) {
1893 Map<String, PatientEnrollment> currentPatientEnrollmentsMap = new Map<String, PatientEnrollment>();
1894 for(PatientEnrollment enrollment: app.patient.enrolledPrograms) {
1895 currentPatientEnrollmentsMap.put(enrollment.programName, enrollment);
1896 }
1897 currentEnrollment = getNewAppointmentPatientEnrollment(app, currentPatientEnrollmentsMap);
1898 }
1899 // Get region
1900 sked__Region__c[] regions = [SELECT Id FROM sked__Region__c ORDER BY Id ASC LIMIT 1];
1901 sked__Region__c defaultRegion = regions.isEmpty() ? null : regions[0];
1902
1903 // get the cp's time zone offset in minutes
1904 Integer cpOffsetInMinutes = getOffsetInMinutes(timeZoneId, app.startTimeAsDate);
1905
1906 Map<DateTime, Map<DateTime, sked__Job__c>> jobsByTimeFrame = new Map<DateTime, Map<DateTime, sked__Job__c>>();
1907 Map<DateTime, sked__Job__c> jobsByEndTime;
1908 Map<DateTime, Map<DateTime, sked__Job_Allocation__c[]>> allocationsByTimeFrame = new Map<DateTime, Map<DateTime, sked__Job_Allocation__c[]>>();
1909 Map<DateTime, sked__Job_Allocation__c[]> allocationsByEndTime;
1910
1911 sked__Job__c firstJob, job;
1912 sked__Job_Allocation__c[] allocations;
1913 DateTime startTime, endTime;
1914 Integer duration, offsetInMinutes, examId;
1915 Map<Id, DateTime> starts = new Map<Id, DateTime>();
1916 Map<Id, Integer> durations = new Map<Id, Integer>();
1917 String jobAddress = app.address;
1918 for (Resource res : app.resources) {
1919 rObject = resources.get(res.Id);
1920 // Calculate times
1921 startTime = res.startTime == null ? app.startTimeAsDate : DateTime.valueOfGmt(res.startTime);
1922 endTime = res.endTime == null? app.endTimeAsDate : DateTime.valueOfGmt(res.endTime);
1923 // Timezone offset
1924 offsetInMinutes = getOffsetInMinutes(rObject.TimeZone__c, startTime) - cpOffsetInMinutes;
1925 startTime = startTime.addMinutes(offsetInMinutes);
1926 endTime = endTime.addMinutes(offsetInMinutes);
1927 // duration as end - start
1928 duration = (Integer)(endTime.getTime()-startTime.getTime())/60000;
1929 starts.put(rObject.Id, startTime);
1930 durations.put(rObject.Id, duration);
1931
1932 // Get map for jobs that start at this resource's start time
1933 if (!jobsByTimeFrame.containsKey(startTime)) {
1934 jobsByTimeFrame.put(startTime, new Map<DateTime, sked__Job__c>());
1935 allocationsByTimeFrame.put(startTime, new Map<DateTime, sked__Job_Allocation__c[]>());
1936 }
1937 jobsByEndTime = jobsByTimeFrame.get(startTime);
1938 allocationsByEndTime = allocationsByTimeFrame.get(startTime);
1939
1940 // Get job that ends at this resource's end time
1941 if (!jobsByEndTime.containsKey(endTime)) {
1942 jobsByEndTime.put(endTime, new sked__Job__c(
1943 sked__Start__c = startTime.addMinutes(-cpOffsetInMinutes),
1944 sked__Finish__c = endTime.addMinutes(-cpOffsetInMinutes),
1945 sked__Type__c = app.type.name.replace('&', '&'),
1946 Type_Code__c = app.type.code,
1947 Category__c = app.type.category,
1948 sked__Account__c = app.patient.Id,
1949 sked__Job_Status__c = 'Pending Dispatch',
1950 sked__Contact__c = app.patient.contact,
1951 sked__Address__c = jobAddress,
1952 sked__Duration__c = duration
1953 ));
1954 allocationsByEndTime.put(endTime, new List<sked__Job_Allocation__c>());
1955 }
1956
1957 job = jobsByEndTime.get(endTime);
1958 allocations = allocationsByEndTime.get(endTime);
1959
1960 if (defaultRegion != null) { job.sked__Region__c = defaultRegion.Id; }
1961
1962 if (firstJob == null) { firstJob = job; }
1963
1964 // @TODO this is awful. it should be a lookup + formula fields
1965 if (job.Patient_Advocate_ID__c == null) {
1966 job.Patient_Advocate_ID__c = rObject.Assigned_Patient_Advocate__c;
1967 job.Patient_Advocate_Name__c = rObject.Assigned_Patient_Advocate__r.Name;
1968 job.Patient_Advocate_Email_Address__c = rObject.Assigned_Patient_Advocate__r.Email;
1969 }
1970
1971 //Notification is harcoded to push as we send sms not skedulo
1972 allocations.add(new sked__Job_Allocation__c(
1973 sked__Resource__c = res.Id,
1974 sked__Estimated_Travel_Time__c = res.drivingTimeFrom,
1975 Distance_From__c = res.distanceFrom,
1976 Distance_To__c = res.distanceTo,
1977 Remote__c = res.remote,
1978 sked__Notification_Type__c = 'push'
1979 ));
1980 }
1981
1982 if (!String.isEmpty(app.notes)) { firstJob.sked__Notes_Comments__c = app.notes; }
1983 //UFI fields
1984 firstJob.sked__Urgency__c = app.urgent == null || !app.urgent ? Constants.URGENCY_NORMAL : Constants.URGENCY_URGENT;
1985 firstJob.Flexible__c = app.flexible == null ? false : app.flexible;
1986 firstJob.Interpreter__c = app.interpreter == null ? false : app.interpreter;
1987 firstJob.TimeAdded__c = app.timeadded == null ? false : app.timeadded;
1988 firstJob.Post_Discharge__c = app.postDischarge == null ? false : app.postDischarge;
1989 firstJob.Snf__c = app.snf == null ? false : app.snf;
1990 firstJob.Eligible_value__c = app.jobEligibility;
1991 firstJob.Program__c = currentEnrollment.programId;
1992 firstJob.Enrollment__c = currentEnrollment.enrollmentId;
1993 //CCP in Appt type => 2Field
1994 if(!haveRemote) { firstJob.CCP_Parent__c = resources.containsKey(ccpId) ? resources.get(ccpId).Name : ''; }
1995 firstJob.Phone_Visit_Type__c = app.phoneVisitType;
1996
1997 // insert the Parent Job
1998 insert firstJob;
1999 List<sked__Job__c> jobsToInsert = new List<sked__Job__c>();
2000 List<sked__Job_Allocation__c> allocationsToInsert = new List<sked__Job_Allocation__c>();
2001 for (Map<DateTime, sked__Job__c> jobsMap : jobsByTimeFrame.values()) {
2002 for (sked__Job__c j : jobsMap.values()) {
2003 jobsToInsert.add(j);
2004 j.Snf__c = firstJob.Snf__c;
2005 if (j.Id != firstJob.Id) {
2006 // add first allocation as parent
2007 j.sked__Parent__c = firstJob.Id;
2008 j.CCP_Parent__c = resources.containsKey(ccpId) ? resources.get(ccpId).Name : '';
2009 j.Eligible_value__c = app.jobEligibility;
2010 if (!String.isEmpty(app.notes)) { j.sked__Notes_Comments__c = app.notes; }
2011 //UFI fields
2012 j.sked__Urgency__c = app.urgent == null || !app.urgent ? Constants.URGENCY_NORMAL : Constants.URGENCY_URGENT;
2013 j.Flexible__c = app.flexible == null ? false : app.flexible;
2014 j.Interpreter__c = app.interpreter == null ? false : app.interpreter;
2015 j.TimeAdded__c = app.timeadded == null ? false : app.timeadded;
2016 j.Post_Discharge__c = app.postDischarge == null ? false : app.postDischarge;
2017 j.Program__c = currentEnrollment.programId;
2018 j.Enrollment__c = currentEnrollment.enrollmentId;
2019 }
2020 }
2021 }
2022
2023 // upsert so we don't try to insert a duplicate for the first job
2024 upsert jobsToInsert;
2025
2026 for (sked__Job__c j : jobsToInsert) {
2027 allocationsByEndTime = allocationsByTimeFrame.get(j.sked__Start__c.addMinutes(cpOffsetInMinutes));
2028 if (allocationsByEndTime == null) {
2029 throw new AppointmentException('There should be an allocation map for this start time');
2030 }
2031 allocations = allocationsByEndTime.get(j.sked__Finish__c.addMinutes(cpOffsetInMinutes));
2032 if (allocations == null) {
2033 throw new AppointmentException('There should be allocations for job ' + j.Id);
2034 }
2035 for (sked__Job_Allocation__c a : allocations) {
2036 a.sked__Job__c = j.Id;
2037 if(!jobsResourceMap.containsKey(j.Id)){
2038 jobsResourceMap.put(j.Id, new List<String>());
2039 }
2040 jobsResourceMap.get(j.Id).add(a.sked__Resource__c);
2041 allocationsToInsert.add(a);
2042 }
2043 }
2044
2045 insert allocationsToInsert;
2046 List<Id> allocationIds = new List<Id>();
2047 for (sked__Job_Allocation__c allocation : allocationsToInsert) {
2048 allocationIds.add(allocation.Id);
2049 }
2050
2051 if (app.patient.type == Constants.PATIENT_TYPE) {
2052 Decimal ehrProfile = Utils.getEHRProfile(app, type, job);
2053
2054 // Create appointment in Drchrono.
2055 bookEHRTime(allocationIds, resourceIds, Integer.valueOf(app.patient.account.Patient_Unique_ID__c), starts, durations, fieldsResources, ehrProfile, type.Force_EHR__c);
2056 }
2057 app.jobID = firstJob.Id;
2058
2059 // Change the job status to Dispached through the Skedulo API.
2060 dispatchAsync(app.jobID);
2061
2062 // Notify the patient that an appointment was scheduled.
2063 sendNotificationPatient(app);
2064
2065 // Notify all CareProvider that an appointment is scheduled for today.
2066 Datetime dtApp = DateTime.valueOfGmt(app.startTime);
2067 if(dtApp.date().isSameDay(Date.today())) {
2068 notificationsResourceIn(app);
2069 }
2070
2071 processPatientPrimaryResource(app, Constants.DML_INSERT);
2072
2073 // Generate PDF doc with Authorization Form for LA CARE Patients.
2074 if (app.patient.account.Parent.Name == Constants.HCO_LA_CARE) {
2075 Map<Id, List<String>> oneAjobsResource = new Map<Id, List<String>>();
2076 for (sked__job__c j :jobsToInsert) {
2077 if (setCodes_1A.contains(j.Type_Code__c) || setCodes_1A.contains(j.sked__Type__c)) {
2078 oneAjobsResource.put(j.id, jobsResourceMap.get(j.id));
2079 }
2080 }
2081 generateAuthorizationForms(app.jobID, JSON.serialize(oneAjobsResource));
2082 }
2083
2084 return app;
2085 }
2086
2087 @future(callout=true)
2088 public static void generateAuthorizationForms(Id parent, String jobsResourcesMapString){
2089 Map<Id, List<String>> jobsResourcesMap = (Map<Id, List<String>>) JSON.deserialize(jobsResourcesMapString, Map<Id, List<String>>.class);
2090 PageReference generator;
2091 Blob pdfData;
2092 List<Attachment> documents = new List<Attachment>();
2093 Attachment document;
2094 for(Id jId: jobsResourcesMap.keySet()){
2095 for(String resourceId: jobsResourcesMap.get(jId)){
2096 generator = new PageReference('/apex/LACareAuthorizationRequest?jobId='+jId+'&resourceId='+resourceId);
2097 pdfData=Test.IsRunningTest() ? Blob.valueOf('UNIT.TEST') : generator.getContent();
2098 document = new Attachment();
2099 document.Body = pdfData;
2100 document.name = Constants.LACareAuthorizationName;
2101 document.parentId = parent;
2102 documents.add(document);
2103 }
2104 }
2105 insert documents;
2106 }
2107
2108 private static void processPatientPrimaryResource(Appointment appt, String dmlOperation) {
2109 Boolean changed = false;
2110 Integer countLVN = 0;
2111 List<Resource> resources = new List<Resource>();
2112
2113 if(dmlOperation == Constants.DML_INSERT) {
2114 resources = appt.resources;
2115 } else if (dmlOperation == Constants.DML_UPDATE) {
2116 resources = appt.allResources;
2117 }
2118
2119 if (!resources.isEmpty()) {
2120 Boolean rewrite = false;
2121 Account patient = [SELECT Id, Name, Primary_Care_Provider__c, Primary_LVN__c, Primary_ROC__c, Secondary_LVN__c, Primary_BH__c FROM Account WHERE Id = :appt.patient.Id LIMIT 1];
2122 for (Resource r : resources) {
2123 rewrite = (r.rewriteResource != null && r.rewriteResource);
2124 //Primary ROC
2125 if ((rewrite || patient.Primary_ROC__c == null)
2126 && Constants.FA_TYPES.contains(r.type)
2127 && Constants.CATEGORIES_FIELD_AGENT.contains(appt.type.categoryCode)
2128 && patient.Primary_ROC__c != r.Id) {
2129 patient.Primary_ROC__c = r.Id;
2130 changed = true;
2131 } else if (rewrite
2132 && Constants.CCP_TYPES.contains(r.type)
2133 && patient.Primary_Care_Provider__c != r.Id) { // Primary CCP
2134 patient.Primary_Care_Provider__c = r.Id;
2135 changed = true;
2136 } else if (r.type == Constants.LVN_TYPE) { // Primary LVN
2137 if (rewrite
2138 && countLVN == 0 && patient.Primary_LVN__c != r.Id) {
2139 patient.Primary_LVN__c = r.Id;
2140 changed = true;
2141 } else if (rewrite
2142 && countLVN == 1 && patient.Secondary_LVN__c != r.Id) { // Secondary LVN
2143 patient.Secondary_LVN__c = r.Id;
2144 changed = true;
2145 }
2146 countLVN++;
2147 }
2148 }
2149 if (changed) {
2150 update patient;
2151 }
2152 }
2153 }
2154
2155 /*
2156 * bookEHRTime
2157 * Future method to create an Appt in drchrono asynchronously.
2158 * @ param allocationIds List of Job Allocations Ids
2159 * @ param resourceIds List of Resources Ids
2160 * @ param patId Patient Id
2161 * @ param starts Map<Resource Id, Date time of appointment start>
2162 * @ param durations Map<Resource Id, Appointment duration>
2163 * @ param fieldsResources Set of not remote Resources Ids
2164 * @ param ehrProfile EHR Profile Id
2165 * @ param forceEHR Flag to indicate if the EHR Id is forced or not (Write to EHR LVN-only)
2166 */
2167 @future(callout=true)
2168 public static void bookEHRTime(List<Id> allocationIds, List<Id> resourceIds, Integer patId, Map<Id, DateTime> starts, Map<Id, Integer> durations, Set<Id> fieldsResources, Decimal ehrProfile, Boolean forceEHR) {
2169 Map<Id, String> allocationEHRIds = new Map<Id, String>();
2170 String currentJobName = '';
2171 List<sked__Job_Allocation__c> allocations = new List<sked__Job_Allocation__c>();
2172 Id patientPrimaryCCP;
2173 for (sked__Job_Allocation__c all :[SELECT Id, sked__Job__r.Category__c, sked__Resource__c, sked__Job__r.Name, patient_primary_CCP_Id__c FROM sked__Job_Allocation__c WHERE Id IN : allocationIds]) {
2174 allocations.add(all);
2175 patientPrimaryCCP = all.patient_primary_CCP_Id__c != null ? all.patient_primary_CCP_Id__c : null;
2176 }
2177
2178 Map<Id, sked__Resource__c> resources = new Map<Id, sked__Resource__c>([SELECT Id, Name,
2179 EHR__r.Id,
2180 TimeZone__c,
2181 EHR__r.Name,
2182 RecordTypeId,
2183 Doctor_ID__c,
2184 Rate_Factor__c,
2185 RecordType.Name,
2186 EHR__r.Token__c,
2187 PrimaryMarket__c,
2188 EHR__r.CallBack__c,
2189 EHR__r.Client_Id__c,
2190 EHR__r.Exam_Room__c,
2191 EHR__r.Expiration__c,
2192 EHR__r.EHR_System__c,
2193 EHR__r.Refresh_Token__c,
2194 EHR__r.Client_Secret__c,
2195 EHR__r.Token_Endpoint__c,
2196 EHR__r.Offices_Endpoint__c,
2197 EHR__r.Doctors_Endpoint__c,
2198 EHR__r.Token_Last_Reset__c,
2199 EHR__r.Appointment_Endpoint__c,
2200 Assigned_Patient_Advocate__r.Id,
2201 Assigned_Patient_Advocate__r.Name,
2202 Assigned_Patient_Advocate__r.Email,
2203 Doctor_s_Practice_Account__r.EHR_Office_Id__c
2204 FROM sked__Resource__c
2205 WHERE Id IN :resourceIds
2206 OR Name = :Constants.DEFAULT_PROVIDER
2207 OR Id = :patientPrimaryCCP]);
2208 sked__Resource__c defaultProvider = null;
2209 if (forceEHR) {
2210 // look for the default provider
2211 for (sked__Resource__c r :resources.values()) {
2212 if (patientPrimaryCCP != null) {
2213 if (r.Id == patientPrimaryCCP) {
2214 defaultProvider = r;
2215 break;
2216 }
2217 } else if (r.Name == Constants.DEFAULT_PROVIDER) {
2218 defaultProvider = r;
2219 break;
2220 }
2221 }
2222 }
2223
2224 sked__Job_Allocation__c a;
2225 sked__Resource__c resource;
2226 DateTime start;
2227 Integer duration, examRoomId;
2228 List<sked__Job_Allocation__c> toUpdate = new List<sked__Job_Allocation__c>();
2229 Map<Id, QueuableSendAppointmentsToEHR.AppointmentBookingData> sendToEHR = new Map<Id, QueuableSendAppointmentsToEHR.AppointmentBookingData>();
2230 for (Integer i = 0; i < allocations.size(); i++) {
2231 a = allocations[i];
2232 resource = resources.get(a.sked__Resource__c);
2233 if ((resource.RecordType.Name != Constants.LVN_TYPE
2234 && resource.RecordType.Name != Constants.OR_TYPE
2235 && (resource.RecordType.Name != Constants.SW_TYPE || (resource.Doctor_ID__c != null && resource.Doctor_ID__c != 'N/A')))
2236 || (forceEHR && i == allocations.size() - 1)) {
2237 start = starts.get(resource.Id);
2238 duration = durations.get(resource.Id);
2239 if (resource.RecordType.Name == Constants.LVN_TYPE || resource.RecordType.Name == Constants.OR_TYPE) {
2240 // Change the resource to the default provider
2241 resource = defaultProvider;
2242 }
2243 List<String> resourcesName = new List<String>();
2244 for (Id r :resources.keySet()) {
2245 if (fieldsResources.contains(r)) {
2246 resourcesName.add(resources.get(r).Name);
2247 }
2248 }
2249 String notes = AppointmentsHelper.getNotes(resourcesName, a.sked__Job__r.Category__c);
2250 examRoomId = resource.EHR__r.Exam_Room__c == null ? Constants.DEFAULT_EXAM_ROOM : Integer.valueOf(resource.EHR__r.Exam_Room__c);
2251 sendToEHR.put(a.Id, new QueuableSendAppointmentsToEHR.AppointmentBookingData(
2252 start,
2253 duration,
2254 patId,
2255 examRoomId,
2256 a.Id,
2257 resource,
2258 ehrProfile,
2259 notes
2260 ));
2261 }
2262 }
2263
2264 System.enqueueJob(new QueuableSendAppointmentsToEHR(
2265 sendToEHR
2266 ));
2267 }
2268
2269 public static Integer getOffsetInMinutes(String tzId, DateTime d) {
2270 TimeZone tz = Timezone.getTimeZone(tzId);
2271 Integer offsetInMS = tz.getOffset(d);
2272 return offsetinMS / 60000;
2273 }
2274
2275 /*
2276 Get a map of the appointments allocated for the given resources between the dates 'startDate' and 'endDate'.
2277 The first key represent the day for which appointments are grouped. The second key are the resources.
2278 For example, assuming there are the next three appointments:
2279 (2016-4-1, John)
2280 (2016-4-1, Jane)
2281 (2016-4-2, John)
2282 This will return:
2283 2016-4-1
2284 |- John
2285 | +- (2016-4-1, John)
2286 +- Jane
2287 +- (2016-4-1, Jane)
2288 2016-4-2
2289 +- John
2290 +- (2016-4-2, John)
2291
2292 Datetimes are returned in the UTC zone (GMT).
2293 */
2294 public static Appointment[] getPatientAppointments(Date startDate, Date endDate, List<Id> patientIds) {
2295 DateTime startDateTime = DateTime.newInstanceGmt(startDate, Time.newInstance(0, 0, 0, 0));
2296 DateTime endDateTime = DateTime.newInstanceGmt(endDate, Time.newInstance(23, 59, 59, 999));
2297
2298 List<sked__Job_Allocation__c> allocations = [SELECT EHR_Appointment_ID__c, sked__Status__c, Remote__c, sked__Estimated_Travel_Time__c, Distance_From__c, Distance_To__c,
2299 sked__Resource__r.Id,
2300 sked__Resource__r.Name,
2301 sked__Resource__r.Timezone__c,
2302 sked__Resource__r.RecordTypeID,
2303 sked__Resource__r.Map_Color__c,
2304 sked__Resource__r.Rate_Factor__c,
2305 sked__Resource__r.RecordType.Name,
2306 sked__Resource__r.sked__User__r.Id,
2307 sked__Resource__r.PrimaryMarket__c,
2308 sked__Resource__r.show_in_calendar__c,
2309 sked__Resource__r.sked__Home_Address__c,
2310 sked__Resource__r.sked__User__r.smallphotourl,
2311 sked__Resource__r.sked__Is_Active__c,
2312 sked__Job__r.Id,
2313 sked__Job__r.Snf__c,
2314 sked__Job__r.CreatedDate,
2315 sked__Job__r.Flexible__c,
2316 sked__Job__r.Category__c,
2317 sked__Job__r.TimeAdded__c,
2318 sked__Job__r.Type_Code__c,
2319 sked__Job__r.Label_Code__c,
2320 sked__Job__r.sked__Type__c,
2321 sked__Job__r.Interpreter__c,
2322 sked__Job__r.CreatedBy.Name,
2323 sked__Job__r.sked__Start__c,
2324 sked__Job__r.sked__Finish__c,
2325 sked__Job__r.sked__Parent__c,
2326 sked__Job__r.sked__Urgency__c,
2327 sked__Job__r.Post_Discharge__c,
2328 sked__Job__r.Updated_Address__c,
2329 sked__Job__r.sked__Notes_Comments__c,
2330 sked__Job__r.sked__Account__c,
2331 sked__Job__r.sked__Account__r.Name,
2332 sked__Job__r.sked__Account__r.Phone,
2333 sked__Job__r.sked__Account__r.PRS__c,
2334 sked__Job__r.sked__Account__r.Completed_Appointments__c,
2335 sked__Job__r.sked__Account__r.Cancelled_Appointments__c,
2336 sked__Job__r.sked__Account__r.Color__c,
2337 sked__Job__r.sked__Account__r.Region__c,
2338 sked__Job__r.sked__Account__r.Market__c,
2339 sked__Job__r.sked__Account__r.EHR_MRN__c,
2340 sked__Job__r.sked__Account__r.Subzone__c,
2341 sked__Job__r.sked__Account__r.TimeZone__c,
2342 sked__Job__r.sked__Account__r.MarketId__c,
2343 sked__Job__r.sked__Account__r.BillingCity,
2344 sked__Job__r.sked__Account__r.Language__c,
2345 sked__Job__r.sked__Account__r.BillingState,
2346 sked__Job__r.sked__Account__r.BillingStreet,
2347 sked__Job__r.sked__Account__r.Color_Dark__c,
2348 sked__Job__r.sked__Account__r.BillingCountry,
2349 sked__Job__r.sked__Account__r.Touch_Count__c,
2350 sked__Job__r.sked__Account__r.RecordType.Name,
2351 sked__Job__r.sked__Account__r.BillingPostalCode,
2352 sked__Job__r.sked__Account__r.Lead_Suborigin__c,
2353 sked__Job__r.sked__Account__r.Patient_Unique_ID__c,
2354 sked__Job__r.sked__Account__r.Insurance_Plan_Name__c,
2355 sked__Job__r.sked__Account__r.Pro_TOC_latest_status__c,
2356 sked__Job__r.sked__Account__r.Pro_TOC_latest_substatus__c,
2357 sked__Job__r.sked__Account__r.In_SNF__c,
2358 sked__Job__r.sked__Account__r.SNF__c,
2359 sked__Job__r.sked__Account__r.SNF__r.BillingStreet,
2360 sked__Job__r.sked__Account__r.SNF__r.BillingCity,
2361 sked__Job__r.sked__Account__r.SNF__r.BillingCountry,
2362 sked__Job__r.sked__Account__r.SNF__r.BillingState,
2363 sked__Job__r.sked__Account__r.SNF__r.BillingPostalCode,
2364 sked__Job__r.sked__Account__r.SNF__r.Subzone__c,
2365 sked__Job__r.sked__Account__r.Parent.Name,
2366 sked__Job__r.sked__Account__r.Parent.SNF_Enabled__c,
2367 sked__Job__r.sked__Account__r.Stratification_Level__c,
2368 sked__Job__r.sked__Address__c
2369 FROM sked__Job_Allocation__c
2370 WHERE sked__Job__r.sked__Start__c >= :startDateTime
2371 AND sked__Job__r.sked__Start__c < :endDateTime
2372 AND sked__Job__r.sked__Account__r.Id IN :patientIds
2373 AND sked__Job__r.sked__Job_Status__c !=: Constants.JOB_STATUS_CANCELLED
2374 ORDER BY sked__Job__r.sked__Parent__c NULLS FIRST];
2375 Map<Id, List<sked__Job_Allocation__c>> allocationsByJob = getAllocationMap(allocations);
2376 Appointment appt = null;
2377 Map<Id, Appointment> appointmentsMap = new Map<Id, Appointment>();
2378 Boolean needsUpdate = false;
2379
2380 for (sked__Job_Allocation__c ja : allocations) {
2381 if (ja.sked__Job__r.sked__Parent__c == null || !appointmentsMap.containsKey(ja.sked__Job__r.sked__Parent__c)) {
2382 // This is the parent appointment or a proper follow-up (which we may very well use in the future)
2383 appt = new Appointment(ja, allocationsByJob.get(ja.sked__Job__r.Id));
2384 appt.address = ja.sked__Job__r.sked__Address__c;
2385 setSNFData(appt, ja.sked__Job__r);
2386 appointmentsMap.put(appt.jobID, appt);
2387 } else {
2388 appt = appointmentsMap.get(ja.sked__Job__r.sked__Parent__c);
2389 needsUpdate = false;
2390 if (ja.sked__Job__r.sked__Start__c < appt.startTimeAsDate) {
2391 // child starts earlier, assign new start time
2392 appt.startTimeAsDate = ja.sked__Job__r.sked__Start__c;
2393 needsUpdate = true;
2394 }
2395 if (ja.sked__Job__r.sked__Finish__c > appt.endTimeAsDate) {
2396 // child starts earlier, assign new start time
2397 appt.endTimeAsDate = ja.sked__Job__r.sked__Finish__c;
2398 needsUpdate = true;
2399 }
2400 if (needsUpdate) {
2401 appt.applyTimezone();
2402 }
2403 // Add resource
2404 appt.resources.add(new Resource(ja.sked__Resource__r, ja.Remote__c));
2405 }
2406 }
2407 return appointmentsMap.values();
2408 }
2409
2410 public static void setSNFData(Appointment appt, sked__Job__c job) {
2411 appt.snf = job.Snf__c; //overrides previous line change un purpose
2412 setSNFDetails(appt, job.sked__Account__r);
2413 }
2414
2415 public static void setSNFDetails(Appointment appt, Account acct) {
2416 if(appt.snf == null) {
2417 appt.snf = acct.In_SNF__c == null ? False : acct.In_SNF__c;
2418 }
2419 appt.type.snfEnabled = (acct.Parent == null ||
2420 (acct.Parent.SNF_Enabled__c != null && !acct.Parent.SNF_Enabled__c)) ? null : acct.SNF__c != null;
2421 if(appt.type.snfEnabled != null && appt.type.snfEnabled) {
2422 if(appt.Patient.addresses == null) {
2423 appt.Patient.addresses = new Map<String, Address> ();
2424 }
2425 appt.Patient.addresses.put('snf', new Address(acct.SNF__r));
2426 if(appt.snf) {
2427 appt.address = appt.Patient.addresses.get('snf').toString();
2428 }
2429 }
2430 }
2431
2432 public static void setHospitalDetails(Appointment appt, Account patient) {
2433 if(!patient.ADT_records__r.isEmpty()) {
2434 if(patient.Hospital__c != null) {
2435 if(patient.ADT_records__r[0].Facility__r.Name == patient.Hospital__c) {
2436 appt.Patient.addresses.put('hospital', new Address(patient.ADT_records__r[0].Facility__r));
2437 } else {
2438 //If adt record mismatch
2439 }
2440 }
2441 }
2442 }
2443
2444 @RemoteAction
2445 public static Appointment getAppointmentForEdit(Id apptId) {
2446 sked__Job__c job = [SELECT Id, sked__Parent__c FROM sked__Job__c WHERE Id =: apptId LIMIT 1];
2447 if (job.sked__Parent__c != null) {
2448 apptId = job.sked__Parent__c;
2449 }
2450 job = [SELECT Id, sked__Start__c, sked__Finish__c, Updated_Address__c, sked__Type__c, Type_Code__c, Category__c,sked__Job_Status__c, Post_Discharge__c,
2451 CreatedBy.Name, CreatedDate, sked__Notes_Comments__c, sked__Urgency__c, Flexible__c, Interpreter__c, TimeAdded__c, Job_confirmation_status__c,
2452 Phone_Visit_Type__c,
2453 sked__Address__c,
2454 sked__Account__c,
2455 sked__Account__r.Name,
2456 sked__Account__r.Phone,
2457 sked__Account__r.PRS__c,
2458 sked__Account__r.Completed_Appointments__c,
2459 sked__Account__r.Cancelled_Appointments__c,
2460 sked__Account__r.Color__c,
2461 sked__Account__r.Region__c,
2462 sked__Account__r.Market__c,
2463 sked__Account__r.Subzone__c,
2464 sked__Account__r.EHR_MRN__c,
2465 sked__Account__r.BillingCity,
2466 sked__Account__r.MarketId__c,
2467 sked__Account__r.TimeZone__c,
2468 sked__Account__r.Language__c,
2469 sked__Account__r.BillingState,
2470 sked__Account__r.BillingStreet,
2471 sked__Account__r.Color_Dark__c,
2472 sked__Account__r.BillingCountry,
2473 sked__Account__r.Touch_Count__c,
2474 sked__Account__r.RecordType.Name,
2475 sked__Account__r.BillingPostalCode,
2476 sked__Account__r.Lead_Suborigin__c,
2477 sked__Account__r.Patient_Unique_ID__c,
2478 sked__Account__r.Insurance_Plan_Name__c,
2479 sked__Account__r.Stratification_Level__c,
2480 sked__Account__r.Pro_TOC_latest_status__c,
2481 sked__Account__r.Primary_Care_Provider__c,
2482 sked__Account__r.Pro_TOC_latest_substatus__c,
2483 sked__Account__r.Parent.Name,
2484 sked__Account__r.Parent.BillingStreet,
2485 sked__Account__r.Parent.BillingCity,
2486 sked__Account__r.Parent.BillingCountry,
2487 sked__Account__r.Parent.BillingState,
2488 sked__Account__r.Parent.BillingPostalCode,
2489 sked__Account__r.Parent.SNF_Enabled__c,
2490 sked__Account__r.Hospital__c,
2491 sked__Account__r.In_SNF__c,
2492 sked__Account__r.SNF__c,
2493 sked__Account__r.SNF__r.BillingStreet,
2494 sked__Account__r.SNF__r.BillingCity,
2495 sked__Account__r.SNF__r.BillingCountry,
2496 sked__Account__r.SNF__r.BillingState,
2497 sked__Account__r.SNF__r.BillingPostalCode,
2498 sked__Account__r.SNF__r.Subzone__c,
2499 Snf__c,
2500 Eligible_value__c,
2501 Enrollment__r.Program_Name__c,
2502 (SELECT Id FROM sked__Followups__r),
2503 (SELECT EHR_Appointment_ID__c, Remote__c,
2504 sked__Resource__r.Id,
2505 sked__Resource__r.Name,
2506 sked__Resource__r.Timezone__c,
2507 sked__Resource__r.Map_Color__c,
2508 sked__Resource__r.RecordTypeID,
2509 sked__Resource__r.Rate_Factor__c,
2510 sked__Resource__r.RecordType.Name,
2511 sked__Resource__r.sked__User__r.Id,
2512 sked__Resource__r.PrimaryMarket__c,
2513 sked__Resource__r.show_in_calendar__c,
2514 sked__Resource__r.sked__Home_Address__c,
2515 sked__Resource__r.sked__User__r.smallphotourl,
2516 sked__Resource__r.sked__Is_Active__c
2517 FROM sked__Job_Allocations__r)
2518 FROM sked__Job__c
2519 WHERE Id =: apptId
2520 ORDER BY sked__Start__c
2521 LIMIT 1];
2522 Map<Id, sked__Job__c> children = new Map<Id, sked__Job__c>([SELECT Id, sked__Start__c, sked__Finish__c, Updated_Address__c, sked__Type__c, Type_Code__c, Category__c, Post_Discharge__c,
2523 sked__Job_Status__c, CreatedBy.Name, CreatedDate, sked__Notes_Comments__c, sked__Urgency__c, Flexible__c, Interpreter__c, TimeAdded__c, Job_confirmation_status__c, sked__Address__c,
2524 sked__Account__c,
2525 sked__Account__r.Name,
2526 sked__Account__r.Phone,
2527 sked__Account__r.PRS__c,
2528 sked__Account__r.Completed_Appointments__c,
2529 sked__Account__r.Cancelled_Appointments__c,
2530 sked__Account__r.Color__c,
2531 sked__Account__r.Region__c,
2532 sked__Account__r.Market__c,
2533 sked__Account__r.Subzone__c,
2534 sked__Account__r.EHR_MRN__c,
2535 sked__Account__r.BillingCity,
2536 sked__Account__r.MarketId__c,
2537 sked__Account__r.TimeZone__c,
2538 sked__Account__r.Language__c,
2539 sked__Account__r.BillingState,
2540 sked__Account__r.BillingStreet,
2541 sked__Account__r.Color_Dark__c,
2542 sked__Account__r.BillingCountry,
2543 sked__Account__r.Touch_Count__c,
2544 sked__Account__r.RecordType.Name,
2545 sked__Account__r.BillingPostalCode,
2546 sked__Account__r.Lead_Suborigin__c,
2547 sked__Account__r.Patient_Unique_ID__c,
2548 sked__Account__r.Insurance_Plan_Name__c,
2549 sked__Account__r.Stratification_Level__c,
2550 sked__Account__r.Pro_TOC_latest_status__c,
2551 sked__Account__r.Primary_Care_Provider__c,
2552 sked__Account__r.Pro_TOC_latest_substatus__c,
2553 sked__Account__r.Parent.Name,
2554 sked__Account__r.ParentId,
2555 sked__Account__r.Parent.SNF_Enabled__c,
2556 sked__Account__r.In_SNF__c,
2557 sked__Account__r.SNF__c,
2558 sked__Account__r.SNF__r.BillingStreet,
2559 sked__Account__r.SNF__r.BillingCity,
2560 sked__Account__r.SNF__r.BillingCountry,
2561 sked__Account__r.SNF__r.BillingState,
2562 sked__Account__r.SNF__r.BillingPostalCode,
2563 sked__Account__r.SNF__r.Subzone__c,
2564 Snf__c,
2565 Eligible_value__c,
2566 Enrollment__r.Program_Name__c,
2567 (SELECT EHR_Appointment_ID__c, Remote__c,
2568 sked__Resource__r.Id,
2569 sked__Resource__r.Name,
2570 sked__Resource__r.Timezone__c,
2571 sked__Resource__r.Map_Color__c,
2572 sked__Resource__r.RecordTypeID,
2573 sked__Resource__r.Rate_Factor__c,
2574 sked__Resource__r.RecordType.Name,
2575 sked__Resource__r.sked__User__r.Id,
2576 sked__Resource__r.PrimaryMarket__c,
2577 sked__Resource__r.show_in_calendar__c,
2578 sked__Resource__r.sked__Home_Address__c,
2579 sked__Resource__r.sked__User__r.smallphotourl,
2580 sked__Resource__r.sked__Is_Active__c
2581 FROM sked__Job_Allocations__r)
2582 FROM sked__Job__c
2583 WHERE Id IN :job.sked__Followups__r
2584 ORDER BY sked__Start__c]);
2585
2586 Appointment appt = new Appointment(job, children.values());
2587 appt.type.snfEnabled = job.sked__Account__r.Parent.SNF_Enabled__c != null && job.sked__Account__r.Parent.SNF_Enabled__c ? null : job.sked__Account__r.SNF__c != null;
2588 appt.snf = job.Snf__c;
2589 appt.program = job.Enrollment__r.Program_Name__c;
2590 appt.address = job.sked__Address__c;
2591 appt.jobEligibility = job.Eligible_value__c;
2592 appt.patient.hospital = job.sked__Account__r.Hospital__c;
2593 appt.phoneVisitType = job.Phone_Visit_Type__c;
2594 String snfAddress;
2595 setSNFData(appt, job);
2596
2597 Account patient;
2598 if(!String.isBlank(job.sked__Account__r.Hospital__c)) {
2599 patient = [SELECT Id,
2600 (SELECT Id, Date_Time__c, Facility__r.Phone, Facility__r.Name, Facility__r.Subzone__c, Facility__r.BillingCity, Facility__r.BillingState, Facility__r.BillingStreet, Facility__r.BillingCountry, Facility__r.BillingPostalCode, Facility__r.RecordType.Name
2601 FROM ADT_records__r
2602 WHERE Facility__r.RecordType.Name = :Constants.HOSPITAL_TYPE AND Type__c = :Constants.ADT_TYPE_ADMISSION ORDER BY Date_Time__c DESC LIMIT 1)
2603 FROM Account WHERE Id = :job.sked__Account__c];
2604 if(!patient.ADT_records__r.isEmpty()) {
2605 appt.patient.locations = new Map<String, Location>();
2606 appt.patient.locations.put(Constants.HOSPITAL_TYPE, new Hospital(patient.ADT_records__r[0]));
2607 }
2608 }
2609
2610 Set<Id> resourceId = new Set<Id>();
2611 for (sked__Job_Allocation__c al : job.sked__Job_Allocations__r) {
2612 resourceId.add(al.sked__Resource__c);
2613 }
2614
2615 for(sked__Job__c ch : children.values()) {
2616 for(sked__Job_Allocation__c al :ch.sked__Job_Allocations__r) {
2617 resourceId.add(al.sked__Resource__c);
2618 }
2619 }
2620
2621 Map<Id, Map<String,List<sked__Availability_Template_Entry__c>>> entriesByResource = getEntriesByResource(resourceId);
2622 for(Integer i = 0; i < appt.resources.size(); i++) {
2623 Resource re = appt.resources[i];
2624 re.entries = entriesByResource.containsKey(re.Id) ? entriesByResource.get(re.Id) : null;
2625 appt.resources[i] = re;
2626 }
2627
2628 for(Integer j = 0; j < appt.children.size(); j++) {
2629 for(Integer k = 0; k < appt.children[j].resources.size(); k++) {
2630 Resource re = appt.children[j].resources[k];
2631 re.entries = entriesByResource.containsKey(re.Id) ? entriesByResource.get(re.Id) : null;
2632 appt.children[j].resources[k] = re;
2633 }
2634 appt.address = job.sked__Address__c;
2635 setSNFData(appt.children[j], job);
2636 }
2637 return appt;
2638 }
2639
2640 @RemoteAction
2641 public static Map<String, Map<Id,List<Appointment>>> getPersonAppointments(Date startDate, Date endDate, List<Id> resources) {
2642 Map<String, Map<Id,List<Appointment>>> appointmentsMap = new Map<String, Map<Id,List<Appointment>>>();
2643 Appointment[] appointments;
2644 String type = findObjectNameFromRecordIdPrefix(resources[0]);
2645
2646 if (type == 'sked__Resource__c'){
2647 sked__Resource__c res = [SELECT RecordType.Name, Rate_Factor__c FROM sked__Resource__c WHERE Id = :resources[0]];
2648 if (res.RecordType.Name == 'LVN'){
2649 appointments = getDocApptCanel(startDate, endDate, resources);
2650 } else {
2651 appointments = getSFAppointments(startDate, endDate, resources);
2652 }
2653 } else {
2654 appointments = getPatientAppointments(startDate, endDate, resources);
2655 }
2656
2657 TimeZone tz = Timezone.getTimeZone(Constants.DEFAULT_TIME_ZONE);
2658 for (Appointment appointment : appointments) {
2659 Integer offset = tz.getOffset(appointment.startTimeAsDate);
2660 appointment.setOffset(offset / 60000);
2661 String dateKey = appointment.startDate;
2662 if (!appointmentsMap.containsKey(dateKey))
2663 appointmentsMap.put(dateKey, new Map<Id, Appointment[]>());
2664 if (!appointmentsMap.get(dateKey).containsKey(resources[0]))
2665 appointmentsMap.get(dateKey).put(resources[0], new Appointment[]{});
2666 appointmentsMap.get(dateKey).get(resources[0]).add(appointment);
2667 }
2668 return appointmentsMap;
2669 }
2670
2671 @RemoteAction
2672 public static Map<String, Map<Id,List<Appointment>>> getAppointments(Date startDate, Date endDate, List<Id> resources) {
2673 sked__Resource__c[] ehrResources = new List<sked__Resource__c>();
2674 Map<Id, List<Assignment>> asgtms = getAssignments(startDate, endDate, resources);
2675 List<Resource> rList = new List<Resource>();
2676 Id someId;
2677 Boolean assigned = false;
2678 Appointment appmnt = new Appointment();
2679 appmnt.resources = new List<Resource>();
2680 List<Appointment> appts = new List<Appointment>();
2681 Map<Id, Appointment[]> assgnMap = new Map<Id, Appointment[]>();
2682
2683 // get the cp timezone
2684 String zone = null;
2685 TimeZone tz = null;
2686 for (sked__Resource__c r : [SELECT Id, Name, TimeZone__c, Rate_Factor__c, RecordTypeId, EHR__r.Id, EHR__r.Name, EHR__r.Client_Id__c,
2687 EHR__r.Client_Secret__c, EHR__r.CallBack__c, EHR__r.Token__c, EHR__r.Refresh_Token__c, EHR__r.Token_Last_Reset__c,
2688 EHR__r.Expiration__c, EHR__r.Token_Endpoint__c, EHR__r.Offices_Endpoint__c, EHR__r.Doctors_Endpoint__c,
2689 EHR__r.Appointment_Endpoint__c, EHR__r.EHR_System__c, RecordType.Name, Doctor_ID__c, sked__Is_Active__c,
2690 Doctor_s_Practice_Account__r.EHR_Office_Id__c, sked__Home_Address__c, PrimaryMarket__c, Map_Color__c, show_in_calendar__c
2691 FROM sked__Resource__c
2692 WHERE Id IN :resources]) {
2693 if (r.RecordType.Name == Constants.NP_TYPE) {
2694 zone = r.TimeZone__c;
2695 }
2696 if (r.RecordType.Name != Constants.LVN_TYPE) {
2697 ehrResources.add(r);
2698 }
2699
2700 if(asgtms.containsKey(r.Id)) {
2701 Resource re = new Resource();
2702 re.Id = r.Id;
2703 re.assignments = new List<Assignment>();
2704 re.assignments.addAll(asgtms.get(r.Id));
2705 someId = r.Id;
2706 appmnt.resources.add(re);
2707 assigned = true;
2708 }
2709 }
2710 tz = zone == null? Timezone.getTimeZone(Constants.DEFAULT_TIME_ZONE) : Timezone.getTimeZone(zone);
2711
2712 Appointment[] appointments = getSFAppointments(startDate, endDate, resources);
2713 Appointment[] getUnAvailability = getResourceUnAvailability(startDate,endDate, resources);
2714 Appointment[] getActivities = getResourceActivity(startDate,endDate, resources);
2715 appointments.addAll(getUnAvailability);
2716 appointments.addAll(getActivities);
2717
2718 Map<String, Map<Id,List<Appointment>>> appointmentsMap = new Map<String, Map<Id,List<Appointment>>>();
2719 Map<Id, List<Appointment>> appointmentsByResource = null;
2720 List<Appointment> resourceAppointments = null;
2721 Boolean isDuplicate;
2722
2723 for (Appointment appointment :appointments) {
2724 // set the offset
2725 Integer offset = tz.getOffset(appointment.startTimeAsDate);
2726 appointment.setOffset(offset / 60000);
2727
2728 String dateKey = appointment.startDate;
2729
2730 if (!appointmentsMap.containsKey(dateKey)) {
2731 appointmentsMap.put(dateKey, new Map<Id, Appointment[]>());
2732 }
2733 appointmentsByResource = appointmentsMap.get(dateKey);
2734
2735 if (!appointmentsByResource.containsKey(appointment.resource.Id)) {
2736 appointmentsByResource.put(appointment.resource.Id, new Appointment[]{});
2737 }
2738 resourceAppointments = appointmentsByResource.get(appointment.resource.Id);
2739
2740 isDuplicate = false;
2741 for (Appointment appt : resourceAppointments) {
2742 if (isDuplicate = (appt.startTimeAsDate == appointment.startTimeAsDate && appt.endTimeAsDate == appointment.endTimeAsDate)) {
2743 break;
2744 }
2745 }
2746 if (!isDuplicate) {
2747 resourceAppointments.add(appointment);
2748 }
2749 }
2750
2751 if (assigned && !appmnt.resources.isEmpty()) {
2752 //Assignments
2753 appts.add(appmnt);
2754 assgnMap.put(someId, appts);
2755 appointmentsMap.put('assignments', assgnMap);
2756 }
2757 return appointmentsMap;
2758 }
2759
2760 private static Map<Id, List<sked__Job_Allocation__c>> getAllocationMap(List<sked__Job_Allocation__c> allocations) {
2761 Map<Id, List<sked__Job_Allocation__c>> allocationsByJob = new Map<Id, List<sked__Job_Allocation__c>>();
2762 for (sked__Job_Allocation__c ja : allocations) {
2763 if (!allocationsByJob.containsKey(ja.sked__Job__r.Id)) {
2764 allocationsByJob.put(ja.sked__Job__r.Id, new List<sked__Job_Allocation__c>{ja});
2765 } else {
2766 allocationsByJob.get(ja.sked__Job__r.Id).add(ja);
2767 }
2768 }
2769 return allocationsByJob;
2770 }
2771
2772 private static Appointment[] getResourceUnAvailability(Date startDate,Date endDate, List<Id> resources) {
2773 Appointment[] appointments = new Appointment[]{};
2774 DateTime startDateTime = DateTime.newInstanceGmt(startDate, Time.newInstance(0, 0, 0, 0)),
2775 endDateTime = DateTime.newInstanceGmt(endDate, Time.newInstance(23, 59, 59, 999));
2776
2777 DateTime startNewDateTime,endNewDateTime,auxStartDateTime,auxEndDateTime,globalDateTime;
2778 for(sked__Availability__c r:[SELECT Id,sked__Is_Available__c,
2779 sked__Start__c,sked__Finish__c,
2780 sked__Resource__c,sked__Type__c,sked__Notes__c,
2781 CreatedBy.Name, CreatedDate
2782 FROM sked__Availability__c
2783 WHERE sked__Is_Available__c = FALSE
2784 AND sked__Status__c = 'Approved'
2785 AND sked__Start__c <= :endDateTime
2786 AND sked__Finish__c >= :startDateTime
2787 AND sked__Resource__c IN :resources]){
2788
2789 Date uaDateStart = Date.newInstance(r.sked__Start__c.year(), r.sked__Start__c.month(), r.sked__Start__c.day());
2790 Date uaDateEnd = Date.newInstance(r.sked__Finish__c.year(), r.sked__Finish__c.month(), r.sked__Finish__c.day());
2791
2792 if (uaDateStart == uaDateEnd && r.sked__Start__c != r.sked__Finish__c) {
2793 appointments.add(new Appointment(r));
2794 } else {
2795 if (uaDateStart < startDate) {
2796 uaDateStart = startDate;
2797 }
2798 if (uaDateEnd > endDate) {
2799 uaDateEnd = endDate;
2800 }
2801 Integer days = uaDateStart.daysBetween(uaDateEnd);
2802
2803 auxStartDateTime = r.sked__Start__c;
2804 auxEndDateTime = r.sked__Finish__c;
2805 globalDateTime = r.sked__Start__c;
2806
2807 for (Integer i = 0; i <= days;i++) {
2808 if(i == 0) {
2809 endNewDateTime = getResourceLimitDate(r.sked__Start__c, 'end');
2810 r.sked__Finish__c = endNewDateTime;
2811 appointments.add(new Appointment(r));
2812 r.sked__Finish__c = auxEndDateTime;
2813 }
2814 else if(i == days) {
2815 startNewDateTime = getResourceLimitDate(r.sked__Finish__c,'start');
2816 r.sked__Start__c = startNewDateTime;
2817 appointments.add(new Appointment(r));
2818 }
2819 else {
2820 Integer day = globalDateTime.day() + 1;
2821 Integer hourStart = 8;
2822 Integer minute = 00;
2823 Date myDateStart = Date.newInstance(globalDateTime.year(), globalDateTime.month(), day);
2824 Time myTimeStart = Time.newInstance(hourStart, minute, 0, 0);
2825 DateTime dtSt = DateTime.newInstance(myDateStart, myTimeStart);
2826
2827 Integer hourEnd = 20;
2828 Integer minuteEnd = 00;
2829 Date myDateEnd = Date.newInstance(globalDateTime.year(), globalDateTime.month(), day);
2830 Time myTimeEnd = Time.newInstance(hourEnd, minuteEnd, 0, 0);
2831 DateTime dtEnd = DateTime.newInstance(myDateEnd, myTimeEnd);
2832
2833 r.sked__Start__c = dtSt;
2834 r.sked__Finish__c = dtEnd;
2835
2836 appointments.add(new Appointment(r));
2837 globalDateTime = dtEnd;
2838 r.sked__Finish__c = auxEndDateTime;
2839 }
2840 }
2841 }
2842 }
2843 return appointments;
2844 }
2845
2846
2847 public static Appointment[] getResourceUnAvailability(DateTime startDateTime,DateTime endDateTime, List<Id> resources) {
2848 Appointment[] appointments = new Appointment[]{};
2849
2850 DateTime startNewDateTime,endNewDateTime,auxStartDateTime,auxEndDateTime,globalDateTime;
2851 for(sked__Availability__c r:[SELECT Id,sked__Is_Available__c,
2852 sked__Start__c,sked__Finish__c,
2853 sked__Resource__c,sked__Type__c,sked__Notes__c,
2854 CreatedBy.Name, CreatedDate
2855 FROM sked__Availability__c
2856 WHERE sked__Is_Available__c = FALSE
2857 AND sked__Status__c = 'Approved'
2858 AND sked__Start__c <= :endDateTime
2859 AND sked__Finish__c >= :startDateTime
2860 AND sked__Resource__c IN :resources]){
2861
2862 DateTime uaDateStart = r.sked__Start__c;
2863 DateTime uaDateEnd = r.sked__Finish__c;
2864
2865 if (uaDateStart == uaDateEnd && r.sked__Start__c != r.sked__Finish__c) {
2866 appointments.add(new Appointment(r));
2867 } else {
2868 if (uaDateStart < startDateTime) {
2869 uaDateStart = startDateTime;
2870 }
2871 if (uaDateEnd > endDateTime) {
2872 uaDateEnd = endDateTime;
2873 }
2874 Integer days = uaDateStart.dateGMT().daysBetween(uaDateEnd.dateGMT());
2875
2876 auxStartDateTime = r.sked__Start__c;
2877 auxEndDateTime = r.sked__Finish__c;
2878 globalDateTime = r.sked__Start__c;
2879
2880 for (Integer i = 0; i <= days;i++) {
2881 if(i == 0) {
2882 endNewDateTime = getResourceLimitDate(r.sked__Start__c, 'end');
2883 r.sked__Finish__c = endNewDateTime;
2884 appointments.add(new Appointment(r));
2885 r.sked__Finish__c = auxEndDateTime;
2886 }
2887 else if(i == days) {
2888 startNewDateTime = getResourceLimitDate(r.sked__Finish__c,'start');
2889 r.sked__Start__c = startNewDateTime;
2890 appointments.add(new Appointment(r));
2891 }
2892 else {
2893 Integer day = globalDateTime.day() + 1;
2894 Integer hourStart = 8;
2895 Integer minute = 00;
2896 Date myDateStart = Date.newInstance(globalDateTime.year(), globalDateTime.month(), day);
2897 Time myTimeStart = Time.newInstance(hourStart, minute, 0, 0);
2898 DateTime dtSt = DateTime.newInstance(myDateStart, myTimeStart);
2899
2900 Integer hourEnd = 20;
2901 Integer minuteEnd = 00;
2902 Date myDateEnd = Date.newInstance(globalDateTime.year(), globalDateTime.month(), day);
2903 Time myTimeEnd = Time.newInstance(hourEnd, minuteEnd, 0, 0);
2904 DateTime dtEnd = DateTime.newInstance(myDateEnd, myTimeEnd);
2905
2906 r.sked__Start__c = dtSt;
2907 r.sked__Finish__c = dtEnd;
2908
2909 appointments.add(new Appointment(r));
2910 globalDateTime = dtEnd;
2911 r.sked__Finish__c = auxEndDateTime;
2912 }
2913 }
2914 }
2915 }
2916 return appointments;
2917 }
2918
2919 private static Datetime getResourceLimitDate(DateTime pDateTime , String pLimit) {
2920 Integer hourEnd;
2921 if(pLimit == 'end') {
2922 hourEnd = 20;
2923 } else {
2924 hourEnd = 8;
2925 }
2926 Integer minute = 00;
2927 Date myDate = Date.newInstance(pDateTime.year(), pDateTime.month(), pDateTime.day());
2928 Time myTime = Time.newInstance(hourEnd, minute, 0, 0);
2929 DateTime dt = DateTime.newInstance(myDate, myTime);
2930 return dt;
2931 }
2932
2933 private static Appointment[] getResourceActivity(Date startDate,Date endDate, List<Id> resources) {
2934 Appointment[] appointments = new Appointment[]{};
2935 DateTime startDateTime = DateTime.newInstanceGmt(startDate, Time.newInstance(0, 0, 0, 0)),
2936 endDateTime = DateTime.newInstanceGmt(endDate, Time.newInstance(23, 59, 59, 999));
2937
2938 DateTime startNewDateTime,endNewDateTime,auxStartDateTime,auxEndDateTime,globalDateTime;
2939
2940 for(sked__Activity__c act :[SELECT Id, sked__Address__c, sked__End__c, sked__Location__c, sked__Notes__c, sked__Resource__c,
2941 sked__Start__c, sked__Type__c, sked__Timezone__c, CreatedBy.Name, CreatedDate
2942 FROM sked__Activity__c
2943 WHERE sked__Start__c <= :endDateTime
2944 AND sked__End__c >= :startDateTime
2945 AND sked__Resource__c IN :resources]) {
2946
2947 Date uaDateStart = Date.newInstance(act.sked__Start__c.year(), act.sked__Start__c.month(), act.sked__Start__c.day());
2948 Date uaDateEnd = Date.newInstance(act.sked__End__c.year(), act.sked__End__c.month(), act.sked__End__c.day());
2949
2950 if (uaDateStart == uaDateEnd && act.sked__Start__c != act.sked__End__c) {
2951 appointments.add(new Appointment(act));
2952 } else if (uaDateStart < uaDateEnd) {
2953 if (uaDateStart < startDate) {
2954 uaDateStart = startDate;
2955 }
2956 if (uaDateEnd > endDate) {
2957 uaDateEnd = endDate;
2958 }
2959 Integer days = uaDateStart.daysBetween(uaDateEnd);
2960 auxStartDateTime = act.sked__Start__c;
2961 auxEndDateTime = act.sked__End__c;
2962 globalDateTime = act.sked__Start__c;
2963
2964 for (Integer i = 0; i <= days; i++) {
2965 if(i == 0) {
2966 endNewDateTime = getResourceLimitDate(act.sked__Start__c, 'end');
2967 act.sked__End__c = endNewDateTime;
2968 appointments.add(new Appointment(act));
2969 act.sked__End__c = auxEndDateTime;
2970 }
2971 else if(i == days) {
2972 startNewDateTime = getResourceLimitDate(act.sked__End__c, 'start');
2973 act.sked__Start__c = startNewDateTime;
2974 appointments.add(new Appointment(act));
2975 }
2976 else {
2977 Integer day = globalDateTime.day() + 1;
2978 Integer hourStart = 8;
2979 Integer minute = 00;
2980 Date myDateStart = Date.newInstance(globalDateTime.year(), globalDateTime.month(), day);
2981 Time myTimeStart = Time.newInstance(hourStart, minute, 0, 0);
2982 DateTime dtSt = DateTime.newInstance(myDateStart, myTimeStart);
2983
2984 Integer hourEnd = 20;
2985 Integer minuteEnd = 00;
2986 Date myDateEnd = Date.newInstance(globalDateTime.year(), globalDateTime.month(), day);
2987 Time myTimeEnd = Time.newInstance(hourEnd, minuteEnd, 0, 0);
2988 DateTime dtEnd = DateTime.newInstance(myDateEnd, myTimeEnd);
2989
2990 act.sked__Start__c = dtSt;
2991 act.sked__End__c = dtEnd;
2992
2993 appointments.add(new Appointment(act));
2994 globalDateTime = dtEnd;
2995 act.sked__End__c = auxEndDateTime;
2996 }
2997 }
2998 }
2999 }
3000 return appointments;
3001 }
3002
3003 public static Appointment[] getResourceActivity(Datetime startDateTime,Datetime endDateTime, List<Id> resources) {
3004 Appointment[] appointments = new Appointment[]{};
3005
3006 DateTime startNewDateTime,endNewDateTime,auxStartDateTime,auxEndDateTime,globalDateTime;
3007
3008 for(sked__Activity__c act :[SELECT Id, sked__Address__c, sked__End__c, sked__Location__c, sked__Notes__c, sked__Resource__c,
3009 sked__Start__c, sked__Type__c, sked__Timezone__c, CreatedBy.Name, CreatedDate
3010 FROM sked__Activity__c
3011 WHERE sked__Start__c <= :endDateTime
3012 AND sked__End__c >= :startDateTime
3013 AND sked__Resource__c IN :resources]) {
3014
3015 Datetime uaDateStart = Datetime.newInstance(act.sked__Start__c.year(), act.sked__Start__c.month(), act.sked__Start__c.day());
3016 Datetime uaDateEnd = Datetime.newInstance(act.sked__End__c.year(), act.sked__End__c.month(), act.sked__End__c.day());
3017
3018
3019 if (uaDateStart == uaDateEnd && act.sked__Start__c != act.sked__End__c) {
3020 appointments.add(new Appointment(act));
3021 } else if (uaDateStart < uaDateEnd) {
3022 if (uaDateStart < startDateTime) {
3023 uaDateStart = startDateTime;
3024 }
3025 if (uaDateEnd > endDateTime) {
3026 uaDateEnd = endDateTime;
3027 }
3028 Integer days = uaDateStart.dateGMT().daysBetween(uaDateEnd.dateGMT());
3029 auxStartDateTime = act.sked__Start__c;
3030 auxEndDateTime = act.sked__End__c;
3031 globalDateTime = act.sked__Start__c;
3032
3033 for (Integer i = 0; i <= days; i++) {
3034 if(i == 0) {
3035 endNewDateTime = getResourceLimitDate(act.sked__Start__c, 'end');
3036 act.sked__End__c = endNewDateTime;
3037 appointments.add(new Appointment(act));
3038 act.sked__End__c = auxEndDateTime;
3039 }
3040 else if(i == days) {
3041 startNewDateTime = getResourceLimitDate(act.sked__End__c, 'start');
3042 act.sked__Start__c = startNewDateTime;
3043 appointments.add(new Appointment(act));
3044 }
3045 else {
3046 Integer day = globalDateTime.day() + 1;
3047 Integer hourStart = 8;
3048 Integer minute = 00;
3049 Date myDateStart = Date.newInstance(globalDateTime.year(), globalDateTime.month(), day);
3050 Time myTimeStart = Time.newInstance(hourStart, minute, 0, 0);
3051 DateTime dtSt = DateTime.newInstance(myDateStart, myTimeStart);
3052
3053 Integer hourEnd = 20;
3054 Integer minuteEnd = 00;
3055 Date myDateEnd = Date.newInstance(globalDateTime.year(), globalDateTime.month(), day);
3056 Time myTimeEnd = Time.newInstance(hourEnd, minuteEnd, 0, 0);
3057 DateTime dtEnd = DateTime.newInstance(myDateEnd, myTimeEnd);
3058
3059 act.sked__Start__c = dtSt;
3060 act.sked__End__c = dtEnd;
3061
3062 appointments.add(new Appointment(act));
3063 globalDateTime = dtEnd;
3064 act.sked__End__c = auxEndDateTime;
3065 }
3066 }
3067 }
3068 }
3069 return appointments;
3070 }
3071
3072 @Remoteaction
3073 public static list<SelectOption> getCancellation() {
3074 AppointmentsHelper appointmentsHelper = AppointmentsHelper.getInstance();
3075 return appointmentsHelper.getCancellation();
3076 }
3077
3078 public static String checkIfisPossibleCancel(Id patientId, String appType, String program) {
3079 String errorHeader = 'The appt on date:\n- ', error = '', errorEnd = '\nNeeds to be cancelled first.';
3080 Set<String> checkCodes = new Set<String>();
3081 if (appType == '1A') {
3082 if (program == Constants.PROGRAM_PC) {
3083 checkCodes = Utils.getJobTypesCodesByCategory(new List<String> {Constants.CATEGORY_PC_FA, Constants.CATEGORY_PC_2A});
3084 } else {
3085 checkCodes = Utils.getJobTypesCodesByCategory(new List<String> {Constants.CATEGORY_FA, Constants.CATEGORY_2A, Constants.CATEGORY_LVN_ONLY_2A});
3086 }
3087 } else { //2A
3088 if (program == Constants.PROGRAM_PC) {
3089 checkCodes = Utils.getJobTypesCodesByCategory(new List<String> {Constants.CATEGORY_PC_FA});
3090 } else {
3091 checkCodes = Utils.getJobTypesCodesByCategory(new List<String> {Constants.CATEGORY_FA});
3092 }
3093 }
3094 Integer count = 0;
3095 for (sked__Job__c job :[SELECT sked__Type__c, sked__Start__c, sked__Region__r.sked__TimeZone__c
3096 FROM sked__Job__c
3097 WHERE sked__Account__c = :patientId
3098 AND Type_Code__c IN :checkCodes
3099 AND sked__Parent__c = null
3100 AND sked__Job_Status__c != :Constants.JOB_STATUS_COMPLETE
3101 AND sked__Job_Status__c != :Constants.JOB_STATUS_CANCELLED]) {
3102 if (count != 0) { error += ' \n- '; }
3103 error += job.sked__Start__c.format('MM/dd/yy h:mm a', job.sked__Region__r.sked__TimeZone__c) + '(' + job.sked__Type__c + ') ';
3104 count++;
3105 }
3106 if (error != '') {
3107 return (errorHeader + error + errorEnd);
3108 }
3109 return error;
3110 }
3111
3112 @RemoteAction
3113 public static Boolean validateApptCancelation (String jobID) {
3114 sked__Job__c job = [SELECT Id,
3115 Category__c,
3116 Type_Code__c,
3117 Program_Name__c,
3118 sked__Type__c,
3119 sked__Account__c
3120 FROM sked__Job__c
3121 WHERE Id = :jobID and sked__Job_Status__c != :Constants.JOB_STATUS_CANCELLED];
3122 Set<String> firstAppCodes = new Set<String>();
3123 String program;
3124 if (job.Program_Name__c == Constants.PROGRAM_PC || job.Category__c.contains(Constants.PROGRAM_PC)) {
3125 firstAppCodes = Utils.getJobTypesCodesByCategory(new List<String> {Constants.CATEGORY_PC_1A, Constants.CATEGORY_PC_2A, Constants.CATEGORY_LVN_ONLY_1A, Constants.CATEGORY_LVN_ONLY_PC_1A});
3126 program = Constants.PROGRAM_PC;
3127 } else {
3128 firstAppCodes = Utils.getJobTypesCodesByCategory(new List<String> {Constants.CATEGORY_1A, Constants.CATEGORY_2A, Constants.CATEGORY_LVN_ONLY_1A, Constants.CATEGORY_LVN_ONLY_2A, Constants.CATEGORY_LVN_ONLY_PC_1A});
3129 program = Constants.PROGRAM_HC20;
3130 }
3131 String error = '';
3132 if (firstAppCodes.contains(job.sked__Type__c) || firstAppCodes.contains(job.Type_Code__c)) {
3133 if (job.Type_Code__c.contains('1A')) {
3134 error = checkIfisPossibleCancel(job.sked__Account__c, '1A', program);
3135 } else {
3136 error = checkIfisPossibleCancel(job.sked__Account__c, '2A', program);
3137 }
3138 }
3139 if (error != '') {
3140 throw new AppointmentException(error);
3141 return false;
3142 }
3143 return true;
3144 }
3145
3146 @RemoteAction
3147 public static Boolean cancelAppointment(CancelRequest app) {
3148 sked__Job__c job;
3149 Set<Id> jobIds = new Set<Id>();
3150 Map<Id, sked__Job__c> jobToUpdate = new Map<Id,sked__Job__c>();
3151 Map<Id, QueuableSendAppointmentsToEHR.AppointmentBookingData> sendToEHR = new Map<Id, QueuableSendAppointmentsToEHR.AppointmentBookingData>();
3152 for (sked__Job_Allocation__c a :[SELECT Remote__c,
3153 sked__Job__c,
3154 sked__Job__r.Category__c,
3155 sked__Job__r.Type_Code__c,
3156 sked__Job__r.sked__Start__c,
3157 sked__Job__r.sked__Type__c,
3158 sked__Job__r.sked__Address__c,
3159 sked__Job__r.sked__Account__c,
3160 sked__Job__r.sked__Job_Status__c,
3161 sked__Job__r.sked__Account__r.Name,
3162 sked__Job__r.sked__Account__r.Mobile_Phone__c,
3163 sked__Job__r.sked__Region__r.sked__TimeZone__c,
3164 Distance_To__c,
3165 Distance_From__c,
3166 EHR_Appointment_ID__c,
3167 sked__Resource__r.Name,
3168 sked__Resource__r.EHR__r.Id,
3169 sked__Resource__r.EHR__r.Name,
3170 sked__Estimated_Travel_Time__c,
3171 sked__Resource__r.EHR__r.Token__c,
3172 sked__Resource__r.EHR__r.CallBack__c,
3173 sked__Resource__r.EHR__r.Client_Id__c,
3174 sked__Resource__r.EHR__r.Expiration__c,
3175 sked__Resource__r.EHR__r.EHR_System__c,
3176 sked__Resource__r.EHR__r.Refresh_Token__c,
3177 sked__Resource__r.EHR__r.Client_Secret__c,
3178 sked__Resource__r.EHR__r.Token_Endpoint__c,
3179 sked__Resource__r.EHR__r.Offices_Endpoint__c,
3180 sked__Resource__r.EHR__r.Doctors_Endpoint__c,
3181 sked__Resource__r.EHR__r.Token_Last_Reset__c,
3182 sked__Resource__r.EHR__r.Appointment_Endpoint__c,
3183 sked__Resource__r.RecordType.Name,
3184 sked__Resource__r.sked__Mobile_Phone__c
3185 FROM sked__Job_Allocation__c
3186 WHERE (sked__Job__c = :app.jobID OR sked__Job__r.sked__Parent__c = :app.jobID) AND sked__Job__r.sked__Job_Status__c != :Constants.JOB_STATUS_CANCELLED
3187 ORDER BY sked__Job__r.sked__Start__c]) {
3188 job = a.sked__Job__r;
3189 if (a.EHR_Appointment_ID__c != null) {
3190 sendToEHR.put(a.Id, new QueuableSendAppointmentsToEHR.AppointmentBookingData(
3191 a.EHR_Appointment_ID__c,
3192 a.Id,
3193 a.sked__Resource__r,
3194 true
3195 ));
3196 }
3197 Date dtStart = job.sked__Start__c.date();
3198 if(dtStart.isSameDay(Date.today())) {
3199 Datetime dt = a.sked__Job__r.sked__Start__c;
3200 String timeApp = dt.format('h:mm a', job.sked__Region__r.sked__TimeZone__c);
3201
3202 //Last minute appointment cancellation for <patient name> for @today at <time> at <address>
3203 String txtSMS = 'Last minute appointment cancellation for '
3204 + job.sked__Account__r.Name +' for today at '
3205 + timeApp +' at '
3206 + job.sked__Address__c;
3207 if(a.sked__Resource__r.sked__Mobile_Phone__c != null) {
3208 notifyResourceAsync(a.sked__Resource__r.sked__Mobile_Phone__c, txtSMS, 'sked', 'en');
3209 }
3210 }
3211 job.Cancelled_By__c = UserInfo.getName();
3212 job.Cancelled_By_Date__c = System.now();
3213 job.sked__Job_Status__c = Constants.JOB_STATUS_CANCELLED;
3214 job.sked__Abort_Reason__c = app.primaryReason;
3215 job.Cancellation_Notes__c = app.note;
3216
3217 if(!jobToUpdate.containsKey(a.sked__Job__c)) {
3218 jobToUpdate.put(a.sked__Job__c, job);
3219 }
3220 jobIds.add(a.sked__Job__c);
3221
3222 }
3223 if(!sendToEHR.isEmpty()) {
3224 System.enqueueJob(new QueuableSendAppointmentsToEHR(
3225 sendToEHR
3226 ));
3227 }
3228 update jobToUpdate.values();
3229
3230 insert app.getCancellationObjects(jobIds);
3231 notifyCancelAsync(app.jobID);
3232 return true;
3233 }
3234
3235 @future(callout=true)
3236 public static void notifyResourceAsync(String to, String text, String pFrom, String lang) {
3237 if (!Test.isRunningTest()) {
3238 SendSMS.send(to, text, pFrom, lang);
3239 }
3240 }
3241
3242 /*
3243 * dispatchAsync
3244 * Future method to change the job status to Dispached through the Skedulo API asynchronously.
3245 * @ param jobId Id of job to update status = Dispached.
3246 */
3247 @future(callout=true)
3248 public static void dispatchAsync(Id jobId) {
3249 processJob(jobId, Constants.SKEDULO_ACTION_DISPATCH);
3250 }
3251
3252 @future(callout=true)
3253 public static void notifyCancelAsync(Id jobId) {
3254 processJob(jobId, Constants.SKEDULO_ACTION_CANCEL);
3255 }
3256
3257 private static void processJob(Id jobId, String action) {
3258 Map<Id, String> pendingProcessingJobs = new Map<Id, String>();
3259 sked__Job__c[] children = [SELECT Id FROM sked__Job__c WHERE sked__Parent__c = :jobId];
3260
3261 try {
3262 SkeduloDispatchAPI.dispatchJobAction(jobId, action);
3263 }
3264 catch (SkeduloDispatchAPI.SkeduloException e) {
3265 pendingProcessingJobs.put(jobId, action);
3266 }
3267
3268 for (sked__Job__c child : children) {
3269 try {
3270 SkeduloDispatchAPI.dispatchJobAction(child.Id, action);
3271 }
3272 catch (SkeduloDispatchAPI.SkeduloException e) {
3273 pendingProcessingJobs.put(child.Id, action);
3274 }
3275 }
3276
3277 if (!pendingProcessingJobs.isEmpty()) {
3278 //To handle processing errors implement the following function with the list of jobs not notified to schedulo
3279 handleNotYetDispatchedJobs(pendingProcessingJobs);
3280 }
3281 }
3282
3283 /*
3284 * handleNotYetDispatchedJobs
3285 * Create the schedule job that in another moment process the pending jobs to update status in Skedulo.
3286 * @ param pendingProcessingJobsMap Map with Action(Cancel or Dispatch) by Job id.
3287 */
3288 public static void handleNotYetDispatchedJobs(Map<Id, String> pendingProcessingJobsMap) {
3289 Integer delay = Test.IsRunningTest() ? 10 : Constants.SKEDULO_REDISPATCH_DELAY;
3290 Datetime run = Datetime.now().addMinutes(delay);
3291 System.schedule('Schedule pendingProcessingJobs to Skedulo Retry' + System.currentTimeMillis(), '0 ' + run.minute() + ' ' + run.hour() + ' ' + run.day() + ' ' + run.month() + ' ? ' + run.year(),
3292 new SchedulableDispatchPendingJobsToSkedulo(pendingProcessingJobsMap, 1));
3293 }
3294
3295 public static Appointment[] getSFAppointments(Date startDate, Date endDate, List<Id> resources) {
3296 return getSFAppointments(Datetime.newInstance(startDate, Time.newInstance(0, 0, 0, 0)),Datetime.newInstance(endDate, Time.newInstance(23, 59, 59, 999)), resources);
3297 }
3298
3299 // get a a list of all appointments managed by skedulo
3300 public static Appointment[] getSFAppointments(Datetime startDateTime, Datetime endDateTime, List<Id> resources) {
3301 //DateTime startDateTime = DateTime.newInstance(startDate, Time.newInstance(0, 0, 0, 0)),
3302 // endDateTime = DateTime.newInstance(endDate, Time.newInstance(23, 59, 59, 999));
3303
3304 List<sked__Job_Allocation__c> cpJobs = [SELECT EHR_Appointment_ID__c, sked__Estimated_Travel_Time__c, Distance_From__c, Distance_To__c, Remote__c, sked__Status__c,
3305 sked__Resource__c,
3306 sked__Resource__r.Name,
3307 sked__Resource__r.TimeZone__c,
3308 sked__Resource__r.Map_Color__c,
3309 sked__Resource__r.RecordTypeID,
3310 sked__Resource__r.sked__User__c,
3311 sked__Resource__r.Rate_Factor__c,
3312 sked__Resource__r.RecordType.Name,
3313 sked__Resource__r.PrimaryMarket__c,
3314 sked__Resource__r.show_in_calendar__c,
3315 sked__Resource__r.sked__Home_Address__c,
3316 sked__Resource__r.sked__User__r.smallphotourl,
3317 sked__Resource__r.sked__Is_Active__c,
3318 sked__Job__r.Enrollment__r.Program_Name__c,
3319 sked__Job__r.Id,
3320 sked__Job__r.Category__c,
3321 sked__Job__r.Flexible__c,
3322 sked__Job__r.CreatedDate,
3323 sked__Job__r.Type_Code__c,
3324 sked__Job__r.TimeAdded__c,
3325 sked__Job__r.sked__Type__c,
3326 sked__Job__r.Label_Code__c,
3327 sked__Job__r.Interpreter__c,
3328 sked__Job__r.sked__Start__c,
3329 sked__Job__r.CreatedBy.Name,
3330 sked__Job__r.sked__Finish__c,
3331 sked__Job__r.sked__Urgency__c,
3332 sked__Job__r.Eligible_value__c,
3333 sked__Job__r.Post_Discharge__c,
3334 sked__Job__r.sked__Job_Status__c,
3335 sked__Job__r.sked__Notes_Comments__c,
3336 sked__Job__r.Job_confirmation_status__c,
3337 sked__Job__r.Updated_Address__c,
3338 sked__Job__r.sked__Address__c,
3339 sked__Job__r.Snf__c,
3340 sked__Job__r.sked__Account__r.In_SNF__c,
3341 sked__Job__r.sked__Account__r.SNF__c,
3342 sked__Job__r.sked__Account__r.SNF__r.BillingStreet,
3343 sked__Job__r.sked__Account__r.SNF__r.BillingCity,
3344 sked__Job__r.sked__Account__r.SNF__r.BillingCountry,
3345 sked__Job__r.sked__Account__r.SNF__r.BillingState,
3346 sked__Job__r.sked__Account__r.SNF__r.BillingPostalCode,
3347 sked__Job__r.sked__Account__r.SNF__r.Subzone__c,
3348 sked__Job__r.sked__Account__r.Name,
3349 sked__Job__r.sked__Account__r.Phone,
3350 sked__Job__r.sked__Account__r.PRS__c,
3351 sked__Job__r.sked__Account__r.Completed_Appointments__c,
3352 sked__Job__r.sked__Account__r.Cancelled_Appointments__c,
3353 sked__Job__r.sked__Account__r.Color__c,
3354 sked__Job__r.sked__Account__r.Market__c,
3355 sked__Job__r.sked__Account__r.Region__c,
3356 sked__Job__r.sked__Account__r.EHR_MRN__c,
3357 sked__Job__r.sked__Account__r.Subzone__c,
3358 sked__Job__r.sked__Account__r.MarketId__c,
3359 sked__Job__r.sked__Account__r.Language__c,
3360 sked__Job__r.sked__Account__r.TimeZone__c,
3361 sked__Job__r.sked__Account__r.BillingCity,
3362 sked__Job__r.sked__Account__r.BillingState,
3363 sked__Job__r.sked__Account__r.BillingStreet,
3364 sked__Job__r.sked__Account__r.Color_Dark__c,
3365 sked__Job__r.sked__Account__r.BillingCountry,
3366 sked__Job__r.sked__Account__r.Touch_Count__c,
3367 sked__Job__r.sked__Account__r.RecordType.Name,
3368 sked__Job__r.sked__Account__r.Lead_Suborigin__c,
3369 sked__Job__r.sked__Account__r.BillingPostalCode,
3370 sked__Job__r.sked__Account__r.Patient_Unique_ID__c,
3371 sked__Job__r.sked__Account__r.Insurance_Plan_Name__c,
3372 sked__Job__r.sked__Account__r.Stratification_Level__c,
3373 sked__Job__r.sked__Account__r.Pro_TOC_latest_status__c,
3374 sked__Job__r.sked__Account__r.Pro_TOC_latest_substatus__c,
3375 sked__Job__r.sked__Account__r.Parent.Name,
3376 sked__Job__r.sked__Account__r.Parent.SNF_Enabled__c,
3377 sked__Job__r.sked__Account__r.Parent.BillingStreet,
3378 sked__Job__r.sked__Account__r.Parent.BillingCity,
3379 sked__Job__r.sked__Account__r.Parent.BillingCountry,
3380 sked__Job__r.sked__Account__r.Parent.BillingState,
3381 sked__Job__r.sked__Account__r.Parent.BillingPostalCode,
3382 sked__Job__r.sked__Account__r.Days_since_last_F2F__c,
3383 sked__Job__r.sked__Account__r.Days_since_appointment_with_a_CCP__c,
3384 sked__Job__r.sked__Account__r.Safety_Grade__c
3385 FROM sked__Job_Allocation__c
3386 WHERE sked__Job__r.sked__Start__c >= :startDateTime
3387 AND sked__Job__r.sked__Start__c < :endDateTime
3388 AND sked__Resource__c IN :resources
3389 AND sked__Job__r.sked__Job_Status__c != :Constants.JOB_STATUS_CANCELLED
3390 ORDER BY sked__Job__r.sked__Start__c];
3391
3392 Map<Id, List<sked__Job_Allocation__c>> allocationsByJob = getAllocationMap(cpJobs);
3393 Map<Id, Map<string,String>> mapMarketColorsByLVNResource = getMarketColorsByLVNResource( new Set<Id>(resources));
3394 Appointment[] appointments = new Appointment[]{};
3395
3396 for (sked__Job_Allocation__c jobA : cpJobs) {
3397 Appointment app = new Appointment(jobA, allocationsByJob.get(jobA.sked__Job__r.Id));
3398 for(Integer i = 0; i < app.resources.size(); i++) {
3399 Resource re = app.resources[i];
3400 if (re.type == Constants.LVN_TYPE
3401 && re.primaryMarketId != app.patient.account.MarketId__c
3402 && !mapMarketColorsByLVNResource.isEmpty()
3403 && mapMarketColorsByLVNResource.containsKey(re.Id)
3404 && mapMarketColorsByLVNResource.get(re.Id).containsKey(app.patient.account.MarketId__c)) {
3405 re.routeColor = mapMarketColorsByLVNResource.get(re.Id).get(app.patient.account.MarketId__c);
3406 }
3407 app.resources[i] = re;
3408 }
3409
3410 if (app.resource.type == Constants.LVN_TYPE
3411 && app.resource.primaryMarketId != app.patient.account.MarketId__c
3412 && !mapMarketColorsByLVNResource.isEmpty()
3413 && mapMarketColorsByLVNResource.containsKey(app.resource.Id)
3414 && mapMarketColorsByLVNResource.get(app.resource.Id).containsKey(app.patient.account.MarketId__c)) {
3415 app.resource.routeColor = mapMarketColorsByLVNResource.get(app.resource.Id).get(app.patient.account.MarketId__c);
3416 }
3417 app.address = jobA.sked__Job__r.sked__Address__c;
3418 app.Patient.hco = jobA.sked__Job__r.sked__Account__r.Parent.Name;
3419 app.Patient.daysSinceLastF2F = Integer.valueOf(jobA.sked__Job__r.sked__Account__r.Days_since_last_F2F__c);
3420 app.Patient.daysSinceLastCCPAppointment = Integer.valueOf(jobA.sked__Job__r.sked__Account__r.Days_since_appointment_with_a_CCP__c);
3421 app.Patient.safetyGrade = jobA.sked__Job__r.sked__Account__r.Safety_Grade__c;
3422 app.confirmationStatus = jobA.sked__Job__r.Job_confirmation_status__c;
3423 app.jobEligibility = jobA.sked__Job__r.Eligible_value__c;
3424 app.jobStatus = jobA.sked__Job__r.sked__Job_Status__c;
3425 setSNFData(app, jobA.sked__Job__r);
3426 appointments.add(app);
3427 }
3428 return appointments;
3429 }
3430
3431 public static Appointment getDoctorAppt(AbstractFW.chktimeResponseAppointments appt, sked__Resource__c res) {
3432 sked__Job_Allocation__c cpjob;
3433 Appointment ret;
3434 List<sked__Job_Allocation__c> cpJobs = [SELECT EHR_Appointment_ID__c, sked__Estimated_Travel_Time__c, Distance_From__c, Distance_To__c, Remote__c, sked__Status__c,
3435 sked__Resource__c,
3436 sked__Resource__r.Name,
3437 sked__Resource__r.RecordTypeID,
3438 sked__Resource__r.Map_Color__c,
3439 sked__Resource__r.sked__Home_Address__c,
3440 sked__Job__r.CreatedDate,
3441 sked__Job__r.Flexible__c,
3442 sked__Job__r.Category__c,
3443 sked__Job__r.Type_Code__c,
3444 sked__Job__r.TimeAdded__c,
3445 sked__Job__r.sked__Type__c,
3446 sked__Job__r.Label_Code__c,
3447 sked__Job__r.Interpreter__c,
3448 sked__Job__r.sked__Start__c,
3449 sked__Job__r.CreatedBy.Name,
3450 sked__Job__r.sked__Finish__c,
3451 sked__Job__r.sked__Urgency__c,
3452 sked__Job__r.Updated_Address__c,
3453 sked__Job__r.sked__Account__r.Name,
3454 sked__Job__r.sked__Account__r.PRS__c,
3455 sked__Job__r.sked__Account__r.Completed_Appointments__c,
3456 sked__Job__r.sked__Account__r.Cancelled_Appointments__c,
3457 sked__Job__r.sked__Account__r.Color__c,
3458 sked__Job__r.sked__Account__r.Region__c,
3459 sked__Job__r.sked__Account__r.Subzone__c,
3460 sked__Job__r.sked__Account__r.EHR_MRN__c,
3461 sked__Job__r.sked__Account__r.BillingCity,
3462 sked__Job__r.sked__Account__r.Language__c,
3463 sked__Job__r.sked__Account__r.BillingState,
3464 sked__Job__r.sked__Account__r.Color_Dark__c,
3465 sked__Job__r.sked__Account__r.BillingStreet,
3466 sked__Job__r.sked__Account__r.BillingCountry,
3467 sked__Job__r.sked__Account__r.Touch_Count__c,
3468 sked__Job__r.sked__Account__r.RecordType.Name,
3469 sked__Job__r.sked__Account__r.Lead_Suborigin__c,
3470 sked__Job__r.sked__Account__r.BillingPostalCode,
3471 sked__Job__r.sked__Account__r.Stratification_Level__c,
3472 sked__Job__r.sked__Account__r.Pro_TOC_latest_status__c,
3473 sked__Job__r.sked__Account__r.Pro_TOC_latest_substatus__c,
3474 sked__Job__r.Snf__c,
3475 sked__Job__r.sked__Account__r.In_SNF__c,
3476 sked__Job__r.sked__Account__r.Parent.Name,
3477 sked__Job__r.sked__Account__r.Parent.SNF_Enabled__c,
3478 sked__Job__r.sked__Account__r.SNF__c,
3479 sked__Job__r.sked__Account__r.SNF__r.BillingStreet,
3480 sked__Job__r.sked__Account__r.SNF__r.BillingCity,
3481 sked__Job__r.sked__Account__r.SNF__r.BillingCountry,
3482 sked__Job__r.sked__Account__r.SNF__r.BillingState,
3483 sked__Job__r.sked__Account__r.SNF__r.BillingPostalCode,
3484 sked__Job__r.sked__Account__r.SNF__r.Subzone__c
3485 FROM sked__Job_Allocation__c
3486 WHERE EHR_Appointment_ID__c = :appt.Id
3487 AND sked__Job__r.sked__Job_Status__c != :Constants.JOB_STATUS_CANCELLED
3488 ORDER BY sked__Job__r.sked__Start__c];
3489 if(cpJobs.size() > 0){
3490 cpjob = cpJobs[0];
3491 ret = new Appointment(appt,res,cpjob);
3492 ret.address = cpjob.sked__Job__r.sked__Address__c;
3493 setSNFData(ret, cpjob.sked__Job__r);
3494 }
3495 return ret;
3496 }
3497
3498 public static Appointment[] getDocApptCanel(Date startDate, Date endDate, List<Id> docs) {
3499
3500 // look for dr Id (in dr. chrono), office's id and EHR id
3501 sked__Resource__c[] doctors = [SELECT Id, Doctor_ID__c, Name, Doctor_s_Practice_Account__r.EHR_Office_Id__c, RecordType.Name, Sked__Home_Address__c,
3502 TimeZone__c, Rate_Factor__c, PrimaryMarket__c, EHR__r.Id, EHR__r.Name, EHR__r.Client_Id__c, EHR__r.Client_Secret__c,
3503 EHR__r.CallBack__c, EHR__r.Token__c, EHR__r.Refresh_Token__c, EHR__r.Token_Last_Reset__c, EHR__r.Expiration__c, EHR__r.Token_Endpoint__c,
3504 EHR__r.Offices_Endpoint__c, EHR__r.Doctors_Endpoint__c, EHR__r.Appointment_Endpoint__c, Map_Color__c
3505 FROM sked__Resource__c
3506 WHERE RecordType.Name = 'LVN'
3507 AND Id in :docs];
3508
3509 Appointment[] appointments = new Appointment[]{};
3510
3511 for (sked__Resource__c res : doctors) {
3512 AbstractFW.chktimeResponseAppointments[] responses = CheckTime.CheckTime(startDate, endDate, res.Doctor_ID__c,
3513 res.Doctor_s_Practice_Account__r.EHR_Office_Id__c, res.EHR__r).appointments;
3514
3515 for (AbstractFW.chktimeResponseAppointments respApp : responses) {
3516 Appointment apt = getDoctorAppt(respApp,res);
3517 if (apt != null) {
3518 String tzId = res.TimeZone__c == null ? Constants.DEFAULT_TIME_ZONE:res.TimeZone__c;
3519 TimeZone tz = Timezone.getTimeZone(tzId);
3520 Integer offset = tz.getOffset(apt.startTimeAsDate);
3521 apt.setOffset(-offset / 60000);
3522 appointments.add(apt);
3523 }
3524 }
3525 }
3526 return appointments;
3527 }
3528
3529 public static void sendNotificationPatient(Appointment app) {
3530 try {
3531 sked__Job__c job = [SELECT Id, sked__Start__c, Name, sked__Job_Status__c, sked__Type__c, Type_Code__c,
3532 (SELECT sked__Start__c FROM sked__Followups__r ORDER BY sked__Start__c ASC),
3533 sked__Account__r.Id,
3534 sked__Account__r.EHR_MRN__c,
3535 sked__Account__r.Language__c,
3536 sked__Account__r.RecordType.Name,
3537 sked__Account__r.Mobile_Phone__c,
3538 sked__Account__r.Authorize_to_receive_SMS__c
3539 FROM sked__Job__c
3540 WHERE Id = :app.jobID
3541 LIMIT 1];
3542 Set<String> excludedTypes = Utils.getSMSExcludedTypes();
3543 if (job.sked__Account__r.Authorize_to_receive_SMS__c == 'YES'
3544 && job.sked__Account__r.Mobile_Phone__c != null
3545 && job.sked__Start__c.hour() < 18
3546 && (!excludedTypes.contains(job.Type_Code__c) && !excludedTypes.contains(job.sked__Type__c))) { //to avoid appt on deck
3547 DateTime start = job.sked__Followups__r.isEmpty()
3548 || job.sked__Start__c < job.sked__Followups__r[0].sked__Start__c?
3549 job.sked__Start__c : job.sked__Followups__r[0].sked__Start__c;
3550 DateTime startGMT = DateTime.newInstanceGmt(start.year(), start.month(), start.day(), start.hour()
3551 , start.minute(), start.second());
3552 // set new start to job just for this method
3553 job.sked__Start__c = start;
3554 Set<String> resourceNames = new Set<String>();
3555 for (Resource res :app.resources) {
3556 resourceNames.add(res.contact.Name);
3557 }
3558 String confirmationText = SendSMS.buildTextSMS(Constants.SMS_TYPE_CONF, resourceNames, startGMT, job.sked__Account__r.Language__c);
3559 Patients.createPatientTouchList(new List<sked__Job__c>{job});
3560 sendSMSinFuture(job.sked__Account__r.Mobile_Phone__c, confirmationText);
3561 }
3562 } catch (Exception e) {
3563 Email.sendException('Appointments', e);
3564 }
3565 }
3566
3567 public static void notificationsResourceIn(Appointment app) {
3568 try {
3569 String patientName = app.patient.Name;
3570 Map<Id, List<sked__Job_Allocation__c>> jobAllocation = new Map<Id, List<sked__Job_Allocation__c>>();
3571 Set<Id> setAllJob = new Set<Id>();
3572 for(sked__Job__c job : [SELECT Id FROM sked__Job__c WHERE sked__Parent__c = :app.jobID]) {
3573 setAllJob.add(job.Id);
3574 }
3575 setAllJob.add(app.jobID);
3576
3577 //get map<Job Ids ,<List sked__Job_Allocation__c >>
3578 for(sked__Job_Allocation__c jobAll :[SELECT Id, sked__Resource__r.Name, sked__Resource__r.sked__Mobile_Phone__c, sked__Job__r.sked__Start__c, sked__Job__c FROM sked__Job_Allocation__c WHERE sked__Job__c IN :setAllJob]) {
3579 if(!jobAllocation.containsKey(jobAll.sked__Job__c)) {
3580 jobAllocation.put(jobAll.sked__Job__c, new List<sked__Job_Allocation__c>());
3581 }
3582 jobAllocation.get(jobAll.sked__Job__c).add(jobAll);
3583 }
3584
3585 Map<Id, sked__Job__c> mapJob = new Map<Id, sked__Job__c>([SELECT Id, sked__Region__r.sked__TimeZone__c FROM sked__Job__c WHERE Id IN :setAllJob]);
3586
3587 for(Id jId :mapJob.keySet() ) {
3588 for(sked__Job_Allocation__c jAll :jobAllocation.get(jId)) {
3589 if(jAll.sked__Resource__r.sked__Mobile_Phone__c != null) {
3590 Datetime dt = jAll.sked__Job__r.sked__Start__c;
3591 String timeApp = dt.format('h:mm a', mapJob.get(jId).sked__Region__r.sked__TimeZone__c);
3592 String ddd = dt.format('EEE, d-MMM-yyyy');
3593
3594 //Last minute appointment schedule for <patient name> for @today at <time> at <address>
3595 String txtSMS = 'Last minute appointment schedule for '
3596 + patientName +' for today at '
3597 + timeApp +' at '
3598 + Patients.clearAddresStreet(app.patient.AddressLine);
3599 sendSMSinFuture(jAll.sked__Resource__r.sked__Mobile_Phone__c, txtSMS);
3600 }
3601 }
3602 }
3603 } catch (Exception e) {
3604 Email.sendException('Resource notification', e);
3605 }
3606 }
3607
3608 public static void notificationsResourceUp(ResourceNotification re, String typeNotification, String pName, String pAddress) {
3609 if(re.mobilePhone != null) {
3610 String txtSMS;
3611 if(typeNotification == 'inToday') { // created today
3612
3613 //Last minute appointment schedule for <patient name> for @today at <time> at <address>
3614 txtSMS = 'Last minute appointment schedule for '
3615 + pName +' for today at '
3616 + re.appTime +' at '
3617 + Patients.clearAddresStreet(pAddress);
3618
3619 } else if(typeNotification == 'upToday') { // updated today
3620
3621 //Today's appointment at <old date and time> with <patient name> has been re-schedule to <day of the week> <new date and time>
3622 txtSMS = 'Today\'s appointment at '
3623 + re.appTimeOld + ' with '
3624 + pName +' has been re-schedule to '
3625 + re.appDate + ' at ' + re.appTime;
3626
3627 txtSMS = String.escapeSingleQuotes(txtSMS); //To escape the apostrophe in Today's
3628
3629 } else if(typeNotification == 'upTodayTime') {// updated today to other time
3630
3631 //Today's appointment with <patient name> has been re-scheduled to <time>
3632 txtSMS = 'Today\'s appointment at '
3633 + re.appTimeOld + ' with '
3634 + pName +' has been re-schedule to '
3635 + re.appTime;
3636 txtSMS = String.escapeSingleQuotes(txtSMS); //To escape the apostrophe in Today's
3637 }
3638 sendSMSinFuture(re.mobilePhone, txtSMS);
3639 }
3640 }
3641
3642 // Assignments
3643 @TestVisible
3644 private static Map<Id, List<Assignment>> getAssignments(Date startDate, Date endDate, List<Id> rIds) {
3645 //Cleaning the dates.
3646 startDate = Date.newInstance(startDate.year(), startDate.month(), startDate.day());
3647 endDate = Date.newInstance(endDate.year(), endDate.month(), endDate.day());
3648
3649 Map<Id, List<Assignment>> assignments = new Map<Id, List<Assignment>>();
3650 Map<Assignment__c, List<Assigned_Subzone__c>> assigs = new Map<Assignment__c, List<Assigned_Subzone__c>>();
3651 Map<Id, Assignment__c> aIds = new Map<Id, Assignment__c>();
3652 for (Assignment__c a : [SELECT id, Name, Date__c, Fixed_Text__c, Resource__c FROM Assignment__c
3653 WHERE Resource__c IN :rIds
3654 AND Date__c >= :startDate AND Date__c <= :endDate]) {
3655 aIds.put(a.Id, a);
3656 assigs.put(a, new List<Assigned_Subzone__c>());
3657 }
3658
3659 for (Assigned_Subzone__c asz : [SELECT Id, Name, Label__c, Assignment__c, Subzone__c, Subzone__r.Name, Secondary__c, Subzone__r.Zone__r.Color_1__c
3660 FROM Assigned_Subzone__c
3661 WHERE Assignment__c IN :aIds.keySet()
3662 ORDER BY Secondary__c]) {
3663 Assignment__c a = aIds.get(asz.Assignment__c);
3664 assigs.get(a).add(asz);
3665 }
3666
3667 for (Assignment__c a :assigs.keySet()) {
3668 Assignment assign = new Assignment(a);
3669 List<String> primary = new List<String>();
3670 List<String> secondary = new List<String>();
3671 for (Assigned_Subzone__c az :assigs.get(a)) {
3672 assign.color = String.isEmpty(assign.color) ? az.Subzone__r.Zone__r.Color_1__c : assign.color;
3673 if (az.Subzone__c != null) {
3674 assign.assSubZones.add(az.Subzone__r.Name);
3675 }
3676 if(String.isEmpty(assign.plan)){
3677 if (az.Secondary__c) {
3678 secondary.add(az.Label__c);
3679 } else {
3680 primary.add(az.Label__c);
3681 }
3682 }
3683 }
3684 if(!primary.isEmpty() || !secondary.isEmpty()) {
3685 assign.plan = secondary.isEmpty() ? String.join(primary, ',') : String.join(primary, ',') + '/' + String.join(secondary, ',');
3686 }
3687 if (!assignments.containsKey(assign.resourceId)) {
3688 assignments.put(assign.resourceId, new List<Assignment>());
3689 }
3690 assignments.get(assign.resourceId).add(assign);
3691 }
3692 return assignments;
3693 }
3694
3695 public static Set<String> get1ATypes() {
3696 AppointmentsHelper appointmentsHelper = AppointmentsHelper.getInstance();
3697 return appointmentsHelper.get1ATypes();
3698 }
3699
3700 private static Boolean isEnrolledinProgram(sked__Job__c job, String programName) {
3701 ProgramHelper programs = new ProgramHelper();
3702 return programs.getJobsProgram(job).name == programName;
3703 }
3704
3705 private static SlotLetter getAppTypeLetter(sked__Job__c job, String jobType) {
3706 SlotLetter result;
3707 String green = '#62bd17';
3708 String red = '#ea401e';
3709 String blue = '#1a75ff';
3710 String orange = '#ff9933';
3711 String defaultColor = '#660000';
3712
3713 /**
3714 if in a calendar view there are more than 149 appts scheduled of type:
3715 - SW + LVN,
3716 - LVN_Only,
3717 - Phone visit
3718 - CCP only,
3719 the use of method isEnrolledinProgram(patientId, 'Program name') can hit the Salesforce governors Limits.
3720 But for now there is no other way to know what program the appt belongs to, keep this in mind in the future
3721 */
3722
3723 if (jobType.contains(Constants.CATEGORY_HRA)) {
3724 result = new SlotLetter('H', defaultColor);
3725 } else if (jobType.contains(Constants.CATEGORY_AWE)) {
3726 result = new SlotLetter('A', defaultColor);
3727 } else if (jobType.contains(Constants.CATEGORY_SC) || jobType.contains(Constants.CATEGORY_SC_FULL)) {
3728 result = new SlotLetter('S', defaultColor);
3729 } else if (jobType.contains(Constants.CATEGORY_OUTREACH)) {
3730 result = new SlotLetter('O', defaultColor);
3731 } else if (jobType.contains('BH')) {
3732 result = new SlotLetter('B', defaultColor);
3733 } else if (jobType.contains(Constants.SW_TYPE) && jobType.contains(Constants.LVN_TYPE)) {
3734 if (isEnrolledinProgram(job,Constants.PROGRAM_PC)) {
3735 //SW + LVN in PC program
3736 result = new SlotLetter('W', blue);
3737 } else {
3738 //SW + LVN in HC20 program
3739 result = new SlotLetter('W', defaultColor);
3740 }
3741 } else if (jobType.contains(Constants.PROGRAM_PC)
3742 && jobType.contains('CH')
3743 && jobType.contains(Constants.LVN_TYPE)) {
3744 result = new SlotLetter('C', blue);
3745 } else if (jobType.contains(Constants.JOBTYPE_NAME_LVN_ONLY)
3746 && !jobType.contains('1A')) {
3747 if (isEnrolledinProgram(job,Constants.PROGRAM_PC)) {
3748 //LVN_only in PC program
3749 result = new SlotLetter('L', blue);
3750 } else if (jobType.contains('2A')) {
3751 //LVN_only_HC20_2A
3752 result = new SlotLetter('2', green);
3753 } else {
3754 //LVN_only in HC20 program
3755 result = new SlotLetter('L', defaultColor);
3756 }
3757 } else if (jobType.contains(Constants.JOBTYPE_NAME_PHONE_VISIT)) {
3758 if (isEnrolledinProgram(job,Constants.PROGRAM_PC)) {
3759 result = new SlotLetter('P', blue);
3760 } else {
3761 result = new SlotLetter('P', defaultColor);
3762 }
3763 } else if (jobType.contains(Constants.JOBTYPE_NAME_CCP_ONLY)) {
3764 if (isEnrolledinProgram(job,Constants.PROGRAM_PC)) {
3765 result = new SlotLetter('C', blue);
3766 } else {
3767 result = new SlotLetter('C', defaultColor);
3768 }
3769 } else if (jobType.contains('1A')) {
3770 if (jobType.contains(Constants.JOBTYPE_NAME_LVN_ONLY)) {
3771 if (jobType.contains(Constants.PROGRAM_PC)) {
3772 //LVN_only_PC_1A
3773 result = new SlotLetter('L', orange);
3774 } else {
3775 // LVN_only_1A
3776 result = new SlotLetter('L', red);
3777 }
3778 }
3779 else if (jobType.contains(Constants.PROGRAM_PC)) {
3780 //HC20_PC_1A
3781 result = new SlotLetter('1', blue);
3782 }
3783 else if (jobType.contains(Constants.PROGRAM_HC20)) {
3784 //HC20_1A
3785 result = new SlotLetter('1', defaultColor);
3786 }
3787 } else if (jobType.contains('FA') || jobType.contains('2A')) {
3788 if (jobType.contains('2A') && !jobType.contains(Constants.PROGRAM_PC)) {
3789 //HC20_2A
3790 result = new SlotLetter('2', red);
3791 }
3792 else if (jobType.contains('FA') && jobType.contains(Constants.PROGRAM_PC)) {
3793 //HC20_PC_FA
3794 result = new SlotLetter('2', blue);
3795 }
3796 else if (jobType.contains('2A') && jobType.contains(Constants.PROGRAM_PC)) {
3797 //'HC20_PC_2A
3798 result = new SlotLetter('2', orange);
3799 }
3800 else {
3801 result = new SlotLetter('2', defaultColor);
3802 }
3803 }
3804 return result;
3805 }
3806
3807 @future(callout=true)
3808 public static void sendSMSinFuture(String to, String txt) {
3809 if (!Test.isRunningTest()) {
3810 SendSMS.send(to, txt, 'sked', 'en');
3811 }
3812 }
3813
3814 global class AppointmentType implements Comparable {
3815 public String order;
3816 public Boolean remote;
3817 public List<Stage> stages;
3818 public Integer ehrProfile;
3819 public String categoryCode;
3820 public Integer interpreter;
3821 public String type {get;set;}
3822 public String code {get;set;}
3823 public String name {get;set;}
3824 public String label {get;set;}
3825 public String category {get;set;}
3826 public Integer totalDuration {get;set;}
3827 public List<Resource> resources {get;set;}
3828 public Map<String, Integer> requiredTypes;
3829 public Boolean snfEnabled;
3830 public Boolean disable;
3831
3832 public AppointmentType(Job_Type__mdt t, List<Job_Type_Allocation__mdt> allocations) {
3833 this.disable = t.Disabled__c;
3834 this.name = t.Name__c;
3835 this.type = t.Type__c;
3836 this.label = t.Label;
3837 this.category = t.Category__r.Label;
3838 this.categoryCode = t.Category__r.DeveloperName;
3839 this.code = t.DeveloperName;
3840 this.order = t.Order__c;
3841 this.totalDuration = 0;
3842 this.ehrProfile = Integer.valueOf(t.EHR_Profile__c);
3843 this.stages = new List<Stage>();
3844 this.resources = new List<Resource>();
3845 this.requiredTypes = new Map<String, Integer>();
3846 this.remote = false;
3847 this.interpreter = t.Interpreter__c == null ? 0 : t.Interpreter__c.intValue();
3848 // Keep track of the current order to know if allocations are simultaneous or not
3849 Integer lastIndex = 0, currentIndex, stageDuration, offset = 0, resourceIndex;
3850 String lastResource;
3851 Map<String, Map<Integer, Resource>> resourceMap = new Map<String, Map<Integer, Resource>>();
3852 Map<Integer, Resource> innerResourceMap;
3853 Resource r = null;
3854 Stage currentStage;
3855 if (!(allocations == null)) {
3856 for (Job_Type_Allocation__mdt a : allocations) {
3857 this.remote = this.remote || a.Remote__c;
3858 currentIndex = Integer.valueOf(a.Order__c);
3859 if (currentIndex > lastIndex) {
3860 // different stage
3861 lastIndex = currentIndex;
3862 resourceIndex = 0;
3863 stageDuration = Integer.valueOf(a.Duration__c);
3864 offset = this.totalDuration;
3865 this.totalDuration += stageDuration;
3866 currentStage = new Stage(a.Stage_Name__c, stageDuration, a.Resource_Types__c);
3867 currentStage.remote = a.Remote__c;
3868 this.stages.add(currentStage);
3869 } else {
3870 // same stage, another resource
3871 if (a.Resource_Types__c == lastResource) {
3872 // same stage + same resource type => two resources of the same type
3873 resourceIndex++;
3874 }
3875 currentStage.addResource(a.Resource_Types__c);
3876 }
3877 lastResource = a.Resource_Types__c;
3878 if (!resourceMap.containsKey(a.Resource_Types__c)) {
3879 // Add inner map
3880 resourceMap.put(a.Resource_Types__c, new Map<Integer, Resource>());
3881 }
3882 innerResourceMap = resourceMap.get(a.Resource_Types__c);
3883 if (innerResourceMap.containsKey(resourceIndex)) {
3884 // Resource was already in. Add additional time
3885 r = innerResourceMap.get(resourceIndex);
3886 r.duration = this.totalDuration;
3887 } else {
3888 // Resource not yet required. Add it
3889 r = new Resource(a, offset);
3890 innerResourceMap.put(resourceIndex, r);
3891 this.resources.add(r);
3892 // check for required type
3893 if (this.requiredTypes.containsKey(r.type)) {
3894 this.requiredTypes.put(r.type, this.requiredTypes.get(r.type) + 1);
3895 } else {
3896 this.requiredTypes.put(r.type, 1);
3897 }
3898 }
3899 }
3900 }
3901
3902 // Set required resources and resource allocation list
3903 this.resources.sort();
3904 }
3905
3906 global Integer compareTo(Object t) {
3907 AppointmentType other = (AppointmentType)t;
3908 Integer order = Integer.valueOf(this.order);
3909 Integer otherOrder = Integer.valueOf(other.order);
3910 return order - otherOrder;
3911 }
3912
3913 public AppointmentType() {}
3914 }
3915
3916 public class Stage {
3917 public String name;
3918 public Boolean remote;
3919 public Integer duration;
3920 public Map<String, Integer> resources;
3921
3922 public Stage(String n, Integer d, String r) {
3923 this.name = n;
3924 this.duration = d;
3925 this.resources = new Map<String, Integer>();
3926 this.resources.put(r, 1);
3927 }
3928
3929 public void addResource(String r) {
3930 if (this.resources.containsKey(r)) {
3931 this.resources.put(r, this.resources.get(r) + 1);
3932 } else {
3933 this.resources.put(r, 1);
3934 }
3935 }
3936 }
3937
3938 public class Appointment implements Comparable {
3939 // this should be used when events are PULLING from the client (i.e. calling to 'getAppointments')
3940 public Resource resource { get; set; }
3941 public Id jobID {get;set;}
3942 public String aId {get;set;}
3943 public Boolean editable { get; set; }
3944 public DateTime startTimeAsDate { get; set; }
3945 public DateTime endTimeAsDate { get; set; }
3946 public DateTime actualStartTimeAsDate { get; set; }
3947 public DateTime actualEndTimeAsDate { get; set; }
3948
3949 public String startDate { get ; set; }
3950 public String startTime { get ; set; }
3951 public String endTime { get ; set; }
3952 public String actualStartTime { get ; set; }
3953 public String actualEndTime { get ; set; }
3954 public String timezone {get; set;}
3955 public String notes {get; set;}
3956 public String jobStatus {get; set;}
3957
3958 public Decimal estimatedTravelTime {get;set;}
3959 public Decimal distanceFrom {get;set;}
3960 public Decimal distanceTo {get;set;}
3961
3962 public AppointmentType type { get; set; }
3963 public String createdByUser { get; set; }
3964 public DateTime createdByDate { get; set; }
3965
3966 public Patient patient { get; set; }
3967 // this is used for PUSHING event data to the server. for example, in 'createAppointment'
3968 // also it is used in 'newAppointment'
3969 public Resource[] resources { get; set; }
3970 public Resource[] allResources { get; set; }
3971 public String program { get; set; }
3972 // useful for editing appointments
3973 public Appointment[] children { get; set; }
3974
3975 //unvailability
3976 public Boolean availableResource { get; set; }
3977 public String unvailabilityType { get; set; }
3978 public String unvailabilityNotes { get; set; }
3979
3980 //activity
3981 public String activityId { get; set; }
3982 public String activityType { get; set; }
3983 public String activityAddress { get; set; }
3984 public String activityNotes { get; set; }
3985 public String reschedulingReason {get; set;}
3986 public String phoneVisitType {get; set;}
3987
3988 public Boolean updatedAddress {get;set;}
3989 public String address {get; set;}
3990 public List<Assignment> assignments {get;set;}
3991 public String confirmationStatus {get; set;}
3992 public String jobEligibility {get; set;}
3993
3994 //ufi
3995 public Boolean urgent { get; set; }
3996 public Boolean flexible {get;set;}
3997 public Boolean interpreter {get;set;}
3998 public Boolean timeadded {get;set;}
3999 public Boolean postDischarge {get;set;}
4000
4001 public Boolean snf {get; set;}
4002 public Boolean highRisk {get; set;}
4003
4004 public SlotLetter appTypeLetter {get;set;}
4005
4006 public void setOffset(Integer offsetInMinutes) {
4007 startTimeAsDate = startTimeAsDate.addMinutes(offsetInMinutes);
4008 endTimeAsDate = endTimeAsDate.addMinutes(offsetInMinutes);
4009 if (actualStartTimeAsDate != null && actualEndTimeAsDate != null) {
4010 actualStartTimeAsDate = actualStartTimeAsDate.addMinutes(offsetInMinutes);
4011 actualEndTimeAsDate = actualEndTimeAsDate.addMinutes(offsetInMinutes);
4012 this.actualStartTime = this.actualStartTimeAsDate.formatGmt('yyyy/MM/dd HH:mm');
4013 this.actualEndTime = this.actualEndTimeAsDate.formatGmt('yyyy/MM/dd HH:mm');
4014 }
4015 this.startDate = this.startTimeAsDate.formatGmt('yyyy-M-d');
4016 this.startTime = this.startTimeAsDate.formatGmt('yyyy/MM/dd HH:mm');
4017 this.endTime = this.endTimeAsDate.formatGmt('yyyy/MM/dd HH:mm');
4018 }
4019
4020 public void applyTimezone() {
4021 this.startDate = this.startTimeAsDate.format('yyyy-M-d', timezone);
4022 this.startTime = this.startTimeAsDate.format('yyyy/MM/dd HH:mm', timezone);
4023 this.endTime = this.endTimeAsDate.format('yyyy/MM/dd HH:mm', timezone);
4024 if (actualStartTimeAsDate != null && actualEndTimeAsDate != null) {
4025 this.actualStartTime = this.actualStartTimeAsDate.format('yyyy/MM/dd HH:mm', timezone);
4026 this.actualEndTime = this.actualEndTimeAsDate.format('yyyy/MM/dd HH:mm', timezone);
4027 }
4028 }
4029
4030 public Appointment() {}
4031
4032 public Appointment(sked__Availability__c unvailability) {
4033 this.startTimeAsDate = unvailability.sked__Start__c;
4034 this.endTimeAsDate = unvailability.sked__Finish__c;
4035 this.resource = new Resource();
4036 this.resource.Id = unvailability.sked__Resource__c;
4037 this.availableResource = unvailability.sked__Is_Available__c;
4038 this.unvailabilityType = unvailability.sked__Type__c;
4039 this.unvailabilityNotes = unvailability.sked__Notes__c;
4040 this.editable = false;
4041 this.createdByUser = unvailability.CreatedBy.Name;
4042 this.createdByDate = unvailability.CreatedDate;
4043 }
4044
4045 public Appointment(sked__Activity__c act) {
4046 this.startTimeAsDate = act.sked__Start__c;
4047 this.endTimeAsDate = act.sked__End__c;
4048 this.resource = new Resource();
4049 this.resource.Id = act.sked__Resource__c;
4050 this.availableResource = false;
4051 this.activityId = act.Id;
4052 this.activityType = act.sked__Type__c;
4053 this.activityAddress = act.sked__Address__c;
4054 this.activityNotes = act.sked__Notes__c;
4055 this.editable = false;
4056 this.createdByUser = act.CreatedBy.Name;
4057 this.createdByDate = act.CreatedDate;
4058 }
4059
4060 public Appointment(sked__Job__c job, sked__Job__c[] children) {
4061 this.resources = new List<Resource>();
4062 this.allResources = new List<Resource>();
4063 this.children = new List<Appointment>();
4064 this.startTimeAsDate = job.sked__Start__c;
4065 this.endTimeAsDate = job.sked__Finish__c;
4066 this.updatedAddress = job.Updated_Address__c;
4067 this.actualStartTimeAsDate = job.sked__Start__c;
4068 this.actualEndTimeAsDate = job.sked__Finish__c;
4069 this.editable = Constants.EDITABLE_STATUSES.contains(job.sked__Job_Status__c);
4070 this.notes = job.sked__Notes_Comments__c;
4071 this.jobID = job.Id;
4072 this.createdByUser = job.CreatedBy.Name;
4073 this.createdByDate = job.CreatedDate;
4074 // UFI
4075 this.urgent = job.sked__Urgency__c != null && job.sked__Urgency__c.equals(Constants.URGENCY_URGENT);
4076 this.flexible = job.Flexible__c;
4077 this.interpreter = job.Interpreter__c;
4078 this.timeadded = job.TimeAdded__c;
4079 this.postDischarge = job.Post_Discharge__c;
4080 this.confirmationStatus = job.Job_confirmation_status__c;
4081
4082 this.highRisk = job.sked__Account__r.Stratification_Level__c == Constants.STRATIFICATION_LEVEL_HIGH ? true :false;
4083
4084 // apply timezone
4085 this.timezone = (job.sked__Account__r.TimeZone__c == null) ? Constants.DEFAULT_TIME_ZONE : job.sked__Account__r.TimeZone__c;
4086 this.type = getAllAppointmentTypes().get(job.Type_Code__c);
4087 this.patient = new Patient(job.sked__Account__r);
4088
4089 for(sked__Job_Allocation__c alo :job.sked__Job_Allocations__r) {
4090 if (alo.EHR_Appointment_ID__c != null) {
4091 this.aId = alo.EHR_Appointment_ID__c;
4092 }
4093 Resource res = new Resource(alo.sked__Resource__r, alo.Remote__c);
4094 this.resources.add(res);
4095 }
4096 for (sked__Job__c childJob : children) {
4097 if (childJob.sked__Start__c < this.actualStartTimeAsDate) {
4098 this.actualStartTimeAsDate = childJob.sked__Start__c;
4099 }
4100 if (childJob.sked__Finish__c > this.actualEndTimeAsDate) {
4101 this.actualEndTimeAsDate = childJob.sked__Finish__c;
4102 }
4103 // UFI
4104 childJob.sked__Urgency__c = this.urgent ? Constants.URGENCY_URGENT : Constants.URGENCY_NORMAL;
4105 childJob.Flexible__c = this.flexible;
4106 childJob.Interpreter__c = this.interpreter;
4107 childJob.TimeAdded__c = this.timeadded;
4108 Appointment childAppt = new Appointment(childJob, new List<sked__Job__c>());
4109 this.children.add(childAppt);
4110 }
4111 String appType = job.Category__c != null ? job.Category__c : job.sked__Type__c;
4112 if(job.getPopulatedFieldsAsMap().containsKey('Enrollment__r')) {
4113 this.appTypeLetter = getAppTypeLetter(job, appType);
4114 }
4115 this.applyTimezone();
4116 }
4117
4118 public Appointment(sked__Job_Allocation__c a, List<sked__Job_Allocation__c> others) {
4119 this.resources = new List<Resource>();
4120 this.startTimeAsDate = a.sked__Job__r.sked__Start__c;
4121 this.endTimeAsDate = a.sked__Job__r.sked__Finish__c;
4122 this.updatedAddress = a.sked__Job__r.Updated_Address__c;
4123 this.jobID = a.sked__Job__r.Id;
4124 this.aId = a.EHR_Appointment_ID__c;
4125 this.editable = Constants.EDITABLE_STATUSES.contains(a.sked__Status__c);
4126 this.createdByUser = a.sked__Job__r.CreatedBy.Name;
4127 this.createdByDate = a.sked__Job__r.CreatedDate;
4128 this.notes = a.sked__Job__r.sked__Notes_Comments__c;
4129 // UFI
4130 this.urgent = a.sked__Job__r.sked__Urgency__c != null && a.sked__Job__r.sked__Urgency__c.equals(Constants.URGENCY_URGENT);
4131 this.flexible = a.sked__Job__r.Flexible__c;
4132 this.interpreter = a.sked__Job__r.Interpreter__c;
4133 this.timeadded = a.sked__Job__r.TimeAdded__c;
4134 this.postDischarge = a.sked__Job__r.Post_Discharge__c;
4135
4136 this.highRisk = a.sked__Job__r.sked__Account__r.Stratification_Level__c == Constants.STRATIFICATION_LEVEL_HIGH ? true :false;
4137
4138 // apply timezone
4139 this.timezone = (a.sked__Job__r.sked__Account__r.TimeZone__c == null) ? Constants.DEFAULT_TIME_ZONE : a.sked__Job__r.sked__Account__r.TimeZone__c;
4140 this.applyTimezone();
4141
4142 // Workaround for update
4143 if (a.sked__Job__r.Type_Code__c != null && a.sked__Job__r.Type_Code__c.contains('FU')) {
4144 a.sked__Job__r.Type_Code__c = a.sked__Job__r.Type_Code__c.replace('FU', 'FA');
4145 }
4146
4147 String appType = a.sked__Job__r.Category__c != null ? a.sked__Job__r.Category__c : a.sked__Job__r.sked__Type__c;
4148 if(a.sked__Job__r.getPopulatedFieldsAsMap().containsKey('Enrollment__r')) {
4149 this.appTypeLetter = getAppTypeLetter(a.sked__Job__r, appType);
4150 }
4151 this.type = getAllAppointmentTypes().get(a.sked__Job__r.Type_Code__c);
4152 this.patient = new Patient(a.sked__Job__r.sked__Account__r);
4153 Integer drivingFrom = a.sked__Estimated_Travel_Time__c != null ? a.sked__Estimated_Travel_Time__c.intValue() : 0;
4154 Decimal distanceFrom = a.Distance_From__c != null ? a.Distance_From__c : 0;
4155 Decimal distanceTo = a.Distance_To__c != null ? a.Distance_To__c : 0;
4156 for(sked__Job_Allocation__c alo: others) {
4157 Resource res = new Resource(alo.sked__Resource__r, alo.Remote__c);
4158 res.drivingTimeFrom = drivingFrom;
4159 res.distanceFrom = distanceFrom;
4160 res.distanceTo = distanceTo;
4161 this.resources.add(res);
4162 }
4163 this.resource = new Resource();
4164 this.resource.Id = a.sked__Resource__c;
4165 this.resource.type = a.sked__Resource__r.RecordType.Name;
4166 this.resource.contact = new ResourceContact();
4167 this.resource.contact.Name = a.sked__Resource__r.Name;
4168 this.resource.address = a.sked__Resource__r.sked__Home_Address__c;
4169 this.resource.remote = a.Remote__c;
4170 this.resource.routeColor = a.sked__Resource__r.Map_Color__c;
4171 this.resource.primaryMarketId = a.sked__Resource__r.PrimaryMarket__c;
4172 try {
4173 this.resource.drivingTimeFrom = drivingFrom;
4174 this.resource.distanceFrom = distanceFrom;
4175 this.resource.distanceTo = distanceTo;
4176 } catch (Exception e) {
4177 System.debug('It is not an Integer, this should not happen anyways');
4178 }
4179 }
4180
4181 public Appointment(AbstractFW.chktimeResponseAppointments chk, sked__Resource__c res,sked__Job_Allocation__c job) {
4182 this.resources = new List<Resource>();
4183 this.startTimeAsDate = chk.scheduled_time;
4184 this.endTimeAsDate = chk.scheduled_time.addMinutes(chk.duration);
4185 this.jobID = job.sked__Job__r.Id;
4186 this.aId = job.EHR_Appointment_ID__c;
4187 this.startDate = this.startTimeAsDate.formatGmt('yyyy-M-d');
4188 this.startTime = this.startTimeAsDate.formatGmt('yyyy/MM/dd HH:mm');
4189 this.endTime = this.endTimeAsDate.formatGmt('yyyy/MM/dd HH:mm');
4190 this.editable = Constants.EDITABLE_STATUSES.contains(job.sked__Status__c);
4191 this.notes = job.sked__Job__r.sked__Notes_Comments__c;
4192 this.resource = new Resource();
4193 this.resource.Id = res.Id;
4194 this.resource.type = job.sked__Resource__r.RecordType.Name;
4195 this.resource.contact = new ResourceContact();
4196 this.resource.contact.Name = res.Name;
4197 this.resource.address = res.sked__Home_Address__c;
4198 this.type = new AppointmentType();
4199 this.patient = new Patient(job.sked__Job__r.sked__Account__r);
4200 this.createdByUser = job.sked__Job__r.CreatedBy.Name;
4201 this.createdByDate = job.sked__Job__r.CreatedDate;
4202 List<Sked__Job_Allocation__c> alos = [select Id , Remote__c, sked__Estimated_Travel_Time__c,
4203 Distance_From__c, Distance_To__c,
4204 sked__Resource__r.Id,sked__Resource__r.RecordTypeId,
4205 sked__Resource__r.Name,sked__Resource__r.sked__User__r.smallphotourl,
4206 sked__Resource__r.sked__User__c,
4207 sked__Resource__r.Timezone__c,
4208 sked__Resource__r.sked__Home_Address__c,
4209 sked__Resource__r.show_in_calendar__c,
4210 sked__Resource__r.PrimaryMarket__c,
4211 sked__Resource__r.sked__Is_Active__c
4212 from Sked__Job_Allocation__c where sked__Job__r.Id = :job.sked__Job__r.Id
4213 order by sked__Job__r.sked__Start__c];
4214 for(sked__Job_Allocation__c alo: alos) {
4215 Resource reso = new Resource(alo.sked__Resource__r, alo.Remote__c);
4216 this.resources.add(reso);
4217 }
4218 }
4219
4220 public Integer compareTo(Object compareTo) {
4221 Appointment compareToAppt = (Appointment) compareTo;
4222
4223 // The return value of 0 indicates that both elements are equal.
4224 Integer returnValue = 0;
4225 if (this.startTimeAsDate > compareToAppt.startTimeAsDate) {
4226 // Set return value to a positive value.
4227 returnValue = 1;
4228 } else if (this.startTimeAsDate < compareToAppt.startTimeAsDate) {
4229 // Set return value to a negative value.
4230 returnValue = -1;
4231 }
4232 return returnValue;
4233 }
4234 }
4235
4236 global class Resource implements Comparable {
4237 public String rol;
4238 public String routeColor;
4239 public Id Id { get; set; }
4240 public Boolean rewriteResource;
4241 public Boolean active { get; set; }
4242 public String type { get; set; }
4243 public ResourceContact contact { get; set; }
4244
4245 //Exclusive for type definition
4246 public Integer duration {get;set;}
4247 public Integer startOffset {get;set;}
4248 public DateTime endTimeAsDate { get; set; }
4249 public DateTime startTimeAsDate { get; set; }
4250
4251 //Exclusive for resource allocation
4252 public String region;
4253 public String address;
4254 public Boolean remote = false;
4255 public Boolean isShown = false;
4256 public String endTime { get;set; }
4257 public String startTime { get; set; }
4258 public Decimal distanceFrom = 0, distanceTo = 0;
4259 public Integer drivingTimeTo = 0, drivingTimeFrom = 0;
4260
4261 public Decimal factor;
4262 // markets
4263 public String primaryMarketId;
4264
4265 // Timezone
4266 public String tzone;
4267 public Integer timezoneOffset;
4268
4269 // Schedulled entries
4270 public Map<String,List<sked__Availability_Template_Entry__c>> entries = null;
4271 public String changed {get;set;}
4272 public Boolean displayed {get;set;}
4273 List<Assignment> assignments {get;set;}
4274
4275 global Integer compareTo(Object r) {
4276 Resource res = (Resource)r;
4277 if (this.startOffset == res.startOffset) { return 0; }
4278 if (this.startOffset > res.startOffset) { return 1; }
4279 return -1;
4280 }
4281
4282 //Contructors
4283 public Resource() {}
4284
4285 public Resource(Job_Type_Allocation__mdt a, Integer offset) {
4286 this.type = a.Resource_Types__c;
4287 this.duration = Integer.valueOf(a.Duration__c);
4288 this.remote = a.Remote__c;
4289 this.startOffset = offset;
4290 this.displayed = false;
4291 this.changed = null;
4292 this.routeColor = 'none';
4293 }
4294
4295 public Resource(Account a) {
4296 this.Id = a.Id;
4297 this.contact = new ResourceContact();
4298 this.contact.Name = a.Name;
4299 this.contact.PhotoURL = '';
4300 this.tzone = a.TimeZone__c == null ? Constants.DEFAULT_TIME_ZONE : a.TimeZone__c;
4301 this.timezoneOffset = TimeZone.getTimeZone(tzone).getOffset(DateTime.now()) / 3600000;
4302 this.address = a.BillingStreet + ' ' + a.BillingCity + ', ' + a.BillingState + ' ' + ((a.BillingPostalCode == null) ? '' : a.BillingPostalCode) + ', ' + a.BillingCountry;
4303 this.region = a.Region__c;
4304 this.displayed = false;
4305 this.changed = null;
4306 this.routeColor = 'none';
4307 }
4308
4309 public Resource(sked__Resource__c resource, Boolean isRemote) {
4310 this.Id = resource.Id;
4311 this.type = resource.RecordType.Name;
4312 this.routeColor = 'none';
4313 this.active = resource.sked__Is_Active__c;
4314
4315 if (this.type == Constants.LVN_TYPE) {
4316 this.rol = 'Licenced Vocational Nurse';
4317 this.routeColor = resource.Map_Color__c;
4318 } else if (this.type == Constants.NP_TYPE) {
4319 this.rol = 'Nurse Practitioner';
4320 } else if (this.type == Constants.MD_TYPE) {
4321 this.rol = 'Primary Physician';
4322 }
4323 this.contact = new ResourceContact();
4324 this.contact.Name = resource.Name;
4325 this.contact.PhotoURL = resource.sked__User__r.smallphotourl;
4326 this.contact.Id = resource.sked__User__c;
4327 this.tzone = resource.Timezone__c == null ? Constants.DEFAULT_TIME_ZONE : resource.Timezone__c;
4328 this.timezoneOffset = TimeZone.getTimeZone(tzone).getOffset(DateTime.now()) / 3600000;
4329 this.address = resource.sked__Home_Address__c;
4330 this.remote = isRemote;
4331 this.factor = resource.Rate_Factor__c;
4332 this.displayed = resource.show_in_calendar__c != null ? resource.show_in_calendar__c : false;
4333 this.changed = null;
4334 this.primaryMarketId = resource.PrimaryMarket__c;
4335 }
4336 }
4337
4338 public class ResourceContact {
4339 public Id Id {get; set;}
4340 public String Name {get; set;}
4341 public String PhotoURL {get; set;}
4342 }
4343
4344 public class AppointmentCancellationData {
4345 String responsible;
4346 String primaryReason;
4347 String secondaryReason;
4348 String outcome;
4349 String note;
4350
4351 public Job_Cancellation__c getSObject() {
4352 return new Job_Cancellation__c(
4353 Cancellation_Responsibility__c = this.responsible,
4354 Cancellation_Primary_reason__c = this.primaryReason,
4355 Cancellation_Secondary_reason__c = this.secondaryReason,
4356 Cancellation_Outcome__c = this.outcome,
4357 Cancellation_Notes__c = note
4358 );
4359 }
4360 }
4361
4362 public class CancelRequest {
4363 public String jobID;
4364 public String responsible;
4365 public String primaryReason;
4366 public String secondaryReason;
4367 public String outcome;
4368 public String note;
4369 public List<Resource> resources;
4370
4371 public List<Job_Cancellation__c> getCancellationObjects(Set<Id> jobIds) {
4372 List<Job_Cancellation__c> cancellationObjects = new List<Job_Cancellation__c>();
4373 for(Id jobId: jobIds) {
4374 cancellationObjects.add(
4375 new Job_Cancellation__c(
4376 Cancellation_Responsibility__c = this.responsible,
4377 Cancellation_Primary_reason__c = this.primaryReason,
4378 Cancellation_Secondary_reason__c = this.secondaryReason,
4379 Cancellation_Outcome__c = this.outcome,
4380 Cancellation_Notes__c = note,
4381 Job_cancelled__c = jobId
4382 )
4383 );
4384 }
4385 return cancellationObjects;
4386 }
4387 }
4388
4389 public class Patient {
4390 public String address;
4391 Map<String, Address> addresses;
4392 Map<String, Location> locations;
4393 public Id Id {get;set;}
4394 public String MRN {get;set;}
4395 public Id contact {get;set;}
4396 public String ZIP {get;set;}
4397 public Integer Age {get;set;}
4398 public String City {get;set;}
4399 public String color {get;set;}
4400 public String State {get;set;}
4401 public String Name {get; set;}
4402 public String parent {get; set;}
4403 public String Country {get;set;}
4404 public String Subzone {get;set;}
4405 public Account account {get;set;}
4406 public String language {get;set;}
4407 public String hco {get;set;}
4408 public String darkColor {get;set;}
4409 public String AddressLine {get;set;}
4410 public String leadSuborigin {get;set;}
4411 public String TOClatestStatus {get;set;}
4412 public String TOClatestSubstatus {get;set;}
4413 public List<PatientEnrollment> enrolledPrograms {get; set;}
4414 public String Hospital {get; set;}
4415 public Integer daysSinceLastF2F {get; set;}
4416 public Integer daysSinceLastCCPAppointment {get; set;}
4417 public String safetyGrade {get;set;}
4418 // counter-intuitive (Patients can be hospitals now)
4419 public String type;
4420 // touches
4421 public String touchesCount {get; set;}
4422 public String prs {get;set;}
4423 public Integer HC20PrsDenominator {get; set;}
4424 // constructors
4425 public Patient(){}
4426 public Patient(Account a) {
4427 this.locations = new Map<String, Location>();
4428 this.Name = a.Name;
4429 this.Id = a.Id;
4430 this.AddressLine = a.BillingStreet;
4431 this.State = a.BillingState;
4432 this.ZIP = a.BillingPostalCode;
4433 this.City = a.BillingCity;
4434 this.Country = a.BillingCountry;
4435 this.Subzone = a.Subzone__c;
4436 this.color = a.Color__c;
4437 this.darkColor = a.Color_Dark__c;
4438 this.account = a;
4439 this.type = a.RecordType.Name;
4440 this.address = a.BillingStreet + ' ' + a.BillingCity + ', ' + a.BillingState + ((a.BillingPostalCode == null) ? '' : ' ' + a.BillingPostalCode);
4441 if (a.Contacts.size() > 0) {
4442 for (Contact co :a.Contacts) {
4443 if (co.Name == a.Name) {
4444 this.contact = co.Id;
4445 }
4446 }
4447 }
4448 this.language = a.Language__c;
4449 this.leadSuborigin = a.Lead_Suborigin__c;
4450 // touches
4451 this.touchesCount = a.Touch_Count__c == null ? '0' : String.valueOf(a.Touch_Count__c);
4452 this.prs = a.PRS__c == null ? '0' : String.valueOf(a.PRS__c);
4453 this.HC20PrsDenominator = (a.Completed_Appointments__c == null ? 0 : Integer.valueOf(a.Completed_Appointments__c)) + (a.Cancelled_Appointments__c == null ? 0 : Integer.valueOf(a.Cancelled_Appointments__c));
4454 this.TOClatestStatus = a.Pro_TOC_latest_status__c;
4455 this.TOClatestSubstatus = a.Pro_TOC_latest_substatus__c;
4456 this.MRN = a.EHR_MRN__c;
4457 }
4458 }
4459
4460 public class PatientEnrollment {
4461 public String status;
4462 public String programName;
4463 public String programId;
4464 public Id enrollmentId;
4465
4466 public PatientEnrollment(Enrollment__c enrollment) {
4467 this.status = enrollment.Status__c;
4468 this.programName = enrollment.Program_Name__c;
4469 this.programId = enrollment.Program__c;
4470 this.enrollmentId = enrollment.Id;
4471 }
4472 }
4473
4474 public class ResourceNotification {
4475 public String appTime;
4476 public String appDate;
4477 public String appTimeOld;
4478 public String mobilePhone;
4479
4480 public ResourceNotification(String appT,String appD, String appTOld, String phone) {
4481 appTime = appT;
4482 appDate = appD;
4483 mobilePhone = phone;
4484 appTimeOld = appTOld;
4485 }
4486 }
4487
4488 public class Assignment {
4489 public Id id;
4490 public String day;
4491 public String plan;
4492 public String aDate;
4493 public String color;
4494 public Id resourceId;
4495 public List<String> assSubZones;
4496 public Assignment(Assignment__c a) {
4497 this.id = a.Id;
4498 this.aDate = dateTime.newInstanceGmt(a.Date__c.year(), a.Date__c.month(), a.Date__c.day()).formatGmt('YYYY-MM-dd');
4499 this.resourceId = a.Resource__c;
4500 this.assSubZones = new List<String>();
4501 this.plan = !String.isEmpty(a.Fixed_Text__c) ? a.Fixed_Text__c : null;
4502 }
4503 }
4504
4505 public class Market {
4506 public Id id;
4507 public String name;
4508 public String regionId;
4509 public Set<String> resources;
4510 public Map<Id,String> colorByResource;
4511 }
4512
4513 public class Zone {
4514 public String id;
4515 public String name;
4516 public Set<String> relatedLvnIds;
4517 }
4518
4519 public class Address {
4520 public String billingStreet;
4521 public String billingCity;
4522 public String billingState;
4523 public String billingPostalCode;
4524 public String billingCountry;
4525 public String subZone;
4526
4527 public Address(Account a) {
4528 this.billingStreet = a.BillingStreet;
4529 this.billingCity = a.BillingCity;
4530 this.billingState = a.BillingState;
4531 this.billingPostalCode = a.BillingPostalCode;
4532 this.billingCountry = a.BillingCountry;
4533 this.subZone = a.Subzone__c;
4534 }
4535
4536 public override String toString() {
4537 return billingStreet + ', ' + billingCity + ', ' + billingState + ' ' + ((billingPostalCode == null) ? '' : billingCountry) + ', ' + billingCountry;
4538 }
4539 }
4540
4541 public virtual class Location {
4542 public String name;
4543 public String type;
4544 public Address address;
4545
4546 public Location(){}
4547
4548 public Location(Account a) {
4549 this.type = Constants.PATIENT_HOME_TYPE;
4550 this.address = new Address(a);
4551 this.name = 'home';
4552 }
4553 }
4554
4555 public class Hospital extends Location {
4556 public Date admissionDate;
4557 public String phone;
4558
4559 public Hospital(ADT__c adtRecord) {
4560 this.name = adtRecord.Facility__r.Name;
4561 this.type = Constants.HOSPITAL_TYPE;
4562 this.address = new Address(adtRecord.Facility__r);
4563 this.admissionDate = adtRecord.Date_Time__c.date();
4564 this.phone = adtRecord.Facility__r.Phone;
4565 }
4566 }
4567
4568 public class SNF extends Location {
4569 public SNF(Account snf) {
4570 this.name = snf.Name;
4571 this.type = Constants.SNF_TYPE;
4572 this.address = new Address(snf);
4573 }
4574 }
4575
4576 public class SlotLetter {
4577 String letter;
4578 String backgroundColor;
4579
4580 public SlotLetter(String l, String color) {
4581 letter = l;
4582 backgroundColor = color;
4583 }
4584 }
4585
4586 public class PicklistSchema {
4587 public Integer defaultValue;
4588 public List<PicklistValue> values;
4589
4590 public PicklistSchema(Schema.sObjectField picklistField) {
4591 this.values = new List<PicklistValue>();
4592 this.defaultValue = 0;
4593
4594 Integer index = 0;
4595 for (Schema.PicklistEntry entry: picklistField.getDescribe().getPicklistValues()) {
4596 index++;
4597 if(entry.isDefaultValue()) {
4598 this.defaultValue = index;
4599 }
4600 this.values.add(
4601 new PicklistValue(entry)
4602 );
4603 }
4604 }
4605 }
4606
4607 public class PicklistValue {
4608 public String label;
4609 public String value;
4610 public Boolean active;
4611
4612 public PicklistValue(Schema.PicklistEntry entry) {
4613 this.label = entry.getValue();
4614 this.value = entry.getValue();
4615 this.active = entry.isActive();
4616 }
4617 }
4618}