/// <summary> /// Read all log entries from Xml file. /// </summary> /// <param name="dataXml"></param> static public void ReadLogEntries(XmlDocument dataXml) { // Load indvidual time log entries XmlNodeList dayNodes = dataXml.SelectNodes("/ptt_data/time_logs/day"); foreach (XmlNode dayNode in dayNodes) { XmlAttribute attr = dayNode.Attributes["date"]; DateTime logDate = DateTime.Parse(attr.Value); SortableBindingList <TimeLogEntry> logEntries = new SortableBindingList <TimeLogEntry>(); TimeLogEntry.AllTimeLogEntries[logDate] = logEntries; XmlNodeList logNodes = dayNode.SelectNodes("log"); foreach (XmlNode node in logNodes) { TimeLogEntry entry = new TimeLogEntry(node, logDate); logEntries.Add(entry); } } }
/// <summary> /// Print Report showing daily activity (optionally showing all tasks) /// </summary> /// <param name="e"></param> private void PrintActivityPage(PrintPageEventArgs e, Point drawLocation) { bool skipRestOfPage = m_needToShowOverallTotals; while (!skipRestOfPage && drawLocation.Y < e.MarginBounds.Bottom && m_currentDateBeingPrinted <= m_endDate) { LogFileManager.LoadDataFileIfNotAlreadyLoaded(m_currentDateBeingPrinted); SortableBindingList <TimeLogEntry> logs = TimeLogEntry.LogsForDay(m_currentDateBeingPrinted); if (null == logs || 0 == logs.Count) { if (!m_skipDaysWithoutTasks) { PrintDayHeaderLine(e, ref drawLocation); e.Graphics.DrawString(Properties.Resources.ReportStrNoActivity, m_detailFont, m_detailBrush, drawLocation); drawLocation.Y += m_detailFont.Height; m_lastDayDisplayed = m_currentDateBeingPrinted; } // Move to next day m_currentDateBeingPrinted = m_currentDateBeingPrinted.AddDays(1); m_customersSeenForDay.Clear(); m_indexOfTaskInCurrentDay = 0; continue; } // If not already past end and do not need to show summary from last page else if (!m_needToShowSummary && m_indexOfTaskInCurrentDay < logs.Count) { TimeLogEntry log = logs[m_indexOfTaskInCurrentDay]; if (!m_customersSeenForDay.Contains(log.Customer.Name)) { m_customersSeenForDay.Add(log.Customer.Name); } // Add to overall totals TimeSpan[] timeSpans = null; if (!m_customerSummaryData.ContainsKey(log.Customer.Name)) { timeSpans = new TimeSpan[2]; m_customerSummaryData[log.Customer.Name] = timeSpans; } else { timeSpans = m_customerSummaryData[log.Customer.Name]; } if (log.IsBillable) { timeSpans[BILLABLE_TIME_INDEX] += log.Duration; } else { timeSpans[NON_BILLABLE_TIME_INDEX] += log.Duration; } if (m_lastDayDisplayed != m_currentDateBeingPrinted) { PrintDayHeaderLine(e, ref drawLocation); m_lastDayDisplayed = m_currentDateBeingPrinted; } if (m_showTasks) { // {0} for {1} from {2} to {3}. Duration{4}({5}) Billable:{6} string durationStr = string.Format("{0}:{1}", log.Duration.Hours.ToString(), log.Duration.Minutes.ToString("00")); string durationPercentStr = string.Format("{0}.{1}", log.Duration.Hours.ToString(), ((int)(log.Duration.Minutes * 100) / 60).ToString("00")); string billiableFlagStr = ""; if (!log.IsBillable) { billiableFlagStr = Properties.Resources.ReportStrNotBillableReportTag; } string taskLine = string.Format(Properties.Resources.ReportStrActivityLine, log.Task.Name, log.Customer.Name, log.StartTime.ToShortTimeString(), log.StopTime.ToShortTimeString(), durationStr, durationPercentStr, billiableFlagStr); e.Graphics.DrawString(taskLine, m_detailFont, m_detailBrush, drawLocation); drawLocation.Y += m_detailFont.Height; } // Move to next item (could bump to next day) ++m_indexOfTaskInCurrentDay; } Point startPoint; Point endPoint; if (null != logs && m_indexOfTaskInCurrentDay >= logs.Count) { if (0 < m_customersSeenForDay.Count) { m_needToShowSummary = true; // See if there is enough room to display the summary (# customers + 1 for totals + 1 for horz line + 1 for header) int summaryHeight = m_summaryFont.Height * (3 + m_customersSeenForDay.Count); if (drawLocation.Y + summaryHeight > e.MarginBounds.Bottom) { // Must wait for next page skipRestOfPage = true; break; } m_needToShowSummary = false; // we are going to print it so clear flag // OK, we have enough room for the summary DrawSummaryHeader(ref drawLocation, e, Properties.Resources.ReportStrCustomerColHeader); TimeSpan dailyTotalTime = new TimeSpan(); TimeSpan dailyBillableTime = new TimeSpan(); TimeSpan dailyNonBillableTime = new TimeSpan(); // Sort customers alphabetically m_customersSeenForDay.Sort(); foreach (string customerName in m_customersSeenForDay) { TimeSpan billableTime = new TimeSpan(); TimeSpan nonBillableTime = new TimeSpan(); // Total up time for this customer foreach (TimeLogEntry log in logs) { if (log.Customer.Name == customerName) { if (log.IsBillable) { billableTime += log.Duration; dailyBillableTime += log.Duration; } else { nonBillableTime += log.Duration; dailyNonBillableTime += log.Duration; } } } DrawSummaryLine(ref drawLocation, e, customerName, billableTime, nonBillableTime); } // Update the overall totals m_overallBillableTime += dailyBillableTime; m_overallNonBillableTime += dailyNonBillableTime; DrawSummaryLine(ref drawLocation, e, Properties.Resources.ReportStrTotalTitle, dailyBillableTime, dailyNonBillableTime); // Draw a dividing line between details and summary (if showing details) startPoint = drawLocation; startPoint.Y += m_summaryFont.Height / 2; endPoint = new Point(e.MarginBounds.Left + e.MarginBounds.Width / 2, drawLocation.Y + m_summaryFont.Height / 2); e.Graphics.DrawLine(m_linePen, startPoint, endPoint); drawLocation.Y += m_summaryFont.Height; } m_indexOfTaskInCurrentDay = 0; m_currentDateBeingPrinted = m_currentDateBeingPrinted.AddDays(1); m_customersSeenForDay.Clear(); } } e.HasMorePages = skipRestOfPage || (m_currentDateBeingPrinted < m_endDate); // See if we need to display the grand totals if (m_needToShowOverallTotals || !e.HasMorePages) { m_needToShowOverallTotals = true; // See if there is enough room to display the summary (# customers + 1 for totals + 1 for horz line + 1 for header) int summaryHeight = m_summaryFont.Height * (3 + m_customerSummaryData.Count); if (drawLocation.Y + summaryHeight > e.MarginBounds.Bottom) { e.HasMorePages = true; // Must put summary on last page } else { m_needToShowOverallTotals = false; // we are going to print it so clear flag if (drawLocation.Y > e.MarginBounds.Top) { drawLocation.Y += m_summaryFont.Height; } // draw a horz line all the way across Point startPoint = drawLocation; startPoint.Y += m_summaryFont.Height / 2; Point endPoint = new Point(e.MarginBounds.Right, drawLocation.Y + m_summaryFont.Height / 2); e.Graphics.DrawLine(m_linePen, startPoint, endPoint); drawLocation.Y += m_summaryFont.Height; DrawSummaryHeader(ref drawLocation, e, Properties.Resources.ReportStrCustomerColHeader); // Sort customers alphabetically List <string> allCustomerNames = new List <string>(); foreach (string key in m_customerSummaryData.Keys) { allCustomerNames.Add(key); } allCustomerNames.Sort(); // Now show each customer's totals TimeSpan overallBillable = new TimeSpan(); TimeSpan overallNonBillable = new TimeSpan(); TimeSpan overallTotal = new TimeSpan(); foreach (string name in allCustomerNames) { TimeSpan[] timeSpans = m_customerSummaryData[name]; DrawSummaryLine(ref drawLocation, e, name, timeSpans[BILLABLE_TIME_INDEX], timeSpans[NON_BILLABLE_TIME_INDEX]); // Add to global total overallBillable += timeSpans[BILLABLE_TIME_INDEX]; overallNonBillable += timeSpans[NON_BILLABLE_TIME_INDEX]; overallTotal += timeSpans[BILLABLE_TIME_INDEX] + timeSpans[NON_BILLABLE_TIME_INDEX]; } DrawSummaryLine(ref drawLocation, e, Properties.Resources.ReportStrTotalTitle, overallBillable, overallNonBillable); e.HasMorePages = false; // this is the end } } }
/// <summary> /// Run through all tasks in given time period and calculate sums. /// </summary> private void CalculateSummaries() { if (m_showTasks) { m_taskSummaryData = new Dictionary <string, Dictionary <string, TimeSpan[]> >(); } while (m_currentDateBeingPrinted <= m_endDate) { LogFileManager.LoadDataFileIfNotAlreadyLoaded(m_currentDateBeingPrinted); SortableBindingList <TimeLogEntry> logs = TimeLogEntry.LogsForDay(m_currentDateBeingPrinted); if (null != logs && 0 != logs.Count) { foreach (TimeLogEntry log in logs) { if (m_showTasks) { // Update task level totals Dictionary <string, TimeSpan[]> taskList = null; // Get the task list for this customer (or create one) if (m_taskSummaryData.ContainsKey(log.Customer.Name)) { taskList = m_taskSummaryData[log.Customer.Name]; } else { taskList = new Dictionary <string, TimeSpan[]>(); m_taskSummaryData[log.Customer.Name] = taskList; } // See if this task exists yet TimeSpan[] taskTimes = null; if (taskList.ContainsKey(log.Task.Name)) { taskTimes = taskList[log.Task.Name]; } else { taskTimes = new TimeSpan[2]; } if (log.IsBillable) { taskTimes[0] += log.Duration; } else { taskTimes[1] += log.Duration; } taskList[log.Task.Name] = taskTimes; } // Update customer level totals TimeSpan[] customerTotals = null; if (m_customerSummaryData.ContainsKey(log.Customer.Name)) { customerTotals = m_customerSummaryData[log.Customer.Name]; } else { customerTotals = new TimeSpan[2]; m_customerSummaryData[log.Customer.Name] = customerTotals; } if (log.IsBillable) { customerTotals[0] += log.Duration; } else { customerTotals[1] += log.Duration; } } } // Move to next day m_currentDateBeingPrinted = m_currentDateBeingPrinted.AddDays(1); } // Sort customer names foreach (string s in m_customerSummaryData.Keys) { m_customersSeenForDay.Add(s); } m_customersSeenForDay.Sort(); // Get ready to run through the list m_customerEnumerator = m_customersSeenForDay.GetEnumerator(); m_needNextCustomer = true; m_needToShowSummary = true; }
/// <summary> /// Adjust existing log times based on the new log entry. /// </summary> /// <param name="newLog">The log just added.</param> static public void AdjustTimeEntriesForNewItem(TimeLogEntry newLog) { SortableBindingList <TimeLogEntry> currentList = LogsForDay(newLog.StartTime); // First adjust for any overlaps with the new entry taking precedence for (int rowIdx = currentList.Count - 1; rowIdx >= 0; --rowIdx) { TimeLogEntry log = currentList[rowIdx]; if (object.ReferenceEquals(log, newLog)) { continue; // skip yourself } // Is the current log is within the new one? if (newLog.StartTime < log.StartTime && newLog.StopTime > log.StopTime) { // 2 Options, delete this log or split up new one // (I hate putting UI into this class but it would be more work than it is worth to have a callback or something) System.Windows.Forms.DialogResult result = System.Windows.Forms.MessageBox.Show("An old log entry falls in the time period specified for this new log. Do you want to delete the old log entry? If you choose No the new entry will be split around it.", "Log Overlap", System.Windows.Forms.MessageBoxButtons.YesNo, System.Windows.Forms.MessageBoxIcon.Question); if (System.Windows.Forms.DialogResult.Yes == result) { // Delete old line currentList.RemoveAt(rowIdx); } else { // Split new line into multiple lines TimeLogEntry newAdjustmentLog = TimeLogEntry.CreateNewLogEntryFromExisingLog(newLog); // New log will be the last one newAdjustmentLog.StartTime = newLog.StartTime; newAdjustmentLog.StopTime = log.StartTime.AddSeconds(-1); newLog.StartTime = log.StopTime.AddSeconds(1); } } // Is the new log inside of this log? else if (newLog.StartTime > log.StartTime && newLog.StartTime < log.StopTime) { // The new log starts in the middle of this log so adjust // First see if the new log is entirely contained in the old log if (newLog.StopTime < log.StopTime) { // Yes the new log is smack in the middle of the old one. // So, split the old one into 2 entries TimeLogEntry newAdjustmentLog = TimeLogEntry.CreateNewLogEntryFromExisingLog(log); newAdjustmentLog.StartTime = newLog.StopTime.AddSeconds(1); log.StopTime = newLog.StartTime.AddSeconds(-1); // Swap interruption time if needed TimeSpan zeroTimeSpan = new TimeSpan(); if (zeroTimeSpan > log.Duration) { if (log.InterruptionTime > newAdjustmentLog.Duration) { // Divide evenly between both TimeSpan halfOfInterruption = new TimeSpan(log.InterruptionTime.Ticks / 2); log.InterruptionTime = halfOfInterruption; newAdjustmentLog.InterruptionTime = halfOfInterruption; } else { // Just swap who gets the interrupt time newAdjustmentLog.InterruptionTime = log.InterruptionTime; log.InterruptionTime = zeroTimeSpan; } } break; // no need to continue } else { log.StopTime = newLog.StartTime.AddSeconds(-1); } } else if (newLog.StopTime > log.StartTime && newLog.StopTime < log.StopTime) { log.StartTime = newLog.StopTime.AddSeconds(1); } } }
static public void SortLogs(SortableBindingList <TimeLogEntry> currentList) { currentList.Sort("StartTime", ListSortDirection.Ascending); }