From 83e425da2b6d1f25acf1fdcc7c881a32de1232a3 Mon Sep 17 00:00:00 2001 From: Christian Arenas Date: Mon, 26 Jun 2017 18:32:25 +0200 Subject: [PATCH] Add Cancel button in order detail view when the status is submitted and add requestId guid to create a new order --- .../Models/Orders/CancelOrderCommand.cs | 12 +++ .../Models/Orders/Order.cs | 2 +- .../Services/Order/IOrderService.cs | 2 + .../Services/Order/OrderMockService.cs | 16 ++-- .../Services/Order/OrderService.cs | 27 ++++++ .../RequestProvider/HttpRequestExceptionEx.cs | 28 ++++++ .../RequestProvider/IRequestProvider.cs | 2 + .../RequestProvider/RequestProvider.cs | 24 ++++- .../ViewModels/CheckoutViewModel.cs | 1 + .../ViewModels/OrderDetailViewModel.cs | 54 ++++++++++- .../Views/MainView.xaml.cs | 9 ++ .../Views/OrderDetailView.xaml | 89 ++++++++++++------- .../Views/Templates/OrderItemTemplate.xaml | 3 +- .../eShopOnContainers.Core.csproj | 2 + 14 files changed, 226 insertions(+), 45 deletions(-) create mode 100644 src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Orders/CancelOrderCommand.cs create mode 100644 src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/RequestProvider/HttpRequestExceptionEx.cs diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Orders/CancelOrderCommand.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Orders/CancelOrderCommand.cs new file mode 100644 index 000000000..62b1e29fc --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Orders/CancelOrderCommand.cs @@ -0,0 +1,12 @@ +namespace eShopOnContainers.Core.Models.Orders +{ + public class CancelOrderCommand + { + public int OrderNumber { get; } + + public CancelOrderCommand(int orderNumber) + { + OrderNumber = orderNumber; + } + } +} \ No newline at end of file diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Orders/Order.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Orders/Order.cs index eb40e1b70..c9adfff14 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Orders/Order.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Orders/Order.cs @@ -54,6 +54,6 @@ namespace eShopOnContainers.Core.Models.Orders public decimal Total { get; set; } [JsonProperty("ordernumber")] - public string OrderNumber { get; set; } + public int OrderNumber { get; set; } } } \ No newline at end of file diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Order/IOrderService.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Order/IOrderService.cs index f13520463..59b746917 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Order/IOrderService.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Order/IOrderService.cs @@ -2,6 +2,7 @@ using System.Collections.ObjectModel; using System.Threading.Tasks; + namespace eShopOnContainers.Core.Services.Order { public interface IOrderService @@ -10,6 +11,7 @@ namespace eShopOnContainers.Core.Services.Order Task> GetOrdersAsync(string token); Task GetOrderAsync(int orderId, string token); Task> GetCardTypesAsync(string token); + Task CancelOrderAsync(int orderId, string token); BasketCheckout MapOrderToBasket(Models.Orders.Order order); } } \ No newline at end of file diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Order/OrderMockService.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Order/OrderMockService.cs index abfce8c2a..c32093203 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Order/OrderMockService.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Order/OrderMockService.cs @@ -47,10 +47,10 @@ namespace eShopOnContainers.Core.Services.Order private List MockOrders = new List() { - new Models.Orders.Order { OrderNumber = "1", SequenceNumber = 123, OrderDate = DateTime.Now, OrderStatus = OrderStatus.Submitted, OrderItems = MockOrderItems, CardTypeId = MockPaymentInfo.CardType.Id, CardHolderName = MockPaymentInfo.CardHolderName, CardNumber = MockPaymentInfo.CardNumber, CardSecurityNumber = MockPaymentInfo.SecurityNumber, CardExpiration = new DateTime(MockPaymentInfo.ExpirationYear, MockPaymentInfo.ExpirationMonth, 1), ShippingCity = MockAdress.City, ShippingState = MockAdress.State, ShippingCountry = MockAdress.Country, ShippingStreet = MockAdress.Street, Total = 36.46M }, - new Models.Orders.Order { OrderNumber = "2", SequenceNumber = 132, OrderDate = DateTime.Now, OrderStatus = OrderStatus.Paid, OrderItems = MockOrderItems, CardTypeId = MockPaymentInfo.CardType.Id, CardHolderName = MockPaymentInfo.CardHolderName, CardNumber = MockPaymentInfo.CardNumber, CardSecurityNumber = MockPaymentInfo.SecurityNumber, CardExpiration = new DateTime(MockPaymentInfo.ExpirationYear, MockPaymentInfo.ExpirationMonth, 1), ShippingCity = MockAdress.City, ShippingState = MockAdress.State, ShippingCountry = MockAdress.Country, ShippingStreet = MockAdress.Street, Total = 36.46M }, - new Models.Orders.Order { OrderNumber = "3", SequenceNumber = 231, OrderDate = DateTime.Now, OrderStatus = OrderStatus.Cancelled, OrderItems = MockOrderItems, CardTypeId = MockPaymentInfo.CardType.Id, CardHolderName = MockPaymentInfo.CardHolderName, CardNumber = MockPaymentInfo.CardNumber, CardSecurityNumber = MockPaymentInfo.SecurityNumber, CardExpiration = new DateTime(MockPaymentInfo.ExpirationYear, MockPaymentInfo.ExpirationMonth, 1), ShippingCity = MockAdress.City, ShippingState = MockAdress.State, ShippingCountry = MockAdress.Country, ShippingStreet = MockAdress.Street, Total = 36.46M }, - new Models.Orders.Order { OrderNumber = "4", SequenceNumber = 131, OrderDate = DateTime.Now, OrderStatus = OrderStatus.Shipped, OrderItems = MockOrderItems, CardTypeId = MockPaymentInfo.CardType.Id, CardHolderName = MockPaymentInfo.CardHolderName, CardNumber = MockPaymentInfo.CardNumber, CardSecurityNumber = MockPaymentInfo.SecurityNumber, CardExpiration = new DateTime(MockPaymentInfo.ExpirationYear, MockPaymentInfo.ExpirationMonth, 1), ShippingCity = MockAdress.City, ShippingState = MockAdress.State, ShippingCountry = MockAdress.Country, ShippingStreet = MockAdress.Street, Total = 36.46M } + new Models.Orders.Order { OrderNumber = 1, SequenceNumber = 123, OrderDate = DateTime.Now, OrderStatus = OrderStatus.Submitted, OrderItems = MockOrderItems, CardTypeId = MockPaymentInfo.CardType.Id, CardHolderName = MockPaymentInfo.CardHolderName, CardNumber = MockPaymentInfo.CardNumber, CardSecurityNumber = MockPaymentInfo.SecurityNumber, CardExpiration = new DateTime(MockPaymentInfo.ExpirationYear, MockPaymentInfo.ExpirationMonth, 1), ShippingCity = MockAdress.City, ShippingState = MockAdress.State, ShippingCountry = MockAdress.Country, ShippingStreet = MockAdress.Street, Total = 36.46M }, + new Models.Orders.Order { OrderNumber = 2, SequenceNumber = 132, OrderDate = DateTime.Now, OrderStatus = OrderStatus.Paid, OrderItems = MockOrderItems, CardTypeId = MockPaymentInfo.CardType.Id, CardHolderName = MockPaymentInfo.CardHolderName, CardNumber = MockPaymentInfo.CardNumber, CardSecurityNumber = MockPaymentInfo.SecurityNumber, CardExpiration = new DateTime(MockPaymentInfo.ExpirationYear, MockPaymentInfo.ExpirationMonth, 1), ShippingCity = MockAdress.City, ShippingState = MockAdress.State, ShippingCountry = MockAdress.Country, ShippingStreet = MockAdress.Street, Total = 36.46M }, + new Models.Orders.Order { OrderNumber = 3, SequenceNumber = 231, OrderDate = DateTime.Now, OrderStatus = OrderStatus.Cancelled, OrderItems = MockOrderItems, CardTypeId = MockPaymentInfo.CardType.Id, CardHolderName = MockPaymentInfo.CardHolderName, CardNumber = MockPaymentInfo.CardNumber, CardSecurityNumber = MockPaymentInfo.SecurityNumber, CardExpiration = new DateTime(MockPaymentInfo.ExpirationYear, MockPaymentInfo.ExpirationMonth, 1), ShippingCity = MockAdress.City, ShippingState = MockAdress.State, ShippingCountry = MockAdress.Country, ShippingStreet = MockAdress.Street, Total = 36.46M }, + new Models.Orders.Order { OrderNumber = 4, SequenceNumber = 131, OrderDate = DateTime.Now, OrderStatus = OrderStatus.Shipped, OrderItems = MockOrderItems, CardTypeId = MockPaymentInfo.CardType.Id, CardHolderName = MockPaymentInfo.CardHolderName, CardNumber = MockPaymentInfo.CardNumber, CardSecurityNumber = MockPaymentInfo.SecurityNumber, CardExpiration = new DateTime(MockPaymentInfo.ExpirationYear, MockPaymentInfo.ExpirationMonth, 1), ShippingCity = MockAdress.City, ShippingState = MockAdress.State, ShippingCountry = MockAdress.Country, ShippingStreet = MockAdress.Street, Total = 36.46M } }; private static List MockOrderItems = new List() @@ -99,8 +99,7 @@ namespace eShopOnContainers.Core.Services.Order if (!string.IsNullOrEmpty(token)) return MockOrders - .FirstOrDefault(o => o.OrderNumber.Equals(orderId.ToString(), - StringComparison.CurrentCultureIgnoreCase)); + .FirstOrDefault(o => o.OrderNumber.Equals(orderId)); else return new Models.Orders.Order(); } @@ -119,5 +118,10 @@ namespace eShopOnContainers.Core.Services.Order { return MockBasketCheckout; } + + public Task CancelOrderAsync(int orderId, string token) + { + return Task.FromResult(true); + } } } \ No newline at end of file diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Order/OrderService.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Order/OrderService.cs index 2c95c62b6..2d85bef5c 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Order/OrderService.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Order/OrderService.cs @@ -2,7 +2,9 @@ using eShopOnContainers.Core.Services.RequestProvider; using System; using System.Collections.ObjectModel; +using System.Net.Http; using System.Threading.Tasks; +using eShopOnContainers.Core.Models.Orders; namespace eShopOnContainers.Core.Services.Order { @@ -88,5 +90,30 @@ namespace eShopOnContainers.Core.Services.Order Street = order.ShippingStreet }; } + + public async Task CancelOrderAsync(int orderId, string token) + { + UriBuilder builder = new UriBuilder(GlobalSetting.Instance.OrdersEndpoint); + + builder.Path = "api/v1/orders/cancel"; + + var cancelOrderCommand = new CancelOrderCommand(orderId); + + string uri = builder.ToString(); + var header = "x-requestid"; + + try + { + await _requestProvider.PutAsync(uri, cancelOrderCommand, token, header); + } + //If the status of the order has changed before to click cancel button, we will get + //a BadRequest HttpStatus + catch (HttpRequestExceptionEx ex) when (ex.HttpCode == System.Net.HttpStatusCode.BadRequest) + { + return false; + } + + return true; + } } } \ No newline at end of file diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/RequestProvider/HttpRequestExceptionEx.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/RequestProvider/HttpRequestExceptionEx.cs new file mode 100644 index 000000000..e88e74cc6 --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/RequestProvider/HttpRequestExceptionEx.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; + +namespace eShopOnContainers.Core.Services.RequestProvider +{ + public class HttpRequestExceptionEx : HttpRequestException + { + public System.Net.HttpStatusCode HttpCode { get; } + public HttpRequestExceptionEx(System.Net.HttpStatusCode code) : this(code, null, null) + { + } + + public HttpRequestExceptionEx(System.Net.HttpStatusCode code, string message) : this (code, message, null) + { + } + + public HttpRequestExceptionEx(System.Net.HttpStatusCode code, string message, Exception inner) : base(message, + inner) + { + HttpCode = code; + } + + } +} diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/RequestProvider/IRequestProvider.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/RequestProvider/IRequestProvider.cs index ba7cf8889..74856b130 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/RequestProvider/IRequestProvider.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/RequestProvider/IRequestProvider.cs @@ -10,6 +10,8 @@ namespace eShopOnContainers.Core.Services.RequestProvider Task PostAsync(string uri, string data, string clientId, string clientSecret); + Task PutAsync(string uri, TResult data, string token = "", string header = ""); + Task DeleteAsync(string uri, string token = ""); } } \ No newline at end of file diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/RequestProvider/RequestProvider.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/RequestProvider/RequestProvider.cs index e7ae41698..29afc24fd 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/RequestProvider/RequestProvider.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/RequestProvider/RequestProvider.cs @@ -83,6 +83,28 @@ namespace eShopOnContainers.Core.Services.RequestProvider return result; } + public async Task PutAsync(string uri, TResult data, string token = "", string header = "") + { + HttpClient httpClient = CreateHttpClient(token); + + if (!string.IsNullOrEmpty(header)) + { + AddHeaderParameter(httpClient, header); + } + + var content = new StringContent(JsonConvert.SerializeObject(data)); + content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + HttpResponseMessage response = await httpClient.PutAsync(uri, content); + + await HandleResponse(response); + string serialized = await response.Content.ReadAsStringAsync(); + + TResult result = await Task.Run(() => + JsonConvert.DeserializeObject(serialized, _serializerSettings)); + + return result; + } + public async Task DeleteAsync(string uri, string token = "") { HttpClient httpClient = CreateHttpClient(token); @@ -135,7 +157,7 @@ namespace eShopOnContainers.Core.Services.RequestProvider throw new ServiceAuthenticationException(content); } - throw new HttpRequestException(content); + throw new HttpRequestExceptionEx(response.StatusCode, content); } } } diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/CheckoutViewModel.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/CheckoutViewModel.cs index 8dd3d9272..a358f94d3 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/CheckoutViewModel.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/CheckoutViewModel.cs @@ -133,6 +133,7 @@ namespace eShopOnContainers.Core.ViewModels var authToken = Settings.AuthAccessToken; var basket = _orderService.MapOrderToBasket(Order); + basket.RequestId = Guid.NewGuid(); // Create basket checkout await _basketService.CheckoutAsync(basket, authToken); diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/OrderDetailViewModel.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/OrderDetailViewModel.cs index 9f0d59676..71d6ac6e1 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/OrderDetailViewModel.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/OrderDetailViewModel.cs @@ -3,14 +3,18 @@ using eShopOnContainers.Core.Models.Orders; using eShopOnContainers.Core.ViewModels.Base; using eShopOnContainers.Core.Services.Order; using System; +using System.Windows.Input; using eShopOnContainers.Core.Helpers; +using Xamarin.Forms; namespace eShopOnContainers.Core.ViewModels { public class OrderDetailViewModel : ViewModelBase { - private IOrderService _ordersService; + private readonly IOrderService _ordersService; private Order _order; + private bool _isSubmittedOrder; + private string _orderStatusText; public OrderDetailViewModel(IOrderService ordersService) { @@ -19,7 +23,7 @@ namespace eShopOnContainers.Core.ViewModels public Order Order { - get { return _order; } + get => _order; set { _order = value; @@ -27,6 +31,29 @@ namespace eShopOnContainers.Core.ViewModels } } + public bool IsSubmittedOrder + { + get => _isSubmittedOrder; + set + { + _isSubmittedOrder = value; + RaisePropertyChanged(() => IsSubmittedOrder); + } + } + + public string OrderStatusText + { + get => _orderStatusText; + set + { + _orderStatusText = value; + RaisePropertyChanged(() => OrderStatusText); + } + } + + + public ICommand ToggleCancelOrderCommand => new Command(async () => await ToggleCancelOrderAsync()); + public override async Task InitializeAsync(object navigationData) { if (navigationData is Order) @@ -37,10 +64,31 @@ namespace eShopOnContainers.Core.ViewModels // Get order detail info var authToken = Settings.AuthAccessToken; - Order = await _ordersService.GetOrderAsync(Convert.ToInt32(order.OrderNumber), authToken); + Order = await _ordersService.GetOrderAsync(order.OrderNumber, authToken); + IsSubmittedOrder = Order.OrderStatus == OrderStatus.Submitted; + OrderStatusText = Order.OrderStatus.ToString().ToUpper(); IsBusy = false; } } + + private async Task ToggleCancelOrderAsync() + { + var authToken = Settings.AuthAccessToken; + + var result = await _ordersService.CancelOrderAsync(_order.OrderNumber, authToken); + + if (result) + { + OrderStatusText = OrderStatus.Cancelled.ToString().ToUpper(); + } + else + { + Order = await _ordersService.GetOrderAsync(Order.OrderNumber, authToken); + OrderStatusText = Order.OrderStatus.ToString().ToUpper(); + } + + IsSubmittedOrder = false; + } } } \ No newline at end of file diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/MainView.xaml.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/MainView.xaml.cs index 71cc15223..1d1753438 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/MainView.xaml.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/MainView.xaml.cs @@ -48,8 +48,17 @@ namespace eShopOnContainers.Core.Views { // Force basket view refresh every time we access it await (BasketView.BindingContext as ViewModelBase).InitializeAsync(null); + } + else if (CurrentPage is CampaignView) + { + // Force campaign view refresh every time we access it await (CampaignView.BindingContext as ViewModelBase).InitializeAsync(null); } + else if (CurrentPage is ProfileView) + { + // Force profile view refresh every time we access it + await (ProfileView.BindingContext as ViewModelBase).InitializeAsync(null); + } } } } diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/OrderDetailView.xaml b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/OrderDetailView.xaml index 82012fb5c..67adbef4a 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/OrderDetailView.xaml +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/OrderDetailView.xaml @@ -55,7 +55,14 @@ - + + + @@ -96,72 +103,89 @@ Animation="{StaticResource OrderItemsAnimation}" /> - - + + + + + + + + + + - - - - - - - - - - + + + + + + + + - - + - - + - - + - - - + + + + \ No newline at end of file diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/Templates/OrderItemTemplate.xaml b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/Templates/OrderItemTemplate.xaml index 36e42267f..75d43f2d1 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/Templates/OrderItemTemplate.xaml +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/Templates/OrderItemTemplate.xaml @@ -59,8 +59,7 @@ + HeightRequest="120"> diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/eShopOnContainers.Core.csproj b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/eShopOnContainers.Core.csproj index 046a79131..7dc2d80e6 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/eShopOnContainers.Core.csproj +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/eShopOnContainers.Core.csproj @@ -75,6 +75,7 @@ + @@ -107,6 +108,7 @@ +