* Updated add and delete to redirect to index on success. fixes #32893 #32930

* On success redirect to prevent resubmission of forms

* redirect on success to prevent resubmission of forms

* removed unused usings

* Updated tests and made name on edit location hidden

* Check to see if location with name exists

* changed to show map and list of results
This commit is contained in:
Jim Paine 2020-04-30 15:16:28 +01:00 коммит произвёл GitHub
Родитель 803efeeb3a
Коммит ef1f2c93ec
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
8 изменённых файлов: 136 добавлений и 95 удалений

Просмотреть файл

@ -7,6 +7,7 @@ using NSubstitute;
using System;
using System.Linq.Expressions;
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
namespace Project.Zap.Tests
{
@ -178,16 +179,36 @@ namespace Project.Zap.Tests
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
}
[Fact]
public async Task Add_LocationWithExistingName_NotAdded()
{
// Arrange
IRepository<Location> repository = Substitute.For<IRepository<Location>>();
repository.Get(Arg.Any<string>(), Arg.Any<Dictionary<string, object>>()).Returns(new[] { new Location { Address = new Address { } } });
IMapService mapService = Substitute.For<IMapService>();
ILocationService service = this.GetLocationService(locationRepository: repository, mapService: mapService);
// Act
await service.Add(new Location { Name = "Contoso" });
// Assert
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
repository.DidNotReceive().Add(Arg.Any<Location>());
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
}
private ILocationService GetLocationService(
IRepository<Location> locationRepository = null,
IRepository<Shift> shiftRepository = null,
IMapService mapService = null)
IMapService mapService = null,
ILogger<LocationService> logger = null)
{
locationRepository = locationRepository ?? Substitute.For<IRepository<Location>>();
shiftRepository = shiftRepository ?? Substitute.For<IRepository<Shift>>();
mapService = mapService ?? Substitute.For<IMapService>();
logger = logger ?? Substitute.For<ILogger<LocationService>>();
return new LocationService(locationRepository, shiftRepository, mapService);
return new LocationService(locationRepository, shiftRepository, mapService, logger);
}
}
}

Просмотреть файл

@ -96,7 +96,7 @@ namespace Project.Zap.Tests
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
service.Received(1).Add(Arg.Any<Location>());
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
Assert.IsType<ViewResult>(result);
Assert.IsType<RedirectResult>(result);
}
[Fact]

Просмотреть файл

@ -4,9 +4,7 @@ using Project.Zap.Helpers;
using Project.Zap.Library.Models;
using Project.Zap.Models;
using Project.Zap.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Project.Zap.Controllers
@ -44,14 +42,14 @@ namespace Project.Zap.Controllers
await this.locationService.Add(viewModel.Map());
return await this.Index();
return Redirect("/Locations");
}
[HttpGet]
public async Task<IActionResult> Delete(string id)
{
await this.locationService.DeleteByName(id);
return await this.Index();
return Redirect("/Locations");
}
[HttpGet]
@ -67,7 +65,7 @@ namespace Project.Zap.Controllers
{
await this.locationService.Update(viewModel.Map());
return await this.Index();
return Redirect("/Locations");
}
}
}

Просмотреть файл

@ -51,15 +51,15 @@ namespace Project.Zap.Controllers
await this.repository.Add(partner);
return await this.Index();
return Redirect("/PartnerOrganization");
}
[HttpGet]
public async Task<IActionResult> DeletePartner(string id)
{
await this.repository.Delete(x => x.Name == id);
return await this.Index();
return Redirect("/PartnerOrganization");
}
private char[] chars = new char[]

Просмотреть файл

