Example #1
0
        /// <summary>
        /// Split a timesheet entry into fragments. One fragment for each work type.
        /// </summary>
        /// <param name="timesheetEntryCursor"></param>
        /// <param name="minTimeFragmentSize"></param>
        /// <param name="database"></param>
        /// <param name="logger"></param>
        /// <returns></returns>
        public static List <TimesheetEntryFragment> GetTimesheetFragments(
            MongoCursor <BsonDocument> timesheetEntryCursor,
            int minTimeFragmentSize,
            MongoDatabase database,
            ILogger logger,
            HashSet <ObjectId> failedExports,
            HashSet <ObjectId> succeededExports,
            bool splitToDays = true)
        {
            var resultFragments = new List <TimesheetEntryFragment>();

            foreach (BsonDocument timesheetEntry in timesheetEntryCursor)
            {
                try
                {
                    TimesheetEntryWithDetails entryWithDetails = GetTimesheetEntryWithDetails(timesheetEntry, database);

                    List <TimesheetEntryFragment> entryFragments = SplitTimesheetEntryWithDetailsToFragments(entryWithDetails, logger);

                    if (splitToDays)
                    {
                        entryFragments = SplitTimesheetFragmentsToDays(entryFragments);
                    }

                    if (minTimeFragmentSize > 0)
                    {
                        RemoveTooShortFragments(entryFragments, logger, minTimeFragmentSize);
                    }

                    resultFragments.AddRange(entryFragments.ToArray());

                    succeededExports.Add((ObjectId)timesheetEntry[DBQuery.Id]);
                }
                catch (Exception ex)
                {
                    logger.LogError("Failed to handle timesheet entry. Skipping this entry", ex, timesheetEntry[DBQuery.Id]);
                    failedExports.Add((ObjectId)timesheetEntry[DBQuery.Id]);
                }
            }

            return(resultFragments);
        }
Example #2
0
        /// <summary>
        /// Add detail and pay type to TimesheetEntryWithDetails object.
        /// </summary>
        /// <param name="timesheetEntryWithDetails"></param>
        /// <param name="timesheetEntryDetail"></param>
        private static void AddDetailAndPayTypeToTimesheetEntryWithDetails(
            TimesheetEntryWithDetails timesheetEntryWithDetails,
            BsonDocument timesheetEntryDetail,
            MongoDatabase database)
        {
            BsonDocument timesheetEntryDetailType = GetTimesheetEntryDetailPayType(timesheetEntryDetail, database);

            // Order timesheet entry details by priority with a simple bubble sort. Times are
            // sorted with order so that we get overtime 100% before overtime 50% for example
            int newOrderNumber = 0;

            if (timesheetEntryDetailType.Contains("order"))
            {
                newOrderNumber = (int)timesheetEntryDetailType["order"];
            }

            int index = 0;

            foreach (Tuple <BsonDocument, BsonDocument> documentAndPayTypes in timesheetEntryWithDetails.EntryDetailsAndPayTypes)
            {
                int currentOrderNumber = 0;
                if (documentAndPayTypes.Item2.Contains("order"))
                {
                    currentOrderNumber = (int)documentAndPayTypes.Item2["order"];
                }

                if (newOrderNumber > currentOrderNumber)
                {
                    break;
                }

                index++;
            }

            timesheetEntryWithDetails.EntryDetailsAndPayTypes.Insert(
                index,
                new Tuple <BsonDocument, BsonDocument>(timesheetEntryDetail, timesheetEntryDetailType));
        }
