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); }
/// <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); }