@ -55,16 +55,21 @@ namespace Project.Zap.Controllers
this.logger.LogInformation("No locations, so redirecting to location view");
return Redirect("/Locations");
}
SearchShiftViewModel viewModel = await GetShifts(locations);
ViewData["AzureMapsKey"] = this.configuration["AzureMapsSubscriptionKey"];
return View("Index", viewModel);
}
private async Task<SearchShiftViewModel> GetShifts(IEnumerable<Location> locations)
{
IEnumerable<Shift> shifts = await this.shiftRepository.Get("SELECT * FROM c WHERE c.StartDateTime > @start", new Dictionary<string, object> { { "@start", DateTime.Now } });
SearchShiftViewModel viewModel = new SearchShiftViewModel
{
LocationNames = this.GetLocationNames(locations),
Result = shifts.Map(locations)
};
ViewData["AzureMapsKey"] = this.configuration["AzureMapsSubscriptionKey"];
return View("Index", viewModel);
return viewModel;
}
private SelectList GetLocationNames(IEnumerable<Location> locations)
@ -115,7 +120,7 @@ namespace Project.Zap.Controllers
{
Location location = await this.GetLocation(viewModel.LocationName);
await this.shiftRepository.Delete(x => x.LocationId == location.id && x.StartDateTime == viewModel.Start && x.EndDateTime == viewModel.End && x.WorkType == viewModel.WorkType);
return await this.Index();
return Redirect("/Shifts");
}
private async Task<Location> GetLocation(string name) => await this.locationService.GetByName(name);
@ -214,7 +219,7 @@ namespace Project.Zap.Controllers
await this.shiftRepository.Update(shift);
return await this.Index();
return Redirect("/Shifts");
}
[HttpGet]
@ -235,7 +240,13 @@ namespace Project.Zap.Controllers
{
this.logger.LogInformation("Trying to book on a shift when user is already booked out for this day");
ViewData["ValidationError"] = "You are already booked to work on this day.";
return await this.Index();
IEnumerable<Location> locations = await this.locationService.Get();
if (locations == null || !locations.Any())
{
this.logger.LogInformation("No locations, so redirecting to location view");
return Redirect("/Locations");
}
return View("/Index", await this.GetShifts(locations));
}
Location location = await this.GetLocation(viewModel.LocationName);
@ -254,7 +265,13 @@ namespace Project.Zap.Controllers
{
this.logger.LogInformation("Trying to book shift for a time when no shifts are available");
ViewData["ValidationError"] = "No available shifts at this time.";
return await this.Index();
IEnumerable<Location> locations = await this.locationService.Get();
if (locations == null || !locations.Any())
{
this.logger.LogInformation("No locations, so redirecting to location view");
return Redirect("/Locations");
}
return View("/Index", await this.GetShifts(locations));
}
shift.EmployeeId = id.Value;
@ -294,7 +311,7 @@ namespace Project.Zap.Controllers
shifts.ForEach(async x => await this.shiftRepository.Add(x));
return await this.Index();
return Redirect("/Shifts");
}
[HttpGet]
@ -336,7 +353,7 @@ namespace Project.Zap.Controllers
}
}
return await this.Index();
return Redirect("/Shifts");
}
}
}

Просмотреть файл