Example #3
0
        /// <summary>
        /// Split timesheet entry so that it doesn't span to multiple days
        /// </summary>
        private static List <TimesheetEntryFragment> SplitTimesheetEntryWithDetailsToFragments(TimesheetEntryWithDetails entryWithDetails, ILogger logger)
        {
            BsonDocument timesheetEntry = entryWithDetails.TimesheetEntry;

            DateTime startTime             = ((DateTime)timesheetEntry["starttimestamp"]).ToLocalTime();
            DateTime currentEndTimestamp   = ((DateTime)timesheetEntry["endtimestamp"]).ToLocalTime();
            DateTime currentStartTimestamp = currentEndTimestamp;

            var timesheetEntryFragments = new List <TimesheetEntryFragment>();

            // Set to true is some details (such as contract pay) mean that regular hours shouldn't be exported.
            bool regularHoursInvalidated = false;

            // Handle detail types that don't count towards regular hours
            foreach (Tuple <BsonDocument, BsonDocument> detailAndPayType in entryWithDetails.EntryDetailsAndPayTypes)
            {
                if ((bool)detailAndPayType.Item2.GetValue("invalidatesregularhours", false))
                {
                    regularHoursInvalidated = true;
                }

                if ((bool)detailAndPayType.Item2.GetValue("countsasregularhours", false))
                {
                    continue;
                }

                var fragment = new TimesheetEntryFragment();

                fragment.Detail              = detailAndPayType.Item1;
                fragment.PayType             = detailAndPayType.Item2;
                fragment.ProjectCategoryBase = entryWithDetails.WorkerCategory;
                fragment.ProjectId           = entryWithDetails.ProjectId;
                fragment.WorkerId            = entryWithDetails.WorkerId;
                fragment.WorkerProfitCenter  = entryWithDetails.WorkerProfitCenter;

                // Use timesheet entry detail's note if present and base entry's note if not
                fragment.Note = (string)detailAndPayType.Item1.GetValue("note", string.Empty);

                if (string.IsNullOrEmpty(fragment.Note))
                {
                    fragment.Note = entryWithDetails.Note;
                }

                if (!fragment.Detail.Contains("duration"))
                {
                    continue;
                }

                TimeSpan duration = TimeSpan.FromMilliseconds((int)fragment.Detail["duration"]);

                fragment.Start = startTime;
                fragment.End   = fragment.Start + duration;

                timesheetEntryFragments.Add(fragment);
            }

            // Handle detail types that count towards regular hours
            foreach (Tuple <BsonDocument, BsonDocument> detailAndPayType in entryWithDetails.EntryDetailsAndPayTypes)
            {
                if (!(bool)detailAndPayType.Item2.GetValue("countsasregularhours", false))
                {
                    continue;
                }

                var fragment = new TimesheetEntryFragment();

                fragment.Detail              = detailAndPayType.Item1;
                fragment.PayType             = detailAndPayType.Item2;
                fragment.ProjectCategoryBase = entryWithDetails.WorkerCategory;
                fragment.ProjectId           = entryWithDetails.ProjectId;
                fragment.WorkerId            = entryWithDetails.WorkerId;
                fragment.WorkerProfitCenter  = entryWithDetails.WorkerProfitCenter;

                // Use timesheet entry detail's note if present and base entry's note if not
                fragment.Note = (string)detailAndPayType.Item1.GetValue("note", string.Empty);

                if (string.IsNullOrEmpty(fragment.Note))
                {
                    fragment.Note = entryWithDetails.Note;
                }

                if (!fragment.Detail.Contains("duration"))
                {
                    continue;
                }

                TimeSpan duration = TimeSpan.FromMilliseconds((int)fragment.Detail["duration"]);

                fragment.End          = currentEndTimestamp;
                currentStartTimestamp = currentEndTimestamp - duration;

                bool breakAfter = false;
                if (currentStartTimestamp < startTime)
                {
                    logger.LogWarning("Timesheet entry details are longer than entry itself. Skipping extra parts.", timesheetEntry[DBQuery.Id]);
                    currentStartTimestamp = startTime;
                    breakAfter            = true;
                }

                fragment.Start = currentStartTimestamp;

                timesheetEntryFragments.Add(fragment);

                // Continue processing backwards from current start timestamp
                currentEndTimestamp = currentStartTimestamp;

                if (breakAfter)
                {
                    break;
                }
            }

            // Add the "root" version timesheet entry not based on any detail (overtime etc.)
            if (currentStartTimestamp > startTime && !regularHoursInvalidated)
            {
                var fragment = new TimesheetEntryFragment();

                fragment.IsRootEntry         = true;
                fragment.IsTravelTime        = (bool)timesheetEntry.GetValue("istraveltime", false);
                fragment.ProjectCategoryBase = entryWithDetails.WorkerCategory;
                fragment.ProjectId           = entryWithDetails.ProjectId;
                fragment.WorkerId            = entryWithDetails.WorkerId;
                fragment.Note  = entryWithDetails.Note;
                fragment.Start = startTime;
                fragment.End   = currentStartTimestamp;
                fragment.WorkerProfitCenter = entryWithDetails.WorkerProfitCenter;

                timesheetEntryFragments.Add(fragment);
            }

            return(timesheetEntryFragments);
        }
