source: Scheduling/trunk/cs/bsdx0200GUISourceCode/CGDocument.cs@ 1097

Last change on this file since 1097 was 1097, checked in by Sam Habiel, 13 years ago

DPatientLookup.cs: Usings Cleanup
DApptSearch: Extensive refactoring. Now uses new algorithm to find appointments. Now outputs CGAvailability
CGView: Appointments checked in now set the Checkin time on the appointment structure; changes to support DApptSearch modified output.
CGSchedLib: Extensive refactoring; only 2 methods remain: CreateAppointmentSchedule and CreateAvailabilitySchedule
CGDocument: SlotsAvailable uses a new algorithm
CGAVView: Uses CalendarGrid.TimesOverlap instead of the removed one in CGSchedLib
CGAVDocument: Uses CalendarGrid.TimesOverlap instead of the removed one in CGSchedLib; CreateAssignedSlotSchedule reassigned to CreateAvailabilitySchedule

File size: 52.0 KB
Line 
1using System;
2using System.Collections;
3using System.Data;
4using System.Data.OleDb;
5using System.Diagnostics;
6using System.Drawing;
7using System.Windows.Forms;
8using IndianHealthService.BMXNet;
9using System.Linq;
10
11namespace IndianHealthService.ClinicalScheduling
12{
13 /// <summary>
14 /// Contains the array of appointments and availabily that make up the document class
15 /// </summary>
16 public class CGDocument
17 {
18 #region Member Variables
19 public int m_nColumnCount; //todo: this should point to the view's member for column count
20 public int m_nTimeUnits; //?
21 private string m_sDocName; //Document Name ?
22 public ArrayList m_sResourcesArray; //keeps the resources
23 public ScheduleType m_ScheduleType; //Either a Resource or a Clinic (Group of Resources)
24 private DateTime m_dSelectedDate; //Holds the user's selection from the dtpicker
25 private DateTime m_dStartDate; //Beginning date of document data
26 private DateTime m_dEndDate; //Ending date of document data
27 public CGAppointments m_appointments; //Appointment List
28 private ArrayList m_pAvArray; //Availability List
29 private CGDocumentManager m_DocManager; //Holds a reference to the document manager
30 private DateTime m_dLastRefresh = DateTime.Now; //Holds last refersh date
31 #endregion
32
33 /// <summary>
34 /// Constructor. Initialize State Data:
35 /// 3 Arrays (Appointments, Availabilities, and Resources)
36 /// Schedule Type is Resource not Clinic
37 /// Selected Date is Today
38 /// Start Date is Today
39 /// End Date is 7 days from Today
40 /// </summary>
41 public CGDocument()
42 {
43 m_appointments = new CGAppointments(); // Holds Appointments
44 m_pAvArray = new ArrayList(); // Holds Availabilites
45 m_sResourcesArray = new ArrayList(); // Holds Resources
46 m_ScheduleType = ScheduleType.Resource; // Default Schedule Type is a Resource
47 m_dSelectedDate = DateTime.Today; // Default Selected Date is Today
48 m_dStartDate = DateTime.Today; // Default Start Date is Today
49 m_dEndDate = DateTime.Today.AddDays(7); // Default End Date is 7 days from Today.
50 }
51
52
53 #region Properties
54
55 /// <summary>
56 /// Returns the latest refresh time for this document
57 /// </summary>
58 public DateTime LastRefreshed
59 {
60 get
61 {
62 return m_dLastRefresh;
63 }
64 }
65
66 /// <summary>
67 /// The list of Resource names
68 /// </summary>
69 public ArrayList Resources
70 {
71 get
72 {
73 return this.m_sResourcesArray;
74 }
75 set
76 {
77 this.m_sResourcesArray = value;
78 }
79 }
80
81 /// <summary>
82 /// The array of CGAvailabilities that contains appt type and slots
83 /// </summary>
84 public ArrayList AvailabilityArray
85 {
86 get
87 {
88 return this.m_pAvArray;
89 }
90 }
91
92 public CGDocumentManager DocManager
93 {
94 get
95 {
96 return m_DocManager;
97 }
98 set
99 {
100 m_DocManager = value;
101 }
102 }
103
104 /// <summary>
105 /// Contains the hashtable of appointments
106 /// </summary>
107 public CGAppointments Appointments
108 {
109 get
110 {
111 return m_appointments;
112 }
113 }
114
115 /// <summary>
116 /// Holds the date selected by the user in CGView.dateTimePicker1
117 /// </summary>
118 public DateTime SelectedDate
119 {
120 get
121 {
122 return this.m_dSelectedDate;
123 }
124 set
125 {
126 this.m_dSelectedDate = value;
127 }
128 }
129
130 /// <summary>
131 /// Contains the beginning date of the appointment document
132 /// </summary>
133 public DateTime StartDate
134 {
135 get
136 {
137 return this.m_dStartDate;
138 }
139 }
140
141 public string DocName
142 {
143 get
144 {
145 return this.m_sDocName;
146 }
147 set
148 {
149 this.m_sDocName = value;
150 }
151 }
152
153 #endregion
154
155 #region Methods
156
157 public void UpdateAllViews()
158 {
159 //iterate through all views and call update.
160 Hashtable h = CGDocumentManager.Current.Views;
161
162 CGDocument d;
163 foreach (CGView v in h.Keys)
164 {
165 d = (CGDocument)h[v];
166 if (d == this)
167 {
168 v.UpdateArrays();
169 }
170 }
171
172 }
173
174 /// <summary>
175 /// Update schedule based on info in RPMS
176 /// <returns>Clears and repopluates m_appointments</returns>
177 /// </summary>
178 private bool RefreshDaysSchedule()
179 {
180 try
181 {
182 string sPatientName;
183 string sPatientID;
184 DateTime dStart;
185 DateTime dEnd;
186 DateTime dCheckIn;
187 DateTime dAuxTime;
188 int nKeyID;
189 string sNote;
190 string sResource;
191 bool bNoShow = false;
192 string sNoShow = "0";
193 string sHRN = "";
194 int nAccessTypeID; //used in autorebook
195 string sWalkIn = "0";
196 bool bWalkIn;
197 CGAppointment pAppointment;
198 CGDocumentManager pApp = CGDocumentManager.Current;
199 DataTable rAppointmentSchedule;
200
201 //Nice to know that it gets set here!!!
202 m_dLastRefresh = DateTime.Now;
203
204 //Clear appointments associated with this document
205 this.m_appointments.ClearAllAppointments();
206
207 // calls RPC to get appointments
208 rAppointmentSchedule = CGSchedLib.CreateAppointmentSchedule(m_DocManager, m_sResourcesArray, this.m_dStartDate, this.m_dEndDate);
209
210 // loop through datatable: Create CGAppointment and add to CGAppointments
211 foreach (DataRow r in rAppointmentSchedule.Rows)
212 {
213
214 if (r["APPOINTMENTID"].ToString() == "0")
215 {
216 string sMsg = r["NOTE"].ToString();
217 throw new BMXNetException(sMsg);
218 }
219 nKeyID = Convert.ToInt32(r["APPOINTMENTID"].ToString());
220 sResource = r["RESOURCENAME"].ToString();
221 sPatientName = r["PATIENTNAME"].ToString();
222 sPatientID = r["PATIENTID"].ToString();
223 dStart = (DateTime)r["START_TIME"];
224 dEnd = (DateTime)r["END_TIME"];
225 dCheckIn = new DateTime();
226 dAuxTime = new DateTime();
227
228 if (r["CHECKIN"].GetType() != typeof(System.DBNull))
229 dCheckIn = (DateTime)r["CHECKIN"];
230 if (r["AUXTIME"].GetType() != typeof(System.DBNull))
231 dCheckIn = (DateTime)r["AUXTIME"];
232 sNote = r["NOTE"].ToString();
233 sNoShow = r["NOSHOW"].ToString();
234 bNoShow = (sNoShow == "1") ? true : false;
235 sHRN = r["HRN"].ToString();
236 nAccessTypeID = (int)r["ACCESSTYPEID"];
237 sWalkIn = r["WALKIN"].ToString();
238 bWalkIn = (sWalkIn == "1") ? true : false;
239
240 pAppointment = new CGAppointment();
241 pAppointment.CreateAppointment(dStart, dEnd, sNote, nKeyID, sResource);
242 pAppointment.PatientName = sPatientName;
243 pAppointment.PatientID = Convert.ToInt32(sPatientID);
244 if (dCheckIn.Ticks > 0)
245 pAppointment.CheckInTime = dCheckIn;
246 if (dAuxTime.Ticks > 0)
247 pAppointment.AuxTime = dAuxTime;
248 pAppointment.NoShow = bNoShow;
249 pAppointment.HealthRecordNumber = sHRN;
250 pAppointment.AccessTypeID = nAccessTypeID;
251 pAppointment.WalkIn = bWalkIn;
252 this.m_appointments.AddAppointment(pAppointment);
253
254 }
255
256 return true;
257 }
258 catch (Exception Ex)
259 {
260 Debug.Write("CGDocument.RefreshDaysSchedule error: " + Ex.Message + "\n");
261 return false;
262 }
263 }
264
265
266 public bool IsRefreshNeeded()
267 {
268 if (m_sResourcesArray.Count == 0) return false;
269 return this.WeekNeedsRefresh(1, m_dSelectedDate, out this.m_dStartDate, out this.m_dEndDate);
270 }
271
272 //sam: This is a test that duplicates RefreshDocument, but without the UpdateAllViews,
273 // as that has to be done synchornously.
274 //XXX: Needs to be refactored obviously, but now for testing.
275 //XXX: Tested extensively enough. Less refactoring now. 2011-01-26
276 public void RefreshDocumentAsync()
277 {
278 Debug.WriteLine("IN REFERSH DOCUMENT ASYNC\n\n");
279
280 bool bRet = false;
281 if (m_sResourcesArray.Count == 0)
282 return;
283 if (m_sResourcesArray.Count == 1)
284 {
285 bRet = this.WeekNeedsRefresh(1, m_dSelectedDate, out this.m_dStartDate, out this.m_dEndDate);
286 }
287 else
288 {
289 this.m_dStartDate = m_dSelectedDate;
290 this.m_dEndDate = m_dSelectedDate;
291 this.m_dEndDate = this.m_dEndDate.AddHours(23);
292 this.m_dEndDate = this.m_dEndDate.AddMinutes(59);
293 this.m_dEndDate = this.m_dEndDate.AddSeconds(59);
294 }
295
296 bRet = RefreshSchedule();
297 }
298
299
300 public void RefreshDocument()
301 {
302 bool bRet = false;
303 if (m_sResourcesArray.Count == 0)
304 return;
305 if (m_sResourcesArray.Count == 1)
306 {
307 bRet = this.WeekNeedsRefresh(1, m_dSelectedDate, out this.m_dStartDate, out this.m_dEndDate);
308 }
309 else
310 {
311 this.m_dStartDate = m_dSelectedDate;
312 this.m_dEndDate = m_dSelectedDate;
313 this.m_dEndDate = this.m_dEndDate.AddHours(23);
314 this.m_dEndDate = this.m_dEndDate.AddMinutes(59);
315 this.m_dEndDate = this.m_dEndDate.AddSeconds(59);
316 }
317
318 bRet = RefreshSchedule();
319
320 this.UpdateAllViews();
321 }
322
323 public void OnOpenDocument()
324 {
325 try
326 {
327 //Create new Document
328 m_ScheduleType = (m_sResourcesArray.Count == 1) ? ScheduleType.Resource : ScheduleType.Clinic;
329 bool bRet = false;
330
331 //Set initial From and To dates based on current day
332 DateTime dDate = DateTime.Today;
333 if (m_ScheduleType == ScheduleType.Resource)
334 {
335 bRet = this.WeekNeedsRefresh(1, dDate, out this.m_dStartDate, out this.m_dEndDate);
336 }
337 else
338 {
339 this.m_dStartDate = dDate;
340 this.m_dEndDate = dDate;
341 this.m_dEndDate = this.m_dEndDate.AddHours(23);
342 this.m_dEndDate = this.m_dEndDate.AddMinutes(59);
343 this.m_dEndDate = this.m_dEndDate.AddSeconds(59);
344 }
345
346 bRet = RefreshSchedule();
347
348 CGView view = null;
349 //If this document already has a view, the use it
350 //SAM: Why do this again???
351 Hashtable h = CGDocumentManager.Current.Views;
352 CGDocument d;
353 bool bReuseView = false;
354 foreach (CGView v in h.Keys)
355 {
356 d = (CGDocument)h[v];
357 if (d == this)
358 {
359 view = v;
360 bReuseView = true;
361 v.InitializeDocView(this.DocName);
362 break;
363 }
364 }
365
366 //Otherwise, create new View
367 if (bReuseView == false)
368 {
369 view = new CGView();
370
371 view.InitializeDocView(this,
372 this.DocManager,
373 m_dStartDate,
374 this.DocName);
375
376 view.Show();
377 view.SyncTree();
378
379 }
380 this.UpdateAllViews();
381 }
382 catch (BMXNetException bmxEx)
383 {
384 throw bmxEx;
385 }
386 catch (Exception ex)
387 {
388 throw new BMXNet.BMXNetException("ClinicalScheduling.OnOpenDocument error: " + ex.Message);
389 }
390 }
391
392 /// <summary>
393 /// Refreshes Availablility and Schedules from RPMS.
394 /// </summary>
395 /// <returns>Success or Failure. Should be always Success.</returns>
396 private bool RefreshSchedule()
397 {
398 try
399 {
400 bool bRet = this.RefreshAvailabilitySchedule();
401 if (bRet == false)
402 {
403 return bRet;
404 }
405 bRet = this.RefreshDaysSchedule();
406 return bRet;
407 }
408 catch (ApplicationException aex)
409 {
410 Debug.Write("CGDocument.RefreshSchedule Application Error: " + aex.Message + "\n");
411 return false;
412 }
413 catch (Exception ex)
414 {
415 MessageBox.Show("CGDocument.RefreshSchedule error: " + ex.Message + "\n");
416 return false;
417 }
418 }
419
420 private bool RefreshAvailabilitySchedule()
421 {
422 try
423 {
424 ArrayList saryApptTypes = new ArrayList();
425
426 //Refresh Availability schedules
427 DataTable rAvailabilitySchedule;
428 rAvailabilitySchedule = CGSchedLib.CreateAvailabilitySchedule(m_DocManager, m_sResourcesArray, this.m_dStartDate, this.m_dEndDate, saryApptTypes,/**/ m_ScheduleType, "0");
429
430 ////NEW
431 //NOTE: This lock makes sure that availabilities aren't queried for slots when the array is an intermediate
432 //state. The other place that has this lock is SlotsAvailable function.
433 lock (this.m_pAvArray)
434 {
435 m_pAvArray.Clear();
436 foreach (DataRow rTemp in rAvailabilitySchedule.Rows)
437 {
438 DateTime dStart = (DateTime)rTemp["START_TIME"];
439 DateTime dEnd = (DateTime)rTemp["END_TIME"];
440
441 //TODO: Fix this slots datatype problem
442 string sSlots = rTemp["SLOTS"].ToString();
443 int nSlots = Convert.ToInt16(sSlots);
444
445 string sResourceList = rTemp["RESOURCE"].ToString();
446 string sAccessRuleList = rTemp["ACCESS_TYPE"].ToString();
447 string sNote = rTemp["NOTE"].ToString();
448
449 int nApptTypeID;
450
451 if ((nSlots < -1000) || (sAccessRuleList == ""))
452 {
453 nApptTypeID = 0;
454 }
455 else
456 {
457 nApptTypeID = Int32.Parse(rTemp["ACCESS_TYPE"].ToString());
458 }
459
460 AddAvailability(dStart, dEnd, nApptTypeID, nSlots, sResourceList, sAccessRuleList, sNote);
461 }
462 }
463 return true;
464
465 /* NOT USED
466 //Refresh Type Schedule
467 string sResourceName = "";
468 DataTable rTypeSchedule = new DataTable(); ;
469 for (int j = 0; j < m_sResourcesArray.Count; j++)
470 {
471 sResourceName = m_sResourcesArray[j].ToString();
472 DataTable dtTemp = CGSchedLib.CreateAssignedTypeSchedule(m_DocManager, sResourceName, this.m_dStartDate, this.m_dEndDate, m_ScheduleType);
473
474 if (j == 0)
475 {
476 rTypeSchedule = dtTemp;
477 }
478 else
479 {
480 rTypeSchedule = CGSchedLib.UnionBlocks(rTypeSchedule, dtTemp);
481 }
482 }
483
484 DateTime dStart;
485 DateTime dEnd;
486 DateTime dTypeStart;
487 DateTime dTypeEnd;
488 int nSlots;
489 Rectangle crRectA = new Rectangle(0, 0, 1, 0);
490 Rectangle crRectB = new Rectangle(0, 0, 1, 0);
491 bool bIsect;
492 string sResourceList;
493 string sAccessRuleList;
494
495 //smh: moved clear availabilities down here.
496 //smh: Temporary solution to make sure that people don't touch the availability table at the same time!!!
497 //NOTE: This lock makes sure that availabilities aren't queried for slots when the array is an intermediate
498 //state. The other place that has this lock is SlotsAvailable function.
499 lock (this.m_pAvArray)
500 {
501 m_pAvArray.Clear();
502
503 foreach (DataRow rTemp in rAvailabilitySchedule.Rows)
504 {
505 //get StartTime, EndTime and Slots
506 dStart = (DateTime)rTemp["START_TIME"];
507 dEnd = (DateTime)rTemp["END_TIME"];
508
509 //TODO: Fix this slots datatype problem
510 string sSlots = rTemp["SLOTS"].ToString();
511 nSlots = Convert.ToInt16(sSlots);
512
513 sResourceList = rTemp["RESOURCE"].ToString();
514 sAccessRuleList = rTemp["ACCESS_TYPE"].ToString();
515
516 string sNote = rTemp["NOTE"].ToString();
517
518 if ((nSlots < -1000) || (sAccessRuleList == ""))
519 {
520 nApptTypeID = 0;
521 }
522 else
523 {
524 foreach (DataRow rType in rTypeSchedule.Rows)
525 {
526
527 dTypeStart = (DateTime)rType["StartTime"];
528 dTypeEnd = (DateTime)rType["EndTime"];
529 //if start & end times overlap, then
530 string sTypeResource = rType["ResourceName"].ToString();
531 if ((dTypeStart.DayOfYear == dStart.DayOfYear) && (sResourceList == sTypeResource))
532 {
533 crRectA.Y = GetTotalMinutes(dStart);
534 crRectA.Height = GetTotalMinutes(dEnd) - crRectA.Top;
535 crRectB.Y = GetTotalMinutes(dTypeStart);
536 crRectB.Height = GetTotalMinutes(dTypeEnd) - crRectB.Top;
537 bIsect = crRectA.IntersectsWith(crRectB);
538 if (bIsect == true)
539 {
540 //TODO: This code:
541 // nApptTypeID = (int) rType["AppointmentTypeID"];
542 //Causes this exception:
543 //Unhandled Exception: System.InvalidCastException: Specified cast is not valid.
544 string sTemp = rType["AppointmentTypeID"].ToString();
545 nApptTypeID = Convert.ToInt16(sTemp);
546 break;
547 }
548 }
549 }//end foreach datarow rType
550 }
551
552
553 //AddAvailability(dStart, dEnd, nApptTypeID, nSlots, sResourceList, sAccessRuleList, sNote);
554 }//end foreach datarow rTemp
555 }//end lock
556 return true;
557 */
558 }
559 catch (Exception ex)
560 {
561 Debug.Write("CGDocument.RefreshAvailabilitySchedule error: " + ex.Message + "\n");
562 return false;
563 }
564 }
565
566 private int GetTotalMinutes(DateTime dDate)
567 {
568 return ((dDate.Hour * 60) + dDate.Minute);
569 }
570
571 /// <summary>
572 /// Adds Availability to Availability Array held by document
573 /// </summary>
574 /// <param name="StartTime">Self-Explan</param>
575 /// <param name="EndTime">Self-Explan</param>
576 /// <param name="nType"></param>
577 /// <param name="nSlots"></param>
578 /// <param name="UpdateView"></param>
579 /// <param name="sResourceList"></param>
580 /// <param name="sAccessRuleList"></param>
581 /// <param name="sNote"></param>
582 /// <returns></returns>
583 public int AddAvailability(DateTime StartTime, DateTime EndTime, int nType, int nSlots, string sResourceList, string sAccessRuleList, string sNote)
584 {
585 //adds it to the object array
586 //Returns the index in the array
587
588 CGAvailability pNewAv = new CGAvailability();
589 pNewAv.Create(StartTime, EndTime, nType, nSlots, sResourceList, sAccessRuleList);
590
591 pNewAv.Note = sNote;
592
593 //Look up the color and type name using the AppointmentTypes datatable
594 DataTable dtType = this.m_DocManager.GlobalDataSet.Tables["AccessTypes"];
595 DataRow dRow = dtType.Rows.Find(nType.ToString());
596 if (dRow != null)
597 {
598 string sColor = dRow["DISPLAY_COLOR"].ToString();
599 pNewAv.DisplayColor = sColor;
600 string sTemp = dRow["RED"].ToString();
601 sTemp = (sTemp == "") ? "0" : sTemp;
602 int nRed = Convert.ToInt16(sTemp);
603 pNewAv.Red = nRed;
604 sTemp = dRow["GREEN"].ToString();
605 sTemp = (sTemp == "") ? "0" : sTemp;
606 int nGreen = Convert.ToInt16(sTemp);
607 pNewAv.Green = nGreen;
608 sTemp = dRow["BLUE"].ToString();
609 sTemp = (sTemp == "") ? "0" : sTemp;
610 int nBlue = Convert.ToInt16(sTemp);
611 pNewAv.Blue = nBlue;
612
613 string sName = dRow["ACCESS_TYPE_NAME"].ToString();
614 pNewAv.AccessTypeName = sName;
615 }
616
617 int nIndex = 0;
618 nIndex = m_pAvArray.Add(pNewAv);
619
620 return nIndex;
621 }
622
623
624 public void AddResource(string sResource)
625 {
626 //TODO: Test that resource is not currently in list, that it IS a resource, etc
627 this.m_sResourcesArray.Add(sResource);
628 }
629
630 /// <summary>
631 /// Gets number of slots left in a certain selection on the grid. Used in Multiple Places.
632 /// </summary>
633 /// <param name="dSelStart">Selection Start Date</param>
634 /// <param name="dSelEnd">Selection End Date</param>
635 /// <param name="sResource">Resource Name</param>
636 /// <param name="sAccessType">Out: Name of Access Type</param>
637 /// <param name="sAvailabilityMessage">Out: Access Note</param>
638 /// <returns>Number of slots</returns>
639 public int SlotsAvailable(DateTime dSelStart, DateTime dSelEnd, string sResource, out string sAccessType, out string sAvailabilityMessage)
640 {
641
642
643 sAccessType = ""; //default out value
644 sAvailabilityMessage = ""; //default out value
645
646 double slotsAvailable = 0; //defalut return value
647
648 //NOTE: What's this lock? This lock makes sure that nobody is editing the availability array
649 //when we are looking at it. Since the availability array could potentially be updated on
650 //a different thread, we are can be potentially left stuck with an empty array.
651 //
652 //The other place that uses this lock is the RefershAvailabilitySchedule method
653 //
654 //This is a temporary fix until I figure out how to divorce the availbilities here from those drawn
655 //on the calendar. Appointments are cloned b/c they are in an object that supports that; and b/c I
656 //don't need to suddenly query them at runtime like I do with Availabilities.
657
658 //Let's Try Linq
659 lock (this.m_pAvArray)
660 {
661 //This foreach loop looks for an availability that overlaps where the user clicked.
662 //There can only be one, as availabilites cannot overlap each other (enforced at the DB level)
663 //If selection hits multiple blocks, get the block with the most slots (reflected by the sorting here)
664 CGAvailability[] pAVs = (from pAV in this.m_pAvArray.Cast<CGAvailability>()
665 where (sResource == pAV.ResourceList && CalendarGrid.TimesOverlap(dSelStart, dSelEnd, pAV.StartTime, pAV.EndTime))
666 orderby pAV.Slots descending
667 select pAV)
668 .ToArray<CGAvailability>();
669
670 if ((pAVs.Length) == 0) return 0;
671
672 slotsAvailable = pAVs[0].Slots;
673 sAccessType = pAVs[0].AccessTypeName;
674 sAvailabilityMessage = pAVs[0].Note;
675
676 //Subtract total slots current appointments take up.
677 slotsAvailable -= (from appt in this.Appointments.AppointmentTable.Values.Cast<CGAppointment>()
678 //If the resource is the same and the user selection overlaps, then...
679 where (sResource == appt.Resource && CalendarGrid.TimesOverlap(pAVs[0].StartTime, pAVs[0].EndTime, appt.StartTime, appt.EndTime))
680 // if appt starttime is before avail start time, only count against the avail starting from the availability start time
681 let startTimeToCountAgainstBlock = appt.StartTime < pAVs[0].StartTime ? pAVs[0].StartTime : appt.StartTime
682 // if appt endtime is after the avail ends, only count against the avail up to where the avail ends
683 let endTimeToCountAgainstBlock = appt.EndTime > pAVs[0].EndTime ? pAVs[0].EndTime : appt.EndTime
684 // theoretical minutes per slot for the availability
685 let minPerSlot = (pAVs[0].EndTime - pAVs[0].StartTime).TotalMinutes / pAVs[0].Slots
686 // how many minutes does this appointment take away from the slot
687 let minPerAppt = (endTimeToCountAgainstBlock - startTimeToCountAgainstBlock).TotalMinutes
688 // how many slots the appointment takes up using this availability's scale
689 let slotsConsumed = minPerAppt / minPerSlot
690 select slotsConsumed)
691 // add up SlotsConsumed to substract from slotsAvailable
692 .Sum();
693 }
694
695 return (int)slotsAvailable;
696
697 /* OLD ALGOTHRIM 2
698
699 lock (this.m_pAvArray)
700 {
701 //This foreach loop looks for an availability that overlaps where the user clicked.
702 //There can only be one, as availabilites cannot overlap each other (enforced at the DB level)
703 //Therefore, we loop, and once we find it, we break.
704 foreach (CGAvailability pAV in this.m_pAvArray)
705 {
706 //If the resource is the same and the user selection overlaps, then...
707 if (sResource == pAV.ResourceList && CalendarGrid.TimesOverlap(dSelStart, dSelEnd, pAV.StartTime, pAV.EndTime))
708 {
709 slotsAvailable = pAV.Slots; //variable now holds the total number of slots
710 sAccessType = pAV.AccessTypeName; //Access Name
711 sAvailabilityMessage = pAV.Note; //Access Block Note
712
713 //Here we substract each appointment weight in slots from slotsAvailable
714 foreach (DictionaryEntry apptDict in this.m_appointments.AppointmentTable)
715 {
716 CGAppointment appt = (CGAppointment)apptDict.Value;
717 //If the appointment is in the same resource and overlaps with this availablity
718 if (sResource == appt.Resource && CalendarGrid.TimesOverlap(pAV.StartTime, pAV.EndTime, appt.StartTime, appt.EndTime))
719 {
720 // if appt starttime is before avail start time, only count against the avail starting from the availability start time
721 DateTime startTimeToCountAgainstBlock = appt.StartTime < pAV.StartTime ? pAV.StartTime : appt.StartTime;
722 // if appt endtime is after the avail ends, only count against the avail up to where the avail ends
723 DateTime endTimeToCountAgainstBlock = appt.EndTime > pAV.EndTime ? pAV.EndTime : appt.EndTime;
724 // theoretical minutes per slot for the availability
725 double minPerSlot = (pAV.EndTime - pAV.StartTime).TotalMinutes/pAV.Slots;
726 // how many minutes does this appointment take away from the slot
727 double minPerAppt = (endTimeToCountAgainstBlock - startTimeToCountAgainstBlock).TotalMinutes;
728 // how many slots the appointment takes up using this availability's scale
729 double slotsConsumed = minPerAppt / minPerSlot;
730 // subscract that from the total slots for the availability
731 slotsAvailable -= slotsConsumed;
732 } //end if
733 //now go to the next appointment in foreach
734 }
735 // As I said above, we can match only one availability. Once we found it and substracted from it; we are done.
736 break;
737 }
738 }
739 // We return the integer portion of the variable for display to the user or for calculations.
740 // That means, if 2.11 slots are left, the user sees 2. If 2.66, still 2.
741 return (int)slotsAvailable;
742 }
743 */
744
745 /* ORIGINAL ALGORITHM
746 sAccessType = "";
747 sAvailabilityMessage = "";
748 DateTime dStart;
749 DateTime dEnd;
750 int nAvailableSlots = 999;
751 int nSlots = 0;
752 int i = 0;
753 CGAvailability pAv;
754 Rectangle crRectA = new Rectangle(0, 0, 1, 0);
755 Rectangle crRectB = new Rectangle(0, 0, 1, 0);
756 bool bIsect;
757 crRectB.Y = GetTotalMinutes(dSelStart);
758 crRectB.Height = GetTotalMinutes(dSelEnd) - crRectB.Y;
759
760 //NOTE: What's this lock? This lock makes sure that nobody is editing the availability array
761 //when we are looking at it. Since the availability array could potentially be updated on
762 //a different thread, we are can be potentially left stuck with an empty array.
763 //
764 //The other place that uses this lock is the RefershAvailabilitySchedule method
765 //
766 //This is a temporary fix until I figure out how to divorce the availbilities here from those drawn
767 //on the calendar. Appointments are cloned b/c they are in an object that supports that; and b/c I
768 //don't need to suddenly query them at runtime like I do with Availabilities.
769
770 lock (this.m_pAvArray)
771 {
772 //loop thru m_pAvArray
773 //Compare the start time and end time of eachblock
774 while (i < m_pAvArray.Count)
775 {
776 pAv = (CGAvailability)m_pAvArray[i];
777 dStart = pAv.StartTime;
778 dEnd = pAv.EndTime;
779 if ((sResource == pAv.ResourceList) &&
780 ((dSelStart.Date == dStart.Date) || (dSelStart.Date == dEnd.Date)))
781 {
782 crRectA.Y = (dStart.Date < dSelStart.Date) ? 0 : GetTotalMinutes(dStart);
783 crRectA.Height = (dEnd.Date > dSelEnd.Date) ? 1440 : GetTotalMinutes(dEnd);
784 crRectA.Height = crRectA.Height - crRectA.Y;
785 bIsect = crRectA.IntersectsWith(crRectB);
786 if (bIsect != false)
787 {
788 nSlots = pAv.Slots;
789 if (nSlots < 1)
790 {
791 nAvailableSlots = 0;
792 break;
793 }
794 if (nSlots < nAvailableSlots)
795 {
796 nAvailableSlots = nSlots;
797 sAccessType = pAv.AccessTypeName;
798 sAvailabilityMessage = pAv.Note;
799
800 }
801 }//end if
802 }//end if
803 i++;
804 }//end while
805 }//end lock
806
807 if (nAvailableSlots == 999)
808 {
809 nAvailableSlots = 0;
810 }
811 return nAvailableSlots;
812 */
813 }
814
815 /// <summary>
816 /// Given a selected date,
817 /// Calculates StartDay and End Day and returns them in output params.
818 /// nWeeks == number of Weeks to display
819 /// nColumnCount is number of days displayed per week.
820 /// If 5 columns, begin on Second Day of Week
821 /// If 7 Columns, begin on First Day of Week
822 /// (this is a change from the hardcoded behavior for US-based calendars)
823 ///
824 /// Returns TRUE if the document's data needs refreshing based on
825 /// this newly selected date.
826 /// </summary>
827 public bool WeekNeedsRefresh(int nWeeks, DateTime SelectedDate,
828 out DateTime WeekStartDay, out DateTime WeekEndDay)
829 {
830 DateTime OldStartDay = m_dStartDate;
831 DateTime OldEndDay = m_dEndDate;
832 // Week start based on thread locale
833 int nStartWeekDay = (int)System.Threading.Thread.CurrentThread.CurrentCulture.DateTimeFormat.FirstDayOfWeek;
834 // Current Day
835 int nWeekDay = (int)SelectedDate.DayOfWeek; //0 == Sunday
836
837 // this offset gets approrpriate day based on locale.
838 int nOff = (nStartWeekDay + 1) % 7;
839 TimeSpan ts = new TimeSpan(nWeekDay - nOff, 0, 0, 0); //d,h,m,s
840
841 // if ts is negative, we will jump to the next week in the logic.
842 // to show the correct week, add 7. Confusing, I know.
843 if (ts < new TimeSpan()) ts = ts + new TimeSpan(7, 0, 0, 0);
844
845 if (m_nColumnCount == 1) // if one column start and end on the same day.
846 {
847 ts = new TimeSpan(0, 23, 59, 59);
848 WeekStartDay = SelectedDate;
849 WeekEndDay = WeekStartDay + ts;
850 }
851 else if (m_nColumnCount == 5 || m_nColumnCount == 0) // if 5 column start (or default) at the 2nd day of this week and end in 4:23:59:59 days.
852 {
853 // if picked day is start of week (Sunday in US), start in the next day since that's the first day of work week
854 // else, just substract the calculated time span to get to the start of week (first work day)
855 WeekStartDay = (nWeekDay == nStartWeekDay) ? SelectedDate + new TimeSpan(1, 0, 0, 0) : SelectedDate - ts;
856 // End day calculation
857 int nEnd = 3;
858 ts = new TimeSpan((7 * nWeeks) - nEnd, 23, 59, 59);
859 WeekEndDay = WeekStartDay + ts;
860 }
861 else // if 7 column start at the 1st day of this week and end in 6:23:59:59 days.
862 {
863 // if picked day is start of week, use that. Otherwise, go to the fist work day and substract one to get to start of week.
864 WeekStartDay = (nWeekDay == nStartWeekDay) ? SelectedDate : SelectedDate - ts - new TimeSpan(1, 0, 0, 0);
865 // End day calculation
866 int nEnd = 1;
867 ts = new TimeSpan((7 * nWeeks) - nEnd, 23, 59, 59);
868 WeekEndDay = WeekStartDay + ts;
869 }
870
871 bool bRet = ((WeekStartDay.Date != OldStartDay.Date) || (WeekEndDay.Date != OldEndDay.Date));
872 return bRet;
873 }
874
875 /// <summary>
876 /// Calls RPMS to create appointment then
877 /// adds appointment to the m_appointments collection
878 /// Returns the IEN of the appointment in the RPMS BSDX APPOINTMENT file.
879 /// </summary>
880 /// <param name="rApptInfo"></param>
881 /// <returns></returns>
882 public int CreateAppointment(CGAppointment rApptInfo)
883 {
884 return CreateAppointment(rApptInfo, false);
885 }
886
887 /// <summary>
888 /// Use this overload to create a walkin appointment
889 /// </summary>
890 /// <param name="rApptInfo"></param>
891 /// <param name="bWalkin"></param>
892 /// <returns></returns>
893 public int CreateAppointment(CGAppointment rApptInfo, bool bWalkin)
894 {
895 string sStart;
896 string sEnd;
897 string sPatID;
898 string sResource;
899 string sNote;
900 string sLen;
901 string sApptID;
902
903 //sStart = rApptInfo.StartTime.ToString("M-d-yyyy@HH:mm");
904 //sEnd = rApptInfo.EndTime.ToString("M-d-yyyy@HH:mm");
905
906 // i18n code -- Use culture neutral FMDates
907 sStart = FMDateTime.Create(rApptInfo.StartTime).FMDateString;
908 sEnd = FMDateTime.Create(rApptInfo.EndTime).FMDateString;
909
910 TimeSpan sp = rApptInfo.EndTime - rApptInfo.StartTime;
911 sLen = sp.TotalMinutes.ToString();
912 sPatID = rApptInfo.PatientID.ToString();
913 sNote = rApptInfo.Note;
914 sResource = rApptInfo.Resource;
915 if (bWalkin == true)
916 {
917 sApptID = "WALKIN";
918 }
919 else
920 {
921 sApptID = rApptInfo.AccessTypeID.ToString();
922 }
923
924 CGAppointment aCopy = new CGAppointment();
925 aCopy.CreateAppointment(rApptInfo.StartTime, rApptInfo.EndTime, sNote, 0, sResource);
926 aCopy.PatientID = rApptInfo.PatientID;
927 aCopy.PatientName = rApptInfo.PatientName;
928 aCopy.HealthRecordNumber = rApptInfo.HealthRecordNumber;
929 aCopy.AccessTypeID = rApptInfo.AccessTypeID;
930 aCopy.WalkIn = bWalkin ? true : false;
931
932 string sSql = "BSDX ADD NEW APPOINTMENT^" + sStart + "^" + sEnd + "^" + sPatID + "^" + sResource + "^" + sLen + "^" + sNote + "^" + sApptID;
933 System.Data.DataTable dtAppt = m_DocManager.RPMSDataTable(sSql, "NewAppointment");
934 int nApptID;
935
936 Debug.Assert(dtAppt.Rows.Count == 1);
937 DataRow r = dtAppt.Rows[0];
938 nApptID = Convert.ToInt32(r["APPOINTMENTID"]);
939 string sErrorID;
940 sErrorID = r["ERRORID"].ToString();
941 if ((sErrorID != "") || (nApptID < 1))
942 {
943 throw new Exception(sErrorID);
944 }
945 aCopy.AppointmentKey = nApptID;
946 this.m_appointments.AddAppointment(aCopy);
947
948
949 //Have make appointment from CGView responsible for requesting an update for the avialability.
950 //bool bRet = RefreshAvailabilitySchedule();
951
952 //Sam: don't think this is needed as it is called from CGView.
953 //Make CGView responsible for all drawing.
954 //UpdateAllViews();
955
956 return nApptID;
957 }
958
959 public void EditAppointment(CGAppointment pAppt, string sNote)
960 {
961 try
962 {
963 int nApptID = pAppt.AppointmentKey;
964 string sSql = "BSDX EDIT APPOINTMENT^" + nApptID.ToString() + "^" + sNote;
965
966 System.Data.DataTable dtAppt = m_DocManager.RPMSDataTable(sSql, "EditAppointment");
967
968 Debug.Assert(dtAppt.Rows.Count == 1);
969 DataRow r = dtAppt.Rows[0];
970 string sErrorID = r["ERRORID"].ToString();
971 if (sErrorID == "-1")
972 pAppt.Note = sNote;
973
974 if (this.m_appointments.AppointmentTable.ContainsKey(nApptID))
975 {
976 bool bRet = RefreshAvailabilitySchedule();
977 UpdateAllViews();
978 }
979 }
980 catch (Exception ex)
981 {
982 Debug.Write("CGDocument.EditAppointment Failed: " + ex.Message);
983 }
984 }
985
986 public void CheckInAppointment(int nApptID, DateTime dCheckIn)
987 {
988 string sCheckIn = FMDateTime.Create(dCheckIn).FMDateString;
989
990 string sSql = "BSDX CHECKIN APPOINTMENT^" + nApptID.ToString() + "^" + sCheckIn; // +"^";
991 //sSql += ClinicStopIEN + "^" + ProviderIEN + "^" + PrintRouteSlip + "^";
992 //sSql += PCCClinicIEN + "^" + PCCFormIEN + "^" + PCCOutGuide;
993
994 System.Data.DataTable dtAppt = m_DocManager.RPMSDataTable(sSql, "CheckInAppointment");
995
996 Debug.Assert(dtAppt.Rows.Count == 1);
997 DataRow r = dtAppt.Rows[0];
998 string sErrorID = r["ERRORID"].ToString();
999
1000
1001 }
1002
1003 public string DeleteAppointment(int nApptID)
1004 {
1005 return DeleteAppointment(nApptID, true, 0, "");
1006 }
1007
1008 public string DeleteAppointment(int nApptID, bool bClinicCancelled, int nReason, string sRemarks)
1009 {
1010 //Returns "" if deletion successful
1011 //Otherwise, returns reason for failure
1012
1013 string sClinicCancelled = (bClinicCancelled == true) ? "C" : "PC";
1014 string sReasonID = nReason.ToString();
1015 string sSql = "BSDX CANCEL APPOINTMENT^" + nApptID.ToString();
1016 sSql += "^" + sClinicCancelled;
1017 sSql += "^" + sReasonID;
1018 sSql += "^" + sRemarks;
1019 DataTable dtAppt = m_DocManager.RPMSDataTable(sSql, "DeleteAppointment");
1020
1021 Debug.Assert(dtAppt.Rows.Count == 1);
1022 DataRow r = dtAppt.Rows[0];
1023 string sErrorID = r["ERRORID"].ToString();
1024 if (sErrorID != "")
1025 return sErrorID;
1026
1027 if (this.m_appointments.AppointmentTable.ContainsKey(nApptID))
1028 {
1029 this.m_appointments.RemoveAppointment(nApptID);
1030
1031 // View responsible for deciding to redraw the grid; not the document now.
1032 //bool bRet = RefreshAvailabilitySchedule();
1033 //UpdateAllViews();
1034 }
1035 return "";
1036 }
1037
1038 public string AutoRebook(CGAppointment a, int nSearchType, int nMinimumDays, int nMaximumDays, out CGAppointment aRebook)
1039 {
1040 //If successful Returns "1" and new start date and time returned in aRebook
1041 //Otherwise, returns error message
1042
1043 CGAppointment aCopy = new CGAppointment();
1044 aCopy.CreateAppointment(a.StartTime, a.EndTime, a.Note, 0, a.Resource);
1045 aCopy.PatientID = a.PatientID;
1046 aCopy.PatientName = a.PatientName;
1047 aCopy.HealthRecordNumber = a.HealthRecordNumber;
1048 aCopy.AccessTypeID = a.AccessTypeID;
1049 aRebook = aCopy;
1050
1051 //Determine Rebook access type
1052 //nSearchType = -1: use current, -2: use any non-zero type, >0 use this access type id
1053 int nAVType = 0;
1054
1055 switch (nSearchType)
1056 {
1057 case -1:
1058 nAVType = a.AccessTypeID;
1059 break;
1060 case -2:
1061 nAVType = 0;
1062 break;
1063 default:
1064 nAVType = nSearchType;
1065 break;
1066 }
1067
1068 int nSlots = 0;
1069 string sSlots = "";
1070 int nAccessTypeID; //To compare with nAVType
1071
1072 DateTime dResult = new DateTime(); //StartTime of access block to autorebook into
1073
1074 //Next two are empty, but needed to pass to CreateAvailabilitySchedule
1075 ArrayList alAccessTypes = new ArrayList();
1076 string sSearchInfo = "";
1077
1078 //Find the StartTime of first availability block of this type for this clinic
1079 //between nMinimumDays and nMaximumDays
1080
1081 string sAVStart = a.StartTime.AddDays(nMinimumDays).ToString("M/d/yyyy@H:mm");
1082
1083 //dtAVEnd is the last day to search
1084 DateTime dtAVEnd = a.StartTime.AddDays(nMinimumDays + nMaximumDays);
1085 string sAVEnd = dtAVEnd.ToString("M/d/yyyy@H:mm");
1086
1087 //Increment start day to search a week (or so) at a time
1088 //30 is a test increment. Need to test different values for performance
1089 int nIncrement = (nMaximumDays < 30) ? nMaximumDays : 30;
1090
1091 //nCount and nCountEnd are the 'moving' counters
1092 //that I add to start and end to get the bracket
1093 //At the beginning of the DO loop, nCount and nCountEnd are already set
1094 bool bFinished = false;
1095 bool bFound = false;
1096
1097 DateTime dStart = a.StartTime.AddDays(nMinimumDays);
1098 // v 1.3 i18n support - FM Date passed insated of American Date
1099 string sStart = FMDateTime.Create(dStart).DateOnly.FMDateString;
1100 DateTime dEnd = dStart.AddDays(nIncrement);
1101 do
1102 {
1103 string sSql = "BSDX REBOOK NEXT BLOCK^" + sStart + "^" + a.Resource + "^" + nAVType.ToString();
1104 DataTable dtNextBlock = this.DocManager.RPMSDataTable(sSql, "NextBlock");
1105 Debug.Assert(dtNextBlock.Rows.Count == 1);
1106 DataRow drNextBlockRow = dtNextBlock.Rows[0];
1107
1108 object oNextBlock;
1109 oNextBlock = drNextBlockRow["NEXTBLOCK"];
1110 if (oNextBlock.GetType() == typeof(System.DBNull))
1111 break;
1112 DateTime dNextBlock = (DateTime)drNextBlockRow["NEXTBLOCK"];
1113 if (dNextBlock > dtAVEnd)
1114 {
1115 break;
1116 }
1117
1118 dStart = dNextBlock;
1119 dEnd = dStart.AddDays(nIncrement);
1120 if (dEnd > dtAVEnd)
1121 dEnd = dtAVEnd;
1122
1123 DataTable dtResult = CGSchedLib.CreateAvailabilitySchedule(m_DocManager, this.Resources, dStart, dEnd, alAccessTypes, ScheduleType.Resource, sSearchInfo);
1124 //Loop thru dtResult looking for a slot having the required availability type.
1125 //If found, set bFinished = true;
1126 foreach (DataRow dr in dtResult.Rows)
1127 {
1128
1129 sSlots = dr["SLOTS"].ToString();
1130 if (sSlots == "")
1131 sSlots = "0";
1132 nSlots = Convert.ToInt16(sSlots);
1133 if (nSlots > 0)
1134 {
1135 nAccessTypeID = 0; //holds the access type id of the availability block
1136 if (dr["ACCESS_TYPE"].ToString() != "")
1137 nAccessTypeID = Convert.ToInt16(dr["ACCESS_TYPE"].ToString());
1138 if ((nSearchType == -2) && (nAccessTypeID > 0)) //Match on any non-zero type
1139 {
1140 bFinished = true;
1141 bFound = true;
1142 dResult = (DateTime)dr["START_TIME"];
1143 break;
1144 }
1145 if (nAccessTypeID == nAVType)
1146 {
1147 bFinished = true;
1148 bFound = true;
1149 dResult = (DateTime)dr["START_TIME"];
1150 break;
1151 }
1152 }
1153 }
1154 dStart = dEnd.AddDays(1);
1155 dEnd = dStart.AddDays(nIncrement);
1156 if (dEnd > dtAVEnd)
1157 dEnd = dtAVEnd;
1158 } while (bFinished == false);
1159
1160 if (bFound == true)
1161 {
1162 aCopy.StartTime = dResult;
1163 aCopy.EndTime = dResult.AddMinutes(a.Duration);
1164 //Create the appointment
1165 //Set the AUTOREBOOKED flag
1166 //and store the "AutoRebooked To DateTime"
1167 //in each autorebooked appointment
1168 this.CreateAppointment(aCopy);
1169 SetAutoRebook(a, dResult);
1170 return "1";
1171 }
1172 else
1173 {
1174 return "0";
1175 }
1176 }
1177
1178 private void SetAutoRebook(CGAppointment a, DateTime dtRebookedTo)
1179 {
1180 string sApptKey = a.AppointmentKey.ToString();
1181 //string sRebookedTo = dtRebookedTo.ToString("M/d/yyyy@HH:mm");
1182 // i18n
1183 string sRebookedTo = FMDateTime.Create(dtRebookedTo).FMDateString;
1184 string sSql = "BSDX REBOOK SET^" + sApptKey + "^" + sRebookedTo;
1185 System.Data.DataTable dtRebook = m_DocManager.RPMSDataTable(sSql, "AutoRebook");
1186
1187 }
1188
1189 public string AppointmentNoShow(int nApptID, bool bNoShow)
1190 {
1191 /*
1192 * BSDX NOSHOW RPC Returns 1 in ERRORID if successfully sets NOSHOW flag in BSDX APPOINTMENT and, if applicable, File 2
1193 *Otherwise, returns negative numbers for failure and errormessage in ERRORTXT
1194 *
1195 */
1196
1197 string sTest = bNoShow.ToString();
1198 string sNoShow = (bNoShow == true) ? "1" : "0";
1199 string sSql = "BSDX NOSHOW^" + nApptID.ToString();
1200 sSql += "^";
1201 sSql += sNoShow;
1202
1203 DataTable dtAppt = m_DocManager.RPMSDataTable(sSql, "AppointmentNoShow");
1204
1205 Debug.Assert(dtAppt.Rows.Count == 1);
1206 DataRow r = dtAppt.Rows[0];
1207 string sErrorID = r["ERRORID"].ToString();
1208 if (sErrorID != "1")
1209 {
1210 return r["ERRORTEXT"].ToString();
1211 }
1212
1213 bool bRet = RefreshSchedule();
1214
1215 return sErrorID;
1216 }
1217
1218 #endregion Methods
1219
1220 }//End class
1221}//End namespace
Note: See TracBrowser for help on using the repository browser.