Bugbash (#20)
* 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:
Родитель
803efeeb3a
Коммит
ef1f2c93ec
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче