Esempio n. 1
0
        /// <summary>
        /// Add a snapshot for the project.
        /// </summary>
        /// <param name="project"></param>
        /// <param name="model"></param>
        /// <param name="metadata"></param>
        private void AddSnapshot(Entity.Project project, Model.ImportProjectModel model, Entity.Models.DisposalProjectSnapshotMetadata metadata)
        {
            var today = model.SnapshotOn ?? DateTime.Today.ToUniversalTime(); // Allows for all imports done on the same day to create the same snapshot time.

            metadata.NetProceeds = model.PriorNetProceeds;                    // Temporarily set it to the prior amount for the snapshot.
            var snapshot = new Entity.ProjectSnapshot(project)
            {
                SnapshotOn = today,
                Metadata   = JsonSerializer.Serialize(metadata, _serializerOptions)
            };

            project.Snapshots.Add(snapshot);
            metadata.NetProceeds = model.NetProceeds; // Set it back to the current value.
        }
Esempio n. 2
0
        /// <summary>
        /// Override the model values with the specified defaults.
        /// </summary>
        /// <param name="model"></param>
        /// <param name="fromSnapshot"></param>
        /// <param name="defaults"></param>
        private void ApplyDefaults(Model.ImportProjectModel model, DateTime?fromSnapshot = null, string[] defaults = null)
        {
            // Use defaults if required
            if (defaults?.Any() ?? false)
            {
                var props = typeof(Model.ImportProjectModel).GetCachedProperties();
                foreach (var kv in defaults)
                {
                    var keyValue = kv.Trim().Split("=");

                    if (keyValue.Length < 2)
                    {
                        throw new ArgumentException($"Argument '{kv}' is not valid.");
                    }

                    var prop       = props.FirstOrDefault(p => String.Compare(p.Name, keyValue[0], true) == 0);
                    var modelValue = prop?.GetValue(model);
                    if (prop != null && modelValue == null || modelValue.Equals(prop.PropertyType.GetDefault()))
                    {
                        if (prop.PropertyType == typeof(DateTime) || prop.PropertyType == typeof(DateTime?))
                        {
                            var value = DateTime.Parse(keyValue[1]);
                            prop.SetValue(model, value);
                        }
                        else
                        {
                            var value = Convert.ChangeType(keyValue[1], prop.PropertyType);
                            prop.SetValue(model, value);
                        }
                    }
                }
            }

            if (fromSnapshot.HasValue && !model.SnapshotOn.HasValue)
            {
                model.SnapshotOn = fromSnapshot;
            }
        }
