Order History (#49)
* working on creating and viewing orders. * Working on wiring up listing of orders * List orders page works as expected. Needed to support ThenInclude scenarios. Currently using strings.
This commit is contained in:
@@ -6,7 +6,17 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class CatalogItemOrdered // ValueObject
|
public class CatalogItemOrdered // ValueObject
|
||||||
{
|
{
|
||||||
public string CatalogItemId { get; private set; }
|
public CatalogItemOrdered(int catalogItemId, string productName, string pictureUri)
|
||||||
|
{
|
||||||
|
CatalogItemId = catalogItemId;
|
||||||
|
ProductName = productName;
|
||||||
|
PictureUri = pictureUri;
|
||||||
|
}
|
||||||
|
private CatalogItemOrdered()
|
||||||
|
{
|
||||||
|
// required by EF
|
||||||
|
}
|
||||||
|
public int CatalogItemId { get; private set; }
|
||||||
public string ProductName { get; private set; }
|
public string ProductName { get; private set; }
|
||||||
public string PictureUri { get; private set; }
|
public string PictureUri { get; private set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,20 +2,31 @@
|
|||||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace ApplicationCore.Entities.OrderAggregate
|
namespace ApplicationCore.Entities.OrderAggregate
|
||||||
{
|
{
|
||||||
public class Order : BaseEntity, IAggregateRoot
|
public class Order : BaseEntity, IAggregateRoot
|
||||||
{
|
{
|
||||||
|
private Order()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Order(string buyerId, Address shipToAddress, List<OrderItem> items)
|
||||||
|
{
|
||||||
|
ShipToAddress = shipToAddress;
|
||||||
|
_orderItems = items;
|
||||||
|
BuyerId = buyerId;
|
||||||
|
}
|
||||||
|
public string BuyerId { get; private set; }
|
||||||
|
|
||||||
public DateTimeOffset OrderDate { get; private set; } = DateTimeOffset.Now;
|
public DateTimeOffset OrderDate { get; private set; } = DateTimeOffset.Now;
|
||||||
public Address ShipToAddress { get; private set; }
|
public Address ShipToAddress { get; private set; }
|
||||||
|
|
||||||
// DDD Patterns comment
|
// DDD Patterns comment
|
||||||
// Using a private collection field, better for DDD Aggregate's encapsulation
|
// Using a private collection field, better for DDD Aggregate's encapsulation
|
||||||
// so OrderItems cannot be added from "outside the AggregateRoot" directly to the collection,
|
// so OrderItems cannot be added from "outside the AggregateRoot" directly to the collection,
|
||||||
// but only through the method OrderAggrergateRoot.AddOrderItem() which includes behaviour.
|
// but only through the method Order.AddOrderItem() which includes behavior.
|
||||||
private readonly List<OrderItem> _orderItems;
|
private readonly List<OrderItem> _orderItems = new List<OrderItem>();
|
||||||
|
|
||||||
public IReadOnlyCollection<OrderItem> OrderItems => _orderItems;
|
public IReadOnlyCollection<OrderItem> OrderItems => _orderItems;
|
||||||
// Using List<>.AsReadOnly()
|
// Using List<>.AsReadOnly()
|
||||||
@@ -23,22 +34,15 @@ namespace ApplicationCore.Entities.OrderAggregate
|
|||||||
// It's much cheaper than .ToList() because it will not have to copy all items in a new collection. (Just one heap alloc for the wrapper instance)
|
// It's much cheaper than .ToList() because it will not have to copy all items in a new collection. (Just one heap alloc for the wrapper instance)
|
||||||
//https://msdn.microsoft.com/en-us/library/e78dcd75(v=vs.110).aspx
|
//https://msdn.microsoft.com/en-us/library/e78dcd75(v=vs.110).aspx
|
||||||
|
|
||||||
|
public decimal Total()
|
||||||
|
{
|
||||||
|
var total = 0m;
|
||||||
|
foreach (var item in _orderItems)
|
||||||
|
{
|
||||||
|
total += item.UnitPrice * item.Units;
|
||||||
|
}
|
||||||
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class OrderItem : BaseEntity
|
|
||||||
{
|
|
||||||
public CatalogItemOrdered ItemOrdered { get; private set; }
|
|
||||||
public decimal UnitPrice { get; private set; }
|
|
||||||
public int Units { get; private set; }
|
|
||||||
|
|
||||||
protected OrderItem()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
public OrderItem(CatalogItemOrdered itemOrdered, decimal unitPrice, int units)
|
|
||||||
{
|
|
||||||
ItemOrdered = itemOrdered;
|
|
||||||
UnitPrice = unitPrice;
|
|
||||||
Units = units;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
22
src/ApplicationCore/Entities/OrderAggregate/OrderItem.cs
Normal file
22
src/ApplicationCore/Entities/OrderAggregate/OrderItem.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||||
|
|
||||||
|
namespace ApplicationCore.Entities.OrderAggregate
|
||||||
|
{
|
||||||
|
|
||||||
|
public class OrderItem : BaseEntity
|
||||||
|
{
|
||||||
|
public CatalogItemOrdered ItemOrdered { get; private set; }
|
||||||
|
public decimal UnitPrice { get; private set; }
|
||||||
|
public int Units { get; private set; }
|
||||||
|
|
||||||
|
protected OrderItem()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public OrderItem(CatalogItemOrdered itemOrdered, decimal unitPrice, int units)
|
||||||
|
{
|
||||||
|
ItemOrdered = itemOrdered;
|
||||||
|
UnitPrice = unitPrice;
|
||||||
|
Units = units;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
/// <typeparam name="T"></typeparam>
|
/// <typeparam name="T"></typeparam>
|
||||||
public interface IAppLogger<T>
|
public interface IAppLogger<T>
|
||||||
{
|
{
|
||||||
|
void LogInformation(string message, params object[] args);
|
||||||
void LogWarning(string message, params object[] args);
|
void LogWarning(string message, params object[] args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
12
src/ApplicationCore/Interfaces/IOrderRepository.cs
Normal file
12
src/ApplicationCore/Interfaces/IOrderRepository.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using ApplicationCore.Entities.OrderAggregate;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace ApplicationCore.Interfaces
|
||||||
|
{
|
||||||
|
|
||||||
|
public interface IOrderRepository : IRepository<Order>, IAsyncRepository<Order>
|
||||||
|
{
|
||||||
|
Order GetByIdWithItems(int id);
|
||||||
|
Task<Order> GetByIdWithItemsAsync(int id);
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/ApplicationCore/Interfaces/IOrderService.cs
Normal file
10
src/ApplicationCore/Interfaces/IOrderService.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using ApplicationCore.Entities.OrderAggregate;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace ApplicationCore.Interfaces
|
||||||
|
{
|
||||||
|
public interface IOrderService
|
||||||
|
{
|
||||||
|
Task CreateOrderAsync(int basketId, Address shippingAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace ApplicationCore.Interfaces
|
namespace ApplicationCore.Interfaces
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ namespace ApplicationCore.Interfaces
|
|||||||
{
|
{
|
||||||
Expression<Func<T, bool>> Criteria { get; }
|
Expression<Func<T, bool>> Criteria { get; }
|
||||||
List<Expression<Func<T, object>>> Includes { get; }
|
List<Expression<Func<T, object>>> Includes { get; }
|
||||||
|
List<string> IncludeStrings { get; }
|
||||||
void AddInclude(Expression<Func<T, object>> includeExpression);
|
void AddInclude(Expression<Func<T, object>> includeExpression);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
namespace ApplicationCore.Interfaces
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace ApplicationCore.Interfaces
|
|
||||||
{
|
{
|
||||||
|
|
||||||
public interface IUriComposer
|
public interface IUriComposer
|
||||||
{
|
{
|
||||||
string ComposePicUri(string uriTemplate);
|
string ComposePicUri(string uriTemplate);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using Microsoft.eShopWeb.ApplicationCore.Entities;
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using ApplicationCore.Entities.OrderAggregate;
|
||||||
|
|
||||||
namespace ApplicationCore.Specifications
|
namespace ApplicationCore.Specifications
|
||||||
{
|
{
|
||||||
@@ -28,6 +29,8 @@ namespace ApplicationCore.Specifications
|
|||||||
|
|
||||||
public List<Expression<Func<Basket, object>>> Includes { get; } = new List<Expression<Func<Basket, object>>>();
|
public List<Expression<Func<Basket, object>>> Includes { get; } = new List<Expression<Func<Basket, object>>>();
|
||||||
|
|
||||||
|
public List<string> IncludeStrings { get; } = new List<string>();
|
||||||
|
|
||||||
public void AddInclude(Expression<Func<Basket, object>> includeExpression)
|
public void AddInclude(Expression<Func<Basket, object>> includeExpression)
|
||||||
{
|
{
|
||||||
Includes.Add(includeExpression);
|
Includes.Add(includeExpression);
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ namespace ApplicationCore.Specifications
|
|||||||
|
|
||||||
public List<Expression<Func<CatalogItem, object>>> Includes { get; } = new List<Expression<Func<CatalogItem, object>>>();
|
public List<Expression<Func<CatalogItem, object>>> Includes { get; } = new List<Expression<Func<CatalogItem, object>>>();
|
||||||
|
|
||||||
|
public List<string> IncludeStrings { get; } = new List<string>();
|
||||||
|
|
||||||
public void AddInclude(Expression<Func<CatalogItem, object>> includeExpression)
|
public void AddInclude(Expression<Func<CatalogItem, object>> includeExpression)
|
||||||
{
|
{
|
||||||
Includes.Add(includeExpression);
|
Includes.Add(includeExpression);
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using ApplicationCore.Interfaces;
|
||||||
|
using System;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using ApplicationCore.Entities.OrderAggregate;
|
||||||
|
|
||||||
|
namespace ApplicationCore.Specifications
|
||||||
|
{
|
||||||
|
public class CustomerOrdersWithItemsSpecification : ISpecification<Order>
|
||||||
|
{
|
||||||
|
private readonly string _buyerId;
|
||||||
|
|
||||||
|
public CustomerOrdersWithItemsSpecification(string buyerId)
|
||||||
|
{
|
||||||
|
_buyerId = buyerId;
|
||||||
|
AddInclude(o => o.OrderItems);
|
||||||
|
AddInclude("OrderItems.ItemOrdered");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Expression<Func<Order, bool>> Criteria => o => o.BuyerId == _buyerId;
|
||||||
|
|
||||||
|
public List<Expression<Func<Order, object>>> Includes { get; } = new List<Expression<Func<Order, object>>>();
|
||||||
|
public List<string> IncludeStrings { get; } = new List<string>();
|
||||||
|
|
||||||
|
public void AddInclude(Expression<Func<Order, object>> includeExpression)
|
||||||
|
{
|
||||||
|
Includes.Add(includeExpression);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddInclude(string includeString)
|
||||||
|
{
|
||||||
|
IncludeStrings.Add(includeString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||||
using Microsoft.EntityFrameworkCore.Metadata;
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using ApplicationCore.Entities.OrderAggregate;
|
||||||
|
|
||||||
namespace Infrastructure.Data
|
namespace Infrastructure.Data
|
||||||
{
|
{
|
||||||
@@ -20,6 +21,8 @@ namespace Infrastructure.Data
|
|||||||
public DbSet<CatalogItem> CatalogItems { get; set; }
|
public DbSet<CatalogItem> CatalogItems { get; set; }
|
||||||
public DbSet<CatalogBrand> CatalogBrands { get; set; }
|
public DbSet<CatalogBrand> CatalogBrands { get; set; }
|
||||||
public DbSet<CatalogType> CatalogTypes { get; set; }
|
public DbSet<CatalogType> CatalogTypes { get; set; }
|
||||||
|
public DbSet<Order> Orders { get; set; }
|
||||||
|
public DbSet<OrderItem> OrderItems { get; set; }
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder builder)
|
protected override void OnModelCreating(ModelBuilder builder)
|
||||||
{
|
{
|
||||||
@@ -27,6 +30,8 @@ namespace Infrastructure.Data
|
|||||||
builder.Entity<CatalogBrand>(ConfigureCatalogBrand);
|
builder.Entity<CatalogBrand>(ConfigureCatalogBrand);
|
||||||
builder.Entity<CatalogType>(ConfigureCatalogType);
|
builder.Entity<CatalogType>(ConfigureCatalogType);
|
||||||
builder.Entity<CatalogItem>(ConfigureCatalogItem);
|
builder.Entity<CatalogItem>(ConfigureCatalogItem);
|
||||||
|
builder.Entity<Order>(ConfigureOrder);
|
||||||
|
builder.Entity<OrderItem>(ConfigureOrderItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ConfigureBasket(EntityTypeBuilder<Basket> builder)
|
private void ConfigureBasket(EntityTypeBuilder<Basket> builder)
|
||||||
@@ -36,7 +41,7 @@ namespace Infrastructure.Data
|
|||||||
navigation.SetPropertyAccessMode(PropertyAccessMode.Field);
|
navigation.SetPropertyAccessMode(PropertyAccessMode.Field);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureCatalogItem(EntityTypeBuilder<CatalogItem> builder)
|
private void ConfigureCatalogItem(EntityTypeBuilder<CatalogItem> builder)
|
||||||
{
|
{
|
||||||
builder.ToTable("Catalog");
|
builder.ToTable("Catalog");
|
||||||
|
|
||||||
@@ -63,7 +68,7 @@ namespace Infrastructure.Data
|
|||||||
.HasForeignKey(ci => ci.CatalogTypeId);
|
.HasForeignKey(ci => ci.CatalogTypeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureCatalogBrand(EntityTypeBuilder<CatalogBrand> builder)
|
private void ConfigureCatalogBrand(EntityTypeBuilder<CatalogBrand> builder)
|
||||||
{
|
{
|
||||||
builder.ToTable("CatalogBrand");
|
builder.ToTable("CatalogBrand");
|
||||||
|
|
||||||
@@ -78,7 +83,7 @@ namespace Infrastructure.Data
|
|||||||
.HasMaxLength(100);
|
.HasMaxLength(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureCatalogType(EntityTypeBuilder<CatalogType> builder)
|
private void ConfigureCatalogType(EntityTypeBuilder<CatalogType> builder)
|
||||||
{
|
{
|
||||||
builder.ToTable("CatalogType");
|
builder.ToTable("CatalogType");
|
||||||
|
|
||||||
@@ -92,5 +97,14 @@ namespace Infrastructure.Data
|
|||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(100);
|
.HasMaxLength(100);
|
||||||
}
|
}
|
||||||
|
private void ConfigureOrder(EntityTypeBuilder<Order> builder)
|
||||||
|
{
|
||||||
|
builder.OwnsOne(o => o.ShipToAddress);
|
||||||
|
}
|
||||||
|
private void ConfigureOrderItem(EntityTypeBuilder<OrderItem> builder)
|
||||||
|
{
|
||||||
|
builder.OwnsOne(i => i.ItemOrdered);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,19 +14,19 @@ namespace Infrastructure.Data
|
|||||||
/// <typeparam name="T"></typeparam>
|
/// <typeparam name="T"></typeparam>
|
||||||
public class EfRepository<T> : IRepository<T>, IAsyncRepository<T> where T : BaseEntity
|
public class EfRepository<T> : IRepository<T>, IAsyncRepository<T> where T : BaseEntity
|
||||||
{
|
{
|
||||||
private readonly CatalogContext _dbContext;
|
protected readonly CatalogContext _dbContext;
|
||||||
|
|
||||||
public EfRepository(CatalogContext dbContext)
|
public EfRepository(CatalogContext dbContext)
|
||||||
{
|
{
|
||||||
_dbContext = dbContext;
|
_dbContext = dbContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
public T GetById(int id)
|
public virtual T GetById(int id)
|
||||||
{
|
{
|
||||||
return _dbContext.Set<T>().Find(id);
|
return _dbContext.Set<T>().Find(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<T> GetByIdAsync(int id)
|
public virtual async Task<T> GetByIdAsync(int id)
|
||||||
{
|
{
|
||||||
return await _dbContext.Set<T>().FindAsync(id);
|
return await _dbContext.Set<T>().FindAsync(id);
|
||||||
}
|
}
|
||||||
@@ -46,7 +46,10 @@ namespace Infrastructure.Data
|
|||||||
var queryableResultWithIncludes = spec.Includes
|
var queryableResultWithIncludes = spec.Includes
|
||||||
.Aggregate(_dbContext.Set<T>().AsQueryable(),
|
.Aggregate(_dbContext.Set<T>().AsQueryable(),
|
||||||
(current, include) => current.Include(include));
|
(current, include) => current.Include(include));
|
||||||
return queryableResultWithIncludes
|
var secondaryResult = spec.IncludeStrings
|
||||||
|
.Aggregate(queryableResultWithIncludes,
|
||||||
|
(current, include) => current.Include(include));
|
||||||
|
return secondaryResult
|
||||||
.Where(spec.Criteria)
|
.Where(spec.Criteria)
|
||||||
.AsEnumerable();
|
.AsEnumerable();
|
||||||
}
|
}
|
||||||
@@ -55,7 +58,11 @@ namespace Infrastructure.Data
|
|||||||
var queryableResultWithIncludes = spec.Includes
|
var queryableResultWithIncludes = spec.Includes
|
||||||
.Aggregate(_dbContext.Set<T>().AsQueryable(),
|
.Aggregate(_dbContext.Set<T>().AsQueryable(),
|
||||||
(current, include) => current.Include(include));
|
(current, include) => current.Include(include));
|
||||||
return await queryableResultWithIncludes
|
var secondaryResult = spec.IncludeStrings
|
||||||
|
.Aggregate(queryableResultWithIncludes,
|
||||||
|
(current, include) => current.Include(include));
|
||||||
|
|
||||||
|
return await secondaryResult
|
||||||
.Where(spec.Criteria)
|
.Where(spec.Criteria)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|||||||
31
src/Infrastructure/Data/OrderRepository.cs
Normal file
31
src/Infrastructure/Data/OrderRepository.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
using ApplicationCore.Entities.OrderAggregate;
|
||||||
|
using ApplicationCore.Interfaces;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Infrastructure.Data
|
||||||
|
{
|
||||||
|
public class OrderRepository : EfRepository<Order>, IOrderRepository
|
||||||
|
{
|
||||||
|
public OrderRepository(CatalogContext dbContext) : base(dbContext)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Order GetByIdWithItems(int id)
|
||||||
|
{
|
||||||
|
return _dbContext.Orders
|
||||||
|
.Include(o => o.OrderItems)
|
||||||
|
.Include("OrderItems.ItemOrdered")
|
||||||
|
.FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Order> GetByIdWithItemsAsync(int id)
|
||||||
|
{
|
||||||
|
return _dbContext.Orders
|
||||||
|
.Include(o => o.OrderItems)
|
||||||
|
.Include("OrderItems.ItemOrdered")
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,10 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
||||||
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.0" PrivateAssets="All" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.0" PrivateAssets="All" />
|
||||||
@@ -17,7 +21,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Data\Migrations\" />
|
<Folder Include="Data\Migrations\" />
|
||||||
<Folder Include="Services\" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -10,9 +10,14 @@ namespace Infrastructure.Logging
|
|||||||
{
|
{
|
||||||
_logger = loggerFactory.CreateLogger<T>();
|
_logger = loggerFactory.CreateLogger<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LogWarning(string message, params object[] args)
|
public void LogWarning(string message, params object[] args)
|
||||||
{
|
{
|
||||||
_logger.LogWarning(message, args);
|
_logger.LogWarning(message, args);
|
||||||
}
|
}
|
||||||
|
public void LogInformation(string message, params object[] args)
|
||||||
|
{
|
||||||
|
_logger.LogInformation(message, args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
40
src/Infrastructure/Services/OrderService.cs
Normal file
40
src/Infrastructure/Services/OrderService.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using ApplicationCore.Interfaces;
|
||||||
|
using ApplicationCore.Entities.OrderAggregate;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Infrastructure.Services
|
||||||
|
{
|
||||||
|
public class OrderService : IOrderService
|
||||||
|
{
|
||||||
|
private readonly IAsyncRepository<Order> _orderRepository;
|
||||||
|
private readonly IAsyncRepository<Basket> _basketRepository;
|
||||||
|
private readonly IAsyncRepository<CatalogItem> _itemRepository;
|
||||||
|
|
||||||
|
public OrderService(IAsyncRepository<Basket> basketRepository,
|
||||||
|
IAsyncRepository<CatalogItem> itemRepository,
|
||||||
|
IAsyncRepository<Order> orderRepository)
|
||||||
|
{
|
||||||
|
_orderRepository = orderRepository;
|
||||||
|
_basketRepository = basketRepository;
|
||||||
|
_itemRepository = itemRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CreateOrderAsync(int basketId, Address shippingAddress)
|
||||||
|
{
|
||||||
|
var basket = await _basketRepository.GetByIdAsync(basketId);
|
||||||
|
var items = new List<OrderItem>();
|
||||||
|
foreach (var item in basket.Items)
|
||||||
|
{
|
||||||
|
var catalogItem = await _itemRepository.GetByIdAsync(item.CatalogItemId);
|
||||||
|
var itemOrdered = new CatalogItemOrdered(catalogItem.Id, catalogItem.Name, catalogItem.PictureUri);
|
||||||
|
var orderItem = new OrderItem(itemOrdered, item.UnitPrice, item.Quantity);
|
||||||
|
items.Add(orderItem);
|
||||||
|
}
|
||||||
|
var order = new Order(basket.BuyerId, shippingAddress, items);
|
||||||
|
|
||||||
|
await _orderRepository.AddAsync(order);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,6 +36,12 @@ namespace Microsoft.eShopWeb.Controllers
|
|||||||
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
|
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
|
||||||
|
|
||||||
ViewData["ReturnUrl"] = returnUrl;
|
ViewData["ReturnUrl"] = returnUrl;
|
||||||
|
if (!String.IsNullOrEmpty(returnUrl) &&
|
||||||
|
returnUrl.ToLower().Contains("checkout"))
|
||||||
|
{
|
||||||
|
ViewData["ReturnUrl"] = "/Basket/Index";
|
||||||
|
}
|
||||||
|
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ using Infrastructure.Identity;
|
|||||||
using System;
|
using System;
|
||||||
using Web;
|
using Web;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using ApplicationCore.Entities.OrderAggregate;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
|
||||||
namespace Microsoft.eShopWeb.Controllers
|
namespace Microsoft.eShopWeb.Controllers
|
||||||
{
|
{
|
||||||
@@ -19,8 +21,10 @@ namespace Microsoft.eShopWeb.Controllers
|
|||||||
private readonly IUriComposer _uriComposer;
|
private readonly IUriComposer _uriComposer;
|
||||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||||
private readonly IAppLogger<BasketController> _logger;
|
private readonly IAppLogger<BasketController> _logger;
|
||||||
|
private readonly IOrderService _orderService;
|
||||||
|
|
||||||
public BasketController(IBasketService basketService,
|
public BasketController(IBasketService basketService,
|
||||||
|
IOrderService orderService,
|
||||||
IUriComposer uriComposer,
|
IUriComposer uriComposer,
|
||||||
SignInManager<ApplicationUser> signInManager,
|
SignInManager<ApplicationUser> signInManager,
|
||||||
IAppLogger<BasketController> logger)
|
IAppLogger<BasketController> logger)
|
||||||
@@ -29,6 +33,7 @@ namespace Microsoft.eShopWeb.Controllers
|
|||||||
_uriComposer = uriComposer;
|
_uriComposer = uriComposer;
|
||||||
_signInManager = signInManager;
|
_signInManager = signInManager;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_orderService = orderService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
@@ -65,19 +70,15 @@ namespace Microsoft.eShopWeb.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IActionResult> Checkout(List<BasketItemViewModel> model)
|
[Authorize]
|
||||||
|
public async Task<IActionResult> Checkout(Dictionary<string, int> items)
|
||||||
{
|
{
|
||||||
// TODO: Get model binding working with collection of items
|
var basketViewModel = await GetBasketViewModelAsync();
|
||||||
var basket = await GetBasketViewModelAsync();
|
await _basketService.SetQuantities(basketViewModel.Id, items);
|
||||||
//await _basketService.SetQuantities(basket.Id, quantities);
|
|
||||||
|
|
||||||
foreach (var item in basket.Items)
|
await _orderService.CreateOrderAsync(basketViewModel.Id, new Address("123 Main St.", "Kent", "OH", "United States", "44240"));
|
||||||
{
|
|
||||||
_logger.LogWarning($"Id: {item.Id}; Qty: {item.Quantity}");
|
|
||||||
}
|
|
||||||
// redirect to OrdersController
|
|
||||||
|
|
||||||
await _basketService.Checkout(basket.Id);
|
await _basketService.DeleteBasketAsync(basketViewModel.Id);
|
||||||
|
|
||||||
return View("Checkout");
|
return View("Checkout");
|
||||||
}
|
}
|
||||||
|
|||||||
96
src/Web/Controllers/OrderController.cs
Normal file
96
src/Web/Controllers/OrderController.cs
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.eShopWeb.ViewModels;
|
||||||
|
using System;
|
||||||
|
using ApplicationCore.Entities.OrderAggregate;
|
||||||
|
using ApplicationCore.Interfaces;
|
||||||
|
using System.Linq;
|
||||||
|
using ApplicationCore.Specifications;
|
||||||
|
|
||||||
|
namespace Microsoft.eShopWeb.Controllers
|
||||||
|
{
|
||||||
|
[Authorize]
|
||||||
|
[Route("[controller]/[action]")]
|
||||||
|
public class OrderController : Controller
|
||||||
|
{
|
||||||
|
private readonly IOrderRepository _orderRepository;
|
||||||
|
|
||||||
|
public OrderController(IOrderRepository orderRepository) {
|
||||||
|
_orderRepository = orderRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> Index()
|
||||||
|
{
|
||||||
|
var orders = await _orderRepository.ListAsync(new CustomerOrdersWithItemsSpecification(User.Identity.Name));
|
||||||
|
|
||||||
|
var viewModel = orders
|
||||||
|
.Select(o => new OrderViewModel()
|
||||||
|
{
|
||||||
|
OrderDate = o.OrderDate,
|
||||||
|
OrderItems = o.OrderItems?.Select(oi => new OrderItemViewModel()
|
||||||
|
{
|
||||||
|
Discount = 0,
|
||||||
|
PictureUrl = oi.ItemOrdered.PictureUri,
|
||||||
|
ProductId = oi.ItemOrdered.CatalogItemId,
|
||||||
|
ProductName = oi.ItemOrdered.ProductName,
|
||||||
|
UnitPrice = oi.UnitPrice,
|
||||||
|
Units = oi.Units
|
||||||
|
}).ToList(),
|
||||||
|
OrderNumber = o.Id,
|
||||||
|
ShippingAddress = o.ShipToAddress,
|
||||||
|
Status = "Pending",
|
||||||
|
Total = o.Total()
|
||||||
|
|
||||||
|
});
|
||||||
|
return View(viewModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{orderId}")]
|
||||||
|
public async Task<IActionResult> Detail(int orderId)
|
||||||
|
{
|
||||||
|
var order = await _orderRepository.GetByIdWithItemsAsync(orderId);
|
||||||
|
var viewModel = new OrderViewModel()
|
||||||
|
{
|
||||||
|
OrderDate = order.OrderDate,
|
||||||
|
OrderItems = order.OrderItems.Select(oi => new OrderItemViewModel()
|
||||||
|
{
|
||||||
|
Discount = 0,
|
||||||
|
PictureUrl = oi.ItemOrdered.PictureUri,
|
||||||
|
ProductId = oi.ItemOrdered.CatalogItemId,
|
||||||
|
ProductName = oi.ItemOrdered.ProductName,
|
||||||
|
UnitPrice = oi.UnitPrice,
|
||||||
|
Units = oi.Units
|
||||||
|
}).ToList(),
|
||||||
|
OrderNumber = order.Id,
|
||||||
|
ShippingAddress = order.ShipToAddress,
|
||||||
|
Status = "Pending",
|
||||||
|
Total = order.Total()
|
||||||
|
};
|
||||||
|
return View(viewModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OrderViewModel GetOrder()
|
||||||
|
{
|
||||||
|
var order = new OrderViewModel()
|
||||||
|
{
|
||||||
|
OrderDate = DateTimeOffset.Now.AddDays(-1),
|
||||||
|
OrderNumber = 12354,
|
||||||
|
Status = "Submitted",
|
||||||
|
Total = 123.45m,
|
||||||
|
ShippingAddress = new Address("123 Main St.", "Kent", "OH", "United States", "44240")
|
||||||
|
};
|
||||||
|
|
||||||
|
order.OrderItems.Add(new OrderItemViewModel()
|
||||||
|
{
|
||||||
|
ProductId = 1,
|
||||||
|
PictureUrl = "",
|
||||||
|
ProductName = "Something",
|
||||||
|
UnitPrice = 5.05m,
|
||||||
|
Units = 2
|
||||||
|
});
|
||||||
|
|
||||||
|
return order;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
using Microsoft.eShopWeb.ViewModels;
|
||||||
using Microsoft.eShopWeb.ViewModels;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@@ -11,6 +10,6 @@ namespace ApplicationCore.Interfaces
|
|||||||
Task TransferBasketAsync(string anonymousId, string userName);
|
Task TransferBasketAsync(string anonymousId, string userName);
|
||||||
Task AddItemToBasket(int basketId, int catalogItemId, decimal price, int quantity);
|
Task AddItemToBasket(int basketId, int catalogItemId, decimal price, int quantity);
|
||||||
Task SetQuantities(int basketId, Dictionary<string, int> quantities);
|
Task SetQuantities(int basketId, Dictionary<string, int> quantities);
|
||||||
Task Checkout(int basketId);
|
Task DeleteBasketAsync(int basketId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,12 +98,10 @@ namespace Web.Services
|
|||||||
await _basketRepository.UpdateAsync(basket);
|
await _basketRepository.UpdateAsync(basket);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Checkout(int basketId)
|
public async Task DeleteBasketAsync(int basketId)
|
||||||
{
|
{
|
||||||
var basket = await _basketRepository.GetByIdAsync(basketId);
|
var basket = await _basketRepository.GetByIdAsync(basketId);
|
||||||
|
|
||||||
// TODO: Actually Process the order
|
|
||||||
|
|
||||||
await _basketRepository.DeleteAsync(basket);
|
await _basketRepository.DeleteAsync(basket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
using ApplicationCore.Interfaces;
|
using ApplicationCore.Entities.OrderAggregate;
|
||||||
|
using ApplicationCore.Interfaces;
|
||||||
using ApplicationCore.Services;
|
using ApplicationCore.Services;
|
||||||
using Infrastructure.Data;
|
using Infrastructure.Data;
|
||||||
using Infrastructure.Identity;
|
using Infrastructure.Identity;
|
||||||
using Infrastructure.Logging;
|
using Infrastructure.Logging;
|
||||||
|
using Infrastructure.Services;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
@@ -12,6 +14,7 @@ using Microsoft.eShopWeb.Services;
|
|||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Web.Services;
|
using Web.Services;
|
||||||
|
|
||||||
@@ -53,12 +56,22 @@ namespace Microsoft.eShopWeb
|
|||||||
.AddEntityFrameworkStores<AppIdentityDbContext>()
|
.AddEntityFrameworkStores<AppIdentityDbContext>()
|
||||||
.AddDefaultTokenProviders();
|
.AddDefaultTokenProviders();
|
||||||
|
|
||||||
|
services.ConfigureApplicationCookie(options =>
|
||||||
|
{
|
||||||
|
options.Cookie.HttpOnly = true;
|
||||||
|
options.ExpireTimeSpan = TimeSpan.FromHours(1);
|
||||||
|
options.LoginPath = "/Account/Signin";
|
||||||
|
options.LogoutPath = "/Account/Signout";
|
||||||
|
});
|
||||||
|
|
||||||
services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>));
|
services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>));
|
||||||
services.AddScoped(typeof(IAsyncRepository<>), typeof(EfRepository<>));
|
services.AddScoped(typeof(IAsyncRepository<>), typeof(EfRepository<>));
|
||||||
|
|
||||||
services.AddMemoryCache();
|
services.AddMemoryCache();
|
||||||
services.AddScoped<ICatalogService, CachedCatalogService>();
|
services.AddScoped<ICatalogService, CachedCatalogService>();
|
||||||
services.AddScoped<IBasketService, BasketService>();
|
services.AddScoped<IBasketService, BasketService>();
|
||||||
|
services.AddScoped<IOrderService, OrderService>();
|
||||||
|
services.AddScoped<IOrderRepository, OrderRepository>();
|
||||||
services.AddScoped<CatalogService>();
|
services.AddScoped<CatalogService>();
|
||||||
services.Configure<CatalogSettings>(Configuration);
|
services.Configure<CatalogSettings>(Configuration);
|
||||||
services.AddSingleton<IUriComposer>(new UriComposer(Configuration.Get<CatalogSettings>()));
|
services.AddSingleton<IUriComposer>(new UriComposer(Configuration.Get<CatalogSettings>()));
|
||||||
|
|||||||
23
src/Web/ViewModels/OrderItemViewModel.cs
Normal file
23
src/Web/ViewModels/OrderItemViewModel.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace Microsoft.eShopWeb.ViewModels
|
||||||
|
{
|
||||||
|
|
||||||
|
public class OrderItemViewModel
|
||||||
|
{
|
||||||
|
public int ProductId { get; set; }
|
||||||
|
|
||||||
|
public string ProductName { get; set; }
|
||||||
|
|
||||||
|
public decimal UnitPrice { get; set; }
|
||||||
|
|
||||||
|
public decimal Discount { get; set; }
|
||||||
|
|
||||||
|
public int Units { get; set; }
|
||||||
|
|
||||||
|
public string PictureUrl { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
21
src/Web/ViewModels/OrderViewModel.cs
Normal file
21
src/Web/ViewModels/OrderViewModel.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using ApplicationCore.Entities.OrderAggregate;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Microsoft.eShopWeb.ViewModels
|
||||||
|
{
|
||||||
|
|
||||||
|
public class OrderViewModel
|
||||||
|
{
|
||||||
|
public int OrderNumber { get; set; }
|
||||||
|
public DateTimeOffset OrderDate { get; set; }
|
||||||
|
public decimal Total { get; set; }
|
||||||
|
public string Status { get; set; }
|
||||||
|
|
||||||
|
public Address ShippingAddress { get; set; }
|
||||||
|
|
||||||
|
public List<OrderItemViewModel> OrderItems { get; set; } = new List<OrderItemViewModel>();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace Microsoft.eShopWeb.ViewModels
|
namespace Microsoft.eShopWeb.ViewModels
|
||||||
{
|
{
|
||||||
|
|||||||
88
src/Web/Views/Order/Detail.cshtml
Normal file
88
src/Web/Views/Order/Detail.cshtml
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
@using Microsoft.eShopWeb.ViewModels
|
||||||
|
@model OrderViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "My Order History";
|
||||||
|
}
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Order Detail";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="esh-orders_detail">
|
||||||
|
<div class="container">
|
||||||
|
<section class="esh-orders_detail-section">
|
||||||
|
<article class="esh-orders_detail-titles row">
|
||||||
|
<section class="esh-orders_detail-title col-xs-3">Order number</section>
|
||||||
|
<section class="esh-orders_detail-title col-xs-3">Date</section>
|
||||||
|
<section class="esh-orders_detail-title col-xs-3">Total</section>
|
||||||
|
<section class="esh-orders_detail-title col-xs-3">Status</section>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="esh-orders_detail-items row">
|
||||||
|
<section class="esh-orders_detail-item col-xs-3">@Model.OrderNumber</section>
|
||||||
|
<section class="esh-orders_detail-item col-xs-3">@Model.OrderDate</section>
|
||||||
|
<section class="esh-orders_detail-item col-xs-3">$@Model.Total</section>
|
||||||
|
<section class="esh-orders_detail-title col-xs-3">@Model.Status</section>
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
@*<section class="esh-orders_detail-section">
|
||||||
|
<article class="esh-orders_detail-titles row">
|
||||||
|
<section class="esh-orders_detail-title col-xs-12">Description</section>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="esh-orders_detail-items row">
|
||||||
|
<section class="esh-orders_detail-item col-xs-12">@Model.Description</section>
|
||||||
|
</article>
|
||||||
|
</section>*@
|
||||||
|
|
||||||
|
<section class="esh-orders_detail-section">
|
||||||
|
<article class="esh-orders_detail-titles row">
|
||||||
|
<section class="esh-orders_detail-title col-xs-12">Shipping Address</section>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="esh-orders_detail-items row">
|
||||||
|
<section class="esh-orders_detail-item col-xs-12">@Model.ShippingAddress.Street</section>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="esh-orders_detail-items row">
|
||||||
|
<section class="esh-orders_detail-item col-xs-12">@Model.ShippingAddress.City</section>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="esh-orders_detail-items row">
|
||||||
|
<section class="esh-orders_detail-item col-xs-12">@Model.ShippingAddress.Country</section>
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="esh-orders_detail-section">
|
||||||
|
<article class="esh-orders_detail-titles row">
|
||||||
|
<section class="esh-orders_detail-title col-xs-12">ORDER DETAILS</section>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
@for (int i = 0; i < Model.OrderItems.Count; i++)
|
||||||
|
{
|
||||||
|
var item = Model.OrderItems[i];
|
||||||
|
<article class="esh-orders_detail-items esh-orders_detail-items--border row">
|
||||||
|
<section class="esh-orders_detail-item col-md-4 hidden-md-down">
|
||||||
|
<img class="esh-orders_detail-image" src="@item.PictureUrl">
|
||||||
|
</section>
|
||||||
|
<section class="esh-orders_detail-item esh-orders_detail-item--middle col-xs-4">@item.ProductName</section>
|
||||||
|
<section class="esh-orders_detail-item esh-orders_detail-item--middle col-xs-1">$ @item.UnitPrice.ToString("N2")</section>
|
||||||
|
<section class="esh-orders_detail-item esh-orders_detail-item--middle col-xs-1">@item.Units</section>
|
||||||
|
<section class="esh-orders_detail-item esh-orders_detail-item--middle col-xs-2">$ @Math.Round(item.Units * item.UnitPrice, 2).ToString("N2")</section>
|
||||||
|
</article>
|
||||||
|
}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="esh-orders_detail-section esh-orders_detail-section--right">
|
||||||
|
<article class="esh-orders_detail-titles esh-basket-titles--clean row">
|
||||||
|
<section class="esh-orders_detail-title col-xs-9"></section>
|
||||||
|
<section class="esh-orders_detail-title col-xs-2">TOTAL</section>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="esh-orders_detail-items row">
|
||||||
|
<section class="esh-orders_detail-item col-xs-9"></section>
|
||||||
|
<section class="esh-orders_detail-item esh-orders_detail-item--mark col-xs-2">$ @Model.Total</section>
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
39
src/Web/Views/Order/Index.cshtml
Normal file
39
src/Web/Views/Order/Index.cshtml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
@using Microsoft.eShopWeb.ViewModels
|
||||||
|
@model IEnumerable<OrderViewModel>
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "My Order History";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="esh-orders">
|
||||||
|
<div class="container">
|
||||||
|
<h1>@ViewData["Title"]</h1>
|
||||||
|
<article class="esh-orders-titles row">
|
||||||
|
<section class="esh-orders-title col-xs-2">Order number</section>
|
||||||
|
<section class="esh-orders-title col-xs-4">Date</section>
|
||||||
|
<section class="esh-orders-title col-xs-2">Total</section>
|
||||||
|
<section class="esh-orders-title col-xs-2">Status</section>
|
||||||
|
<section class="esh-orders-title col-xs-2"></section>
|
||||||
|
</article>
|
||||||
|
@if (Model != null && Model.Any())
|
||||||
|
{
|
||||||
|
@foreach (var item in Model)
|
||||||
|
{
|
||||||
|
<article class="esh-orders-items row">
|
||||||
|
<section class="esh-orders-item col-xs-2">@Html.DisplayFor(modelItem => item.OrderNumber)</section>
|
||||||
|
<section class="esh-orders-item col-xs-4">@Html.DisplayFor(modelItem => item.OrderDate)</section>
|
||||||
|
<section class="esh-orders-item col-xs-2">$ @Html.DisplayFor(modelItem => item.Total)</section>
|
||||||
|
<section class="esh-orders-item col-xs-2">@Html.DisplayFor(modelItem => item.Status)</section>
|
||||||
|
<section class="esh-orders-item col-xs-1">
|
||||||
|
<a class="esh-orders-link" asp-controller="Order" asp-action="Detail" asp-route-orderId="@item.OrderNumber">Detail</a>
|
||||||
|
</section>
|
||||||
|
<section class="esh-orders-item col-xs-1">
|
||||||
|
@if (item.Status.ToLower() == "submitted")
|
||||||
|
{
|
||||||
|
<a class="esh-orders-link" asp-controller="Order" asp-action="cancel" asp-route-orderId="@item.OrderNumber">Cancel</a>
|
||||||
|
}
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<a class="esh-basketstatus "
|
<a class="esh-basketstatus "
|
||||||
asp-area=""
|
asp-area=""
|
||||||
asp-controller="Cart"
|
asp-controller="Basket"
|
||||||
asp-action="Index">
|
asp-action="Index">
|
||||||
<div class="esh-basketstatus-image">
|
<div class="esh-basketstatus-image">
|
||||||
<img src="~/images/cart.png" />
|
<img src="~/images/cart.png" />
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
<link rel="stylesheet" href="~/css/catalog/pager.css" />
|
<link rel="stylesheet" href="~/css/catalog/pager.css" />
|
||||||
<link rel="stylesheet" href="~/css/catalog/catalog.component.css" />
|
<link rel="stylesheet" href="~/css/catalog/catalog.component.css" />
|
||||||
<link rel="stylesheet" href="~/css/basket/basket-status/basket-status.component.css" />
|
<link rel="stylesheet" href="~/css/basket/basket-status/basket-status.component.css" />
|
||||||
|
<link rel="stylesheet" href="~/css/orders/orders.component.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header class="navbar navbar-light navbar-static-top">
|
<header class="navbar navbar-light navbar-static-top">
|
||||||
@@ -46,7 +47,6 @@
|
|||||||
<article class="row">
|
<article class="row">
|
||||||
|
|
||||||
<section class="col-sm-6">
|
<section class="col-sm-6">
|
||||||
<img class="esh-app-footer-brand" src="../images/brand_dark.png" />
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="col-sm-6">
|
<section class="col-sm-6">
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
[
|
[
|
||||||
//{
|
{
|
||||||
// "outputFile": "wwwroot/css/orders/orders.component.css",
|
"outputFile": "wwwroot/css/orders/orders.component.css",
|
||||||
// "inputFile": "wwwroot/css/orders/orders.component.scss"
|
"inputFile": "wwwroot/css/orders/orders.component.scss"
|
||||||
//},
|
},
|
||||||
//{
|
//{
|
||||||
// "outputFile": "wwwroot/css/orders/orders-new/orders-new.component.css",
|
// "outputFile": "wwwroot/css/orders/orders-new/orders-new.component.css",
|
||||||
// "inputFile": "wwwroot/css/orders/orders-new/orders-new.component.scss"
|
// "inputFile": "wwwroot/css/orders/orders-new/orders-new.component.scss"
|
||||||
|
|||||||
50
src/Web/wwwroot/css/orders/orders.component.css
Normal file
50
src/Web/wwwroot/css/orders/orders.component.css
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
.esh-orders {
|
||||||
|
min-height: 80vh;
|
||||||
|
overflow-x: hidden; }
|
||||||
|
.esh-orders-header {
|
||||||
|
background-color: #00A69C;
|
||||||
|
height: 4rem; }
|
||||||
|
.esh-orders-back {
|
||||||
|
color: rgba(255, 255, 255, 0.4);
|
||||||
|
line-height: 4rem;
|
||||||
|
text-decoration: none;
|
||||||
|
text-transform: uppercase;
|
||||||
|
transition: color 0.35s; }
|
||||||
|
.esh-orders-back:hover {
|
||||||
|
color: #FFFFFF;
|
||||||
|
transition: color 0.35s; }
|
||||||
|
.esh-orders-titles {
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
padding-top: 2rem; }
|
||||||
|
.esh-orders-title {
|
||||||
|
text-transform: uppercase; }
|
||||||
|
.esh-orders-items {
|
||||||
|
height: 2rem;
|
||||||
|
line-height: 2rem;
|
||||||
|
position: relative; }
|
||||||
|
.esh-orders-items:nth-of-type(2n + 1):before {
|
||||||
|
background-color: #EEEEFF;
|
||||||
|
content: '';
|
||||||
|
height: 100%;
|
||||||
|
left: 0;
|
||||||
|
margin-left: -100vw;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
width: 200vw;
|
||||||
|
z-index: -1; }
|
||||||
|
.esh-orders-item {
|
||||||
|
font-weight: 300; }
|
||||||
|
.esh-orders-item--hover {
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none; }
|
||||||
|
.esh-orders-items:hover .esh-orders-item--hover {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: all; }
|
||||||
|
.esh-orders-link {
|
||||||
|
color: #83D01B;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.35s; }
|
||||||
|
.esh-orders-link:hover {
|
||||||
|
color: #75b918;
|
||||||
|
transition: color 0.35s; }
|
||||||
|
|
||||||
1
src/Web/wwwroot/css/orders/orders.component.min.css
vendored
Normal file
1
src/Web/wwwroot/css/orders/orders.component.min.css
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.esh-orders{min-height:80vh;overflow-x:hidden}.esh-orders-header{background-color:#00a69c;height:4rem}.esh-orders-back{color:rgba(255,255,255,.4);line-height:4rem;text-decoration:none;text-transform:uppercase;transition:color .35s}.esh-orders-back:hover{color:#fff;transition:color .35s}.esh-orders-titles{padding-bottom:1rem;padding-top:2rem}.esh-orders-title{text-transform:uppercase}.esh-orders-items{height:2rem;line-height:2rem;position:relative}.esh-orders-items:nth-of-type(2n+1):before{background-color:#eef;content:'';height:100%;left:0;margin-left:-100vw;position:absolute;top:0;width:200vw;z-index:-1}.esh-orders-item{font-weight:300}.esh-orders-item--hover{opacity:0;pointer-events:none}.esh-orders-items:hover .esh-orders-item--hover{opacity:1;pointer-events:all}.esh-orders-link{color:#83d01b;text-decoration:none;transition:color .35s}.esh-orders-link:hover{color:#75b918;transition:color .35s}
|
||||||
91
src/Web/wwwroot/css/orders/orders.component.scss
Normal file
91
src/Web/wwwroot/css/orders/orders.component.scss
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
@import '../variables';
|
||||||
|
|
||||||
|
.esh-orders {
|
||||||
|
min-height: 80vh;
|
||||||
|
overflow-x: hidden;
|
||||||
|
$header-height: 4rem;
|
||||||
|
&-header
|
||||||
|
|
||||||
|
{
|
||||||
|
background-color: #00A69C;
|
||||||
|
height: $header-height;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-back {
|
||||||
|
color: rgba($color-foreground-brighter, .4);
|
||||||
|
line-height: $header-height;
|
||||||
|
text-decoration: none;
|
||||||
|
text-transform: uppercase;
|
||||||
|
transition: color $animation-speed-default;
|
||||||
|
&:hover
|
||||||
|
|
||||||
|
{
|
||||||
|
color: $color-foreground-brighter;
|
||||||
|
transition: color $animation-speed-default;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
&-titles {
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
padding-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-title {
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-items {
|
||||||
|
$height: 2rem;
|
||||||
|
height: $height;
|
||||||
|
line-height: $height;
|
||||||
|
position: relative;
|
||||||
|
&:nth-of-type(2n + 1)
|
||||||
|
|
||||||
|
{
|
||||||
|
&:before
|
||||||
|
|
||||||
|
{
|
||||||
|
background-color: $color-background-bright;
|
||||||
|
content: '';
|
||||||
|
height: 100%;
|
||||||
|
left: 0;
|
||||||
|
margin-left: -100vw;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
width: 200vw;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
font-weight: $font-weight-semilight;
|
||||||
|
&--hover
|
||||||
|
|
||||||
|
{
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
&-items:hover &-item--hover {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-link {
|
||||||
|
color: $color-secondary;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color $animation-speed-default;
|
||||||
|
&:hover
|
||||||
|
|
||||||
|
{
|
||||||
|
color: $color-secondary-dark;
|
||||||
|
transition: color $animation-speed-default;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.1 KiB |
@@ -4,6 +4,11 @@
|
|||||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
|
||||||
<PackageReference Include="xunit" Version="2.2.0" />
|
<PackageReference Include="xunit" Version="2.2.0" />
|
||||||
|
|||||||
@@ -4,6 +4,11 @@
|
|||||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
|
||||||
<PackageReference Include="xunit" Version="2.2.0" />
|
<PackageReference Include="xunit" Version="2.2.0" />
|
||||||
|
|||||||
@@ -4,6 +4,10 @@
|
|||||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.0" />
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user