564 lines
16 KiB
C#
564 lines
16 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Data;
|
|
//using System.Data.OleDb;
|
|
using System.Diagnostics;
|
|
using System.Drawing;
|
|
using System.Windows.Forms;
|
|
using IndianHealthService.BMXNet;
|
|
|
|
namespace IndianHealthService.ClinicalScheduling
|
|
{
|
|
/// <summary>
|
|
/// Contains array of availability blocks for a scheduling resource
|
|
/// </summary>
|
|
public class CGAVDocument : System.Object
|
|
{
|
|
public CGAVDocument()
|
|
{
|
|
m_AVBlocks = new CGAppointments();
|
|
m_sResourcesArray = new ArrayList();
|
|
}
|
|
|
|
#region Member Variables
|
|
|
|
public int m_nColumnCount; //todo: this should point to the view's member for column count
|
|
public int m_nTimeUnits;
|
|
public string m_sSecondary;
|
|
private string m_sDocName;
|
|
public ArrayList m_sResourcesArray;
|
|
public ScheduleType m_ScheduleType;
|
|
private DateTime m_dSelectedDate; //Holds the user's selection from the dtpicker
|
|
private DateTime m_dStartDate; //Beginning date of document data
|
|
private DateTime m_dEndDate; //Ending date of document data
|
|
public CGAppointments m_AVBlocks;
|
|
private CGDocumentManager m_DocManager;
|
|
private int m_nDocID; //Resource IEN in ^BSDXRES
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
/// <summary>
|
|
/// Resource IEN in ^BSDXRES
|
|
/// </summary>
|
|
public int ResourceID
|
|
{
|
|
get
|
|
{
|
|
return m_nDocID;
|
|
}
|
|
set
|
|
{
|
|
m_nDocID = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The list of Resource names
|
|
/// </summary>
|
|
public ArrayList Resources
|
|
{
|
|
get
|
|
{
|
|
return this.m_sResourcesArray;
|
|
}
|
|
set
|
|
{
|
|
this.m_sResourcesArray = value;
|
|
}
|
|
}
|
|
|
|
public CGDocumentManager DocManager
|
|
{
|
|
get
|
|
{
|
|
return m_DocManager;
|
|
}
|
|
set
|
|
{
|
|
m_DocManager = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the hashtable of Availability Blocks
|
|
/// </summary>
|
|
public CGAppointments AVBlocks
|
|
{
|
|
get
|
|
{
|
|
return m_AVBlocks;
|
|
}
|
|
set
|
|
{
|
|
m_AVBlocks = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Holds the date selected by the user in CGView.dateTimePicker1
|
|
/// </summary>
|
|
public DateTime SelectedDate
|
|
{
|
|
get
|
|
{
|
|
return this.m_dSelectedDate;
|
|
}
|
|
set
|
|
{
|
|
this.m_dSelectedDate = value;
|
|
bool bRet = false;
|
|
if (m_ScheduleType == ScheduleType.Resource)
|
|
{
|
|
bRet = this.WeekNeedsRefresh(1, m_dSelectedDate, out this.m_dStartDate, out this.m_dEndDate);
|
|
}
|
|
else
|
|
{
|
|
this.m_dStartDate = m_dSelectedDate;
|
|
this.m_dEndDate = m_dSelectedDate;
|
|
this.m_dEndDate = this.m_dEndDate.AddHours(23);
|
|
this.m_dEndDate = this.m_dEndDate.AddMinutes(59);
|
|
this.m_dEndDate = this.m_dEndDate.AddSeconds(59);
|
|
}
|
|
|
|
bRet = this.RefreshDaysSchedule();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the beginning date of the appointment document
|
|
/// </summary>
|
|
public DateTime StartDate
|
|
{
|
|
get
|
|
{
|
|
return this.m_dStartDate;
|
|
}
|
|
}
|
|
|
|
public string DocName
|
|
{
|
|
get
|
|
{
|
|
return this.m_sDocName;
|
|
}
|
|
set
|
|
{
|
|
this.m_sDocName = value;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Methods
|
|
|
|
public void ChangeAppointmentTime(CGAppointment pAppt, DateTime dNewStart, DateTime dNewEnd, string sResource)
|
|
{
|
|
try
|
|
{
|
|
DateTime dOldStart = pAppt.StartTime;
|
|
DateTime dOldEnd = pAppt.EndTime;
|
|
if ((dOldStart == dNewStart) && (dOldEnd == dNewEnd))
|
|
{ //no change in time
|
|
return;
|
|
}
|
|
|
|
foreach (CGAppointment a in m_AVBlocks.AppointmentTable.Values)
|
|
{
|
|
DateTime sStart2 = a.StartTime;
|
|
DateTime sEnd2 = a.EndTime;
|
|
if ((a.AppointmentKey != pAppt.AppointmentKey) && (CGSchedLib.TimesOverlap(dNewStart, dNewEnd, a.StartTime, a.EndTime)))
|
|
{
|
|
MessageBox.Show("Access blocks may not overlap.","Clinical Scheduling", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
|
|
return;
|
|
}
|
|
}
|
|
|
|
this.DeleteAvailability(pAppt.AppointmentKey);
|
|
pAppt.StartTime = dNewStart;
|
|
pAppt.EndTime = dNewEnd;
|
|
pAppt.Resource = sResource;
|
|
this.CreateAppointment(pAppt);
|
|
|
|
}
|
|
catch(Exception e)
|
|
{
|
|
MessageBox.Show("CGDocument::ChangeAppointmentTime failed: " + e.Message);
|
|
return;
|
|
}
|
|
}
|
|
|
|
public void DeleteAvailability(int nApptID)
|
|
{
|
|
if (this.m_AVBlocks.AppointmentTable.ContainsKey(nApptID))
|
|
{
|
|
string sSql = "BSDX CANCEL AVAILABILITY^" + nApptID.ToString();
|
|
DataTable dtAppt =m_DocManager.RPMSDataTable(sSql, "DeleteAvailability");
|
|
int nErrorID;
|
|
Debug.Assert(dtAppt.Rows.Count == 1);
|
|
DataRow r = dtAppt.Rows[0];
|
|
nErrorID = Convert.ToInt32(r["ERRORID"]);
|
|
this.m_AVBlocks.RemoveAppointment(nApptID);
|
|
UpdateAllViews();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called by LoadTemplate to create Access Block
|
|
/// Returns the IEN of the availability block in the RPMS BSDX AVAILABILITY file.
|
|
/// </summary>
|
|
public int CreateAppointmentAuto(CGAppointment rApptInfo)
|
|
{
|
|
try
|
|
{
|
|
string sStart;
|
|
string sEnd;
|
|
string sResource;
|
|
string sNote;
|
|
string sTypeID;
|
|
string sSlots;
|
|
|
|
//sStart = rApptInfo.StartTime.ToString("M-d-yyyy@HH:mm");
|
|
//sEnd = rApptInfo.EndTime.ToString("M-d-yyyy@HH:mm");
|
|
// i18n support
|
|
sStart = FMDateTime.Create(rApptInfo.StartTime).FMDateString;
|
|
sEnd = FMDateTime.Create(rApptInfo.EndTime).FMDateString;
|
|
sNote = rApptInfo.Note;
|
|
sResource = rApptInfo.Resource;
|
|
sTypeID = rApptInfo.AccessTypeID.ToString();
|
|
sSlots = rApptInfo.Slots.ToString();
|
|
|
|
CGAppointment aCopy = new CGAppointment();
|
|
aCopy.CreateAppointment(rApptInfo.StartTime, rApptInfo.EndTime, sNote, 0, sResource);
|
|
aCopy.AccessTypeID = rApptInfo.AccessTypeID;
|
|
aCopy.Slots = rApptInfo.Slots;
|
|
aCopy.IsAccessBlock = true;
|
|
|
|
string sSql = "BSDX ADD NEW AVAILABILITY^" + sStart + "^" + sEnd + "^" + sTypeID + "^" + sResource + "^" + sSlots + "^" + sNote;
|
|
DataTable dtAppt = m_DocManager.RPMSDataTable(sSql, "NewAvailability");
|
|
|
|
int nApptID;
|
|
int nErrorID;
|
|
|
|
Debug.Assert(dtAppt.Rows.Count == 1);
|
|
DataRow r = dtAppt.Rows[0];
|
|
nApptID =Convert.ToInt32(r["AVAILABILITYID"]);
|
|
nErrorID = Convert.ToInt32(r["ERRORID"]);
|
|
if (nErrorID != -1)
|
|
{
|
|
throw new Exception("VistA Error");
|
|
}
|
|
Debug.Write("CreateAvailabilityAuto -- new AV block created\n");
|
|
|
|
UpdateAllViews();
|
|
|
|
aCopy.AppointmentKey = nApptID;
|
|
return nApptID;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.Write("CGAVDocument.CreateAppointmentAuto Failed: " + ex.Message);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
|
|
public int CreateAppointment(CGAppointment rApptInfo)
|
|
{
|
|
try
|
|
{
|
|
string sStart;
|
|
string sEnd;
|
|
string sResource;
|
|
string sNote;
|
|
string sTypeID;
|
|
string sSlots;
|
|
|
|
//sStart = rApptInfo.StartTime.ToString("M-d-yyyy@HH:mm");
|
|
//sEnd = rApptInfo.EndTime.ToString("M-d-yyyy@HH:mm");
|
|
// i18n support
|
|
sStart = FMDateTime.Create(rApptInfo.StartTime).FMDateString;
|
|
sEnd = FMDateTime.Create(rApptInfo.EndTime).FMDateString;
|
|
sNote = rApptInfo.Note;
|
|
sResource = rApptInfo.Resource;
|
|
sTypeID = rApptInfo.AccessTypeID.ToString();
|
|
sSlots = rApptInfo.Slots.ToString();
|
|
|
|
CGAppointment aCopy = new CGAppointment();
|
|
aCopy.CreateAppointment(rApptInfo.StartTime, rApptInfo.EndTime, sNote, 0, sResource);
|
|
aCopy.AccessTypeID = rApptInfo.AccessTypeID;
|
|
aCopy.Slots = rApptInfo.Slots;
|
|
aCopy.IsAccessBlock = true;
|
|
|
|
aCopy.AccessTypeName = this.AccessNameFromID(aCopy.AccessTypeID);
|
|
|
|
foreach (CGAppointment a in this.m_AVBlocks.AppointmentTable.Values)
|
|
{
|
|
DateTime sStart2 = a.StartTime;
|
|
DateTime sEnd2 = a.EndTime;
|
|
if (CGSchedLib.TimesOverlap(aCopy.StartTime, aCopy.EndTime, a.StartTime, a.EndTime))
|
|
{
|
|
// throw new Exception("Access blocks may not overlap.");
|
|
MessageBox.Show("Access blocks may not overlap.","Clinical Scheduling", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
|
|
return -1;
|
|
}
|
|
|
|
}
|
|
|
|
string sSql = "BSDX ADD NEW AVAILABILITY^" + sStart + "^" + sEnd + "^" + sTypeID + "^" + sResource + "^" + sSlots + "^" + sNote;
|
|
DataTable dtAppt =m_DocManager.RPMSDataTable(sSql, "NewAvailability");
|
|
|
|
int nApptID;
|
|
int nErrorID;
|
|
|
|
Debug.Assert(dtAppt.Rows.Count == 1);
|
|
DataRow r = dtAppt.Rows[0];
|
|
nApptID =Convert.ToInt32(r["AVAILABILITYID"]);
|
|
nErrorID = Convert.ToInt32(r["ERRORID"]);
|
|
if (nErrorID != -1)
|
|
{
|
|
throw new Exception("VistA Error");
|
|
}
|
|
Debug.Write("CreateAvailability -- new AV block created\n");
|
|
|
|
aCopy.AppointmentKey = nApptID;
|
|
|
|
this.m_AVBlocks.AddAppointment(aCopy);
|
|
|
|
UpdateAllViews();
|
|
|
|
return nApptID;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.Write("CGAVDocument.CreateAppointment Failed: " + ex.Message);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
private int GetTotalMinutes(DateTime dDate)
|
|
{
|
|
return ((dDate.Hour * 60) + dDate.Minute);
|
|
}
|
|
|
|
private string AccessNameFromID(int nAccessTypeID)
|
|
{
|
|
DataView dvTypes = new DataView(this.DocManager.GlobalDataSet.Tables["AccessTypes"]);
|
|
dvTypes.Sort = "BMXIEN ASC";
|
|
int nRow = dvTypes.Find(nAccessTypeID);
|
|
DataRowView drv = dvTypes[nRow];
|
|
string sAccessTypeName = drv["ACCESS_TYPE_NAME"].ToString();
|
|
return sAccessTypeName;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Update availability block schedule based on info in RPMS
|
|
/// </summary>
|
|
public bool RefreshDaysSchedule()
|
|
{
|
|
DateTime dStart;
|
|
DateTime dEnd;
|
|
int nKeyID;
|
|
string sNote;
|
|
string sResource;
|
|
string sAccessType;
|
|
string sSlots;
|
|
CGAppointment pAppointment;
|
|
CGDocumentManager pApp = CGDocumentManager.Current;
|
|
DataTable rAppointmentSchedule;
|
|
|
|
this.m_AVBlocks.ClearAllAppointments();
|
|
|
|
ArrayList apptTypeIDs = new ArrayList();
|
|
|
|
rAppointmentSchedule = CGSchedLib.CreateAssignedSlotSchedule(m_DocManager, (string) m_sResourcesArray[0], this.m_dStartDate, this.m_dEndDate, apptTypeIDs,/* */ this.m_ScheduleType, "0");
|
|
CGSchedLib.OutputArray(rAppointmentSchedule, "rAppointmentSchedule");
|
|
foreach (DataRow r in rAppointmentSchedule.Rows)
|
|
{
|
|
nKeyID = Convert.ToInt32(r["AVAILABILITYID"].ToString());
|
|
if (nKeyID > 0)
|
|
{
|
|
dStart = (DateTime) r["START_TIME"];
|
|
dEnd = (DateTime) r["END_TIME"];
|
|
sNote = r["NOTE"].ToString();
|
|
sResource = r["RESOURCE"].ToString();
|
|
sAccessType = r["ACCESS_TYPE"].ToString();
|
|
sSlots = r["SLOTS"].ToString();
|
|
|
|
pAppointment = new CGAppointment();
|
|
pAppointment.CreateAppointment(dStart, dEnd, sNote, nKeyID, sResource);
|
|
pAppointment.AccessTypeID = Convert.ToInt16(sAccessType);
|
|
pAppointment.Slots = Convert.ToInt16(sSlots);
|
|
pAppointment.IsAccessBlock = true;
|
|
pAppointment.AccessTypeName = this.AccessNameFromID(pAppointment.AccessTypeID);
|
|
|
|
this.m_AVBlocks.AddAppointment(pAppointment);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Given a selected date,
|
|
/// Calculates StartDay and End Day and returns them in output params.
|
|
/// nWeeks == number of Weeks to display
|
|
/// nColumnCount is number of days displayed per week.
|
|
/// If 5 columns, begin on Second Day of Week
|
|
/// If 7 Columns, begin on First Day of Week
|
|
/// (this is a change from the hardcoded behavior for US-based calendars)
|
|
///
|
|
/// Returns TRUE if the document's data needs refreshing based on
|
|
/// this newly selected date.
|
|
/// </summary>
|
|
/// TODO: This is a duplicate of the method in CGDocument. We need to put them together.
|
|
public bool WeekNeedsRefresh(int nWeeks, DateTime SelectedDate,
|
|
out DateTime WeekStartDay, out DateTime WeekEndDay)
|
|
{
|
|
DateTime OldStartDay = m_dStartDate;
|
|
DateTime OldEndDay = m_dEndDate;
|
|
// Week start based on thread locale
|
|
int nStartWeekDay = (int)System.Threading.Thread.CurrentThread.CurrentCulture.DateTimeFormat.FirstDayOfWeek;
|
|
// Current Day
|
|
int nWeekDay = (int)SelectedDate.DayOfWeek; //0 == Sunday
|
|
|
|
// this offset gets approrpriate day based on locale.
|
|
int nOff = (nStartWeekDay + 1) % 7;
|
|
TimeSpan ts = new TimeSpan(nWeekDay - nOff, 0, 0, 0); //d,h,m,s
|
|
|
|
// if ts is negative, we will jump to the next week in the logic.
|
|
// to show the correct week, add 7. Confusing, I know.
|
|
if (ts < new TimeSpan()) ts = ts + new TimeSpan(7, 0, 0, 0);
|
|
|
|
if (m_nColumnCount == 1) // if one column start and end on the same day.
|
|
{
|
|
ts = new TimeSpan(0, 23, 59, 59);
|
|
WeekStartDay = SelectedDate;
|
|
WeekEndDay = WeekStartDay + ts;
|
|
}
|
|
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.
|
|
{
|
|
// if picked day is start of week (Sunday in US), start in the next day since that's the first day of work week
|
|
// else, just substract the calculated time span to get to the start of week (first work day)
|
|
WeekStartDay = (nWeekDay == nStartWeekDay) ? SelectedDate + new TimeSpan(1, 0, 0, 0) : SelectedDate - ts;
|
|
// End day calculation
|
|
int nEnd = 3;
|
|
ts = new TimeSpan((7 * nWeeks) - nEnd, 23, 59, 59);
|
|
WeekEndDay = WeekStartDay + ts;
|
|
}
|
|
else // if 7 column start at the 1st day of this week and end in 6:23:59:59 days.
|
|
{
|
|
// 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.
|
|
WeekStartDay = (nWeekDay == nStartWeekDay) ? SelectedDate : SelectedDate - ts - new TimeSpan(1, 0, 0, 0);
|
|
// End day calculation
|
|
int nEnd = 1;
|
|
ts = new TimeSpan((7 * nWeeks) - nEnd, 23, 59, 59);
|
|
WeekEndDay = WeekStartDay + ts;
|
|
}
|
|
|
|
bool bRet = ((WeekStartDay.Date != OldStartDay.Date) || (WeekEndDay.Date != OldEndDay.Date));
|
|
return bRet;
|
|
}
|
|
|
|
public void OnOpenDocument()
|
|
{
|
|
//Create new Document
|
|
// DateTime dStart;
|
|
// DateTime dEnd;
|
|
|
|
Debug.Assert(m_sResourcesArray.Count > 0);
|
|
|
|
m_sSecondary = "";
|
|
|
|
m_ScheduleType = (m_sResourcesArray.Count == 1) ? ScheduleType.Resource: ScheduleType.Clinic;
|
|
|
|
bool bRet = false;
|
|
//Set initial From and To dates based on current day
|
|
// DateTime dDate = new DateTime(2001,12,05); //test line
|
|
DateTime dDate = DateTime.Today;
|
|
if (m_ScheduleType == ScheduleType.Resource)
|
|
{
|
|
bRet = this.WeekNeedsRefresh(1,dDate, out this.m_dStartDate, out this.m_dEndDate);
|
|
}
|
|
else
|
|
{
|
|
this.m_dStartDate = dDate;
|
|
this.m_dEndDate = dDate;
|
|
this.m_dEndDate = this.m_dEndDate.AddHours(23);
|
|
this.m_dEndDate = this.m_dEndDate.AddMinutes(59);
|
|
this.m_dEndDate = this.m_dEndDate.AddSeconds(59);
|
|
}
|
|
|
|
bRet = this.RefreshDaysSchedule();
|
|
|
|
CGAVView view = null;
|
|
//If this document already has a view, the use it
|
|
Hashtable h = CGDocumentManager.Current.AvailabilityViews;
|
|
CGAVDocument d;
|
|
bool bReuseView = false;
|
|
foreach (CGAVView v in h.Keys)
|
|
{
|
|
d = (CGAVDocument) h[v];
|
|
if (d == this)
|
|
{
|
|
view = v;
|
|
bReuseView = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//Otherwise, create new View
|
|
if (bReuseView == false)
|
|
{
|
|
view = new CGAVView();
|
|
|
|
view.DocManager = this.DocManager;
|
|
view.StartDate = m_dStartDate;
|
|
|
|
//Assign the document to the view
|
|
view.Document = this;
|
|
|
|
//Link the calendargrid's appointments table to the document's table
|
|
view.AVBlocks = this.AVBlocks;
|
|
|
|
view.Text = "Edit Availability - " + this.DocName;
|
|
view.Show();
|
|
}
|
|
|
|
this.UpdateAllViews();
|
|
|
|
}
|
|
|
|
public void AddResource(string sResource)
|
|
{
|
|
//TODO: Test that resource is not currently in list, that it IS a resource, etc
|
|
this.m_sResourcesArray.Add(sResource);
|
|
this.UpdateAllViews();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calls each AVview associated with this AVdocument and tells it to update itself
|
|
/// </summary>
|
|
public void UpdateAllViews()
|
|
{
|
|
//iterate through all views and call update.
|
|
Hashtable h = CGDocumentManager.Current.AvailabilityViews;
|
|
|
|
CGAVDocument d;
|
|
foreach (CGAVView v in h.Keys)
|
|
{
|
|
d = (CGAVDocument) h[v];
|
|
if (d == this)
|
|
{
|
|
v.UpdateArrays();
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
}//End class
|
|
}
|