Esempio n. 3
0
        /// <summary>
        /// Copy the 'model' project properties into existing projects, or add new projects to PIMS.
        /// </summary>
        /// <param name="project"></param>
        /// <param name="model"></param>
        /// <param name="stopOnError"></param>
        /// <param name="defaults"></param>
        /// <returns></returns>
        private Entity.Project Merge(Entity.Project project, Model.ImportProjectModel model, bool stopOnError = true, string[] defaults = null)
        {
            try
            {
                // Use defaults if required
                if (defaults?.Any() ?? false)
                {
                    var props = typeof(Model.ImportProjectModel).GetCachedProperties();
                    foreach (var kv in defaults)
                    {
                        var keyValue = kv.Trim().Split("=");

                        if (keyValue.Length < 2)
                        {
                            throw new ArgumentException($"Argument '{kv}' is not valid.");
                        }

                        var prop       = props.FirstOrDefault(p => String.Compare(p.Name, keyValue[0], true) == 0);
                        var modelValue = prop?.GetValue(model);
                        if (prop != null && modelValue == null || modelValue.Equals(prop.PropertyType.GetDefault()))
                        {
                            var value = Convert.ChangeType(keyValue[1], prop.PropertyType);
                            prop.SetValue(model, value);
                        }
                    }
                }

                _logger.LogDebug($"Importing Project '{model.ProjectNumber}', agency:'{model.Agency}', workflow:'{model.Workflow}', status:'{model.Status}'");

                project ??= new Entity.Project();

                project.ProjectNumber      = model.ProjectNumber;
                project.Name               = model.Description;
                project.Description        = model.Description;
                project.Manager            = model.Manager;
                project.ActualFiscalYear   = model.ActualFiscalYear;
                project.ReportedFiscalYear = model.ReportedFiscalYear;

                project.Agency      = GetAgency(model.Agency);
                project.AgencyId    = project.Agency.Id;
                project.Workflow    = GetWorkflow(model.Workflow);
                project.WorkflowId  = project.Workflow.Id;
                project.Status      = GetStatus(model.Status);
                project.StatusId    = project.Status.Id;
                project.TierLevel   = GetTier(model.Estimated, project.Properties.Count()); // TODO: Need to import or link project properties.
                project.TierLevelId = project.TierLevel.Id;
                project.Risk        = GetRisk(model.Risk);
                project.RiskId      = project.Risk.Id;

                project.OcgFinancialStatement = model.OcgFinancialStatement;
                project.NetBook              = model.NetBook;
                project.Estimated            = model.Estimated;
                project.ProgramCost          = model.ProgramCost;
                project.SalesCost            = model.SalesCost;
                project.InterestComponent    = model.InterestComponent;
                project.NetProceeds          = model.NetProceeds;
                project.GainLoss             = model.GainLoss;
                project.SaleWithLeaseInPlace = model.SaleWithLeaseInPlace;

                if (model.PriorNetProceeds.HasValue)
                {
                    project.Snapshots.Add(new Entity.ProjectSnapshot(project)
                    {
                        NetProceeds = model.PriorNetProceeds.Value
                    });
                }

                project.MarketedOn  = model.MarketedOn;
                project.CompletedOn = model.CompletedOn;

                project.PrivateNote = model.PrivateNote;

                // The data doesn't provide the purchasing agency information so the response will be the current owning agency.
                if (model.AgencyResponseDate.HasValue)
                {
                    var response = project.Responses.FirstOrDefault(r => r.AgencyId == project.AgencyId);
                    if (response == null)
                    {
                        project.Responses.Add(new Entity.ProjectAgencyResponse(project, project.Agency, Entity.NotificationResponses.Watch, model.AgencyResponseDate));
                    }
                    else
                    {
                        response.Response   = Entity.NotificationResponses.Watch;
                        response.ReceivedOn = model.AgencyResponseDate ?? response.ReceivedOn;
                    }
                }

                if (!String.IsNullOrWhiteSpace(model.FinancialNote))
                {
                    var financialNote = project.Notes.FirstOrDefault(n => n.NoteType == Entity.NoteTypes.Financial);
                    if (financialNote == null)
                    {
                        project.Notes.Add(new Entity.ProjectNote(project, Entity.NoteTypes.Financial, model.FinancialNote));
                    }
                    else
                    {
                        financialNote.Note = model.FinancialNote;
                    }
                }

                // Add tasks if they haven't been added already.
                if (!project.Tasks.Any())
                {
                    // Assumption we'll need to add all tasks from Submit to SPL.
                    var tasks = _service.Task.GetForWorkflow("SUBMIT-DISPOSAL");
                    tasks = tasks.Concat(_service.Task.GetForWorkflow("ASSESS-DISPOSAL"));
                    tasks = tasks.Concat(_service.Task.GetForWorkflow("ERP"));
                    tasks = tasks.Concat(_service.Task.GetForWorkflow("SPL"));
                    project.AddTask(tasks.DistinctBy(t => t.Id).ToArray());
                    project.Tasks.ForEach(t =>
                    {
                        t.IsCompleted = !t.Task.IsOptional;
                    });
                }

                _logger.LogDebug($"Parsed project '{project.ProjectNumber}' - '{project.Status.Code}'", project);

                return(project);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, $"Failed to parse this project while importing '{model.ProjectNumber}' - {ex.Message}");
                if (stopOnError)
                {
                    throw;
                }
                return(null);
            }
        }
