예제 #1
0
        public void ParcelParcel_Constructor_01()
        {
            // Arrange
            var parcel    = EntityHelper.CreateParcel(123);
            var subParcel = EntityHelper.CreateParcel(321);

            // Act
            var parcelParcel = new ParcelParcel(parcel, subParcel);

            // Assert
            parcelParcel.ParcelId.Should().Be(parcel.Id);
            parcelParcel.Parcel.Should().Be(parcel);
            parcelParcel.SubdivisionId.Should().Be(subParcel.Id);
            parcelParcel.Subdivision.Should().Be(subParcel);
        }
예제 #2
0
        /// <summary>
        /// Update the specified parcel in the datasource, but do not commit the transaction.
        /// </summary>
        /// <param name="parcel"></param>
        /// <exception type="KeyNotFoundException">Entity does not exist in the datasource.</exception>
        /// <returns></returns>
        public Parcel PendingUpdate(Parcel parcel)
        {
            parcel.ThrowIfNotAllowedToEdit(nameof(parcel), this.User, new[] { Permissions.PropertyEdit, Permissions.AdminProperties });
            var isAdmin = this.User.HasPermission(Permissions.AdminProperties);

            var originalParcel = this.Context.Parcels
                                 .Include(p => p.Agency)
                                 .Include(p => p.Address)
                                 .Include(p => p.Evaluations)
                                 .Include(p => p.Fiscals)
                                 .Include(p => p.Buildings).ThenInclude(pb => pb.Building).ThenInclude(b => b.Evaluations)
                                 .Include(p => p.Buildings).ThenInclude(pb => pb.Building).ThenInclude(b => b.Fiscals)
                                 .Include(p => p.Buildings).ThenInclude(pb => pb.Building).ThenInclude(b => b.Address)
                                 .Include(p => p.Parcels).ThenInclude(pp => pp.Parcel)
                                 .Include(p => p.Subdivisions).ThenInclude(pp => pp.Subdivision)
                                 .SingleOrDefault(p => p.Id == parcel.Id) ?? throw new KeyNotFoundException();

            var userAgencies     = this.User.GetAgencies();
            var originalAgencyId = (int)this.Context.Entry(originalParcel).OriginalValues[nameof(Parcel.AgencyId)];
            var allowEdit        = isAdmin || userAgencies.Contains(originalAgencyId);
            var ownsABuilding    = originalParcel.Buildings.Any(pb => userAgencies.Contains(pb.Building.AgencyId.Value));

            if (!allowEdit && !ownsABuilding)
            {
                throw new NotAuthorizedException("User may not edit parcels outside of their agency.");
            }

            parcel.PropertyTypeId = originalParcel.PropertyTypeId; // property type cannot be changed directly.
            if (parcel.PropertyTypeId == (int)PropertyTypes.Subdivision && parcel.Parcels.Any())
            {
                var parentPid = parcel.Parcels.FirstOrDefault()?.Parcel?.PID;
                if (parentPid == null)
                {
                    throw new InvalidOperationException("Invalid parent parcel associated to subdivision, parent parcels must contain a valid PID");
                }
                parcel.PID = parentPid.Value;
            }

            originalParcel.ThrowIfPropertyInSppProject(this.User);

            // Do not allow switching agencies through this method.
            if (!isAdmin && originalAgencyId != parcel.AgencyId)
            {
                throw new NotAuthorizedException("Parcel cannot be transferred to the specified agency.");
            }

            // Do not allow making property visible through this service.
            if (originalParcel.IsVisibleToOtherAgencies != parcel.IsVisibleToOtherAgencies)
            {
                throw new InvalidOperationException("Parcel cannot be made visible to other agencies through this service.");
            }

            // Only administrators can dispose a property.
            if (!isAdmin && parcel.ClassificationId == (int)ClassificationTypes.Disposed)
            {
                throw new NotAuthorizedException("Parcel classification cannot be changed to disposed.");                                                                           // TODO: Classification '4' should be a config settings.
            }
            // Only administrators can set parcel to subdivided
            if (!isAdmin && parcel.ClassificationId == (int)ClassificationTypes.Subdivided)
            {
                throw new NotAuthorizedException("Parcel classification cannot be changed to subdivided.");
            }

            // Only buildings can be set to demolished
            if (parcel.ClassificationId == (int)ClassificationTypes.Demolished)
            {
                throw new NotAuthorizedException("Only buildings may be set to demolished.");
            }

            if ((parcel.Parcels.Count > 0 && parcel.Subdivisions.Count > 0) ||
                (originalParcel.Parcels.Count > 0 && parcel.Subdivisions.Count > 0) ||
                (originalParcel.Subdivisions.Count > 0 && parcel.Parcels.Count > 0))
            {
                throw new InvalidOperationException("Parcel may only have associated parcels or subdivisions, not both.");
            }

            // Users who don't own the parcel, but only own a building cannot update the parcel.
            if (allowEdit)
            {
                parcel.PropertyTypeId = originalParcel.PropertyTypeId;
                this.Context.Entry(originalParcel.Address).CurrentValues.SetValues(parcel.Address);
                this.Context.Entry(originalParcel).CurrentValues.SetValues(parcel);
                this.Context.SetOriginalRowVersion(originalParcel);
            }

            foreach (var building in parcel.Buildings.Select(pb => pb.Building))
            {
                // Check if the building already exists.
                var existingBuilding = originalParcel.Buildings
                                       .FirstOrDefault(pb => pb.BuildingId == building.Id)?.Building;

                // Reset all relationships that are not changed through this update.
                building.Address.Province         = this.Context.Provinces.FirstOrDefault(p => p.Id == building.Address.ProvinceId);
                building.BuildingConstructionType = this.Context.BuildingConstructionTypes.FirstOrDefault(b => b.Id == building.BuildingConstructionTypeId);
                building.BuildingOccupantType     = this.Context.BuildingOccupantTypes.FirstOrDefault(b => b.Id == building.BuildingOccupantTypeId);
                building.BuildingPredominateUse   = this.Context.BuildingPredominateUses.FirstOrDefault(b => b.Id == building.BuildingPredominateUseId);
                building.Agency = this.Context.Agencies.FirstOrDefault(a => a.Id == building.AgencyId);

                if (existingBuilding == null)
                {
                    if (!allowEdit)
                    {
                        throw new NotAuthorizedException("User may not add properties to a parcel they don't own.");
                    }

                    originalParcel.Buildings.Add(new ParcelBuilding(parcel, building));
                }
                else
                {
                    building.PropertyTypeId = existingBuilding.PropertyTypeId;
                    this.ThrowIfNotAllowedToUpdate(existingBuilding, _options.Project);

                    if (!allowEdit && !userAgencies.Contains(existingBuilding.AgencyId.Value))
                    {
                        throw new NotAuthorizedException("User may not update a property they don't own.");
                    }
                    // Do not allow switching agencies through this method.
                    if (!isAdmin && existingBuilding.AgencyId != building.AgencyId)
                    {
                        throw new NotAuthorizedException("Building cannot be transferred to the specified agency.");
                    }

                    this.Context.Entry(existingBuilding).CurrentValues.SetValues(building);
                    this.Context.Entry(existingBuilding.Address).CurrentValues.SetValues(building.Address);
                    this.Context.UpdateBuildingFinancials(existingBuilding, building.Evaluations, building.Fiscals);
                }
            }

            // This property is a divided parcel with child subdivision parcels.
            if (parcel.Subdivisions.Count > 0 || originalParcel.Subdivisions.Count > 0)
            {
                // loop through all passed in subdivisions, add any new subdivisions and remove any missing subdivisions from the current divided parcel.
                foreach (var subdivisionId in parcel.Subdivisions.Select(pb => pb.SubdivisionId))
                {
                    // Check if the subdivision already exists.
                    var existingSubdivision = originalParcel.Subdivisions
                                              .FirstOrDefault(pb => pb.SubdivisionId == subdivisionId)?.Subdivision;

                    //Just add any new subdivisions, users cannot edit a subdivision within a parent parcel.
                    if (existingSubdivision == null)
                    {
                        if (!allowEdit)
                        {
                            throw new NotAuthorizedException("User may not add subdivisions to a parcel they don't own.");
                        }
                        // This parcel is a divided parent parcel with one or more subdivisions. Therefore, add the current parcel id as the parent divided ParcelId with a ParcelParcel relationship to the new subdivisionId.
                        var pp = new ParcelParcel()
                        {
                            SubdivisionId = subdivisionId, ParcelId = parcel.Id
                        };

                        originalParcel.Subdivisions.Add(pp);
                    }
                }
                foreach (var subdivision in originalParcel.Subdivisions)
                {
                    // Delete the subdivisions that have been removed from this parent divided parcel.
                    if (!parcel.Subdivisions.Any(e => (e.SubdivisionId == subdivision.SubdivisionId)))
                    {
                        this.Context.ParcelParcels.Remove(subdivision);
                    }
                }
            }
            else
            {
                // This property is a Subdivision with parent divided parcels.
                // loop through all passed in owning divided parcels, adding any new divided parcels and removing any missing divided parcels from the current subdivision.
                foreach (var dividedParcelId in parcel.Parcels.Select(pb => pb.ParcelId))
                {
                    // Check if the subdivided parcel already exists.
                    var existingDividedParcel = originalParcel.Parcels
                                                .FirstOrDefault(pb => pb.ParcelId == dividedParcelId)?.Parcel;

                    //Just add any new divided parcels, users cannot edit a parcel within a subdivision.
                    if (existingDividedParcel == null)
                    {
                        if (!allowEdit)
                        {
                            throw new NotAuthorizedException("User may not add divided parcels to a subdivision they don't own.");
                        }
                        // This parcel is a subdivision with one or more parent divided parcels. Therefore, add the current parcel id as the SubdivisionId with a ParcelParcel relationship to the new dividedParcelId.
                        var pp = new ParcelParcel()
                        {
                            SubdivisionId = parcel.Id, ParcelId = dividedParcelId
                        };

                        originalParcel.Parcels.Add(pp);
                    }
                }
                foreach (var dividedParcel in originalParcel.Parcels)
                {
                    // Delete the divided parcels that have been removed from this subdivision parcel.
                    if (!parcel.Parcels.Any(e => (e.ParcelId == dividedParcel.ParcelId)))
                    {
                        this.Context.ParcelParcels.Remove(dividedParcel);
                    }
                }
            }

            if (allowEdit)
            {
                this.Context.UpdateParcelFinancials(originalParcel, parcel.Evaluations, parcel.Fiscals);

                // Go through the existing buildings and see if they have been deleted from the updated parcel.
                // If they have been removed, delete them from the datasource.
                foreach (var parcelBuilding in originalParcel.Buildings)
                {
                    var updateBuilding = parcel.Buildings.FirstOrDefault(pb => pb.BuildingId == parcelBuilding.BuildingId);

                    // The building may have evaluations or fiscals that need to be deleted.
                    foreach (var buildingEvaluation in parcelBuilding.Building.Evaluations)
                    {
                        // Delete the evaluations that have been removed.
                        if (!updateBuilding.Building.Evaluations.Any(e => (e.BuildingId == buildingEvaluation.BuildingId && e.Date == buildingEvaluation.Date && e.Key == buildingEvaluation.Key)))
                        {
                            this.Context.BuildingEvaluations.Remove(buildingEvaluation);
                        }
                    }
                    foreach (var buildingFiscal in parcelBuilding.Building.Fiscals)
                    {
                        // Delete the fiscals that have been removed.
                        if (!updateBuilding.Building.Fiscals.Any(e => (e.BuildingId == buildingFiscal.BuildingId && e.FiscalYear == buildingFiscal.FiscalYear && e.Key == buildingFiscal.Key)))
                        {
                            this.Context.BuildingFiscals.Remove(buildingFiscal);
                        }
                    }
                }

                foreach (var parcelEvaluation in originalParcel.Evaluations)
                {
                    // Delete the evaluations from the parcel that have been removed.
                    if (!parcel.Evaluations.Any(e => (e.ParcelId == parcelEvaluation.ParcelId && e.Date == parcelEvaluation.Date && e.Key == parcelEvaluation.Key)))
                    {
                        this.Context.ParcelEvaluations.Remove(parcelEvaluation);
                    }
                }
                foreach (var parcelFiscals in originalParcel.Fiscals)
                {
                    // Delete the fiscals from the parcel that have been removed.
                    if (!parcel.Fiscals.Any(e => (e.ParcelId == parcelFiscals.ParcelId && e.FiscalYear == parcelFiscals.FiscalYear && e.Key == parcelFiscals.Key)))
                    {
                        this.Context.ParcelFiscals.Remove(parcelFiscals);
                    }
                }
            }

            return(originalParcel);
        }