@ -1,4 +1,5 @@
using Project.Zap.Library.Models;
using Microsoft.Extensions.Logging;
using Project.Zap.Library.Models;
using Project.Zap.Library.Services;
using System.Collections.Generic;
using System.Linq;
@ -21,24 +22,37 @@ namespace Project.Zap.Services
private readonly IRepository<Location> repository;
private readonly IRepository<Shift> shiftRepository;
private readonly IMapService mapService;
private readonly ILogger<LocationService> logger;
public LocationService(IRepository<Location> repository, IRepository<Shift> shiftRepository, IMapService mapService)
public LocationService(IRepository<Location> repository, IRepository<Shift> shiftRepository, IMapService mapService, ILogger<LocationService> logger)
{
this.repository = repository;
this.shiftRepository = shiftRepository;
this.mapService = mapService;
this.logger = logger;
}
public async Task Add(Location location)
{
location.Address.Point = await this.mapService.GetCoordinates(location.Address);
Location existing = await this.GetByName(location.Name);
if(existing != null)
{
return;
}
location.Address.Point = await this.mapService.GetCoordinates(location.Address);
await this.repository.Add(location);
}
public async Task DeleteByName(string name)
{
Location existing = await this.GetByName(name);
if (existing == null)
{
this.logger.LogWarning("Trying to delete location that doesn't exist");
return;
}
await this.repository.Delete(x => x.Name == name);
await this.shiftRepository.Delete(x => x.LocationId == existing.id);
}
@ -46,6 +60,11 @@ namespace Project.Zap.Services
public async Task Update(Location location)
{
Location existing = await this.GetByName(location.Name);
if (existing == null)
{
this.logger.LogWarning("Trying to update location that doesn't exist");
return;
}
existing.Address = location.Address;
existing.Address.Point = await this.mapService.GetCoordinates(location.Address);
await this.repository.Replace(existing);
@ -69,7 +88,12 @@ namespace Project.Zap.Services
public async Task<Location> GetByName(string name)
{
Location location = (await this.repository.Get("SELECT * FROM c WHERE c.Name = @name", new Dictionary<string, object> { { "@name", name } })).FirstOrDefault();
if (location == null)
{
this.logger.LogWarning("Trying to get location that doesn't exist");
return null;
}
if (location.Address.Point == null)
{
location.Address.Point = await this.mapService.GetCoordinates(location.Address);

Просмотреть файл

@ -10,11 +10,11 @@
<form asp-controller="Locations" asp-action="EditLocation" method="Post">
<div class="form-group">
<label asp-for="@Model.Name"></label>
<input type="text" asp-for="@Model.Name" class="form-control" />
<span asp-validation-for="@Model.Name"></span>
<label asp-for="@Model.Name"></label><br />
<span>@Model.Name</span>
<input type="hidden" asp-for="@Model.Name" class="form-control" />
</div>
<h5>Address</h5>
<div class="form-group">
<label asp-for="@Model.Address"></label>
<input type="text" asp-for="@Model.Address" class="form-control" />

Просмотреть файл

@ -36,75 +36,56 @@
<div id="results">
<h3 class="mt-3">@Localizer["ResultsTitle"]</h3>
<ul class="nav nav-tabs">
<li class="nav-item">
<a class="nav-link" data-toggle="tab" href="#listView">List View</a>
</li>
<li class="nav-item">
<a class="nav-link active" data-toggle="tab" href="#mapView">Map View</a>
</li>
</ul>
<div class="container-fluid">
<div class="tab-content">
<div role="tabpanel" class="tab-pane" id="listView">
@if (Model.Result != null)
{
<div class="row">
<div class="d-none d-xs-block d-sm-block col-md-4 font-weight-bold mt-2">@Localizer["TableStore"]</div>
<div class="d-none d-xs-block d-sm-block col-md-2 font-weight-bold mt-2">@Localizer["TableStart"]</div>
<div class="d-none d-xs-block d-sm-block col-md-2 font-weight-bold mt-2">@Localizer["TableEnd"]</div>
<div class="d-none d-xs-block d-sm-block col-md-2 font-weight-bold mt-2">@Localizer["TableWorkType"]</div>
<div class="d-none d-xs-block d-sm-block col-md-1 font-weight-bold mt-2">@Localizer["TableAvailable"]</div>
@if ((await AuthorizationService.AuthorizeAsync(User, "OrgAManager")).Succeeded)
{
<div class="d-none d-xs-block d-sm-block col-md-1 font-weight-bold mt-2 invisible">@Localizer["TableView"]</div>
}
@if ((await AuthorizationService.AuthorizeAsync(User, "OrgBEmployee")).Succeeded)
{
<div class="d-none d-xs-block d-sm-block col-md-1 font-weight-bold mt-2 invisible">@Localizer["TableBook"]</div>
}
</div>
@foreach (var shift in Model.Result)
{
<div class="row">
<div class="d-block d-xs-none d-sm-none col-xs-6 col-sm-6 font-weight-bold mt-2">@Localizer["TableStore"]</div>
<div class="col-xs-6 col-sm-6 col-md-4 mt-2">@shift.LocationName</div>
<div class="d-block d-xs-none d-sm-none col-xs-6 col-sm-6 font-weight-bold mt-2">@Localizer["TableStart"]</div>
<div class="col-xs-6 col-sm-6 col-md-2 mt-2">@shift.Start.ToString("MM/dd/yyyy HH:mm")</div>
<div class="d-block d-xs-none d-sm-none col-xs-6 col-sm-6 font-weight-bold mt-2">@Localizer["TableEnd"]</div>
<div class="col-xs-6 col-sm-6 col-md-2 mt-2">@shift.End.ToString("MM/dd/yyyy HH:mm")</div>
<div class="d-block d-xs-none d-sm-none col-xs-6 col-sm-6 font-weight-bold mt-2">@Localizer["TableWorkType"]</div>
<div class="col-xs-6 col-sm-6 col-md-2 mt-2">@shift.WorkType</div>
<div class="d-block d-xs-none d-sm-none col-xs-6 col-sm-6 font-weight-bold mt-2">@Localizer["TableAvailable"]</div>
<div class="col-xs-6 col-sm-6 col-md-1 mt-2">@shift.Available / @shift.Quantity</div>
@if ((await AuthorizationService.AuthorizeAsync(User, "OrgAManager")).Succeeded)
{
<a class="btn btn-primary col-xs-6 col-sm-6 col-md-1 mt-2" asp-controller="Shifts" asp-action="ViewShift" asp-route-LocationName="@shift.LocationName" asp-route-Start="@shift.Start.ToString("yyyy-MM-ddTHH:mm")" asp-route-End="@shift.End.ToString("yyyy-MM-ddTHH:mm")" asp-route-WorkType="@shift.WorkType" asp-route-Quantity="@shift.Quantity" asp-route-Available="@shift.Available">@Localizer["LinkView"]</a>
}
@if ((await AuthorizationService.AuthorizeAsync(User, "OrgBEmployee")).Succeeded)
{
<a class="btn btn-primary col-xs-6 col-sm-6 col-md-1 mt-2" asp-controller="Shifts" asp-action="Book" asp-route-LocationName="@shift.LocationName" asp-route-Start="@shift.Start.ToString("yyyy-MM-ddTHH:mm")" asp-route-End="@shift.End.ToString("yyyy-MM-ddTHH:mm")" asp-route-WorkType="@shift.WorkType">@Localizer["LinkBook"]</a>
}
</div>
}
}
</div>
<div role="tabpanel" class="tab-pane active" id="mapView">
<div class="row">
<div id="myMap" class="mt-2 col-12" style="height:400px"></div>
</div>
</div>
</div>
@if (Model.Result != null)
{
<div class="row">
<div id="myMap" class="mt-2 col-12" style="height:400px"></div>
</div>
<div class="row">
<div class="d-none d-xs-block d-sm-block col-md-4 font-weight-bold mt-2">@Localizer["TableStore"]</div>
<div class="d-none d-xs-block d-sm-block col-md-2 font-weight-bold mt-2">@Localizer["TableStart"]</div>
<div class="d-none d-xs-block d-sm-block col-md-2 font-weight-bold mt-2">@Localizer["TableEnd"]</div>
<div class="d-none d-xs-block d-sm-block col-md-2 font-weight-bold mt-2">@Localizer["TableWorkType"]</div>
<div class="d-none d-xs-block d-sm-block col-md-1 font-weight-bold mt-2">@Localizer["TableAvailable"]</div>
@if ((await AuthorizationService.AuthorizeAsync(User, "OrgAManager")).Succeeded)
{
<div class="d-none d-xs-block d-sm-block col-md-1 font-weight-bold mt-2 invisible">@Localizer["TableView"]</div>
}
@if ((await AuthorizationService.AuthorizeAsync(User, "OrgBEmployee")).Succeeded)
{
<div class="d-none d-xs-block d-sm-block col-md-1 font-weight-bold mt-2 invisible">@Localizer["TableBook"]</div>
}
</div>
@foreach (var shift in Model.Result)
{
<div class="row">
<div class="d-block d-xs-none d-sm-none col-xs-6 col-sm-6 font-weight-bold mt-2">@Localizer["TableStore"]</div>
<div class="col-xs-6 col-sm-6 col-md-4 mt-2">@shift.LocationName</div>
<div class="d-block d-xs-none d-sm-none col-xs-6 col-sm-6 font-weight-bold mt-2">@Localizer["TableStart"]</div>
<div class="col-xs-6 col-sm-6 col-md-2 mt-2">@shift.Start.ToString("MM/dd/yyyy HH:mm")</div>
<div class="d-block d-xs-none d-sm-none col-xs-6 col-sm-6 font-weight-bold mt-2">@Localizer["TableEnd"]</div>
<div class="col-xs-6 col-sm-6 col-md-2 mt-2">@shift.End.ToString("MM/dd/yyyy HH:mm")</div>
<div class="d-block d-xs-none d-sm-none col-xs-6 col-sm-6 font-weight-bold mt-2">@Localizer["TableWorkType"]</div>
<div class="col-xs-6 col-sm-6 col-md-2 mt-2">@shift.WorkType</div>
<div class="d-block d-xs-none d-sm-none col-xs-6 col-sm-6 font-weight-bold mt-2">@Localizer["TableAvailable"]</div>
<div class="col-xs-6 col-sm-6 col-md-1 mt-2">@shift.Available / @shift.Quantity</div>
@if ((await AuthorizationService.AuthorizeAsync(User, "OrgAManager")).Succeeded)
{
<a class="btn btn-primary col-xs-6 col-sm-6 col-md-1 mt-2" asp-controller="Shifts" asp-action="ViewShift" asp-route-LocationName="@shift.LocationName" asp-route-Start="@shift.Start.ToString("yyyy-MM-ddTHH:mm")" asp-route-End="@shift.End.ToString("yyyy-MM-ddTHH:mm")" asp-route-WorkType="@shift.WorkType" asp-route-Quantity="@shift.Quantity" asp-route-Available="@shift.Available">@Localizer["LinkView"]</a>
}
@if ((await AuthorizationService.AuthorizeAsync(User, "OrgBEmployee")).Succeeded)
{
<a class="btn btn-primary col-xs-6 col-sm-6 col-md-1 mt-2" asp-controller="Shifts" asp-action="Book" asp-route-LocationName="@shift.LocationName" asp-route-Start="@shift.Start.ToString("yyyy-MM-ddTHH:mm")" asp-route-End="@shift.End.ToString("yyyy-MM-ddTHH:mm")" asp-route-WorkType="@shift.WorkType">@Localizer["LinkBook"]</a>
}
</div>
}
}
</div>
@section Scripts