Esempio n. 4
0
        /// <summary>
        /// Copy the 'model' project properties into existing projects, or add new projects to PIMS.
        /// </summary>
        /// <param name="project"></param>
        /// <param name="model"></param>
        /// <returns></returns>
        private Entity.Project Merge(Entity.Project project, Model.ImportProjectModel model)
        {
            var status = GetStatus(model.Status);

            // Default to the last workflow this status is associated with.
            // This isn't ideal and could end up with projects associated with an incorrect workflow.
            // Without better data from SIS it isn't entirely possible to determine the correct workflow automatically.
            var workflow = !String.IsNullOrWhiteSpace(model.Workflow) ? GetWorkflow(model.Workflow) : model.Activity switch
            {
                "Completed Deal" => GetWorkflow("SPL"),
                "Contract in Place" => GetWorkflow("SPL"),
                "On Market" => GetWorkflow("SPL"),
                "Pre-Market" => GetWorkflow("SPL"),
                _ => _adminService.Workflow.GetForStatus(model.Status).OrderBy(w => w.SortOrder).Last()
            };

            project ??= new Entity.Project();

            project.ProjectNumber      = String.IsNullOrWhiteSpace(model.ProjectNumber) ? $"TEMP-{DateTime.UtcNow.Ticks:00000}" : model.ProjectNumber;
            project.Name               = model.Description.Truncate(100);
            project.Description        = model.Description;
            project.Manager            = model.Manager;
            project.ActualFiscalYear   = model.ActualFiscalYear;
            project.ReportedFiscalYear = model.ReportedFiscalYear;

            project.Agency     = GetAgency(model.Agency);
            project.AgencyId   = project.Agency.Id;
            project.Workflow   = workflow;
            project.WorkflowId = workflow.Id;
            project.Status     = status;
            project.StatusId   = status.Id;

            // Extract properties from PID note.
            var pidNote = model.Notes?.FirstOrDefault(n => n.Key == "PID").Value;
            var pids    = Regex.Matches(pidNote ?? "", "[0-9]{3}-[0-9]{3}-[0-9]{3}").Select(m => m.Value).NotNull().Distinct();

            if (!String.IsNullOrWhiteSpace(pidNote))
            {
                // Need to load any properties currently linked to this project.
                if (project.Id > 0)
                {
                    var existingProject = _service.Project.Get(project.ProjectNumber);

                    pids.ForEach(pid =>
                    {
                        // If the parcel has not already been added, add it to the project.
                        var addProperty = true;
                        foreach (var property in existingProject.Properties.Where(p => p.PropertyType == Entity.PropertyTypes.Land))
                        {
                            var parcel = _service.Parcel.Get(property.ParcelId.Value);
                            if (parcel.ParcelIdentity == pid)
                            {
                                addProperty = false;
                                break;
                            }
                        }
                        if (addProperty)
                        {
                            AddProperty(project, pid);
                        }
                    });
                }
                else
                {
                    pids.ForEach(pid => AddProperty(project, pid));
                }
            }

            project.TierLevel   = GetTier(model.Market, pids.Any() ? pids.Count() : project.Properties.Count()); // Most projects have no properties linked.
            project.TierLevelId = project.TierLevel.Id;
            project.Risk        = GetRisk(model.Risk);
            project.RiskId      = project.Risk.Id;

            // If there are properties linked, load them into context.
            decimal assessed = 0;

            if (project.Properties.Any())
            {
                project.Properties.Where(p => p.PropertyType == Entity.PropertyTypes.Land).ForEach(p =>
                {
                    var property = _service.Parcel.Get(p.ParcelId.Value);
                    assessed    += property.Evaluations.Where(e => e.Key == Entity.EvaluationKeys.Assessed).OrderByDescending(e => e.Date).FirstOrDefault()?.Value ?? 0;
                });
            }

            project.Assessed  = model.Assessed ?? assessed;
            project.NetBook   = model.NetBook;
            project.Market    = model.Market;
            project.Appraised = model.Appraised;

            project.SubmittedOn = new DateTime(model.ReportedFiscalYear - 1, 1, 1); // Don't have a source for this information.
            project.ApprovedOn  = project.SubmittedOn;                              // Don't have a source for this information.
            project.CompletedOn = model.CompletedOn;

            var metadata = new Entity.Models.DisposalProjectSnapshotMetadata
            {
                InitialNotificationSentOn   = null,                          // Don't have a source for this information.
                ThirtyDayNotificationSentOn = null,                          // Don't have a source for this information.
                SixtyDayNotificationSentOn  = null,                          // Don't have a source for this information.
                NinetyDayNotificationSentOn = null,                          // Don't have a source for this information.
                OnHoldNotificationSentOn    = null,                          // Don't have a source for this information.
                InterestedReceivedOn        = model.InterestedReceivedOn,
                TransferredWithinGreOn      = null,                          // Don't have a source for this information.
                ClearanceNotificationSentOn = null,                          // Don't have a source for this information.
                RequestForSplReceivedOn     = model.RequestForSplReceivedOn, // Don't have a source for this information.
                ApprovedForSplOn            = model.ApprovedForSplOn,
                MarketedOn            = model.MarketedOn,
                Purchaser             = model.Purchaser,
                OcgFinancialStatement = model.OcgFinancialStatement,
                AppraisedBy           = model.AppraisedBy,
                ProgramCost           = model.ProgramCost,
                SalesCost             = model.SalesCost,
                InterestComponent     = model.InterestComponent,
                NetProceeds           = model.NetProceeds,
                GainLoss             = model.GainLoss,
                SaleWithLeaseInPlace = model.SaleWithLeaseInPlace,
                DisposedOn           = model.DisposedOn ?? (project.Status.Code == "DIS" ? model.CompletedOn : null),
                OfferAmount          = project.Status.Code == "DIS" ? model.Market : (decimal?)null, // This value would only be accurate if the property is disposed.
                OfferAcceptedOn      = null                                                          // Don't have a source for this information.
            };

            // A prior net proceeds was provided, which means a prior snapshot needs to be generated.
            // If the project already exists, don't add prior snapshots.
            if (model.PriorNetProceeds.HasValue && project.Id == 0)
            {
                AddSnapshot(project, model, metadata);
            }

            project.Metadata = JsonSerializer.Serialize(metadata, _serializerOptions);

            // The data doesn't provide the purchasing agency information so the response will be the current owning agency.
            if (model.InterestedReceivedOn.HasValue)
            {
                var response = project.Responses.FirstOrDefault(r => r.AgencyId == project.AgencyId);
                if (response == null)
                {
                    project.Responses.Add(new Entity.ProjectAgencyResponse(project, project.Agency, Entity.NotificationResponses.Watch, model.InterestedReceivedOn));
                }
                else
                {
                    response.Response   = Entity.NotificationResponses.Watch;
                    response.ReceivedOn = model.InterestedReceivedOn ?? response.ReceivedOn;
                }
            }

            foreach (var note in model.Notes)
            {
                AddNote(project, note.Key, note.Value);
            }

            // Add tasks if they haven't been added already.
            if (!project.Tasks.Any())
            {
                // Assumption we'll need to add all tasks from Submit to SPL.
                var tasks = _service.Task.GetForWorkflow("SUBMIT-DISPOSAL");
                tasks = tasks.Concat(_service.Task.GetForWorkflow("ASSESS-DISPOSAL"));
                var distinctTasks = tasks.DistinctBy(t => t.Id).ToArray();
                project.AddTask(distinctTasks);
                project.Tasks.ForEach(t =>
                {
                    t.IsCompleted = !t.Task.IsOptional; // Complete all required steps.
                    t.Task        = null;
                });

                var erpTasks = _service.Task.GetForWorkflow("ERP");
                var splTasks = erpTasks.Concat(_service.Task.GetForWorkflow("SPL"));
                distinctTasks = splTasks.Where(t => !distinctTasks.Any(dt => dt.Id == t.Id)).DistinctBy(t => t.Id).ToArray();
                project.AddTask(distinctTasks);
                project.Tasks.ForEach(t =>
                {
                    t.IsCompleted = false;
                    t.Task        = null;
                });
            }

            _logger.LogDebug($"Parsed project '{project.ProjectNumber}' - '{project.Status.Code}'", project);

            return(project);
        }