Example #4
0
        /// <summary>
        /// Return tiemsheet entry split to different parts. First get all detail parts starting from end
        /// and counting using duration and what remains is the main part.
        /// </summary>
        /// <param name="timesheetEntry"></param>
        private static TimesheetEntryWithDetails GetTimesheetEntryWithDetails(
            BsonDocument timesheetEntry,
            MongoDatabase database)
        {
            if (!timesheetEntry.Contains("starttimestamp") || !timesheetEntry.Contains("endtimestamp"))
            {
                throw new HandlerException("Start or end time missing in timesheet entry.");
            }

            MongoCollection <BsonDocument> usersCollection = database.GetCollection("user");
            BsonDocument worker = usersCollection.FindOne(Query.EQ(DBQuery.Id, timesheetEntry["user"][0]));

            MongoCollection <BsonDocument> projectsCollection = database.GetCollection("project");
            BsonDocument project = projectsCollection.FindOne(Query.EQ(DBQuery.Id, (ObjectId)timesheetEntry["project"][0]));

            MongoCollection <BsonDocument> claContractsCollection = database.GetCollection("clacontract");
            BsonDocument claContract = claContractsCollection.FindOne(Query.EQ(DBQuery.Id, (ObjectId)worker["clacontract"][0]));

            MongoCollection <BsonDocument> profitCenterCollection = database.GetCollection("profitcenter");
            BsonDocument profitCenter = profitCenterCollection.FindOne(Query.EQ(DBQuery.Id, (ObjectId)worker["profitcenter"][0]));

            if (project == null)
            {
                throw new HandlerException("Project not found for timesheet entry.");
            }

            if (worker == null)
            {
                throw new HandlerException("Worker not found for timesheet entry.");
            }

            if (claContract == null)
            {
                throw new HandlerException("CLA contract not found for timesheet entry worker.");
            }

            if (profitCenter == null)
            {
                throw new HandlerException("Profit center not found for timesheet entry worker.");
            }

            if (!project.Contains("identifier"))
            {
                throw new HandlerException("Project is missing an identifier.");
            }

            if (!worker.Contains("identifier"))
            {
                throw new HandlerException("Worker is missing an identifier.");
            }

            var timesheetEntryWithDetails = new TimesheetEntryWithDetails();

            timesheetEntryWithDetails.WorkerCategory     = IntegrationHelpers.GetCategoryForWorker(claContract, profitCenter);
            timesheetEntryWithDetails.ProjectId          = (string)project["identifier"];
            timesheetEntryWithDetails.Note               = (string)timesheetEntry.GetValue("note", "");
            timesheetEntryWithDetails.WorkerId           = (string)worker["identifier"];
            timesheetEntryWithDetails.WorkerProfitCenter = (string)profitCenter["identifier"];

            MongoCollection <BsonDocument> timsheetEntryDetailsCollection = database.GetCollection("timesheetentry");
            MongoCursor <BsonDocument>     cursor = timsheetEntryDetailsCollection.Find(Query.EQ("parent", timesheetEntry[DBQuery.Id]));

            timesheetEntryWithDetails.TimesheetEntry = timesheetEntry;

            foreach (BsonDocument timesheetEntryDetail in cursor)
            {
                AddDetailAndPayTypeToTimesheetEntryWithDetails(timesheetEntryWithDetails, timesheetEntryDetail, database);
            }

            return(timesheetEntryWithDetails);
        }